diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index d255e25bb7b..9fc9021d422 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -40,6 +40,9 @@ jobs: run: | tools/install-rust-dependencies dev + - name: Install emsdk + run: tools/install-wasm-dependencies + - name: Check code formatting run: | cargo fmt --check @@ -55,6 +58,14 @@ jobs: cargo llvm-cov nextest --profile ci --no-fail-fast --lcov --output-path coverage.info working-directory: rust + - name: Run tests in WASM + run: | + source ../emsdk/emsdk_env.sh + cargo test --target wasm32-unknown-emscripten --release + env: + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node + working-directory: rust + - name: Rust Test Report uses: dorny/test-reporter@v1 if: success() || failure() diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3199454c20e..f5d1a836521 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -28,17 +28,15 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[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", @@ -48,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", @@ -58,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", @@ -107,6 +103,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bcs" version = "0.1.4" @@ -117,17 +125,11 @@ 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.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" dependencies = [ "num-bigint", "num-integer", @@ -135,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" @@ -231,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" @@ -258,6 +226,12 @@ dependencies = [ "vec_map", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -269,15 +243,27 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "subtle", "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -296,9 +282,13 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "der" -version = "0.7.6" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] name = "derivative" @@ -327,15 +317,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", + "const-oid", "crypto-common", "subtle", ] [[package]] -name = "either" -version = "1.8.1" +name = "ecdsa" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" +dependencies = [ + "der", + "digest 0.10.6", + "elliptic-curve", + "rfc6979 0.4.0", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" +dependencies = [ + "base16ct", + "crypto-bigint 0.5.1", + "digest 0.10.6", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] [[package]] name = "env_logger" @@ -350,6 +368,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -358,13 +386,14 @@ checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "js-sys", @@ -382,6 +411,17 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -398,10 +438,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hex_lit" -version = "0.1.1" +name = "hkdf" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] [[package]] name = "hmac" @@ -421,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" @@ -445,6 +479,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.3" @@ -454,6 +502,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.141" @@ -560,6 +614,26 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -568,9 +642,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -592,9 +666,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -666,6 +740,17 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -687,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", ] @@ -701,36 +786,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] -name = "secp256k1" -version = "0.27.0" +name = "sec1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" dependencies = [ - "bitcoin_hashes", - "rand", - "secp256k1-sys", + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] -name = "secp256k1-sys" -version = "0.8.1" +name = "semver" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "cc", + "semver-parser", ] [[package]] -name = "semver" -version = "1.0.17" +name = "semver-parser" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] [[package]] name = "serde" -version = "1.0.163" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -746,13 +837,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.15", ] [[package]] @@ -798,19 +889,39 @@ dependencies = [ "keccak", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.6", + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der", +] + [[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", + "rfc6979 0.3.1", "sha2", "starknet-crypto-codegen", "starknet-curve", @@ -820,33 +931,33 @@ 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.18", + "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", + "crypto-bigint 0.4.9", "getrandom", "hex", "serde", @@ -877,9 +988,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -936,20 +1047,6 @@ 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" @@ -969,22 +1066,33 @@ dependencies = [ "blake2b-ref", "digest 0.10.6", "groestl", - "hex", "hmac", "ripemd", + "serde", + "serde_json", "sha1", "sha2", "sha3", + "tw_encoding", "tw_memory", + "zeroize", ] [[package]] name = "tw_keypair" version = "0.1.0" dependencies = [ - "der", + "k256", + "lazy_static", + "serde", + "serde_json", + "starknet-crypto", + "starknet-ff", "tw_encoding", + "tw_hash", "tw_memory", + "tw_misc", + "zeroize", ] [[package]] @@ -1018,23 +1126,18 @@ dependencies = [ "tw_memory", ] -[[package]] -name = "tw_starknet" -version = "0.1.0" -dependencies = [ - "hex", - "starknet-crypto", - "starknet-ff", - "tw_encoding", - "tw_memory", -] - [[package]] name = "typenum" 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" @@ -1069,14 +1172,12 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "wallet-core-rs" version = "0.1.0" dependencies = [ - "tw_bitcoin", "tw_encoding", "tw_hash", "tw_keypair", "tw_memory", "tw_move_parser", "tw_proto", - "tw_starknet", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2a3f3dd5dfc..79d116838fb 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,6 +8,5 @@ members = [ "tw_misc", "tw_move_parser", "tw_proto", - "tw_starknet", "wallet_core_rs", ] diff --git a/rust/coverage.stats b/rust/coverage.stats index 2d567ce4c07..7aebde8602a 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -86.4 \ No newline at end of file +94.9 \ No newline at end of file diff --git a/rust/tw_encoding/src/base32.rs b/rust/tw_encoding/src/base32.rs index 9831437144e..d43c10acaf5 100644 --- a/rust/tw_encoding/src/base32.rs +++ b/rust/tw_encoding/src/base32.rs @@ -10,6 +10,7 @@ use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; +/// cbindgen:ignore const ALPHABET_RFC4648: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; type EncodingMap = HashMap; diff --git a/rust/tw_encoding/src/ffi.rs b/rust/tw_encoding/src/ffi.rs index 1acde6ebcc7..c406f574e3c 100644 --- a/rust/tw_encoding/src/ffi.rs +++ b/rust/tw_encoding/src/ffi.rs @@ -104,7 +104,7 @@ pub unsafe extern "C" fn decode_base32( }; base32::decode(input, alphabet, padding) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -141,7 +141,7 @@ pub unsafe extern "C" fn decode_base58( }; base58::decode(input, alphabet.into()) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -172,7 +172,7 @@ pub unsafe extern "C" fn decode_base64(data: *const c_char, is_url: bool) -> CBy Err(_) => return CByteArrayResult::error(CEncodingCode::InvalidInput), }; base64::decode(str_slice, is_url) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } @@ -191,7 +191,7 @@ pub unsafe extern "C" fn decode_hex(data: *const c_char) -> CByteArrayResult { }; hex::decode(hex_string) - .map(CByteArray::new) + .map(CByteArray::from) .map_err(CEncodingCode::from) .into() } diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index ec0b1d60eaf..b9d52db5e3b 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -11,8 +11,8 @@ pub fn decode(data: &str) -> Result, FromHexError> { hex::decode(hex_string) } -pub fn encode(data: &[u8], prefixed: bool) -> String { - let encoded = hex::encode(data); +pub fn encode>(data: T, prefixed: bool) -> String { + let encoded = hex::encode(data.as_ref()); if prefixed { return format!("0x{encoded}"); } diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index a1f57154a06..a4759077ca6 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -3,6 +3,9 @@ name = "tw_hash" version = "0.1.0" edition = "2021" +[features] +default = ["serde"] + [dependencies] blake-hash = "0.4.1" blake2 = "0.10.6" @@ -11,10 +14,13 @@ digest = "0.10.6" groestl = "0.10.1" hmac = "0.12.1" ripemd = "0.1.3" +serde = { version = "1.0.159", features = ["derive"], optional = true } sha1 = "0.10.5" sha2 = "0.10.6" sha3 = "0.10.6" +tw_encoding = { path = "../tw_encoding" } tw_memory = { path = "../tw_memory" } +zeroize = "1.6.0" [dev-dependencies] -hex = "0.4.3" +serde_json = "1.0.95" diff --git a/rust/tw_hash/src/hash_array.rs b/rust/tw_hash/src/hash_array.rs new file mode 100644 index 00000000000..2313ed8cb9a --- /dev/null +++ b/rust/tw_hash/src/hash_array.rs @@ -0,0 +1,226 @@ +// 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 std::fmt; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; +use tw_encoding::hex; +use zeroize::DefaultIsZeroes; + +pub type H256 = Hash<32>; +pub type H264 = Hash<33>; +pub type H512 = Hash<64>; +pub type H520 = Hash<65>; + +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +pub struct Hash([u8; N]); + +impl DefaultIsZeroes for Hash {} + +impl Hash { + pub const fn new() -> Self { + Hash([0; N]) + } + + pub fn as_slice(&self) -> &[u8] { + &self.0 + } + + pub fn into_vec(self) -> Vec { + self.0.to_vec() + } + + pub const fn take(self) -> [u8; N] { + self.0 + } + + pub const fn len() -> usize { + N + } +} + +/// Implement `str` -> `Hash` conversion for test purposes. +impl From<&'static str> for Hash { + fn from(hex: &'static str) -> Self { + hex.parse().expect("Expected a valid hex-encoded hash") + } +} + +impl From<[u8; N]> for Hash { + fn from(data: [u8; N]) -> Self { + Hash(data) + } +} + +impl<'a, const N: usize> TryFrom<&'a [u8]> for Hash { + type Error = Error; + + fn try_from(data: &'a [u8]) -> Result { + if data.len() != N { + return Err(Error::InvalidHashLength); + } + + let mut dest = Hash::default(); + dest.0.copy_from_slice(data); + Ok(dest) + } +} + +impl FromStr for Hash { + type Err = Error; + + fn from_str(s: &str) -> Result { + let data = hex::decode(s)?; + Hash::try_from(data.as_slice()) + } +} + +impl Default for Hash { + fn default() -> Self { + Hash::new() + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for Hash { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Hash { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let prefixed = false; + write!(f, "{}", hex::encode(self.0, prefixed)) + } +} + +#[cfg(feature = "serde")] +mod impl_serde { + use super::Hash; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de, const N: usize> Deserialize<'de> for Hash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + hex.parse().map_err(|e| Error::custom(format!("{e:?}"))) + } + } + + impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_from_str() { + let actual = H256::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + let expected = [ + 175u8, 238, 252, 167, 77, 154, 50, 92, 241, 214, 182, 145, 29, 97, 166, 92, 50, 175, + 168, 224, 43, 213, 231, 142, 46, 74, 194, 145, 11, 171, 69, 245, + ]; + assert_eq!(actual.0[..], expected[..]); + } + + #[test] + fn test_hash_display() { + let str = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + assert_eq!(H256::from(str).to_string(), str); + } + + #[test] + #[should_panic] + fn test_from_hex_literal_invalid() { + let _ = H256::from("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45x5"); + } + + #[test] + fn test_from_hex_invalid() { + let err = + H256::from_str("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45x5") + .unwrap_err(); + + match err { + Error::FromHexError(_) => (), + other => panic!("Expected 'FromHexError', found: {other:?}"), + } + } + + #[test] + fn test_from_hex_invalid_len() { + let err = H256::from_str("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45") + .unwrap_err(); + + match err { + Error::InvalidHashLength => (), + other => panic!("Expected 'Error::InvalidHashLength', found: {other:?}"), + } + } +} + +/// cbindgen:ignore +#[cfg(all(test, feature = "serde"))] +mod serde_tests { + use super::Hash; + use serde_json::json; + + const BYTES_32: [u8; 32] = [ + 175u8, 238, 252, 167, 77, 154, 50, 92, 241, 214, 182, 145, 29, 97, 166, 92, 50, 175, 168, + 224, 43, 213, 231, 142, 46, 74, 194, 145, 11, 171, 69, 245, + ]; + const HEX_32: &str = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + + #[test] + fn test_hash_deserialize() { + let unprefixed: Hash<32> = serde_json::from_value(json!(HEX_32)).unwrap(); + assert_eq!(unprefixed.0, BYTES_32); + + let prefixed: Hash<32> = serde_json::from_value(json!(HEX_32)).unwrap(); + assert_eq!(prefixed.0, BYTES_32); + } + + #[test] + fn test_hash_deserialize_error() { + serde_json::from_value::>(json!( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45" + )) + .unwrap_err(); + } + + #[test] + fn test_hash_serialize() { + let hash = Hash::<32>::from(HEX_32); + let actual = serde_json::to_value(&hash).unwrap(); + assert_eq!(actual, json!(HEX_32)); + } +} diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index 24a6f5d3121..f8f20e88468 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -14,4 +14,23 @@ pub mod sha1; pub mod sha2; pub mod sha3; +mod hash_array; mod hash_wrapper; + +pub use hash_array::{H256, H264, H512, H520}; + +use tw_encoding::hex::FromHexError; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + FromHexError(FromHexError), + InvalidHashLength, +} + +impl From for Error { + fn from(e: FromHexError) -> Self { + Error::FromHexError(e) + } +} diff --git a/rust/tw_hash/tests/hash_ffi_tests.rs b/rust/tw_hash/tests/hash_ffi_tests.rs index 9fc13561154..6d07050a3f5 100644 --- a/rust/tw_hash/tests/hash_ffi_tests.rs +++ b/rust/tw_hash/tests/hash_ffi_tests.rs @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use tw_encoding::hex; use tw_hash::ffi::{ blake2_b, blake2_b_personal, blake_256, groestl_512, hmac__sha256, keccak256, keccak512, ripemd_160, sha1, sha256, sha3__256, sha3__512, sha512, sha512_256, @@ -15,7 +16,7 @@ type ExternFn = unsafe extern "C" fn(*const u8, usize) -> CByteArray; #[track_caller] pub fn test_hash_helper(hash: ExternFn, input: &[u8], expected: &str) { let decoded = unsafe { hash(input.as_ptr(), input.len()).into_vec() }; - assert_eq!(hex::encode(decoded), expected); + assert_eq!(hex::encode(decoded, false), expected); } #[test] @@ -47,7 +48,7 @@ fn test_blake2b_personal() { .into_vec() }; let expected = "20d9cd024d4fb086aae819a1432dd2466de12947831b75c5a30cf2676095d3b4"; - assert_eq!(hex::encode(actual), expected); + assert_eq!(hex::encode(actual, false), expected); } #[test] @@ -81,7 +82,7 @@ fn test_hmac_sha256() { let actual = unsafe { hmac__sha256(key.as_ptr(), key.len(), data.as_ptr(), data.len()).into_vec() }; let expected = "a7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab".to_string(); - assert_eq!(hex::encode(actual), expected); + assert_eq!(hex::encode(actual, false), expected); } #[test] diff --git a/rust/tw_keypair/Cargo.toml b/rust/tw_keypair/Cargo.toml index 0b7a81b8e7d..3f5a1e4e19d 100644 --- a/rust/tw_keypair/Cargo.toml +++ b/rust/tw_keypair/Cargo.toml @@ -4,6 +4,16 @@ version = "0.1.0" edition = "2021" [dependencies] +lazy_static = "1.4.0" +k256 = { version = "0.13.0", features = ["ecdh", "ecdsa", "schnorr", "std"], default-features = false } +starknet-crypto = "0.4.3" +starknet-ff = "0.3.1" tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } -der = "0.7.3" +tw_misc = { path = "../tw_misc" } +zeroize = "1.6.0" + +[dev-dependencies] +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" diff --git a/rust/tw_keypair/src/ffi/mod.rs b/rust/tw_keypair/src/ffi/mod.rs index 9a641758528..2c317fa3c8c 100644 --- a/rust/tw_keypair/src/ffi/mod.rs +++ b/rust/tw_keypair/src/ffi/mod.rs @@ -4,4 +4,5 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod asn; +pub mod privkey; +pub mod pubkey; diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs new file mode 100644 index 00000000000..f3d458b5547 --- /dev/null +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -0,0 +1,133 @@ +// 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. + +#![allow(clippy::missing_safety_doc)] + +use crate::ffi::pubkey::TWPublicKey; +use crate::tw::{Curve, PrivateKey, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +pub struct TWPrivateKey(pub(crate) PrivateKey); + +impl RawPtrTrait for TWPrivateKey {} + +/// Create a private key with the given block of data. +/// +/// \param input *non-null* byte array. +/// \param input_len the length of the `input` array. +/// \note Should be deleted with \tw_private_key_delete. +/// \return Nullable pointer to Private Key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_create_with_data( + input: *const u8, + input_len: usize, +) -> *mut TWPrivateKey { + let bytes_ref = CByteArrayRef::new(input, input_len); + let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + + PrivateKey::new(bytes) + .map(|private| TWPrivateKey(private).into_ptr()) + // Return null if the private key is invalid. + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given private key. +/// +/// \param key *non-null* pointer to private key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_delete(key: *mut TWPrivateKey) { + // Take the ownership back to rust and drop the owner. + let _ = TWPrivateKey::from_ptr(key); +} + +/// Determines if the given private key is valid or not. +/// +/// \param key *non-null* byte array. +/// \param key_len the length of the `key` array. +/// \param curve Eliptic curve of the private key. +/// \return true if the private key is valid, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_is_valid( + key: *const u8, + key_len: usize, + curve: u32, +) -> bool { + let curve = try_or_false!(Curve::from_raw(curve)); + let priv_key_slice = try_or_false!(CByteArrayRef::new(key, key_len).as_slice()); + PrivateKey::is_valid(priv_key_slice, curve) +} + +/// Signs a digest using ECDSA and given curve. +/// +/// \param key *non-null* pointer to a Private key +/// \param hash *non-null* byte array. +/// \param hash_len the length of the `input` array. +/// \param curve Eliptic curve. +/// \return Signature as a C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_sign( + key: *mut TWPrivateKey, + hash: *const u8, + hash_len: usize, + curve: u32, +) -> CByteArray { + let curve = try_or_else!(Curve::from_raw(curve), CByteArray::default); + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); + let hash_to_sign = try_or_else!( + CByteArrayRef::new(hash, hash_len).as_slice(), + CByteArray::default + ); + + // Return an empty signature if an error occurs. + let sig = private.0.sign(hash_to_sign, curve).unwrap_or_default(); + CByteArray::from(sig) +} + +/// Returns the public key associated with the given pubkeyType and privateKey +/// +/// \param key *non-null* pointer to the private key. +/// \param pubkey_type type of the public key to return. +/// \return *non-null* pointer to the corresponding public key. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_get_public_key_by_type( + key: *mut TWPrivateKey, + pubkey_type: u32, +) -> *mut TWPublicKey { + let ty = try_or_else!(PublicKeyType::from_raw(pubkey_type), std::ptr::null_mut); + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), std::ptr::null_mut); + private + .0 + .get_public_key_by_type(ty) + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +// #[no_mangle] +// pub unsafe extern "C" fn tw_private_key_get_shared_key( +// key: *mut TWPrivateKey, +// hash: *const u8, +// hash_len: usize, +// curve: u32, +// ) -> CByteArray { +// let curve = match Curve::from_raw(curve) { +// Some(curve) => curve, +// None => return CByteArray::empty(), +// }; +// let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::empty); +// +// let hash = CByteArrayRef::new(hash, hash_len); +// let hash_to_sign = match hash.as_slice() { +// Some(hash) => hash, +// None => return CByteArray::empty(), +// }; +// +// // Return an empty signature if an error occurs. +// let sig = private.sign(hash_to_sign, curve).unwrap_or_default(); +// CByteArray::from(sig) +// } diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs new file mode 100644 index 00000000000..118204e1309 --- /dev/null +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -0,0 +1,98 @@ +// 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. + +#![allow(clippy::missing_safety_doc)] + +use crate::tw::{PublicKey, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; + +pub struct TWPublicKey(pub(crate) PublicKey); + +impl RawPtrTrait for TWPublicKey {} + +/// Create a public key with the given block of data and specified public key type. +/// +/// \param input *non-null* byte array. +/// \param input_len the length of the `input` array. +/// \param ty type of the public key. +/// \note Should be deleted with \tw_public_key_delete. +/// \return Nullable pointer to the public key. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_create_with_data( + input: *const u8, + input_len: usize, + ty: u32, +) -> *mut TWPublicKey { + let bytes_ref = CByteArrayRef::new(input, input_len); + let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); + let ty = try_or_else!(PublicKeyType::from_raw(ty), std::ptr::null_mut); + PublicKey::new(bytes, ty) + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given public key. +/// +/// \param key *non-null* pointer to public key. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_delete(key: *mut TWPublicKey) { + // Take the ownership back to rust and drop the owner. + let _ = TWPublicKey::from_ptr(key); +} + +/// Verify the validity of a signature and a message using the given public key. +/// +/// \param key *non-null* pointer to a Public key. +/// \param sig *non-null* pointer to a block of data corresponding to the signature. +/// \param sig_len the length of the `sig` array. +/// \param msg *non-null* pointer to a block of data corresponding to the message. +/// \param msg_len the length of the `msg` array. +/// \return true if the signature and the message belongs to the given public key, otherwise false. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_verify( + key: *mut TWPublicKey, + sig: *const u8, + sig_len: usize, + msg: *const u8, + msg_len: usize, +) -> bool { + let public = try_or_false!(TWPublicKey::from_ptr_as_ref(key)); + let sig = try_or_false!(CByteArrayRef::new(sig, sig_len).as_slice()); + let msg = try_or_false!(CByteArrayRef::new(msg, msg_len).as_slice()); + public.0.verify(sig, msg) +} + +/// Returns the raw data of a given public-key. +/// +/// \param key *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_data(key: *mut TWPublicKey) -> CByteArray { + let public = try_or_else!(TWPublicKey::from_ptr_as_ref(key), CByteArray::default); + CByteArray::from(public.0.to_bytes()) +} + +// #[no_mangle] +// pub unsafe extern "C" fn tw_public_key_is_valid( +// pubkey: *const u8, +// pubkey_len: usize, +// pubkey_type: u32, +// ) -> bool { +// let ty = match TWPublicKeyType::from_raw(pubkey_type) { +// Some(ty) => ty, +// None => return false, +// }; +// +// let pubkey_slice = match CByteArrayRef::new(pubkey, pubkey_len).as_slice() { +// Some(pubkey) => pubkey, +// None => return false, +// }; +// +// TWPublicKey::is_valid(pubkey_slice, ty) +// } diff --git a/rust/tw_keypair/src/lib.rs b/rust/tw_keypair/src/lib.rs index bf7edad42ab..84afd1e3c6f 100644 --- a/rust/tw_keypair/src/lib.rs +++ b/rust/tw_keypair/src/lib.rs @@ -4,12 +4,58 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod ecdsa; -pub mod ffi; +//! `tw_keypair` crate defines the keypairs, private and public keys that are used to sign messages, +//! verify signatures and more. +//! +//! # Usage - Generic TW solution +//! +//! If you plan to work with different curves in the same app by using the same private key, +//! consider using [`tw::PrivateKey`], [`tw::PublicKey`], [`tw::KeyPair`] (TODO). +//! +//! ```rust,ignore +//! use tw_keypair::{tw::PrivateKey, Curve}; +//! +//! let private = PrivateKey::try_from(YOUR_SECRET_BYTES).unwrap(); +//! +//! // Sign an ETH transaction hash with the `private` key. +//! let eth_signature = private.sign(ETH_TX_HASH, Curve::Secp256k1).unwrap(); +//! assert_eq(eth_signature.len(), 65); +//! +//! // Sign a SUI transaction hash with the same `private` key, but different `curve`. +//! let sui_signature = private.sign(SUI_TX_HASH, Curve::Ed25519).unwrap(); +//! ``` +//! +//! # Usage - Specific curve +//! +//! If you plan to work with only one curve, consider using a specific curve implementation. +//! For example, if you work with ETH, therefore the`secp256k1` curve: +//! +//! ```rust,ignore +//! use tw_keypair::secp256k1::KeyPair; +//! +//! let keypair = KeyPair::try_from(YOUR_SECRET_BYTES).unwrap(); +//! +//! // Sign an ETH transaction hash. +//! // [`tw_keypair::secp256k1::KeyPair::sign`] returns a [`tw_keypair::secp256k1::Signature`]. +//! let eth_signature = private.sign(ETH_TX_HASH, Curve::Secp256k1).unwrap(); +//! +//! assert_eq(eth_signature.r, H256::from(EXPECTED_R_HEX)); +//! assert_eq(eth_signature.s, H256::from(EXPECTED_S_HEX)); +//! assert_eq(eth_signature.v, H256::from(EXPECTED_V)); +//! ``` -pub type KeyPairResult = Result; +pub mod ffi; +pub mod secp256k1; +pub mod starkex; +pub mod traits; +pub mod tw; #[derive(Debug)] -pub enum KeyPairError { +pub enum Error { + InvalidSecretKey, + InvalidPublicKey, InvalidSignature, + InvalidSignMessage, + SignatureVerifyError, + SigningError, } 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, + } + } +} diff --git a/rust/tw_keypair/src/starkex/keypair.rs b/rust/tw_keypair/src/starkex/keypair.rs new file mode 100644 index 00000000000..89aef9189a4 --- /dev/null +++ b/rust/tw_keypair/src/starkex/keypair.rs @@ -0,0 +1,67 @@ +// 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::starkex::private::PrivateKey; +use crate::starkex::public::PublicKey; +use crate::starkex::signature::Signature; +use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use crate::Error; +use tw_encoding::hex; + +/// Represents a pair of private and public keys that are used in `starknet` context. +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 = Vec; + type Signature = Signature; + + fn sign(&self, hash: Self::SigningHash) -> Result { + self.private.sign(hash) + } +} + +impl VerifyingKeyTrait for KeyPair { + type SigningHash = Vec; + type VerifySignature = Signature; + + 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 `data` 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/starkex/mod.rs b/rust/tw_keypair/src/starkex/mod.rs new file mode 100644 index 00000000000..6fedd54bf8b --- /dev/null +++ b/rust/tw_keypair/src/starkex/mod.rs @@ -0,0 +1,109 @@ +// 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 keypair; +mod private; +mod public; +mod signature; + +pub use keypair::KeyPair; +pub use private::PrivateKey; +pub use public::PublicKey; +pub use signature::Signature; +use starknet_ff::FieldElement; + +fn field_element_from_bytes_be(bytes: &[u8]) -> Result { + const FIELD_ELEMENT_LEN: usize = 32; + + if bytes.len() > FIELD_ELEMENT_LEN { + return Err(()); + } + let mut buffer = [0u8; FIELD_ELEMENT_LEN]; + buffer[(FIELD_ELEMENT_LEN - bytes.len())..].copy_from_slice(bytes); + FieldElement::from_bytes_be(&buffer).map_err(|_| ()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; + use tw_encoding::hex; + use tw_hash::{H256, H512}; + use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + + #[test] + fn test_key_pair_sign_verify() { + let keypair = + KeyPair::from("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + + let hash_to_sign = + hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + .unwrap(); + let actual = keypair.sign(hash_to_sign.clone()).unwrap(); + + let expected = H512::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + assert_eq!(actual.to_vec(), expected.into_vec()); + + assert!(keypair.verify(actual, hash_to_sign)); + } + + #[test] + fn test_key_pair_get_private_public() { + let privkey_bytes = + hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + let pubkey_bytes = + hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd") + .unwrap(); + + let keypair = KeyPair::try_from(privkey_bytes.as_slice()).unwrap(); + assert_eq!( + keypair.private().to_zeroizing_vec().as_slice(), + privkey_bytes + ); + assert_eq!(keypair.public().to_vec(), pubkey_bytes); + } + + #[test] + fn test_field_element_from_bytes_be_invalid() { + let invalid_element = + hex::decode("00058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + field_element_from_bytes_be(&invalid_element).unwrap_err(); + } + + #[test] + fn test_private_key_to_from_bytes() { + let bytes = hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe") + .unwrap(); + let private = PrivateKey::try_from(bytes.as_slice()).unwrap(); + assert_eq!(private.to_zeroizing_vec().as_slice(), bytes); + } + + #[test] + fn test_public_key_to_from_bytes() { + let bytes = hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd") + .unwrap(); + let public = PublicKey::try_from(bytes.as_slice()).unwrap(); + assert_eq!(public.to_vec(), bytes); + } + + #[test] + fn test_signature_to_from_bytes() { + let bytes = hex::decode("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a").unwrap(); + let sign = Signature::try_from(bytes.as_slice()).unwrap(); + assert_eq!(sign.to_vec(), bytes); + + assert_eq!( + sign.r(), + H256::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f") + ); + assert_eq!( + sign.s(), + H256::from("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + ); + } +} diff --git a/rust/tw_keypair/src/starkex/private.rs b/rust/tw_keypair/src/starkex/private.rs new file mode 100644 index 00000000000..897a7bc2a78 --- /dev/null +++ b/rust/tw_keypair/src/starkex/private.rs @@ -0,0 +1,98 @@ +// 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::starkex::field_element_from_bytes_be; +use crate::starkex::public::PublicKey; +use crate::starkex::signature::Signature; +use crate::traits::SigningKeyTrait; +use crate::Error; +use starknet_crypto::{ + get_public_key, rfc6979_generate_k, sign, SignError, Signature as EcdsaSignature, +}; +use starknet_ff::FieldElement; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroizing; + +/// Represents a private key that is used in `starknet` context. +pub struct PrivateKey { + secret: FieldElement, +} + +impl PrivateKey { + /// Returns an associated `starknet` public key. + pub fn public(&self) -> PublicKey { + let public_scalar = get_public_key(&self.secret); + PublicKey::from_scalar(public_scalar) + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningHash = Vec; + type Signature = Signature; + + fn sign(&self, hash: Self::SigningHash) -> Result { + let hash_to_sign = + field_element_from_bytes_be(&hash).map_err(|_| Error::InvalidSignMessage)?; + let signature = ecdsa_sign(&self.secret, &hash_to_sign).map_err(|_| Error::SigningError)?; + Ok(Signature::new(signature)) + } +} + +impl<'a> TryFrom<&'a [u8]> for PrivateKey { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + let bytes = H256::try_from(bytes).map_err(|_| Error::InvalidSecretKey)?; + let secret = + FieldElement::from_bytes_be(&bytes.take()).map_err(|_| Error::InvalidSecretKey)?; + Ok(PrivateKey { secret }) + } +} + +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 bytes = hex::decode(hex).expect("Expected a valid Private Key hex"); + PrivateKey::try_from(bytes.as_slice()).expect("Expected a valid Private Key") + } +} + +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.secret.to_bytes_be()); + Zeroizing::new(secret.to_vec()) + } +} + +/// `starknet-core` depends on an out-dated `starknet-crypto` crate. +/// We need to reimplement the same but using the latest `starknet-crypto` version. +/// https://github.com/xJonathanLEI/starknet-rs/blob/0c78b365c2a7a7d4138553cba42fa69d695aa73d/starknet-core/src/crypto.rs#L34-L59 +pub fn ecdsa_sign( + private_key: &FieldElement, + message_hash: &FieldElement, +) -> Result { + // Seed-retry logic ported from `cairo-lang` + let mut seed = None; + loop { + let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref()); + + match sign(private_key, message_hash, &k) { + Ok(sig) => { + return Ok(EcdsaSignature { r: sig.r, s: sig.s }); + }, + Err(SignError::InvalidMessageHash) => return Err(SignError::InvalidMessageHash), + Err(SignError::InvalidK) => { + // Bump seed and retry + seed = match seed { + Some(prev_seed) => Some(prev_seed + FieldElement::ONE), + None => Some(FieldElement::ONE), + }; + }, + }; + } +} diff --git a/rust/tw_keypair/src/starkex/public.rs b/rust/tw_keypair/src/starkex/public.rs new file mode 100644 index 00000000000..2de3c561004 --- /dev/null +++ b/rust/tw_keypair/src/starkex/public.rs @@ -0,0 +1,63 @@ +// 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::starkex::field_element_from_bytes_be; +use crate::starkex::signature::Signature; +use crate::traits::VerifyingKeyTrait; +use crate::Error; +use starknet_crypto::verify as ecdsa_verify; +use starknet_ff::FieldElement; +use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_false; + +pub struct PublicKey { + public: FieldElement, +} + +impl PublicKey { + /// Creates a public key from the given [`FieldElement`]. + pub(crate) fn from_scalar(public: FieldElement) -> PublicKey { + PublicKey { public } + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningHash = Vec; + type VerifySignature = Signature; + + fn verify(&self, signature: Self::VerifySignature, hash: Self::SigningHash) -> bool { + let hash = try_or_false!(field_element_from_bytes_be(&hash)); + let ecdsa_signature = signature.inner(); + ecdsa_verify(&self.public, &hash, &ecdsa_signature.r, &ecdsa_signature.s) + .unwrap_or_default() + } +} + +impl<'a> TryFrom<&'a [u8]> for PublicKey { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + let bytes = H256::try_from(bytes).map_err(|_| Error::InvalidPublicKey)?; + let public_scalar = + FieldElement::from_bytes_be(&bytes.take()).map_err(|_| Error::InvalidPublicKey)?; + Ok(PublicKey::from_scalar(public_scalar)) + } +} + +impl From<&'static str> for PublicKey { + fn from(hex: &'static str) -> Self { + let bytes = hex::decode(hex).expect("Expected a valid Public Key hex"); + PublicKey::try_from(bytes.as_slice()).expect("Expected a valid Public Key") + } +} + +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.public.to_bytes_be().to_vec() + } +} diff --git a/rust/tw_keypair/src/starkex/signature.rs b/rust/tw_keypair/src/starkex/signature.rs new file mode 100644 index 00000000000..c0a5f16206d --- /dev/null +++ b/rust/tw_keypair/src/starkex/signature.rs @@ -0,0 +1,83 @@ +// 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 starknet_ff::FieldElement; +use std::ops::Range; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; + +/// cbindgen:ignore +const R_RANGE: Range = 0..32; +/// cbindgen:ignore +const S_RANGE: Range = 32..64; + +/// Represents a `starknet` signature. +pub struct Signature { + pub(crate) signature: starknet_crypto::Signature, +} + +/// cbindgen:ignore +impl Signature { + /// The number of bytes for a serialized signature representation. + pub const LEN: usize = 64; + + /// Returns the number of bytes for a serialized signature representation. + pub const fn len() -> usize { + Self::LEN + } + + /// Creates a `starknet` signature from the given [`starknet_crypto::Signature`]. + pub(crate) fn new(signature: starknet_crypto::Signature) -> Signature { + Signature { signature } + } + + /// Returns a reference to the inner [`starknet_crypto::Signature`]. + pub(crate) fn inner(&self) -> &starknet_crypto::Signature { + &self.signature + } + + /// Returns an r-coordinate as 32 byte array. + pub fn r(&self) -> H256 { + H256::from(self.signature.r.to_bytes_be()) + } + + /// Returns an s-value as 32 byte array. + pub fn s(&self) -> H256 { + H256::from(self.signature.s.to_bytes_be()) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + let mut to_return = Vec::with_capacity(Signature::len()); + to_return.extend_from_slice(self.r().as_slice()); + to_return.extend_from_slice(self.s().as_slice()); + to_return + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Signature::len() { + return Err(Error::InvalidSignature); + } + + let r_bytes = H256::try_from(&bytes[R_RANGE]).expect("Expected a valid r range"); + let s_bytes = H256::try_from(&bytes[S_RANGE]).expect("Expected a valid s range"); + + let r = + FieldElement::from_bytes_be(&r_bytes.take()).map_err(|_| Error::InvalidSignature)?; + let s = + FieldElement::from_bytes_be(&s_bytes.take()).map_err(|_| Error::InvalidSignature)?; + + Ok(Signature { + signature: starknet_crypto::Signature { r, s }, + }) + } +} diff --git a/rust/tw_keypair/src/traits.rs b/rust/tw_keypair/src/traits.rs new file mode 100644 index 00000000000..471148e33a6 --- /dev/null +++ b/rust/tw_keypair/src/traits.rs @@ -0,0 +1,39 @@ +// 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 tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; + +pub trait KeyPairTrait: FromSlice + SigningKeyTrait + VerifyingKeyTrait { + type Private: FromSlice + ToBytesZeroizing; + type Public: FromSlice + ToBytesVec; + + /// Returns the reference to the public key. + fn public(&self) -> &Self::Public; + + /// Returns the reference to the private key. + fn private(&self) -> &Self::Private; +} + +pub trait SigningKeyTrait { + type SigningHash: FromSlice; + type Signature: ToBytesVec; + + /// Signs the given `hash` using the private key. + fn sign(&self, hash: Self::SigningHash) -> Result; +} + +pub trait VerifyingKeyTrait { + type SigningHash: FromSlice; + type VerifySignature: FromSlice; + + /// Verifies if the given `hash` was signed using the private key. + fn verify(&self, signature: Self::VerifySignature, hash: Self::SigningHash) -> bool; +} + +pub trait FromSlice: for<'a> TryFrom<&'a [u8]> {} + +impl FromSlice for T where for<'a> T: TryFrom<&'a [u8]> {} diff --git a/rust/tw_keypair/src/tw/mod.rs b/rust/tw_keypair/src/tw/mod.rs new file mode 100644 index 00000000000..64f837824aa --- /dev/null +++ b/rust/tw_keypair/src/tw/mod.rs @@ -0,0 +1,64 @@ +// 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 private; +mod public; + +pub use private::PrivateKey; +pub use public::PublicKey; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Curve { + Secp256k1 = 0, + Starkex = 6, +} + +impl Curve { + /// Returns `None` if the given curve is not supported in Rust yet. + pub fn from_raw(curve: u32) -> Option { + match curve { + 0 => Some(Curve::Secp256k1), + 6 => Some(Curve::Starkex), + _ => None, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PublicKeyType { + Secp256k1 = 0, + Secp256k1Extended = 1, + Starkex = 8, +} + +impl PublicKeyType { + /// Returns `None` if the given pubkey type is not supported in Rust yet. + pub fn from_raw(ty: u32) -> Option { + match ty { + 0 => Some(PublicKeyType::Secp256k1), + 1 => Some(PublicKeyType::Secp256k1Extended), + 8 => Some(PublicKeyType::Starkex), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_starkex_tw_public_key_type_from_raw() { + assert_eq!(PublicKeyType::from_raw(8), Some(PublicKeyType::Starkex)); + } + + #[test] + fn test_starkex_curve_from_raw() { + assert_eq!(Curve::from_raw(6), Some(Curve::Starkex)); + } +} diff --git a/rust/tw_keypair/src/tw/private.rs b/rust/tw_keypair/src/tw/private.rs new file mode 100644 index 00000000000..fd19f2118fc --- /dev/null +++ b/rust/tw_keypair/src/tw/private.rs @@ -0,0 +1,112 @@ +// 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::traits::SigningKeyTrait; +use crate::tw::{Curve, PublicKey, PublicKeyType}; +use crate::{secp256k1, starkex, Error}; +use std::ops::Range; +use tw_hash::H256; +use tw_misc::traits::ToBytesVec; +use zeroize::ZeroizeOnDrop; + +/// Represents a private key that can be used to sign messages with different elliptic curves. +/// +/// TODO add `secp256k1: Once` for each curve. +#[derive(ZeroizeOnDrop)] +pub struct PrivateKey { + bytes: Vec, +} + +/// cbindgen:ignore +impl PrivateKey { + /// The number of bytes in a private key. + const SIZE: usize = 32; + + const KEY_RANGE: Range = 0..Self::SIZE; + + /// Validates the given `bytes` secret and creates a private key. + pub fn new(bytes: Vec) -> Result { + if !Self::is_valid_general(&bytes) { + return Err(Error::InvalidSecretKey); + } + Ok(PrivateKey { bytes }) + } + + /// Returns the 32 byte array - the essential private key data. + pub fn key(&self) -> H256 { + assert!( + self.bytes.len() >= Self::SIZE, + "'PrivateKey::bytes' has an unexpected length" + ); + H256::try_from(&self.bytes[Self::KEY_RANGE]) + .expect("H256 and KEY_RANGE must be 32 byte length") + } + + /// Checks if the given `bytes` secret is valid in general (without a concrete curve). + pub fn is_valid_general(bytes: &[u8]) -> bool { + if bytes.len() != Self::SIZE { + return false; + } + // Check for zero address. + !bytes.iter().all(|byte| *byte == 0) + } + + /// Checks if the given `bytes` secret is valid. + pub fn is_valid(bytes: &[u8], curve: Curve) -> bool { + if !Self::is_valid_general(bytes) { + return false; + } + match curve { + Curve::Secp256k1 => secp256k1::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + Curve::Starkex => starkex::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + } + } + + /// Signs a `hash` with using the given elliptic curve. + pub fn sign(&self, hash: &[u8], curve: Curve) -> Result, Error> { + fn sign_impl(signing_key: Key, hash: &[u8]) -> Result, Error> + where + Key: SigningKeyTrait, + { + let hash_to_sign = ::SigningHash::try_from(hash) + .map_err(|_| Error::InvalidSignMessage)?; + signing_key.sign(hash_to_sign).map(|sig| sig.to_vec()) + } + + match curve { + Curve::Secp256k1 => sign_impl(self.to_secp256k1_privkey()?, hash), + Curve::Starkex => sign_impl(self.to_starkex_privkey()?, hash), + } + } + + /// Returns the public key associated with the `self` private key and `ty` public key type. + pub fn get_public_key_by_type(&self, ty: PublicKeyType) -> Result { + match ty { + PublicKeyType::Secp256k1 => { + let privkey = self.to_secp256k1_privkey()?; + Ok(PublicKey::Secp256k1(privkey.public())) + }, + PublicKeyType::Secp256k1Extended => { + let privkey = self.to_secp256k1_privkey()?; + Ok(PublicKey::Secp256k1Extended(privkey.public())) + }, + PublicKeyType::Starkex => { + let privkey = self.to_starkex_privkey()?; + Ok(PublicKey::Starkex(privkey.public())) + }, + } + } + + /// Tries to convert [`PrivateKey::key`] to [`secp256k1::PrivateKey`]. + fn to_secp256k1_privkey(&self) -> Result { + secp256k1::PrivateKey::try_from(self.key().as_slice()) + } + + /// Tries to convert [`PrivateKey::key`] to [`starkex::PrivateKey`]. + fn to_starkex_privkey(&self) -> Result { + starkex::PrivateKey::try_from(self.key().as_slice()) + } +} diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs new file mode 100644 index 00000000000..29a1176f6f4 --- /dev/null +++ b/rust/tw_keypair/src/tw/public.rs @@ -0,0 +1,75 @@ +// 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::traits::VerifyingKeyTrait; +use crate::tw::PublicKeyType; +use crate::{secp256k1, starkex, Error}; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_false; + +/// Represents a public key that can be used to verify signatures and messages. +pub enum PublicKey { + Secp256k1(secp256k1::PublicKey), + Secp256k1Extended(secp256k1::PublicKey), + Starkex(starkex::PublicKey), +} + +impl PublicKey { + /// Validates the given `bytes` using the `ty` public key type and creates a public key from it. + pub fn new(bytes: Vec, ty: PublicKeyType) -> Result { + match ty { + PublicKeyType::Secp256k1 if secp256k1::PublicKey::COMPRESSED == bytes.len() => { + let pubkey = secp256k1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Secp256k1(pubkey)) + }, + PublicKeyType::Secp256k1Extended + if secp256k1::PublicKey::UNCOMPRESSED == bytes.len() => + { + let pubkey = secp256k1::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Secp256k1Extended(pubkey)) + }, + PublicKeyType::Starkex => { + let pubkey = starkex::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::Starkex(pubkey)) + }, + _ => Err(Error::InvalidPublicKey), + } + } + + /// Checks if the given `bytes` is valid using `ty` public key type. + pub fn is_valid(bytes: Vec, ty: PublicKeyType) -> bool { + PublicKey::new(bytes, ty).is_ok() + } + + /// Verifies if the given `hash` was signed using a private key associated with the public key. + pub fn verify(&self, sig: &[u8], hash: &[u8]) -> bool { + fn verify_impl(verifying_key: &Key, sig: &[u8], hash: &[u8]) -> bool + where + Key: VerifyingKeyTrait, + { + let verify_sig = + try_or_false!(::VerifySignature::try_from(sig)); + let hash = try_or_false!(::SigningHash::try_from(hash)); + verifying_key.verify(verify_sig, hash) + } + + match self { + PublicKey::Secp256k1(secp) | PublicKey::Secp256k1Extended(secp) => { + verify_impl(secp, sig, hash) + }, + PublicKey::Starkex(stark) => verify_impl(stark, sig, hash), + } + } + + /// Returns the raw data of the public key. + pub fn to_bytes(&self) -> Vec { + match self { + PublicKey::Secp256k1(secp) => secp.compressed().into_vec(), + PublicKey::Secp256k1Extended(secp) => secp.uncompressed().into_vec(), + PublicKey::Starkex(stark) => stark.to_vec(), + } + } +} diff --git a/rust/tw_keypair/tests/private_key_ffi_tests.rs b/rust/tw_keypair/tests/private_key_ffi_tests.rs new file mode 100644 index 00000000000..37c87583f10 --- /dev/null +++ b/rust/tw_keypair/tests/private_key_ffi_tests.rs @@ -0,0 +1,173 @@ +// 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 tw_encoding::hex; +use tw_hash::{H256, H520}; +use tw_keypair::ffi::privkey::{ + tw_private_key_create_with_data, tw_private_key_delete, tw_private_key_get_public_key_by_type, + tw_private_key_is_valid, tw_private_key_sign, TWPrivateKey, +}; +use tw_keypair::ffi::pubkey::{tw_public_key_data, tw_public_key_delete}; +use tw_keypair::tw::{Curve, PublicKeyType}; +use tw_memory::ffi::c_byte_array::CByteArray; + +struct TWPrivateKeyHelper { + ptr: *mut TWPrivateKey, +} + +impl TWPrivateKeyHelper { + fn with_bytes>>(bytes: T) -> TWPrivateKeyHelper { + let priv_key_raw = CByteArray::from(bytes.into()); + let ptr = + unsafe { tw_private_key_create_with_data(priv_key_raw.data(), priv_key_raw.size()) }; + TWPrivateKeyHelper { ptr } + } + + fn with_hex(hex: &'static str) -> TWPrivateKeyHelper { + let priv_key_data = H256::from(hex); + TWPrivateKeyHelper::with_bytes(priv_key_data.into_vec()) + } +} + +impl Drop for TWPrivateKeyHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_private_key_delete(self.ptr) }; + } +} + +#[test] +fn test_tw_private_key_create() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", + ); + assert!(!tw_privkey.ptr.is_null()); + + // Invalid hex. + let tw_privkey = TWPrivateKeyHelper::with_bytes(*b"123"); + assert!(tw_privkey.ptr.is_null()); + + // Zero private key. + let tw_privkey = TWPrivateKeyHelper::with_hex( + "0000000000000000000000000000000000000000000000000000000000000000", + ); + assert!(tw_privkey.ptr.is_null()); +} + +#[test] +fn test_tw_private_key_delete_null() { + unsafe { tw_private_key_create_with_data(std::ptr::null_mut(), 0) }; +} + +#[test] +fn test_tw_private_key_sign() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let hash = H256::from("1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"); + let hash_raw = CByteArray::from(hash.to_vec()); + let actual = unsafe { + tw_private_key_sign( + tw_privkey.ptr, + hash_raw.data(), + hash_raw.size(), + Curve::Secp256k1 as u32, + ) + .into_vec() + }; + let expected = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + assert_eq!(actual, expected.into_vec()); +} + +#[test] +fn test_tw_private_key_sign_invalid_hash() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let hash = hex::decode("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080").unwrap(); + let hash_raw = CByteArray::from(hash); + let actual = unsafe { + tw_private_key_sign( + tw_privkey.ptr, + hash_raw.data(), + hash_raw.size(), + Curve::Secp256k1 as u32, + ) + .into_vec() + }; + assert!(actual.is_empty()); +} + +#[test] +fn test_tw_private_key_sign_null_hash() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let actual = unsafe { + tw_private_key_sign(tw_privkey.ptr, std::ptr::null(), 0, Curve::Secp256k1 as u32).into_vec() + }; + assert!(actual.is_empty()); +} + +#[test] +fn test_tw_private_key_get_public_key_by_type() { + #[track_caller] + fn test_get_public_key_data_hex(tw_privkey: &TWPrivateKeyHelper, ty: PublicKeyType) -> String { + let tw_pubkey = unsafe { tw_private_key_get_public_key_by_type(tw_privkey.ptr, ty as u32) }; + assert!(!tw_pubkey.is_null()); + + let actual = unsafe { tw_public_key_data(tw_pubkey).into_vec() }; + unsafe { tw_public_key_delete(tw_pubkey) }; + hex::encode(actual, false) + } + + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + assert!(!tw_privkey.ptr.is_null()); + + // secp256k1 compressed + let actual = test_get_public_key_data_hex(&tw_privkey, PublicKeyType::Secp256k1); + assert_eq!( + actual, + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" + ); + + // secp256k1 uncompressed + let actual = test_get_public_key_data_hex(&tw_privkey, PublicKeyType::Secp256k1Extended); + assert_eq!(actual, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); +} + +#[test] +fn test_tw_private_key_is_valid() { + fn is_valid(privkey_bytes: Vec) -> bool { + let privkey_raw = CByteArray::from(privkey_bytes); + unsafe { + tw_private_key_is_valid( + privkey_raw.data(), + privkey_raw.size(), + Curve::Secp256k1 as u32, + ) + } + } + + // Non-zero private key. + let privkey_bytes = + H256::from("0000000000000000000000000000000000000000000000000000000000000001"); + assert!(is_valid(privkey_bytes.into_vec())); + + // Cardano private key is not supported yet. + let privkey_bytes = + hex::decode("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e").unwrap(); + assert!(!is_valid(privkey_bytes)); + + // Zero private key. + let privkey_bytes = + H256::from("0000000000000000000000000000000000000000000000000000000000000000"); + assert!(!is_valid(privkey_bytes.into_vec())); +} diff --git a/rust/tw_keypair/tests/public_key_ffi_tests.rs b/rust/tw_keypair/tests/public_key_ffi_tests.rs new file mode 100644 index 00000000000..283083248c8 --- /dev/null +++ b/rust/tw_keypair/tests/public_key_ffi_tests.rs @@ -0,0 +1,108 @@ +// 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 tw_encoding::hex; +use tw_hash::sha3::keccak256; +use tw_hash::{H256, H520}; +use tw_keypair::ffi::pubkey::{ + tw_public_key_create_with_data, tw_public_key_delete, tw_public_key_verify, TWPublicKey, +}; +use tw_keypair::tw::PublicKeyType; +use tw_memory::ffi::c_byte_array::CByteArray; + +struct TWPublicKeyHelper { + ptr: *mut TWPublicKey, +} + +impl TWPublicKeyHelper { + fn with_bytes>>(bytes: T, ty: PublicKeyType) -> TWPublicKeyHelper { + let public_key_raw = CByteArray::from(bytes.into()); + let ptr = unsafe { + tw_public_key_create_with_data(public_key_raw.data(), public_key_raw.size(), ty as u32) + }; + TWPublicKeyHelper { ptr } + } + + fn with_hex(hex: &'static str, ty: PublicKeyType) -> TWPublicKeyHelper { + let priv_key_data = hex::decode(hex).unwrap(); + TWPublicKeyHelper::with_bytes(priv_key_data, ty) + } +} + +impl Drop for TWPublicKeyHelper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { tw_public_key_delete(self.ptr) }; + } +} + +#[test] +fn test_tw_public_key_create_by_type() { + let tw_public = TWPublicKeyHelper::with_hex( + "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", + PublicKeyType::Secp256k1, + ); + assert!(!tw_public.ptr.is_null()); + + // Compressed pubkey with '03' prefix. + let tw_public = TWPublicKeyHelper::with_hex( + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", + PublicKeyType::Secp256k1, + ); + assert!(!tw_public.ptr.is_null()); + + let tw_public = TWPublicKeyHelper::with_hex( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + PublicKeyType::Secp256k1Extended, + ); + assert!(!tw_public.ptr.is_null()); + + // Pass an extended pubkey, but Secp256k1 type. + let tw_public = TWPublicKeyHelper::with_hex( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + PublicKeyType::Secp256k1, + ); + assert!(tw_public.ptr.is_null()); + + // Pass a compressed pubkey, but Secp256k1Extended type. + let tw_public = TWPublicKeyHelper::with_hex( + "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", + PublicKeyType::Secp256k1Extended, + ); + assert!(tw_public.ptr.is_null()); +} + +#[test] +fn test_tw_public_key_delete_null() { + unsafe { tw_public_key_delete(std::ptr::null_mut()) }; +} + +#[test] +fn test_tw_public_key_verify() { + let tw_public = TWPublicKeyHelper::with_hex( + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", + PublicKeyType::Secp256k1, + ); + assert!(!tw_public.ptr.is_null()); + + let signature_bytes = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); + let signature_raw = CByteArray::from(signature_bytes.into_vec()); + let hash = H256::try_from(keccak256(b"hello").as_slice()).unwrap(); + let hash_raw = CByteArray::from(hash.to_vec()); + + let is_valid = unsafe { + tw_public_key_verify( + tw_public.ptr, + signature_raw.data(), + signature_raw.size(), + hash_raw.data(), + hash_raw.size(), + ) + }; + assert!(is_valid); +} diff --git a/rust/tw_keypair/tests/secp256k1_sign.json b/rust/tw_keypair/tests/secp256k1_sign.json new file mode 100644 index 00000000000..42c29431309 --- /dev/null +++ b/rust/tw_keypair/tests/secp256k1_sign.json @@ -0,0 +1,502 @@ +[ + { + "hash": "e47a0e9d0444a58e4aa423b92734280d9c754dbf05e488f69c496105236577ea", + "secret": "cc446e8442f76121d3fc366e0d0ab30920ad4d1f02010f2d715622ab261962a4", + "signature": "bc70bd3d2e8ffdb2dc6ca44e19696e505b3c4890d51963f5eb387b1d21b70d0605f22f354e81e9821c1ce5f6e26b88b3207b030ad455cd27079bea14b3fc7d8201" + }, + { + "hash": "8b2f77ece11bab3cb3cd62c24a655af6399d87d3fd084e50c4d8aca31fe5e6c1", + "secret": "7057b3fa5dd6d46ffa291530630a14c79973efa3f3ae9b7393bfb40caf90887a", + "signature": "5305bc280335e1ac1de5814e7079810ad5f1ab6e26e9ca4aa1ff91454889ac495176bec4dc64967e90215a7e5b13adc2fee0c6914f27093ab3c27250d861101f01" + }, + { + "hash": "1cbed9daf28b8baa54c91578cb81a7fc0149287ad78a392538a6ea34d382908f", + "secret": "36a45e08fae0b1bb715e2a187715343727359bee1a475f53f5f107d9b911f554", + "signature": "672a72b22ccb35129defdb671c2f3c11070e0c4fa912edfe2e30130640c3febe5ae9cacd0b44533a4492b3d56c276399f727fb4bd9c0f48cc91bb7cf464558f900" + }, + { + "hash": "0a9f9273696da5bbbc45c169defaa5b67e1b907b0554400f28ff1134f2399f8b", + "secret": "b304f3a5467454e71141afb281565b1cbd45b56b719e77605b8cd9c28217eaf6", + "signature": "e62704eb552e0c188e15e1d67e2f4e707d3562ca72edd31a11967109923ed07c06f30297cf701e7a05e0873654274ce79aeb7de6355bf6577ca8d42f280c65d401" + }, + { + "hash": "1e02ef222b0dad8126c3e67fb9e331953b2b7a838396eba93d7cb300dce250bd", + "secret": "d287cbca0ba0e5e595137c138e74cbe623173aab9b5d396307bdc396adab1ef8", + "signature": "34c7da02b40ad41c887581c4e0978d50fb9eabf9a7bb22d7cf3809de33f3ec737561f9965a315566fe0216b6b9a629af373c1ab897ab16bff0320550cddd3e1300" + }, + { + "hash": "7016b6f5e3527de8353c8b7406683dd27e8afcfd7a132269d1bb1d3b6c2abece", + "secret": "20be2b599db8e62388a88e04eac527bf5b66924b1fc5ddee41bc76a2870115a9", + "signature": "2b2260a994488642b9c5c40630563b0f0adcbaccfeffe98e8bf4aeeac5c9722d15878d4df619bac21ce9bbecabcc185522bc4adbee5cdaa31e53a4e4c0a591ee00" + }, + { + "hash": "ea2d778a76d19bfa4fef744485582291587e7c117c092055e0ec250b02a1dd82", + "secret": "2a8f9d590eee3785a12460029d16c7d324a1102e30af5183d77c70b3883354d9", + "signature": "9fdcd89858e8c376ec68c9099587370a6418beea052ea6562fe0ee7ee0532873620d0e3c6ab90ea0c0f675f11311e2ba509cd01d45ff4c775fc7197a85f35b2501" + }, + { + "hash": "bf8a405154b95076dd08e0eae0e1cbde8addc8c03ee9685f8471b1d59b97c10e", + "secret": "50ef8cff6983498f713823b7c39b51ef75066badfca1ed6af1951ea5f3b0021a", + "signature": "11d811bac7a9a37dd84b1a5e5780775797c35f9b6f217d2d36bf2f28879441f60e304299405918a8e6ef9e7775557a2af30548e6af32b2561392d472bcba8a0e01" + }, + { + "hash": "14a880275ab266a5ccad44c0a7330092beb2b6e355bb0800841abc7a22fec160", + "secret": "acfd831f61dac04c52edca91e62400764b58eea62876bd7491faa01be9955d18", + "signature": "dc9e5af5d74b7c09e0c2f8752296f5abe0103d7d77e48e769a3b75721dc8c41e109edb98d19ee4d635f1c56123f87ef4652d15fc9e1785c3955f286a5f599f0301" + }, + { + "hash": "a2c9b0b7cdcf47e43bb1a0d55f29cc7bded2c20e3c278bb609315e771265b8d3", + "secret": "900a33439cc91e06f10ed016a9b77fb4a2bcb9c0b5f5d64e27f23b224326cb65", + "signature": "5acf06a23a4d7aa0efffb6d17f81aa3c95c2c4977cf44d72a962c98342e1bbb808f43a2a654dcbf64608537744c61ed2e910b07375df33867ea72d6e9f136e4800" + }, + { + "hash": "6e00f7ac0f2ce58900b37a562313b526fcf35f0f2fe91b8b813876271a394231", + "secret": "67fb6806a97821affb20e960ba5dd57e9fe565cb419f92df85f03fcb6e353206", + "signature": "29ed122ff7f02cfdede11d3a5f817027f003bb94cde9b6a19361f3e4c49a150174614ed76ff575b909c835e7053776a35d593bd064373d8e2d682d1d1888cb0b00" + }, + { + "hash": "63bcc7f35ec60b4660a4af4ac568ac7023c72b7b94cb69ea561bd0a51b533208", + "secret": "d5567b440186aadc79fb41bb9a5595a6d3e9ffd1b31de5f319a056c55c1a5180", + "signature": "0f8bf2b4f1c32829ea7b8ecdbc4b1d920c5ead58589f895445aa9db05a726096636b873e4f33327e19db035ef1037f2d7a46b325d72a961c34fe5bf49ef70d8401" + }, + { + "hash": "b2bd1ed10c343ab83ac0183f3d03f87c4513e22337b07f31a69acd57614454b1", + "secret": "c6703284f87452cdd53e5224b4ff6afb8547f89f42e105c28d7aac35ddc0cfe3", + "signature": "5fb35b052aa058c673249c90161e9ff8690886ce3e7fbe158260fe3a0e0bbfed7171eaddc33251a4243fef892985bcfe05f94ebde202ae899284f7d72741001d01" + }, + { + "hash": "76c0016f9efcfb0f3114e30f7e69497b6be26c9b17068f195ec085a981a021a7", + "secret": "be44fd4ec29b541947cd7b8e697dba1ac42fe1bfe879a726e8a1bce894f81251", + "signature": "af56b85d029a78b04aa720a4d7113d457f67d0de422288b817f4ef2e389933f227a29153c399a9a417d9506750c51fcc20b31b802e54acba141fe33fce2d165f00" + }, + { + "hash": "a05f60c79781ee69ab3d656e2408c88fdfc96659ad16bca61091c435b3d7c33c", + "secret": "d7f53418f26ace9f24aa5fadb327f65fdd622f4b3bfd4638b713b50c362cc742", + "signature": "7dbbdb81fcf9cc8b963aa5e3d9cbd3863f99fbd048e81341ea006bbb6a7c4e5221bbd317baa74557129c35b2cd1443fec4dde78a2b5064fbc57e53c9d5b7d9d400" + }, + { + "hash": "3fe350ed89da6946d9291dfdcde0a55febbdab3eebd9010e2804a3fd1ba9d647", + "secret": "a88c8001d825d7c0019ba16a0ce7c89941d4bffdbff4cba2a348fedf4f992f02", + "signature": "abd4cc167b48215c1e82a7dc25c8cfda5962ef8e4c3d71f16877fa1d0fedfe240039664a8442f3cb636a87564e700c92ddc06f33579e3277ec5edf8b2118b11000" + }, + { + "hash": "ed9d574fd453b5fac1b0a93a80573d7df064b37e166b0c3e4a10c016ac6ecee0", + "secret": "9f75127c2d635a5fd27ef3b613ab5011db8b3f5e37537629cba43e37aa4d4489", + "signature": "7aaec22a07280b4425410d154f43223731d2c606a11282fffe6aeee84d8184ae7c0bf88fe4ca6c628c9488f67e45c3a4ad0c0edb4d50bfec3c0656a65b44938e01" + }, + { + "hash": "2624913cb516d997d6d042e5d9d5b30a4131ba3c31f82a34d35018350e9ca68f", + "secret": "9d802bc343231cbb7004deedfc056372e6fa4b02b48ac56285941ed33761bcd4", + "signature": "6b02fdf137298bc5b6dcf9494cd6dfa7c369dbf7b3068a826abea8fad8cc65254bece3c0ce763f5fbf8b157cd9cbc295a63ec2c0d2bc24f5d9d0e024b01fe7a601" + }, + { + "hash": "44b4a8ae135675846b8fa732eb2622f63f95fce5b2b10730d20283ee7a6aed63", + "secret": "bf9e6687a24727d2350a5da6211a43fb3d44a27ba81e1ad9c5d1b91526752963", + "signature": "4fa117306368df1170d7fcca01e80353f77f52db807619ad824e5c4898a0663d72af1dbead5f3930f377b92cb9b6a502fba73eae09baceff500a0a58b2325d4301" + }, + { + "hash": "6f7878f3ad75aa55ec6c7599ad1400101b656fc73535c9e685ca602f8cd20a15", + "secret": "1532a5657dda80bda879f601d8a021607d381714256503ab616d2326b9941eb3", + "signature": "c46940f33634f37110d801358db10471b89be637fc082a0ce7da2fb6c8012a9622df557358ef2a766ad81381fa02182fdcd259e263452ffae50690bfc473920401" + }, + { + "hash": "3c014d188be81a9c617625ae2d9bf67108b260858fc4a01e9b835630b1f0496d", + "secret": "2f5d0ecc962c457e148e2858a56faaf873ff86484f50ba73721edede01da6c37", + "signature": "1adffa5c92d00be99358ac30eaf77608f6c335594f7d2cb698c84675b7e5ab5b686a35dda6163fe469465a6e97e949d584906e6cf19f7786cc45269e38301bbf00" + }, + { + "hash": "c294789679f2e887db22b0c7cb240b911468e3a2ab028878bec2d8357ae61900", + "secret": "10bc10b8a45e77c332dfab667e413f18ea3dca01af9a3d0f687d4158f2616e54", + "signature": "73cc844db97306c77cde84719815feb9a0f6758332956ee1fee809b69c4928845920e6c820d38445ad770a462444f23bda87f8b1c7a596617ed66f9c08975a4101" + }, + { + "hash": "afdac9c303e05371802bd0054f0b01a90d5fbf0df8acd41659812eb0f0fbbf63", + "secret": "3ed0724a7874a3642c5a8956d5ee63bb1ecb683383af7cbcfbde3e6593d98e92", + "signature": "7a3bfe06618e30c936a42d9977955ec8b2ca9d5bb3ac64e05c963bef5f65a3e938f48fb000cdfe86a3c80161a272c8636d24e92fcb753735e0f209b8031489c501" + }, + { + "hash": "f9d60b3db6cbf724a573180d7cbbacbac3f64da7b4490b73c1bb7e6b45ed13b8", + "secret": "f53c65f3da3171b9ad12edac26e56dcef4db8859a933c6b2a25d34c4c89ba8ab", + "signature": "0ffed06a951920df34e47d81f9cb2e22266a4d676dc62ab52eceb809688a5bfb0c8af3d4097ee1b73379fe0dd98b84d37f8e5a3e7cec51aab9ab207a2ef80c5101" + }, + { + "hash": "c5ca565076b7e29aafe596bbb7fc62e35705dc15d6b9f4ca81a57e4049fd79fd", + "secret": "9f3953048042a019fa38eda94fd3a4551f27f7c9561b2172b026dee63a0c4733", + "signature": "c5b92f677aed396d95ccab20dc3dcdf138795faf895840f7fd487048aca2400c4616204d45c3974d917250cdb255979b7f81cb1366d6468d22ed5e4ab022180a00" + }, + { + "hash": "dba0ffe77b1e38325881bb24fe9eb2cf997e1f6a14924e2b876f5238376175b8", + "secret": "4a8d2fb9a95a5d2371da8b0eda619acfc32f6f282992033b3ff445b51669ab70", + "signature": "443be5107a6a48f383a36330eb07ec8db9193314120b5bfaf31eaa79836a19381daeced7e560aeb4c2a568ad8d2389292ff51b8a8d3d368b399b755717c5ef1e00" + }, + { + "hash": "4b18609c56d06e1797248e227f3c6bcabf117275d14afaf23980a7c12ca1bd68", + "secret": "178c69840aa469159e2c36d456a5074363e7092765177d1e363e9bafa66411ac", + "signature": "43414a346bac4c686fbc1c9dfab55ae91a4cc0c323e742d5bfaea3393f0e301c18d6be934769ec7f492cb0718e4d7fbe66a3afd5b0f0572cf49a0b72eee9e95601" + }, + { + "hash": "04b09c6a84f7a6f2d307fc568a706e69544f5391a61656a0cdbc5531dc50ab0e", + "secret": "59f1f81572e13525f4bd23ffe8cc0d8a3f1e61b8eadcb8b7f4007ccc5014773a", + "signature": "77c3de54e7f3792b1f518ec687df2cbe578e732f47266cc6e5345792e07675bc696e2441cda375f5868e8e584db548fff218a8b66f6d56daad84128be0b6a3d300" + }, + { + "hash": "8f9f8950a3c69ac371c1111a45b09f9c97b47eb298b78134b1c8f2061262f58a", + "secret": "d75c60c24f7ec9779f8e4784708e250b18c37feed155bdda66c39f43342b5edc", + "signature": "5e11734434c570813a6eec42226e4bcddf94c60829333fea8bdc89b802e912520ab53114e73c70f541079e833a483aca59a9c547f355c870d08312470ec759b500" + }, + { + "hash": "fbe8b8c87551c171dea9aa9f1398d160652ea825339adbd6f85dfa0b58a86562", + "secret": "87818f8318b7b249a23f0719fee909d8431ff9e590c86497b1bd271efc3f79c6", + "signature": "64b9be5b2aa9a5325014e11366bcb13a06710ecc0d763044802c84939b8fa4ab6e895868df160b7ce836252600f3293309b7b4cb75dd7bd0194edb4d657937c100" + }, + { + "hash": "3b3b081e02323e29f343d08848b43eb988cc76e0985d25e38e8929c7005b8158", + "secret": "7ec81368d0eb5a50a4d155558d0fc4f59e0b1469ce92cc8b607db929a6b08903", + "signature": "ed6583c58c9d3303340b2ce0b254d8cb105933c88a62ee222c60ffa14a44aa335d8a7db8cf17412c4c5383d65b5f7ca2e094baa0124da78ffa134f47acd6d68c00" + }, + { + "hash": "72cc90e1a27aef0e63afae2e6b2d6adaee0d6cb749683f6a540082b718a611f1", + "secret": "3f7cf867ba530c11c2c913e61951bc81b0326eaa088561afd70d5c48ad34162f", + "signature": "51ecc9b8ceb5c699efbb1563e28879bb21800ab95224ad65a8b8680576c247b16da9ae06f2f36dbb714beaaf473db13c2363f5c26d449b2902ccf8d98c16e2d201" + }, + { + "hash": "f9c6c5d2ae4e6546be1ce7de502ca88f2c4a187240963f967fa4a23a85991115", + "secret": "476b349bfdefb01e41779a96e8b1ebb2222be3ab19ce9fff5d128065f1b837d0", + "signature": "91b9f72f12b1616849badb127384ff8f4527490418706da39f15bca4d7bfc4b8564d82702ebcc2f70c07457d7b59240aaeed0919a95780fde6975dcb599b925b00" + }, + { + "hash": "72d98ac91ff1d66f2e1009e61ba2b44c53b38d67b137e0a6128f68b6079054d7", + "secret": "99b5b03cf2dc0c3ad8517cba4186cbb286c77ce26bb71e23ff1b531021976202", + "signature": "1895371c41d18068a95d12503e45a58a00973892acc4289424728284a5f926e06da7cbc921529deaf85d5a148f730b842468f0d51c525b979f10dac638537f2e00" + }, + { + "hash": "a5ec417f9eac2510c5443f21b50513bdc86ce6c4a66bc14359890dd5cc8a7b40", + "secret": "ce58ce540c3001a2fd28502e5ca892e7b7eb00f55c287062ec843ac7752fa6e2", + "signature": "4fe00b7b0ef1c0a9a8e057a512721bc3ca8052a06272c94010c266b22343678b31dc281f2edddebb9b64b0d174a97ed5611a7f7c12e6f4f06a151965f2f30e3e00" + }, + { + "hash": "ca74b92c4e9cf5a3cf4de9268ac60bc9866e3238966232bc6f4e4d2f69a62885", + "secret": "b96f178ca927f5f8e8eaa6c45441db5060dcd1a09f272aeafda0bcf0c700cff5", + "signature": "7f12290968b60841be92af8e5d9e99ea4a7565eeb58130c2ec6b0ae4acd5466276d96e5806beeac99a25a67be8164e31eca830a5389d101a8c24e60aa06909e200" + }, + { + "hash": "73c75c6d182eae3e423af7049e5b18f8240b635d9d1b694d12342da08f6777e5", + "secret": "826a2f08be69e49d8b09b29db3b4aa5779a6744332725fa5e1814110fa4cdb96", + "signature": "a2137c4d430546cb04cca3c0299d6938deffb9e78df6c5a36296cde6584cb02a42f2d6105700f1772752a1dfcaeb113582c6999a9f44da60d50f0ea8affc321901" + }, + { + "hash": "77a033035f47a54614ab135b507e5f2989b2ffc62793ba59c662f744f3f7d1ee", + "secret": "e01a232ba20a238fa1725ae95a08d5d0ec15379f362ce1b0ef59ad209bcd06e8", + "signature": "e7945c17883124cc85134043842fb236510993134d9975c8e32eaf23e9877823255c257b86334488151dfbd65360bf534418f76d53050f9879ad04881f71394501" + }, + { + "hash": "45a2c86962e9e408a81f9f687795fdb71b3a000b62731f438d12ee40d42cb374", + "secret": "9b8c1f300a7ff483e0f6d1d72cf87b88d10fe27461399df74ed9dd319848afd3", + "signature": "1a4452b0ccf856aa29ac2b56d7db74ac5bfbb9cde6fa71599717824c9d1305a23cfab403e7f59865f33dde626e220a3d59597c050723e0af44a57d360c2a19dc01" + }, + { + "hash": "49c76133ced2492f275f0b6fb26067020b1919691c0956c9ddb0e4375bfdf4ff", + "secret": "6e6ed3ec4255c4194f479d05b3c7c2e1bed82ff1b7994d0692094dd2637c12a7", + "signature": "71d128694790c1dd91632b2866e6a6840c79b24420105e3b74913a37f799fc616a0b0d5dd4babea6bb01d11f823ea66a740ca249db6e19086565ee7279eb2db101" + }, + { + "hash": "387eb0cf09c1a00c17a6ac8ece3119b34d116733e3960fb44e50056c4d0a0aaa", + "secret": "58e1ceb39789cb4fa05d3af6f9172cf6af53fb8679bf32979b28f4ebd173d72b", + "signature": "3228a0322861193e10664780df8c557267f5208904afacb9035524e2c16c7df166ed796dfefe493defc5c1348cbf9ce727f603c65a3c5e500da8a99f88abcd7201" + }, + { + "hash": "bc8e5887ce6847ed67be2a781633b06dcdfb816038ca0739a105d0bc80a56d75", + "secret": "30b94c4c87c770bd21620791704408291b86b171e939c2e2a05ae618a6fe94b9", + "signature": "58e1821e5cc088e26980559a87da62b6fb9d5b0515f3a245fde860472dccdeb22e8d460802ce34174abe3f138f78ea7516fde8109f24a7c2efd5318e2240891001" + }, + { + "hash": "c428d4f949aa6fdab3fa5a784451745bac85fd81c2a0d9c5ac6f521786cd56df", + "secret": "af86222312d73b593e911ba7a929f3e97d567043bd0078d443206b7d0f3f931f", + "signature": "0fdafcb6f9037865092a50ce95ab9891f9b478242aa206ef4ded9f8b61b86f2f0c22d2e545c8dbe7f24a1a5137e38e325fa926c7f17c15ccf962aae8d962db0d00" + }, + { + "hash": "219f55f3012c0c7d16e0172c6ff1b21080bdf3d6e867d32196e3b79a5b4a6b7c", + "secret": "fbcbefbf81c033df501309e25f39ef3e15b22ec2061c97c3f8a5c784a7149b87", + "signature": "3d0a81e647abddc6d38fca85acadf483835471e5d8820ad51891ef19a65346ea6646b6f3d9d8556831c48e289a23b277b5169d9ccea6ad76c846b74e5ef38a6a01" + }, + { + "hash": "1b8840a329ad8bcf2614a06c3d6bfb9385cbb22a58952fe6eea67310518f983e", + "secret": "a1632ce45d4025bff643ce1a0b36a3ca7a7298804602679eb49be6bfc6553c92", + "signature": "059d90136fd57f372dd12fc1e10f2d3a3700031c4f1e4f36459923e4fd3c5d6b6d83e3914a3eef24b951c38025092303eb91e786a2ef9a23750b54faadbb00c700" + }, + { + "hash": "d17decd2a4b042738261d6e63ef012cc6867e729a3e6f8ba3e1bc79532bb463c", + "secret": "d2247583b85dd27f39ca9dc8893966364e8a12699753f6ef9928f2d9011f9cdc", + "signature": "f12be874105b61217869cf6b49239385cf1c3753b5551cfb1acd358dc297780765f0c591dcb24f142a5045ab0fca4e90bb9cbb4c08195a34483b8ad3bce9cc2d00" + }, + { + "hash": "bafe579a79d46dc918dd15b6643f312e952ca60deb5bbfe2bb361c1eea9c4a4c", + "secret": "9fab92109e06a36d1d21002b020c1f9171003fd463105fa8896970496f56c13e", + "signature": "bda204f249f0c0d2a60ea4b4e87bee941d356224160f2a769881635d5363700963cae24a2bfeb616c3f72d4be2128bea3972ad14ffded72964a66546e8383cb200" + }, + { + "hash": "eff6a8531bae03050f6a62f7652b6213e1755631191c363826ddcb185e2a43b9", + "secret": "fe756b78c5425405092aa198c7104069343e0c890998a1e699bc9f908c7cb264", + "signature": "853d656c1d293e04f7d425c13412b350582c23700d4975e302fdd761562bc9e00c0bf359d360a736089690e1e3f517fdb4db230f7e76489fc9e1eb58500f8f3e00" + }, + { + "hash": "4917e9f4dc97968151de0bf71b5b2bc6ee2544b49b368b305d503dc781ca8cdb", + "secret": "6b3a552cd49db3aff943b090dfac7e41a820d8b6319fcb66e5b75c2e89dc4a8a", + "signature": "d571d17126ccab7734c7d3b348117fb105f45e6d1b6b6898a489cf8ef99e1ab95f0fdce993b33123cfddbbeb7c6788655829b6d9ddada341540a2629342f061000" + }, + { + "hash": "738841a6a624805d5cc0670c9508c72f2bd3bd41e3a652efe536ae67da39525b", + "secret": "a5c50ca0b63ed744d10b3579fd187f2555da46350fdca74be052ef7f19ac2ac8", + "signature": "3d050cdb5a335a85907be4a660f153cce2a18f5db3e3db6671e2a76daf3d02935bbcc06971462f25409defa49250d30bdb55710cf69a5cd1a486926a7fe5ce3d01" + }, + { + "hash": "44751c72517dfe4fcf5c09b84926f096d4ef69d6c131aab4d3d2af88798fd0ea", + "secret": "67f42cbc42437eb15d699848783c4295514c9fa8b57c5f4dc9284f1c421e3a0f", + "signature": "18bc8c522efd14dff80a0c5c68db20f0e3947d2040fc7235fdf60038769a6e8c099700104432dfa3292985b5f48939b36fe83d048835fd71b36f37b034d280fb01" + }, + { + "hash": "ab3eb5d20f2b4a616e9b2a94b6bcd586bdaf485bb78e444505ea94f379e53892", + "secret": "73ed40023e40bbccc49e26497004c6e5347af9272382ac1d5a9a3f2f78f9bf23", + "signature": "b7c4af4bb53af7ec1abcdec919d344ba06f5d4623594eb380def2215d188b9c63c62b6cd1cc4eaf7967a3ff4e50364d2591e8966856c15bb4efd2fc8d75fa25c01" + }, + { + "hash": "8d49d2d75ca11e719941259415524e2a2d8858a5e1c98431b0ca4036b0af6f27", + "secret": "d8a771b23524fb94766078c3f458939ad178a05caadfcc6cf5a9410dc2ca7cd0", + "signature": "3fe0f65840cc2795197740e3776ccbabbaa1c68111fb2250979889e7cd663a9141f997a117a30ef68164a8a57d3ba82f58d7684cd69658b47f267a93ac09807701" + }, + { + "hash": "100de6c9ccec037ab289812462506507e615dd1e7ad4fb33c724af9f1eab1070", + "secret": "ed7fa7580d848a15ca06018acf288dfe7547e0e44e175d009f52a47e40e5cc17", + "signature": "c52016c525ed8d7f7b7345cb55c98ae9bce55afffb517a19c06a31b68bb7bfc77b580affb42618f30bdc7526d42e6439621311407bcaf12d3ea716bb4cb20bd101" + }, + { + "hash": "0b629bac6290cb90bf375893c324f7e8b3e1dcb76c6defbe9be1b0953a5eae38", + "secret": "ef44bf690abde5c3226dda3aa133b77a81f0082542d9bb9b439645d63fa0003b", + "signature": "3e5a814d8e201cca69cdc570697fa8dccbe48c04ebfaaadaf8343749b5e7fe200a384ebfad0f54c8cef939a549a2a91d4039889eb9034f259648c0ea203fc4e701" + }, + { + "hash": "98fa86a923ef8f905672122adf722a3c56ee0b8349fefa5b703b7d0381ccd421", + "secret": "8c9154788d4a19ad4e081cc243be741eafd48d166ac6fe110dfb3dfeeb10feae", + "signature": "803095e5097c048b12026e8d25b513ec7417bef26c3b1bf910c9773a2d37895e0dc1e184eea9ffbe7f8640e7c98d7b893512336f8125dc37f9e4b460f6f58cd000" + }, + { + "hash": "976c107451a19191481fb3f413a42ff20066cc7e0d8a3809ee614cdb1728ecb9", + "secret": "64c008571f89e53d19491e7e63e6968ae08187a9dfa6bd99c6ff5c5f79f88004", + "signature": "7ca7c754521993153d08d0f22f65bfbfe69ede0900ce759e4ba7fbd5fffdde631b864769cd882c37e559af447b0d47121f32b8697d13d3fd3c504d687b4cbc2c00" + }, + { + "hash": "d8887c6743c97980b503c9a69d0a43602db596094553661fa1df2a4a6ac18a53", + "secret": "c364018454d6ad29523a425e83ffed1d0b5d17e52fd16524e746d860a5634b92", + "signature": "a7c22af6a315bd946cb8f933a29dbdd6a80192c4147b4d208ef3a818e208edbf6058c36a51a31456b16e853eaa830d0d3cc075371b65188f07ba6220fc9c906801" + }, + { + "hash": "e3590881f4b0b72083a8d7ec269d3c11ed8daf2a9a187348b04205f1bb036548", + "secret": "e1aa8dd19ac8b8bfb872d4340241ba096a16f44417f7425bc1dd1d20d5acd35b", + "signature": "731551bcd244e4a05f7cca7aca9a93f5029bfcf8f6cfae30e8ae610c569985f634a6c871a775033480f00263fb138b5260acdd8b93df9647db6e6b3cc20a4f6301" + }, + { + "hash": "a43034b5cf06b3d905b376e178ea231208cb830aa4cc7679ec49e7720ef873a5", + "secret": "0d58f19d5eaa6c3e8659507395125ae60099cef7f1255a13c217cd61f2e6ae4d", + "signature": "f5bba2ef38972af254b97edc3b24982dd24a505ba1f339c18fe714f8fdbdb20b21f9efeb0a019ff2060d14b655687ef70b2c09e6e8596cadef183de4d6c5283200" + }, + { + "hash": "1287ac880b34700a5689fe03ee7fcf6b13d988680e69e9272455d3fb38a5e246", + "secret": "0f91d900b120b9c6960878f8985772d8cf29ffae77748aff0889183ed2bcd618", + "signature": "c5bc42118301c8ff4631e652b4d6bef8c13b521bd404d5e7af710c9ab63c248048cfedfa380ca3d966a718a36fcf3c6a1f456c8f8b47b2fd83931fcb4f02d08e01" + }, + { + "hash": "5c67aca884da5f738b1624c501bf4063540a2153d602aaff56ebac89e11dcd1c", + "secret": "81f54e72796d6a447bcff6b1022bf6cbf48b2b39160b9e2a4fa07116783c8bf9", + "signature": "0e322a34ce048d9497562c53cff8d3266e889513f489decccdbbcfce99ffd478796852b4b6fbb07e6f58651347d00b99c5a2c5633f03baeab5314aa6cb58ecba01" + }, + { + "hash": "01133a236bba209b10a710a8f70a4fce5eb043306efa3ba74ae6bfe016e0b7de", + "secret": "ce469cba4dd74fff54257d98deb1ec0f4b4d92f13c22ccf88327729fb20a91a9", + "signature": "4e893c2de72f2b9bef8adc86ac6da301a0e774feb3ec10c6995262b3a554f209732bd0ae41bfe5fc1b863cb08d74ef15fa250264649030b30c8fe44e894c171601" + }, + { + "hash": "c02ae83c3be29fb6e9ee687c4f01cea32ce8bedba3f5441a6cca28b38ae92c54", + "secret": "209eb71c8e9e5de35e67440e331d2f2f31aff08410baa2e0ca7eb29cf20fe003", + "signature": "882d92979f3fd4df2b19eecea6b4ca3104898774e83506d934736a80697a19366f4fde413d1fcf73adc00dd3938be427cea62a2aa166eab209703a3f63bd77fc00" + }, + { + "hash": "f536d9eb429ba0bc5459fa7960c7693775004ea9e57d56e3e94c3b196c49bbb4", + "secret": "359484ee9ea253e1f791d1a17f790f79f6607bbe26146aa8ac542d8612858e1a", + "signature": "7c78ceba9be9ddf18f2b5bc4414d795d882564a768a78f28702570bb04d0a65e17184e9ef667a2ad8e689ae8c03c8eb73117436035653481f297cd84a1f67a9301" + }, + { + "hash": "0a67a0dfdb30f0a6e5863a62b6b7659d0276c7b12c8a8ea87478fd921c3101c9", + "secret": "29aeae7ac718964988b35969113c14b39c81e2064794d92af09b4a1d27804267", + "signature": "63982045a58db529208243ddeab9b5835d7ce4d45cc557f9b7ecbfd1e9ceebe5567a65ad3f50a8e365146a7fc1490dc5bfd53bcb22e6256ec4c18047caaa4a1b00" + }, + { + "hash": "a8a12969ea054930d0898b91184929ac78ad0fd313ac7e809deafc7e01b9a368", + "secret": "75b84e21a454b7e454a17e054d0657d1c9ccb801e8579f5b9cebe9249e020fe6", + "signature": "383cd3371af70ee93ddd440643196ef945a2c9918be5381f6c41052f6e0279730372f224b2d6be3fcc0f9fb1e2d974923e2ff6129a1dcce73d2ce2d3442d30d001" + }, + { + "hash": "5ac6af499cb44bd57dfc9ceda8316c43e02627f14722e2ed6d0051e9539a2406", + "secret": "92292d68d69cc60544f58f51a947bf3b1b5c7f1015bc04a8a3b7ba93d74191c6", + "signature": "100380c86d1e6d552d5d940fb9670814b45ef2eb5818a69a6ede13e64aad9d7f36245f21b758aac79b46d3df520bdd96a62f052762fb371e56b5aa288c0b849e01" + }, + { + "hash": "07497143b21ed20745359b553d106c25d77304995206d3df2d0248a8ddf23089", + "secret": "650ad13e7686f91d2c92f7b8fd1e8d61fc9143774d3bc1b7826b1642d39bd6b9", + "signature": "a77d5774655680c3920641003a86a5be888c45a9da3f8f40d28f08e2925409b119cdf82f7ad7f7b954123cc73c96256373461d949e6befb51da41290f79608ff01" + }, + { + "hash": "06fd74e0d7c1d5337c6a370a8c6b7373449521af7cba1b7ef7959b23e711a054", + "secret": "d528eccaf41259f212192f4c02e2ac07f5caf759b927c7b012ea15639476fc3f", + "signature": "0bb24124b2eac1ce7f9acd56c40c8e1ebe46534ea6d2bc0f4383e11084ad2783772209e0343be1ad24e5dc2c5f57d301f1c73fd34cded83986321c4360a502cb01" + }, + { + "hash": "883771ff758909a7599473c3fbf94c24244c8cda06c1bac871a18d293fce0bbf", + "secret": "e151f9e575c9e0540b68dc277a2223539a8344c3061d6727599c28363d3b233d", + "signature": "73fd11a7b59ddbaec26cd5bea43a54dca96dd5af9592ea953217e46354d4e3cf27bda4fdae1faed15039179f7c7bcd4cbbc513aaa6a9486f69219dd7f70219e101" + }, + { + "hash": "0df5e39277381f18a0d775d5d286a764584c69dcbe32a9a5c7e507efaeb27e1f", + "secret": "00c4a04d39bb8c57322973905e3c8c4b18c87246cc848cf6a8cde7a0db70ca63", + "signature": "970b0bfeb72b54932915c8b435755af319bc2f06bc855eb2ea1b9d95651edf6b181e7972820bbc0baa82537a6ae223558e18476fbd0a01bdcd495f3955dc0c8300" + }, + { + "hash": "6df9a6205bd51fd47b464010bed49292ce0c11ffa4b71dbf1d138ad085eb0259", + "secret": "2dc5614cd6016205763887255255caefe7d45fad6533af075923ebfd72826bef", + "signature": "3d662fff2fd66147b8d26126c0c733113f2e9d1d1538513c6f1fe1e24dc6acb73668517cdf18edabf93a8673837e15613f0f5e6908e374093bc3ab067bf976cd01" + }, + { + "hash": "f3a83eba1d296ae5f2ee8187a3bafb895d1f2a15aa0aefb5f8e5369d2a37dbf4", + "secret": "41065f52afeceefe3f0c3cad6b17203c1588d467179c51be86b15a2aeba12abb", + "signature": "950ae774809cc3b55a36f1545340ac5e7a389b7f3a36f1a4f0a91b81518d748c47d9aeef9634f11296abea27336dbf36c5aeb515b497c6ceb8439a14238162b701" + }, + { + "hash": "a7f45622b70ff8d2fd31dd7440cebbf75294ce9429da31ae167473a90f6d9fd0", + "secret": "6d5ec055def0492d9fa613b0fcfaf80f7ff01316bfd8a2e0a4b8caef80df2f5a", + "signature": "448a6aeeb7163b08fdc0b9a2a2f412ccf215feee8d98c76c84b157c3a204c52a041ba06ffe90dd39732ef07342fdc4c7f03a6d895036a9208455732cff6effe200" + }, + { + "hash": "31ad8350e908b50fc1962b8d92fecf3d1ffee7d04a75545b1f4c5facd18cc6ed", + "secret": "408fd6b154cd826297934641614fd02327ec55ad86fb0440c170bfa82ab55025", + "signature": "7537f706fa6c98bb0e53d45d0531cea5ac2e3d92327fb867211a316ea15a2ea611e89049859edbbf78c0a9bd04de39bae265d6b23b4ae5df19c3529bed05be5801" + }, + { + "hash": "1466a7f0f1836ff904e250cb68ea1780bb36f1f91b26fa0dc46cbe20607ba3af", + "secret": "b393312821e73f19eb762a8b4148977236eec4e62cd49b27d2d9de7d1290a2e5", + "signature": "8d94d905a984fc65e9ff50370c30f1eb8cce5dc373c4744d6e77ce8caad957cc06e2c895f32b5f14a63c71b78e25ac3454507bc5b709aa84bf80713bdac6ba1f01" + }, + { + "hash": "b7b9eb14091e5b7c58275b94a7b18deab7e15511873e5e0446d0a7e33be29a03", + "secret": "21ad5b12aba0f73925faffa0dfdfd6a392e5fdc25e622c90be0a9e3996e875ca", + "signature": "8e56b3cbbaf6aa67590e1fbcf08998a0b5d224e90462b7fca645d4b16d056a7e20f8f4dacaa635c51b6ee3cfd71fa54e71ff25f167b35eafa1c6b91686274d3200" + }, + { + "hash": "dcc962043b56a88c57829ff8711fe930aaf4d5b536a2a77d4ad5872d56951d9c", + "secret": "59d21db2cfa096c2f12fe6470331ed422f8b1523810a8602217b1e8b30f4c5e4", + "signature": "5a5ac663960d84b3e0432c6497d5b49c6e1852c839e19169ab48fd0aeeeda28f10b6a106119ca4a73328d264c301657d443b77d01ae19737f7cde37ce128a81501" + }, + { + "hash": "ed80800e8c54311caa90f6500919e73c74e8fe63b274f77d1e0510c28ebbf794", + "secret": "0df0a1d3c6fc9c2998eca608d63a9227b345c3ca5d07b033ed270148b980c1ce", + "signature": "c59abb615cfe8e010afafbb5705b1a82017ef8804c423d17be59996476bfb14a108f77fcb6e1e591100055b36e5ac1932413f4c8eb696065b6a0f00c88ce1df101" + }, + { + "hash": "625e6831a31ab003c8fc67f5966ccb4cfca04ea958cffa62da790cb9c709097c", + "secret": "112c9ca4426ae3c15bcd2bb09c29e9ed1fb84d3e01ef9fc9cf91d8eb02a29781", + "signature": "bfa82e403eb4b5d93f3dde39170975b3ba8df615b0fd81fd7963f32c1ae6fe976a43be2e74b17d4880da0f4a437ddc6a3aed938c988f096bfc321316caebd99900" + }, + { + "hash": "297d4f7b3342a3379b6ceab7a00299cb62a57b1c1a242f26bb1029cd3330e834", + "secret": "47520e3ba5aba9384d865176422be4179bc800b95c620fb086b67adad7fbbfa3", + "signature": "0af9b607ca5c239449e6a61231e625f2b910d6e91f06b2c67fa53c3c005abb4528a982f4078b18b0b97ba9a63daef121059c8489116d9e815af65c18953d118a01" + }, + { + "hash": "77e3814bd70a03368799cc5832e56c013c56b9c0b0bd3e2b567fcb71c32ed28e", + "secret": "20e5e49627e340c1b69bdbd341df97626968bfdc338e4e2e8761a4b95029c2b0", + "signature": "8aff739ee3e77f81c2fc8420c361d26dcbfd7721879a58b902866f8d3d40b2bf33793318ffeea1fde04ae92c62d115e9c9820d26b438ee3b1c4007794bb4432800" + }, + { + "hash": "998a4f780984056368e0ed5594ae1baec3cea5a265f425d08147e6fa0574399e", + "secret": "00c1f966de7e9a697eee6285692c89405936050e5ca515e3dd215d45a9cb30db", + "signature": "31b0d5bb010ea160e52b3c4dc65330200af2ceebe84a36185caaf1d0fe3e5df22a9a71525a4e7ece31a280cb6c70256cbd397bb3b01350ab3cab0e1b9512b6c800" + }, + { + "hash": "712acfad93cc8a23b6d2025f8e9fa7de633c757d62414b7cc1c9cdaf4145f014", + "secret": "3b3ed15fb5fce6ee908c674d3473c3b53d20486b449a6ced8c63b35134c558ab", + "signature": "95cbd1cff963ff5fb12ac9b88c5542d9e80b6598a78a6a483b8821cf461108b4208f482624a168bf446df32695d0460a6a699a12dc8a7b66f6b1d56b668d106b00" + }, + { + "hash": "bf03271a3d3cac46a2f4af85ab4f28dc94b60845e8e97c5513cc26d33029c583", + "secret": "08b9e31e9593601e7cfca3418efe530b4780b525a08af0d7b0027ec8af3af148", + "signature": "6582629fbd61152183129c373519d0008384284928ebeccc367aa99aa1405cf821db10ada6fec1480de397fdedf924b7d845f318a3e01e1f40a2eaeb68f8815d01" + }, + { + "hash": "0054212f799cbc36e84771c779dc23018b98ea415311428bd6b4788eaafbb24c", + "secret": "b66c1c37869c3033dc1d5f47a50bac9b0addc121b401bcf0b5a9113080711dbe", + "signature": "73c3aa168c21b93a8f126b008bb0abbeb5130ab50cd30195d0d2bdcfaa87031940dcad39d4e7b2fc93768eb218683ed9ca34ccc7556b3fad6aee45f1d79fd6bf01" + }, + { + "hash": "f5e6eb9c9f0b22b2d86444c1ed8fea751ccb8b5de0ca90610380960198808301", + "secret": "a2af24e33b9995b4f2317c22094d7d564516d5a3b9d6613d988403d704ae2dae", + "signature": "5537b5fd15e0183565126b49799e87c5f322fdc40a4eb8bb96641961c86fe6557662a9478ec75b25617beb3f317b394d896eea6552c96a10b43ae08c2aaa56a001" + }, + { + "hash": "df2655b91f9979e7ef9934f6d42c70506e9fdc4c9a631c219d5c2cbcc5b2fbf2", + "secret": "a582bfc617c3d71b8f1d36a78d5da56a263f2cd4402a8ac3d1407fa7570d49dd", + "signature": "1d0c768064da2f755ca3027070315cee087ecee60abe9552a0c88d8485fb440c65e837a2390a68389996bb24f7aa8ec1414c8d05721531b76cefac67a0b8bffb01" + }, + { + "hash": "92a55da0a5a40ce354fcea2e1e19e423c4bc1918b27cb1da9a82bbd19739c656", + "secret": "54e3763bffdc23be470911b4a00cf1823402d60e6b3f05cb412eb743c710e6f0", + "signature": "ea15a3a4f3b394ae794f9011232b6475af40f70f4f0e56beaad1866080ddd064710d38451c93e451aa5e45e7bbba53a60d101a9ccecc8f81e1a450e8fca63e5c00" + }, + { + "hash": "dc789ac9ee186b72ed75874ddfec75404a325ce715f4acf0e7f15716873d27a5", + "secret": "eed38ac3a51d0b3b30ca0ba931a2eccc581978287af8dfe83c8a13f18063e4b1", + "signature": "b723f6c7ca1ed1cb2ac085cf084596c507471de79300071f54e00d857e9b3e502ecb9449f8a9e2d5d9983197b28dbf80249357ee5335b5c2c973de474ac2966501" + }, + { + "hash": "256bf871b80790aeeb3129684e6577094e892de32277d56335b6efa548c7760c", + "secret": "1d92a4d3e7702fc569e34b8c03a6002454d2a7b7ce1fb9e9e68fc1c22079b2a2", + "signature": "0cefc1979b394dc0ee569bdeb61e93abeeca083cddd1679a0216faaa5c2ee75136121fc441fa56eae40283ff8d9961deb8f12c2053f7e0d2a5e0e2b07398581800" + }, + { + "hash": "135dac79c2c15df5df784efc77d01f37fae8391f744e0754b3c2ced4b1aa4f35", + "secret": "cc4a32d268af6bd954854cc7a566fbbad87014735355bf37ff9815a32ad711bb", + "signature": "76be7b98542d4201fb6841fb729e6d0c864826632b6f88ff3865a26c6a134e743508e6d2cf5fc314600e0af433d9feef330bc470d9facd032fed13c47ab84e7701" + }, + { + "hash": "32dc1a4cacd866796365578fc9f34933d045181850fc595f308fce0b098179be", + "secret": "5d6353c5e80c2c8484a7fba5c8747acea369b3867e38202847d7412b9fffa3ec", + "signature": "3d4e87f439c79a4a41d2e3ae85012bb2be536e576f0d9b443e05dde31f6b764150d3890ba16d0831a161e0b46d953aa428da51ee32a0b4fdcee4a3474dbbdb5300" + }, + { + "hash": "edb61c559d972631edfc038ed12ec70c4f7364edb160622e30af1e983b26f44c", + "secret": "eae7552961000ec6c486803e006c5e9c8a426df8d712844616dfbf27845297c3", + "signature": "354b81c1993efae290a52bfbe4bf0fe8b1bbb311e75315a450ae5459fc2559a304af37c0c47e96238f487444ec022c23245814d0af97691d1578caa043fc575800" + }, + { + "hash": "3dc69be3da8b0c46ec7abb0b50d1232136ce3d2e5906d8714165ffc679145af0", + "secret": "1e10080c45b5b583072e1685f6afe98c357ed0d7dafb6c3aa63f06fbe47adcb2", + "signature": "9539625576b54478ac7bf94d107d1106a3ac9a0664080fb29fbaf3d0a8ce3882204faccb5b8b798b611e73d089326ff40e4fb497d50e563e649d5712c752c19600" + }, + { + "hash": "d60d71d332ef3ea77188f8845182b78bbae330790cdef1f08689d5466405c675", + "secret": "a6971000bf7ce5b2ab4529f60836de2a818ec2f643294d438d3973443a014051", + "signature": "4dcbedef2023d1812b3fcbc87b692be63363382a470073e4ab4116920b8e93da6f8b5671f51b16d7437df976e080a4eea99c5d07452b74f64ff0f5d7c5de648701" + }, + { + "hash": "2cc264741c237b61b59376c554424ad49ab4c92fa91fd0ed44dc27755525779e", + "secret": "b1aae3bf6f3a423291dc61dfc1a7d463a8e0d3823c243e99dd53228acf4d37f6", + "signature": "7f58d5e3289bb3e057789ed49841dc45ff5b94b31e1572d144c9544fe1bc30e21cb31301f2754e4ad6f471fd5610287245bdda11766038706b0e70bb1f46c38f00" + }, + { + "hash": "bf50b828447a139678010329af94d718f026c8275646cb6aa3b88d0536edb278", + "secret": "9d92aa2dd3d49f846e399d820642eafde6e47341ed863643225ffb68d982f8cd", + "signature": "7d1f5708d69f77b0df5996604c2eca23c9c24e4c29929d3a2d012aec01e42f8a00fd2f5f9b4dee732c93bba18a6680c771487a42055498e217ad72f2b71c824a00" + }, + { + "hash": "60eb03e7c48e7ce8cd7b9801957e97f15a4685441ab62e152294faed5f70447a", + "secret": "22cf83b25deb53cfe31c555fd0255b3272d7ab61f2aca1f19f36cc4bd82cea49", + "signature": "0aabd16e289762f9c363329e883b505f1c85c8fb0df7f383db2c7f651794ae2d6095285a7588187c78527d3b45438085bb69610c959dae58a08b1b43334f1d7b00" + } +] \ No newline at end of file diff --git a/rust/tw_keypair/tests/secp256k1_tests.rs b/rust/tw_keypair/tests/secp256k1_tests.rs new file mode 100644 index 00000000000..05a2c4f6a93 --- /dev/null +++ b/rust/tw_keypair/tests/secp256k1_tests.rs @@ -0,0 +1,33 @@ +// 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 serde::Deserialize; +use tw_hash::{H256, H520}; +use tw_keypair::secp256k1::{KeyPair, VerifySignature}; +use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; + +/// The tests were generated in C++ using the `trezor-crypto` library. +const SECP256K1_SIGN: &str = include_str!("secp256k1_sign.json"); + +#[derive(Deserialize)] +struct Secp256k1SignTest { + secret: H256, + hash: H256, + signature: H520, +} + +#[test] +fn test_secp256k1_sign_verify() { + let tests: Vec = serde_json::from_str(SECP256K1_SIGN).unwrap(); + for test in tests { + let keypair = KeyPair::try_from(test.secret.as_slice()).unwrap(); + let actual = keypair.sign(test.hash).unwrap(); + assert_eq!(actual.to_bytes(), test.signature); + + let verify_sign = VerifySignature::from(actual); + assert!(keypair.verify(verify_sign, test.hash)); + } +} diff --git a/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs new file mode 100644 index 00000000000..40d28c2417d --- /dev/null +++ b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs @@ -0,0 +1,38 @@ +// 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 tw_encoding::hex; +use tw_hash::H512; +use tw_keypair::tw::{Curve, PrivateKey, PublicKeyType}; + +#[test] +fn test_starkex_tw_private_key() { + let privkey_bytes = + hex::decode("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe").unwrap(); + let pubkey_bytes = + hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd").unwrap(); + + let privkey = PrivateKey::new(privkey_bytes.clone()).unwrap(); + assert_eq!(privkey.key().into_vec(), privkey_bytes); + + let public = privkey + .get_public_key_by_type(PublicKeyType::Starkex) + .unwrap(); + assert_eq!(public.to_bytes(), pubkey_bytes); +} + +#[test] +fn test_starkex_tw_private_key_sign() { + let privkey_bytes = + hex::decode("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79").unwrap(); + let hash_to_sign = + hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76").unwrap(); + + let privkey = PrivateKey::new(privkey_bytes).unwrap(); + let actual = privkey.sign(&hash_to_sign, Curve::Starkex).unwrap(); + let expected = H512::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + assert_eq!(actual, expected.into_vec()); +} diff --git a/rust/tw_memory/src/ffi/c_byte_array.rs b/rust/tw_memory/src/ffi/c_byte_array.rs index 65f04e2f13d..337cff99677 100644 --- a/rust/tw_memory/src/ffi/c_byte_array.rs +++ b/rust/tw_memory/src/ffi/c_byte_array.rs @@ -28,8 +28,22 @@ impl Drop for CByteArray { } impl From> for CByteArray { - fn from(data: Vec) -> Self { - CByteArray::new(data) + fn from(mut mut_vec: Vec) -> Self { + let data = mut_vec.as_mut_ptr(); + let size = mut_vec.len(); + let capacity = mut_vec.capacity(); + std::mem::forget(mut_vec); + CByteArray { + data, + size, + capacity, + } + } +} + +impl Default for CByteArray { + fn default() -> Self { + CByteArray::new() } } @@ -43,17 +57,9 @@ impl CByteArray { } } - /// Returns a `CByteArray` instance from the given `mut_vec` bytes. - pub fn new(mut mut_vec: Vec) -> CByteArray { - let data = mut_vec.as_mut_ptr(); - let size = mut_vec.len(); - let capacity = mut_vec.capacity(); - std::mem::forget(mut_vec); - CByteArray { - data, - size, - capacity, - } + /// Returns an empty `CByteArray` instance. + pub fn new() -> CByteArray { + CByteArray::from(Vec::new()) } /// Converts `CByteArray` into `Vec` without additional allocation. @@ -95,6 +101,11 @@ impl CByteArray { pub fn size(&self) -> usize { self.size } + + /// Returns the data slice. + pub unsafe fn as_slice(&self) -> &[u8] { + std::slice::from_raw_parts(self.data, self.size) + } } /// Releases the memory previously allocated for the pointer to `CByteArray`. diff --git a/rust/tw_memory/src/ffi/mod.rs b/rust/tw_memory/src/ffi/mod.rs index 058f841646a..f9664fa35b7 100644 --- a/rust/tw_memory/src/ffi/mod.rs +++ b/rust/tw_memory/src/ffi/mod.rs @@ -38,11 +38,4 @@ pub trait RawPtrTrait: Sized { } Some(&*raw) } - - unsafe fn from_ptr_as_box(raw: *mut Self) -> Option> { - if raw.is_null() { - return None; - } - Some(Box::from_raw(raw)) - } } diff --git a/rust/tw_memory/tests/c_byte_array_ffi_tests.rs b/rust/tw_memory/tests/c_byte_array_ffi_tests.rs index 5cbd8078f11..e3fe8e41ba3 100644 --- a/rust/tw_memory/tests/c_byte_array_ffi_tests.rs +++ b/rust/tw_memory/tests/c_byte_array_ffi_tests.rs @@ -11,7 +11,7 @@ fn test_free_c_byte_array() { unsafe { free_c_byte_array(std::ptr::null_mut()); - let mut raw_array = CByteArray::new(vec![1, 2, 3]); + let mut raw_array = CByteArray::from(vec![1, 2, 3]); free_c_byte_array(&mut raw_array as *mut CByteArray); // The following leads to an undefined behaviour. @@ -22,12 +22,12 @@ fn test_free_c_byte_array() { #[test] fn test_drop_c_byte_array() { // The memory must be released on `Drop::drop`. - let _ = CByteArray::new(vec![1, 2, 3]); + let _ = CByteArray::from(vec![1, 2, 3]); } #[test] fn test_c_byte_array_into_vec() { // The memory must be valid after `CByteArray::into_vec` and `CByteArray::drop`. - let data = unsafe { CByteArray::new(vec![1, 2, 3]).into_vec() }; + let data = unsafe { CByteArray::from(vec![1, 2, 3]).into_vec() }; assert_eq!(data, [1, 2, 3]); } diff --git a/rust/tw_memory/tests/c_result_ffi_tests.rs b/rust/tw_memory/tests/c_result_ffi_tests.rs index 58e11216516..dca5cee3f9e 100644 --- a/rust/tw_memory/tests/c_result_ffi_tests.rs +++ b/rust/tw_memory/tests/c_result_ffi_tests.rs @@ -9,7 +9,7 @@ use tw_memory::ffi::c_result::{OK_CODE, UNKNOWN_ERROR}; #[test] fn test_c_result_unwrap() { - let c_res = CByteArrayResult::ok(CByteArray::new(vec![1, 2, 3])); + let c_res = CByteArrayResult::ok(CByteArray::from(vec![1, 2, 3])); assert!(c_res.is_ok()); assert!(!c_res.is_err()); @@ -35,7 +35,7 @@ fn test_c_result_error_with_ok_code() { #[test] fn test_c_result_into_result() { - let c_res = CByteArrayResult::ok(CByteArray::new(vec![1, 2, 3])); + let c_res = CByteArrayResult::ok(CByteArray::from(vec![1, 2, 3])); c_res.into_result().unwrap(); let c_res = CByteArrayResult::error(10); diff --git a/rust/tw_proto/tests/proto_ffi_tests.rs b/rust/tw_proto/tests/proto_ffi_tests.rs index 654141268da..ce3df7c6a03 100644 --- a/rust/tw_proto/tests/proto_ffi_tests.rs +++ b/rust/tw_proto/tests/proto_ffi_tests.rs @@ -11,7 +11,7 @@ use tw_memory::ffi::c_byte_array::CByteArray; fn test_pass_eth_signing_msg_through() { let serialized = tw_encoding::hex::decode("0a0101120100220509c76524002a030130b9422a3078366231373534373465383930393463343464613938623935346565646561633439353237316430664a20608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151523812360a2a30783533323262333463383865643036393139373162663532613730343734343866306634656663383412081bc16d674ec80000").unwrap(); let actual = unsafe { - let array = CByteArray::new(serialized.clone()); + let array = CByteArray::from(serialized.clone()); tw_proto::ffi::pass_eth_signing_msg_through(array.data(), array.size()) .unwrap() .into_vec() diff --git a/rust/tw_starknet/Cargo.toml b/rust/tw_starknet/Cargo.toml deleted file mode 100644 index 8aa39b7880c..00000000000 --- a/rust/tw_starknet/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tw_starknet" -version = "0.1.0" -edition = "2021" - -[dependencies] -hex = "0.4.3" -starknet-crypto = "0.5.1" -starknet-ff = "0.3.3" -tw_encoding = { path = "../tw_encoding" } -tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_starknet/src/ffi.rs b/rust/tw_starknet/src/ffi.rs deleted file mode 100644 index 79f243b52fe..00000000000 --- a/rust/tw_starknet/src/ffi.rs +++ /dev/null @@ -1,97 +0,0 @@ -// 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. - -#![allow(clippy::missing_safety_doc)] - -use crate::key_pair; -use std::ffi::{c_char, CStr}; -use tw_memory::ffi::c_result::{CBoolResult, CStrResult, ErrorCode}; - -#[repr(C)] -pub enum CStarknetCode { - Ok = 0, - InvalidInput = 1, - PrivKeyError = 2, -} - -impl From for ErrorCode { - fn from(code: CStarknetCode) -> Self { - code as ErrorCode - } -} - -impl From for CStarknetCode { - fn from(_: key_pair::StarknetKeyPairError) -> Self { - CStarknetCode::PrivKeyError - } -} - -/// Returns a StarkNet pubkey corresponding to the given `priv_key`. -/// \param priv_key - *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string. -#[no_mangle] -pub unsafe extern "C" fn starknet_pubkey_from_private(priv_key: *const c_char) -> CStrResult { - let priv_key = match CStr::from_ptr(priv_key).to_str() { - Ok(priv_key) => priv_key, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - key_pair::starknet_pubkey_from_private(priv_key) - .map(tw_memory::c_string_standalone) - .map_err(CStarknetCode::from) - .into() -} - -/// Signs the input `hash` with the given `priv_key` and returns a signature compatible with StarkNet. -/// \param priv_key *non-null* C-compatible, nul-terminated string. -/// \param hash *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string. -#[no_mangle] -pub unsafe extern "C" fn starknet_sign(priv_key: *const c_char, hash: *const c_char) -> CStrResult { - let priv_key = match CStr::from_ptr(priv_key).to_str() { - Ok(priv_key) => priv_key, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - let hash = match CStr::from_ptr(hash).to_str() { - Ok(hash) => hash, - Err(_) => return CStrResult::error(CStarknetCode::InvalidInput), - }; - key_pair::starknet_sign(priv_key, hash) - .map(tw_memory::c_string_standalone) - .map_err(CStarknetCode::from) - .into() -} - -/// Verifies if the given signature (`r` and `s`) is valid over a message `hash` given a StarkNet `pub_key`. -/// \param pub_key *non-null* C-compatible, nul-terminated string. -/// \param hash *non-null* C-compatible, nul-terminated string. -/// \param r *non-null* C-compatible, nul-terminated string. -/// \param s *non-null* C-compatible, nul-terminated string. -/// \return true if the signature is valid. -#[no_mangle] -pub unsafe extern "C" fn starknet_verify( - pub_key: *const c_char, - hash: *const c_char, - r: *const c_char, - s: *const c_char, -) -> CBoolResult { - macro_rules! parse_c_str { - ($s:expr) => { - match CStr::from_ptr($s).to_str() { - Ok(s) => s, - Err(_) => return CBoolResult::error(CStarknetCode::InvalidInput), - } - }; - } - - let pub_key = parse_c_str!(pub_key); - let hash = parse_c_str!(hash); - let r = parse_c_str!(r); - let s = parse_c_str!(s); - - key_pair::starknet_verify(pub_key, hash, r, s) - .map_err(CStarknetCode::from) - .into() -} diff --git a/rust/tw_starknet/src/key_pair.rs b/rust/tw_starknet/src/key_pair.rs deleted file mode 100644 index 1d63b6f6f56..00000000000 --- a/rust/tw_starknet/src/key_pair.rs +++ /dev/null @@ -1,107 +0,0 @@ -// 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 starknet_crypto::{get_public_key, rfc6979_generate_k, sign, verify, SignError, Signature}; -use starknet_ff::{FieldElement, FromByteArrayError}; -use tw_encoding::hex::{self as tw_hex, FromHexError}; - -/// The maximum number of attempts to sign a message. -/// As the number is coming from `rfc6979_generate_k` so the probability is lower. -const SIGN_RETRIES: usize = 5; - -pub type Result = std::result::Result; - -#[derive(Debug)] -pub enum StarknetKeyPairError { - HexError(FromHexError), - ByteArrayError(FromByteArrayError), - InvalidLength, - ErrorSigning, -} - -impl From for StarknetKeyPairError { - fn from(err: FromHexError) -> Self { - StarknetKeyPairError::HexError(err) - } -} - -impl From for StarknetKeyPairError { - fn from(err: FromByteArrayError) -> Self { - StarknetKeyPairError::ByteArrayError(err) - } -} - -pub fn starknet_pubkey_from_private(priv_key: &str) -> Result { - let private_key = field_element_from_be_hex(priv_key)?; - Ok(format!("{:#02x}", get_public_key(&private_key))) -} - -pub fn starknet_sign(priv_key: &str, hash: &str) -> Result { - let private_key = field_element_from_be_hex(priv_key)?; - let hash_field = field_element_from_be_hex(hash)?; - let signature = ecdsa_sign(&private_key, &hash_field)?; - Ok(signature.to_string()) -} - -pub fn starknet_verify(pub_key: &str, hash: &str, r: &str, s: &str) -> Result { - let pub_key = field_element_from_be_hex(pub_key)?; - let hash = field_element_from_be_hex(hash)?; - let r = field_element_from_be_hex(r)?; - let s = field_element_from_be_hex(s)?; - - Ok(verify(&pub_key, &hash, &r, &s).unwrap_or_default()) -} - -fn field_element_from_be_hex(hex: &str) -> Result { - let decoded = tw_hex::decode(hex)?; - if decoded.len() > 32 { - return Err(StarknetKeyPairError::InvalidLength); - } - - let mut buffer = [0u8; 32]; - buffer[(32 - decoded.len())..].copy_from_slice(&decoded[..]); - - FieldElement::from_bytes_be(&buffer).map_err(StarknetKeyPairError::from) -} - -/// `starknet-core` depends on an out-dated `starknet-crypto` crate. -/// We need to reimplement the same but using the latest `starknet-crypto` version. -/// https://github.com/xJonathanLEI/starknet-rs/blob/0c78b365c2a7a7d4138553cba42fa69d695aa73d/starknet-core/src/crypto.rs#L34-L59 -pub fn ecdsa_sign(private_key: &FieldElement, message_hash: &FieldElement) -> Result { - // Seed-retry logic ported from `cairo-lang` - let mut seed = None; - for _ in 0..SIGN_RETRIES { - let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref()); - - match sign(private_key, message_hash, &k) { - Ok(sig) => { - return Ok(Signature { r: sig.r, s: sig.s }); - }, - Err(SignError::InvalidMessageHash) => return Err(StarknetKeyPairError::ErrorSigning), - Err(SignError::InvalidK) => { - // Bump seed and retry - seed = match seed { - Some(prev_seed) => Some(prev_seed + FieldElement::ONE), - None => Some(FieldElement::ONE), - }; - }, - }; - } - Err(StarknetKeyPairError::ErrorSigning) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_starknet_sign_invalid_k() { - let private = "0000000000000000000000000000000000000000000000000000000000000000"; - let hash = "0000000000000000000000000000000000000000000000000000000000000000"; - let err = starknet_sign(private, hash).expect_err("Retry limit expected"); - assert!(matches!(err, StarknetKeyPairError::ErrorSigning)); - } -} diff --git a/rust/tw_starknet/src/lib.rs b/rust/tw_starknet/src/lib.rs deleted file mode 100644 index fc5cac0c0ae..00000000000 --- a/rust/tw_starknet/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -// 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. - -pub mod ffi; -pub mod key_pair; diff --git a/rust/tw_starknet/tests/starknet_ffi_tests.rs b/rust/tw_starknet/tests/starknet_ffi_tests.rs deleted file mode 100644 index 958a08847fe..00000000000 --- a/rust/tw_starknet/tests/starknet_ffi_tests.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::ffi::{c_char, CString}; -use tw_memory::c_string_standalone; -use tw_memory::ffi::c_result::OK_CODE; -use tw_memory::ffi::free_string; -use tw_starknet::ffi::{ - starknet_pubkey_from_private, starknet_sign, starknet_verify, CStarknetCode, -}; - -#[test] -fn test_starknet_pubkey_from_private() { - let priv_key_hex = - c_string_standalone("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe"); - let pub_key_raw = unsafe { starknet_pubkey_from_private(priv_key_hex) }; - assert_eq!(pub_key_raw.code, OK_CODE); - let actual = unsafe { - CString::from_raw(pub_key_raw.result as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!( - actual, - "0x2a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd" - ); - - unsafe { free_string(priv_key_hex) }; -} - -#[test] -fn test_starknet_pubkey_from_private_invalid() { - let priv_key_raw = - c_string_standalone("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afx"); - let pub_key_raw = unsafe { starknet_pubkey_from_private(priv_key_raw) }; - assert_eq!(pub_key_raw.code, CStarknetCode::PrivKeyError as i32); - assert!(pub_key_raw.result.is_null()); - - // Non-null terminated string. - let invalid_data = vec![159, 146, 150]; - let invalid_priv_key_ptr = invalid_data.as_ptr() as *const c_char; - - let pub_key_raw = unsafe { starknet_pubkey_from_private(invalid_priv_key_ptr) }; - assert_eq!(pub_key_raw.code, CStarknetCode::InvalidInput as i32); - assert!(pub_key_raw.result.is_null()); - - unsafe { free_string(priv_key_raw) }; -} - -#[test] -fn test_starknet_sign() { - let priv_key_raw = - c_string_standalone("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); - let digest_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - - let signature = unsafe { starknet_sign(priv_key_raw, digest_raw) }; - assert_eq!(signature.code, OK_CODE); - let actual = unsafe { CString::from_raw(signature.result.cast_mut()) } - .into_string() - .unwrap(); - let expected = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; - assert_eq!(actual, expected); - - unsafe { free_string(priv_key_raw) }; - unsafe { free_string(digest_raw) }; -} - -#[test] -fn test_starknet_verify() { - let pubkey_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); - let hash_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - let signature_r_raw = - c_string_standalone("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); - let signature_s_raw = - c_string_standalone("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); - - let res = unsafe { starknet_verify(pubkey_raw, hash_raw, signature_r_raw, signature_s_raw) }; - assert_eq!(res.code, OK_CODE); - assert!(res.result); - - unsafe { free_string(pubkey_raw) }; - unsafe { free_string(hash_raw) }; - unsafe { free_string(signature_r_raw) }; - unsafe { free_string(signature_s_raw) }; -} - -#[test] -fn test_starknet_verify_fail() { - let pubkey_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); - let hash_raw = - c_string_standalone("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - let signature_r_raw = - c_string_standalone("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); - let signature_s_raw = - c_string_standalone("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b"); - - let res = unsafe { starknet_verify(pubkey_raw, hash_raw, signature_r_raw, signature_s_raw) }; - assert_eq!(res.code, OK_CODE); - assert!(!res.result); - - unsafe { free_string(pubkey_raw) }; - unsafe { free_string(hash_raw) }; - unsafe { free_string(signature_r_raw) }; - unsafe { free_string(signature_s_raw) }; -} - -#[test] -fn test_starknet_private_key_invalid() { - let private_raw = - c_string_standalone("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f831590123"); - - let res = unsafe { starknet_pubkey_from_private(private_raw) }; - assert_eq!(res.code, CStarknetCode::PrivKeyError as i32); - - unsafe { free_string(private_raw) }; -} - -#[test] -fn test_starknet_private_key_zero() { - let private_raw = - c_string_standalone("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - let res = unsafe { starknet_pubkey_from_private(private_raw) }; - assert_eq!(res.code, CStarknetCode::PrivKeyError as i32); - - unsafe { free_string(private_raw) }; -} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 87386edb3d6..3925e1a6d4f 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -14,5 +14,3 @@ tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_move_parser = { path = "../tw_move_parser" } tw_proto = { path = "../tw_proto" } -tw_bitcoin = { path = "../tw_bitcoin" } -tw_starknet = { path = "../tw_starknet" } diff --git a/rust/wallet_core_rs/cbindgen.toml b/rust/wallet_core_rs/cbindgen.toml index f2a1fc06b70..9af4160fdcb 100644 --- a/rust/wallet_core_rs/cbindgen.toml +++ b/rust/wallet_core_rs/cbindgen.toml @@ -6,5 +6,5 @@ namespaces = ["TW", "Rust"] [parse] parse_deps = true -extra_bindings = ["tw_bitcoin", "tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto", "tw_starknet"] -include = ["tw_bitcoin", "tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto", "tw_starknet"] +extra_bindings = ["tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto"] +include = ["tw_memory", "tw_encoding", "tw_hash", "tw_keypair", "tw_move_parser", "tw_proto"] diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index b3eb03421d4..17e5fcc641a 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -11,4 +11,3 @@ pub extern crate tw_keypair; pub extern crate tw_memory; pub extern crate tw_move_parser; pub extern crate tw_proto; -pub extern crate tw_starknet; diff --git a/src/ImmutableX/StarkKey.cpp b/src/ImmutableX/StarkKey.cpp index 2f2604ccadd..b249269f4aa 100644 --- a/src/ImmutableX/StarkKey.cpp +++ b/src/ImmutableX/StarkKey.cpp @@ -53,38 +53,4 @@ PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const Derivation return getPrivateKeyFromSeed(seed, derivationPath); } -Data getPublicKeyFromPrivateKey(const Data& privateKey) { - auto pubKey = Rust::starknet_pubkey_from_private(hex(privateKey).c_str()); - if (pubKey.code != Rust::OK_CODE) { - return {}; - } - const auto toReturn = parse_hex(pubKey.result, true); - Rust::free_string(pubKey.result); - return toReturn; -} - -Data sign(const Data& privateKey, const Data& digest) { - auto privKeyStr = hex(privateKey); - auto hexDigest = hex(digest); - auto resultSignature = Rust::starknet_sign(privKeyStr.c_str(), hexDigest.c_str()); - if (resultSignature.code != Rust::OK_CODE) { - return {}; - } - auto toReturn = parse_hex(resultSignature.result); - Rust::free_string(resultSignature.result); - return toReturn; -} - -bool verify(const Data& pubKey, const Data& signature, const Data& digest) { - if (signature.size() != 64) { - return false; - } - auto r = hex(subData(signature, 0, 32)); - auto s = hex(subData(signature, 32)); - auto pubKeyStr = hex(pubKey); - auto digestStr = hex(digest); - const auto res = Rust::starknet_verify(pubKeyStr.c_str(), digestStr.c_str(), r.c_str(), s.c_str()); - return res.code == Rust::OK_CODE && res.result; -} - } // namespace TW::ImmutableX diff --git a/src/ImmutableX/StarkKey.h b/src/ImmutableX/StarkKey.h index 82e3c4eae49..5c2643a56da 100644 --- a/src/ImmutableX/StarkKey.h +++ b/src/ImmutableX/StarkKey.h @@ -23,10 +23,4 @@ PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey); PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath); -Data getPublicKeyFromPrivateKey(const Data& privateKey); - -Data sign(const Data &privateKey, const Data& digest); - -bool verify(const Data &pubKey, const Data& signature, const Data& digest); - } // namespace TW::ImmutableX diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 373c021c3e6..18c247da89b 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -25,6 +25,36 @@ using namespace TW; +Data rust_get_public_from_private(const Data& key, TWPublicKeyType public_type) { + auto* privkey = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (privkey == nullptr) { + return {}; + } + Data toReturn; + + auto* pubkey = Rust::tw_private_key_get_public_key_by_type(privkey, static_cast(public_type)); + if (pubkey == nullptr) { + Rust::tw_private_key_delete(privkey); + return {}; + } + + Rust::CByteArrayWrapper res = Rust::tw_public_key_data(pubkey); + + Rust::tw_public_key_delete(pubkey); + Rust::tw_private_key_delete(privkey); + return res.data; +} + +Data rust_private_key_sign(const Data& key, const Data& hash, TWCurve curve) { + auto* priv = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (priv == nullptr) { + return {}; + } + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign(priv, hash.data(), hash.size(), static_cast(curve)); + Rust::tw_private_key_delete(priv); + return res.data; +} + bool PrivateKey::isValid(const Data& data) { // Check length if (data.size() != _size && data.size() != cardanoKeySize) { @@ -162,10 +192,7 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { } case TWPublicKeyTypeStarkex: { - result = ImmutableX::getPublicKeyFromPrivateKey(this->bytes); - if (result.size() == PublicKey::starkexSize - 1) { - result.insert(result.begin(), 0); - } + result = rust_get_public_from_private(this->bytes, type); break; } } @@ -203,8 +230,12 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { bool success = false; switch (curve) { case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; + result = rust_private_key_sign(key(), digest, curve); + success = result.size() == 65; + } break; + case TWCurveStarkex: { + result = rust_private_key_sign(key(), digest, curve); + success = result.size() == 64; } break; case TWCurveED25519: { result.resize(64); @@ -234,11 +265,6 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { result.resize(65); success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; } break; - case TWCurveStarkex: { - result = ImmutableX::sign(this->bytes, digest); - success = true; - break; - } case TWCurveNone: default: break; diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 8c6fd35c569..fa5c16f2bee 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -7,6 +7,7 @@ #include "PublicKey.h" #include "PrivateKey.h" #include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include #include @@ -132,11 +133,22 @@ PublicKey PublicKey::extended() const { } } +bool rust_public_key_verify(const Data& key, TWPublicKeyType type, const Data& sig, const Data& msgHash) { + auto* pubkey = Rust::tw_public_key_create_with_data(key.data(), key.size(), static_cast(type)); + if (pubkey == nullptr) { + return {}; + } + bool verified = Rust::tw_public_key_verify(pubkey, sig.data(), sig.size(), msgHash.data(), msgHash.size()); + Rust::tw_public_key_delete(pubkey); + return verified; +} + bool PublicKey::verify(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: - return ecdsa_verify_digest(&secp256k1, bytes.data(), signature.data(), message.data()) == 0; + case TWPublicKeyTypeStarkex: + return rust_public_key_verify(bytes, type, signature, message); case TWPublicKeyTypeNIST256p1: case TWPublicKeyTypeNIST256p1Extended: return ecdsa_verify_digest(&nist256p1, bytes.data(), signature.data(), message.data()) == 0; @@ -162,8 +174,6 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { verifyBuffer[63] &= 127; return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), verifyBuffer.data()) == 0; } - case TWPublicKeyTypeStarkex: - return ImmutableX::verify(this->bytes, signature, message); default: throw std::logic_error("Not yet implemented"); } diff --git a/tests/chains/Ethereum/SignerTests.cpp b/tests/chains/Ethereum/SignerTests.cpp index 524542ff212..c4e7ecdb2d8 100644 --- a/tests/chains/Ethereum/SignerTests.cpp +++ b/tests/chains/Ethereum/SignerTests.cpp @@ -111,7 +111,7 @@ TEST(EthereumSigner, EIP1559_1442) { TEST(EthereumSigner, SignatureBreakdownNoEip155) { const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); + const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac49527"); const auto signature = Signer::sign(key, hash, false, 5); const auto r = store(signature.r); @@ -129,7 +129,7 @@ TEST(EthereumSigner, SignatureBreakdownNoEip155) { TEST(EthereumSigner, SignatureBreakdownEip155Legacy) { const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); + const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac49527"); const auto signature = Signer::sign(key, hash, true, 0); const auto r = store(signature.r); @@ -147,7 +147,7 @@ TEST(EthereumSigner, SignatureBreakdownEip155Legacy) { TEST(EthereumSigner, SignatureBreakdownEip155) { const auto key = PrivateKey(parse_hex("f9fb27c90dcaa5631f373330eeef62ae7931587a19bd8215d0c2addf28e439c8")); - const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); + const auto hash = parse_hex("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac49527"); const auto signature = Signer::sign(key, hash, true, 1); const auto r = store(signature.r); diff --git a/tests/chains/ImmutableX/StarkKeyTests.cpp b/tests/chains/ImmutableX/StarkKeyTests.cpp index 72cc24fe1ef..69f5be62c27 100644 --- a/tests/chains/ImmutableX/StarkKeyTests.cpp +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -26,8 +26,8 @@ TEST(ImmutableX, ExtraGrinding) { auto data = parse_hex(signature); auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); - auto pubKey = hexEncoded(getPublicKeyFromPrivateKey(privKey.bytes)); - ASSERT_EQ(pubKey, "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hexEncoded(pubKey.bytes), "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); } TEST(ImmutableX, GrindKey) { @@ -56,32 +56,23 @@ TEST(ImmutableX, GetPrivateKeyFromSignature) { } TEST(ImmutableX, GetPublicKeyFromPrivateKey) { - auto privKey = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); - auto pubKey = hexEncoded(getPublicKeyFromPrivateKey(privKey)); - ASSERT_EQ(pubKey, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); - - { - auto priv = PrivateKey(parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe")); - auto pub = priv.getPublicKey(TWPublicKeyTypeStarkex); - ASSERT_EQ(hexEncoded(pub.bytes), "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); - } + auto privKeyData = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); + PrivateKey privKey(privKeyData); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + auto pubKeyHex = hexEncoded(pubKey.bytes); + ASSERT_EQ(pubKeyHex, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); } TEST(ImmutableX, SimpleSign) { - auto privKey = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + auto privKeyBytes = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + PrivateKey privKey(privKeyBytes); auto digest = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - auto signature = hex(ImmutableX::sign(privKey, digest)); + auto signature = hex(privKey.sign(digest, TWCurve::TWCurveStarkex)); auto expectedSignature = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; ASSERT_EQ(signature.size(), 128ULL); ASSERT_EQ(signature.substr(0, 64), "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); ASSERT_EQ(signature.substr(64, 64), "04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); ASSERT_EQ(signature, expectedSignature); - - { - PrivateKey priv(privKey); - auto result = hex(priv.sign(digest, TWCurveStarkex)); - ASSERT_EQ(result, expectedSignature); - } } TEST(ImmutableX, VerifySign) { @@ -90,7 +81,6 @@ TEST(ImmutableX, VerifySign) { auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); - ASSERT_TRUE(verify(pubKeyData, signature, hash)); auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); ASSERT_TRUE(pubKey.verify(signature, hash)); } @@ -99,7 +89,6 @@ TEST(ImmutableX, VerifySign) { auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b"); - ASSERT_FALSE(verify(pubKeyData, signature, hash)); auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); ASSERT_FALSE(pubKey.verify(signature, hash)); }