From 05ad88ff85a021ac98b56641ac9ccb7d5af987fc Mon Sep 17 00:00:00 2001 From: octobocto Date: Thu, 24 Sep 2026 08:29:40 -0700 Subject: [PATCH] types: move authorization from ed25519 to schnorrkel --- Cargo.lock | 359 ++++++++++++++++-------- Cargo.toml | 18 +- app/Cargo.toml | 1 + app/app.rs | 3 +- app/rpc_server.rs | 25 +- deny.toml | 1 + lib/Cargo.toml | 6 +- lib/authorization.rs | 502 ++++++++++++++++++++++++++-------- lib/net/mod.rs | 6 + lib/net/peer/mod.rs | 2 + lib/net/peer/task.rs | 6 +- lib/node/mod.rs | 32 ++- lib/node/net_task.rs | 10 +- lib/state/block.rs | 31 ++- lib/state/error.rs | 2 +- lib/state/mod.rs | 52 ++-- lib/state/two_way_peg_data.rs | 21 +- lib/types/keys.rs | 56 ++-- lib/types/mod.rs | 8 - lib/wallet.rs | 83 ++++-- 20 files changed, 893 insertions(+), 331 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9aa7d4e5..4f43749c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,7 +153,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -645,6 +645,12 @@ dependencies = [ "tower-service", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base58ck" version = "0.1.101" @@ -1004,7 +1010,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1030,6 +1036,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -1303,7 +1318,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -1373,7 +1388,7 @@ checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", - "unicode-width 0.1.14", + "unicode-width 0.2.2", ] [[package]] @@ -1492,10 +1507,22 @@ dependencies = [ ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "const-crc32-nostd" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" + +[[package]] +name = "const-hex" +version = "1.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "0e59eef12462b0f9b0a3620219be5d639afd79fe39dff0a42c3997061f9298b4" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] [[package]] name = "constant_time_eq" @@ -1658,6 +1685,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core 0.10.1", +] + [[package]] name = "ctr" version = "0.9.2" @@ -1682,8 +1719,26 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", - "fiat-crypto", + "fiat-crypto 0.2.9", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "group", + "rand_core 0.10.1", "rustc_version", "serde", "subtle", @@ -1767,22 +1822,23 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "deranged" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "const-oid", - "zeroize", + "powerfmt", ] [[package]] -name = "deranged" -version = "0.5.5" +name = "derive-getters" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ - "powerfmt", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -1814,11 +1870,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1937,33 +2003,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "serde", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "merlin", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - [[package]] name = "educe" version = "0.6.0" @@ -2432,12 +2471,28 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "file-guard" version = "0.2.0" @@ -2561,6 +2616,49 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "frost-core" +version = "3.0.0" +source = "git+https://github.com/ZcashFoundation/frost?rev=0966bd1529aa062ad3b621af99e277f976b1c0f0#0966bd1529aa062ad3b621af99e277f976b1c0f0" +dependencies = [ + "byteorder", + "const-crc32-nostd", + "const-hex", + "derive-getters", + "document-features", + "rand_core 0.10.1", + "serde", + "serdect", + "thiserror 2.0.18", + "visibility", + "zeroize", +] + +[[package]] +name = "frost-rerandomized" +version = "3.0.0" +source = "git+https://github.com/ZcashFoundation/frost?rev=0966bd1529aa062ad3b621af99e277f976b1c0f0#0966bd1529aa062ad3b621af99e277f976b1c0f0" +dependencies = [ + "const-hex", + "derive-getters", + "document-features", + "frost-core", + "rand_core 0.10.1", +] + +[[package]] +name = "frost-ristretto255" +version = "3.0.0" +source = "git+https://github.com/ZcashFoundation/frost?rev=0966bd1529aa062ad3b621af99e277f976b1c0f0#0966bd1529aa062ad3b621af99e277f976b1c0f0" +dependencies = [ + "curve25519-dalek 5.0.0", + "document-features", + "frost-core", + "frost-rerandomized", + "rand_core 0.10.1", + "sha2 0.11.0", +] + [[package]] name = "fs-err" version = "2.11.0" @@ -2958,6 +3056,17 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "h2" version = "0.4.13" @@ -3229,7 +3338,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -3283,6 +3392,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9994b79e8c1a39b3166c63ae7823bb2b00831e2a96a31399c50fe69df408eaeb" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -4042,15 +4160,6 @@ dependencies = [ "url", ] -[[package]] -name = "keccak" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" -dependencies = [ - "cpufeatures 0.2.17", -] - [[package]] name = "khronos-egl" version = "6.0.0" @@ -4154,7 +4263,7 @@ dependencies = [ "hkdf", "hmac", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", "x25519-dalek", ] @@ -4394,18 +4503,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - [[package]] name = "miette" version = "7.6.0" @@ -5262,7 +5359,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -5434,16 +5531,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -5465,11 +5552,13 @@ dependencies = [ "byteorder", "bytes", "clap", - "ed25519-dalek", + "curve25519-dalek 5.0.0", "educe", "error-fatality", "fallible-iterator", "fraction", + "frost-core", + "frost-ristretto255", "futures", "governor", "hashlink 0.10.0", @@ -5487,6 +5576,7 @@ dependencies = [ "prost-types", "protox", "quinn", + "rand 0.10.1", "rayon", "rcgen", "rustls", @@ -5542,6 +5632,7 @@ dependencies = [ "plain_bitassets_app_cli", "plain_bitassets_app_rpc_api", "poll-promise", + "rand 0.10.1", "serde", "shlex", "strum 0.27.2", @@ -5817,6 +5908,20 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags 2.10.0", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + [[package]] name = "prost" version = "0.14.3" @@ -5833,7 +5938,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -6137,6 +6242,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rand_xoshiro" version = "0.7.0" @@ -6825,6 +6939,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -6833,7 +6957,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -6844,7 +6968,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -6868,19 +7003,10 @@ version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "errno 0.2.8", + "errno 0.3.14", "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -7090,16 +7216,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -7375,7 +7491,7 @@ dependencies = [ "pbkdf2", "rand 0.8.6", "rustc-hash 1.1.0", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "unicode-normalization", "wasm-bindgen", @@ -7843,6 +7959,12 @@ dependencies = [ "web-time", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.9.0" @@ -7900,7 +8022,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -8013,6 +8135,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "vt100" version = "0.16.2" @@ -8601,7 +8734,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9061,7 +9194,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -9270,18 +9403,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d0ff0d2b..0ce6859d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,9 @@ borsh = "1.3.1" byteorder = "1.4.3" bytes = "1.9.0" clap = "4.5.4" +curve25519-dalek = { version = "5", default-features = false } dirs = "6.0.0" dotenvy = "0.15.7" -ed25519-dalek = "2.1.1" educe = { version = "0.6.0", default-features = false } eframe = "0.34.0" either = "1.13.0" @@ -53,6 +53,8 @@ prost-build = "0.14.3" prost-types = "0.14.3" protox = "0.9.1" quinn = "0.11.6" +# needs to line up with version required by frost-core +rand = { version = "0.10", default-features = false } rayon = "1.7.0" rcgen = "0.13.2" reserve-port = "2.0.1" @@ -99,6 +101,20 @@ default-features = false git = "https://github.com/LayerTwo-Labs/bip300301_enforcer" rev = "1100b6085d9ed64eeaf1b57b3c44609626bd4b23" +# TODO: remove once a release is published that is compatible with +# curve25519-dalek v5 +[workspace.dependencies.frost-core] +default-features = false +git = "https://github.com/ZcashFoundation/frost" +rev = "0966bd1529aa062ad3b621af99e277f976b1c0f0" + +# TODO: remove once a release is published that is compatible with +# curve25519-dalek v5 +[workspace.dependencies.frost-ristretto255] +default-features = false +git = "https://github.com/ZcashFoundation/frost" +rev = "0966bd1529aa062ad3b621af99e277f976b1c0f0" + [workspace.dependencies.l2l-openapi] git = "https://github.com/Ash-L2L/l2l-openapi" rev = "5f8fb2237c95725731d5fb10542098303d82e215" diff --git a/app/Cargo.toml b/app/Cargo.toml index e89d85dd..a1adccd6 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -31,6 +31,7 @@ plain_bitassets = { path = "../lib", features = ["clap"] } plain_bitassets_app_cli = { path = "../cli" } plain_bitassets_app_rpc_api = { path = "../rpc-api" } poll-promise = { workspace = true, features = ["tokio"] } +rand = { workspace = true, features = ["thread_rng"] } serde = { workspace = true, features = ["derive"] } shlex = { workspace = true } strum = { workspace = true, features = ["derive"] } diff --git a/app/app.rs b/app/app.rs index 5b4b6998..48dbabcb 100644 --- a/app/app.rs +++ b/app/app.rs @@ -256,6 +256,7 @@ impl App { config.network, cusf_mainchain, cusf_mainchain_wallet, + &mut rand::rng(), &runtime, #[cfg(feature = "zmq")] config.zmq_addr, @@ -306,7 +307,7 @@ impl App { } pub fn sign_and_send(&self, tx: Transaction) -> Result<(), Error> { - let authorized_transaction = self.wallet.authorize(tx)?; + let authorized_transaction = self.wallet.authorize(rand::rng(), tx)?; self.node.submit_transaction(authorized_transaction)?; let () = self.update()?; Ok(()) diff --git a/app/rpc_server.rs b/app/rpc_server.rs index d4950c63..86e23cf3 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -143,8 +143,11 @@ impl RpcServer for RpcServerImpl { amount_receive, ) .map_err(custom_err)?; - let authorized_tx = - self.app.wallet.authorize(tx).map_err(custom_err)?; + let authorized_tx = self + .app + .wallet + .authorize(rand::rng(), tx) + .map_err(custom_err)?; self.app .node .submit_transaction(authorized_tx) @@ -238,8 +241,11 @@ impl RpcServer for RpcServerImpl { receive_quantity, ) .map_err(custom_err)?; - let authorized_tx = - self.app.wallet.authorize(tx).map_err(custom_err)?; + let authorized_tx = self + .app + .wallet + .authorize(rand::rng(), tx) + .map_err(custom_err)?; self.app .node .submit_transaction(authorized_tx) @@ -274,8 +280,11 @@ impl RpcServer for RpcServerImpl { auction_state.quote_amount.latest().data, ) .map_err(custom_err)?; - let authorized_tx = - self.app.wallet.authorize(tx).map_err(custom_err)?; + let authorized_tx = self + .app + .wallet + .authorize(rand::rng(), tx) + .map_err(custom_err)?; self.app .node .submit_transaction(authorized_tx) @@ -629,7 +638,7 @@ impl RpcServer for RpcServerImpl { ) -> RpcResult { self.app .wallet - .sign_arbitrary_msg(&verifying_key, &msg) + .sign_arbitrary_msg(rand::rng(), &verifying_key, &msg) .map_err(custom_err) } @@ -640,7 +649,7 @@ impl RpcServer for RpcServerImpl { ) -> RpcResult { self.app .wallet - .sign_arbitrary_msg_as_addr(&address, &msg) + .sign_arbitrary_msg_as_addr(rand::rng(), &address, &msg) .map_err(custom_err) } diff --git a/deny.toml b/deny.toml index 9af410bb..2098174f 100644 --- a/deny.toml +++ b/deny.toml @@ -274,6 +274,7 @@ allow-git = [ "https://github.com/LayerTwo-Labs/bip300301_enforcer.git", "https://github.com/LayerTwo-Labs/cusf-enforcer-mempool.git", "https://github.com/torkelrogstad/tokio-tracing.git?branch=log-rotation-size", + "https://github.com/ZcashFoundation/frost.git?rev=0966bd1529aa062ad3b621af99e277f976b1c0f0", ] [sources.allow-org] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 0c5624d7..9a15bcd7 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -24,10 +24,12 @@ borsh = { workspace = true, features = ["derive"] } byteorder = { workspace = true } bytes = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } -ed25519-dalek = { workspace = true, features = ["batch", "serde"] } +curve25519-dalek = { workspace = true, features = ["group", "serde"] } educe = { workspace = true, features = ["Clone", "Debug"] } error-fatality = { workspace = true } fallible-iterator = { workspace = true } +frost-core = { workspace = true, features = ["internals"] } +frost-ristretto255 = { workspace = true, features = ["serde"] } futures = { workspace = true, features = ["alloc"] } hashlink = { workspace = true, features = ["serde_impl"] } fraction = { workspace = true } @@ -43,6 +45,7 @@ parking_lot = { workspace = true } prost = { workspace = true } prost-types = { workspace = true } quinn = { workspace = true } +rand = { workspace = true, features = ["chacha"] } rayon = { workspace = true } rcgen = { workspace = true } rustls = { workspace = true, features = ["ring"] } @@ -71,6 +74,7 @@ workspace = true features = ["AES256-GCM", "ECIES-MAC", "HMAC-SHA256", "x25519"] [dev-dependencies] +rand = { workspace = true, features = ["thread_rng"] } temp-dir = { workspace = true } [features] diff --git a/lib/authorization.rs b/lib/authorization.rs index 2d9883dc..0f634b0c 100644 --- a/lib/authorization.rs +++ b/lib/authorization.rs @@ -2,28 +2,45 @@ use std::str::FromStr; use borsh::BorshSerialize; use hex::FromHex; -use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; +use rayon::{ + iter::{IntoParallelRefIterator as _, ParallelIterator as _}, + slice::ParallelSlice as _, +}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::types::{ - Address, AuthorizedTransaction, Body, GetAddress, Transaction, Verify, - VerifyingKey, + Address, AuthorizedTransaction, Body, GetAddress, Transaction, VerifyingKey, }; -pub use ed25519_dalek::{SignatureError, Signer, SigningKey, Verifier}; +pub use frost_ristretto255::rand_core; + +pub type SignatureError = frost_ristretto255::Error; +pub type SigningKey = frost_ristretto255::SigningKey; #[derive(Clone, Copy, Debug, Eq, PartialEq, ToSchema)] #[repr(transparent)] #[schema(value_type = String)] -pub struct Signature(pub ed25519_dalek::Signature); +pub struct Signature(pub frost_ristretto255::Signature); + +impl Signature { + /// A compressed Ristretto point, then a scalar. + pub const BYTE_SIZE: usize = 64; + + pub fn to_bytes(self) -> [u8; Self::BYTE_SIZE] { + let mut out = [0u8; Self::BYTE_SIZE]; + out[..32].copy_from_slice(self.0.R().compress().as_bytes()); + out[32..].copy_from_slice(self.0.z().as_bytes()); + out + } +} impl BorshSerialize for Signature { fn serialize( &self, writer: &mut W, ) -> std::io::Result<()> { - self.0.to_bytes().serialize(writer) + self.to_bytes().serialize(writer) } } @@ -35,26 +52,28 @@ impl<'de> Deserialize<'de> for Signature { if deserializer.is_human_readable() { hex::serde::deserialize(deserializer) } else { - ed25519_dalek::Signature::deserialize(deserializer).map(Self) + ::deserialize( + deserializer, + ) + .map(Self) } } } impl std::fmt::Display for Signature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + hex::encode(self.to_bytes()).fmt(f) } } impl FromHex for Signature { - type Error = <[u8; ed25519_dalek::Signature::BYTE_SIZE] as FromHex>::Error; + type Error = hex::FromHexError; fn from_hex>(hex: T) -> Result { - let bytes = - <[u8; ed25519_dalek::Signature::BYTE_SIZE] as FromHex>::from_hex( - hex, - )?; - Ok(Self(ed25519_dalek::Signature::from_bytes(&bytes))) + let bytes = <[u8; Self::BYTE_SIZE] as FromHex>::from_hex(hex)?; + frost_ristretto255::Signature::deserialize(&bytes) + .map(Self) + .map_err(|_| hex::FromHexError::InvalidStringLength) } } @@ -72,9 +91,9 @@ impl Serialize for Signature { S: serde::Serializer, { if serializer.is_human_readable() { - hex::serde::serialize(self.0.to_bytes(), serializer) + hex::serde::serialize(self.to_bytes(), serializer) } else { - self.0.serialize(serializer) + Serialize::serialize(&self.0, serializer) } } } @@ -95,8 +114,8 @@ pub enum Dst { pub enum Error { #[error("borsh serialization error")] BorshSerialize(#[from] borsh::io::Error), - #[error("ed25519_dalek error")] - Dalek(#[from] SignatureError), + #[error("signature verification error")] + SignatureVerification(#[from] SignatureError), #[error("not enough authorizations")] NotEnoughAuthorizations, #[error("too many authorizations")] @@ -133,18 +152,103 @@ impl GetAddress for Authorization { } } -impl Verify for Authorization { - type Error = Error; - fn verify_transaction( - transaction: &AuthorizedTransaction, - ) -> Result<(), Self::Error> { - verify_authorized_transaction(transaction)?; - Ok(()) +/// Derives a CSPRNG seed for a single batch verification. +struct BatchVerifier { + hasher: blake3::Hasher, + inner: frost_core::batch::Verifier, + /// Item counter, added as a suffix to the hasher before verification + items: usize, +} + +impl BatchVerifier { + fn queue_item( + mut self, + verifying_key: VerifyingKey, + signature: Signature, + msg: Msg, + ) -> Result + where + Msg: AsRef<[u8]>, + { + let Self { + inner, + items, + hasher, + } = &mut self; + let msg_bytes = msg.as_ref(); + // Borsh encoding for hashing + #[derive(BorshSerialize)] + struct HashComponents<'a> { + verifying_key: &'a VerifyingKey, + signature: &'a Signature, + msg_bytes: &'a [u8], + } + borsh::to_writer( + hasher, + &HashComponents { + verifying_key: &verifying_key, + signature: &signature, + msg_bytes, + }, + ) + .expect("failed to serialize with borsh to compute a hash"); + *items += 1; + inner.queue(frost_core::batch::Item::new( + verifying_key.0, + signature.0, + msg_bytes, + )?); + Ok(self) } - fn verify_body(body: &Body) -> Result<(), Self::Error> { - verify_authorizations(body)?; - Ok(()) + /// Performs batch verification, returning `Ok(_)` if all signatures were + /// valid and the batch was non-empty, and `Err(_)` otherwise. + fn verify(self) -> Result<(), SignatureError> { + let Self { + hasher, + inner, + items, + } = self; + let rng = { + // move hasher so that it can be dropped early automatically + let mut hasher = hasher; + borsh::to_writer(&mut hasher, &items) + .expect("failed to serialize with borsh to compute a hash"); + ::from_seed( + hasher.finalize().into(), + ) + }; + inner.verify(rng) + } +} + +/// Required for batched verification. +/// It should be safe to re-use the same batch verification context for +/// several batched verifications. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct BatchVerificationContext { + mac_key: [u8; blake3::KEY_LEN], +} + +impl BatchVerificationContext { + pub fn new(rng: &mut R) -> Self + where + R: rand_core::CryptoRng, + { + let mut mac_key = [0; blake3::KEY_LEN]; + rng.fill_bytes(&mut mac_key); + Self { mac_key } + } + + /// Construct a new batch verifier + fn verifier(&self) -> BatchVerifier { + let Self { mac_key } = self; + BatchVerifier { + hasher: blake3::Hasher::new_keyed(mac_key), + inner: frost_core::batch::Verifier::new(), + items: 0, + } } } @@ -156,12 +260,6 @@ pub fn get_address(verifying_key: &VerifyingKey) -> Address { Address(output) } -struct Package<'a> { - messages: Vec<&'a [u8]>, - signatures: Vec, - verifying_keys: Vec, -} - /// Canonical message to sign a tx fn tx_msg_canonical(tx: &Transaction) -> borsh::io::Result> { let mut buf = vec![Dst::Transaction as u8]; @@ -170,6 +268,7 @@ fn tx_msg_canonical(tx: &Transaction) -> borsh::io::Result> { } pub fn verify_authorized_transaction( + ctxt: &BatchVerificationContext, transaction: &AuthorizedTransaction, ) -> Result<(), Error> { let verifications_required = &transaction.transaction.inputs.len(); @@ -180,30 +279,32 @@ pub fn verify_authorized_transaction( return Err(Error::TooManyAuthorizations); } } + // A frost batch rejects an empty batch, and a transaction without inputs + // has nothing to sign. + if transaction.authorizations.is_empty() { + return Ok(()); + } let tx_msg_canonical = tx_msg_canonical(&transaction.transaction)?; - let messages: Vec<_> = std::iter::repeat_n( - tx_msg_canonical.as_slice(), - transaction.authorizations.len(), - ) - .collect(); - let (verifying_keys, signatures): ( - Vec, - Vec, - ) = transaction - .authorizations - .iter() - .map( - |Authorization { - verifying_key, - signature, - }| (verifying_key.0, signature.0), - ) - .unzip(); - ed25519_dalek::verify_batch(&messages, &signatures, &verifying_keys)?; + let mut batch_verifier = ctxt.verifier(); + for Authorization { + verifying_key, + signature, + } in &transaction.authorizations + { + batch_verifier = batch_verifier.queue_item( + *verifying_key, + *signature, + &tx_msg_canonical, + )?; + } + let () = batch_verifier.verify()?; Ok(()) } -pub fn verify_authorizations(body: &Body) -> Result<(), Error> { +pub fn verify_authorizations( + ctxt: &BatchVerificationContext, + body: &Body, +) -> Result<(), Error> { let verifications_required = body.transactions.par_iter().map(|tx| tx.inputs.len()).sum(); match body.authorizations.len().cmp(&verifications_required) { @@ -230,62 +331,35 @@ pub fn verify_authorizations(body: &Body) -> Result<(), Error> { }); let pairs = body.authorizations.iter().zip(messages).collect::>(); - - let num_threads = rayon::current_num_threads(); - let num_authorizations = body.authorizations.len(); - let package_size = num_authorizations / num_threads; - let mut packages: Vec = Vec::with_capacity(num_threads); - for i in 0..num_threads { - let mut package = Package { - messages: Vec::with_capacity(package_size), - signatures: Vec::with_capacity(package_size), - verifying_keys: Vec::with_capacity(package_size), - }; - for (authorization, message) in - &pairs[i * package_size..(i + 1) * package_size] - { - package.messages.push(*message); - package.signatures.push(authorization.signature.0); - package.verifying_keys.push(authorization.verifying_key.0); + assert_eq!(pairs.len(), body.authorizations.len()); + const CHUNK_SIZE: usize = 1 << 14; + pairs.par_chunks(CHUNK_SIZE).try_for_each(|chunk| { + let mut batch_verifier = ctxt.verifier(); + for (authorization, msg) in chunk { + let Authorization { + verifying_key, + signature, + } = authorization; + batch_verifier = + batch_verifier.queue_item(*verifying_key, *signature, msg)?; } - packages.push(package); - } - for (authorization, message) in &pairs[num_threads * package_size..] { - packages[num_threads - 1].messages.push(*message); - packages[num_threads - 1] - .signatures - .push(authorization.signature.0); - packages[num_threads - 1] - .verifying_keys - .push(authorization.verifying_key.0); - } - assert_eq!( - packages.iter().map(|p| p.signatures.len()).sum::(), - body.authorizations.len() - ); - packages - .par_iter() - .map( - |Package { - messages, - signatures, - verifying_keys, - }| { - ed25519_dalek::verify_batch( - messages, - signatures, - verifying_keys, - ) - }, - ) - .collect::>()?; + batch_verifier.verify() + })?; Ok(()) } /// Sign a message with DST prefix -pub fn sign(signing_key: &SigningKey, dst: Dst, msg: &[u8]) -> Signature { +pub fn sign( + rng: R, + signing_key: &SigningKey, + dst: Dst, + msg: &[u8], +) -> Signature +where + R: rand_core::CryptoRng, +{ let msg_buf = [&[dst as u8], msg].concat(); - Signature(signing_key.sign(&msg_buf)) + Signature(signing_key.sign(rng, &msg_buf)) } /// Verify a message with DST prefix @@ -296,29 +370,39 @@ pub fn verify( msg: &[u8], ) -> bool { let msg_buf = [&[dst as u8], msg].concat(); - verifying_key - .0 - .verify_strict(&msg_buf, &signature.0) - .is_ok() + verifying_key.0.verify(&msg_buf, &signature.0).is_ok() } -pub fn sign_tx( +pub fn sign_tx( + rng: R, signing_key: &SigningKey, transaction: &Transaction, -) -> Result { +) -> Result +where + R: rand_core::CryptoRng, +{ let tx_bytes_canonical = borsh::to_vec(&transaction)?; - Ok(sign(signing_key, Dst::Transaction, &tx_bytes_canonical)) + Ok(sign( + rng, + signing_key, + Dst::Transaction, + &tx_bytes_canonical, + )) } -pub fn authorize( +pub fn authorize( + mut rng: R, addresses_signing_keys: &[(Address, &SigningKey)], transaction: Transaction, -) -> Result { +) -> Result +where + R: rand_core::CryptoRng, +{ let mut authorizations: Vec = Vec::with_capacity(addresses_signing_keys.len()); let tx_bytes_canonical = borsh::to_vec(&transaction)?; for (address, signing_key) in addresses_signing_keys { - let verifying_key = signing_key.verifying_key().into(); + let verifying_key = VerifyingKey::from(*signing_key); let hash_verifying_key = get_address(&verifying_key); if *address != hash_verifying_key { return Err(Error::WrongKeyForAddress { @@ -328,7 +412,12 @@ pub fn authorize( } let authorization = Authorization { verifying_key, - signature: sign(signing_key, Dst::Transaction, &tx_bytes_canonical), + signature: sign( + &mut rng, + signing_key, + Dst::Transaction, + &tx_bytes_canonical, + ), }; authorizations.push(authorization); } @@ -337,3 +426,186 @@ pub fn authorize( transaction, }) } + +#[cfg(test)] +pub(crate) fn test_signing_key(seed: u8) -> SigningKey { + let scalar = curve25519_dalek::Scalar::from_bytes_mod_order([seed; 32]); + SigningKey::from_scalar(scalar).expect("non-zero scalar") +} + +#[cfg(test)] +mod tests { + use hex::FromHex as _; + + use super::{ + Authorization, BatchVerificationContext, Dst, Error, Signature, + authorize, get_address, sign, sign_tx, test_signing_key, verify, + verify_authorizations, verify_authorized_transaction, + }; + use crate::types::{ + Address, AuthorizedTransaction, Body, GetAddress as _, OutPoint, + Transaction, Txid, VerifyingKey, + }; + + fn one_input_tx(txid: u8) -> Transaction { + Transaction::new( + vec![OutPoint::Regular { + txid: Txid([txid; 32]), + vout: 0, + }], + Vec::new(), + ) + } + + #[test] + fn an_address_comes_from_the_compressed_ristretto_point() { + let verifying_key = VerifyingKey::from(&test_signing_key(1)); + let point = verifying_key.0.to_element().compress(); + assert_eq!(verifying_key.to_bytes(), point.to_bytes()); + let expected = { + let mut output = [0u8; 20]; + blake3::Hasher::new() + .update(point.as_bytes()) + .finalize_xof() + .fill(&mut output); + Address(output) + }; + assert_eq!(get_address(&verifying_key), expected); + } + + #[test] + fn a_verifying_key_round_trips_through_bech32m() { + let key = VerifyingKey::from(&test_signing_key(2)); + let encoded = key.bech32m_encode(); + assert_eq!(VerifyingKey::bech32m_decode(&encoded).unwrap(), key); + } + + #[test] + fn a_signature_round_trips_through_hex_and_borsh() { + let signature = + sign(rand::rng(), &test_signing_key(3), Dst::Arbitrary, b"hello"); + let encoded = signature.to_string(); + assert_eq!(Signature::from_hex(&encoded).unwrap(), signature); + let expected: Vec = [ + signature.0.R().compress().as_bytes().as_slice(), + signature.0.z().as_bytes().as_slice(), + ] + .concat(); + assert_eq!(borsh::to_vec(&signature).unwrap(), expected); + } + + #[test] + fn a_good_signature_verifies() { + let mut rng = rand::rng(); + let key = test_signing_key(4); + let address = get_address(&VerifyingKey::from(&key)); + let authorized = + authorize(&mut rng, &[(address, &key)], one_input_tx(1)) + .expect("authorize"); + assert_eq!(authorized.authorizations[0].get_address(), address); + let ctxt = BatchVerificationContext::new(&mut rng); + verify_authorized_transaction(&ctxt, &authorized) + .expect("a good signature must verify"); + let body = Body::new(vec![authorized], Vec::new()); + verify_authorizations(&ctxt, &body) + .expect("a good block body must verify"); + } + + #[test] + fn a_signature_from_another_key_fails() { + let mut rng = rand::rng(); + let owner = test_signing_key(5); + let attacker = test_signing_key(6); + let transaction = one_input_tx(1); + let forged = Authorization { + verifying_key: VerifyingKey::from(&owner), + signature: sign_tx(&mut rng, &attacker, &transaction) + .expect("sign"), + }; + let authorized = AuthorizedTransaction { + transaction, + authorizations: vec![forged], + }; + let ctxt = BatchVerificationContext::new(&mut rng); + assert!(verify_authorized_transaction(&ctxt, &authorized).is_err()); + let body = Body::new(vec![authorized], Vec::new()); + assert!(verify_authorizations(&ctxt, &body).is_err()); + } + + #[test] + fn a_signature_replayed_onto_another_transaction_fails() { + let mut rng = rand::rng(); + let key = test_signing_key(7); + let address = get_address(&VerifyingKey::from(&key)); + let signed = authorize(&mut rng, &[(address, &key)], one_input_tx(1)) + .expect("authorize"); + let replayed = AuthorizedTransaction { + transaction: one_input_tx(2), + authorizations: signed.authorizations, + }; + let ctxt = BatchVerificationContext::new(&mut rng); + assert!(verify_authorized_transaction(&ctxt, &replayed).is_err()); + } + + #[test] + fn a_transaction_without_inputs_verifies() { + let mut rng = rand::rng(); + let authorized = AuthorizedTransaction { + transaction: Transaction::new(Vec::new(), Vec::new()), + authorizations: Vec::new(), + }; + let ctxt = BatchVerificationContext::new(&mut rng); + verify_authorized_transaction(&ctxt, &authorized) + .expect("a transaction without inputs must verify"); + let body = Body::new(vec![authorized], Vec::new()); + verify_authorizations(&ctxt, &body) + .expect("a body without inputs must verify"); + } + + #[test] + fn a_missing_authorization_fails() { + let mut rng = rand::rng(); + let authorized = AuthorizedTransaction { + transaction: one_input_tx(1), + authorizations: Vec::new(), + }; + let ctxt = BatchVerificationContext::new(&mut rng); + assert!(matches!( + verify_authorized_transaction(&ctxt, &authorized), + Err(Error::NotEnoughAuthorizations) + )); + let body = Body::new(vec![authorized], Vec::new()); + assert!(matches!( + verify_authorizations(&ctxt, &body), + Err(Error::NotEnoughAuthorizations) + )); + } + + #[test] + fn authorize_rejects_a_key_that_does_not_match_the_address() { + let key = test_signing_key(8); + let other = get_address(&VerifyingKey::from(&test_signing_key(9))); + let err = authorize(rand::rng(), &[(other, &key)], one_input_tx(1)) + .expect_err("authorize must reject a key for another address"); + assert!(matches!(err, Error::WrongKeyForAddress { .. })); + } + + #[test] + fn a_domain_tag_separates_two_messages() { + let key = test_signing_key(10); + let verifying_key = VerifyingKey::from(&key); + let signature = sign(rand::rng(), &key, Dst::Arbitrary, b"message"); + assert!(verify( + signature, + &verifying_key, + Dst::Arbitrary, + b"message" + )); + assert!(!verify( + signature, + &verifying_key, + Dst::Transaction, + b"message" + )); + } +} diff --git a/lib/net/mod.rs b/lib/net/mod.rs index 771ef618..d34b728f 100644 --- a/lib/net/mod.rs +++ b/lib/net/mod.rs @@ -15,6 +15,7 @@ use tracing::instrument; use crate::{ archive::Archive, + authorization::BatchVerificationContext, state::State, types::{AuthorizedTransaction, Network, THIS_SIDECHAIN, VERSION, Version}, }; @@ -184,6 +185,7 @@ const fn seed_node_addrs(network: Network) -> &'static [SocketAddr] { pub struct Net { pub server: Endpoint, archive: Archive, + pub(crate) batch_verification_ctxt: BatchVerificationContext, network: Network, state: State, active_peers: Arc>>, @@ -278,6 +280,7 @@ impl Net { let connection_ctxt = PeerConnectionCtxt { env, archive: self.archive.clone(), + batch_verification_ctxt: self.batch_verification_ctxt, network: self.network, state: self.state.clone(), }; @@ -314,6 +317,7 @@ impl Net { pub fn new( env: &sneed::Env, archive: Archive, + batch_verification_ctxt: BatchVerificationContext, network: Network, state: State, bind_addr: SocketAddr, @@ -342,6 +346,7 @@ impl Net { let net = Net { server, archive, + batch_verification_ctxt, network, state, active_peers, @@ -441,6 +446,7 @@ impl Net { let connection_ctxt = PeerConnectionCtxt { env, archive: self.archive.clone(), + batch_verification_ctxt: self.batch_verification_ctxt, network: self.network, state: self.state.clone(), }; diff --git a/lib/net/peer/mod.rs b/lib/net/peer/mod.rs index a9b31312..8a13250f 100644 --- a/lib/net/peer/mod.rs +++ b/lib/net/peer/mod.rs @@ -17,6 +17,7 @@ use tokio::{spawn, task::JoinHandle, time::Duration}; use crate::{ archive::Archive, + authorization::BatchVerificationContext, state::State, types::{ AuthorizedTransaction, Hash, Network, Tip, Version, hashes::hash, @@ -370,6 +371,7 @@ impl Connection { pub struct ConnectionContext { pub env: sneed::Env, pub archive: Archive, + pub batch_verification_ctxt: BatchVerificationContext, pub network: Network, pub state: State, } diff --git a/lib/net/peer/task.rs b/lib/net/peer/task.rs index 0d603ae1..32fc1614 100644 --- a/lib/net/peer/task.rs +++ b/lib/net/peer/task.rs @@ -657,7 +657,11 @@ impl ConnectionTask { let txid = tx.transaction.txid(); let validate_tx_result = { let rotxn = ctxt.env.read_txn().map_err(EnvError::from)?; - ctxt.state.validate_transaction(&rotxn, &tx) + ctxt.state.validate_transaction( + &rotxn, + &ctxt.batch_verification_ctxt, + &tx, + ) }; match validate_tx_result { Err(err) => { diff --git a/lib/node/mod.rs b/lib/node/mod.rs index 0365d205..917aedee 100644 --- a/lib/node/mod.rs +++ b/lib/node/mod.rs @@ -17,6 +17,7 @@ use tonic::transport::Channel; use crate::{ archive::{self, Archive}, + authorization::{BatchVerificationContext, rand_core::CryptoRng}, mempool::{self, MemPool}, net::{self, Net, Peer}, state::{ @@ -117,6 +118,7 @@ pub type FilledTransactionWithPosition = #[derive(Clone)] pub struct Node { archive: Archive, + batch_verification_ctxt: BatchVerificationContext, cusf_mainchain: Arc>>, cusf_mainchain_wallet: Option>>>, @@ -135,7 +137,7 @@ where MainchainTransport: proto::Transport, { #[allow(clippy::too_many_arguments)] - pub async fn new( + pub async fn new( bind_addr: SocketAddr, datadir: &Path, network: Network, @@ -143,16 +145,18 @@ where cusf_mainchain_wallet: Option< mainchain::WalletClient, >, + rng: &mut R, runtime: &tokio::runtime::Runtime, #[cfg(feature = "zmq")] zmq_addr: SocketAddr, ) -> Result where + R: CryptoRng, mainchain::ValidatorClient: Clone, MainchainTransport: Send + 'static, >::Future: Send, - { +{ let env_path = datadir.join("data.mdb"); // let _ = std::fs::remove_dir_all(&env_path); std::fs::create_dir_all(&env_path)?; @@ -201,8 +205,15 @@ where archive.clone(), cusf_mainchain.clone(), ); - let (net, peer_info_rx) = - Net::new(&env, archive.clone(), network, state.clone(), bind_addr)?; + let batch_verification_ctxt = BatchVerificationContext::new(rng); + let (net, peer_info_rx) = Net::new( + &env, + archive.clone(), + batch_verification_ctxt, + network, + state.clone(), + bind_addr, + )?; let cusf_mainchain_wallet = cusf_mainchain_wallet.map(|wallet| Arc::new(Mutex::new(wallet))); let net_task = NetTaskHandle::new( @@ -220,6 +231,7 @@ where ); Ok(Self { archive, + batch_verification_ctxt, cusf_mainchain: Arc::new(Mutex::new(cusf_mainchain)), cusf_mainchain_wallet, env, @@ -479,7 +491,11 @@ where ) -> Result<(), Error> { { let mut rotxn = self.env.write_txn()?; - self.state.validate_transaction(&rotxn, &transaction)?; + self.state.validate_transaction( + &rotxn, + &self.batch_verification_ctxt, + &transaction, + )?; self.mempool.put(&mut rotxn, &transaction)?; rotxn.commit().map_err(RwTxnError::from)?; } @@ -685,7 +701,11 @@ where } if self .state - .validate_transaction(&rwtxn, &transaction) + .validate_transaction( + &rwtxn, + &self.batch_verification_ctxt, + &transaction, + ) .is_err() { self.mempool diff --git a/lib/node/net_task.rs b/lib/node/net_task.rs index fac79081..454d8e55 100644 --- a/lib/node/net_task.rs +++ b/lib/node/net_task.rs @@ -30,6 +30,7 @@ use tokio_stream::StreamNotifyClose; use super::mainchain_task::{self, MainchainTaskHandle}; use crate::{ archive::{self, Archive}, + authorization::BatchVerificationContext, mempool::{self, MemPool}, net::{ self, Net, PeerConnectionError, PeerConnectionInfo, @@ -137,9 +138,11 @@ impl From for Error { } } +#[allow(clippy::too_many_arguments)] fn connect_tip_( rwtxn: &mut RwTxn<'_>, archive: &Archive, + batch_verification_ctxt: &BatchVerificationContext, mempool: &MemPool, state: &State, header: &Header, @@ -151,10 +154,10 @@ fn connect_tip_( let merkle_root = Body::compute_merkle_root(&body.coinbase, &body.transactions); let height = state.try_get_height(rwtxn)?; - state.apply_block(rwtxn, header, body)?; + state.apply_block(rwtxn, batch_verification_ctxt, header, body)?; tracing::debug!(?height, %merkle_root, %block_hash, "connected body") } else { - state.apply_block(rwtxn, header, body)?; + state.apply_block(rwtxn, batch_verification_ctxt, header, body)?; } let () = state.connect_two_way_peg_data(rwtxn, two_way_peg_data)?; let () = archive.put_header(rwtxn, header)?; @@ -277,6 +280,7 @@ fn is_fatal_reorg_error(err: &Error) -> bool { fn reorg_to_tip( env: &sneed::Env, archive: &Archive, + batch_verification_ctxt: &BatchVerificationContext, mempool: &MemPool, state: &State, #[cfg(feature = "zmq")] zmq_pub_handler: &ZmqPubHandler, @@ -407,6 +411,7 @@ fn reorg_to_tip( let () = match connect_tip_( &mut rwtxn, archive, + batch_verification_ctxt, mempool, state, header, @@ -1058,6 +1063,7 @@ impl NetTask { reorg_to_tip( &self.ctxt.env, &self.ctxt.archive, + &self.ctxt.net.batch_verification_ctxt, &self.ctxt.mempool, &self.ctxt.state, #[cfg(feature = "zmq")] diff --git a/lib/state/block.rs b/lib/state/block.rs index 2c378591..d4564d29 100644 --- a/lib/state/block.rs +++ b/lib/state/block.rs @@ -4,12 +4,12 @@ use rayon::prelude::*; use sneed::{RoTxn, RwTxn}; use crate::{ + authorization::{self, BatchVerificationContext}, state::{Error, PrevalidatedBlock, State, amm, dutch_auction, error}, types::{ - AmountOverflowError, Authorization, BitAssetId, Body, FilledOutput, + AmountOverflowError, BitAssetId, Body, FilledOutput, FilledOutputContent, GetAddress as _, GetBitcoinValue as _, Header, InPoint, OutPoint, OutPointKey, OutputContent, SpentOutput, TxData, - Verify as _, }, }; @@ -20,6 +20,7 @@ fn calculate_total_inputs(body: &Body) -> usize { /// Validate a block, returning fees pub fn validate( + batch_verification_ctxt: &BatchVerificationContext, state: &State, rotxn: &RoTxn, header: &Header, @@ -93,11 +94,14 @@ pub fn validate( return Err(Error::WrongPubKeyForAddress); } } - let () = Authorization::verify_body(body).map_err(Error::Authorization)?; + let () = + authorization::verify_authorizations(batch_verification_ctxt, body) + .map_err(Error::Authorization)?; Ok(total_fees) } pub fn prevalidate( + batch_verification_ctxt: &BatchVerificationContext, state: &State, rotxn: &RoTxn, header: &Header, @@ -179,7 +183,9 @@ pub fn prevalidate( } } - let () = Authorization::verify_body(body).map_err(Error::Authorization)?; + let () = + authorization::verify_authorizations(batch_verification_ctxt, body) + .map_err(Error::Authorization)?; let height = state.try_get_height(rotxn)?.map_or(0, |height| height + 1); @@ -719,7 +725,7 @@ mod test { use bitcoin::hashes::Hash as _; use crate::{ - authorization::{self, SigningKey}, + authorization::{self, BatchVerificationContext}, state::{ BitAssetSeqId, block::{connect, disconnect_tip, validate}, @@ -757,8 +763,10 @@ mod test { fn disconnect_bitasset_data_update() -> anyhow::Result<()> { let (_temp_dir, env, state) = fresh_state("disconnect_bitasset_data_update")?; - let signing_key = SigningKey::from_bytes(&[7; 32]); - let verifying_key = signing_key.verifying_key().into(); + let mut rng = rand::rng(); + let batch_verification_ctxt = BatchVerificationContext::new(&mut rng); + let signing_key = authorization::test_signing_key(7); + let verifying_key = (&signing_key).into(); let address = authorization::get_address(&verifying_key); let name_hash: Hash = [1; 32]; @@ -863,6 +871,7 @@ mod test { data: Some(TxData::BitAssetUpdate(Box::new(updates))), }; let authorized_update = authorization::authorize( + &mut rng, &[(address, &signing_key), (address, &signing_key)], update_tx, )?; @@ -871,7 +880,13 @@ mod test { { let rotxn = env.read_txn()?; - validate(&state, &rotxn, &update_header, &update_body)?; + validate( + &batch_verification_ctxt, + &state, + &rotxn, + &update_header, + &update_body, + )?; } { let mut rwtxn = env.write_txn()?; diff --git a/lib/state/error.rs b/lib/state/error.rs index 02f4b64c..8434c214 100644 --- a/lib/state/error.rs +++ b/lib/state/error.rs @@ -406,7 +406,7 @@ pub enum Error { )] SecondLastOutputNotBitAsset, #[error(transparent)] - SignatureError(#[from] ed25519_dalek::SignatureError), + SignatureError(#[from] frost_ristretto255::Error), #[error("Too few BitAsset control coin outputs")] TooFewBitAssetControlOutputs, #[error( diff --git a/lib/state/mod.rs b/lib/state/mod.rs index 679dc55f..1126d98d 100644 --- a/lib/state/mod.rs +++ b/lib/state/mod.rs @@ -8,13 +8,13 @@ use serde::{Deserialize, Serialize}; use sneed::{DatabaseUnique, RoDatabaseUnique, RoTxn, RwTxn, UnitKey}; use crate::{ - authorization::Authorization, + authorization::{self, BatchVerificationContext}, types::{ Address, AmountOverflowError, Authorized, AuthorizedTransaction, BitAssetId, BlockHash, Body, FilledOutput, FilledTransaction, GetAddress as _, GetBitcoinValue as _, Header, InPoint, M6id, OutPoint, - OutPointKey, SpentOutput, Transaction, TxData, VERSION, Verify as _, - Version, WithdrawalBundle, WithdrawalBundleStatus, + OutPointKey, SpentOutput, Transaction, TxData, VERSION, Version, + WithdrawalBundle, WithdrawalBundleStatus, proto::mainchain::TwoWayPegData, }, util::Watchable, @@ -627,6 +627,7 @@ impl State { pub fn validate_transaction( &self, rotxn: &RoTxn, + batch_verification_ctxt: &BatchVerificationContext, transaction: &AuthorizedTransaction, ) -> Result { let filled_transaction = @@ -640,8 +641,11 @@ impl State { return Err(Error::WrongPubKeyForAddress); } } - let () = Authorization::verify_transaction(transaction) - .map_err(Error::Authorization)?; + let () = authorization::verify_authorized_transaction( + batch_verification_ctxt, + transaction, + ) + .map_err(Error::Authorization)?; let fee = self.validate_filled_transaction(rotxn, &filled_transaction)?; Ok(fee) @@ -715,10 +719,11 @@ impl State { pub fn validate_block( &self, rotxn: &RoTxn, + batch_verification_ctxt: &BatchVerificationContext, header: &Header, body: &Body, ) -> Result { - block::validate(self, rotxn, header, body) + block::validate(batch_verification_ctxt, self, rotxn, header, body) } pub fn connect_block( @@ -758,10 +763,11 @@ impl State { pub fn prevalidate_block( &self, rotxn: &RoTxn, + batch_verification_ctxt: &BatchVerificationContext, header: &Header, body: &Body, ) -> Result { - block::prevalidate(self, rotxn, header, body) + block::prevalidate(batch_verification_ctxt, self, rotxn, header, body) } pub fn connect_prevalidated_block( @@ -777,10 +783,16 @@ impl State { pub fn apply_block( &self, rwtxn: &mut RwTxn, + batch_verification_ctxt: &BatchVerificationContext, header: &Header, body: &Body, ) -> Result<(), Error> { - let prevalidated = self.prevalidate_block(rwtxn, header, body)?; + let prevalidated = self.prevalidate_block( + rwtxn, + batch_verification_ctxt, + header, + body, + )?; self.connect_prevalidated_block(rwtxn, header, body, prevalidated)?; Ok(()) } @@ -798,10 +810,9 @@ impl Watchable<()> for State { #[cfg(test)] mod test { use bitcoin::hashes::Hash as _; - use ed25519_dalek::SigningKey; use crate::{ - authorization, + authorization::{self, BatchVerificationContext}, state::{Error, State, error}, types::{ Address, AuthorizedTransaction, BitAssetData, BitAssetId, @@ -919,8 +930,10 @@ mod test { fn validate_transaction_rejects_missing_authorization() -> anyhow::Result<()> { let (_temp_dir, env, state) = fresh_state("auth_count")?; - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let verifying_key: VerifyingKey = signing_key.verifying_key().into(); + let mut rng = rand::rng(); + let batch_verification_ctxt = BatchVerificationContext::new(&mut rng); + let signing_key = authorization::test_signing_key(1); + let verifying_key = VerifyingKey::from(&signing_key); let address = authorization::get_address(&verifying_key); let outpoint = fund(&env, &state, address, 1000); @@ -936,7 +949,11 @@ mod test { }; let rotxn = env.read_txn()?; let err = state - .validate_transaction(&rotxn, &unauthorized) + .validate_transaction( + &rotxn, + &batch_verification_ctxt, + &unauthorized, + ) .expect_err("tx with no authorizations must be rejected"); anyhow::ensure!( matches!( @@ -949,10 +966,13 @@ mod test { ); // The same transaction with a valid authorization is accepted. - let authorized = - authorization::authorize(&[(address, &signing_key)], transaction)?; + let authorized = authorization::authorize( + &mut rng, + &[(address, &signing_key)], + transaction, + )?; state - .validate_transaction(&rotxn, &authorized) + .validate_transaction(&rotxn, &batch_verification_ctxt, &authorized) .expect("correctly authorized tx should validate"); Ok(()) } diff --git a/lib/state/two_way_peg_data.rs b/lib/state/two_way_peg_data.rs index 72cdc5ab..32582d21 100644 --- a/lib/state/two_way_peg_data.rs +++ b/lib/state/two_way_peg_data.rs @@ -1253,11 +1253,14 @@ mod test { // connecting a deposit then disconnecting it on a reorg must round-trip #[test] fn deposit_reorg_round_trips() -> anyhow::Result<()> { - use crate::types::{ - Body, Header, Transaction, proto::mainchain::Deposit, + use crate::{ + authorization::BatchVerificationContext, + types::{Body, Header, Transaction, proto::mainchain::Deposit}, }; let (_temp_dir, env, state) = fresh_state("deposit_reorg_round_trips")?; + let batch_verification_ctxt = + BatchVerificationContext::new(&mut rand::rng()); let empty_body = Body { coinbase: Vec::new(), transactions: Vec::new(), @@ -1275,7 +1278,12 @@ mod test { }; { let mut rwtxn = env.write_txn()?; - state.apply_block(&mut rwtxn, &genesis, &empty_body)?; + state.apply_block( + &mut rwtxn, + &batch_verification_ctxt, + &genesis, + &empty_body, + )?; state.connect_two_way_peg_data( &mut rwtxn, &TwoWayPegData::default(), @@ -1311,7 +1319,12 @@ mod test { }; { let mut rwtxn = env.write_txn()?; - state.apply_block(&mut rwtxn, &block1, &empty_body)?; + state.apply_block( + &mut rwtxn, + &batch_verification_ctxt, + &block1, + &empty_body, + )?; state.connect_two_way_peg_data(&mut rwtxn, &deposit_twpd)?; anyhow::ensure!( state.utxos.try_get(&rwtxn, &deposit_key)?.is_some() diff --git a/lib/types/keys.rs b/lib/types/keys.rs index 0c00d226..a57041c3 100644 --- a/lib/types/keys.rs +++ b/lib/types/keys.rs @@ -19,7 +19,7 @@ pub enum Bech32mDecodeError { #[error("Invalid bytes: {}", hex::encode(.bytes))] InvalidBytes { bytes: [u8; 32], - source: Box, + source: Box, }, #[error(transparent)] WrongHrp(#[from] Box), @@ -127,34 +127,37 @@ impl Serialize for EncryptionPubKey { } } -fn borsh_serialize_ed25519_vk( - vk: &ed25519_dalek::VerifyingKey, +fn borsh_serialize_ristretto_vk( + vk: &frost_ristretto255::VerifyingKey, writer: &mut W, ) -> borsh::io::Result<()> where W: borsh::io::Write, { - borsh::BorshSerialize::serialize(vk.as_bytes(), writer) + borsh::BorshSerialize::serialize( + vk.to_element().compress().as_bytes(), + writer, + ) } -/// Wrapper around x25519 pubkeys -#[derive(BorshSerialize, Clone, Copy, Debug, Eq, Hash, PartialEq, ToSchema)] +/// Wrapper around ristretto255 verifying keys +#[derive(BorshSerialize, Clone, Copy, Debug, Eq, PartialEq, ToSchema)] #[repr(transparent)] #[schema(value_type = String)] pub struct VerifyingKey( - #[borsh(serialize_with = "borsh_serialize_ed25519_vk")] - pub ed25519_dalek::VerifyingKey, + #[borsh(serialize_with = "borsh_serialize_ristretto_vk")] + pub frost_ristretto255::VerifyingKey, ); impl VerifyingKey { /// HRP for Bech32m encoding const BECH32M_HRP: bech32::Hrp = bech32::Hrp::parse_unchecked("ba-svk"); - const BYTE_LEN: usize = ed25519_dalek::PUBLIC_KEY_LENGTH; + const BYTE_LEN: usize = 32; /// Encode to Bech32m format pub fn bech32m_encode(&self) -> String { - bech32::encode::(Self::BECH32M_HRP, self.0.as_bytes()) + bech32::encode::(Self::BECH32M_HRP, &self.to_bytes()) .expect("Bech32m Encoding should not fail") } @@ -171,7 +174,7 @@ impl VerifyingKey { let Ok(bytes) = <[u8; Self::BYTE_LEN]>::try_from(data) else { return Err(Bech32mDecodeError::WrongSize); }; - let res = match ed25519_dalek::VerifyingKey::from_bytes(&bytes) { + let res = match frost_ristretto255::VerifyingKey::deserialize(&bytes) { Ok(vk) => Self(vk), Err(err) => { let err = Bech32mDecodeError::InvalidBytes { @@ -188,7 +191,16 @@ impl VerifyingKey { } pub fn to_bytes(&self) -> [u8; Self::BYTE_LEN] { - self.0.to_bytes() + self.0.to_element().compress().to_bytes() + } +} + +impl std::hash::Hash for VerifyingKey { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.to_bytes().hash(state) } } @@ -198,25 +210,31 @@ impl std::fmt::Display for VerifyingKey { } } -impl From for VerifyingKey { - fn from(vk: ed25519_dalek::VerifyingKey) -> Self { +impl From for VerifyingKey { + fn from(vk: frost_ristretto255::VerifyingKey) -> Self { Self(vk) } } -impl From for ed25519_dalek::VerifyingKey { +impl From for frost_ristretto255::VerifyingKey { fn from(vk: VerifyingKey) -> Self { vk.0 } } +impl From<&frost_ristretto255::SigningKey> for VerifyingKey { + fn from(sk: &frost_ristretto255::SigningKey) -> Self { + Self(frost_ristretto255::VerifyingKey::from(sk)) + } +} + impl TryFrom<&[u8; VerifyingKey::BYTE_LEN]> for VerifyingKey { - type Error = ed25519_dalek::SignatureError; + type Error = frost_ristretto255::Error; fn try_from( bytes: &[u8; VerifyingKey::BYTE_LEN], ) -> Result { - ed25519_dalek::VerifyingKey::from_bytes(bytes).map(Self) + frost_ristretto255::VerifyingKey::deserialize(bytes).map(Self) } } @@ -235,7 +253,7 @@ impl<'de> Deserialize<'de> for VerifyingKey { { serde_with::IfIsHumanReadable::< DisplayFromStr, - FromInto, + FromInto, >::deserialize_as(deserializer) } } @@ -247,7 +265,7 @@ impl Serialize for VerifyingKey { { serde_with::IfIsHumanReadable::< DisplayFromStr, - FromInto, + FromInto, >::serialize_as(self, serializer) } } diff --git a/lib/types/mod.rs b/lib/types/mod.rs index d53e024b..41e5d141 100644 --- a/lib/types/mod.rs +++ b/lib/types/mod.rs @@ -532,14 +532,6 @@ impl Body { } } -pub trait Verify { - type Error; - fn verify_transaction( - transaction: &AuthorizedTransaction, - ) -> Result<(), Self::Error>; - fn verify_body(body: &Body) -> Result<(), Self::Error>; -} - #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct Block { #[serde(flatten)] diff --git a/lib/wallet.rs b/lib/wallet.rs index 43a4d8b3..db2a80d2 100644 --- a/lib/wallet.rs +++ b/lib/wallet.rs @@ -21,7 +21,10 @@ use thiserror::Error; use tokio_stream::{StreamMap, wrappers::WatchStream}; use crate::{ - authorization::{self, Authorization, Signature, get_address}, + authorization::{ + self, Authorization, Signature, SigningKey, get_address, + rand_core::CryptoRng, + }, types::{ Address, AmountOverflowError, AmountUnderflowError, AssetId, AuthorizedTransaction, BitAssetData, BitAssetId, BitcoinOutputContent, @@ -33,6 +36,13 @@ use crate::{ util::Watchable, }; +/// Reinterpret 32 bip32 secret bytes as a Ristretto255 signing key. +fn signing_key_from_secret_bytes(bytes: [u8; 32]) -> SigningKey { + let scalar = curve25519_dalek::Scalar::from_bytes_mod_order(bytes); + SigningKey::from_scalar(scalar) + .expect("expected secret scalar to be non-zero") +} + #[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] pub struct Balance { #[serde(rename = "total_sats", with = "bitcoin::amount::serde::as_sat")] @@ -280,14 +290,15 @@ impl Wallet { &self, rotxn: &RoTxn, index: u32, - ) -> Result { + ) -> Result { let master_xpriv = self.get_master_xpriv(rotxn)?; let derivation_path = DerivationPath::master() .child(ChildNumber::Hardened { index: 0 }) .child(ChildNumber::Normal { index }); let xpriv = master_xpriv .derive_priv(&bitcoin::key::Secp256k1::new(), &derivation_path)?; - let signing_key = xpriv.private_key.secret_bytes().into(); + let signing_key = + signing_key_from_secret_bytes(xpriv.private_key.secret_bytes()); Ok(signing_key) } @@ -296,14 +307,14 @@ impl Wallet { &self, rotxn: &RoTxn, address: &Address, - ) -> Result { + ) -> Result { let addr_idx = self .address_to_index .try_get(rotxn, address)? .ok_or(Error::AddressDoesNotExist { address: *address })?; let signing_key = self.get_tx_signing_key(rotxn, addr_idx)?; // sanity check that signing key corresponds to address - assert_eq!(*address, get_address(&signing_key.verifying_key().into())); + assert_eq!(*address, get_address(&(&signing_key).into())); Ok(signing_key) } @@ -311,14 +322,15 @@ impl Wallet { &self, rotxn: &RoTxn, index: u32, - ) -> Result { + ) -> Result { let master_xpriv = self.get_master_xpriv(rotxn)?; let derivation_path = DerivationPath::master() .child(ChildNumber::Hardened { index: 2 }) .child(ChildNumber::Normal { index }); let xpriv = master_xpriv .derive_priv(&bitcoin::key::Secp256k1::new(), &derivation_path)?; - let signing_key = xpriv.private_key.secret_bytes().into(); + let signing_key = + signing_key_from_secret_bytes(xpriv.private_key.secret_bytes()); Ok(signing_key) } @@ -327,14 +339,14 @@ impl Wallet { &self, rotxn: &RoTxn, vk: &VerifyingKey, - ) -> Result { + ) -> Result { let vk_idx = self .vk_to_index .try_get(rotxn, vk)? .ok_or_else(|| Box::new(VkDoesNotExistError { vk: *vk }))?; let signing_key = self.get_message_signing_key(rotxn, vk_idx)?; // sanity check that signing key corresponds to vk - assert_eq!(*vk, signing_key.verifying_key().into()); + assert_eq!(*vk, (&signing_key).into()); Ok(signing_key) } @@ -346,7 +358,7 @@ impl Wallet { .map(|(idx, _)| idx + 1) .unwrap_or(0); let tx_signing_key = self.get_tx_signing_key(&txn, next_index)?; - let address = get_address(&tx_signing_key.verifying_key().into()); + let address = get_address(&(&tx_signing_key).into()); self.index_to_address.put(&mut txn, &next_index, &address)?; self.address_to_index.put(&mut txn, &address, &next_index)?; txn.commit()?; @@ -377,7 +389,7 @@ impl Wallet { .map(|(idx, _)| idx + 1) .unwrap_or(0); let signing_key = self.get_message_signing_key(&txn, next_index)?; - let vk = signing_key.verifying_key().into(); + let vk = (&signing_key).into(); self.index_to_vk.put(&mut txn, &next_index, &vk)?; self.vk_to_index.put(&mut txn, &vk, &next_index)?; txn.commit()?; @@ -616,9 +628,11 @@ impl Wallet { let name_hash: Hash = blake3::hash(plain_name.as_bytes()).into(); let bitasset_id = BitAssetId(name_hash); // hmac(secret, name_hash) - let nonce = - blake3::keyed_hash(reservation_signing_key.as_bytes(), &name_hash) - .into(); + let nonce = blake3::keyed_hash( + &reservation_signing_key.to_scalar().to_bytes(), + &name_hash, + ) + .into(); // hmac(nonce, name_hash) let commitment = blake3::keyed_hash(&nonce, &name_hash).into(); // store reservation data @@ -688,7 +702,7 @@ impl Wallet { .get_tx_signing_key_for_addr(&rotxn, &reservation_addr)?; // hmac(secret, name_hash) let nonce = blake3::keyed_hash( - reservation_signing_key.as_bytes(), + &reservation_signing_key.to_scalar().to_bytes(), &name_hash, ) .into(); @@ -1508,10 +1522,14 @@ impl Wallet { Ok(addresses) } - pub fn authorize( + pub fn authorize( &self, + mut rng: R, transaction: Transaction, - ) -> Result { + ) -> Result + where + R: CryptoRng, + { let rotxn = self.env.read_txn()?; let mut authorizations = vec![]; for input in &transaction.inputs { @@ -1536,10 +1554,13 @@ impl Wallet { address: spent_utxo.address, })?; let tx_signing_key = self.get_tx_signing_key(&rotxn, index)?; - let signature = - crate::authorization::sign_tx(&tx_signing_key, &transaction)?; + let signature = crate::authorization::sign_tx( + &mut rng, + &tx_signing_key, + &transaction, + )?; authorizations.push(Authorization { - verifying_key: tx_signing_key.verifying_key().into(), + verifying_key: (&tx_signing_key).into(), signature, }); } @@ -1572,29 +1593,37 @@ impl Wallet { Ok(res) } - pub fn sign_arbitrary_msg( + pub fn sign_arbitrary_msg( &self, + rng: R, verifying_key: &VerifyingKey, msg: &str, - ) -> Result { + ) -> Result + where + R: CryptoRng, + { use authorization::{Dst, sign}; let rotxn = self.env.read_txn()?; let signing_key = self.get_message_signing_key_for_vk(&rotxn, verifying_key)?; - let res = sign(&signing_key, Dst::Arbitrary, msg.as_bytes()); + let res = sign(rng, &signing_key, Dst::Arbitrary, msg.as_bytes()); Ok(res) } - pub fn sign_arbitrary_msg_as_addr( + pub fn sign_arbitrary_msg_as_addr( &self, + rng: R, address: &Address, msg: &str, - ) -> Result { + ) -> Result + where + R: CryptoRng, + { use authorization::{Dst, sign}; let rotxn = self.env.read_txn()?; let signing_key = self.get_tx_signing_key_for_addr(&rotxn, address)?; - let signature = sign(&signing_key, Dst::Arbitrary, msg.as_bytes()); - let verifying_key = signing_key.verifying_key().into(); + let signature = sign(rng, &signing_key, Dst::Arbitrary, msg.as_bytes()); + let verifying_key = (&signing_key).into(); Ok(Authorization { verifying_key, signature,