From ac0fe38af2e504b96816dfe8794d3a77e0a5960e Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 26 Apr 2023 12:26:33 +0200 Subject: [PATCH] [Rust]: Implement Private and Public keys (#3107) --- codegen-v2/src/codegen/mod.rs | 5 + rust/Cargo.lock | 396 +++++---------------- rust/coverage.stats | 2 +- rust/tw_keypair/src/secp256k1/canonical.rs | 137 +++++++ rust/tw_keypair/src/secp256k1/keypair.rs | 68 ++++ rust/tw_keypair/src/secp256k1/mod.rs | 153 ++++++++ rust/tw_keypair/src/secp256k1/private.rs | 88 +++++ rust/tw_keypair/src/secp256k1/public.rs | 82 +++++ rust/tw_keypair/src/secp256k1/signature.rs | 129 +++++++ 9 files changed, 757 insertions(+), 303 deletions(-) create mode 100644 rust/tw_keypair/src/secp256k1/canonical.rs create mode 100644 rust/tw_keypair/src/secp256k1/keypair.rs create mode 100644 rust/tw_keypair/src/secp256k1/mod.rs create mode 100644 rust/tw_keypair/src/secp256k1/private.rs create mode 100644 rust/tw_keypair/src/secp256k1/public.rs create mode 100644 rust/tw_keypair/src/secp256k1/signature.rs diff --git a/codegen-v2/src/codegen/mod.rs b/codegen-v2/src/codegen/mod.rs index 52487305973..872e6bd906f 100644 --- a/codegen-v2/src/codegen/mod.rs +++ b/codegen-v2/src/codegen/mod.rs @@ -4,4 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +<<<<<<<< HEAD:codegen-v2/src/codegen/mod.rs pub mod swift; +======== +pub mod privkey; +pub mod pubkey; +>>>>>>>> cb5be683 ([Rust]: Implement Private and Public keys (#3107)):rust/tw_keypair/src/ffi/mod.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6057003937b..f5d1a836521 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -26,28 +26,17 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -[[package]] -name = "arbitrary" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "ark-ff" -version = "0.4.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" dependencies = [ "ark-ff-asm", "ark-ff-macros", "ark-serialize", "ark-std", "derivative", - "digest 0.10.6", - "itertools", "num-bigint", "num-traits", "paste", @@ -57,9 +46,9 @@ dependencies = [ [[package]] name = "ark-ff-asm" -version = "0.4.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", "syn 1.0.107", @@ -67,33 +56,31 @@ dependencies = [ [[package]] name = "ark-ff-macros" -version = "0.4.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ "num-bigint", "num-traits", - "proc-macro2", "quote", "syn 1.0.107", ] [[package]] name = "ark-serialize" -version = "0.4.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" dependencies = [ "ark-std", - "digest 0.10.6", - "num-bigint", + "digest 0.9.0", ] [[package]] name = "ark-std" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", "rand", @@ -138,12 +125,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - [[package]] name = "bigdecimal" version = "0.3.0" @@ -156,34 +137,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitcoin" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36f4c848f6bd9ff208128f08751135846cc23ae57d66ab10a22efff1c675f3c" -dependencies = [ - "bech32", - "bitcoin-private", - "bitcoin_hashes", - "hex_lit", - "secp256k1", -] - -[[package]] -name = "bitcoin-private" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" - -[[package]] -name = "bitcoin_hashes" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" -dependencies = [ - "bitcoin-private", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -203,13 +156,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.9.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "crypto-mac", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.6", ] [[package]] @@ -254,12 +205,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -298,47 +243,35 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.1" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", - "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" +name = "crypto-bigint" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" dependencies = [ "generic-array", + "rand_core", "subtle", + "zeroize", ] [[package]] -name = "curve25519-dalek" -version = "3.2.0" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", + "generic-array", + "typenum", ] [[package]] @@ -368,17 +301,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "derive_arbitrary" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - [[package]] name = "digest" version = "0.9.0" @@ -409,16 +331,10 @@ dependencies = [ "der", "digest 0.10.6", "elliptic-curve", - "rfc6979", + "rfc6979 0.4.0", "signature", ] -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - [[package]] name = "elliptic-curve" version = "0.13.4" @@ -426,14 +342,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" dependencies = [ "base16ct", - "crypto-bigint", + "crypto-bigint 0.5.1", "digest 0.10.6", "ff", "generic-array", "group", "hkdf", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -458,7 +374,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -475,25 +391,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -513,7 +418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -532,12 +437,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex_lit" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" - [[package]] name = "hkdf" version = "0.12.3" @@ -565,15 +464,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.6" @@ -599,7 +489,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.6", + "sha2", "signature", ] @@ -706,24 +596,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.6", -] - [[package]] name = "paste" version = "1.0.11" @@ -742,6 +614,16 @@ dependencies = [ "nom", ] +[[package]] +name = "pest" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +dependencies = [ + "thiserror", + "ucd-trie", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -758,15 +640,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "primeorder" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro2" version = "1.0.56" @@ -808,7 +681,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -818,16 +691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -836,7 +700,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom", ] [[package]] @@ -878,27 +742,23 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ + "crypto-bigint 0.4.9", "hmac", - "subtle", + "zeroize", ] [[package]] -name = "ring" -version = "0.16.20" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", + "hmac", + "subtle", ] [[package]] @@ -912,9 +772,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ "semver", ] @@ -940,36 +800,28 @@ dependencies = [ ] [[package]] -name = "secp256k1" -version = "0.27.0" +name = "semver" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "bitcoin_hashes", - "rand", - "secp256k1-sys", + "semver-parser", ] [[package]] -name = "secp256k1-sys" -version = "0.8.1" +name = "semver-parser" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ - "cc", + "pest", ] -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" - [[package]] name = "serde" -version = "1.0.164" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -985,9 +837,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", @@ -1016,19 +868,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.6" @@ -1057,15 +896,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.6", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spki" version = "0.7.1" @@ -1078,18 +911,18 @@ dependencies = [ [[package]] name = "starknet-crypto" -version = "0.5.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" +checksum = "8802a516a2556b2ddb9630898d2c8387d928a3e603799b8b2a7dc4018b852c8f" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hex", "hmac", "num-bigint", "num-integer", "num-traits", - "rfc6979", - "sha2 0.10.6", + "rfc6979 0.3.1", + "sha2", "starknet-crypto-codegen", "starknet-curve", "starknet-ff", @@ -1098,34 +931,34 @@ dependencies = [ [[package]] name = "starknet-crypto-codegen" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6dc88f1f470d9de1001ffbb90d2344c9dd1a615f5467daf0574e2975dfd9ebd" +checksum = "bff08f74f3ac785ac34ac05c68c5bd4df280107ab35df69dbcbde35183d89eba" dependencies = [ "starknet-curve", "starknet-ff", - "syn 2.0.15", + "syn 1.0.107", ] [[package]] name = "starknet-curve" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" +checksum = "fe0dbde7ef14d54c2117bc6d2efb68c2383005f1cd749b277c11df874d07b7af" dependencies = [ "starknet-ff", ] [[package]] name = "starknet-ff" -version = "0.3.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdf692e13247ec111718e219caaa44ea1a687e9c36bf6083e1cd1b98374a2ad" +checksum = "78d484109da192f3a8cd58f674861c2d5e4b3e1765a466362c6f350ef213dfd1" dependencies = [ "ark-ff", "bigdecimal", - "crypto-bigint", - "getrandom 0.2.9", + "crypto-bigint 0.4.9", + "getrandom", "hex", "serde", ] @@ -1214,25 +1047,10 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "tw_bitcoin" -version = "0.1.0" -dependencies = [ - "bitcoin", - "secp256k1", - "serde", - "serde_json", - "tw_encoding", - "tw_memory", - "tw_misc", - "tw_proto", -] - [[package]] name = "tw_encoding" version = "0.1.0" dependencies = [ - "arbitrary", "bs58", "data-encoding", "hex", @@ -1244,6 +1062,7 @@ name = "tw_hash" version = "0.1.0" dependencies = [ "blake-hash", + "blake2", "blake2b-ref", "digest 0.10.6", "groestl", @@ -1252,7 +1071,7 @@ dependencies = [ "serde", "serde_json", "sha1", - "sha2 0.10.6", + "sha2", "sha3", "tw_encoding", "tw_memory", @@ -1263,20 +1082,10 @@ dependencies = [ name = "tw_keypair" version = "0.1.0" dependencies = [ - "arbitrary", - "blake2", - "curve25519-dalek", - "der", - "digest 0.9.0", - "ecdsa", "k256", "lazy_static", - "p256", - "rfc6979", - "ring", "serde", "serde_json", - "sha2 0.9.9", "starknet-crypto", "starknet-ff", "tw_encoding", @@ -1323,6 +1132,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + [[package]] name = "unicode-ident" version = "1.0.6" @@ -1341,12 +1156,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "vec_map" version = "0.8.2" @@ -1363,7 +1172,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "wallet-core-rs" version = "0.1.0" dependencies = [ - "tw_bitcoin", "tw_encoding", "tw_hash", "tw_keypair", @@ -1372,12 +1180,6 @@ dependencies = [ "tw_proto", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1438,16 +1240,6 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" -[[package]] -name = "web-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/rust/coverage.stats b/rust/coverage.stats index 7d7ab43dc7c..7aebde8602a 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -92.0 \ No newline at end of file +94.9 \ No newline at end of file diff --git a/rust/tw_keypair/src/secp256k1/canonical.rs b/rust/tw_keypair/src/secp256k1/canonical.rs new file mode 100644 index 00000000000..e2e9b177340 --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/canonical.rs @@ -0,0 +1,137 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +//! This module is a Proof Of Concept that proves the possibility to implement the following in Rust: +//! https://github.com/trustwallet/wallet-core/blob/d9e35ec485b1366dd10509192d02d9dbb6877ab3/src/PrivateKey.cpp#L253-L282 +//! +//! # Warning +//! +//! **Not production ready** + +use crate::secp256k1::{PrivateKey, Signature}; +use crate::Error; +use k256::ecdsa::hazmat::{bits2field, DigestPrimitive, SignPrimitive}; +use k256::elliptic_curve::generic_array::ArrayLength; +use k256::elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use k256::elliptic_curve::{Curve, FieldBytesEncoding, PrimeField}; +use k256::sha2::digest::crypto_common::BlockSizeUser; +use k256::sha2::digest::{FixedOutput, FixedOutputReset}; +use k256::sha2::Digest; +use k256::{Scalar, Secp256k1}; +use rfc6979::{ByteArray, HmacDrbg}; +use tw_hash::H256; + +/// Implements https://github.com/trustwallet/wallet-core/blob/d9e35ec485b1366dd10509192d02d9dbb6877ab3/src/PrivateKey.cpp#L253-L282 +pub(crate) fn sign_with_canonical( + private: &PrivateKey, + hash_to_sign: H256, + mut is_canonical: F, +) -> Result +where + for<'a> F: FnMut(&'a Signature) -> bool, +{ + let priv_scalar = private.secret.as_nonzero_scalar(); + + let nonce = + bits2field::(hash_to_sign.as_slice()).map_err(|_| Error::InvalidSignMessage)?; + let entropy_input = &priv_scalar.to_repr(); + let n = &Secp256k1::ORDER.encode_field_bytes(); + let additional_data = &[]; + + let mut hmac_drbg = HmacDrbg::<::Digest>::new( + entropy_input, + &nonce, + additional_data, + ); + + for _ in 0..10000 { + // The `k` number is different on each iteration due to the `hmac_drbg` mutation. + let k = generate_k::<::Digest, _>(&mut hmac_drbg, n); + let k_scalar = Scalar::from_repr(k).unwrap(); + + let (sig, r) = priv_scalar + .try_sign_prehashed(k_scalar, &nonce) + .map_err(|_| Error::SigningError)?; + let r = r.ok_or_else(|| Error::SigningError)?; + + let signature = Signature::new(sig, r.to_byte()); + if is_canonical(&signature) { + return Ok(signature); + } + } + + Err(Error::SigningError) +} + +fn ct_eq>(a: &ByteArray, b: &ByteArray) -> Choice { + let mut ret = Choice::from(1); + + for (a, b) in a.iter().zip(b.iter()) { + ret.conditional_assign(&Choice::from(0), !a.ct_eq(b)); + } + + ret +} + +pub(crate) fn ct_lt>(a: &ByteArray, b: &ByteArray) -> Choice { + let mut borrow = 0; + + // Perform subtraction with borrow a byte-at-a-time, interpreting a + // no-borrow condition as the less-than case + for (&a, &b) in a.iter().zip(b.iter()).rev() { + let c = (b as u16).wrapping_add(borrow >> (u8::BITS - 1)); + borrow = (a as u16).wrapping_sub(c) >> u8::BITS as u8; + } + + !borrow.ct_eq(&0) +} + +pub(crate) fn generate_k(hmac_drbg: &mut HmacDrbg, n: &ByteArray) -> ByteArray +where + D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset, + N: ArrayLength, +{ + loop { + let mut k = ByteArray::::default(); + hmac_drbg.fill_bytes(&mut k); + + let k_is_zero = ct_eq(&k, &ByteArray::default()); + if (!k_is_zero & ct_lt(&k, n)).into() { + return k; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_hash::H520; + use tw_misc::traits::ToBytesVec; + + fn is_unsigned(byte: u8) -> bool { + byte & 0x80 == 0 + } + + fn fio_is_canonical(sig: &Signature) -> bool { + let sig = sig.to_vec(); + is_unsigned(sig[0]) + && !(sig[0] == 0 && is_unsigned(sig[1])) + && is_unsigned(sig[32]) + && !(sig[32] == 0 && is_unsigned(sig[33])) + } + + #[test] + fn test_sign_canonical() { + let private = + PrivateKey::from("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"); + let hash_to_sign = + H256::from("71b7098e8150cde90f3ec00280815d3069f81c7cdb6d83bbe2b897b1afbe7cd6"); + + let actual = sign_with_canonical(&private, hash_to_sign, fio_is_canonical).unwrap(); + let expected = H520::from("42ceeaa4a3d0ea0429ab09e4d969abd812c65ad4efef9e95e3a19cc3c41be3770ad0222dac6aa1b350cf9273fa922801d11b6142cb0fe639e2fe3fd988e5aec400"); + assert_eq!(actual.to_bytes(), expected); + } +} diff --git a/rust/tw_keypair/src/secp256k1/keypair.rs b/rust/tw_keypair/src/secp256k1/keypair.rs new file mode 100644 index 00000000000..403b2deb80f --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/keypair.rs @@ -0,0 +1,68 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::secp256k1::private::PrivateKey; +use crate::secp256k1::public::PublicKey; +use crate::secp256k1::{Signature, VerifySignature}; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::Error; +use tw_encoding::hex; +use tw_hash::H256; + +/// Represents a pair of `secp256k1` private and public keys. +pub struct KeyPair { + private: PrivateKey, + public: PublicKey, +} + +impl KeyPairTrait for KeyPair { + type Private = PrivateKey; + type Public = PublicKey; + + fn public(&self) -> &Self::Public { + &self.public + } + + fn private(&self) -> &Self::Private { + &self.private + } +} + +impl SigningKeyTrait for KeyPair { + type SigningHash = H256; + type Signature = Signature; + + fn sign(&self, hash: Self::SigningHash) -> Result { + self.private.sign(hash) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningHash = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, signature: Self::VerifySignature, hash: Self::SigningHash) -> bool { + self.public.verify(signature, hash) + } +} + +impl<'a> TryFrom<&'a [u8]> for KeyPair { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + let private = PrivateKey::try_from(bytes)?; + let public = private.public(); + Ok(KeyPair { private, public }) + } +} + +impl From<&'static str> for KeyPair { + fn from(hex: &'static str) -> Self { + // There is no need to zeroize the `bytes` as it has a static lifetime (so most likely included in the binary). + let bytes = hex::decode(hex).expect("Expected a valid Secret Key hex"); + KeyPair::try_from(bytes.as_slice()).expect("Expected a valid Secret Key") + } +} diff --git a/rust/tw_keypair/src/secp256k1/mod.rs b/rust/tw_keypair/src/secp256k1/mod.rs new file mode 100644 index 00000000000..36304fa9295 --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/mod.rs @@ -0,0 +1,153 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +// mod canonical; +mod keypair; +mod private; +mod public; +mod signature; + +pub use keypair::KeyPair; +pub use private::PrivateKey; +pub use public::PublicKey; +pub use signature::{Signature, VerifySignature}; + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::sha3::keccak256; + use tw_hash::{H256, H264, H520}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_key_pair() { + let secret = + hex::decode("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5") + .unwrap(); + let key_pair = KeyPair::try_from(secret.as_slice()).unwrap(); + assert_eq!(key_pair.private().to_zeroizing_vec().as_slice(), secret); + assert_eq!( + key_pair.public().compressed(), + H264::from("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1") + ); + } + + #[test] + fn test_key_pair_sign() { + let key_pair = + KeyPair::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + let signature = key_pair.sign(hash_to_sign).unwrap(); + + let expected = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + assert_eq!(signature.to_bytes(), expected); + + let verify_signature = VerifySignature::from(signature); + assert!(key_pair.verify(verify_signature, hash_to_sign)); + } + + #[test] + fn test_private_key_from() { + let hex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let expected = hex::decode(hex).unwrap(); + + // Test `From<&'static str>`. + let private = PrivateKey::from(hex); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + + // Test `From<&'a [u8]>`. + let private = PrivateKey::try_from(expected.as_slice()).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), expected); + } + + #[test] + fn test_private_key_sign_verify() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let private = PrivateKey::from(secret); + let public = private.public(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + let signature = private.sign(hash_to_sign).unwrap(); + + let expected = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + assert_eq!(signature.to_bytes(), expected); + + let verify_signature = VerifySignature::from(signature); + assert!(public.verify(verify_signature, hash_to_sign)); + } + + #[test] + fn test_public_key_from() { + let compressed = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + let uncompressed = "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"; + let expected_compressed = H264::from(compressed); + let expected_uncompressed = H520::from(uncompressed); + + // From extended public key. + let public = PublicKey::from(uncompressed); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + + // From compressed public key. + let public = PublicKey::from(compressed); + assert_eq!(public.to_vec(), expected_compressed.into_vec()); + assert_eq!(public.compressed(), expected_compressed); + assert_eq!(public.uncompressed(), expected_uncompressed); + } + + #[test] + fn test_verify_invalid() { + let secret = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + let private = PrivateKey::from(secret); + + let signature_bytes = H520::from("375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f0801"); + let verify_sig = VerifySignature::try_from(signature_bytes.as_slice()).unwrap(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + + assert!(!private.public().verify(verify_sig, hash_to_sign)); + } + + #[test] + fn test_signature() { + let sign_bytes = H520::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a00"); + let sign = Signature::from_bytes(sign_bytes.as_slice()).unwrap(); + assert_eq!( + sign.r(), + H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47") + ); + assert_eq!( + sign.s(), + H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a") + ); + assert_eq!(sign.v(), 0); + assert_eq!(sign.to_bytes(), sign_bytes); + } + + #[test] + fn test_signature_from_invalid_bytes() { + Signature::from_bytes(b"123").unwrap_err(); + } + + #[test] + fn test_shared_key_hash() { + let private = + PrivateKey::from("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); + let public = + PublicKey::from("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"); + let actual = private.shared_key_hash(&public); + let expected = + H256::from("ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); + assert_eq!(actual, expected); + } +} diff --git a/rust/tw_keypair/src/secp256k1/private.rs b/rust/tw_keypair/src/secp256k1/private.rs new file mode 100644 index 00000000000..80789f17ff3 --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/private.rs @@ -0,0 +1,88 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::secp256k1::public::PublicKey; +use crate::secp256k1::signature::Signature; +use crate::traits::SigningKeyTrait; +use crate::Error; +use k256::ecdsa::{SigningKey, VerifyingKey}; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use k256::{AffinePoint, ProjectivePoint}; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroizing; + +/// Represents a `secp256k1` private key. +pub struct PrivateKey { + pub(crate) secret: SigningKey, +} + +impl PrivateKey { + /// Returns an associated `secp256k1` public key. + pub fn public(&self) -> PublicKey { + PublicKey::new(*self.secret.verifying_key()) + } + + /// Computes an EC Diffie-Hellman secret in constant time. + /// The method is ported from [TW::PrivateKey::getSharedKey](https://github.com/trustwallet/wallet-core/blob/830b1c5baaf90692196163999e4ee2063c5f4e49/src/PrivateKey.cpp#L175-L191). + pub fn shared_key_hash(&self, pubkey: &PublicKey) -> H256 { + let shared_secret = diffie_hellman(&self.secret, &pubkey.public); + + // Get a compressed shared secret (33 bytes with a tag in front). + let compress = true; + let shared_secret_compressed = shared_secret.to_encoded_point(compress); + + let shared_secret_hash = tw_hash::sha2::sha256(shared_secret_compressed.as_bytes()); + H256::try_from(shared_secret_hash.as_slice()).expect("Expected 32 byte array sha256 hash") + } +} + +/// This method is inspired by [elliptic_curve::ecdh::diffie_hellman](https://github.com/RustCrypto/traits/blob/f0dbe44fea56d4c17e625ababacb580fec842137/elliptic-curve/src/ecdh.rs#L60-L70) +fn diffie_hellman(private: &SigningKey, public: &VerifyingKey) -> AffinePoint { + let public_point = ProjectivePoint::from(*public.as_affine()); + let secret_scalar = private.as_nonzero_scalar().as_ref(); + // Multiply the secret and public to get a shared secret affine point (x, y). + (public_point * secret_scalar).to_affine() +} + +impl SigningKeyTrait for PrivateKey { + type SigningHash = H256; + type Signature = Signature; + + fn sign(&self, hash: Self::SigningHash) -> Result { + let (signature, recovery_id) = self + .secret + .sign_prehash_recoverable(&hash) + .map_err(|_| Error::SigningError)?; + Ok(Signature::new(signature, recovery_id.to_byte())) + } +} + +/// Implement `str` -> `PrivateKey` conversion for test purposes. +impl From<&'static str> for PrivateKey { + fn from(hex: &'static str) -> Self { + // There is no need to zeroize the `data` as it has a static lifetime (so most likely included in the binary). + let data = hex::decode(hex).expect("Expected a valid Secret Key hex"); + PrivateKey::try_from(data.as_slice()).expect("Expected a valid Secret Key") + } +} + +impl<'a> TryFrom<&'a [u8]> for PrivateKey { + type Error = Error; + + fn try_from(data: &'a [u8]) -> Result { + let secret = SigningKey::from_slice(data).map_err(|_| Error::InvalidSecretKey)?; + Ok(PrivateKey { secret }) + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.secret.to_bytes()); + Zeroizing::new(secret.as_slice().to_vec()) + } +} diff --git a/rust/tw_keypair/src/secp256k1/public.rs b/rust/tw_keypair/src/secp256k1/public.rs new file mode 100644 index 00000000000..0b18b9360a6 --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/public.rs @@ -0,0 +1,82 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::secp256k1::signature::VerifySignature; +use crate::traits::VerifyingKeyTrait; +use crate::Error; +use k256::ecdsa::signature::hazmat::PrehashVerifier; +use k256::ecdsa::VerifyingKey; +use tw_encoding::hex; +use tw_hash::{H256, H264, H520}; +use tw_misc::traits::ToBytesVec; + +/// Represents a `secp256k1` public key. +pub struct PublicKey { + pub(crate) public: VerifyingKey, +} + +/// cbindgen:ignore +impl PublicKey { + /// The number of bytes in a compressed public key. + pub const COMPRESSED: usize = H264::len(); + /// The number of bytes in an uncompressed public key. + pub const UNCOMPRESSED: usize = H520::len(); + + /// Creates a public key from the given [`VerifyingKey`]. + pub(crate) fn new(public: VerifyingKey) -> PublicKey { + PublicKey { public } + } + + /// Returns the raw data of the compressed public key (33 bytes). + pub fn compressed(&self) -> H264 { + let compressed = true; + H264::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 33 byte array Public Key") + } + + /// Returns the raw data of the uncompressed public key (65 bytes). + pub fn uncompressed(&self) -> H520 { + let compressed = false; + H520::try_from(self.public.to_encoded_point(compressed).as_bytes()) + .expect("Expected 65 byte array Public Key") + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningHash = H256; + type VerifySignature = VerifySignature; + + fn verify(&self, sign: Self::VerifySignature, hash: Self::SigningHash) -> bool { + self.public.verify_prehash(&hash, &sign.signature).is_ok() + } +} + +impl From<&'static str> for PublicKey { + fn from(hex: &'static str) -> Self { + // Expected either `H264` or `H520`. + let data = hex::decode(hex).expect("Expected a valid Public Key hex"); + PublicKey::try_from(data.as_slice()).expect("Expected a valid Public Key") + } +} + +impl<'a> TryFrom<&'a [u8]> for PublicKey { + type Error = Error; + + /// Expected either `H264` or `H520` slice. + fn try_from(data: &'a [u8]) -> Result { + Ok(PublicKey { + public: VerifyingKey::from_sec1_bytes(data).map_err(|_| Error::InvalidPublicKey)?, + }) + } +} + +/// Return the compressed bytes representation by default. +/// Consider using [`PublicKey::compressed`] or [`PublicKey::uncompressed`] instead. +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.compressed().to_vec() + } +} diff --git a/rust/tw_keypair/src/secp256k1/signature.rs b/rust/tw_keypair/src/secp256k1/signature.rs new file mode 100644 index 00000000000..bba24d871ff --- /dev/null +++ b/rust/tw_keypair/src/secp256k1/signature.rs @@ -0,0 +1,129 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::Error; +use k256::FieldBytes; +use std::ops::{Range, RangeInclusive}; +use tw_hash::{H256, H520}; +use tw_misc::traits::ToBytesVec; + +/// cbindgen:ignore +const R_RANGE: Range = 0..32; +/// cbindgen:ignore +const S_RANGE: Range = 32..64; +/// cbindgen:ignore +const RECOVERY_LAST: usize = Signature::len() - 1; +/// Expected signature with or without recovery byte in the end of the slice. +/// cbindgen:ignore +const VERIFY_SIGNATURE_LEN_RANGE: RangeInclusive = 64..=65; + +/// Represents an ECDSA signature. +#[derive(Debug, PartialEq)] +pub struct Signature { + signature: k256::ecdsa::Signature, + v: u8, +} + +/// cbindgen:ignore +impl Signature { + /// The number of bytes for a serialized signature representation. + pub const LEN: usize = 65; + + /// Creates a `secp256k1` recoverable signature from the given [`k256::ecdsa::Signature`] + /// and the `v` recovery byte. + pub(crate) fn new(signature: k256::ecdsa::Signature, v: u8) -> Signature { + Signature { signature, v } + } + + /// Returns the number of bytes for a serialized signature representation. + pub const fn len() -> usize { + Self::LEN + } + + /// Returns an r-coordinate as 32 byte array. + pub fn r(&self) -> H256 { + let (r, _s) = self.signature.split_bytes(); + H256::try_from(r.as_slice()).expect("Expected 'r' 32 byte length array") + } + + /// Returns an s-value as 32 byte array. + pub fn s(&self) -> H256 { + let (_, s) = self.signature.split_bytes(); + H256::try_from(s.as_slice()).expect("Expected 's' 32 byte length array") + } + + /// Returns a recovery ID. + pub fn v(&self) -> u8 { + self.v + } + + /// Tries to create a Signature from the serialized representation. + pub fn from_bytes(sig: &[u8]) -> Result { + if sig.len() != Signature::len() { + return Err(Error::InvalidSignature); + } + + Ok(Signature { + signature: Self::signature_from_slices(&sig[R_RANGE], &sig[S_RANGE])?, + v: sig[RECOVERY_LAST], + }) + } + + /// Returns a standard binary signature representation: + /// RSV, where R - 32 byte array, S - 32 byte array, V - 1 byte. + pub fn to_bytes(&self) -> H520 { + let (r, s) = self.signature.split_bytes(); + + let mut dest = H520::default(); + dest[R_RANGE].copy_from_slice(r.as_slice()); + dest[S_RANGE].copy_from_slice(s.as_slice()); + dest[RECOVERY_LAST] = self.v; + dest + } + + /// # Panic + /// + /// `r` and `s` must be 32 byte arrays, otherwise the function panics. + fn signature_from_slices(r: &[u8], s: &[u8]) -> Result { + let r = FieldBytes::clone_from_slice(r); + let s = FieldBytes::clone_from_slice(s); + + k256::ecdsa::Signature::from_scalars(r, s).map_err(|_| Error::InvalidSignature) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + self.to_bytes().to_vec() + } +} + +/// To verify the signature, it's enough to check `r` and `s` parts without the recovery ID. +pub struct VerifySignature { + pub signature: k256::ecdsa::Signature, +} + +impl<'a> TryFrom<&'a [u8]> for VerifySignature { + type Error = Error; + + fn try_from(sig: &'a [u8]) -> Result { + if !VERIFY_SIGNATURE_LEN_RANGE.contains(&sig.len()) { + return Err(Error::InvalidSignature); + } + + Ok(VerifySignature { + signature: Signature::signature_from_slices(&sig[R_RANGE], &sig[S_RANGE])?, + }) + } +} + +impl From for VerifySignature { + fn from(sig: Signature) -> Self { + VerifySignature { + signature: sig.signature, + } + } +}