diff --git a/packages/wasm-mps/Cargo.lock b/packages/wasm-mps/Cargo.lock index 97736688008..ac1fedeadf6 100644 --- a/packages/wasm-mps/Cargo.lock +++ b/packages/wasm-mps/Cargo.lock @@ -12,6 +12,44 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "base16ct" version = "0.2.0" @@ -30,6 +68,12 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bincode" version = "1.3.3" @@ -41,9 +85,9 @@ dependencies = [ [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -51,6 +95,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -60,6 +115,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "bs58" version = "0.5.1" @@ -71,9 +137,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.1" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -95,12 +161,57 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -139,12 +250,39 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "const-crc32-nostd" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "corez" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df6f98652d30167eaeea34d77b730e07c8caba6df17bd4551842b9b8da01deb" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -154,6 +292,12 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crunchy" version = "0.2.4" @@ -168,6 +312,7 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", + "serdect", "subtle", "zeroize", ] @@ -259,6 +404,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -271,6 +427,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -311,6 +476,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -331,6 +502,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "ff" version = "0.13.1" @@ -348,12 +531,101 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "frost-core" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ef2787af391c7e8bedc037a3b9ea03dde803fbd93e778e6bb369547800e5cd" +dependencies = [ + "byteorder", + "const-crc32-nostd", + "derive-getters", + "document-features", + "hex", + "itertools", + "postcard", + "rand_core", + "serde", + "serdect", + "thiserror 2.0.18", + "visibility", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "frost-rerandomized" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4c5cedd2426728adef2c0b1720f57676354c473836d1ccc50d0f0d1c91942b" +dependencies = [ + "derive-getters", + "document-features", + "frost-core", + "hex", + "rand_core", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "garbled-circuit" +version = "1.3.1-pre.2" +source = "git+ssh://git@github.com/silence-laboratories/garbling?rev=b18fb707#b18fb707884fc30e54563265a2afdb99a0076001" +dependencies = [ + "aes", + "generic-array", + "rand", + "rand_chacha", + "sha2", + "signature", + "sl-compute-common", + "sl-messages", + "thiserror 1.0.69", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -378,6 +650,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getset" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "group" version = "0.13.0" @@ -400,6 +683,41 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2_poseidon" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" +dependencies = [ + "bitvec", + "ff", + "group", + "pasta_curves", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "hex" version = "0.4.3" @@ -418,6 +736,15 @@ dependencies = [ "digest", ] +[[package]] +name = "incrementalmerkletree" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30821f91f0fa8660edca547918dc59812893b497d07c1144f326f07fdd94aba9" +dependencies = [ + "either", +] + [[package]] name = "inout" version = "0.1.4" @@ -427,16 +754,40 @@ dependencies = [ "generic-array", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core", + "subtle", +] + [[package]] name = "k256" version = "0.13.4" @@ -449,11 +800,47 @@ dependencies = [ "serdect", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libc" -version = "0.2.182" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" [[package]] name = "multi-party-schnorr" @@ -461,6 +848,7 @@ version = "1.3.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ec19b462861fe10a2c01dd2245ab00a47dfe854a73bec120699ef47849921c0" dependencies = [ + "blake2b_simd", "bytemuck", "ciborium", "crypto-bigint", @@ -470,11 +858,14 @@ dependencies = [ "ed25519-dalek", "elliptic-curve", "ff", + "group", "hmac", "k256", "pasta_curves", "rand", "rand_chacha", + "rand_core", + "reddsa", "serde", "serde_bytes", "sha2", @@ -485,11 +876,45 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -497,21 +922,62 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "orchard" +version = "0.13.1" +source = "git+https://github.com/zcash/orchard?tag=0.13.1#f8915bc5c8d1c9fa3124ad28bcf73ce232ef3669" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "corez", + "ff", + "fpe", + "getset", + "group", + "halo2_poseidon", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "rand_core", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "pasta_curves" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" dependencies = [ + "blake2b_simd", "ff", "group", "hex", + "lazy_static", "rand", "serde", "static_assertions", "subtle", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkcs8" version = "0.10.2" @@ -533,6 +999,19 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -553,9 +1032,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -568,9 +1047,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha", @@ -585,6 +1064,7 @@ checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", + "serde", ] [[package]] @@ -596,6 +1076,25 @@ dependencies = [ "getrandom", ] +[[package]] +name = "reddsa" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4784b85c8bfd17b36b86e664e6e504ecdb586001086ee23749e4a633bbb84832" +dependencies = [ + "blake2b_simd", + "byteorder", + "frost-rerandomized", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core", + "serde", + "thiserror 2.0.18", + "zeroize", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -629,6 +1128,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sec1" version = "0.7.3" @@ -646,9 +1151,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -721,6 +1226,51 @@ dependencies = [ "rand_core", ] +[[package]] +name = "sinsemilla" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" +dependencies = [ + "group", + "pasta_curves", + "subtle", +] + +[[package]] +name = "sl-compute-common" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b202c662800c00d9f5149457220a52aa704ecaf57e5b19a08b2e59d792db3" +dependencies = [ + "aead", + "chacha20", + "crypto-bigint", + "rand", + "rand_chacha", + "serde", +] + +[[package]] +name = "sl-messages" +version = "1.3.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54aeea5765d7fa65dbe33cd40727518e1e5c8655749c024f76e8ed8fca285af" +dependencies = [ + "aead", + "bytemuck", + "bytes", + "chacha20", + "chacha20poly1305", + "derivation-path", + "generic-array", + "rand_core", + "sha2", + "signature", + "x25519-dalek", + "zeroize", +] + [[package]] name = "sl-mpc-derive" version = "0.1.0" @@ -741,9 +1291,9 @@ dependencies = [ [[package]] name = "sl-mpc-mate" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a84618a45d32e654109a17118dc963305fe8177d045af1c818a92289c5382e2" +checksum = "7997a4b34225e7553da96e3c1f27e14b009ff74852962c645570bcd7d812eb7a" dependencies = [ "base64", "bs58", @@ -751,8 +1301,6 @@ dependencies = [ "elliptic-curve", "hex", "hmac", - "k256", - "rand", "rand_core", "ripemd", "serde", @@ -762,6 +1310,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sl-secret-sharing" +version = "0.1.0-pre.1" +source = "git+ssh://git@github.com/silence-laboratories/garbling?rev=b18fb707#b18fb707884fc30e54563265a2afdb99a0076001" +dependencies = [ + "ff", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -772,6 +1343,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -786,9 +1363,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -843,9 +1420,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -856,11 +1433,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -884,6 +1477,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", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -892,9 +1496,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -905,9 +1509,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -915,9 +1519,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -928,9 +1532,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] @@ -945,18 +1549,21 @@ dependencies = [ "getrandom", "js-sys", "multi-party-schnorr", + "orchard", + "pasta_curves", "rand", "serde", "thiserror 2.0.18", "wasm-bindgen", "web-sys", + "zcash", ] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -971,20 +1578,79 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zcash" +version = "0.1.0-pre.10" +source = "git+ssh://git@github.com/silence-laboratories/garbling?rev=b18fb707#b18fb707884fc30e54563265a2afdb99a0076001" +dependencies = [ + "ciborium", + "crypto_box", + "ff", + "garbled-circuit", + "group", + "hex", + "hmac", + "multi-party-schnorr", + "orchard", + "pasta_curves", + "rand", + "rand_chacha", + "serde", + "serde_bytes", + "sha2", + "sl-compute-common", + "sl-messages", + "sl-secret-sharing", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77efec759c3798b6e4d829fcc762070d9b229b0f13338c40bf993b7b609c2272" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core", + "subtle", +] + +[[package]] +name = "zcash_spec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -993,20 +1659,33 @@ 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", "syn", ] + +[[package]] +name = "zip32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" +dependencies = [ + "bech32", + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec", +] diff --git a/packages/wasm-mps/Cargo.toml b/packages/wasm-mps/Cargo.toml index 9d6c435e563..1ebb2010d7e 100644 --- a/packages/wasm-mps/Cargo.toml +++ b/packages/wasm-mps/Cargo.toml @@ -17,7 +17,10 @@ bincode = "1.3" crypto_box = "0.9" getrandom = { version = "0.2", features = ["js"] } js-sys = "0.3" -multi-party-schnorr = { version = "1.3.0-pre.5", features = ["serde"]} +multi-party-schnorr = { version = "1.3.0-pre.5", features = ["serde", "eddsa", "redpallas"] } +orchard = { version = "0.13", default-features = false } +pasta_curves = { version = "0.5", default-features = false } +zcash = { git = "ssh://git@github.com/silence-laboratories/garbling", rev = "b18fb707", features = ["dkg"] } rand = "0.8" serde = { version = "1.0", features = ["derive"] } thiserror = "2.0.18" @@ -29,4 +32,8 @@ ed25519-dalek = "2.1.0" [profile.release] strip = true +lto = "fat" +codegen-units = 1 +[patch.crates-io] +orchard = { git = "https://github.com/zcash/orchard", tag = "0.13.1" } \ No newline at end of file diff --git a/packages/wasm-mps/Makefile b/packages/wasm-mps/Makefile index 406deb84fb4..f77b02fffab 100644 --- a/packages/wasm-mps/Makefile +++ b/packages/wasm-mps/Makefile @@ -30,7 +30,7 @@ endef # run wasm-opt separately so we can pass `--enable-bulk-memory` define WASM_OPT_COMMAND - $(WASM_OPT) --enable-bulk-memory --enable-nontrapping-float-to-int --enable-sign-ext -Oz $(1)/*.wasm -o $(1)/*.wasm + $(WASM_OPT) --enable-bulk-memory --enable-nontrapping-float-to-int --enable-sign-ext --enable-simd -O3 $(1)/*.wasm -o $(1)/*.wasm endef define REMOVE_GITIGNORE diff --git a/packages/wasm-mps/package.json b/packages/wasm-mps/package.json index 500b6ed4b75..cf1918f9e32 100644 --- a/packages/wasm-mps/package.json +++ b/packages/wasm-mps/package.json @@ -46,7 +46,7 @@ ], "scripts": { "test": "npm run test:mocha", - "test:mocha": "mocha --recursive test", + "test:mocha": "mocha --recursive test --node-option=disable-warning=ExperimentalWarning", "build:wasm": "make js/wasm && make dist/esm/js/wasm && make dist/cjs/js/wasm && make dist/web/js/wasm", "build:ts-esm": "tsc", "build:ts-cjs": "tsc --project tsconfig.cjs.json", diff --git a/packages/wasm-mps/src/lib.rs b/packages/wasm-mps/src/lib.rs index 4d9af3ecbfc..be6dd915a58 100644 --- a/packages/wasm-mps/src/lib.rs +++ b/packages/wasm-mps/src/lib.rs @@ -4,6 +4,7 @@ mod mps { use multi_party_schnorr::{ common::{ + redpallas::{RedPallasPoint, RedPallasPointBytes}, ser::Serializable, traits::{GroupElem, Round, ScalarReduce}, Bip32Public, @@ -14,12 +15,18 @@ mod mps { }, sign::{ messages::{SignMsg1, SignMsg2, SignMsg3}, - PartialSign, SignerParty, R0 as DsgR0, R1 as DsgR1, R2 as DsgR2, + PartialSign, SignError, SignReady, SignerParty, R0 as DsgR0, R1 as DsgR1, R2 as DsgR2, }, }; use serde::{Deserialize, Serialize}; - use std::sync::Arc; + use std::{ + io::{Cursor, Read}, + sync::Arc, + }; use thiserror::Error; + use zcash::derivation_session::{ + DerivationStatus, Message as DrvMessage, Session as DerivationSession, + }; /// Errors that can be returned as results. #[derive(Debug, Error)] @@ -35,6 +42,9 @@ mod mps { #[error("Protocol Error")] ProtocolError, + + #[error("Protocol Error: {0}")] + ProtocolErrorDetail(String), } /// Internal DKG state used for round 1. @@ -44,6 +54,7 @@ mod mps { G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { + pub party_id: u8, pub msg: KeygenMsg1, #[serde(bound( @@ -55,22 +66,26 @@ mod mps { /// Internal DKG state used for round 2. #[derive(Serialize, Deserialize)] + #[serde(bound( + serialize = "G::Scalar: Serializable", + deserialize = "G::Scalar: Serializable" + ))] struct DkgStateR2 where G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { + pub party_id: u8, pub msg: KeygenMsg2, - - #[serde(bound( - serialize = "KeygenParty: Serialize", - deserialize = "KeygenParty: Deserialize<'de>" - ))] pub party: KeygenParty, } /// Internal DSG state used for round 1. #[derive(Serialize, Deserialize)] + #[serde(bound( + serialize = "G::Scalar: Serializable", + deserialize = "G::Scalar: Serializable" + ))] struct DsgStateR1 where G: GroupElem, @@ -82,6 +97,10 @@ mod mps { /// Internal DSG state used for round 2. #[derive(Serialize, Deserialize)] + #[serde(bound( + serialize = "G::Scalar: Serializable", + deserialize = "G::Scalar: Serializable" + ))] struct DsgStateR2 where G: GroupElem, @@ -93,6 +112,10 @@ mod mps { /// Internal DSG state used for round 3. #[derive(Serialize, Deserialize)] + #[serde(bound( + serialize = "G::Scalar: Serializable", + deserialize = "G::Scalar: Serializable" + ))] struct DsgStateR3 where G: GroupElem, @@ -100,6 +123,7 @@ mod mps { { pub msg: SignMsg3, pub party: PartialSign, + pub alpha: [u8; 32], } /// Result from processing that includes a public messages for other @@ -116,6 +140,57 @@ mod mps { pub chaincode: [u8; 32], } + pub struct MsgDerivationInit { + pub share: Vec, + pub pk: [u8; 32], + pub drv: Vec, + pub state: Vec, + } + + /// Result from round 3 of RedPallas DSG. + pub struct RedPallasSignature { + pub signature: Vec, + pub rk: [u8; 32], + pub alpha: [u8; 32], + } + + pub struct MsgDerivation { + pub messages: Vec, + pub state: Vec, + pub done: bool, + pub ask: [u8; 32], + pub nk: [u8; 32], + pub rivk: [u8; 32], + pub internal_ivk: [u8; 64], + pub external_ivk: [u8; 64], + } + + /// Orchard incoming viewing keys derived from FVK components. + pub struct RedPallasIvks { + pub internal_ivk: [u8; 64], + pub external_ivk: [u8; 64], + } + + trait IntoSignReady { + fn into_sign_ready(self) -> Result<(SignReady, [u8; 32]), MpsError>; + } + + impl IntoSignReady for SignReady { + fn into_sign_ready(self) -> Result<(SignReady, [u8; 32]), MpsError> { + Ok((self, [0u8; 32])) + } + } + + impl IntoSignReady for (SignReady, T) { + fn into_sign_ready(self) -> Result<(SignReady, [u8; 32]), MpsError> { + let alpha: [u8; 32] = bincode::serialize(&self.1) + .map_err(|_| MpsError::SerializationError)? + .try_into() + .map_err(|_| MpsError::SerializationError)?; + Ok((self.0, alpha)) + } + } + fn rem_prefix(prefix: &str, data: &Vec) -> Result, MpsError> { Ok(data .as_slice() @@ -128,6 +203,73 @@ mod mps { [prefix.as_bytes(), data.as_slice()].concat() } + /// Serialize a message pool as a concatenation of individually-encoded messages. + /// This format supports simple byte concatenation to merge pools. + pub fn serialize_pool(prefix: &str, msgs: &[DrvMessage]) -> Result, MpsError> { + let mut buf = Vec::new(); + for msg in msgs { + buf.extend(add_prefix( + prefix, + &bincode::serialize(&msg).map_err(|_| MpsError::SerializationError)?, + )); + } + Ok(buf) + } + + /// Deserialize a pool produced by `serialize_pool`. + pub fn deserialize_pool(prefix: &str, data: &[u8]) -> Result, MpsError> { + let mut cursor = Cursor::new(data); + let mut msgs = Vec::new(); + while (cursor.position() as usize) < data.len() { + let mut buf_prefix = vec![0u8; prefix.len()]; + cursor + .read_exact(&mut buf_prefix) + .map_err(|_| MpsError::DeserializationError)?; + buf_prefix + .strip_prefix(prefix.as_bytes()) + .ok_or(MpsError::InvalidInput)?; + let msg: DrvMessage = + bincode::deserialize_from(&mut cursor).map_err(|_| MpsError::SerializationError)?; + msgs.push(msg); + } + Ok(msgs) + } + + fn internal_dsg_round2_process( + round2_message: &[u8], + state: &[u8], + ) -> Result + where + G: GroupElem, + G::Scalar: ScalarReduce<[u8; 32]> + Serializable, + SignerParty, G>: Round>, Error = SignError>, + , G> as Round>::Output: IntoSignReady, + SignReady: Round, SignMsg3), Error = SignError>, + { + let i0_msg2: SignMsg2 = + bincode::deserialize(round2_message).map_err(|_| MpsError::DeserializationError)?; + let state: DsgStateR2 = + bincode::deserialize(state).map_err(|_| MpsError::DeserializationError)?; + let msgs = vec![i0_msg2, state.msg]; + let (ready_signer, alpha) = state + .party + .process(msgs) + .map_err(|_| MpsError::ProtocolError)? + .into_sign_ready()?; + let (p3, msg3) = ready_signer + .process(()) + .map_err(|_| MpsError::ProtocolError)?; + let new_state = DsgStateR3 { + msg: msg3.clone(), + party: p3, + alpha, + }; + Ok(MsgState { + msg: bincode::serialize(&msg3).map_err(|_| MpsError::SerializationError)?, + state: bincode::serialize(&new_state).map_err(|_| MpsError::SerializationError)?, + }) + } + fn internal_dkg_round0_process( party_id: u8, decryption_key: &[u8; 32], @@ -135,7 +277,7 @@ mod mps { seed: &[u8; 32], ) -> Result where - G: GroupElem + Serialize, + G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { if party_id >= 3 { @@ -184,6 +326,7 @@ mod mps { // Create the state for storage between rounds let state = DkgStateR1 { + party_id, msg: msg1, party: p1, }; @@ -194,35 +337,12 @@ mod mps { }) } - /// Process round 0 of DKG protocol for Ed25519. - /// party_id: Party identifier / index. - /// decryption_key: Private Curve25519 key. - /// encryption_keys: Public Curve25519 keys of other parties. - /// seed: PRNG seed for entropy. - pub fn ed25519_dkg_round0_process( - party_id: u8, - decryption_key: &[u8; 32], - encryption_keys: &[Vec; 2], - seed: &[u8; 32], - ) -> Result { - let result = internal_dkg_round0_process::( - party_id, - decryption_key, - encryption_keys, - seed, - )?; - Ok(MsgState { - msg: add_prefix("mps-ed25519-dkg-round1-message$", &result.msg), - state: add_prefix("mps-ed25519-dkg-round1-state$", &result.state), - }) - } - fn internal_dkg_round1_process( round1_messages: &[Vec; 2], state: &[u8], ) -> Result where - G: GroupElem + Serialize, + G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { // Parse state @@ -244,6 +364,7 @@ mod mps { // Create the state for storage between rounds let state = DkgStateR2 { + party_id: state.party_id, msg: msg2.clone(), party: p2, }; @@ -254,29 +375,12 @@ mod mps { }) } - /// Process round 1 of DKG protocol. - /// round1_messages: Public messages from other parties. - /// state: Private state result from from round 0. - pub fn ed25519_dkg_round1_process( - round1_messages: &[Vec; 2], - state: &[u8], - ) -> Result { - let i0_msg1 = rem_prefix("mps-ed25519-dkg-round1-message$", &round1_messages[0])?; - let i1_msg1 = rem_prefix("mps-ed25519-dkg-round1-message$", &round1_messages[1])?; - let state = rem_prefix("mps-ed25519-dkg-round1-state$", &state.to_vec())?; - let result = internal_dkg_round1_process::(&[i0_msg1, i1_msg1], &state)?; - Ok(MsgState { - msg: add_prefix("mps-ed25519-dkg-round2-message$", &result.msg), - state: add_prefix("mps-ed25519-dkg-round2-state$", &result.state), - }) - } - fn internal_dkg_round2_process( round2_messages: &[Vec; 2], state: &[u8], - ) -> Result, MpsError> + ) -> Result<(Keyshare, u8), MpsError> where - G: GroupElem + Serialize + for<'de> Deserialize<'de>, + G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { // Deserialize round2 messages from other parties @@ -289,36 +393,20 @@ mod mps { let state: DkgStateR2 = bincode::deserialize(state).map_err(|_| MpsError::DeserializationError)?; + let party_id = state.party_id; + // Generate share let share = state .party .process(vec![i0_msg2, i1_msg2, state.msg.clone()]) .map_err(|_| MpsError::ProtocolError)?; - Ok(share) - } - - /// Process round 2 of DKG protocol. - /// round2_messages: Public messages from other parties. - /// state: Private state result from round 1. - pub fn ed25519_dkg_round2_process( - round2_messages: &[Vec; 2], - state: &[u8], - ) -> Result { - let i0_msg2 = rem_prefix("mps-ed25519-dkg-round2-message$", &round2_messages[0])?; - let i1_msg2 = rem_prefix("mps-ed25519-dkg-round2-message$", &round2_messages[1])?; - let state = rem_prefix("mps-ed25519-dkg-round2-state$", &state.to_vec())?; - let share = internal_dkg_round2_process::(&[i0_msg2, i1_msg2], &state)?; - Ok(Share { - share: bincode::serialize(&share).map_err(|_| MpsError::SerializationError)?, - pk: share.public_key.compress().to_bytes(), - chaincode: share.root_chain_code, - }) + Ok((share, party_id)) } fn internal_dsg_round0_process(p0: SignerParty) -> Result where - G: GroupElem + Serialize, + G: GroupElem, G::Scalar: Serializable, { // Generate message @@ -336,43 +424,12 @@ mod mps { }) } - /// Process round 0 of DSG protocol. - /// share: Signing share from DKG. - /// derivation_path: Key derivation path. - /// message: Message to sign. - pub fn ed25519_dsg_round0_process( - share: &[u8], - derivation_path: String, - message: &[u8], - ) -> Result { - // Deserialize share - let keyshare: Keyshare = - bincode::deserialize(share).map_err(|_| MpsError::DeserializationError)?; - - // Create signer party - let p0 = SignerParty::::new_with_format::<_, Bip32Public>( - Arc::new(keyshare), - message.to_vec(), - derivation_path - .parse() - .map_err(|_| MpsError::DeserializationError)?, - &mut rand::thread_rng(), - ); - - let result = internal_dsg_round0_process(p0)?; - - Ok(MsgState { - msg: add_prefix("mps-ed25519-dsg-round1-message$", &result.msg), - state: add_prefix("mps-ed25519-dsg-round1-state$", &result.state), - }) - } - fn internal_dsg_round1_process( round1_message: &[u8], state: &[u8], ) -> Result where - G: GroupElem + Serialize + for<'de> Deserialize<'de>, + G: GroupElem, G::Scalar: ScalarReduce<[u8; 32]> + Serializable, { // Parse state @@ -402,6 +459,119 @@ mod mps { }) } + fn internal_dsg_round3_process( + round3_message: &[u8], + state: &[u8], + ) -> Result<(Vec, G, [u8; 32]), MpsError> + where + G: GroupElem, + G::Scalar: ScalarReduce<[u8; 32]> + Serializable, + PartialSign: Round>, Output = (S, SC), Error = SignError>, + S: Into<[u8; 64]>, + { + let state: DsgStateR3 = + bincode::deserialize(state).map_err(|_| MpsError::DeserializationError)?; + let i0_msg3: SignMsg3 = + bincode::deserialize(round3_message).map_err(|_| MpsError::DeserializationError)?; + let public_key = state.party.public_key; + let alpha = state.alpha; + let (sig, _) = state + .party + .process(vec![i0_msg3, state.msg]) + .map_err(|_| MpsError::ProtocolError)?; + let sig_bytes: [u8; 64] = sig.into(); + Ok((sig_bytes.to_vec(), public_key, alpha)) + } + + /// Process round 0 of DKG protocol for Ed25519. + /// party_id: Party identifier / index. + /// decryption_key: Private Curve25519 key. + /// encryption_keys: Public Curve25519 keys of other parties. + /// seed: PRNG seed for entropy. + pub fn ed25519_dkg_round0_process( + party_id: u8, + decryption_key: &[u8; 32], + encryption_keys: &[Vec; 2], + seed: &[u8; 32], + ) -> Result { + let result = internal_dkg_round0_process::( + party_id, + decryption_key, + encryption_keys, + seed, + )?; + Ok(MsgState { + msg: add_prefix("mps-ed25519-dkg-round1-message$", &result.msg), + state: add_prefix("mps-ed25519-dkg-round1-state$", &result.state), + }) + } + + /// Process round 1 of DKG protocol. + /// round1_messages: Public messages from other parties. + /// state: Private state result from from round 0. + pub fn ed25519_dkg_round1_process( + round1_messages: &[Vec; 2], + state: &[u8], + ) -> Result { + let i0_msg1 = rem_prefix("mps-ed25519-dkg-round1-message$", &round1_messages[0])?; + let i1_msg1 = rem_prefix("mps-ed25519-dkg-round1-message$", &round1_messages[1])?; + let state = rem_prefix("mps-ed25519-dkg-round1-state$", &state.to_vec())?; + let result = internal_dkg_round1_process::(&[i0_msg1, i1_msg1], &state)?; + Ok(MsgState { + msg: add_prefix("mps-ed25519-dkg-round2-message$", &result.msg), + state: add_prefix("mps-ed25519-dkg-round2-state$", &result.state), + }) + } + + /// Process round 2 of DKG protocol. + /// round2_messages: Public messages from other parties. + /// state: Private state result from round 1. + pub fn ed25519_dkg_round2_process( + round2_messages: &[Vec; 2], + state: &[u8], + ) -> Result { + let i0_msg2 = rem_prefix("mps-ed25519-dkg-round2-message$", &round2_messages[0])?; + let i1_msg2 = rem_prefix("mps-ed25519-dkg-round2-message$", &round2_messages[1])?; + let state = rem_prefix("mps-ed25519-dkg-round2-state$", &state.to_vec())?; + let (share, _) = internal_dkg_round2_process::(&[i0_msg2, i1_msg2], &state)?; + Ok(Share { + share: bincode::serialize(&share).map_err(|_| MpsError::SerializationError)?, + pk: share.public_key.compress().to_bytes(), + chaincode: share.root_chain_code, + }) + } + + /// Process round 0 of DSG protocol. + /// share: Signing share from DKG. + /// derivation_path: Key derivation path. + /// message: Message to sign. + pub fn ed25519_dsg_round0_process( + share: &[u8], + derivation_path: String, + message: &[u8], + ) -> Result { + // Deserialize share + let keyshare: Keyshare = + bincode::deserialize(share).map_err(|_| MpsError::DeserializationError)?; + + // Create signer party + let p0 = SignerParty::::new_with_format::<_, Bip32Public>( + Arc::new(keyshare), + message.to_vec(), + derivation_path + .parse() + .map_err(|_| MpsError::DeserializationError)?, + &mut rand::thread_rng(), + ); + + let result = internal_dsg_round0_process(p0)?; + + Ok(MsgState { + msg: add_prefix("mps-ed25519-dsg-round1-message$", &result.msg), + state: add_prefix("mps-ed25519-dsg-round1-state$", &result.state), + }) + } + /// Process round 1 of DSG protocol. /// round1_messages: Public messages from other parties. /// state: Private state result from round 0. @@ -434,45 +604,15 @@ mod mps { rem_prefix("mps-ed25519-dsg-round2-message$", &round2_message.to_vec())?; let state = rem_prefix("mps-ed25519-dsg-round2-state$", &state.to_vec())?; - // Parse state - let state: DsgStateR2 = - bincode::deserialize(&state).map_err(|_| MpsError::DeserializationError)?; - - // Parse messages - let i0_msg2: SignMsg2 = - bincode::deserialize(&round2_message).map_err(|_| MpsError::DeserializationError)?; - let msgs = vec![i0_msg2, state.msg]; + let result = internal_dsg_round2_process::(&round2_message, &state)?; - // Process all round2 messages together - let party = state - .party - .process(msgs) - .map_err(|_| MpsError::ProtocolError)?; + Ok(MsgState { + msg: add_prefix("mps-ed25519-dsg-round3-message$", &result.msg), + state: add_prefix("mps-ed25519-dsg-round3-state$", &result.state), + }) + } - // Process partial signature - let (p3, msg3) = party.process(()).map_err(|_| MpsError::ProtocolError)?; - - // Create the state for storage between rounds - let state = DsgStateR3 { - msg: msg3.clone(), - party: p3, - }; - - Ok(MsgState { - msg: add_prefix( - "mps-ed25519-dsg-round3-message$", - &bincode::serialize(&msg3).map_err(|_| MpsError::SerializationError)?, - ), - state: add_prefix( - "mps-ed25519-dsg-round3-state$", - &bincode::serialize(&state).map_err(|_| MpsError::SerializationError)?, - ), - }) - } - - /// Process round 3 of DSG protocol. - /// round3_messages: Public messages from other parties. - /// state: Private state result from round 2. + /// Process round 3 of DSG protocol; returns the 64-byte signature. pub fn ed25519_dsg_round3_process( round3_message: &[u8], state: &[u8], @@ -482,22 +622,260 @@ mod mps { rem_prefix("mps-ed25519-dsg-round3-message$", &round3_message.to_vec())?; let state = rem_prefix("mps-ed25519-dsg-round3-state$", &state.to_vec())?; - // Parse state - let state: DsgStateR3 = + let (sig, _, _) = + internal_dsg_round3_process::(&round3_message, &state)?; + + Ok(sig) + } + + /// Process round 0 of RedPallas DKG (same flow as ed25519). + pub fn redpallas_dkg_round0_process( + party_id: u8, + decryption_key: &[u8; 32], + encryption_keys: &[Vec; 2], + seed: &[u8; 32], + ) -> Result { + let result = internal_dkg_round0_process::( + party_id, + decryption_key, + encryption_keys, + seed, + )?; + Ok(MsgState { + msg: add_prefix("mps-redpallas-dkg-round1-message$", &result.msg), + state: add_prefix("mps-redpallas-dkg-round1-state$", &result.state), + }) + } + + /// Process round 1 of RedPallas DKG (same flow as ed25519). + pub fn redpallas_dkg_round1_process( + round1_messages: &[Vec; 2], + state: &[u8], + ) -> Result { + let i0_msg1 = rem_prefix("mps-redpallas-dkg-round1-message$", &round1_messages[0])?; + let i1_msg1 = rem_prefix("mps-redpallas-dkg-round1-message$", &round1_messages[1])?; + let state = rem_prefix("mps-redpallas-dkg-round1-state$", &state.to_vec())?; + let result = internal_dkg_round1_process::(&[i0_msg1, i1_msg1], &state)?; + Ok(MsgState { + msg: add_prefix("mps-redpallas-dkg-round2-message$", &result.msg), + state: add_prefix("mps-redpallas-dkg-round2-state$", &result.state), + }) + } + + /// Process round 2 of RedPallas DKG; finalizes keyshare and starts derivation session. + pub fn redpallas_dkg_round2_process( + round2_messages: &[Vec; 2], + state: &[u8], + derivation_seed: &[u8; 32], + ) -> Result { + let i0_msg2 = rem_prefix("mps-redpallas-dkg-round2-message$", &round2_messages[0])?; + let i1_msg2 = rem_prefix("mps-redpallas-dkg-round2-message$", &round2_messages[1])?; + let state = rem_prefix("mps-redpallas-dkg-round2-state$", &state.to_vec())?; + let (share, party_id) = + internal_dkg_round2_process::(&[i0_msg2, i1_msg2], &state)?; + let pk = RedPallasPointBytes::from(share.public_key).0; + let share_bytes = bincode::serialize(&share).map_err(|_| MpsError::SerializationError)?; + + let (drv_session, initial_outgoing) = + DerivationSession::new(party_id, *share.shamir_share(), *derivation_seed) + .map_err(|_| MpsError::ProtocolError)?; + + let drv = serialize_pool("mps-redpallas-dkg-derivation-message$", &initial_outgoing)?; + + let state = bincode::serialize(&(party_id, &drv_session)) + .map_err(|_| MpsError::SerializationError)?; + + Ok(MsgDerivationInit { + share: share_bytes, + pk, + drv, + state: add_prefix("mps-redpallas-dkg-derivation-state$", &state), + }) + } + + /// Drive the Orchard derivation session forward by one message. + /// `messages` is the global pool shared across all parties. One message + /// addressed to this party is consumed; any generated messages are added. + /// The updated pool is returned as `messages`. Completion is detectable on + /// any subsequent call via `session.derived_keys()`. + pub fn redpallas_derivation_process( + messages: &[u8], + state: &[u8], + ) -> Result { + let state = rem_prefix("mps-redpallas-dkg-derivation-state$", &state.to_vec())?; + let (party_id, mut session): (u8, DerivationSession) = bincode::deserialize(&state).map_err(|_| MpsError::DeserializationError)?; - // Parse messages - let i0_msg3: SignMsg3 = - bincode::deserialize(&round3_message).map_err(|_| MpsError::DeserializationError)?; - let msgs = vec![i0_msg3, state.msg]; + let mut pool = deserialize_pool("mps-redpallas-dkg-derivation-message$", messages)?; + + // Find and consume the first message in the pool addressed to this party. + let pos = pool + .iter() + .position(|msg| msg.receiver().is_none_or(|to| to == party_id)); + if let Some(idx) = pos { + let msg = pool.remove(idx); + let phase = session.current_phase_name(); + let mut outgoing: Vec = Vec::new(); + let status = session + .handle_messages(vec![msg], &mut outgoing) + .map_err(|e| MpsError::ProtocolErrorDetail(format!("phase={phase}: {e:?}")))?; + if let DerivationStatus::Aborted(reason) = status { + return Err(MpsError::ProtocolErrorDetail(format!( + "Aborted: {reason:?}" + ))); + } + pool.extend(outgoing); + } - // Process all round2 messages together - let (signature, _) = state - .party - .process(msgs) - .map_err(|_| MpsError::ProtocolError)?; + let new_messages = serialize_pool("mps-redpallas-dkg-derivation-message$", &pool)?; + + let (done, ask, nk, rivk, internal_ivk, external_ivk) = + if let Some(keys) = session.derived_keys() { + ( + true, + keys.ask, + keys.nk, + keys.rivk, + keys.internal_ivk, + keys.external_ivk, + ) + } else { + (false, [0u8; 32], [0u8; 32], [0u8; 32], [0u8; 64], [0u8; 64]) + }; + + let new_state = + bincode::serialize(&(party_id, &session)).map_err(|_| MpsError::SerializationError)?; + + Ok(MsgDerivation { + messages: new_messages, + state: add_prefix("mps-redpallas-dkg-derivation-state$", &new_state), + done, + ask, + nk, + rivk, + internal_ivk, + external_ivk, + }) + } + + /// Process round 0 of RedPallas DSG. + pub fn redpallas_dsg_round0_process( + share: &[u8], + message: &[u8], + ) -> Result { + let keyshare: Keyshare = + bincode::deserialize(share).map_err(|_| MpsError::DeserializationError)?; + let p0 = SignerParty::::new( + Arc::new(keyshare), + message.to_vec(), + "m".parse().map_err(|_| MpsError::InvalidInput)?, + &mut rand::thread_rng(), + ); + let result = internal_dsg_round0_process(p0)?; + Ok(MsgState { + msg: add_prefix("mps-redpallas-dsg-round1-message$", &result.msg), + state: add_prefix("mps-redpallas-dsg-round1-state$", &result.state), + }) + } + + /// Process round 1 of RedPallas DSG. + pub fn redpallas_dsg_round1_process( + round1_message: &[u8], + state: &[u8], + ) -> Result { + let round1_message = rem_prefix( + "mps-redpallas-dsg-round1-message$", + &round1_message.to_vec(), + )?; + let state = rem_prefix("mps-redpallas-dsg-round1-state$", &state.to_vec())?; + let result = internal_dsg_round1_process::(&round1_message, &state)?; + Ok(MsgState { + msg: add_prefix("mps-redpallas-dsg-round2-message$", &result.msg), + state: add_prefix("mps-redpallas-dsg-round2-state$", &result.state), + }) + } + + /// Process round 2 of RedPallas DSG. + pub fn redpallas_dsg_round2_process( + round2_message: &[u8], + state: &[u8], + ) -> Result { + let round2_message = rem_prefix( + "mps-redpallas-dsg-round2-message$", + &round2_message.to_vec(), + )?; + let state = rem_prefix("mps-redpallas-dsg-round2-state$", &state.to_vec())?; + let result = internal_dsg_round2_process::(&round2_message, &state)?; + Ok(MsgState { + msg: add_prefix("mps-redpallas-dsg-round3-message$", &result.msg), + state: add_prefix("mps-redpallas-dsg-round3-state$", &result.state), + }) + } + + /// Derive Orchard incoming viewing keys from FVK components (ask, nk, rivk). + /// Applies sign correction to ask so the resulting ak has a positive x-coordinate + /// (bit 255 of encoding == 0), matching the Orchard FVK convention. + pub fn redpallas_fvk_to_ivks( + ask: &[u8; 32], + nk: &[u8; 32], + rivk: &[u8; 32], + ) -> Result { + use multi_party_schnorr::group::ff::PrimeField; + use orchard::{ + keys::{FullViewingKey, Scope}, + primitives::redpallas::{SigningKey, SpendAuth, VerificationKey}, + }; + use pasta_curves::pallas; + + // Compute ak = ask * G_SpendAuth with sign correction (bit 255 must be 0). + let mut ask_eff: pallas::Scalar = + Option::from(pallas::Scalar::from_repr(*ask)).ok_or(MpsError::InvalidInput)?; + let ak_bytes: [u8; 32] = loop { + let sk: SigningKey = ask_eff + .to_repr() + .try_into() + .map_err(|_| MpsError::InvalidInput)?; + let vk: VerificationKey = (&sk).into(); + let ak_candidate: [u8; 32] = (&vk).into(); + if (ak_candidate[31] >> 7) == 1 { + ask_eff = -ask_eff; + continue; + } + break ak_candidate; + }; + + let mut fvk_bytes = [0u8; 96]; + fvk_bytes[0..32].copy_from_slice(&ak_bytes); + fvk_bytes[32..64].copy_from_slice(nk); + fvk_bytes[64..96].copy_from_slice(rivk); - Ok(signature.to_vec()) + let fvk = FullViewingKey::from_bytes(&fvk_bytes).ok_or(MpsError::InvalidInput)?; + + Ok(RedPallasIvks { + internal_ivk: fvk.to_ivk(Scope::Internal).to_bytes(), + external_ivk: fvk.to_ivk(Scope::External).to_bytes(), + }) + } + + /// Process round 3 of RedPallas DSG; returns the 64-byte signature and the + /// per-session randomized verification key (rk) against which it verifies. + pub fn redpallas_dsg_round3_process( + round3_message: &[u8], + state: &[u8], + ) -> Result { + let round3_message = rem_prefix( + "mps-redpallas-dsg-round3-message$", + &round3_message.to_vec(), + )?; + let state = rem_prefix("mps-redpallas-dsg-round3-state$", &state.to_vec())?; + let (signature, pk, alpha) = + internal_dsg_round3_process::(&round3_message, &state)?; + let rk = RedPallasPointBytes::from(pk).0; + Ok(RedPallasSignature { + signature, + rk, + alpha, + }) } } @@ -742,6 +1120,140 @@ mod tests { ) .unwrap(); } + + /// Test full RedPallas DSG protocol; verifies alpha is non-zero, consistent + /// between parties, and that the signature verifies against rk. + #[test] + fn test_redpallas_dsg() { + use orchard::primitives::redpallas::{Signature, SpendAuth, VerificationKey}; + + let mut prv_keys = Vec::new(); + let mut pub_keys = Vec::new(); + let mut seeds = Vec::new(); + for i in 0..3 { + let secret_key = crypto_box::SecretKey::generate(&mut rand::thread_rng()); + let public_key = secret_key.public_key(); + prv_keys.push(secret_key); + pub_keys.push((i, public_key)); + let seed: [u8; 32] = rand::thread_rng().gen(); + seeds.push(seed); + } + let derivation_seed: [u8; 32] = rand::thread_rng().gen(); + + // DKG round 0 + let dkg_p0_0 = mps::redpallas_dkg_round0_process( + 0, + &prv_keys[0].to_bytes(), + &[ + pub_keys[1].1.to_bytes().to_vec(), + pub_keys[2].1.to_bytes().to_vec(), + ], + &seeds[0], + ) + .unwrap(); + let dkg_p1_0 = mps::redpallas_dkg_round0_process( + 1, + &prv_keys[1].to_bytes(), + &[ + pub_keys[0].1.to_bytes().to_vec(), + pub_keys[2].1.to_bytes().to_vec(), + ], + &seeds[1], + ) + .unwrap(); + let dkg_p2_0 = mps::redpallas_dkg_round0_process( + 2, + &prv_keys[2].to_bytes(), + &[ + pub_keys[0].1.to_bytes().to_vec(), + pub_keys[1].1.to_bytes().to_vec(), + ], + &seeds[2], + ) + .unwrap(); + + // DKG round 1 + let dkg_p0_1 = mps::redpallas_dkg_round1_process( + &[dkg_p1_0.msg.clone(), dkg_p2_0.msg.clone()], + dkg_p0_0.state.as_slice(), + ) + .unwrap(); + let dkg_p1_1 = mps::redpallas_dkg_round1_process( + &[dkg_p0_0.msg.clone(), dkg_p2_0.msg.clone()], + dkg_p1_0.state.as_slice(), + ) + .unwrap(); + let dkg_p2_1 = mps::redpallas_dkg_round1_process( + &[dkg_p0_0.msg.clone(), dkg_p1_0.msg.clone()], + dkg_p2_0.state.as_slice(), + ) + .unwrap(); + + // DKG round 2 - get shares for parties 0 and 2 + let dkg_p0_init = mps::redpallas_dkg_round2_process( + &[dkg_p1_1.msg.clone(), dkg_p2_1.msg.clone()], + dkg_p0_1.state.as_slice(), + &derivation_seed, + ) + .unwrap(); + let dkg_p2_init = mps::redpallas_dkg_round2_process( + &[dkg_p0_1.msg.clone(), dkg_p1_1.msg.clone()], + dkg_p2_1.state.as_slice(), + &derivation_seed, + ) + .unwrap(); + + assert_eq!(dkg_p0_init.pk, dkg_p2_init.pk, "DKG public keys differ"); + + let msg = b"Test message for RedPallas signing"; + + // DSG round 0 + let dsg_p0_0 = + mps::redpallas_dsg_round0_process(dkg_p0_init.share.as_slice(), msg).unwrap(); + let dsg_p2_0 = + mps::redpallas_dsg_round0_process(dkg_p2_init.share.as_slice(), msg).unwrap(); + + // DSG round 1 + let dsg_p0_1 = + mps::redpallas_dsg_round1_process(dsg_p2_0.msg.as_slice(), dsg_p0_0.state.as_slice()) + .unwrap(); + let dsg_p2_1 = + mps::redpallas_dsg_round1_process(dsg_p0_0.msg.as_slice(), dsg_p2_0.state.as_slice()) + .unwrap(); + + // DSG round 2 + let dsg_p0_2 = + mps::redpallas_dsg_round2_process(dsg_p2_1.msg.as_slice(), dsg_p0_1.state.as_slice()) + .unwrap(); + let dsg_p2_2 = + mps::redpallas_dsg_round2_process(dsg_p0_1.msg.as_slice(), dsg_p2_1.state.as_slice()) + .unwrap(); + + // DSG round 3 + let dsg_p0 = + mps::redpallas_dsg_round3_process(dsg_p2_2.msg.as_slice(), dsg_p0_2.state.as_slice()) + .unwrap(); + let dsg_p2 = + mps::redpallas_dsg_round3_process(dsg_p0_2.msg.as_slice(), dsg_p2_2.state.as_slice()) + .unwrap(); + + // Both parties produce identical outputs + assert_eq!(dsg_p0.signature, dsg_p2.signature, "Signatures differ"); + assert_eq!(dsg_p0.rk, dsg_p2.rk, "Randomized keys differ"); + assert_eq!(dsg_p0.alpha, dsg_p2.alpha, "Alpha values differ"); + + // Alpha is a random field element and must not be zero + assert_ne!(dsg_p0.alpha, [0u8; 32], "Alpha is zero"); + + // Signature verifies against rk, not the original public key + let rk = VerificationKey::::try_from(dsg_p0.rk) + .expect("rk must be a valid verification key"); + let sig = Signature::::from( + <[u8; 64]>::try_from(dsg_p0.signature.as_slice()).expect("signature must be 64 bytes"), + ); + rk.verify(msg, &sig) + .expect("signature must verify against rk"); + } } use js_sys::Array; @@ -792,28 +1304,110 @@ impl Share { } #[wasm_bindgen] -pub struct MsgShare { - msg: Vec, - share: Share, +pub struct MsgDerivationInit { + share: Vec, + pk: Vec, + drv: Vec, + state: Vec, } #[wasm_bindgen] -impl MsgShare { +impl MsgDerivationInit { #[wasm_bindgen(getter)] - pub fn msg(&self) -> Vec { - self.msg.clone() + pub fn share(&self) -> Vec { + self.share.clone() } #[wasm_bindgen(getter)] - pub fn share(&self) -> Share { - Share { - share: self.share.share.clone(), - pk: self.share.pk.clone(), - chaincode: self.share.chaincode.clone(), - } + pub fn pk(&self) -> Vec { + self.pk.clone() + } + + #[wasm_bindgen(getter)] + pub fn drv(&self) -> Vec { + self.drv.clone() + } + + #[wasm_bindgen(getter)] + pub fn state(&self) -> Vec { + self.state.clone() } } +#[wasm_bindgen] +pub struct MsgDerivation { + messages: Vec, + state: Vec, + done: bool, + ask: Vec, + nk: Vec, + rivk: Vec, + internal_ivk: Vec, + external_ivk: Vec, +} + +#[wasm_bindgen] +impl MsgDerivation { + #[wasm_bindgen(getter)] + pub fn messages(&self) -> Vec { + self.messages.clone() + } + + #[wasm_bindgen(getter)] + pub fn state(&self) -> Vec { + self.state.clone() + } + + #[wasm_bindgen(getter)] + pub fn done(&self) -> bool { + self.done + } + + #[wasm_bindgen(getter)] + pub fn ask(&self) -> Vec { + self.ask.clone() + } + + #[wasm_bindgen(getter)] + pub fn nk(&self) -> Vec { + self.nk.clone() + } + + #[wasm_bindgen(getter)] + pub fn rivk(&self) -> Vec { + self.rivk.clone() + } + + #[wasm_bindgen(getter)] + pub fn internal_ivk(&self) -> Vec { + self.internal_ivk.clone() + } + + #[wasm_bindgen(getter)] + pub fn external_ivk(&self) -> Vec { + self.external_ivk.clone() + } +} + +/// Extract exactly two `Vec` from a JS `Uint8Array[]`. +fn js_array_to_2_bufs(arr: &Array) -> Result<[Vec; 2], String> { + use wasm_bindgen::JsCast; + if arr.length() < 2 { + return Err(mps::MpsError::InvalidInput.to_string()); + } + let b0 = arr + .get(0) + .dyn_into::() + .map_err(|_| mps::MpsError::InvalidInput.to_string())? + .to_vec(); + let b1 = arr + .get(1) + .dyn_into::() + .map_err(|_| mps::MpsError::InvalidInput.to_string())? + .to_vec(); + Ok([b0, b1]) +} + #[wasm_bindgen] pub fn ed25519_dkg_round0_process( party_id: u8, @@ -825,16 +1419,10 @@ pub fn ed25519_dkg_round0_process( .try_into() .map_err(|_| "Deserialization Error")?; let seed_32: [u8; 32] = seed[..32].try_into().map_err(|_| "Deserialization Error")?; - let result = mps::ed25519_dkg_round0_process( - party_id, - &decryption_key_32, - &[ - js_sys::Uint8Array::from(encryption_keys.get(0)).to_vec(), - js_sys::Uint8Array::from(encryption_keys.get(1)).to_vec(), - ], - &seed_32, - ) - .map_err(|e| e.to_string())?; + let [ek0, ek1] = js_array_to_2_bufs(&encryption_keys)?; + let result = + mps::ed25519_dkg_round0_process(party_id, &decryption_key_32, &[ek0, ek1], &seed_32) + .map_err(|e| e.to_string())?; Ok(MsgState { msg: result.msg, @@ -847,14 +1435,8 @@ pub fn ed25519_dkg_round1_process( round1_messages: Array, state: &[u8], ) -> Result { - let result = mps::ed25519_dkg_round1_process( - &[ - js_sys::Uint8Array::from(round1_messages.get(0)).to_vec(), - js_sys::Uint8Array::from(round1_messages.get(1)).to_vec(), - ], - state, - ) - .map_err(|e| e.to_string())?; + let [m0, m1] = js_array_to_2_bufs(&round1_messages)?; + let result = mps::ed25519_dkg_round1_process(&[m0, m1], state).map_err(|e| e.to_string())?; Ok(MsgState { msg: result.msg, @@ -864,14 +1446,8 @@ pub fn ed25519_dkg_round1_process( #[wasm_bindgen] pub fn ed25519_dkg_round2_process(round2_messages: Array, state: &[u8]) -> Result { - let result = mps::ed25519_dkg_round2_process( - &[ - js_sys::Uint8Array::from(round2_messages.get(0)).to_vec(), - js_sys::Uint8Array::from(round2_messages.get(1)).to_vec(), - ], - state, - ) - .map_err(|e| e.to_string())?; + let [m0, m1] = js_array_to_2_bufs(&round2_messages)?; + let result = mps::ed25519_dkg_round2_process(&[m0, m1], state).map_err(|e| e.to_string())?; Ok(Share { share: result.share, @@ -924,3 +1500,220 @@ pub fn ed25519_dsg_round3_process(round2_message: &[u8], state: &[u8]) -> Result Ok(result.to_vec()) } + +#[wasm_bindgen] +pub fn redpallas_dkg_round0_process( + party_id: u8, + decryption_key: &[u8], + encryption_keys: Array, + seed: &[u8], +) -> Result { + let decryption_key_32: [u8; 32] = decryption_key[..32] + .try_into() + .map_err(|_| "Deserialization Error")?; + let seed_32: [u8; 32] = seed[..32].try_into().map_err(|_| "Deserialization Error")?; + let [ek0, ek1] = js_array_to_2_bufs(&encryption_keys)?; + let result = + mps::redpallas_dkg_round0_process(party_id, &decryption_key_32, &[ek0, ek1], &seed_32) + .map_err(|e| e.to_string())?; + + Ok(MsgState { + msg: result.msg, + state: result.state, + }) +} + +#[wasm_bindgen] +pub fn redpallas_dkg_round1_process( + round1_messages: Array, + state: &[u8], +) -> Result { + let [m0, m1] = js_array_to_2_bufs(&round1_messages)?; + let result = mps::redpallas_dkg_round1_process(&[m0, m1], state).map_err(|e| e.to_string())?; + + Ok(MsgState { + msg: result.msg, + state: result.state, + }) +} + +#[wasm_bindgen] +pub fn redpallas_dkg_round2_process( + round2_messages: Array, + state: &[u8], + derivation_seed: &[u8], +) -> Result { + let derivation_seed_32: [u8; 32] = derivation_seed[..32] + .try_into() + .map_err(|_| "Deserialization Error")?; + let [m0, m1] = js_array_to_2_bufs(&round2_messages)?; + let result = mps::redpallas_dkg_round2_process(&[m0, m1], state, &derivation_seed_32) + .map_err(|e| e.to_string())?; + + Ok(MsgDerivationInit { + share: result.share, + pk: result.pk.to_vec(), + drv: result.drv, + state: result.state, + }) +} + +#[wasm_bindgen] +pub fn redpallas_derivation_process(parts: Array, state: &[u8]) -> Result { + use wasm_bindgen::JsCast; + let mut messages: Vec = Vec::new(); + for i in 0..parts.length() { + let buf = parts + .get(i) + .dyn_into::() + .map_err(|_| mps::MpsError::InvalidInput.to_string())? + .to_vec(); + messages.extend_from_slice(&buf); + } + let result = mps::redpallas_derivation_process(&messages, state).map_err(|e| e.to_string())?; + + Ok(MsgDerivation { + messages: result.messages, + state: result.state, + done: result.done, + ask: result.ask.to_vec(), + nk: result.nk.to_vec(), + rivk: result.rivk.to_vec(), + internal_ivk: result.internal_ivk.to_vec(), + external_ivk: result.external_ivk.to_vec(), + }) +} + +#[wasm_bindgen] +pub fn redpallas_dsg_round0_process(share: &[u8], message: &[u8]) -> Result { + let result = mps::redpallas_dsg_round0_process(share, message).map_err(|e| e.to_string())?; + + Ok(MsgState { + msg: result.msg, + state: result.state, + }) +} + +#[wasm_bindgen] +pub fn redpallas_dsg_round1_process( + round1_message: &[u8], + state: &[u8], +) -> Result { + let result = + mps::redpallas_dsg_round1_process(round1_message, state).map_err(|e| e.to_string())?; + + Ok(MsgState { + msg: result.msg, + state: result.state, + }) +} + +#[wasm_bindgen] +pub fn redpallas_dsg_round2_process( + round2_message: &[u8], + state: &[u8], +) -> Result { + let result = + mps::redpallas_dsg_round2_process(round2_message, state).map_err(|e| e.to_string())?; + + Ok(MsgState { + msg: result.msg, + state: result.state, + }) +} + +#[wasm_bindgen] +pub struct RedPallasSignature { + signature: Vec, + rk: Vec, + alpha: Vec, +} + +#[wasm_bindgen] +impl RedPallasSignature { + #[wasm_bindgen(getter)] + pub fn signature(&self) -> Vec { + self.signature.clone() + } + + #[wasm_bindgen(getter)] + pub fn rk(&self) -> Vec { + self.rk.clone() + } + + #[wasm_bindgen(getter)] + pub fn alpha(&self) -> Vec { + self.alpha.clone() + } +} + +#[wasm_bindgen] +pub fn redpallas_dsg_round3_process( + round3_message: &[u8], + state: &[u8], +) -> Result { + let result = + mps::redpallas_dsg_round3_process(round3_message, state).map_err(|e| e.to_string())?; + Ok(RedPallasSignature { + signature: result.signature, + rk: result.rk.to_vec(), + alpha: result.alpha.to_vec(), + }) +} + +#[wasm_bindgen] +pub struct RedPallasIvks { + internal_ivk: Vec, + external_ivk: Vec, +} + +#[wasm_bindgen] +impl RedPallasIvks { + #[wasm_bindgen(getter)] + pub fn internal_ivk(&self) -> Vec { + self.internal_ivk.clone() + } + + #[wasm_bindgen(getter)] + pub fn external_ivk(&self) -> Vec { + self.external_ivk.clone() + } +} + +#[wasm_bindgen] +pub fn redpallas_fvk_to_ivks(ask: &[u8], nk: &[u8], rivk: &[u8]) -> Result { + let ask_32: [u8; 32] = ask.try_into().map_err(|_| "ask must be 32 bytes")?; + let nk_32: [u8; 32] = nk.try_into().map_err(|_| "nk must be 32 bytes")?; + let rivk_32: [u8; 32] = rivk.try_into().map_err(|_| "rivk must be 32 bytes")?; + let result = + mps::redpallas_fvk_to_ivks(&ask_32, &nk_32, &rivk_32).map_err(|e| e.to_string())?; + Ok(RedPallasIvks { + internal_ivk: result.internal_ivk.to_vec(), + external_ivk: result.external_ivk.to_vec(), + }) +} + +#[wasm_bindgen] +pub fn redpallas_verify(pk: &[u8], sig: &[u8], msg: &[u8]) -> Result { + use orchard::primitives::redpallas::{Signature, SpendAuth, VerificationKey}; + + let pk_bytes: [u8; 32] = pk.try_into().map_err(|_| "pk must be 32 bytes")?; + let sig_bytes: [u8; 64] = sig.try_into().map_err(|_| "sig must be 64 bytes")?; + + let vk = VerificationKey::::try_from(pk_bytes).map_err(|e| e.to_string())?; + let signature = Signature::::from(sig_bytes); + + Ok(vk.verify(msg, &signature).is_ok()) +} + +/// Compute the spending verification key ak = ask * G_SpendAuth. +#[wasm_bindgen] +pub fn redpallas_ask_to_ak(ask: &[u8]) -> Result, String> { + use orchard::primitives::redpallas::{SigningKey, SpendAuth, VerificationKey}; + + let ask_bytes: [u8; 32] = ask.try_into().map_err(|_| "ask must be 32 bytes")?; + let sk = SigningKey::::try_from(ask_bytes).map_err(|e| e.to_string())?; + let vk = VerificationKey::::from(&sk); + let ak_bytes: [u8; 32] = (&vk).into(); + Ok(ak_bytes.to_vec()) +} diff --git a/packages/wasm-mps/test/mps.ts b/packages/wasm-mps/test/mps.ts index 05ff3cdc2bc..cbaa70244e2 100644 --- a/packages/wasm-mps/test/mps.ts +++ b/packages/wasm-mps/test/mps.ts @@ -28,451 +28,972 @@ describe("mps", function () { } }); - describe("dkg", function () { - it("performs round 0", function () { - const messagePrefix = Buffer.from("mps-ed25519-dkg-round1-message$"); - const statePrefix = Buffer.from("mps-ed25519-dkg-round1-state$"); - for (let i = 0; i < keypairs.length; i++) { - const result = mps.ed25519_dkg_round0_process( - i, - keypairs[i].privateKey, - otherIndices[i].map((i) => keypairs[i].publicKey), - crypto.randomBytes(32), + describe("ed25519", function () { + describe("dkg", function () { + it("performs round 0", function () { + const messagePrefix = Buffer.from("mps-ed25519-dkg-round1-message$"); + const statePrefix = Buffer.from("mps-ed25519-dkg-round1-state$"); + for (let i = 0; i < keypairs.length; i++) { + const result = mps.ed25519_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((i) => keypairs[i].publicKey), + crypto.randomBytes(32), + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); + + let results1: Array; + + before("performs round 0", function () { + results1 = [0, 1, 2].map((i) => + mps.ed25519_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((i) => keypairs[i].publicKey), + crypto.randomBytes(32), + ), ); - assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); - assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); - } - }); + }); - let results1: Array; + it("performs round 1", function () { + const messagePrefix = Buffer.from("mps-ed25519-dkg-round2-message$"); + const statePrefix = Buffer.from("mps-ed25519-dkg-round2-state$"); + for (let i = 0; i < results1.length; i++) { + const result = mps.ed25519_dkg_round1_process( + otherIndices[i].map((i) => results1[i].msg), + results1[i].state, + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); - before("performs round 0", function () { - results1 = [0, 1, 2].map((i) => - mps.ed25519_dkg_round0_process( - i, - keypairs[i].privateKey, - otherIndices[i].map((i) => keypairs[i].publicKey), - crypto.randomBytes(32), - ), - ); - }); + it("fails to perform round 1 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-ed25519-dkg-round1-message$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.ed25519_dkg_round1_process( + otherIndices[i].map((i) => Buffer.from(results1[i].msg).slice(messagePrefix.length)), + results1[i].state, + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round1_process( + otherIndices[i].map((i) => + Buffer.concat([ + Buffer.from("msg-ed25519-dkg-round2-message$"), + Buffer.from(results1[i].msg).slice(messagePrefix.length), + ]), + ), + results1[i].state, + ), + ); + } + }); - it("performs round 1", function () { - const messagePrefix = Buffer.from("mps-ed25519-dkg-round2-message$"); - const statePrefix = Buffer.from("mps-ed25519-dkg-round2-state$"); - for (let i = 0; i < results1.length; i++) { - const result = mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => results1[i].msg), - results1[i].state, - ); - assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); - assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); - } - }); + it("fails to perform round 1 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-ed25519-dkg-round1-state$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.ed25519_dkg_round1_process( + otherIndices[i].map((i) => results1[i].msg), + Buffer.from(results1[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round1_process( + results1[i].msg, + Buffer.concat([ + "mps-ed25519-dkg-round2-state$", + Buffer.from(results1[i].state).slice(statePrefix.length), + ]), + ), + ); + } + }); + + let results2: Array; - it("fails to perform round 1 with invalid message prefix", function () { - const messagePrefix = Buffer.from("mps-ed25519-dkg-round1-message$"); - for (let i = 0; i < results1.length; i++) { - shouldThrow(() => + before("performs round 1", function () { + results2 = [0, 1, 2].map((i) => mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => Buffer.from(results1[i].msg).slice(messagePrefix.length)), + otherIndices[i].map((i) => results1[i].msg), results1[i].state, ), ); - shouldThrow(() => - mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => + }); + + it("performs round 2", function () { + const results3 = [0, 1, 2].map((i) => + mps.ed25519_dkg_round2_process( + otherIndices[i].map((i) => results2[i].msg), + results2[i].state, + ), + ); + for (let i = 0; i < 2; i++) { + assert.ok(results3[i].pk.every((value, index) => value === results3[2].pk[index])); + assert.ok( + results3[i].chaincode.every((value, index) => value === results3[2].chaincode[index]), + ); + } + }); + + it("fails to perform round 2 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-ed25519-dkg-round2-message$"); + for (let i = 0; i < results2.length; i++) { + shouldThrow(() => + mps.ed25519_dkg_round2_process( + otherIndices[i].map((i) => Buffer.from(results2[i].msg).slice(messagePrefix.length)), + results2[i].state, + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round2_process( + otherIndices[i].map((i) => + Buffer.concat([ + Buffer.from("msg-ed25519-dkg-round3-message$"), + Buffer.from(results2[i].msg).slice(messagePrefix.length), + ]), + ), + results2[i].state, + ), + ); + } + }); + + it("fails to perform round 2 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-ed25519-dkg-round2-state$"); + for (let i = 0; i < results2.length; i++) { + shouldThrow(() => + mps.ed25519_dkg_round2_process( + otherIndices[i].map((i) => results2[i].msg), + Buffer.from(results2[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round2_process( + results2[i].msg, Buffer.concat([ - Buffer.from("msg-ed25519-dkg-round2-message$"), - Buffer.from(results1[i].msg).slice(messagePrefix.length), + "mps-ed25519-dkg-round3-state$", + Buffer.from(results2[i].state).slice(statePrefix.length), ]), ), - results1[i].state, - ), - ); - } + ); + } + }); + + describe("input handling", function () { + function shouldThrow(fn: () => unknown): unknown { + try { + fn(); + } catch (e: unknown) { + return e; + } + throw new Error("Expected function to throw an error"); + } + + describe("round0_process", function () { + it("does not panic on bad party size", function () { + shouldThrow(() => + mps.ed25519_dkg_round0_process( + "255", + Buffer.alloc(32), + [Buffer.alloc(32), Buffer.alloc(32)], + crypto.randomBytes(32), + ), + ); + }); + + it("does not panic on bad encryption key", function () { + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + "encryption key", + [Buffer.alloc(32), Buffer.alloc(32)], + crypto.randomBytes(32), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + [Buffer.alloc(32), Buffer.alloc(32)], + crypto.randomBytes(32), + ), + ); + }); + + it("does not panic on bad decryption keys", function () { + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + "decryption keys", + crypto.randomBytes(32), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process(0, Buffer.alloc(0), [], crypto.randomBytes(32)), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + ["decryption key"], + crypto.randomBytes(32), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + [Buffer.alloc(0)], + crypto.randomBytes(32), + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + [Buffer.alloc(32), Buffer.alloc(0)], + crypto.randomBytes(32), + ), + ); + }); + + it("does not panic on bad seed", function () { + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + [Buffer.alloc(32), Buffer.alloc(32)], + "seed", + ), + ); + shouldThrow(() => + mps.ed25519_dkg_round0_process( + 0, + Buffer.alloc(0), + [Buffer.alloc(32), Buffer.alloc(32)], + Buffer.alloc(0), + ), + ); + }); + }); + + describe("round1_process", function () { + it("does not panic on bad messages", function () { + shouldThrow(() => mps.ed25519_dkg_round1_process("messages", Buffer.alloc(1224))); + shouldThrow(() => mps.ed25519_dkg_round1_process([], Buffer.alloc(1224))); + shouldThrow(() => mps.ed25519_dkg_round1_process(["message"], Buffer.alloc(1224))); + shouldThrow(() => + mps.ed25519_dkg_round1_process([Buffer.alloc(0), Buffer.alloc(1224)]), + ); + }); + + it("does not panic on bad state", function () { + shouldThrow(() => + mps.ed25519_dkg_round1_process([Buffer.alloc(65), Buffer.alloc(65)], "state"), + ); + shouldThrow(() => + mps.ed25519_dkg_round1_process([Buffer.alloc(65), Buffer.alloc(65)], Buffer.alloc(0)), + ); + }); + }); + + describe("round2_process", function () { + it("does not panic on bad messages", function () { + shouldThrow(() => mps.ed25519_dkg_round2_process("messages", Buffer.alloc(1224))); + shouldThrow(() => mps.ed25519_dkg_round2_process([], Buffer.alloc(1224))); + shouldThrow(() => mps.ed25519_dkg_round2_process(["message"], Buffer.alloc(1224))); + shouldThrow(() => + mps.ed25519_dkg_round2_process([Buffer.alloc(0), Buffer.alloc(1224)]), + ); + }); + + it("does not panic on bad state", function () { + shouldThrow(() => + mps.ed25519_dkg_round2_process([Buffer.alloc(65), Buffer.alloc(65)], "state"), + ); + shouldThrow(() => + mps.ed25519_dkg_round2_process([Buffer.alloc(65), Buffer.alloc(65)], Buffer.alloc(0)), + ); + }); + }); + }); }); - it("fails to perform round 1 with invalid state prefix", function () { - const statePrefix = Buffer.from("mps-ed25519-dkg-round1-state$"); - for (let i = 0; i < results1.length; i++) { - shouldThrow(() => - mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => results1[i].msg), - Buffer.from(results1[i].state).slice(statePrefix.length), + describe("dsg", function () { + const otherIndex = [1, 0]; + let shares: Array; + + before("performs dkg", function () { + const results1 = [0, 1, 2].map((i) => + mps.ed25519_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((i) => keypairs[i].publicKey), + crypto.randomBytes(32), ), ); - shouldThrow(() => + const results2 = [0, 1, 2].map((i) => mps.ed25519_dkg_round1_process( otherIndices[i].map((i) => results1[i].msg), - Buffer.concat([ - Buffer.from("mps-ed25519-dkg-round2-state$"), - Buffer.from(results1[i].state).slice(statePrefix.length), - ]), + results1[i].state, ), ); - } - }); - - let results2: Array; + shares = [0, 1, 2].map((i) => + mps.ed25519_dkg_round2_process( + otherIndices[i].map((i) => results2[i].msg), + results2[i].state, + ), + ); + }); - before("performs round 1", function () { - results2 = [0, 1, 2].map((i) => - mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => results1[i].msg), - results1[i].state, - ), + const message = Buffer.from( + "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", ); - }); - it("performs round 2", function () { - const results3 = [0, 1, 2].map((i) => - mps.ed25519_dkg_round2_process( - otherIndices[i].map((i) => results2[i].msg), - results2[i].state, - ), - ); - for (let i = 0; i < 2; i++) { - assert.ok(results3[i].pk.every((value, index) => value === results3[2].pk[index])); - assert.ok( - results3[i].chaincode.every((value, index) => value === results3[2].chaincode[index]), - ); - } - }); + it("performs round 0", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round1-message$"); + const statePrefix = Buffer.from("mps-ed25519-dsg-round1-state$"); + for (const i of [0, 2]) { + const result = mps.ed25519_dsg_round0_process(shares[i].share, "m", message); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); - describe("input handling", function () { - describe("round0_process", function () { - it("does not panic on bad party size", function () { - shouldThrow(() => - mps.ed25519_dkg_round0_process( - "255", - Buffer.alloc(32), - [Buffer.alloc(32), Buffer.alloc(32)], - crypto.randomBytes(32), - ), + let results1: Array; + + before("performs round 0", function () { + results1 = [0, 2].map((i) => mps.ed25519_dsg_round0_process(shares[i].share, "m", message)); + }); + + it("performs round 1", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round2-message$"); + const statePrefix = Buffer.from("mps-ed25519-dsg-round2-state$"); + for (let i = 0; i < results1.length; i++) { + const result = mps.ed25519_dsg_round1_process( + results1[otherIndex[i]].msg, + results1[i].state, ); - }); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); - it("does not panic on bad encryption key", function () { + it("fails to perform round 1 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round1-message$"); + for (let i = 0; i < results1.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - "encryption key", - [Buffer.alloc(32), Buffer.alloc(32)], - crypto.randomBytes(32), + mps.ed25519_dsg_round1_process( + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + results1[i].state, ), ); shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - [Buffer.alloc(32), Buffer.alloc(32)], - crypto.randomBytes(32), + mps.ed25519_dsg_round1_process( + Buffer.concat([ + Buffer.from("mps-ed25519-dsg-round2-message$"), + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results1[i].state, ), ); - }); + } + }); - it("does not panic on bad decryption keys", function () { + it("fails to perform round 1 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-ed25519-dsg-round1-state$"); + for (let i = 0; i < results1.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - "decryption keys", - crypto.randomBytes(32), + mps.ed25519_dsg_round1_process( + results1[otherIndex[i]].msg, + Buffer.from(results1[i].state).slice(statePrefix.length), ), ); shouldThrow(() => - mps.ed25519_dkg_round0_process(0, Buffer.alloc(0), [], crypto.randomBytes(32)), - ); - shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - ["decryption key"], - crypto.randomBytes(32), + mps.ed25519_dsg_round1_process( + results1[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-ed25519-dsg-round2-state$"), results1[i].state]), ), ); + } + }); + + let results2: Array; + + before("performs round 1", function () { + results2 = [0, 1].map((i) => + mps.ed25519_dsg_round1_process(results1[otherIndex[i]].msg, results1[i].state), + ); + }); + + it("performs round 2", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round3-message$"); + const statePrefix = Buffer.from("mps-ed25519-dsg-round3-state$"); + for (let i = 0; i < results2.length; i++) { + const result = mps.ed25519_dsg_round2_process( + results2[otherIndex[i]].msg, + results2[i].state, + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); + + it("fails to perform round 2 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round2-message$"); + for (let i = 0; i < results2.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - [Buffer.alloc(0)], - crypto.randomBytes(32), + mps.ed25519_dsg_round2_process( + Buffer.from(results2[otherIndex[i]].msg).slice(messagePrefix.length), + results2[i].state, ), ); shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - [Buffer.alloc(32), Buffer.alloc(0)], - crypto.randomBytes(32), + mps.ed25519_dsg_round2_process( + Buffer.concat([ + Buffer.from("mps-ed25519-dsg-round3-message$"), + Buffer.from(results2[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results2[i].state, ), ); - }); + } + }); - it("does not panic on bad seed", function () { + it("fails to perform round 2 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-ed25519-dsg-round2-state$"); + for (let i = 0; i < results2.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - [Buffer.alloc(32), Buffer.alloc(32)], - "seed", + mps.ed25519_dsg_round2_process( + results2[otherIndex[i]].msg, + Buffer.from(results2[i].state).slice(statePrefix.length), ), ); shouldThrow(() => - mps.ed25519_dkg_round0_process( - 0, - Buffer.alloc(0), - [Buffer.alloc(32), Buffer.alloc(32)], - Buffer.alloc(0), + mps.ed25519_dsg_round2_process( + results2[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-ed25519-dsg-round3-state$"), results2[i].state]), ), ); - }); + } }); - describe("round1_process", function () { - it("does not panic on bad messages", function () { - shouldThrow(() => mps.ed25519_dkg_round1_process("messages", Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round1_process([], Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round1_process(["message"], Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round1_process([Buffer.alloc(0), Buffer.alloc(1224)])); - }); + let results3: Array; - it("does not panic on bad state", function () { + before("performs round 2", function () { + results3 = [0, 1].map((i) => + mps.ed25519_dsg_round2_process(results2[otherIndex[i]].msg, results2[i].state), + ); + }); + + it("performs round 3", function () { + const signatures = [0, 1].map((i) => + mps.ed25519_dsg_round3_process(results3[otherIndex[i]].msg, results3[i].state), + ); + assert(sodium.crypto_sign_verify_detached(signatures[0], message, shares[0].pk)); + assert(sodium.crypto_sign_verify_detached(signatures[1], message, shares[2].pk)); + }); + + it("fails to perform round 3 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-ed25519-dsg-round3-message$"); + for (let i = 0; i < results3.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round1_process([Buffer.alloc(65), Buffer.alloc(65)], "state"), + mps.ed25519_dsg_round3_process( + Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), + results3[i].state, + ), ); shouldThrow(() => - mps.ed25519_dkg_round1_process([Buffer.alloc(65), Buffer.alloc(65)], Buffer.alloc(0)), + mps.ed25519_dsg_round3_process( + Buffer.concat([ + Buffer.from("mps-ed25519-dsg-round4-message$"), + Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results3[i].state, + ), ); - }); + } }); - describe("round2_process", function () { - it("does not panic on bad messages", function () { - shouldThrow(() => mps.ed25519_dkg_round2_process("messages", Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round2_process([], Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round2_process(["message"], Buffer.alloc(1224))); - shouldThrow(() => mps.ed25519_dkg_round2_process([Buffer.alloc(0), Buffer.alloc(1224)])); - }); - - it("does not panic on bad state", function () { + it("fails to perform round 3 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-ed25519-dsg-round3-state$"); + for (let i = 0; i < results3.length; i++) { shouldThrow(() => - mps.ed25519_dkg_round2_process([Buffer.alloc(65), Buffer.alloc(65)], "state"), + mps.ed25519_dsg_round3_process( + results3[otherIndex[i]].msg, + Buffer.from(results3[i].state).slice(statePrefix.length), + ), ); shouldThrow(() => - mps.ed25519_dkg_round2_process([Buffer.alloc(65), Buffer.alloc(65)], Buffer.alloc(0)), + mps.ed25519_dsg_round3_process( + results3[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-ed25519-dsg-round4-state$"), results3[i].state]), + ), ); - }); + } }); }); }); - describe("dsg", function () { - const otherIndex = [1, 0]; - let shares: Array; - - before("performs dkg", function () { - const results1 = [0, 1, 2].map((i) => - mps.ed25519_dkg_round0_process( - i, - keypairs[i].privateKey, - otherIndices[i].map((i) => keypairs[i].publicKey), - crypto.randomBytes(32), - ), - ); - const results2 = [0, 1, 2].map((i) => - mps.ed25519_dkg_round1_process( - otherIndices[i].map((i) => results1[i].msg), - results1[i].state, - ), - ); - shares = [0, 1, 2].map((i) => - mps.ed25519_dkg_round2_process( - otherIndices[i].map((i) => results2[i].msg), - results2[i].state, - ), - ); - }); - - const message = Buffer.from( - "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", - ); - - it("performs round 0", function () { - const messagePrefix = Buffer.from("mps-ed25519-dsg-round1-message$"); - const statePrefix = Buffer.from("mps-ed25519-dsg-round1-state$"); - for (const i of [0, 2]) { - const result = mps.ed25519_dsg_round0_process(shares[i].share, "m", message); - assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); - assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); - } - }); - - let results1: Array; + describe("redpallas", function () { + describe("dkg", function () { + it("performs round 0", function () { + const messagePrefix = Buffer.from("mps-redpallas-dkg-round1-message$"); + const statePrefix = Buffer.from("mps-redpallas-dkg-round1-state$"); + for (let i = 0; i < keypairs.length; i++) { + const result = mps.redpallas_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((i) => keypairs[i].publicKey), + crypto.randomBytes(32), + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); - before("performs round 0", function () { - results1 = [0, 2].map((i) => mps.ed25519_dsg_round0_process(shares[i].share, "m", message)); - }); + let results1: Array; - it("performs round 1", function () { - const messagePrefix = Buffer.from("mps-ed25519-dsg-round2-message$"); - const statePrefix = Buffer.from("mps-ed25519-dsg-round2-state$"); - for (let i = 0; i < results1.length; i++) { - const result = mps.ed25519_dsg_round1_process( - results1[otherIndex[i]].msg, - results1[i].state, + before("performs round 0", function () { + results1 = [0, 1, 2].map((i) => + mps.redpallas_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((i) => keypairs[i].publicKey), + crypto.randomBytes(32), + ), ); - assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); - assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); - } - }); + }); - it("fails to perform round 1 with invalid message prefix", function () { - const messagePrefix = Buffer.from("mps-ed25519-dsg-round1-message$"); - for (let i = 0; i < results1.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round1_process( - Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + it("performs round 1", function () { + const messagePrefix = Buffer.from("mps-redpallas-dkg-round2-message$"); + const statePrefix = Buffer.from("mps-redpallas-dkg-round2-state$"); + for (let i = 0; i < results1.length; i++) { + const result = mps.redpallas_dkg_round1_process( + otherIndices[i].map((i) => results1[i].msg), results1[i].state, - ), - ); - shouldThrow(() => - mps.ed25519_dsg_round1_process( - Buffer.concat([ - Buffer.from("mps-ed25519-dsg-round2-message$"), - Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), - ]), + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); + + it("fails to perform round 1 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-redpallas-dkg-round1-message$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dkg_round1_process( + otherIndices[i].map((i) => Buffer.from(results1[i].msg).slice(messagePrefix.length)), + results1[i].state, + ), + ); + shouldThrow(() => + mps.redpallas_dkg_round1_process( + otherIndices[i].map((i) => + Buffer.concat([ + Buffer.from("msg-redpallas-dkg-round2-message$"), + Buffer.from(results1[i].msg).slice(messagePrefix.length), + ]), + ), + results1[i].state, + ), + ); + } + }); + + it("fails to perform round 1 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-redpallas-dkg-round1-state$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dkg_round1_process( + otherIndices[i].map((i) => results1[i].msg), + Buffer.from(results1[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.redpallas_dkg_round1_process( + results1[i].msg, + Buffer.concat([ + "mps-redpallas-dkg-round2-state$", + Buffer.from(results1[i].state).slice(statePrefix.length), + ]), + ), + ); + } + }); + + let results2: Array; + + before("performs round 1", function () { + results2 = [0, 1, 2].map((i) => + mps.redpallas_dkg_round1_process( + otherIndices[i].map((i) => results1[i].msg), results1[i].state, ), ); - } - }); + }); - it("fails to perform round 1 with invalid state prefix", function () { - const statePrefix = Buffer.from("mps-ed25519-dsg-round1-state$"); - for (let i = 0; i < results1.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round1_process( - results1[otherIndex[i]].msg, - Buffer.from(results1[i].state).slice(statePrefix.length), - ), - ); - shouldThrow(() => - mps.ed25519_dsg_round1_process( - results1[otherIndex[i]].msg, - Buffer.concat([Buffer.from("mps-ed25519-dsg-round2-state$"), results1[i].state]), - ), - ); - } - }); + it("performs round 2", function () { + const messagePrefix = Buffer.from("mps-redpallas-dkg-derivation-message$"); + const statePrefix = Buffer.from("mps-redpallas-dkg-derivation-state$"); + for (let i = 0; i < results2.length; i++) { + const result = mps.redpallas_dkg_round2_process( + otherIndices[i].map((i) => results2[i].msg), + results2[i].state, + crypto.randomBytes(32), + ); + if (result.drv.length) { + assert(Buffer.from(result.drv).slice(0, messagePrefix.length).equals(messagePrefix)); + } + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + assert.ok(results3[i].pk.every((value, index) => value === results3[2].pk[index])); + } + }); - let results2: Array; + it("fails to perform round 2 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-redpallas-dkg-round2-message$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dkg_round2_process( + otherIndices[i].map((i) => Buffer.from(results2[i].msg).slice(messagePrefix.length)), + results2[i].state, + ), + ); + shouldThrow(() => + mps.redpallas_dkg_round2_process( + otherIndices[i].map((i) => + Buffer.concat([ + Buffer.from("msg-redpallas-dkg-round3-message$"), + Buffer.from(results1[i].msg).slice(messagePrefix.length), + ]), + ), + results1[i].state, + ), + ); + } + }); - before("performs round 1", function () { - results2 = [0, 1].map((i) => - mps.ed25519_dsg_round1_process(results1[otherIndex[i]].msg, results1[i].state), - ); - }); + it("fails to perform round 2 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-redpallas-dkg-round2-state$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dkg_round2_process( + otherIndices[i].map((i) => results1[i].msg), + Buffer.from(results1[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.redpallas_dkg_round2_process( + results1[i].msg, + Buffer.concat([ + "mps-redpallas-dkg-round2-state$", + Buffer.from(results1[i].state).slice(statePrefix.length), + ]), + ), + ); + } + }); - it("performs round 2", function () { - for (let i = 0; i < results2.length; i++) { - mps.ed25519_dsg_round2_process(results2[otherIndex[i]].msg, results2[i].state); - } - }); + let results3: Array; - it("fails to perform round 2 with invalid message prefix", function () { - const messagePrefix = Buffer.from("mps-ed25519-dsg-round2-message$"); - for (let i = 0; i < results2.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round2_process( - Buffer.from(results2[otherIndex[i]].msg).slice(messagePrefix.length), + before("performs round 2", function () { + results3 = [0, 1, 2].map((i) => + mps.redpallas_dkg_round2_process( + otherIndices[i].map((i) => results2[i].msg), results2[i].state, + crypto.randomBytes(32), ), ); - shouldThrow(() => - mps.ed25519_dsg_round2_process( - Buffer.concat([ - Buffer.from("mps-ed25519-dsg-round3-message$"), - Buffer.from(results2[otherIndex[i]].msg).slice(messagePrefix.length), - ]), - results2[i].state, - ), + }); + + it("runs derivation to completion", function () { + this.timeout(30000); + const messagePrefix = Buffer.from("mps-redpallas-dkg-derivation-message$"); + const statePrefix = Buffer.from("mps-redpallas-dkg-derivation-state$"); + let messages: Array = results3.map((d) => d.drv); + const states = results3.map((d) => d.state); + const derivedKeys: Map = new Map(); + for (let round = 0; round < 500 && Array.from(derivedKeys.keys()).length < 3; round++) { + for (let party = 0; party < 3; party++) { + const result = mps.redpallas_derivation_process(messages, states[party]); + if (result.messages.length) { + assert( + Buffer.from(result.messages).slice(0, messagePrefix.length).equals(messagePrefix), + ); + } + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + messages = [result.messages]; + states[party] = result.state; + if (result.done) { + derivedKeys.set(party, result); + } + } + } + assert.ok( + Array.from(derivedKeys.keys()).length == 3, + "derivation did not complete within 500 rounds", ); - } + for (let i = 0; i < 3; i++) { + const k = derivedKeys.get(i); + assert.equal(k.ask.length, 32); + assert.equal(k.nk.length, 32); + assert.equal(k.rivk.length, 32); + assert.equal(k.internal_ivk.length, 64); + assert.equal(k.external_ivk.length, 64); + } + const hsmKeys = derivedKeys.get(2); + for (let i = 0; i < 2; i++) { + const k = derivedKeys.get(i); + assert.deepStrictEqual(k.ask, hsmKeys.ask); + assert.deepStrictEqual(k.nk, hsmKeys.nk); + assert.deepStrictEqual(k.rivk, hsmKeys.rivk); + assert.deepStrictEqual(k.internal_ivk, hsmKeys.internal_ivk); + assert.deepStrictEqual(k.external_ivk, hsmKeys.external_ivk); + } + for (let i = 0; i < 3; i++) { + const k = derivedKeys.get(i); + assert(!k.ask.every((b) => b === 0)); + assert(!k.nk.every((b) => b === 0)); + assert(!k.rivk.every((b) => b === 0)); + assert(!k.internal_ivk.every((b) => b === 0)); + assert(!k.external_ivk.every((b) => b === 0)); + } + for (let i = 0; i < 3; i++) { + const k = derivedKeys.get(i); + const ivks = mps.redpallas_fvk_to_ivks(k.ask, k.nk, k.rivk); + assert.deepStrictEqual(ivks.internal_ivk, k.internal_ivk); + assert.deepStrictEqual(ivks.external_ivk, k.external_ivk); + } + }); }); - it("fails to perform round 2 with invalid state prefix", function () { - const statePrefix = Buffer.from("mps-ed25519-dsg-round2-state$"); - for (let i = 0; i < results2.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round2_process( - results2[otherIndex[i]].msg, - Buffer.from(results2[i].state).slice(statePrefix.length), + describe("dsg", function () { + const otherIndex = [1, 0]; + let shares: Array; + + before("performs dkg", function () { + const results1 = [0, 1, 2].map((i) => + mps.redpallas_dkg_round0_process( + i, + keypairs[i].privateKey, + otherIndices[i].map((j) => keypairs[j].publicKey), + crypto.randomBytes(32), ), ); - shouldThrow(() => - mps.ed25519_dsg_round2_process( - results2[otherIndex[i]].msg, - Buffer.concat([Buffer.from("mps-ed25519-dsg-round3-state$"), results2[i].state]), + const results2 = [0, 1, 2].map((i) => + mps.redpallas_dkg_round1_process( + otherIndices[i].map((j) => results1[j].msg), + results1[i].state, ), ); - } - }); - - let results3: Array; + shares = [0, 1, 2].map((i) => + mps.redpallas_dkg_round2_process( + otherIndices[i].map((j) => results2[j].msg), + results2[i].state, + crypto.randomBytes(32), + ), + ); + }); - before("performs round 2", function () { - results3 = [0, 1].map((i) => - mps.ed25519_dsg_round2_process(results2[otherIndex[i]].msg, results2[i].state), + const message = Buffer.from( + "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", ); - }); - it("performs round 3", function () { - const signatures = [0, 1].map((i) => - mps.ed25519_dsg_round3_process(results3[otherIndex[i]].msg, results3[i].state), - ); - assert(sodium.crypto_sign_verify_detached(signatures[0], message, shares[0].pk)); - assert(sodium.crypto_sign_verify_detached(signatures[1], message, shares[2].pk)); - }); + it("performs round 0", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round1-message$"); + const statePrefix = Buffer.from("mps-redpallas-dsg-round1-state$"); + for (const i of [0, 2]) { + const result = mps.redpallas_dsg_round0_process(shares[i].share, message); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); - it("fails to perform round 3 with invalid message prefix", function () { - const messagePrefix = Buffer.from("mps-ed25519-dsg-round3-message$"); - for (let i = 0; i < results3.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round3_process( - Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), - results3[i].state, - ), - ); - shouldThrow(() => - mps.ed25519_dsg_round3_process( - Buffer.concat([ - Buffer.from("mps-ed25519-dsg-round4-message$"), - Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), - ]), - results3[i].state, - ), + let results1: Array; + + before("performs round 0", function () { + results1 = [0, 2].map((i) => mps.redpallas_dsg_round0_process(shares[i].share, message)); + }); + + it("performs round 1", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round2-message$"); + const statePrefix = Buffer.from("mps-redpallas-dsg-round2-state$"); + for (let i = 0; i < results1.length; i++) { + const result = mps.redpallas_dsg_round1_process( + results1[otherIndex[i]].msg, + results1[i].state, + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); + + it("fails to perform round 1 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round1-message$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round1_process( + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + results1[i].state, + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round1_process( + Buffer.concat([ + Buffer.from("mps-redpallas-dsg-round2-message$"), + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results1[i].state, + ), + ); + } + }); + + it("fails to perform round 1 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-redpallas-dsg-round1-state$"); + for (let i = 0; i < results1.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round1_process( + results1[otherIndex[i]].msg, + Buffer.from(results1[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round1_process( + results1[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-redpallas-dsg-round2-state$"), results1[i].state]), + ), + ); + } + }); + + let results2: Array; + + before("performs round 1", function () { + results2 = [0, 1].map((i) => + mps.redpallas_dsg_round1_process(results1[otherIndex[i]].msg, results1[i].state), ); - } - }); + }); - it("fails to perform round 3 with invalid state prefix", function () { - const statePrefix = Buffer.from("mps-ed25519-dsg-round3-state$"); - for (let i = 0; i < results3.length; i++) { - shouldThrow(() => - mps.ed25519_dsg_round3_process( - results3[otherIndex[i]].msg, - Buffer.from(results3[i].state).slice(statePrefix.length), - ), + it("performs round 2", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round3-message$"); + const statePrefix = Buffer.from("mps-redpallas-dsg-round3-state$"); + for (let i = 0; i < results2.length; i++) { + const result = mps.redpallas_dsg_round2_process( + results2[otherIndex[i]].msg, + results2[i].state, + ); + assert(Buffer.from(result.msg).slice(0, messagePrefix.length).equals(messagePrefix)); + assert(Buffer.from(result.state).slice(0, statePrefix.length).equals(statePrefix)); + } + }); + + it("fails to perform round 2 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round2-message$"); + for (let i = 0; i < results2.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round2_process( + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + results1[i].state, + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round2_process( + Buffer.concat([ + Buffer.from("mps-redpallas-dsg-round3-message$"), + Buffer.from(results1[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results1[i].state, + ), + ); + } + }); + + it("fails to perform round 2 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-redpallas-dsg-round2-state$"); + for (let i = 0; i < results2.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round2_process( + results1[otherIndex[i]].msg, + Buffer.from(results1[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round2_process( + results1[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-redpallas-dsg-round3-state$"), results1[i].state]), + ), + ); + } + }); + + let results3: Array; + + before("performs round 2", function () { + results3 = [0, 1].map((i) => + mps.redpallas_dsg_round2_process(results2[otherIndex[i]].msg, results2[i].state), ); - shouldThrow(() => - mps.ed25519_dsg_round3_process( - results3[otherIndex[i]].msg, - Buffer.concat([Buffer.from("mps-ed25519-dsg-round4-state$"), results3[i].state]), - ), + }); + + it("performs round 3", function () { + const results4 = [0, 1].map((i) => + mps.redpallas_dsg_round3_process(results3[otherIndex[i]].msg, results3[i].state), ); - } + for (let i = 0; i < 2; i++) { + assert(mps.redpallas_verify(results4[i].rk, results4[i].signature, message)); + } + // Both parties produce the same alpha and rk + assert.deepStrictEqual(results4[0].alpha, results4[1].alpha, "alpha values differ"); + assert.deepStrictEqual(results4[0].rk, results4[1].rk, "rk values differ"); + // Alpha is a random field element — must not be zero + assert(!results4[0].alpha.every((b) => b === 0), "alpha is zero"); + }); + + it("fails to perform round 3 with invalid message prefix", function () { + const messagePrefix = Buffer.from("mps-redpallas-dsg-round3-message$"); + for (let i = 0; i < results3.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round3_process( + Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), + results1[i].state, + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round3_process( + Buffer.concat([ + Buffer.from("mps-redpallas-dsg-round4-message$"), + Buffer.from(results3[otherIndex[i]].msg).slice(messagePrefix.length), + ]), + results1[i].state, + ), + ); + } + }); + + it("fails to perform round 3 with invalid state prefix", function () { + const statePrefix = Buffer.from("mps-redpallas-dsg-round3-state$"); + for (let i = 0; i < results3.length; i++) { + shouldThrow(() => + mps.redpallas_dsg_round3_process( + results3[otherIndex[i]].msg, + Buffer.from(results3[i].state).slice(statePrefix.length), + ), + ); + shouldThrow(() => + mps.redpallas_dsg_round3_process( + results3[otherIndex[i]].msg, + Buffer.concat([Buffer.from("mps-redpallas-dsg-round3-state$"), results3[i].state]), + ), + ); + } + }); }); }); });