From 64d59d93c1621b9758e658b3cc87b7ef26c10304 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sat, 22 Feb 2025 07:19:32 +0100 Subject: [PATCH 01/29] First draft --- Cargo.lock | 338 +++++++-- Cargo.toml | 1 + substrate/primitives/core/Cargo.toml | 5 +- .../primitives/core/src/bandersnatch2.rs | 687 ++++++++++++++++++ substrate/primitives/core/src/lib.rs | 1 + 5 files changed, 986 insertions(+), 46 deletions(-) create mode 100644 substrate/primitives/core/src/bandersnatch2.rs diff --git a/Cargo.lock b/Cargo.lock index cf75334f495b0..687649f4618f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,7 +453,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -465,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-models-ext", "ark-std 0.4.0", ] @@ -476,20 +476,32 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", ] +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + [[package]] name = "ark-bls12-381-ext" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" dependencies = [ - "ark-bls12-381", - "ark-ec", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-models-ext", "ark-serialize 0.4.2", @@ -503,7 +515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -515,7 +527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" dependencies = [ "ark-bw6-761", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-models-ext", "ark-std 0.4.0", @@ -528,7 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", @@ -539,6 +551,46 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ec-vrfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbce7dad39bc73d5ca4d5815eea91e54e1e940d86d5739ab8d380b43f3e1910b" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ed-on-bls12-381-bandersnatch 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_chacha 0.3.1", + "sha2 0.10.8", + "w3f-ring-proof", + "zeroize", +] + [[package]] name = "ark-ed-on-bls12-377" version = "0.4.0" @@ -546,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -557,7 +609,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ff 0.4.2", "ark-models-ext", @@ -570,20 +622,32 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ - "ark-bls12-381", - "ark-ec", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1786b2e3832f6f0f7c8d62d5d5a282f6952a1ab99981c54cd52b6ac1d8f02df5" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + [[package]] name = "ark-ed-on-bls12-381-bandersnatch-ext" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" dependencies = [ - "ark-ec", - "ark-ed-on-bls12-381-bandersnatch", + "ark-ec 0.4.2", + "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ff 0.4.2", "ark-models-ext", "ark-std 0.4.0", @@ -627,6 +691,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -647,6 +731,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote 1.0.38", + "syn 2.0.98", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -672,13 +766,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.98", +] + [[package]] name = "ark-models-ext" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -698,13 +805,28 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", +] + [[package]] name = "ark-scale" version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -718,7 +840,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -731,11 +853,11 @@ name = "ark-secret-scalar" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", - "ark-transcript", + "ark-transcript 0.0.2", "digest 0.10.7", "getrandom_or_panic", "zeroize", @@ -757,12 +879,25 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive", + "ark-serialize-derive 0.4.2", "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-serialize-derive" version = "0.4.2" @@ -774,6 +909,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.98", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -795,6 +941,16 @@ dependencies = [ "rayon", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "ark-transcript" version = "0.0.2" @@ -808,6 +964,20 @@ dependencies = [ "sha3 0.10.8", ] +[[package]] +name = "ark-transcript" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c1c928edb9d8ff24cb5dcb7651d3a98494fff3099eee95c2404cd813a9139f" +dependencies = [ + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3 0.10.8", +] + [[package]] name = "array-bytes" version = "6.2.2" @@ -1717,9 +1887,9 @@ name = "bandersnatch_vrfs" version = "0.0.4" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-bls12-381", - "ark-ec", - "ark-ed-on-bls12-381-bandersnatch", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", + "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -3830,9 +4000,9 @@ name = "common" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "fflonk", @@ -6242,13 +6412,13 @@ name = "dleq_vrf" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-scale 0.0.12", "ark-secret-scalar", "ark-serialize 0.4.2", "ark-std 0.4.0", - "ark-transcript", + "ark-transcript 0.0.2", "arrayvec 0.7.4", "zeroize", ] @@ -6430,6 +6600,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.98", +] + [[package]] name = "either" version = "1.13.0" @@ -6520,6 +6702,26 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.98", +] + [[package]] name = "enumflags2" version = "0.7.11" @@ -6938,9 +7140,9 @@ name = "fflonk" version = "0.1.0" source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "merlin", @@ -8490,6 +8692,7 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", "foldhash", "serde", ] @@ -8792,7 +8995,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -21763,9 +21966,9 @@ name = "ring" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "arrayvec 0.7.4", @@ -26549,6 +26752,7 @@ dependencies = [ name = "sp-core" version = "28.0.0" dependencies = [ + "ark-ec-vrfs", "array-bytes", "bandersnatch_vrfs", "bitflags 1.3.2", @@ -26821,14 +27025,14 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", - "ark-bls12-381", + "ark-bls12-381 0.4.0", "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", - "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ed-on-bls12-381-bandersnatch-ext", "ark-scale 0.0.11", "sp-runtime-interface 17.0.0", @@ -26841,14 +27045,14 @@ version = "0.10.0" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", - "ark-bls12-381", + "ark-bls12-381 0.4.0", "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", - "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ed-on-bls12-381-bandersnatch-ext", "ark-scale 0.0.12", "sp-runtime-interface 24.0.0", @@ -26862,14 +27066,14 @@ checksum = "2acb24f8a607a48a87f0ee4c090fc5d577eee49ff39ced6a3c491e06eca03c37" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", - "ark-bls12-381", + "ark-bls12-381 0.4.0", "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", - "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ed-on-bls12-381-bandersnatch-ext", "ark-scale 0.0.12", "sp-runtime-interface 28.0.0", @@ -31161,11 +31365,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" dependencies = [ "ark-bls12-377", - "ark-bls12-381", - "ark-ec", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", - "ark-serialize-derive", + "ark-serialize-derive 0.4.2", "arrayref", "constcat", "digest 0.10.7", @@ -31178,6 +31382,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "w3f-pcs" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe7a8d5c914b69392ab3b267f679a2e546fe29afaddce47981772ac71bd02e1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "merlin", +] + +[[package]] +name = "w3f-plonk-common" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aca389e494fe08c5c108b512e2328309036ee1c0bc7bdfdb743fef54d448c8c" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "getrandom_or_panic", + "rand_core 0.6.4", + "w3f-pcs", +] + +[[package]] +name = "w3f-ring-proof" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a639379402ad51504575dbd258740383291ac8147d3b15859bdf1ea48c677de" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "ark-transcript 0.0.3", + "w3f-pcs", + "w3f-plonk-common", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index b73e87d9bac26..804db6dacbe41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -615,6 +615,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } +ark-ec-vrfs = { version = "0.1.0", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 0ea885abd22d7..1342c4abff2a6 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -73,6 +73,7 @@ w3f-bls = { optional = true, workspace = true } bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = [ "substrate-curves", ], optional = true } +ark-ec-vrfs = { optional = true, workspace = true, features = ["bandersnatch", "ring"] } [dev-dependencies] criterion = { workspace = true, default-features = true } @@ -87,7 +88,7 @@ harness = false bench = false [features] -default = ["std"] +default = ["std", "bandersnatch-experimental"] std = [ "bandersnatch_vrfs?/std", @@ -165,4 +166,4 @@ bls-experimental = ["w3f-bls"] # This feature adds Bandersnatch crypto primitives. # It should not be used in production since the implementation and interface may still # be subject to significant changes. -bandersnatch-experimental = ["bandersnatch_vrfs"] +bandersnatch-experimental = ["bandersnatch_vrfs", "ark-ec-vrfs"] diff --git a/substrate/primitives/core/src/bandersnatch2.rs b/substrate/primitives/core/src/bandersnatch2.rs new file mode 100644 index 0000000000000..6fd9ef32f98ce --- /dev/null +++ b/substrate/primitives/core/src/bandersnatch2.rs @@ -0,0 +1,687 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch), +//! an elliptic curve built over BLS12-381 scalar field. +//! +//! The primitive can operate both as a regular VRF or as an anonymized Ring VRF. + +#[cfg(feature = "full_crypto")] +use crate::crypto::VrfSecret; +use crate::crypto::{ + ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, + PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, +}; +use ark_ec_vrfs::{ + prelude::ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, + ring::RingSuite, + suites::bandersnatch::edwards as bandersnatch, +}; +use bandersnatch::Secret; +use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; +use scale_info::TypeInfo; + +use alloc::vec::Vec; + +/// Identifier used to match public keys against bandersnatch-vrf keys. +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); + +/// Context used to produce a plain signature without any VRF input/output. +pub const SIGNING_CTX: &[u8] = b"BandersnatchSigningContext"; + +/// The byte length of secret key seed. +pub const SEED_SERIALIZED_SIZE: usize = 32; + +/// The byte length of serialized public key. +pub const PUBLIC_SERIALIZED_SIZE: usize = 32; + +/// The byte length of serialized signature. +pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; + +/// The byte length of serialized pre-output. +pub const PREOUT_SERIALIZED_SIZE: usize = 32; + +#[doc(hidden)] +pub struct BandersnatchTag; + +/// Bandersnatch public key. +pub type Public = PublicBytes; + +impl CryptoType for Public { + type Pair = Pair; +} + +/// Bandersnatch signature. +/// +/// The signature is created via [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript +/// `label`. +pub type Signature = SignatureBytes; + +impl CryptoType for Signature { + type Pair = Pair; +} + +/// The raw secret seed, which can be used to reconstruct the secret [`Pair`]. +type Seed = [u8; SEED_SERIALIZED_SIZE]; + +/// Bandersnatch secret key. +#[derive(Clone)] +pub struct Pair { + secret: Secret, + seed: Seed, +} + +impl Pair { + /// Get the key seed. + pub fn seed(&self) -> Seed { + self.seed + } +} + +impl TraitPair for Pair { + type Seed = Seed; + type Public = Public; + type Signature = Signature; + + /// Make a new key pair from secret seed material. + /// + /// The slice must be 32 bytes long or it will return an error. + fn from_seed_slice(seed_slice: &[u8]) -> Result { + if seed_slice.len() != SEED_SERIALIZED_SIZE { + return Err(SecretStringError::InvalidSeedLength) + } + let mut seed = [0; SEED_SERIALIZED_SIZE]; + seed.copy_from_slice(seed_slice); + let secret = Secret::from_seed(&seed); + Ok(Pair { secret, seed }) + } + + /// Derive a child key from a series of given (hard) junctions. + /// + /// Soft junctions are not supported. + fn derive>( + &self, + path: Iter, + _seed: Option, + ) -> Result<(Pair, Option), DeriveError> { + let derive_hard = |seed, cc| -> Seed { + ("bandersnatch-vrf-HDKD", seed, cc).using_encoded(sp_crypto_hashing::blake2_256) + }; + + let mut seed = self.seed(); + for p in path { + if let DeriveJunction::Hard(cc) = p { + seed = derive_hard(seed, cc); + } else { + return Err(DeriveError::SoftKeyInPath) + } + } + Ok((Self::from_seed(&seed), Some(seed))) + } + + fn public(&self) -> Public { + let public = self.secret.public(); + let mut raw = [0; PUBLIC_SERIALIZED_SIZE]; + public + .serialize_compressed(raw.as_mut_slice()) + .expect("serialization length is constant and checked by test; qed"); + Public::unchecked_from(raw) + } + + /// Sign a message. + /// + /// In practice this produce a Schnorr signature. + #[cfg(feature = "full_crypto")] + fn sign(&self, data: &[u8]) -> Signature { + use ark_ec_vrfs::Suite; + use bandersnatch::BandersnatchSha512Ell2; + let input = bandersnatch::Input::new(data).unwrap(); + let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); + let c = BandersnatchSha512Ell2::challenge(&[&self.secret.public.0, &input.0], data); + let s = k + c * self.secret.scalar; + let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; + bandersnatch::IetfProof { c, s } + .serialize_compressed(&mut raw_signature.as_mut_slice()) + .unwrap(); + Signature::from_raw(raw_signature) + } + + fn verify>(signature: &Signature, data: M, public: &Public) -> bool { + let data = vrf::VrfSignData::new(SIGNING_CTX, data.as_ref()); + let dummy = ark_ec_vrfs::Output(bandersnatch::BandersnatchSha512Ell2::PADDING); + let signature = + vrf::VrfSignature { proof: *signature, pre_output: vrf::VrfPreOutput(dummy) }; + public.vrf_verify(&data, &signature) + } + + /// Return a vector filled with the seed (32 bytes). + fn to_raw_vec(&self) -> Vec { + self.seed().to_vec() + } +} + +impl CryptoType for Pair { + type Pair = Pair; +} + +/// Bandersnatch VRF types and operations. +pub mod vrf { + use super::*; + use crate::crypto::VrfCrypto; + use ark_ec_vrfs::ietf::{Prover, Verifier}; + + /// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`]. + #[derive(Clone, Debug)] + pub struct VrfInput(pub(super) bandersnatch::Input); + + impl VrfInput { + /// Construct a new VRF input. + pub fn new(data: impl AsRef<[u8]>) -> Self { + Self(bandersnatch::Input::new(data.as_ref()).expect("Can't fail")) + } + } + + /// VRF pre-output derived from [`VrfInput`] using a [`VrfSecret`]. + /// + /// This object is used to produce an arbitrary number of verifiable pseudo random + /// bytes and is often called pre-output to emphasize that this is not the actual + /// output of the VRF but an object capable of generating the output. + #[derive(Clone, Debug)] + pub struct VrfPreOutput(pub(super) bandersnatch::Output); + + // Workaround until traits are not implemented for newtypes https://github.com/davxy/ark-ec-vrfs/issues/41 + impl PartialEq for VrfPreOutput { + fn eq(&self, other: &Self) -> bool { + self.0 .0 == other.0 .0 + } + } + impl Eq for VrfPreOutput {} + + impl Encode for VrfPreOutput { + fn encode(&self) -> Vec { + let mut bytes = [0; PREOUT_SERIALIZED_SIZE]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("serialization length is constant and checked by test; qed"); + bytes.encode() + } + } + + impl Decode for VrfPreOutput { + fn decode(i: &mut R) -> Result { + let buf = <[u8; PREOUT_SERIALIZED_SIZE]>::decode(i)?; + let preout = bandersnatch::Output::deserialize_compressed_unchecked(buf.as_slice()) + .map_err(|_| "vrf-preout decode error: bad preout")?; + Ok(VrfPreOutput(preout)) + } + } + + // `VrfPreOutput` resolves to: + // ``` + // pub struct Affine { + // pub x: P::BaseField, + // pub y: P::BaseField, + // } + // ``` + // where each `P::BaseField` contains a `pub struct BigInt(pub [u64; N]);` + // Since none of these structures is allocated on the heap, we don't need any special + // memory tracking logic. We can simply implement `DecodeWithMemTracking`. + impl DecodeWithMemTracking for VrfPreOutput {} + + impl EncodeLike for VrfPreOutput {} + + impl MaxEncodedLen for VrfPreOutput { + fn max_encoded_len() -> usize { + <[u8; PREOUT_SERIALIZED_SIZE]>::max_encoded_len() + } + } + + impl TypeInfo for VrfPreOutput { + type Identity = [u8; PREOUT_SERIALIZED_SIZE]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + /// Data to be signed via one of the two provided vrf flavors. + /// + /// The object contains the VRF input and additional data to be signed together + /// with the VRF input. Additional data doesn't influence the VRF output. + /// + /// The `input` is a [`VrfInput`]s which, during the signing procedure, is first mapped + /// to a [`VrfPreOutput`]. + #[derive(Clone)] + pub struct VrfSignData { + /// VRF input. + pub vrf_input: VrfInput, + /// Additional data. + pub aux_data: Vec, + } + + impl VrfSignData { + /// Construct a new data to be signed. + pub fn new(vrf_input_data: &[u8], aux_data: &[u8]) -> Self { + Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_owned() } + } + } + + /// VRF signature. + /// + /// Includes both the VRF proof and the pre-output generated from the [`VrfSignData::input`]. + /// + /// Refer to [`VrfSignData`] for more details. + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct VrfSignature { + /// VRF pre-output. + pub pre_output: VrfPreOutput, + /// VRF proof. + pub proof: Signature, + } + + #[cfg(feature = "full_crypto")] + impl VrfCrypto for Pair { + type VrfInput = VrfInput; + type VrfPreOutput = VrfPreOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } + + #[cfg(feature = "full_crypto")] + impl VrfSecret for Pair { + fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { + let pre_output_impl = self.secret.output(data.vrf_input.0); + let pre_output = VrfPreOutput(pre_output_impl); + let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); + let mut proof = Signature::default(); + proof_impl + .serialize_compressed(proof.0.as_mut_slice()) + .expect("serialization length is constant and checked by test; qed"); + VrfSignature { pre_output, proof } + } + + fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput { + let pre_output_impl = self.secret.output(input.0); + VrfPreOutput(pre_output_impl) + } + } + + impl VrfCrypto for Public { + type VrfInput = VrfInput; + type VrfPreOutput = VrfPreOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } + + impl VrfPublic for Public { + fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { + let Ok(public) = + bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) + else { + return false + }; + + // Deserialize only the proof, the rest has already been deserialized + // This is another hack used because backend signature type is generic over + // the number of ios. + let Ok(proof) = ark_ec_vrfs::ietf::Proof::deserialize_compressed_unchecked( + signature.proof.as_slice(), + ) else { + return false + }; + + public + .verify(data.vrf_input.0, signature.pre_output.0, &data.aux_data, &proof) + .is_ok() + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// Generate VRF output bytes for the given `input`. + pub fn make_bytes(&self, input: &VrfInput) -> [u8; 32] { + self.vrf_pre_output(input).make_bytes() + } + } + + impl VrfPreOutput { + /// Generate VRF output bytes. + pub fn make_bytes(&self) -> [u8; 32] { + let mut bytes = [0_u8; 32]; + bytes.copy_from_slice(&self.0.hash()[..32]); + bytes + } + } +} + +// /// Bandersnatch Ring-VRF types and operations. +// pub mod ring_vrf { +// use super::{vrf::*, *}; +// pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; +// use bandersnatch_vrfs::{ring::VerifierKey, CanonicalDeserialize, PublicKey}; + +// /// Overhead in the domain size with respect to the supported ring size. +// /// +// /// Some bits of the domain are reserved for the zk-proof to work. +// pub const RING_DOMAIN_OVERHEAD: u32 = 257; + +// // Max size of serialized ring-vrf context given `domain_len`. +// pub(crate) const fn ring_context_serialized_size(domain_len: u32) -> usize { +// // const G1_POINT_COMPRESSED_SIZE: usize = 48; +// // const G2_POINT_COMPRESSED_SIZE: usize = 96; +// const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; +// const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; +// const OVERHEAD_SIZE: usize = 20; +// const G2_POINTS_NUM: usize = 2; +// let g1_points_num = 3 * domain_len as usize + 1; + +// OVERHEAD_SIZE + +// g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + +// G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE +// } + +// pub(crate) const RING_VERIFIER_DATA_SERIALIZED_SIZE: usize = 388; +// pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755; + +// /// remove as soon as soon as serialization is implemented by the backend +// pub struct RingVerifierData { +// /// Domain size. +// pub domain_size: u32, +// /// Verifier key. +// pub verifier_key: VerifierKey, +// } + +// impl From for RingVerifier { +// fn from(vd: RingVerifierData) -> RingVerifier { +// bandersnatch_vrfs::ring::make_ring_verifier(vd.verifier_key, vd.domain_size as usize) +// } +// } + +// impl Encode for RingVerifierData { +// fn encode(&self) -> Vec { +// const ERR_STR: &str = "serialization length is constant and checked by test; qed"; +// let mut buf = [0; RING_VERIFIER_DATA_SERIALIZED_SIZE]; +// self.domain_size.serialize_compressed(&mut buf[..4]).expect(ERR_STR); +// self.verifier_key.serialize_compressed(&mut buf[4..]).expect(ERR_STR); +// buf.encode() +// } +// } + +// impl Decode for RingVerifierData { +// fn decode(i: &mut R) -> Result { +// const ERR_STR: &str = "serialization length is constant and checked by test; qed"; +// let buf = <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::decode(i)?; +// let domain_size = +// ::deserialize_compressed_unchecked(&mut &buf[..4]) +// .expect(ERR_STR); +// let verifier_key = ::deserialize_compressed_unchecked(&mut &buf[4..]).expect(ERR_STR); + +// Ok(RingVerifierData { domain_size, verifier_key }) +// } +// } + +// impl EncodeLike for RingVerifierData {} + +// impl MaxEncodedLen for RingVerifierData { +// fn max_encoded_len() -> usize { +// <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::max_encoded_len() +// } +// } + +// impl TypeInfo for RingVerifierData { +// type Identity = [u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]; + +// fn type_info() -> scale_info::Type { +// Self::Identity::type_info() +// } +// } + +// /// Context used to construct ring prover and verifier. +// /// +// /// Generic parameter `D` represents the ring domain size and drives +// /// the max number of supported ring members [`RingContext::max_keyset_size`] +// /// which is equal to `D - RING_DOMAIN_OVERHEAD`. +// #[derive(Clone)] +// pub struct RingContext(KZG); + +// impl RingContext { +// /// Build an dummy instance for testing purposes. +// pub fn new_testing() -> Self { +// Self(KZG::testing_kzg_setup([0; 32], D)) +// } + +// /// Get the keyset max size. +// pub fn max_keyset_size(&self) -> usize { +// self.0.max_keyset_size() +// } + +// /// Get ring prover for the key at index `public_idx` in the `public_keys` set. +// pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { +// let mut pks = Vec::with_capacity(public_keys.len()); +// for public_key in public_keys { +// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; +// pks.push(pk.0.into()); +// } + +// let prover_key = self.0.prover_key(pks); +// let ring_prover = self.0.init_ring_prover(prover_key, public_idx); +// Some(ring_prover) +// } + +// /// Get ring verifier for the `public_keys` set. +// pub fn verifier(&self, public_keys: &[Public]) -> Option { +// let mut pks = Vec::with_capacity(public_keys.len()); +// for public_key in public_keys { +// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; +// pks.push(pk.0.into()); +// } + +// let verifier_key = self.0.verifier_key(pks); +// let ring_verifier = self.0.init_ring_verifier(verifier_key); +// Some(ring_verifier) +// } + +// /// Information required for a lazy construction of a ring verifier. +// pub fn verifier_data(&self, public_keys: &[Public]) -> Option { +// let mut pks = Vec::with_capacity(public_keys.len()); +// for public_key in public_keys { +// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; +// pks.push(pk.0.into()); +// } +// Some(RingVerifierData { +// verifier_key: self.0.verifier_key(pks), +// domain_size: self.0.domain_size, +// }) +// } +// } + +// impl Encode for RingContext { +// fn encode(&self) -> Vec { +// let mut buf = vec![0; ring_context_serialized_size(D)]; +// self.0 +// .serialize_uncompressed(buf.as_mut_slice()) +// .expect("serialization length is constant and checked by test; qed"); +// buf +// } +// } + +// impl Decode for RingContext { +// fn decode(input: &mut R) -> Result { +// let mut buf = vec![0; ring_context_serialized_size(D)]; +// input.read(&mut buf[..])?; +// let kzg = KZG::deserialize_uncompressed_unchecked(buf.as_slice()) +// .map_err(|_| "KZG decode error")?; +// Ok(RingContext(kzg)) +// } +// } + +// impl EncodeLike for RingContext {} + +// impl MaxEncodedLen for RingContext { +// fn max_encoded_len() -> usize { +// ring_context_serialized_size(D) +// } +// } + +// impl TypeInfo for RingContext { +// type Identity = Self; + +// fn type_info() -> scale_info::Type { +// let path = scale_info::Path::new("RingContext", module_path!()); +// let array_type_def = scale_info::TypeDefArray { +// len: ring_context_serialized_size(D) as u32, +// type_param: scale_info::MetaType::new::(), +// }; +// let type_def = scale_info::TypeDef::Array(array_type_def); +// scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() } +// } +// } + +// /// Ring VRF signature. +// #[derive( +// Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, +// )] +// pub struct RingVrfSignature { +// /// Ring signature. +// pub signature: [u8; RING_SIGNATURE_SERIALIZED_SIZE], +// /// VRF pre-outputs. +// pub pre_outputs: VrfIosVec, +// } + +// #[cfg(feature = "full_crypto")] +// impl Pair { +// /// Produce a ring-vrf signature. +// /// +// /// The ring signature is verifiable if the public key corresponding to the +// /// signing [`Pair`] is part of the ring from which the [`RingProver`] has +// /// been constructed. If not, the produced signature is just useless. +// pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { +// const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); +// // Workaround to overcome backend signature generic over the number of IOs. +// match data.inputs.len() { +// 0 => self.ring_vrf_sign_gen::<0>(data, prover), +// 1 => self.ring_vrf_sign_gen::<1>(data, prover), +// 2 => self.ring_vrf_sign_gen::<2>(data, prover), +// 3 => self.ring_vrf_sign_gen::<3>(data, prover), +// _ => unreachable!(), +// } +// } + +// fn ring_vrf_sign_gen( +// &self, +// data: &VrfSignData, +// prover: &RingProver, +// ) -> RingVrfSignature { +// let ios = core::array::from_fn(|i| self.secret.vrf_inout(data.inputs[i].0)); + +// let ring_signature: bandersnatch_vrfs::RingVrfSignature = +// bandersnatch_vrfs::RingProver { ring_prover: prover, secret: &self.secret } +// .sign_ring_vrf(data.transcript.clone(), &ios); + +// let pre_outputs: Vec<_> = +// ring_signature.preouts.into_iter().map(VrfPreOutput).collect(); +// let pre_outputs = VrfIosVec::truncate_from(pre_outputs); + +// let mut signature = +// RingVrfSignature { pre_outputs, signature: [0; RING_SIGNATURE_SERIALIZED_SIZE] }; + +// ring_signature +// .proof +// .serialize_compressed(signature.signature.as_mut_slice()) +// .expect("serialization length is constant and checked by test; qed"); + +// signature +// } +// } + +// impl RingVrfSignature { +// /// Verify a ring-vrf signature. +// /// +// /// The signature is verifiable if it has been produced by a member of the ring +// /// from which the [`RingVerifier`] has been constructed. +// pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { +// const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); +// let preouts_len = self.pre_outputs.len(); +// if preouts_len != data.inputs.len() { +// return false +// } +// // Workaround to overcome backend signature generic over the number of IOs. +// match preouts_len { +// 0 => self.ring_vrf_verify_gen::<0>(data, verifier), +// 1 => self.ring_vrf_verify_gen::<1>(data, verifier), +// 2 => self.ring_vrf_verify_gen::<2>(data, verifier), +// 3 => self.ring_vrf_verify_gen::<3>(data, verifier), +// _ => unreachable!(), +// } +// } + +// fn ring_vrf_verify_gen( +// &self, +// data: &VrfSignData, +// verifier: &RingVerifier, +// ) -> bool { +// let Ok(vrf_signature) = +// bandersnatch_vrfs::RingVrfSignature::<0>::deserialize_compressed_unchecked( +// self.signature.as_slice(), +// ) +// else { +// return false +// }; + +// let preouts: [bandersnatch_vrfs::VrfPreOut; N] = +// core::array::from_fn(|i| self.pre_outputs[i].0); + +// let signature = +// bandersnatch_vrfs::RingVrfSignature { proof: vrf_signature.proof, preouts }; + +// let inputs = data.inputs.iter().map(|i| i.0); + +// bandersnatch_vrfs::RingVerifier(verifier) +// .verify_ring_vrf(data.transcript.clone(), inputs, &signature) +// .is_ok() +// } +// } +// } + +#[cfg(test)] +mod tests { + use super::{vrf::*, *}; + use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; + + const TEST_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; + const TEST_DOMAIN_SIZE: u32 = 1024; + + #[allow(unused)] + fn b2h(bytes: &[u8]) -> String { + array_bytes::bytes2hex("", bytes) + } + + fn h2b(hex: &str) -> Vec { + array_bytes::hex2bytes_unchecked(hex) + } + + #[test] + fn sign_verify() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let msg = b"hello"; + + let signature = pair.sign(msg); + assert!(Pair::verify(&signature, msg, &public)); + } +} diff --git a/substrate/primitives/core/src/lib.rs b/substrate/primitives/core/src/lib.rs index b24eb400e6459..76356cc1b4ac6 100644 --- a/substrate/primitives/core/src/lib.rs +++ b/substrate/primitives/core/src/lib.rs @@ -72,6 +72,7 @@ pub mod uint; #[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; +pub mod bandersnatch2; #[cfg(feature = "bls-experimental")] pub mod bls; pub mod crypto_bytes; From 1d2eed88eeceec65a969873eab1c1048239be56c Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 15:20:53 +0100 Subject: [PATCH 02/29] Implement Schnorr signature for Bandersnatch --- Cargo.lock | 2 - Cargo.toml | 3 + .../primitives/core/src/bandersnatch2.rs | 549 ++++++++---------- 3 files changed, 247 insertions(+), 307 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 687649f4618f4..e7e8befddad76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,8 +575,6 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbce7dad39bc73d5ca4d5815eea91e54e1e940d86d5739ab8d380b43f3e1910b" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 804db6dacbe41..8ef00acb34fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1478,3 +1478,6 @@ wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } + +[patch.crates-io] +ark-ec-vrfs = { path = "/mnt/ssd/develop/personal/ark-ec-vrfs" } diff --git a/substrate/primitives/core/src/bandersnatch2.rs b/substrate/primitives/core/src/bandersnatch2.rs index 6fd9ef32f98ce..a351724bacbba 100644 --- a/substrate/primitives/core/src/bandersnatch2.rs +++ b/substrate/primitives/core/src/bandersnatch2.rs @@ -27,9 +27,12 @@ use crate::crypto::{ PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, }; use ark_ec_vrfs::{ - prelude::ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, + prelude::{ + ark_ec::CurveGroup, + ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, + }, ring::RingSuite, - suites::bandersnatch::edwards as bandersnatch, + suites::bandersnatch::te as bandersnatch, }; use bandersnatch::Secret; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; @@ -142,16 +145,17 @@ impl TraitPair for Pair { Public::unchecked_from(raw) } - /// Sign a message. - /// - /// In practice this produce a Schnorr signature. #[cfg(feature = "full_crypto")] fn sign(&self, data: &[u8]) -> Signature { use ark_ec_vrfs::Suite; use bandersnatch::BandersnatchSha512Ell2; let input = bandersnatch::Input::new(data).unwrap(); let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); - let c = BandersnatchSha512Ell2::challenge(&[&self.secret.public.0, &input.0], data); + let gk = BandersnatchSha512Ell2::generator() * k; + let c = BandersnatchSha512Ell2::challenge( + &[&gk.into_affine(), &self.secret.public.0, &input.0], + &[], + ); let s = k + c * self.secret.scalar; let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; bandersnatch::IetfProof { c, s } @@ -161,11 +165,21 @@ impl TraitPair for Pair { } fn verify>(signature: &Signature, data: M, public: &Public) -> bool { - let data = vrf::VrfSignData::new(SIGNING_CTX, data.as_ref()); - let dummy = ark_ec_vrfs::Output(bandersnatch::BandersnatchSha512Ell2::PADDING); - let signature = - vrf::VrfSignature { proof: *signature, pre_output: vrf::VrfPreOutput(dummy) }; - public.vrf_verify(&data, &signature) + use ark_ec_vrfs::Suite; + use bandersnatch::BandersnatchSha512Ell2; + let Ok(signature) = bandersnatch::IetfProof::deserialize_compressed(&signature.0[..]) + else { + return false + }; + let Ok(public) = bandersnatch::Public::deserialize_compressed(&public.0[..]) else { + return false + }; + let input = bandersnatch::Input::new(data.as_ref()).expect("Can't fail"); + let gs = BandersnatchSha512Ell2::generator() * signature.s; + let yc = public.0 * signature.c; + let rv = gs - yc; + let cv = BandersnatchSha512Ell2::challenge(&[&rv.into_affine(), &public.0, &input.0], &[]); + signature.c == cv } /// Return a vector filled with the seed (32 bytes). @@ -334,16 +348,11 @@ pub mod vrf { else { return false }; - - // Deserialize only the proof, the rest has already been deserialized - // This is another hack used because backend signature type is generic over - // the number of ios. let Ok(proof) = ark_ec_vrfs::ietf::Proof::deserialize_compressed_unchecked( signature.proof.as_slice(), ) else { return false }; - public .verify(data.vrf_input.0, signature.pre_output.0, &data.aux_data, &proof) .is_ok() @@ -368,295 +377,225 @@ pub mod vrf { } } -// /// Bandersnatch Ring-VRF types and operations. -// pub mod ring_vrf { -// use super::{vrf::*, *}; -// pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; -// use bandersnatch_vrfs::{ring::VerifierKey, CanonicalDeserialize, PublicKey}; - -// /// Overhead in the domain size with respect to the supported ring size. -// /// -// /// Some bits of the domain are reserved for the zk-proof to work. -// pub const RING_DOMAIN_OVERHEAD: u32 = 257; - -// // Max size of serialized ring-vrf context given `domain_len`. -// pub(crate) const fn ring_context_serialized_size(domain_len: u32) -> usize { -// // const G1_POINT_COMPRESSED_SIZE: usize = 48; -// // const G2_POINT_COMPRESSED_SIZE: usize = 96; -// const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; -// const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; -// const OVERHEAD_SIZE: usize = 20; -// const G2_POINTS_NUM: usize = 2; -// let g1_points_num = 3 * domain_len as usize + 1; - -// OVERHEAD_SIZE + -// g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + -// G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE -// } - -// pub(crate) const RING_VERIFIER_DATA_SERIALIZED_SIZE: usize = 388; -// pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755; - -// /// remove as soon as soon as serialization is implemented by the backend -// pub struct RingVerifierData { -// /// Domain size. -// pub domain_size: u32, -// /// Verifier key. -// pub verifier_key: VerifierKey, -// } - -// impl From for RingVerifier { -// fn from(vd: RingVerifierData) -> RingVerifier { -// bandersnatch_vrfs::ring::make_ring_verifier(vd.verifier_key, vd.domain_size as usize) -// } -// } - -// impl Encode for RingVerifierData { -// fn encode(&self) -> Vec { -// const ERR_STR: &str = "serialization length is constant and checked by test; qed"; -// let mut buf = [0; RING_VERIFIER_DATA_SERIALIZED_SIZE]; -// self.domain_size.serialize_compressed(&mut buf[..4]).expect(ERR_STR); -// self.verifier_key.serialize_compressed(&mut buf[4..]).expect(ERR_STR); -// buf.encode() -// } -// } - -// impl Decode for RingVerifierData { -// fn decode(i: &mut R) -> Result { -// const ERR_STR: &str = "serialization length is constant and checked by test; qed"; -// let buf = <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::decode(i)?; -// let domain_size = -// ::deserialize_compressed_unchecked(&mut &buf[..4]) -// .expect(ERR_STR); -// let verifier_key = ::deserialize_compressed_unchecked(&mut &buf[4..]).expect(ERR_STR); - -// Ok(RingVerifierData { domain_size, verifier_key }) -// } -// } - -// impl EncodeLike for RingVerifierData {} - -// impl MaxEncodedLen for RingVerifierData { -// fn max_encoded_len() -> usize { -// <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::max_encoded_len() -// } -// } - -// impl TypeInfo for RingVerifierData { -// type Identity = [u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]; - -// fn type_info() -> scale_info::Type { -// Self::Identity::type_info() -// } -// } - -// /// Context used to construct ring prover and verifier. -// /// -// /// Generic parameter `D` represents the ring domain size and drives -// /// the max number of supported ring members [`RingContext::max_keyset_size`] -// /// which is equal to `D - RING_DOMAIN_OVERHEAD`. -// #[derive(Clone)] -// pub struct RingContext(KZG); - -// impl RingContext { -// /// Build an dummy instance for testing purposes. -// pub fn new_testing() -> Self { -// Self(KZG::testing_kzg_setup([0; 32], D)) -// } - -// /// Get the keyset max size. -// pub fn max_keyset_size(&self) -> usize { -// self.0.max_keyset_size() -// } - -// /// Get ring prover for the key at index `public_idx` in the `public_keys` set. -// pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { -// let mut pks = Vec::with_capacity(public_keys.len()); -// for public_key in public_keys { -// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; -// pks.push(pk.0.into()); -// } - -// let prover_key = self.0.prover_key(pks); -// let ring_prover = self.0.init_ring_prover(prover_key, public_idx); -// Some(ring_prover) -// } - -// /// Get ring verifier for the `public_keys` set. -// pub fn verifier(&self, public_keys: &[Public]) -> Option { -// let mut pks = Vec::with_capacity(public_keys.len()); -// for public_key in public_keys { -// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; -// pks.push(pk.0.into()); -// } - -// let verifier_key = self.0.verifier_key(pks); -// let ring_verifier = self.0.init_ring_verifier(verifier_key); -// Some(ring_verifier) -// } - -// /// Information required for a lazy construction of a ring verifier. -// pub fn verifier_data(&self, public_keys: &[Public]) -> Option { -// let mut pks = Vec::with_capacity(public_keys.len()); -// for public_key in public_keys { -// let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; -// pks.push(pk.0.into()); -// } -// Some(RingVerifierData { -// verifier_key: self.0.verifier_key(pks), -// domain_size: self.0.domain_size, -// }) -// } -// } - -// impl Encode for RingContext { -// fn encode(&self) -> Vec { -// let mut buf = vec![0; ring_context_serialized_size(D)]; -// self.0 -// .serialize_uncompressed(buf.as_mut_slice()) -// .expect("serialization length is constant and checked by test; qed"); -// buf -// } -// } - -// impl Decode for RingContext { -// fn decode(input: &mut R) -> Result { -// let mut buf = vec![0; ring_context_serialized_size(D)]; -// input.read(&mut buf[..])?; -// let kzg = KZG::deserialize_uncompressed_unchecked(buf.as_slice()) -// .map_err(|_| "KZG decode error")?; -// Ok(RingContext(kzg)) -// } -// } - -// impl EncodeLike for RingContext {} - -// impl MaxEncodedLen for RingContext { -// fn max_encoded_len() -> usize { -// ring_context_serialized_size(D) -// } -// } - -// impl TypeInfo for RingContext { -// type Identity = Self; - -// fn type_info() -> scale_info::Type { -// let path = scale_info::Path::new("RingContext", module_path!()); -// let array_type_def = scale_info::TypeDefArray { -// len: ring_context_serialized_size(D) as u32, -// type_param: scale_info::MetaType::new::(), -// }; -// let type_def = scale_info::TypeDef::Array(array_type_def); -// scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() } -// } -// } - -// /// Ring VRF signature. -// #[derive( -// Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, -// )] -// pub struct RingVrfSignature { -// /// Ring signature. -// pub signature: [u8; RING_SIGNATURE_SERIALIZED_SIZE], -// /// VRF pre-outputs. -// pub pre_outputs: VrfIosVec, -// } - -// #[cfg(feature = "full_crypto")] -// impl Pair { -// /// Produce a ring-vrf signature. -// /// -// /// The ring signature is verifiable if the public key corresponding to the -// /// signing [`Pair`] is part of the ring from which the [`RingProver`] has -// /// been constructed. If not, the produced signature is just useless. -// pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { -// const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); -// // Workaround to overcome backend signature generic over the number of IOs. -// match data.inputs.len() { -// 0 => self.ring_vrf_sign_gen::<0>(data, prover), -// 1 => self.ring_vrf_sign_gen::<1>(data, prover), -// 2 => self.ring_vrf_sign_gen::<2>(data, prover), -// 3 => self.ring_vrf_sign_gen::<3>(data, prover), -// _ => unreachable!(), -// } -// } - -// fn ring_vrf_sign_gen( -// &self, -// data: &VrfSignData, -// prover: &RingProver, -// ) -> RingVrfSignature { -// let ios = core::array::from_fn(|i| self.secret.vrf_inout(data.inputs[i].0)); - -// let ring_signature: bandersnatch_vrfs::RingVrfSignature = -// bandersnatch_vrfs::RingProver { ring_prover: prover, secret: &self.secret } -// .sign_ring_vrf(data.transcript.clone(), &ios); - -// let pre_outputs: Vec<_> = -// ring_signature.preouts.into_iter().map(VrfPreOutput).collect(); -// let pre_outputs = VrfIosVec::truncate_from(pre_outputs); - -// let mut signature = -// RingVrfSignature { pre_outputs, signature: [0; RING_SIGNATURE_SERIALIZED_SIZE] }; - -// ring_signature -// .proof -// .serialize_compressed(signature.signature.as_mut_slice()) -// .expect("serialization length is constant and checked by test; qed"); - -// signature -// } -// } - -// impl RingVrfSignature { -// /// Verify a ring-vrf signature. -// /// -// /// The signature is verifiable if it has been produced by a member of the ring -// /// from which the [`RingVerifier`] has been constructed. -// pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { -// const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); -// let preouts_len = self.pre_outputs.len(); -// if preouts_len != data.inputs.len() { -// return false -// } -// // Workaround to overcome backend signature generic over the number of IOs. -// match preouts_len { -// 0 => self.ring_vrf_verify_gen::<0>(data, verifier), -// 1 => self.ring_vrf_verify_gen::<1>(data, verifier), -// 2 => self.ring_vrf_verify_gen::<2>(data, verifier), -// 3 => self.ring_vrf_verify_gen::<3>(data, verifier), -// _ => unreachable!(), -// } -// } - -// fn ring_vrf_verify_gen( -// &self, -// data: &VrfSignData, -// verifier: &RingVerifier, -// ) -> bool { -// let Ok(vrf_signature) = -// bandersnatch_vrfs::RingVrfSignature::<0>::deserialize_compressed_unchecked( -// self.signature.as_slice(), -// ) -// else { -// return false -// }; - -// let preouts: [bandersnatch_vrfs::VrfPreOut; N] = -// core::array::from_fn(|i| self.pre_outputs[i].0); - -// let signature = -// bandersnatch_vrfs::RingVrfSignature { proof: vrf_signature.proof, preouts }; - -// let inputs = data.inputs.iter().map(|i| i.0); - -// bandersnatch_vrfs::RingVerifier(verifier) -// .verify_ring_vrf(data.transcript.clone(), inputs, &signature) -// .is_ok() -// } -// } -// } +/// Bandersnatch Ring-VRF types and operations. +pub mod ring_vrf { + use super::{vrf::*, *}; + use ark_ec_vrfs::ring::{Prover, Verifier}; + use bandersnatch::{RingContext as RingContextImpl, RingProver, RingVerifier, VerifierKey}; + + // Max size of serialized ring-vrf context given `domain_len`. + // TODO @davxy: test this + fn ring_context_serialized_size(ring_size: usize) -> usize { + use ark_ec_vrfs::prelude::ark_ff::PrimeField; + // const G1_POINT_COMPRESSED_SIZE: usize = 48; + // const G2_POINT_COMPRESSED_SIZE: usize = 96; + const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; + const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; + const OVERHEAD_SIZE: usize = 20; + const G2_POINTS_NUM: usize = 2; + let w = 4 + ring_size + bandersnatch::ScalarField::MODULUS_BIT_SIZE as usize; + let domain_size = ark_ec_vrfs::prelude::ark_std::log2(w); + let g1_points_num = 3 * domain_size as usize + 1; + OVERHEAD_SIZE + + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + + G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE + } + + pub(crate) const RING_COMMITMENT_SERIALIZED_SIZE: usize = 388; + pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755; + + // impl Encode for RingVerifierData { + // fn encode(&self) -> Vec { + // const ERR_STR: &str = "serialization length is constant and checked by test; qed"; + // let mut buf = [0; RING_VERIFIER_DATA_SERIALIZED_SIZE]; + // self.domain_size.serialize_compressed(&mut buf[..4]).expect(ERR_STR); + // self.verifier_key.serialize_compressed(&mut buf[4..]).expect(ERR_STR); + // buf.encode() + // } + // } + + // impl Decode for RingVerifierData { + // fn decode(i: &mut R) -> Result { + // const ERR_STR: &str = "serialization length is constant and checked by test; qed"; + // let buf = <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::decode(i)?; + // let domain_size = + // ::deserialize_compressed_unchecked(&mut &buf[..4]) + // .expect(ERR_STR); + // let verifier_key = ::deserialize_compressed_unchecked(&mut &buf[4..]).expect(ERR_STR); + + // Ok(RingVerifierData { domain_size, verifier_key }) + // } + // } + + // impl EncodeLike for RingVerifierData {} + + // impl MaxEncodedLen for RingVerifierData { + // fn max_encoded_len() -> usize { + // <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::max_encoded_len() + // } + // } + + // impl TypeInfo for RingVerifierData { + // type Identity = [u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]; + + // fn type_info() -> scale_info::Type { + // Self::Identity::type_info() + // } + // } + + /// Context used to construct ring prover and verifier. + /// + /// Generic parameter `R` represents the ring size. + #[derive(Clone)] + pub struct RingContext(RingContextImpl); + + impl RingContext { + /// Build an dummy instance for testing purposes. + pub fn new_testing() -> Self { + Self(RingContextImpl::from_seed(R, [0; 32])) + } + + /// Get the keyset max size. + pub fn max_keyset_size(&self) -> usize { + self.0.max_ring_size() + } + + /// Get ring prover for the key at index `public_idx` in the `public_keys` set. + pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { + let pks = Self::make_ring_vector(public_keys)?; + let prover_key = self.0.prover_key(&pks[..]); + Some(self.0.prover(prover_key, public_idx)) + } + + /// Get ring verifier for the `public_keys` set. + pub fn verifier(&self, public_keys: &[Public]) -> Option { + self.verifier_key(public_keys).map(|vk| self.0.verifier(vk)) + } + + /// Build ring commitment for `RingVerifier` lazy construction. + pub fn verifier_key(&self, public_keys: &[Public]) -> Option { + let pks = Self::make_ring_vector(public_keys)?; + Some(self.0.verifier_key(&pks[..])) + } + + /// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance. + /// + /// While this approach is computationally slightly less efficient than using a + /// pre-constructed `RingContext`, as some parameters need to be computed on-the-fly, it + /// is beneficial in memory or storage constrained environments. This avoids the need to + /// retain the full `RingContext` for ring signature verification. Instead, the + /// `VerifierKey` contains only the essential information needed to verify ring proofs. + pub fn verifier_no_context(verifier_key: VerifierKey) -> RingVerifier { + RingContextImpl::verifier_no_context(verifier_key, R) + } + + fn make_ring_vector(public_keys: &[Public]) -> Option> { + use bandersnatch::Public as PublicImpl; + let mut pts = Vec::with_capacity(public_keys.len()); + for pk in public_keys { + let pk = PublicImpl::deserialize_compressed_unchecked(pk.as_slice()).ok()?; + pts.push(pk.0); + } + Some(pts) + } + } + + impl Encode for RingContext { + fn encode(&self) -> Vec { + let mut buf = Vec::with_capacity(ring_context_serialized_size(R)); + self.0 + .serialize_uncompressed(&mut buf) + .expect("serialization length is constant and checked by test; qed"); + buf + } + } + + impl Decode for RingContext { + fn decode(input: &mut I) -> Result { + let mut buf = vec![0; ring_context_serialized_size(R)]; + input.read(&mut buf[..])?; + let ctx = RingContextImpl::deserialize_uncompressed_unchecked(buf.as_slice()) + .map_err(|_| "RingContext decode error")?; + Ok(RingContext(ctx)) + } + } + + impl EncodeLike for RingContext {} + + impl MaxEncodedLen for RingContext { + fn max_encoded_len() -> usize { + ring_context_serialized_size(R) + } + } + + impl TypeInfo for RingContext { + type Identity = Self; + + fn type_info() -> scale_info::Type { + let path = scale_info::Path::new("RingContext", module_path!()); + let array_type_def = scale_info::TypeDefArray { + len: ring_context_serialized_size(R) as u32, + type_param: scale_info::MetaType::new::(), + }; + let type_def = scale_info::TypeDef::Array(array_type_def); + scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() } + } + } + + /// Ring VRF signature. + #[derive( + Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, + )] + pub struct RingVrfSignature { + /// VRF pre-output. + pub pre_output: VrfPreOutput, + /// Ring signature. + pub proof: [u8; RING_SIGNATURE_SERIALIZED_SIZE], + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// Produce a ring-vrf signature. + /// + /// The ring signature is verifiable if the public key corresponding to the + /// signing [`Pair`] is part of the ring from which the [`RingProver`] has + /// been constructed. If not, the produced signature is just useless. + pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> VrfSignature { + let pre_output_impl = self.secret.output(data.vrf_input.0); + let pre_output = VrfPreOutput(pre_output_impl); + let proof_impl = + self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data, prover); + let mut proof = Signature::default(); + proof_impl + .serialize_compressed(proof.0.as_mut_slice()) + .expect("serialization length is constant and checked by test; qed"); + VrfSignature { pre_output, proof } + } + } + + impl RingVrfSignature { + /// Verify a ring-vrf signature. + /// + /// The signature is verifiable if it has been produced by a member of the ring + /// from which the [`RingVerifier`] has been constructed. + fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + let Ok(proof) = + bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) + else { + return false + }; + bandersnatch::Public::verify( + data.vrf_input.0, + self.pre_output.0, + &data.aux_data, + &proof, + verifier, + ) + .is_ok() + } + } +} #[cfg(test)] mod tests { From be672533cb867017b51fc2387e10fd6d44a34f78 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 17:49:38 +0100 Subject: [PATCH 03/29] Working integration --- substrate/primitives/core/src/bandersnatch.rs | 314 --------------- .../primitives/core/src/bandersnatch2.rs | 379 ++++++++++++++---- 2 files changed, 311 insertions(+), 382 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 0ede8119ce3d9..7d098b3c3cd91 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -781,7 +781,6 @@ pub mod ring_vrf { #[cfg(test)] mod tests { use super::{ring_vrf::*, vrf::*, *}; - use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; const DEV_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; const TEST_DOMAIN_SIZE: u32 = 1024; @@ -792,317 +791,4 @@ mod tests { fn b2h(bytes: &[u8]) -> String { array_bytes::bytes2hex("", bytes) } - - fn h2b(hex: &str) -> Vec { - array_bytes::hex2bytes_unchecked(hex) - } - - #[test] - fn backend_assumptions_sanity_check() { - let kzg = KZG::testing_kzg_setup([0; 32], TEST_DOMAIN_SIZE); - assert_eq!(kzg.max_keyset_size() as u32, TEST_DOMAIN_SIZE - RING_DOMAIN_OVERHEAD); - - assert_eq!(kzg.uncompressed_size(), ring_context_serialized_size(TEST_DOMAIN_SIZE)); - - let pks: Vec<_> = (0..16) - .map(|i| SecretKey::from_seed(&[i as u8; 32]).to_public().0.into()) - .collect(); - - let secret = SecretKey::from_seed(&[0u8; 32]); - - let public = secret.to_public(); - assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE); - - let input = VrfInput::new(b"foo", &[]); - let preout = secret.vrf_preout(&input.0); - assert_eq!(preout.compressed_size(), PREOUT_SERIALIZED_SIZE); - - let verifier_key = kzg.verifier_key(pks.clone()); - assert_eq!(verifier_key.compressed_size() + 4, RING_VERIFIER_DATA_SERIALIZED_SIZE); - - let prover_key = kzg.prover_key(pks); - let ring_prover = kzg.init_ring_prover(prover_key, 0); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], None); - - let thin_signature: bandersnatch_vrfs::ThinVrfSignature<0> = - secret.sign_thin_vrf(data.transcript.clone(), &[]); - assert_eq!(thin_signature.compressed_size(), SIGNATURE_SERIALIZED_SIZE); - - let ring_signature: bandersnatch_vrfs::RingVrfSignature<0> = - bandersnatch_vrfs::RingProver { ring_prover: &ring_prover, secret: &secret } - .sign_ring_vrf(data.transcript.clone(), &[]); - assert_eq!(ring_signature.compressed_size(), RING_SIGNATURE_SERIALIZED_SIZE); - } - - #[test] - fn max_vrf_ios_bound_respected() { - let inputs: Vec<_> = (0..MAX_VRF_IOS - 1).map(|_| VrfInput::new(b"", &[])).collect(); - let mut sign_data = VrfSignData::new(b"", &[b""], inputs).unwrap(); - let res = sign_data.push_vrf_input(VrfInput::new(b"", b"")); - assert!(res.is_ok()); - let res = sign_data.push_vrf_input(VrfInput::new(b"", b"")); - assert!(res.is_err()); - let inputs: Vec<_> = (0..MAX_VRF_IOS + 1).map(|_| VrfInput::new(b"", b"")).collect(); - let res = VrfSignData::new(b"mydata", &[b"tdata"], inputs); - assert!(res.is_err()); - } - - #[test] - fn derive_works() { - let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap(); - let known = h2b("2b340c18b94dc1916979cb83daf3ed4ac106742ddc06afc42cf26be3b18a523f80"); - assert_eq!(pair.public().as_ref(), known); - - // Soft derivation not supported - let res = Pair::from_string(&format!("{}//Alice/Soft", DEV_PHRASE), None); - assert!(res.is_err()); - } - - #[test] - fn generate_with_phrase_should_be_recoverable_with_from_string() { - let (pair, phrase, seed) = Pair::generate_with_phrase(None); - let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); - assert_eq!(pair.public(), repair_seed.public()); - let (repair_phrase, reseed) = - Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); - assert_eq!(seed, reseed); - assert_eq!(pair.public(), repair_phrase.public()); - let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); - assert_eq!(pair.public(), repair_string.public()); - } - - #[test] - fn sign_verify() { - let pair = Pair::from_seed(DEV_SEED); - let public = pair.public(); - let msg = b"hello"; - - let signature = pair.sign(msg); - assert!(Pair::verify(&signature, msg, &public)); - } - - #[test] - fn vrf_sign_verify() { - let pair = Pair::from_seed(DEV_SEED); - let public = pair.public(); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - let i3 = VrfInput::new(b"dom3", b"baz"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); - - let signature = pair.vrf_sign(&data); - - assert!(public.vrf_verify(&data, &signature)); - } - - #[test] - fn vrf_sign_verify_bad_inputs() { - let pair = Pair::from_seed(DEV_SEED); - let public = pair.public(); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"aaaa"], [i1.clone(), i2.clone()]); - let signature = pair.vrf_sign(&data); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"bbb"], [i1, i2.clone()]); - assert!(!public.vrf_verify(&data, &signature)); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"aaa"], [i2]); - assert!(!public.vrf_verify(&data, &signature)); - } - - #[test] - fn vrf_make_bytes_matches() { - let pair = Pair::from_seed(DEV_SEED); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); - let signature = pair.vrf_sign(&data); - - let o10 = pair.make_bytes::<32>(b"ctx1", &i1); - let o11 = signature.pre_outputs[0].make_bytes::<32>(b"ctx1", &i1); - assert_eq!(o10, o11); - - let o20 = pair.make_bytes::<48>(b"ctx2", &i2); - let o21 = signature.pre_outputs[1].make_bytes::<48>(b"ctx2", &i2); - assert_eq!(o20, o21); - } - - #[test] - fn encode_decode_vrf_signature() { - // Transcript data is hashed together and signed. - // It doesn't contribute to serialized length. - let pair = Pair::from_seed(DEV_SEED); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); - let expected = pair.vrf_sign(&data); - - let bytes = expected.encode(); - - let expected_len = - data.inputs.len() * PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE + 1; - assert_eq!(bytes.len(), expected_len); - - let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); - assert_eq!(expected, decoded); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], []); - let expected = pair.vrf_sign(&data); - - let bytes = expected.encode(); - - let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); - assert_eq!(expected, decoded); - } - - #[test] - fn ring_vrf_sign_verify() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(DEV_SEED); - - // Just pick one index to patch with the actual public key - let prover_idx = 3; - pks[prover_idx] = pair.public(); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - let i3 = VrfInput::new(b"dom3", b"baz"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); - - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - let signature = pair.ring_vrf_sign(&data, &prover); - - let verifier = ring_ctx.verifier(&pks).unwrap(); - assert!(signature.ring_vrf_verify(&data, &verifier)); - } - - #[test] - fn ring_vrf_sign_verify_with_out_of_ring_key() { - let ring_ctx = TestRingContext::new_testing(); - - let pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - let pair = Pair::from_seed(DEV_SEED); - - // Just pick one index to patch with the actual public key - let i1 = VrfInput::new(b"dom1", b"foo"); - let data = VrfSignData::new_unchecked(b"mydata", Some(b"tdata"), Some(i1)); - - // pair.public != pks[0] - let prover = ring_ctx.prover(&pks, 0).unwrap(); - let signature = pair.ring_vrf_sign(&data, &prover); - - let verifier = ring_ctx.verifier(&pks).unwrap(); - assert!(!signature.ring_vrf_verify(&data, &verifier)); - } - - #[test] - fn ring_vrf_make_bytes_matches() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(DEV_SEED); - - // Just pick one index to patch with the actual public key - let prover_idx = 3; - pks[prover_idx] = pair.public(); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); - - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - let signature = pair.ring_vrf_sign(&data, &prover); - - let o10 = pair.make_bytes::<32>(b"ctx1", &i1); - let o11 = signature.pre_outputs[0].make_bytes::<32>(b"ctx1", &i1); - assert_eq!(o10, o11); - - let o20 = pair.make_bytes::<48>(b"ctx2", &i2); - let o21 = signature.pre_outputs[1].make_bytes::<48>(b"ctx2", &i2); - assert_eq!(o20, o21); - } - - #[test] - fn encode_decode_ring_vrf_signature() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(DEV_SEED); - - // Just pick one... - let prover_idx = 3; - pks[prover_idx] = pair.public(); - - let i1 = VrfInput::new(b"dom1", b"foo"); - let i2 = VrfInput::new(b"dom2", b"bar"); - let i3 = VrfInput::new(b"dom3", b"baz"); - - let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); - - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - let expected = pair.ring_vrf_sign(&data, &prover); - - let bytes = expected.encode(); - - let expected_len = - data.inputs.len() * PREOUT_SERIALIZED_SIZE + RING_SIGNATURE_SERIALIZED_SIZE + 1; - assert_eq!(bytes.len(), expected_len); - - let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); - assert_eq!(expected, decoded); - } - - #[test] - fn encode_decode_ring_vrf_context() { - let ctx1 = TestRingContext::new_testing(); - let enc1 = ctx1.encode(); - - let _ti = ::type_info(); - - assert_eq!(enc1.len(), ring_context_serialized_size(TEST_DOMAIN_SIZE)); - assert_eq!(enc1.len(), TestRingContext::max_encoded_len()); - - let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap(); - let enc2 = ctx2.encode(); - - assert_eq!(enc1, enc2); - } - - #[test] - fn encode_decode_verifier_data() { - let ring_ctx = TestRingContext::new_testing(); - - let pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let verifier_data = ring_ctx.verifier_data(&pks).unwrap(); - let enc1 = verifier_data.encode(); - - assert_eq!(enc1.len(), RING_VERIFIER_DATA_SERIALIZED_SIZE); - assert_eq!(RingVerifierData::max_encoded_len(), RING_VERIFIER_DATA_SERIALIZED_SIZE); - - let vd2 = RingVerifierData::decode(&mut enc1.as_slice()).unwrap(); - let enc2 = vd2.encode(); - - assert_eq!(enc1, enc2); - } } diff --git a/substrate/primitives/core/src/bandersnatch2.rs b/substrate/primitives/core/src/bandersnatch2.rs index a351724bacbba..d5fe01f50e5a2 100644 --- a/substrate/primitives/core/src/bandersnatch2.rs +++ b/substrate/primitives/core/src/bandersnatch2.rs @@ -34,7 +34,7 @@ use ark_ec_vrfs::{ ring::RingSuite, suites::bandersnatch::te as bandersnatch, }; -use bandersnatch::Secret; +use bandersnatch::{BandersnatchSha512Ell2 as BandersnatchSuite, Secret}; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; @@ -198,6 +198,10 @@ pub mod vrf { use crate::crypto::VrfCrypto; use ark_ec_vrfs::ietf::{Prover, Verifier}; + /// [`VrfSignature`] serialized size. + pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize = + PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; + /// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`]. #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch::Input); @@ -317,7 +321,7 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { - fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { + fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature { let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); @@ -342,7 +346,7 @@ pub mod vrf { } impl VrfPublic for Public { - fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { + fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool { let Ok(public) = bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) else { @@ -381,68 +385,70 @@ pub mod vrf { pub mod ring_vrf { use super::{vrf::*, *}; use ark_ec_vrfs::ring::{Prover, Verifier}; - use bandersnatch::{RingContext as RingContextImpl, RingProver, RingVerifier, VerifierKey}; + use bandersnatch::{ + RingContext as RingContextImpl, RingProver, RingVerifier, VerifierKey as VerifierKeyImpl, + }; // Max size of serialized ring-vrf context given `domain_len`. // TODO @davxy: test this - fn ring_context_serialized_size(ring_size: usize) -> usize { - use ark_ec_vrfs::prelude::ark_ff::PrimeField; + pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize { // const G1_POINT_COMPRESSED_SIZE: usize = 48; // const G2_POINT_COMPRESSED_SIZE: usize = 96; const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; - const OVERHEAD_SIZE: usize = 20; + const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let w = 4 + ring_size + bandersnatch::ScalarField::MODULUS_BIT_SIZE as usize; - let domain_size = ark_ec_vrfs::prelude::ark_std::log2(w); + let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); let g1_points_num = 3 * domain_size as usize + 1; OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE } - pub(crate) const RING_COMMITMENT_SERIALIZED_SIZE: usize = 388; - pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755; - - // impl Encode for RingVerifierData { - // fn encode(&self) -> Vec { - // const ERR_STR: &str = "serialization length is constant and checked by test; qed"; - // let mut buf = [0; RING_VERIFIER_DATA_SERIALIZED_SIZE]; - // self.domain_size.serialize_compressed(&mut buf[..4]).expect(ERR_STR); - // self.verifier_key.serialize_compressed(&mut buf[4..]).expect(ERR_STR); - // buf.encode() - // } - // } - - // impl Decode for RingVerifierData { - // fn decode(i: &mut R) -> Result { - // const ERR_STR: &str = "serialization length is constant and checked by test; qed"; - // let buf = <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::decode(i)?; - // let domain_size = - // ::deserialize_compressed_unchecked(&mut &buf[..4]) - // .expect(ERR_STR); - // let verifier_key = ::deserialize_compressed_unchecked(&mut &buf[4..]).expect(ERR_STR); - - // Ok(RingVerifierData { domain_size, verifier_key }) - // } - // } - - // impl EncodeLike for RingVerifierData {} - - // impl MaxEncodedLen for RingVerifierData { - // fn max_encoded_len() -> usize { - // <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::max_encoded_len() - // } - // } - - // impl TypeInfo for RingVerifierData { - // type Identity = [u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]; - - // fn type_info() -> scale_info::Type { - // Self::Identity::type_info() - // } - // } + /// [`RingVerifierKey`] serialized size. + pub const RING_VERIFIER_KEY_SERIALIZED_SIZE: usize = 384; + /// [`RingProof`] serialized size. + pub(crate) const RING_PROOF_SERIALIZED_SIZE: usize = 752; + /// [`RingVrrfSignature`] serialized size. + pub const RING_SIGNATURE_SERIALIZED_SIZE: usize = + RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE; + + /// Ring verifier key + pub struct RingVerifierKey(VerifierKeyImpl); + + impl Encode for RingVerifierKey { + fn encode(&self) -> Vec { + const ERR_STR: &str = "serialization length is constant and checked by test; qed"; + let mut buf = Vec::with_capacity(RING_VERIFIER_KEY_SERIALIZED_SIZE); + self.0.serialize_compressed(&mut buf).expect(ERR_STR); + buf + } + } + + impl Decode for RingVerifierKey { + fn decode(input: &mut R) -> Result { + let mut buf = vec![0; RING_VERIFIER_KEY_SERIALIZED_SIZE]; + input.read(&mut buf[..])?; + let vk = VerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice()) + .map_err(|_| "RingVerifierKey decode error")?; + Ok(RingVerifierKey(vk)) + } + } + + impl EncodeLike for RingVerifierKey {} + + impl MaxEncodedLen for RingVerifierKey { + fn max_encoded_len() -> usize { + RING_VERIFIER_KEY_SERIALIZED_SIZE + } + } + + impl TypeInfo for RingVerifierKey { + type Identity = [u8; RING_VERIFIER_KEY_SERIALIZED_SIZE]; + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } /// Context used to construct ring prover and verifier. /// @@ -464,19 +470,19 @@ pub mod ring_vrf { /// Get ring prover for the key at index `public_idx` in the `public_keys` set. pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { let pks = Self::make_ring_vector(public_keys)?; - let prover_key = self.0.prover_key(&pks[..]); + let prover_key = self.0.prover_key(&pks); Some(self.0.prover(prover_key, public_idx)) } /// Get ring verifier for the `public_keys` set. pub fn verifier(&self, public_keys: &[Public]) -> Option { - self.verifier_key(public_keys).map(|vk| self.0.verifier(vk)) + self.verifier_key(public_keys).map(|vk| self.0.verifier(vk.0)) } /// Build ring commitment for `RingVerifier` lazy construction. - pub fn verifier_key(&self, public_keys: &[Public]) -> Option { + pub fn verifier_key(&self, public_keys: &[Public]) -> Option { let pks = Self::make_ring_vector(public_keys)?; - Some(self.0.verifier_key(&pks[..])) + Some(RingVerifierKey(self.0.verifier_key(&pks))) } /// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance. @@ -486,8 +492,8 @@ pub mod ring_vrf { /// is beneficial in memory or storage constrained environments. This avoids the need to /// retain the full `RingContext` for ring signature verification. Instead, the /// `VerifierKey` contains only the essential information needed to verify ring proofs. - pub fn verifier_no_context(verifier_key: VerifierKey) -> RingVerifier { - RingContextImpl::verifier_no_context(verifier_key, R) + pub fn verifier_no_context(verifier_key: RingVerifierKey) -> RingVerifier { + RingContextImpl::verifier_no_context(verifier_key.0, R) } fn make_ring_vector(public_keys: &[Public]) -> Option> { @@ -531,7 +537,6 @@ pub mod ring_vrf { impl TypeInfo for RingContext { type Identity = Self; - fn type_info() -> scale_info::Type { let path = scale_info::Path::new("RingContext", module_path!()); let array_type_def = scale_info::TypeDefArray { @@ -551,7 +556,7 @@ pub mod ring_vrf { /// VRF pre-output. pub pre_output: VrfPreOutput, /// Ring signature. - pub proof: [u8; RING_SIGNATURE_SERIALIZED_SIZE], + pub proof: [u8; RING_PROOF_SERIALIZED_SIZE], } #[cfg(feature = "full_crypto")] @@ -561,16 +566,16 @@ pub mod ring_vrf { /// The ring signature is verifiable if the public key corresponding to the /// signing [`Pair`] is part of the ring from which the [`RingProver`] has /// been constructed. If not, the produced signature is just useless. - pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> VrfSignature { + pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data, prover); - let mut proof = Signature::default(); + let mut proof = [0; RING_PROOF_SERIALIZED_SIZE]; proof_impl - .serialize_compressed(proof.0.as_mut_slice()) + .serialize_compressed(proof.as_mut_slice()) .expect("serialization length is constant and checked by test; qed"); - VrfSignature { pre_output, proof } + RingVrfSignature { pre_output, proof } } } @@ -579,7 +584,7 @@ pub mod ring_vrf { /// /// The signature is verifiable if it has been produced by a member of the ring /// from which the [`RingVerifier`] has been constructed. - fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let Ok(proof) = bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) else { @@ -599,11 +604,13 @@ pub mod ring_vrf { #[cfg(test)] mod tests { - use super::{vrf::*, *}; + use super::{ring_vrf::*, vrf::*, *}; use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; const TEST_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; - const TEST_DOMAIN_SIZE: u32 = 1024; + const TEST_RING_SIZE: usize = 16; + + type TestRingContext = RingContext; #[allow(unused)] fn b2h(bytes: &[u8]) -> String { @@ -614,13 +621,249 @@ mod tests { array_bytes::hex2bytes_unchecked(hex) } + #[test] + fn backend_assumptions_sanity_check() { + use bandersnatch::{Input, RingContext as RingContextImpl, Secret}; + const OVERHEAD_SIZE: usize = 257; + + let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); + + let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); + assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); + + assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); + + let prover_key_index = 3; + let secret = Secret::from_seed(&[prover_key_index as u8; 32]); + let public = secret.public(); + assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE); + + let input = Input::new(b"foo").unwrap(); + let preout = secret.output(input); + assert_eq!(preout.compressed_size(), PREOUT_SERIALIZED_SIZE); + + let ring_keys: Vec<_> = (0..TEST_RING_SIZE) + .map(|i| Secret::from_seed(&[i as u8; 32]).public().0.into()) + .collect(); + + let verifier_key = ctx.verifier_key(&ring_keys[..]); + assert_eq!(verifier_key.compressed_size(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + + let prover_key = ctx.prover_key(&ring_keys); + let ring_prover = ctx.prover(prover_key, prover_key_index); + + { + use ark_ec_vrfs::ietf::Prover; + let proof = secret.prove(input, preout, &[]); + assert_eq!(proof.compressed_size(), SIGNATURE_SERIALIZED_SIZE); + } + + { + use ark_ec_vrfs::ring::Prover; + let proof = secret.prove(input, preout, &[], &ring_prover); + assert_eq!(proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE); + } + } + + #[test] + fn derive_works() { + let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap(); + let known = h2b("f706ea7ee4eef553428a768dbf3a1ede0b389a9f75867ade317a61cbb4efeb01"); + assert_eq!(pair.public().as_ref(), known); + + // Soft derivation not supported + let res = Pair::from_string(&format!("{}//Alice/Soft", DEV_PHRASE), None); + assert!(res.is_err()); + } + + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + } + #[test] fn sign_verify() { let pair = Pair::from_seed(TEST_SEED); let public = pair.public(); - let msg = b"hello"; - + let msg = b"foo"; let signature = pair.sign(msg); assert!(Pair::verify(&signature, msg, &public)); } + + #[test] + fn vrf_sign_verify() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + assert!(public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_sign_verify_with_bad_input() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + let data = VrfSignData::new(b"foo", b"bad"); + assert!(!public.vrf_verify(&data, &signature)); + let data = VrfSignData::new(b"bar", b"aux"); + assert!(!public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_output_bytes_match() { + let pair = Pair::from_seed(TEST_SEED); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + let o0 = pair.make_bytes(&data.vrf_input); + let o1 = signature.pre_output.make_bytes(); + assert_eq!(o0, o1); + } + + #[test] + fn vrf_signature_encode_decode() { + let pair = Pair::from_seed(TEST_SEED); + + let data = VrfSignData::new(b"data", b"aux"); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let expected_len = PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; + assert_eq!(bytes.len(), expected_len); + + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn ring_vrf_sign_verify() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + + let data = VrfSignData::new(b"data", b"aux"); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(signature.ring_vrf_verify(&data, &verifier)); + } + + #[test] + fn ring_vrf_sign_verify_with_out_of_ring_key() { + let ring_ctx = TestRingContext::new_testing(); + + let pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + let pair = Pair::from_seed(TEST_SEED); + + let data = VrfSignData::new(b"foo", b"aux"); + + // pair.public != pks[0] + let prover = ring_ctx.prover(&pks, 0).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(!signature.ring_vrf_verify(&data, &verifier)); + } + + #[test] + fn ring_vrf_make_bytes_matches() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let data = VrfSignData::new(b"data", b"aux"); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let o0 = pair.make_bytes(&data.vrf_input); + let o1 = signature.pre_output.make_bytes(); + assert_eq!(o0, o1); + } + + #[test] + fn ring_vrf_signature_encode_decode() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let data = VrfSignData::new(b"foo", b"aux"); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let expected = pair.ring_vrf_sign(&data, &prover); + + let bytes = expected.encode(); + assert_eq!(bytes.len(), RING_SIGNATURE_SERIALIZED_SIZE); + + let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn ring_vrf_context_encode_decode() { + let ctx1 = TestRingContext::new_testing(); + let enc1 = ctx1.encode(); + + assert_eq!(enc1.len(), ring_context_serialized_size(TEST_RING_SIZE)); + assert_eq!(enc1.len(), TestRingContext::max_encoded_len()); + + let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = ctx2.encode(); + + assert_eq!(enc1, enc2); + } + + #[test] + fn verifier_key_encode_decode() { + let ring_ctx = TestRingContext::new_testing(); + + let pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let verifier_key = ring_ctx.verifier_key(&pks).unwrap(); + let enc1 = verifier_key.encode(); + assert_eq!(enc1.len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + assert_eq!(RingVerifierKey::max_encoded_len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + + let vd2 = RingVerifierKey::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = vd2.encode(); + assert_eq!(enc1, enc2); + } } From 3cbdf7538ccc574ca5818d7222413b134c41fbba Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:12:47 +0100 Subject: [PATCH 04/29] Removed old bandersnatch_vrfs --- Cargo.lock | 337 +------ substrate/primitives/core/Cargo.toml | 7 +- substrate/primitives/core/src/bandersnatch.rs | 863 +++++++++-------- .../primitives/core/src/bandersnatch2.rs | 869 ------------------ substrate/primitives/core/src/lib.rs | 1 - 5 files changed, 483 insertions(+), 1594 deletions(-) delete mode 100644 substrate/primitives/core/src/bandersnatch2.rs diff --git a/Cargo.lock b/Cargo.lock index e7e8befddad76..40598b7b259ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -818,20 +818,6 @@ dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "ark-scale" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "parity-scale-codec", - "scale-info", -] - [[package]] name = "ark-scale" version = "0.0.12" @@ -846,21 +832,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "ark-secret-scalar" -version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "ark-transcript 0.0.2", - "digest 0.10.7", - "getrandom_or_panic", - "zeroize", -] - [[package]] name = "ark-serialize" version = "0.3.0" @@ -949,19 +920,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "ark-transcript" -version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" -dependencies = [ - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "digest 0.10.7", - "rand_core 0.6.4", - "sha3 0.10.8", -] - [[package]] name = "ark-transcript" version = "0.0.3" @@ -1880,27 +1838,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "bandersnatch_vrfs" -version = "0.0.4" -source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" -dependencies = [ - "ark-bls12-381 0.4.0", - "ark-ec 0.4.2", - "ark-ed-on-bls12-381-bandersnatch 0.4.0", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "dleq_vrf", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "ring 0.1.0", - "sha2 0.10.8", - "sp-ark-bls12-381", - "sp-ark-ed-on-bls12-381-bandersnatch", - "zeroize", -] - [[package]] name = "base-x" version = "0.2.11" @@ -3993,22 +3930,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "common" -version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "fflonk", - "getrandom_or_panic", - "merlin", - "rand_chacha 0.3.1", -] - [[package]] name = "common-path" version = "1.0.0" @@ -5457,7 +5378,7 @@ dependencies = [ "sp-io 30.0.0", "sp-maybe-compressed-blob 11.0.0", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -6405,22 +6326,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" -[[package]] -name = "dleq_vrf" -version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-scale 0.0.12", - "ark-secret-scalar", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "ark-transcript 0.0.2", - "arrayvec 0.7.4", - "zeroize", -] - [[package]] name = "dlmalloc" version = "0.2.4" @@ -7133,19 +7038,6 @@ dependencies = [ "subtle 2.5.0", ] -[[package]] -name = "fflonk" -version = "0.1.0" -source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "merlin", -] - [[package]] name = "fiat-crypto" version = "0.2.5" @@ -7728,7 +7620,7 @@ dependencies = [ "sp-statement-store 10.0.0", "sp-tracing 16.0.0", "tempfile", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -11082,15 +10974,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchers" version = "0.1.0" @@ -21959,23 +21842,6 @@ dependencies = [ "subtle 2.5.0", ] -[[package]] -name = "ring" -version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "arrayvec 0.7.4", - "blake2 0.10.6", - "common", - "fflonk", - "merlin", -] - [[package]] name = "ring" version = "0.16.20" @@ -23414,7 +23280,7 @@ dependencies = [ "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", "wat", ] @@ -24342,8 +24208,8 @@ dependencies = [ "sp-tracing 16.0.0", "thiserror 1.0.65", "tracing", - "tracing-log 0.2.0", - "tracing-subscriber 0.3.18", + "tracing-log", + "tracing-subscriber", ] [[package]] @@ -26448,24 +26314,6 @@ dependencies = [ "sp-arithmetic 23.0.0", ] -[[package]] -name = "sp-ark-bls12-381" -version = "0.4.2" -source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" -dependencies = [ - "ark-bls12-381-ext", - "sp-crypto-ec-utils 0.4.1", -] - -[[package]] -name = "sp-ark-ed-on-bls12-381-bandersnatch" -version = "0.4.2" -source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" -dependencies = [ - "ark-ed-on-bls12-381-bandersnatch-ext", - "sp-crypto-ec-utils 0.4.1", -] - [[package]] name = "sp-authority-discovery" version = "26.0.0" @@ -26752,7 +26600,6 @@ version = "28.0.0" dependencies = [ "ark-ec-vrfs", "array-bytes", - "bandersnatch_vrfs", "bitflags 1.3.2", "blake2 0.10.6", "bounded-collections", @@ -27016,27 +26863,6 @@ dependencies = [ "sp-crypto-hashing-proc-macro 0.1.0", ] -[[package]] -name = "sp-crypto-ec-utils" -version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "ark-bls12-377", - "ark-bls12-377-ext", - "ark-bls12-381 0.4.0", - "ark-bls12-381-ext", - "ark-bw6-761", - "ark-bw6-761-ext", - "ark-ec 0.4.2", - "ark-ed-on-bls12-377", - "ark-ed-on-bls12-377-ext", - "ark-ed-on-bls12-381-bandersnatch 0.4.0", - "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.11", - "sp-runtime-interface 17.0.0", - "sp-std 8.0.0", -] - [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" @@ -27052,7 +26878,7 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.12", + "ark-scale", "sp-runtime-interface 24.0.0", ] @@ -27073,7 +26899,7 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch 0.4.0", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.12", + "ark-scale", "sp-runtime-interface 28.0.0", ] @@ -27133,16 +26959,6 @@ dependencies = [ "parking_lot 0.12.3", ] -[[package]] -name = "sp-debug-derive" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.98", -] - [[package]] name = "sp-debug-derive" version = "14.0.0" @@ -27163,17 +26979,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "sp-externalities" -version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std 8.0.0", - "sp-storage 13.0.0", -] - [[package]] name = "sp-externalities" version = "0.25.0" @@ -27736,24 +27541,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "sp-runtime-interface" -version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types 0.12.2", - "sp-externalities 0.19.0", - "sp-runtime-interface-proc-macro 11.0.0", - "sp-std 8.0.0", - "sp-storage 13.0.0", - "sp-tracing 10.0.0", - "sp-wasm-interface 14.0.0", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "24.0.0" @@ -27838,18 +27625,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "sp-runtime-interface-proc-macro" -version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "Inflector", - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.98", -] - [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" @@ -28116,11 +27891,6 @@ dependencies = [ "x25519-dalek", ] -[[package]] -name = "sp-std" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" - [[package]] name = "sp-std" version = "14.0.0" @@ -28131,19 +27901,6 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" -[[package]] -name = "sp-storage" -version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "impl-serde 0.4.0", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive 8.0.0", - "sp-std 8.0.0", -] - [[package]] name = "sp-storage" version = "19.0.0" @@ -28217,18 +27974,6 @@ dependencies = [ "thiserror 1.0.65", ] -[[package]] -name = "sp-tracing" -version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "parity-scale-codec", - "sp-std 8.0.0", - "tracing", - "tracing-core", - "tracing-subscriber 0.2.25", -] - [[package]] name = "sp-tracing" version = "16.0.0" @@ -28236,7 +27981,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -28248,7 +27993,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -28471,19 +28216,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "sp-wasm-interface" -version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "anyhow", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "sp-std 8.0.0", - "wasmtime", -] - [[package]] name = "sp-wasm-interface" version = "20.0.0" @@ -30133,7 +29865,7 @@ checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger 0.11.3", "test-log-macros", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -30837,17 +30569,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -30859,38 +30580,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers 0.0.1", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log 0.1.3", - "tracing-serde", -] - [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -30898,7 +30587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "chrono", - "matchers 0.1.0", + "matchers", "nu-ansi-term", "once_cell", "parking_lot 0.12.3", @@ -30909,7 +30598,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -31421,7 +31110,7 @@ dependencies = [ "ark-poly 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "ark-transcript 0.0.3", + "ark-transcript", "w3f-pcs", "w3f-plonk-common", ] diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 1342c4abff2a6..b556798708604 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -70,9 +70,6 @@ secp256k1 = { features = [ # bls crypto w3f-bls = { optional = true, workspace = true } # bandersnatch crypto -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = [ - "substrate-curves", -], optional = true } ark-ec-vrfs = { optional = true, workspace = true, features = ["bandersnatch", "ring"] } [dev-dependencies] @@ -91,7 +88,7 @@ bench = false default = ["std", "bandersnatch-experimental"] std = [ - "bandersnatch_vrfs?/std", + "ark-ec-vrfs?/std", "bip39/rand", "bip39/std", "blake2/std", @@ -166,4 +163,4 @@ bls-experimental = ["w3f-bls"] # This feature adds Bandersnatch crypto primitives. # It should not be used in production since the implementation and interface may still # be subject to significant changes. -bandersnatch-experimental = ["bandersnatch_vrfs", "ark-ec-vrfs"] +bandersnatch-experimental = ["ark-ec-vrfs"] diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 7d098b3c3cd91..3aac5239f6002 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -26,12 +26,18 @@ use crate::crypto::{ ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, }; - -use bandersnatch_vrfs::{CanonicalSerialize, SecretKey}; +use ark_ec_vrfs::{ + prelude::{ + ark_ec::CurveGroup, + ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, + }, + suites::bandersnatch::te as bandersnatch, +}; +use bandersnatch::{BandersnatchSha512Ell2 as BandersnatchSuite, Secret}; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; /// Identifier used to match public keys against bandersnatch-vrf keys. pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); @@ -43,13 +49,13 @@ pub const SIGNING_CTX: &[u8] = b"BandersnatchSigningContext"; pub const SEED_SERIALIZED_SIZE: usize = 32; /// The byte length of serialized public key. -pub const PUBLIC_SERIALIZED_SIZE: usize = 33; +pub const PUBLIC_SERIALIZED_SIZE: usize = 32; /// The byte length of serialized signature. -pub const SIGNATURE_SERIALIZED_SIZE: usize = 65; +pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; /// The byte length of serialized pre-output. -pub const PREOUT_SERIALIZED_SIZE: usize = 33; +pub const PREOUT_SERIALIZED_SIZE: usize = 32; #[doc(hidden)] pub struct BandersnatchTag; @@ -63,7 +69,7 @@ impl CryptoType for Public { /// Bandersnatch signature. /// -/// The signature is created via the [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript +/// The signature is created via [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript /// `label`. pub type Signature = SignatureBytes; @@ -77,7 +83,7 @@ type Seed = [u8; SEED_SERIALIZED_SIZE]; /// Bandersnatch secret key. #[derive(Clone)] pub struct Pair { - secret: SecretKey, + secret: Secret, seed: Seed, } @@ -102,7 +108,7 @@ impl TraitPair for Pair { } let mut seed = [0; SEED_SERIALIZED_SIZE]; seed.copy_from_slice(seed_slice); - let secret = SecretKey::from_seed(&seed); + let secret = Secret::from_seed(&seed); Ok(Pair { secret, seed }) } @@ -130,7 +136,7 @@ impl TraitPair for Pair { } fn public(&self) -> Public { - let public = self.secret.to_public(); + let public = self.secret.public(); let mut raw = [0; PUBLIC_SERIALIZED_SIZE]; public .serialize_compressed(raw.as_mut_slice()) @@ -138,23 +144,41 @@ impl TraitPair for Pair { Public::unchecked_from(raw) } - /// Sign a message. - /// - /// In practice this produce a Schnorr signature of a transcript composed by - /// the constant label [`SIGNING_CTX`] and `data` without any additional data. - /// - /// See [`vrf::VrfSignData`] for additional details. #[cfg(feature = "full_crypto")] fn sign(&self, data: &[u8]) -> Signature { - let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data], None); - self.vrf_sign(&data).signature + use ark_ec_vrfs::Suite; + use bandersnatch::BandersnatchSha512Ell2; + let input = bandersnatch::Input::new(data).unwrap(); + let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); + let gk = BandersnatchSha512Ell2::generator() * k; + let c = BandersnatchSha512Ell2::challenge( + &[&gk.into_affine(), &self.secret.public.0, &input.0], + &[], + ); + let s = k + c * self.secret.scalar; + let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; + bandersnatch::IetfProof { c, s } + .serialize_compressed(&mut raw_signature.as_mut_slice()) + .unwrap(); + Signature::from_raw(raw_signature) } fn verify>(signature: &Signature, data: M, public: &Public) -> bool { - let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data.as_ref()], None); - let signature = - vrf::VrfSignature { signature: *signature, pre_outputs: vrf::VrfIosVec::default() }; - public.vrf_verify(&data, &signature) + use ark_ec_vrfs::Suite; + use bandersnatch::BandersnatchSha512Ell2; + let Ok(signature) = bandersnatch::IetfProof::deserialize_compressed(&signature.0[..]) + else { + return false + }; + let Ok(public) = bandersnatch::Public::deserialize_compressed(&public.0[..]) else { + return false + }; + let input = bandersnatch::Input::new(data.as_ref()).expect("Can't fail"); + let gs = BandersnatchSha512Ell2::generator() * signature.s; + let yc = public.0 * signature.c; + let rv = gs - yc; + let cv = BandersnatchSha512Ell2::challenge(&[&rv.into_affine(), &public.0, &input.0], &[]); + signature.c == cv } /// Return a vector filled with the seed (32 bytes). @@ -170,32 +194,21 @@ impl CryptoType for Pair { /// Bandersnatch VRF types and operations. pub mod vrf { use super::*; - use crate::{bounded::BoundedVec, crypto::VrfCrypto, ConstU32}; - use bandersnatch_vrfs::{ - CanonicalDeserialize, CanonicalSerialize, IntoVrfInput, Message, PublicKey, - ThinVrfSignature, Transcript, - }; + use crate::crypto::VrfCrypto; + use ark_ec_vrfs::ietf::{Prover, Verifier}; - /// Max number of inputs/pre-outputs which can be handled by the VRF signing procedures. - /// - /// The number is quite arbitrary and chosen to fulfill the use cases found so far. - /// If required it can be extended in the future. - pub const MAX_VRF_IOS: u32 = 3; - - /// Bounded vector used for VRF inputs and pre-outputs. - /// - /// Can contain at most [`MAX_VRF_IOS`] elements. - pub type VrfIosVec = BoundedVec>; + /// [`VrfSignature`] serialized size. + pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize = + PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; /// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`]. #[derive(Clone, Debug)] - pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); + pub struct VrfInput(pub(super) bandersnatch::Input); impl VrfInput { /// Construct a new VRF input. - pub fn new(domain: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> Self { - let msg = Message { domain: domain.as_ref(), message: data.as_ref() }; - VrfInput(msg.into_vrf_input()) + pub fn new(data: impl AsRef<[u8]>) -> Self { + Self(bandersnatch::Input::new(data.as_ref()).expect("Can't fail")) } } @@ -204,8 +217,16 @@ pub mod vrf { /// This object is used to produce an arbitrary number of verifiable pseudo random /// bytes and is often called pre-output to emphasize that this is not the actual /// output of the VRF but an object capable of generating the output. - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct VrfPreOutput(pub(super) bandersnatch_vrfs::VrfPreOut); + #[derive(Clone, Debug)] + pub struct VrfPreOutput(pub(super) bandersnatch::Output); + + // Workaround until traits are not implemented for newtypes https://github.com/davxy/ark-ec-vrfs/issues/41 + impl PartialEq for VrfPreOutput { + fn eq(&self, other: &Self) -> bool { + self.0 .0 == other.0 .0 + } + } + impl Eq for VrfPreOutput {} impl Encode for VrfPreOutput { fn encode(&self) -> Vec { @@ -220,22 +241,17 @@ pub mod vrf { impl Decode for VrfPreOutput { fn decode(i: &mut R) -> Result { let buf = <[u8; PREOUT_SERIALIZED_SIZE]>::decode(i)?; - let preout = - bandersnatch_vrfs::VrfPreOut::deserialize_compressed_unchecked(buf.as_slice()) - .map_err(|_| "vrf-preout decode error: bad preout")?; + let preout = bandersnatch::Output::deserialize_compressed_unchecked(buf.as_slice()) + .map_err(|_| "vrf-preout decode error: bad preout")?; Ok(VrfPreOutput(preout)) } } // `VrfPreOutput` resolves to: // ``` - // pub struct Affine { - // #[doc(hidden)] + // pub struct Affine { // pub x: P::BaseField, - // #[doc(hidden)] // pub y: P::BaseField, - // #[doc(hidden)] - // pub infinity: bool, // } // ``` // where each `P::BaseField` contains a `pub struct BigInt(pub [u64; N]);` @@ -261,101 +277,37 @@ pub mod vrf { /// Data to be signed via one of the two provided vrf flavors. /// - /// The object contains a transcript and a sequence of [`VrfInput`]s ready to be signed. - /// - /// The `transcript` summarizes a set of messages which are defining a particular - /// protocol by automating the Fiat-Shamir transform for challenge generation. - /// A good explanation of the topic can be found in Merlin [docs](https://merlin.cool/) + /// The object contains the VRF input and additional data to be signed together + /// with the VRF input. Additional data doesn't influence the VRF output. /// - /// The `inputs` is a sequence of [`VrfInput`]s which, during the signing procedure, are - /// first transformed to [`VrfPreOutput`]s. Both inputs and pre-outputs are then appended to - /// the transcript before signing the Fiat-Shamir transform result (the challenge). - /// - /// In practice, as a user, all these technical details can be easily ignored. - /// What is important to remember is: - /// - *Transcript* is an object defining the protocol and used to produce the signature. This - /// object doesn't influence the `VrfPreOutput`s values. - /// - *Vrf inputs* is some additional data which is used to produce *vrf pre-outputs*. This data - /// will contribute to the signature as well. + /// The `input` is a [`VrfInput`]s which, during the signing procedure, is first mapped + /// to a [`VrfPreOutput`]. #[derive(Clone)] pub struct VrfSignData { - /// Associated protocol transcript. - pub transcript: Transcript, - /// VRF inputs to be signed. - pub inputs: VrfIosVec, + /// VRF input. + pub vrf_input: VrfInput, + /// Additional data. + pub aux_data: Vec, } impl VrfSignData { /// Construct a new data to be signed. - /// - /// Fails if the `inputs` iterator yields more elements than [`MAX_VRF_IOS`] - /// - /// Refer to [`VrfSignData`] for details about transcript and inputs. - pub fn new( - transcript_label: &'static [u8], - transcript_data: impl IntoIterator>, - inputs: impl IntoIterator, - ) -> Result { - let inputs: Vec = inputs.into_iter().collect(); - if inputs.len() > MAX_VRF_IOS as usize { - return Err(()) - } - Ok(Self::new_unchecked(transcript_label, transcript_data, inputs)) - } - - /// Construct a new data to be signed. - /// - /// At most the first [`MAX_VRF_IOS`] elements of `inputs` are used. - /// - /// Refer to [`VrfSignData`] for details about transcript and inputs. - pub fn new_unchecked( - transcript_label: &'static [u8], - transcript_data: impl IntoIterator>, - inputs: impl IntoIterator, - ) -> Self { - let inputs: Vec = inputs.into_iter().collect(); - let inputs = VrfIosVec::truncate_from(inputs); - let mut transcript = Transcript::new_labeled(transcript_label); - transcript_data.into_iter().for_each(|data| transcript.append(data.as_ref())); - VrfSignData { transcript, inputs } - } - - /// Append a message to the transcript. - pub fn push_transcript_data(&mut self, data: &[u8]) { - self.transcript.append(data); - } - - /// Tries to append a [`VrfInput`] to the vrf inputs list. - /// - /// On failure, returns back the [`VrfInput`] parameter. - pub fn push_vrf_input(&mut self, input: VrfInput) -> Result<(), VrfInput> { - self.inputs.try_push(input) - } - - /// Get the challenge associated to the `transcript` contained within the signing data. - /// - /// Ignores the vrf inputs and outputs. - pub fn challenge(&self) -> [u8; N] { - let mut output = [0; N]; - let mut transcript = self.transcript.clone(); - let mut reader = transcript.challenge(b"bandersnatch challenge"); - reader.read_bytes(&mut output); - output + pub fn new(vrf_input_data: &[u8], aux_data: &[u8]) -> Self { + Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_owned() } } } /// VRF signature. /// - /// Includes both the transcript `signature` and the `pre-outputs` generated from the - /// [`VrfSignData::inputs`]. + /// Includes both the VRF proof and the pre-output generated from the [`VrfSignData::input`]. /// /// Refer to [`VrfSignData`] for more details. #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct VrfSignature { - /// Transcript signature. - pub signature: Signature, - /// VRF pre-outputs. - pub pre_outputs: VrfIosVec, + /// VRF pre-output. + pub pre_output: VrfPreOutput, + /// VRF proof. + pub proof: Signature, } #[cfg(feature = "full_crypto")] @@ -368,21 +320,20 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { - fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { - const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - // Workaround to overcome backend signature generic over the number of IOs. - match data.inputs.len() { - 0 => self.vrf_sign_gen::<0>(data), - 1 => self.vrf_sign_gen::<1>(data), - 2 => self.vrf_sign_gen::<2>(data), - 3 => self.vrf_sign_gen::<3>(data), - _ => unreachable!(), - } + fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature { + let pre_output_impl = self.secret.output(data.vrf_input.0); + let pre_output = VrfPreOutput(pre_output_impl); + let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); + let mut proof = Signature::default(); + proof_impl + .serialize_compressed(proof.0.as_mut_slice()) + .expect("serialization length is constant and checked by test; qed"); + VrfSignature { pre_output, proof } } fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput { - let pre_output = self.secret.vrf_preout(&input.0); - VrfPreOutput(pre_output) + let pre_output_impl = self.secret.output(input.0); + VrfPreOutput(pre_output_impl) } } @@ -394,97 +345,37 @@ pub mod vrf { } impl VrfPublic for Public { - fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { - const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - let pre_outputs_len = signature.pre_outputs.len(); - if pre_outputs_len != data.inputs.len() { + fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool { + let Ok(public) = + bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) + else { return false - } - // Workaround to overcome backend signature generic over the number of IOs. - match pre_outputs_len { - 0 => self.vrf_verify_gen::<0>(data, signature), - 1 => self.vrf_verify_gen::<1>(data, signature), - 2 => self.vrf_verify_gen::<2>(data, signature), - 3 => self.vrf_verify_gen::<3>(data, signature), - _ => unreachable!(), - } + }; + let Ok(proof) = ark_ec_vrfs::ietf::Proof::deserialize_compressed_unchecked( + signature.proof.as_slice(), + ) else { + return false + }; + public + .verify(data.vrf_input.0, signature.pre_output.0, &data.aux_data, &proof) + .is_ok() } } #[cfg(feature = "full_crypto")] impl Pair { - fn vrf_sign_gen(&self, data: &VrfSignData) -> VrfSignature { - let ios = core::array::from_fn(|i| self.secret.vrf_inout(data.inputs[i].0)); - - let thin_signature: ThinVrfSignature = - self.secret.sign_thin_vrf(data.transcript.clone(), &ios); - - let pre_outputs: Vec<_> = - thin_signature.preouts.into_iter().map(VrfPreOutput).collect(); - let pre_outputs = VrfIosVec::truncate_from(pre_outputs); - - let mut signature = VrfSignature { signature: Signature::default(), pre_outputs }; - - thin_signature - .proof - .serialize_compressed(signature.signature.0.as_mut_slice()) - .expect("serialization length is constant and checked by test; qed"); - - signature - } - - /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. - pub fn make_bytes( - &self, - context: &'static [u8], - input: &VrfInput, - ) -> [u8; N] { - let transcript = Transcript::new_labeled(context); - let inout = self.secret.vrf_inout(input.0); - inout.vrf_output_bytes(transcript) - } - } - - impl Public { - fn vrf_verify_gen( - &self, - data: &VrfSignData, - signature: &VrfSignature, - ) -> bool { - let Ok(public) = PublicKey::deserialize_compressed_unchecked(self.as_slice()) else { - return false - }; - - let preouts: [bandersnatch_vrfs::VrfPreOut; N] = - core::array::from_fn(|i| signature.pre_outputs[i].0); - - // Deserialize only the proof, the rest has already been deserialized - // This is another hack used because backend signature type is generic over - // the number of ios. - let Ok(proof) = ThinVrfSignature::<0>::deserialize_compressed_unchecked( - signature.signature.as_slice(), - ) - .map(|s| s.proof) else { - return false - }; - let signature = ThinVrfSignature { proof, preouts }; - - let inputs = data.inputs.iter().map(|i| i.0); - - public.verify_thin_vrf(data.transcript.clone(), inputs, &signature).is_ok() + /// Generate VRF output bytes for the given `input`. + pub fn make_bytes(&self, input: &VrfInput) -> [u8; 32] { + self.vrf_pre_output(input).make_bytes() } } impl VrfPreOutput { - /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. - pub fn make_bytes( - &self, - context: &'static [u8], - input: &VrfInput, - ) -> [u8; N] { - let transcript = Transcript::new_labeled(context); - let inout = bandersnatch_vrfs::VrfInOut { input: input.0, preoutput: self.0 }; - inout.vrf_output_bytes(transcript) + /// Generate VRF output bytes. + pub fn make_bytes(&self) -> [u8; 32] { + let mut bytes = [0_u8; 32]; + bytes.copy_from_slice(&self.0.hash()[..32]); + bytes } } } @@ -492,80 +383,66 @@ pub mod vrf { /// Bandersnatch Ring-VRF types and operations. pub mod ring_vrf { use super::{vrf::*, *}; - pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; - use bandersnatch_vrfs::{ring::VerifierKey, CanonicalDeserialize, PublicKey}; - - /// Overhead in the domain size with respect to the supported ring size. - /// - /// Some bits of the domain are reserved for the zk-proof to work. - pub const RING_DOMAIN_OVERHEAD: u32 = 257; + use ark_ec_vrfs::ring::{Prover, Verifier}; + use bandersnatch::{RingContext as RingContextImpl, VerifierKey as VerifierKeyImpl}; + pub use bandersnatch::{RingProver, RingVerifier}; // Max size of serialized ring-vrf context given `domain_len`. - pub(crate) const fn ring_context_serialized_size(domain_len: u32) -> usize { + // TODO @davxy: test this + pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize { // const G1_POINT_COMPRESSED_SIZE: usize = 48; // const G2_POINT_COMPRESSED_SIZE: usize = 96; const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; - const OVERHEAD_SIZE: usize = 20; + const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let g1_points_num = 3 * domain_len as usize + 1; - + let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); + let g1_points_num = 3 * domain_size as usize + 1; OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE } - pub(crate) const RING_VERIFIER_DATA_SERIALIZED_SIZE: usize = 388; - pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755; + /// [`RingVerifierKey`] serialized size. + pub const RING_VERIFIER_KEY_SERIALIZED_SIZE: usize = 384; + /// [`RingProof`] serialized size. + pub(crate) const RING_PROOF_SERIALIZED_SIZE: usize = 752; + /// [`RingVrrfSignature`] serialized size. + pub const RING_SIGNATURE_SERIALIZED_SIZE: usize = + RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE; - /// remove as soon as soon as serialization is implemented by the backend - pub struct RingVerifierData { - /// Domain size. - pub domain_size: u32, - /// Verifier key. - pub verifier_key: VerifierKey, - } - - impl From for RingVerifier { - fn from(vd: RingVerifierData) -> RingVerifier { - bandersnatch_vrfs::ring::make_ring_verifier(vd.verifier_key, vd.domain_size as usize) - } - } + /// Ring verifier key + pub struct RingVerifierKey(VerifierKeyImpl); - impl Encode for RingVerifierData { + impl Encode for RingVerifierKey { fn encode(&self) -> Vec { const ERR_STR: &str = "serialization length is constant and checked by test; qed"; - let mut buf = [0; RING_VERIFIER_DATA_SERIALIZED_SIZE]; - self.domain_size.serialize_compressed(&mut buf[..4]).expect(ERR_STR); - self.verifier_key.serialize_compressed(&mut buf[4..]).expect(ERR_STR); - buf.encode() + let mut buf = Vec::with_capacity(RING_VERIFIER_KEY_SERIALIZED_SIZE); + self.0.serialize_compressed(&mut buf).expect(ERR_STR); + buf } } - impl Decode for RingVerifierData { - fn decode(i: &mut R) -> Result { - const ERR_STR: &str = "serialization length is constant and checked by test; qed"; - let buf = <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::decode(i)?; - let domain_size = - ::deserialize_compressed_unchecked(&mut &buf[..4]) - .expect(ERR_STR); - let verifier_key = ::deserialize_compressed_unchecked(&mut &buf[4..]).expect(ERR_STR); - - Ok(RingVerifierData { domain_size, verifier_key }) + impl Decode for RingVerifierKey { + fn decode(input: &mut R) -> Result { + let mut buf = vec![0; RING_VERIFIER_KEY_SERIALIZED_SIZE]; + input.read(&mut buf[..])?; + let vk = VerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice()) + .map_err(|_| "RingVerifierKey decode error")?; + Ok(RingVerifierKey(vk)) } } - impl EncodeLike for RingVerifierData {} + impl EncodeLike for RingVerifierKey {} - impl MaxEncodedLen for RingVerifierData { + impl MaxEncodedLen for RingVerifierKey { fn max_encoded_len() -> usize { - <[u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]>::max_encoded_len() + RING_VERIFIER_KEY_SERIALIZED_SIZE } } - impl TypeInfo for RingVerifierData { - type Identity = [u8; RING_VERIFIER_DATA_SERIALIZED_SIZE]; - + impl TypeInfo for RingVerifierKey { + type Identity = [u8; RING_VERIFIER_KEY_SERIALIZED_SIZE]; fn type_info() -> scale_info::Type { Self::Identity::type_info() } @@ -573,98 +450,95 @@ pub mod ring_vrf { /// Context used to construct ring prover and verifier. /// - /// Generic parameter `D` represents the ring domain size and drives - /// the max number of supported ring members [`RingContext::max_keyset_size`] - /// which is equal to `D - RING_DOMAIN_OVERHEAD`. + /// Generic parameter `R` represents the ring size. #[derive(Clone)] - pub struct RingContext(KZG); + pub struct RingContext(RingContextImpl); - impl RingContext { + impl RingContext { /// Build an dummy instance for testing purposes. pub fn new_testing() -> Self { - Self(KZG::testing_kzg_setup([0; 32], D)) + Self(RingContextImpl::from_seed(R, [0; 32])) } /// Get the keyset max size. pub fn max_keyset_size(&self) -> usize { - self.0.max_keyset_size() + self.0.max_ring_size() } /// Get ring prover for the key at index `public_idx` in the `public_keys` set. pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { - let mut pks = Vec::with_capacity(public_keys.len()); - for public_key in public_keys { - let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; - pks.push(pk.0.into()); - } - - let prover_key = self.0.prover_key(pks); - let ring_prover = self.0.init_ring_prover(prover_key, public_idx); - Some(ring_prover) + let pks = Self::make_ring_vector(public_keys)?; + let prover_key = self.0.prover_key(&pks); + Some(self.0.prover(prover_key, public_idx)) } /// Get ring verifier for the `public_keys` set. pub fn verifier(&self, public_keys: &[Public]) -> Option { - let mut pks = Vec::with_capacity(public_keys.len()); - for public_key in public_keys { - let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; - pks.push(pk.0.into()); - } + self.verifier_key(public_keys).map(|vk| self.0.verifier(vk.0)) + } - let verifier_key = self.0.verifier_key(pks); - let ring_verifier = self.0.init_ring_verifier(verifier_key); - Some(ring_verifier) + /// Build ring commitment for `RingVerifier` lazy construction. + pub fn verifier_key(&self, public_keys: &[Public]) -> Option { + let pks = Self::make_ring_vector(public_keys)?; + Some(RingVerifierKey(self.0.verifier_key(&pks))) } - /// Information required for a lazy construction of a ring verifier. - pub fn verifier_data(&self, public_keys: &[Public]) -> Option { - let mut pks = Vec::with_capacity(public_keys.len()); - for public_key in public_keys { - let pk = PublicKey::deserialize_compressed_unchecked(public_key.as_slice()).ok()?; - pks.push(pk.0.into()); + /// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance. + /// + /// While this approach is computationally slightly less efficient than using a + /// pre-constructed `RingContext`, as some parameters need to be computed on-the-fly, it + /// is beneficial in memory or storage constrained environments. This avoids the need to + /// retain the full `RingContext` for ring signature verification. Instead, the + /// `VerifierKey` contains only the essential information needed to verify ring proofs. + pub fn verifier_no_context(verifier_key: RingVerifierKey) -> RingVerifier { + RingContextImpl::verifier_no_context(verifier_key.0, R) + } + + fn make_ring_vector(public_keys: &[Public]) -> Option> { + use bandersnatch::Public as PublicImpl; + let mut pts = Vec::with_capacity(public_keys.len()); + for pk in public_keys { + let pk = PublicImpl::deserialize_compressed_unchecked(pk.as_slice()).ok()?; + pts.push(pk.0); } - Some(RingVerifierData { - verifier_key: self.0.verifier_key(pks), - domain_size: self.0.domain_size, - }) + Some(pts) } } - impl Encode for RingContext { + impl Encode for RingContext { fn encode(&self) -> Vec { - let mut buf = vec![0; ring_context_serialized_size(D)]; + let mut buf = Vec::with_capacity(ring_context_serialized_size(R)); self.0 - .serialize_uncompressed(buf.as_mut_slice()) + .serialize_uncompressed(&mut buf) .expect("serialization length is constant and checked by test; qed"); buf } } - impl Decode for RingContext { - fn decode(input: &mut R) -> Result { - let mut buf = vec![0; ring_context_serialized_size(D)]; + impl Decode for RingContext { + fn decode(input: &mut I) -> Result { + let mut buf = vec![0; ring_context_serialized_size(R)]; input.read(&mut buf[..])?; - let kzg = KZG::deserialize_uncompressed_unchecked(buf.as_slice()) - .map_err(|_| "KZG decode error")?; - Ok(RingContext(kzg)) + let ctx = RingContextImpl::deserialize_uncompressed_unchecked(buf.as_slice()) + .map_err(|_| "RingContext decode error")?; + Ok(RingContext(ctx)) } } - impl EncodeLike for RingContext {} + impl EncodeLike for RingContext {} - impl MaxEncodedLen for RingContext { + impl MaxEncodedLen for RingContext { fn max_encoded_len() -> usize { - ring_context_serialized_size(D) + ring_context_serialized_size(R) } } - impl TypeInfo for RingContext { + impl TypeInfo for RingContext { type Identity = Self; - fn type_info() -> scale_info::Type { let path = scale_info::Path::new("RingContext", module_path!()); let array_type_def = scale_info::TypeDefArray { - len: ring_context_serialized_size(D) as u32, + len: ring_context_serialized_size(R) as u32, type_param: scale_info::MetaType::new::(), }; let type_def = scale_info::TypeDef::Array(array_type_def); @@ -677,10 +551,10 @@ pub mod ring_vrf { Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, )] pub struct RingVrfSignature { + /// VRF pre-output. + pub pre_output: VrfPreOutput, /// Ring signature. - pub signature: [u8; RING_SIGNATURE_SERIALIZED_SIZE], - /// VRF pre-outputs. - pub pre_outputs: VrfIosVec, + pub proof: [u8; RING_PROOF_SERIALIZED_SIZE], } #[cfg(feature = "full_crypto")] @@ -691,41 +565,15 @@ pub mod ring_vrf { /// signing [`Pair`] is part of the ring from which the [`RingProver`] has /// been constructed. If not, the produced signature is just useless. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { - const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - // Workaround to overcome backend signature generic over the number of IOs. - match data.inputs.len() { - 0 => self.ring_vrf_sign_gen::<0>(data, prover), - 1 => self.ring_vrf_sign_gen::<1>(data, prover), - 2 => self.ring_vrf_sign_gen::<2>(data, prover), - 3 => self.ring_vrf_sign_gen::<3>(data, prover), - _ => unreachable!(), - } - } - - fn ring_vrf_sign_gen( - &self, - data: &VrfSignData, - prover: &RingProver, - ) -> RingVrfSignature { - let ios = core::array::from_fn(|i| self.secret.vrf_inout(data.inputs[i].0)); - - let ring_signature: bandersnatch_vrfs::RingVrfSignature = - bandersnatch_vrfs::RingProver { ring_prover: prover, secret: &self.secret } - .sign_ring_vrf(data.transcript.clone(), &ios); - - let pre_outputs: Vec<_> = - ring_signature.preouts.into_iter().map(VrfPreOutput).collect(); - let pre_outputs = VrfIosVec::truncate_from(pre_outputs); - - let mut signature = - RingVrfSignature { pre_outputs, signature: [0; RING_SIGNATURE_SERIALIZED_SIZE] }; - - ring_signature - .proof - .serialize_compressed(signature.signature.as_mut_slice()) + let pre_output_impl = self.secret.output(data.vrf_input.0); + let pre_output = VrfPreOutput(pre_output_impl); + let proof_impl = + self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data, prover); + let mut proof = [0; RING_PROOF_SERIALIZED_SIZE]; + proof_impl + .serialize_compressed(proof.as_mut_slice()) .expect("serialization length is constant and checked by test; qed"); - - signature + RingVrfSignature { pre_output, proof } } } @@ -735,45 +583,19 @@ pub mod ring_vrf { /// The signature is verifiable if it has been produced by a member of the ring /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { - const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - let preouts_len = self.pre_outputs.len(); - if preouts_len != data.inputs.len() { - return false - } - // Workaround to overcome backend signature generic over the number of IOs. - match preouts_len { - 0 => self.ring_vrf_verify_gen::<0>(data, verifier), - 1 => self.ring_vrf_verify_gen::<1>(data, verifier), - 2 => self.ring_vrf_verify_gen::<2>(data, verifier), - 3 => self.ring_vrf_verify_gen::<3>(data, verifier), - _ => unreachable!(), - } - } - - fn ring_vrf_verify_gen( - &self, - data: &VrfSignData, - verifier: &RingVerifier, - ) -> bool { - let Ok(vrf_signature) = - bandersnatch_vrfs::RingVrfSignature::<0>::deserialize_compressed_unchecked( - self.signature.as_slice(), - ) + let Ok(proof) = + bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) else { return false }; - - let preouts: [bandersnatch_vrfs::VrfPreOut; N] = - core::array::from_fn(|i| self.pre_outputs[i].0); - - let signature = - bandersnatch_vrfs::RingVrfSignature { proof: vrf_signature.proof, preouts }; - - let inputs = data.inputs.iter().map(|i| i.0); - - bandersnatch_vrfs::RingVerifier(verifier) - .verify_ring_vrf(data.transcript.clone(), inputs, &signature) - .is_ok() + bandersnatch::Public::verify( + data.vrf_input.0, + self.pre_output.0, + &data.aux_data, + &proof, + verifier, + ) + .is_ok() } } } @@ -781,14 +603,265 @@ pub mod ring_vrf { #[cfg(test)] mod tests { use super::{ring_vrf::*, vrf::*, *}; + use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; - const DEV_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; - const TEST_DOMAIN_SIZE: u32 = 1024; + const TEST_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; + const TEST_RING_SIZE: usize = 16; - type TestRingContext = RingContext; + type TestRingContext = RingContext; #[allow(unused)] fn b2h(bytes: &[u8]) -> String { array_bytes::bytes2hex("", bytes) } + + fn h2b(hex: &str) -> Vec { + array_bytes::hex2bytes_unchecked(hex) + } + + #[test] + fn backend_assumptions_sanity_check() { + use bandersnatch::{Input, RingContext as RingContextImpl, Secret}; + const OVERHEAD_SIZE: usize = 257; + + let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); + + let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); + assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); + + assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); + + let prover_key_index = 3; + let secret = Secret::from_seed(&[prover_key_index as u8; 32]); + let public = secret.public(); + assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE); + + let input = Input::new(b"foo").unwrap(); + let preout = secret.output(input); + assert_eq!(preout.compressed_size(), PREOUT_SERIALIZED_SIZE); + + let ring_keys: Vec<_> = (0..TEST_RING_SIZE) + .map(|i| Secret::from_seed(&[i as u8; 32]).public().0.into()) + .collect(); + + let verifier_key = ctx.verifier_key(&ring_keys[..]); + assert_eq!(verifier_key.compressed_size(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + + let prover_key = ctx.prover_key(&ring_keys); + let ring_prover = ctx.prover(prover_key, prover_key_index); + + { + use ark_ec_vrfs::ietf::Prover; + let proof = secret.prove(input, preout, &[]); + assert_eq!(proof.compressed_size(), SIGNATURE_SERIALIZED_SIZE); + } + + { + use ark_ec_vrfs::ring::Prover; + let proof = secret.prove(input, preout, &[], &ring_prover); + assert_eq!(proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE); + } + } + + #[test] + fn derive_works() { + let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap(); + let known = h2b("f706ea7ee4eef553428a768dbf3a1ede0b389a9f75867ade317a61cbb4efeb01"); + assert_eq!(pair.public().as_ref(), known); + + // Soft derivation not supported + let res = Pair::from_string(&format!("{}//Alice/Soft", DEV_PHRASE), None); + assert!(res.is_err()); + } + + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + } + + #[test] + fn sign_verify() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let msg = b"foo"; + let signature = pair.sign(msg); + assert!(Pair::verify(&signature, msg, &public)); + } + + #[test] + fn vrf_sign_verify() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + assert!(public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_sign_verify_with_bad_input() { + let pair = Pair::from_seed(TEST_SEED); + let public = pair.public(); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + let data = VrfSignData::new(b"foo", b"bad"); + assert!(!public.vrf_verify(&data, &signature)); + let data = VrfSignData::new(b"bar", b"aux"); + assert!(!public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_output_bytes_match() { + let pair = Pair::from_seed(TEST_SEED); + let data = VrfSignData::new(b"foo", b"aux"); + let signature = pair.vrf_sign(&data); + let o0 = pair.make_bytes(&data.vrf_input); + let o1 = signature.pre_output.make_bytes(); + assert_eq!(o0, o1); + } + + #[test] + fn vrf_signature_encode_decode() { + let pair = Pair::from_seed(TEST_SEED); + + let data = VrfSignData::new(b"data", b"aux"); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let expected_len = PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; + assert_eq!(bytes.len(), expected_len); + + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn ring_vrf_sign_verify() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + + let data = VrfSignData::new(b"data", b"aux"); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(signature.ring_vrf_verify(&data, &verifier)); + } + + #[test] + fn ring_vrf_sign_verify_with_out_of_ring_key() { + let ring_ctx = TestRingContext::new_testing(); + + let pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + let pair = Pair::from_seed(TEST_SEED); + + let data = VrfSignData::new(b"foo", b"aux"); + + // pair.public != pks[0] + let prover = ring_ctx.prover(&pks, 0).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(!signature.ring_vrf_verify(&data, &verifier)); + } + + #[test] + fn ring_vrf_make_bytes_matches() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let data = VrfSignData::new(b"data", b"aux"); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let o0 = pair.make_bytes(&data.vrf_input); + let o1 = signature.pre_output.make_bytes(); + assert_eq!(o0, o1); + } + + #[test] + fn ring_vrf_signature_encode_decode() { + let ring_ctx = TestRingContext::new_testing(); + + let mut pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(TEST_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let data = VrfSignData::new(b"foo", b"aux"); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let expected = pair.ring_vrf_sign(&data, &prover); + + let bytes = expected.encode(); + assert_eq!(bytes.len(), RING_SIGNATURE_SERIALIZED_SIZE); + + let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn ring_vrf_context_encode_decode() { + let ctx1 = TestRingContext::new_testing(); + let enc1 = ctx1.encode(); + + assert_eq!(enc1.len(), ring_context_serialized_size(TEST_RING_SIZE)); + assert_eq!(enc1.len(), TestRingContext::max_encoded_len()); + + let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = ctx2.encode(); + + assert_eq!(enc1, enc2); + } + + #[test] + fn verifier_key_encode_decode() { + let ring_ctx = TestRingContext::new_testing(); + + let pks: Vec<_> = + (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let verifier_key = ring_ctx.verifier_key(&pks).unwrap(); + let enc1 = verifier_key.encode(); + assert_eq!(enc1.len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + assert_eq!(RingVerifierKey::max_encoded_len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); + + let vd2 = RingVerifierKey::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = vd2.encode(); + assert_eq!(enc1, enc2); + } } diff --git a/substrate/primitives/core/src/bandersnatch2.rs b/substrate/primitives/core/src/bandersnatch2.rs deleted file mode 100644 index d5fe01f50e5a2..0000000000000 --- a/substrate/primitives/core/src/bandersnatch2.rs +++ /dev/null @@ -1,869 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch), -//! an elliptic curve built over BLS12-381 scalar field. -//! -//! The primitive can operate both as a regular VRF or as an anonymized Ring VRF. - -#[cfg(feature = "full_crypto")] -use crate::crypto::VrfSecret; -use crate::crypto::{ - ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, - PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, -}; -use ark_ec_vrfs::{ - prelude::{ - ark_ec::CurveGroup, - ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, - }, - ring::RingSuite, - suites::bandersnatch::te as bandersnatch, -}; -use bandersnatch::{BandersnatchSha512Ell2 as BandersnatchSuite, Secret}; -use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; -use scale_info::TypeInfo; - -use alloc::vec::Vec; - -/// Identifier used to match public keys against bandersnatch-vrf keys. -pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); - -/// Context used to produce a plain signature without any VRF input/output. -pub const SIGNING_CTX: &[u8] = b"BandersnatchSigningContext"; - -/// The byte length of secret key seed. -pub const SEED_SERIALIZED_SIZE: usize = 32; - -/// The byte length of serialized public key. -pub const PUBLIC_SERIALIZED_SIZE: usize = 32; - -/// The byte length of serialized signature. -pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; - -/// The byte length of serialized pre-output. -pub const PREOUT_SERIALIZED_SIZE: usize = 32; - -#[doc(hidden)] -pub struct BandersnatchTag; - -/// Bandersnatch public key. -pub type Public = PublicBytes; - -impl CryptoType for Public { - type Pair = Pair; -} - -/// Bandersnatch signature. -/// -/// The signature is created via [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript -/// `label`. -pub type Signature = SignatureBytes; - -impl CryptoType for Signature { - type Pair = Pair; -} - -/// The raw secret seed, which can be used to reconstruct the secret [`Pair`]. -type Seed = [u8; SEED_SERIALIZED_SIZE]; - -/// Bandersnatch secret key. -#[derive(Clone)] -pub struct Pair { - secret: Secret, - seed: Seed, -} - -impl Pair { - /// Get the key seed. - pub fn seed(&self) -> Seed { - self.seed - } -} - -impl TraitPair for Pair { - type Seed = Seed; - type Public = Public; - type Signature = Signature; - - /// Make a new key pair from secret seed material. - /// - /// The slice must be 32 bytes long or it will return an error. - fn from_seed_slice(seed_slice: &[u8]) -> Result { - if seed_slice.len() != SEED_SERIALIZED_SIZE { - return Err(SecretStringError::InvalidSeedLength) - } - let mut seed = [0; SEED_SERIALIZED_SIZE]; - seed.copy_from_slice(seed_slice); - let secret = Secret::from_seed(&seed); - Ok(Pair { secret, seed }) - } - - /// Derive a child key from a series of given (hard) junctions. - /// - /// Soft junctions are not supported. - fn derive>( - &self, - path: Iter, - _seed: Option, - ) -> Result<(Pair, Option), DeriveError> { - let derive_hard = |seed, cc| -> Seed { - ("bandersnatch-vrf-HDKD", seed, cc).using_encoded(sp_crypto_hashing::blake2_256) - }; - - let mut seed = self.seed(); - for p in path { - if let DeriveJunction::Hard(cc) = p { - seed = derive_hard(seed, cc); - } else { - return Err(DeriveError::SoftKeyInPath) - } - } - Ok((Self::from_seed(&seed), Some(seed))) - } - - fn public(&self) -> Public { - let public = self.secret.public(); - let mut raw = [0; PUBLIC_SERIALIZED_SIZE]; - public - .serialize_compressed(raw.as_mut_slice()) - .expect("serialization length is constant and checked by test; qed"); - Public::unchecked_from(raw) - } - - #[cfg(feature = "full_crypto")] - fn sign(&self, data: &[u8]) -> Signature { - use ark_ec_vrfs::Suite; - use bandersnatch::BandersnatchSha512Ell2; - let input = bandersnatch::Input::new(data).unwrap(); - let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); - let gk = BandersnatchSha512Ell2::generator() * k; - let c = BandersnatchSha512Ell2::challenge( - &[&gk.into_affine(), &self.secret.public.0, &input.0], - &[], - ); - let s = k + c * self.secret.scalar; - let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; - bandersnatch::IetfProof { c, s } - .serialize_compressed(&mut raw_signature.as_mut_slice()) - .unwrap(); - Signature::from_raw(raw_signature) - } - - fn verify>(signature: &Signature, data: M, public: &Public) -> bool { - use ark_ec_vrfs::Suite; - use bandersnatch::BandersnatchSha512Ell2; - let Ok(signature) = bandersnatch::IetfProof::deserialize_compressed(&signature.0[..]) - else { - return false - }; - let Ok(public) = bandersnatch::Public::deserialize_compressed(&public.0[..]) else { - return false - }; - let input = bandersnatch::Input::new(data.as_ref()).expect("Can't fail"); - let gs = BandersnatchSha512Ell2::generator() * signature.s; - let yc = public.0 * signature.c; - let rv = gs - yc; - let cv = BandersnatchSha512Ell2::challenge(&[&rv.into_affine(), &public.0, &input.0], &[]); - signature.c == cv - } - - /// Return a vector filled with the seed (32 bytes). - fn to_raw_vec(&self) -> Vec { - self.seed().to_vec() - } -} - -impl CryptoType for Pair { - type Pair = Pair; -} - -/// Bandersnatch VRF types and operations. -pub mod vrf { - use super::*; - use crate::crypto::VrfCrypto; - use ark_ec_vrfs::ietf::{Prover, Verifier}; - - /// [`VrfSignature`] serialized size. - pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize = - PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; - - /// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`]. - #[derive(Clone, Debug)] - pub struct VrfInput(pub(super) bandersnatch::Input); - - impl VrfInput { - /// Construct a new VRF input. - pub fn new(data: impl AsRef<[u8]>) -> Self { - Self(bandersnatch::Input::new(data.as_ref()).expect("Can't fail")) - } - } - - /// VRF pre-output derived from [`VrfInput`] using a [`VrfSecret`]. - /// - /// This object is used to produce an arbitrary number of verifiable pseudo random - /// bytes and is often called pre-output to emphasize that this is not the actual - /// output of the VRF but an object capable of generating the output. - #[derive(Clone, Debug)] - pub struct VrfPreOutput(pub(super) bandersnatch::Output); - - // Workaround until traits are not implemented for newtypes https://github.com/davxy/ark-ec-vrfs/issues/41 - impl PartialEq for VrfPreOutput { - fn eq(&self, other: &Self) -> bool { - self.0 .0 == other.0 .0 - } - } - impl Eq for VrfPreOutput {} - - impl Encode for VrfPreOutput { - fn encode(&self) -> Vec { - let mut bytes = [0; PREOUT_SERIALIZED_SIZE]; - self.0 - .serialize_compressed(bytes.as_mut_slice()) - .expect("serialization length is constant and checked by test; qed"); - bytes.encode() - } - } - - impl Decode for VrfPreOutput { - fn decode(i: &mut R) -> Result { - let buf = <[u8; PREOUT_SERIALIZED_SIZE]>::decode(i)?; - let preout = bandersnatch::Output::deserialize_compressed_unchecked(buf.as_slice()) - .map_err(|_| "vrf-preout decode error: bad preout")?; - Ok(VrfPreOutput(preout)) - } - } - - // `VrfPreOutput` resolves to: - // ``` - // pub struct Affine { - // pub x: P::BaseField, - // pub y: P::BaseField, - // } - // ``` - // where each `P::BaseField` contains a `pub struct BigInt(pub [u64; N]);` - // Since none of these structures is allocated on the heap, we don't need any special - // memory tracking logic. We can simply implement `DecodeWithMemTracking`. - impl DecodeWithMemTracking for VrfPreOutput {} - - impl EncodeLike for VrfPreOutput {} - - impl MaxEncodedLen for VrfPreOutput { - fn max_encoded_len() -> usize { - <[u8; PREOUT_SERIALIZED_SIZE]>::max_encoded_len() - } - } - - impl TypeInfo for VrfPreOutput { - type Identity = [u8; PREOUT_SERIALIZED_SIZE]; - - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } - } - - /// Data to be signed via one of the two provided vrf flavors. - /// - /// The object contains the VRF input and additional data to be signed together - /// with the VRF input. Additional data doesn't influence the VRF output. - /// - /// The `input` is a [`VrfInput`]s which, during the signing procedure, is first mapped - /// to a [`VrfPreOutput`]. - #[derive(Clone)] - pub struct VrfSignData { - /// VRF input. - pub vrf_input: VrfInput, - /// Additional data. - pub aux_data: Vec, - } - - impl VrfSignData { - /// Construct a new data to be signed. - pub fn new(vrf_input_data: &[u8], aux_data: &[u8]) -> Self { - Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_owned() } - } - } - - /// VRF signature. - /// - /// Includes both the VRF proof and the pre-output generated from the [`VrfSignData::input`]. - /// - /// Refer to [`VrfSignData`] for more details. - #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] - pub struct VrfSignature { - /// VRF pre-output. - pub pre_output: VrfPreOutput, - /// VRF proof. - pub proof: Signature, - } - - #[cfg(feature = "full_crypto")] - impl VrfCrypto for Pair { - type VrfInput = VrfInput; - type VrfPreOutput = VrfPreOutput; - type VrfSignData = VrfSignData; - type VrfSignature = VrfSignature; - } - - #[cfg(feature = "full_crypto")] - impl VrfSecret for Pair { - fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature { - let pre_output_impl = self.secret.output(data.vrf_input.0); - let pre_output = VrfPreOutput(pre_output_impl); - let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); - let mut proof = Signature::default(); - proof_impl - .serialize_compressed(proof.0.as_mut_slice()) - .expect("serialization length is constant and checked by test; qed"); - VrfSignature { pre_output, proof } - } - - fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput { - let pre_output_impl = self.secret.output(input.0); - VrfPreOutput(pre_output_impl) - } - } - - impl VrfCrypto for Public { - type VrfInput = VrfInput; - type VrfPreOutput = VrfPreOutput; - type VrfSignData = VrfSignData; - type VrfSignature = VrfSignature; - } - - impl VrfPublic for Public { - fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool { - let Ok(public) = - bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) - else { - return false - }; - let Ok(proof) = ark_ec_vrfs::ietf::Proof::deserialize_compressed_unchecked( - signature.proof.as_slice(), - ) else { - return false - }; - public - .verify(data.vrf_input.0, signature.pre_output.0, &data.aux_data, &proof) - .is_ok() - } - } - - #[cfg(feature = "full_crypto")] - impl Pair { - /// Generate VRF output bytes for the given `input`. - pub fn make_bytes(&self, input: &VrfInput) -> [u8; 32] { - self.vrf_pre_output(input).make_bytes() - } - } - - impl VrfPreOutput { - /// Generate VRF output bytes. - pub fn make_bytes(&self) -> [u8; 32] { - let mut bytes = [0_u8; 32]; - bytes.copy_from_slice(&self.0.hash()[..32]); - bytes - } - } -} - -/// Bandersnatch Ring-VRF types and operations. -pub mod ring_vrf { - use super::{vrf::*, *}; - use ark_ec_vrfs::ring::{Prover, Verifier}; - use bandersnatch::{ - RingContext as RingContextImpl, RingProver, RingVerifier, VerifierKey as VerifierKeyImpl, - }; - - // Max size of serialized ring-vrf context given `domain_len`. - // TODO @davxy: test this - pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize { - // const G1_POINT_COMPRESSED_SIZE: usize = 48; - // const G2_POINT_COMPRESSED_SIZE: usize = 96; - const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; - const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; - const OVERHEAD_SIZE: usize = 16; - const G2_POINTS_NUM: usize = 2; - let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); - let g1_points_num = 3 * domain_size as usize + 1; - OVERHEAD_SIZE + - g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + - G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE - } - - /// [`RingVerifierKey`] serialized size. - pub const RING_VERIFIER_KEY_SERIALIZED_SIZE: usize = 384; - /// [`RingProof`] serialized size. - pub(crate) const RING_PROOF_SERIALIZED_SIZE: usize = 752; - /// [`RingVrrfSignature`] serialized size. - pub const RING_SIGNATURE_SERIALIZED_SIZE: usize = - RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE; - - /// Ring verifier key - pub struct RingVerifierKey(VerifierKeyImpl); - - impl Encode for RingVerifierKey { - fn encode(&self) -> Vec { - const ERR_STR: &str = "serialization length is constant and checked by test; qed"; - let mut buf = Vec::with_capacity(RING_VERIFIER_KEY_SERIALIZED_SIZE); - self.0.serialize_compressed(&mut buf).expect(ERR_STR); - buf - } - } - - impl Decode for RingVerifierKey { - fn decode(input: &mut R) -> Result { - let mut buf = vec![0; RING_VERIFIER_KEY_SERIALIZED_SIZE]; - input.read(&mut buf[..])?; - let vk = VerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice()) - .map_err(|_| "RingVerifierKey decode error")?; - Ok(RingVerifierKey(vk)) - } - } - - impl EncodeLike for RingVerifierKey {} - - impl MaxEncodedLen for RingVerifierKey { - fn max_encoded_len() -> usize { - RING_VERIFIER_KEY_SERIALIZED_SIZE - } - } - - impl TypeInfo for RingVerifierKey { - type Identity = [u8; RING_VERIFIER_KEY_SERIALIZED_SIZE]; - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } - } - - /// Context used to construct ring prover and verifier. - /// - /// Generic parameter `R` represents the ring size. - #[derive(Clone)] - pub struct RingContext(RingContextImpl); - - impl RingContext { - /// Build an dummy instance for testing purposes. - pub fn new_testing() -> Self { - Self(RingContextImpl::from_seed(R, [0; 32])) - } - - /// Get the keyset max size. - pub fn max_keyset_size(&self) -> usize { - self.0.max_ring_size() - } - - /// Get ring prover for the key at index `public_idx` in the `public_keys` set. - pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { - let pks = Self::make_ring_vector(public_keys)?; - let prover_key = self.0.prover_key(&pks); - Some(self.0.prover(prover_key, public_idx)) - } - - /// Get ring verifier for the `public_keys` set. - pub fn verifier(&self, public_keys: &[Public]) -> Option { - self.verifier_key(public_keys).map(|vk| self.0.verifier(vk.0)) - } - - /// Build ring commitment for `RingVerifier` lazy construction. - pub fn verifier_key(&self, public_keys: &[Public]) -> Option { - let pks = Self::make_ring_vector(public_keys)?; - Some(RingVerifierKey(self.0.verifier_key(&pks))) - } - - /// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance. - /// - /// While this approach is computationally slightly less efficient than using a - /// pre-constructed `RingContext`, as some parameters need to be computed on-the-fly, it - /// is beneficial in memory or storage constrained environments. This avoids the need to - /// retain the full `RingContext` for ring signature verification. Instead, the - /// `VerifierKey` contains only the essential information needed to verify ring proofs. - pub fn verifier_no_context(verifier_key: RingVerifierKey) -> RingVerifier { - RingContextImpl::verifier_no_context(verifier_key.0, R) - } - - fn make_ring_vector(public_keys: &[Public]) -> Option> { - use bandersnatch::Public as PublicImpl; - let mut pts = Vec::with_capacity(public_keys.len()); - for pk in public_keys { - let pk = PublicImpl::deserialize_compressed_unchecked(pk.as_slice()).ok()?; - pts.push(pk.0); - } - Some(pts) - } - } - - impl Encode for RingContext { - fn encode(&self) -> Vec { - let mut buf = Vec::with_capacity(ring_context_serialized_size(R)); - self.0 - .serialize_uncompressed(&mut buf) - .expect("serialization length is constant and checked by test; qed"); - buf - } - } - - impl Decode for RingContext { - fn decode(input: &mut I) -> Result { - let mut buf = vec![0; ring_context_serialized_size(R)]; - input.read(&mut buf[..])?; - let ctx = RingContextImpl::deserialize_uncompressed_unchecked(buf.as_slice()) - .map_err(|_| "RingContext decode error")?; - Ok(RingContext(ctx)) - } - } - - impl EncodeLike for RingContext {} - - impl MaxEncodedLen for RingContext { - fn max_encoded_len() -> usize { - ring_context_serialized_size(R) - } - } - - impl TypeInfo for RingContext { - type Identity = Self; - fn type_info() -> scale_info::Type { - let path = scale_info::Path::new("RingContext", module_path!()); - let array_type_def = scale_info::TypeDefArray { - len: ring_context_serialized_size(R) as u32, - type_param: scale_info::MetaType::new::(), - }; - let type_def = scale_info::TypeDef::Array(array_type_def); - scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() } - } - } - - /// Ring VRF signature. - #[derive( - Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, - )] - pub struct RingVrfSignature { - /// VRF pre-output. - pub pre_output: VrfPreOutput, - /// Ring signature. - pub proof: [u8; RING_PROOF_SERIALIZED_SIZE], - } - - #[cfg(feature = "full_crypto")] - impl Pair { - /// Produce a ring-vrf signature. - /// - /// The ring signature is verifiable if the public key corresponding to the - /// signing [`Pair`] is part of the ring from which the [`RingProver`] has - /// been constructed. If not, the produced signature is just useless. - pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { - let pre_output_impl = self.secret.output(data.vrf_input.0); - let pre_output = VrfPreOutput(pre_output_impl); - let proof_impl = - self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data, prover); - let mut proof = [0; RING_PROOF_SERIALIZED_SIZE]; - proof_impl - .serialize_compressed(proof.as_mut_slice()) - .expect("serialization length is constant and checked by test; qed"); - RingVrfSignature { pre_output, proof } - } - } - - impl RingVrfSignature { - /// Verify a ring-vrf signature. - /// - /// The signature is verifiable if it has been produced by a member of the ring - /// from which the [`RingVerifier`] has been constructed. - pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { - let Ok(proof) = - bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) - else { - return false - }; - bandersnatch::Public::verify( - data.vrf_input.0, - self.pre_output.0, - &data.aux_data, - &proof, - verifier, - ) - .is_ok() - } - } -} - -#[cfg(test)] -mod tests { - use super::{ring_vrf::*, vrf::*, *}; - use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; - - const TEST_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE]; - const TEST_RING_SIZE: usize = 16; - - type TestRingContext = RingContext; - - #[allow(unused)] - fn b2h(bytes: &[u8]) -> String { - array_bytes::bytes2hex("", bytes) - } - - fn h2b(hex: &str) -> Vec { - array_bytes::hex2bytes_unchecked(hex) - } - - #[test] - fn backend_assumptions_sanity_check() { - use bandersnatch::{Input, RingContext as RingContextImpl, Secret}; - const OVERHEAD_SIZE: usize = 257; - - let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); - - let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); - assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); - - assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); - - let prover_key_index = 3; - let secret = Secret::from_seed(&[prover_key_index as u8; 32]); - let public = secret.public(); - assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE); - - let input = Input::new(b"foo").unwrap(); - let preout = secret.output(input); - assert_eq!(preout.compressed_size(), PREOUT_SERIALIZED_SIZE); - - let ring_keys: Vec<_> = (0..TEST_RING_SIZE) - .map(|i| Secret::from_seed(&[i as u8; 32]).public().0.into()) - .collect(); - - let verifier_key = ctx.verifier_key(&ring_keys[..]); - assert_eq!(verifier_key.compressed_size(), RING_VERIFIER_KEY_SERIALIZED_SIZE); - - let prover_key = ctx.prover_key(&ring_keys); - let ring_prover = ctx.prover(prover_key, prover_key_index); - - { - use ark_ec_vrfs::ietf::Prover; - let proof = secret.prove(input, preout, &[]); - assert_eq!(proof.compressed_size(), SIGNATURE_SERIALIZED_SIZE); - } - - { - use ark_ec_vrfs::ring::Prover; - let proof = secret.prove(input, preout, &[], &ring_prover); - assert_eq!(proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE); - } - } - - #[test] - fn derive_works() { - let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap(); - let known = h2b("f706ea7ee4eef553428a768dbf3a1ede0b389a9f75867ade317a61cbb4efeb01"); - assert_eq!(pair.public().as_ref(), known); - - // Soft derivation not supported - let res = Pair::from_string(&format!("{}//Alice/Soft", DEV_PHRASE), None); - assert!(res.is_err()); - } - - #[test] - fn generate_with_phrase_should_be_recoverable_with_from_string() { - let (pair, phrase, seed) = Pair::generate_with_phrase(None); - let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); - assert_eq!(pair.public(), repair_seed.public()); - let (repair_phrase, reseed) = - Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); - assert_eq!(seed, reseed); - assert_eq!(pair.public(), repair_phrase.public()); - let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); - assert_eq!(pair.public(), repair_string.public()); - } - - #[test] - fn sign_verify() { - let pair = Pair::from_seed(TEST_SEED); - let public = pair.public(); - let msg = b"foo"; - let signature = pair.sign(msg); - assert!(Pair::verify(&signature, msg, &public)); - } - - #[test] - fn vrf_sign_verify() { - let pair = Pair::from_seed(TEST_SEED); - let public = pair.public(); - let data = VrfSignData::new(b"foo", b"aux"); - let signature = pair.vrf_sign(&data); - assert!(public.vrf_verify(&data, &signature)); - } - - #[test] - fn vrf_sign_verify_with_bad_input() { - let pair = Pair::from_seed(TEST_SEED); - let public = pair.public(); - let data = VrfSignData::new(b"foo", b"aux"); - let signature = pair.vrf_sign(&data); - let data = VrfSignData::new(b"foo", b"bad"); - assert!(!public.vrf_verify(&data, &signature)); - let data = VrfSignData::new(b"bar", b"aux"); - assert!(!public.vrf_verify(&data, &signature)); - } - - #[test] - fn vrf_output_bytes_match() { - let pair = Pair::from_seed(TEST_SEED); - let data = VrfSignData::new(b"foo", b"aux"); - let signature = pair.vrf_sign(&data); - let o0 = pair.make_bytes(&data.vrf_input); - let o1 = signature.pre_output.make_bytes(); - assert_eq!(o0, o1); - } - - #[test] - fn vrf_signature_encode_decode() { - let pair = Pair::from_seed(TEST_SEED); - - let data = VrfSignData::new(b"data", b"aux"); - let expected = pair.vrf_sign(&data); - - let bytes = expected.encode(); - - let expected_len = PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE; - assert_eq!(bytes.len(), expected_len); - - let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); - assert_eq!(expected, decoded); - } - - #[test] - fn ring_vrf_sign_verify() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = - (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(TEST_SEED); - - // Just pick one index to patch with the actual public key - let prover_idx = 3; - pks[prover_idx] = pair.public(); - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - - let data = VrfSignData::new(b"data", b"aux"); - let signature = pair.ring_vrf_sign(&data, &prover); - - let verifier = ring_ctx.verifier(&pks).unwrap(); - assert!(signature.ring_vrf_verify(&data, &verifier)); - } - - #[test] - fn ring_vrf_sign_verify_with_out_of_ring_key() { - let ring_ctx = TestRingContext::new_testing(); - - let pks: Vec<_> = - (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - let pair = Pair::from_seed(TEST_SEED); - - let data = VrfSignData::new(b"foo", b"aux"); - - // pair.public != pks[0] - let prover = ring_ctx.prover(&pks, 0).unwrap(); - let signature = pair.ring_vrf_sign(&data, &prover); - - let verifier = ring_ctx.verifier(&pks).unwrap(); - assert!(!signature.ring_vrf_verify(&data, &verifier)); - } - - #[test] - fn ring_vrf_make_bytes_matches() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = - (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(TEST_SEED); - - // Just pick one index to patch with the actual public key - let prover_idx = 3; - pks[prover_idx] = pair.public(); - - let data = VrfSignData::new(b"data", b"aux"); - - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - let signature = pair.ring_vrf_sign(&data, &prover); - - let o0 = pair.make_bytes(&data.vrf_input); - let o1 = signature.pre_output.make_bytes(); - assert_eq!(o0, o1); - } - - #[test] - fn ring_vrf_signature_encode_decode() { - let ring_ctx = TestRingContext::new_testing(); - - let mut pks: Vec<_> = - (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let pair = Pair::from_seed(TEST_SEED); - - // Just pick one index to patch with the actual public key - let prover_idx = 3; - pks[prover_idx] = pair.public(); - - let data = VrfSignData::new(b"foo", b"aux"); - - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); - let expected = pair.ring_vrf_sign(&data, &prover); - - let bytes = expected.encode(); - assert_eq!(bytes.len(), RING_SIGNATURE_SERIALIZED_SIZE); - - let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); - assert_eq!(expected, decoded); - } - - #[test] - fn ring_vrf_context_encode_decode() { - let ctx1 = TestRingContext::new_testing(); - let enc1 = ctx1.encode(); - - assert_eq!(enc1.len(), ring_context_serialized_size(TEST_RING_SIZE)); - assert_eq!(enc1.len(), TestRingContext::max_encoded_len()); - - let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap(); - let enc2 = ctx2.encode(); - - assert_eq!(enc1, enc2); - } - - #[test] - fn verifier_key_encode_decode() { - let ring_ctx = TestRingContext::new_testing(); - - let pks: Vec<_> = - (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); - assert!(pks.len() <= ring_ctx.max_keyset_size()); - - let verifier_key = ring_ctx.verifier_key(&pks).unwrap(); - let enc1 = verifier_key.encode(); - assert_eq!(enc1.len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); - assert_eq!(RingVerifierKey::max_encoded_len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); - - let vd2 = RingVerifierKey::decode(&mut enc1.as_slice()).unwrap(); - let enc2 = vd2.encode(); - assert_eq!(enc1, enc2); - } -} diff --git a/substrate/primitives/core/src/lib.rs b/substrate/primitives/core/src/lib.rs index 76356cc1b4ac6..b24eb400e6459 100644 --- a/substrate/primitives/core/src/lib.rs +++ b/substrate/primitives/core/src/lib.rs @@ -72,7 +72,6 @@ pub mod uint; #[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; -pub mod bandersnatch2; #[cfg(feature = "bls-experimental")] pub mod bls; pub mod crypto_bytes; From fa5377a7460578d3c698cefae77476d6eb9d9a77 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:33:20 +0100 Subject: [PATCH 05/29] Infallible prover/verifier construction with padding fallback --- substrate/primitives/core/src/bandersnatch.rs | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 3aac5239f6002..4c588e33fc1de 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -466,21 +466,22 @@ pub mod ring_vrf { } /// Get ring prover for the key at index `public_idx` in the `public_keys` set. - pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { - let pks = Self::make_ring_vector(public_keys)?; + pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> RingProver { + let pks = Self::make_ring_vector(public_keys); let prover_key = self.0.prover_key(&pks); - Some(self.0.prover(prover_key, public_idx)) + self.0.prover(prover_key, public_idx) } /// Get ring verifier for the `public_keys` set. - pub fn verifier(&self, public_keys: &[Public]) -> Option { - self.verifier_key(public_keys).map(|vk| self.0.verifier(vk.0)) + pub fn verifier(&self, public_keys: &[Public]) -> RingVerifier { + let vk = self.verifier_key(public_keys); + self.0.verifier(vk.0) } - /// Build ring commitment for `RingVerifier` lazy construction. - pub fn verifier_key(&self, public_keys: &[Public]) -> Option { - let pks = Self::make_ring_vector(public_keys)?; - Some(RingVerifierKey(self.0.verifier_key(&pks))) + /// Build `RingVerifierKey` for lazy `RingVerifier` construction. + pub fn verifier_key(&self, public_keys: &[Public]) -> RingVerifierKey { + let pks = Self::make_ring_vector(public_keys); + RingVerifierKey(self.0.verifier_key(&pks)) } /// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance. @@ -494,14 +495,15 @@ pub mod ring_vrf { RingContextImpl::verifier_no_context(verifier_key.0, R) } - fn make_ring_vector(public_keys: &[Public]) -> Option> { - use bandersnatch::Public as PublicImpl; - let mut pts = Vec::with_capacity(public_keys.len()); - for pk in public_keys { - let pk = PublicImpl::deserialize_compressed_unchecked(pk.as_slice()).ok()?; - pts.push(pk.0); - } - Some(pts) + fn make_ring_vector(public_keys: &[Public]) -> Vec { + use bandersnatch::AffinePoint; + public_keys + .iter() + .map(|pk| { + AffinePoint::deserialize_compressed_unchecked(pk.as_slice()) + .unwrap_or(RingContextImpl::padding_point()) + }) + .collect() } } @@ -756,12 +758,12 @@ mod tests { // Just pick one index to patch with the actual public key let prover_idx = 3; pks[prover_idx] = pair.public(); - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx); let data = VrfSignData::new(b"data", b"aux"); let signature = pair.ring_vrf_sign(&data, &prover); - let verifier = ring_ctx.verifier(&pks).unwrap(); + let verifier = ring_ctx.verifier(&pks); assert!(signature.ring_vrf_verify(&data, &verifier)); } @@ -776,10 +778,10 @@ mod tests { let data = VrfSignData::new(b"foo", b"aux"); // pair.public != pks[0] - let prover = ring_ctx.prover(&pks, 0).unwrap(); + let prover = ring_ctx.prover(&pks, 0); let signature = pair.ring_vrf_sign(&data, &prover); - let verifier = ring_ctx.verifier(&pks).unwrap(); + let verifier = ring_ctx.verifier(&pks); assert!(!signature.ring_vrf_verify(&data, &verifier)); } @@ -799,7 +801,7 @@ mod tests { let data = VrfSignData::new(b"data", b"aux"); - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx); let signature = pair.ring_vrf_sign(&data, &prover); let o0 = pair.make_bytes(&data.vrf_input); @@ -823,7 +825,7 @@ mod tests { let data = VrfSignData::new(b"foo", b"aux"); - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx); let expected = pair.ring_vrf_sign(&data, &prover); let bytes = expected.encode(); @@ -855,7 +857,7 @@ mod tests { (0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); assert!(pks.len() <= ring_ctx.max_keyset_size()); - let verifier_key = ring_ctx.verifier_key(&pks).unwrap(); + let verifier_key = ring_ctx.verifier_key(&pks); let enc1 = verifier_key.encode(); assert_eq!(enc1.len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); assert_eq!(RingVerifierKey::max_encoded_len(), RING_VERIFIER_KEY_SERIALIZED_SIZE); From 7103b4071d2da7c62bc3a248974ed46f836f01f3 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:33:38 +0100 Subject: [PATCH 06/29] Sassafras primitives --- .../primitives/consensus/sassafras/src/vrf.rs | 82 +++++-------------- 1 file changed, 21 insertions(+), 61 deletions(-) diff --git a/substrate/primitives/consensus/sassafras/src/vrf.rs b/substrate/primitives/consensus/sassafras/src/vrf.rs index f8def1b5f189f..13b6205423493 100644 --- a/substrate/primitives/consensus/sassafras/src/vrf.rs +++ b/substrate/primitives/consensus/sassafras/src/vrf.rs @@ -24,88 +24,48 @@ use codec::Encode; use sp_consensus_slots::Slot; pub use sp_core::bandersnatch::{ - ring_vrf::{RingProver, RingVerifier, RingVerifierData, RingVrfSignature}, + ring_vrf::{RingProver, RingVerifier, RingVerifierKey, RingVrfSignature}, vrf::{VrfInput, VrfPreOutput, VrfSignData, VrfSignature}, }; -/// Ring VRF domain size for Sassafras consensus. -pub const RING_VRF_DOMAIN_SIZE: u32 = 2048; +/// Ring size (aka authorities count) for Sassafras consensus. +pub const RING_SIZE: usize = 1024; -/// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_VRF_DOMAIN_SIZE`]. -pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext; +/// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_SIZE`]. +pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext; -fn vrf_input_from_data( - domain: &[u8], - data: impl IntoIterator>, -) -> VrfInput { - let buf = data.into_iter().fold(Vec::new(), |mut buf, item| { - let bytes = item.as_ref(); - buf.extend_from_slice(bytes); - let len = u8::try_from(bytes.len()).expect("private function with well known inputs; qed"); - buf.push(len); - buf - }); - VrfInput::new(domain, buf) -} - -/// VRF input to claim slot ownership during block production. +/// TODO pub fn slot_claim_input(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput { - vrf_input_from_data( - b"sassafras-claim-v1.0", - [randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()], - ) + let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()] + .concat(); + VrfInput::new(&v[..]) } /// Signing-data to claim slot ownership during block production. pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData { - let input = slot_claim_input(randomness, slot, epoch); - VrfSignData::new_unchecked( - b"sassafras-slot-claim-transcript-v1.0", - Option::<&[u8]>::None, - Some(input), - ) + let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()] + .concat(); + VrfSignData::new(&v[..], &[]) } /// VRF input to generate the ticket id. pub fn ticket_id_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { - vrf_input_from_data( - b"sassafras-ticket-v1.0", - [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], - ) -} - -/// VRF input to generate the revealed key. -pub fn revealed_key_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { - vrf_input_from_data( - b"sassafras-revealed-v1.0", - [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], - ) + let v = + [b"sassafras-ticket", randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()] + .concat(); + VrfInput::new(&v[..]) } /// Data to be signed via ring-vrf. pub fn ticket_body_sign_data(ticket_body: &TicketBody, ticket_id_input: VrfInput) -> VrfSignData { - VrfSignData::new_unchecked( - b"sassafras-ticket-body-transcript-v1.0", - Some(ticket_body.encode().as_slice()), - Some(ticket_id_input), - ) + VrfSignData { vrf_input: ticket_id_input, aux_data: ticket_body.encode() } } -/// Make ticket-id from the given VRF input and pre-output. +/// Make ticket-id from the given VRF pre-output. /// -/// Input should have been obtained via [`ticket_id_input`]. /// Pre-output should have been obtained from the input directly using the vrf -/// secret key or from the vrf signature pre-outputs. -pub fn make_ticket_id(input: &VrfInput, pre_output: &VrfPreOutput) -> TicketId { - let bytes = pre_output.make_bytes::<16>(b"ticket-id", input); +/// secret key or from the vrf signature pre-output. +pub fn make_ticket_id(preout: &VrfPreOutput) -> TicketId { + let bytes: [u8; 16] = preout.make_bytes()[..16].try_into().unwrap(); u128::from_le_bytes(bytes) } - -/// Make revealed key seed from a given VRF input and pre-output. -/// -/// Input should have been obtained via [`revealed_key_input`]. -/// Pre-output should have been obtained from the input directly using the vrf -/// secret key or from the vrf signature pre-outputs. -pub fn make_revealed_key_seed(input: &VrfInput, pre_output: &VrfPreOutput) -> [u8; 32] { - pre_output.make_bytes::<32>(b"revealed-seed", input) -} From db08b5338cc890410f3c2774225b775f02e5d99b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:39:40 +0100 Subject: [PATCH 07/29] Pallet builds --- substrate/frame/sassafras/src/lib.rs | 40 ++++++++-------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/substrate/frame/sassafras/src/lib.rs b/substrate/frame/sassafras/src/lib.rs index f6c409833e333..213a858d89557 100644 --- a/substrate/frame/sassafras/src/lib.rs +++ b/substrate/frame/sassafras/src/lib.rs @@ -90,9 +90,6 @@ pub use pallet::*; const LOG_TARGET: &str = "sassafras::runtime"; -// Contextual string used by the VRF to generate per-block randomness. -const RANDOMNESS_VRF_CONTEXT: &[u8] = b"SassafrasOnChainRandomness"; - // Max length for segments holding unsorted tickets. const SEGMENT_MAX_SIZE: u32 = 128; @@ -272,7 +269,7 @@ pub mod pallet { /// Ring verifier data for the current epoch. #[pallet::storage] - pub type RingVerifierData = StorageValue<_, vrf::RingVerifierData>; + pub type RingVerifierData = StorageValue<_, vrf::RingVerifierKey>; /// Slot claim VRF pre-output used to generate per-slot randomness. /// @@ -326,11 +323,7 @@ pub mod pallet { Self::post_genesis_initialize(claim.slot); } - let randomness_pre_output = claim - .vrf_signature - .pre_outputs - .get(0) - .expect("Valid claim must have VRF signature; qed"); + let randomness_pre_output = claim.vrf_signature.pre_output; ClaimTemporaryData::::put(randomness_pre_output); let trigger_weight = T::EpochChangeTrigger::trigger::(block_num); @@ -343,15 +336,9 @@ pub mod pallet { // to the accumulator. If we've determined that this block was the first in // a new epoch, the changeover logic has already occurred at this point // (i.e. `enact_epoch_change` has already been called). - let randomness_input = vrf::slot_claim_input( - &Self::randomness(), - CurrentSlot::::get(), - EpochIndex::::get(), - ); let randomness_pre_output = ClaimTemporaryData::::take() .expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed"); - let randomness = randomness_pre_output - .make_bytes::(RANDOMNESS_VRF_CONTEXT, &randomness_input); + let randomness = randomness_pre_output.make_bytes(); Self::deposit_slot_randomness(&randomness); // Check if we are in the epoch's second half. @@ -367,7 +354,7 @@ pub mod pallet { Self::sort_segments( metadata .unsorted_tickets_count - .div_ceil(SEGMENT_MAX_SIZE * slots_left as u32), + .div_ceil(SEGMENT_MAX_SIE * slots_left as u32), next_epoch_tag, &mut metadata, ); @@ -399,7 +386,9 @@ pub mod pallet { return Err("Tickets shall be submitted in the first epoch half".into()) } - let Some(verifier) = RingVerifierData::::get().map(|v| v.into()) else { + let Some(verifier) = + RingVerifierData::::get().map(|vk| vrf::RingContext::verifier_no_context(vk)) + else { warn!(target: LOG_TARGET, "Ring verifier key not initialized"); return Err("Ring verifier key not initialized".into()) }; @@ -424,15 +413,8 @@ pub mod pallet { for ticket in tickets { debug!(target: LOG_TARGET, "Checking ring proof"); - let Some(ticket_id_pre_output) = ticket.signature.pre_outputs.get(0) else { - debug!(target: LOG_TARGET, "Missing ticket VRF pre-output from ring signature"); - continue - }; - let ticket_id_input = - vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx); - // Check threshold constraint - let ticket_id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output); + let ticket_id = vrf::make_ticket_id(&ticket.signature.pre_output); if ticket_id >= ticket_threshold { debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold); continue @@ -445,6 +427,8 @@ pub mod pallet { } // Check ring signature + let ticket_id_input = + vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx); let sign_data = vrf::ticket_body_sign_data(&ticket.body, ticket_id_input); if !ticket.signature.ring_vrf_verify(&sign_data, &verifier) { debug!(target: LOG_TARGET, "Proof verification failure for ticket ({:032x})", ticket_id); @@ -585,9 +569,7 @@ impl Pallet { let pks: Vec<_> = authorities.iter().map(|auth| *auth.as_ref()).collect(); debug!(target: LOG_TARGET, "Building ring verifier (ring size: {})", pks.len()); - let verifier_data = ring_ctx - .verifier_data(&pks) - .expect("Failed to build ring verifier. This is a bug"); + let verifier_data = ring_ctx.verifier_key(&pks); RingVerifierData::::put(verifier_data); } From 7e060e45ac1868273e627e1e184292dc1463d013 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:43:25 +0100 Subject: [PATCH 08/29] Test run, but fail --- substrate/frame/sassafras/src/lib.rs | 2 +- substrate/frame/sassafras/src/mock.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/frame/sassafras/src/lib.rs b/substrate/frame/sassafras/src/lib.rs index 213a858d89557..1f34393bb805e 100644 --- a/substrate/frame/sassafras/src/lib.rs +++ b/substrate/frame/sassafras/src/lib.rs @@ -354,7 +354,7 @@ pub mod pallet { Self::sort_segments( metadata .unsorted_tickets_count - .div_ceil(SEGMENT_MAX_SIE * slots_left as u32), + .div_ceil(SEGMENT_MAX_SIZE * slots_left as u32), next_epoch_tag, &mut metadata, ); diff --git a/substrate/frame/sassafras/src/mock.rs b/substrate/frame/sassafras/src/mock.rs index d7e2fb63dc2f4..aa190a8ce5039 100644 --- a/substrate/frame/sassafras/src/mock.rs +++ b/substrate/frame/sassafras/src/mock.rs @@ -176,7 +176,7 @@ pub fn make_prover(pair: &AuthorityPair) -> RingProver { .collect(); log::debug!("Building prover. Ring size: {}", pks.len()); - let prover = ring_ctx.prover(&pks, prover_idx.unwrap()).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx.unwrap()); log::debug!("Done"); prover @@ -201,7 +201,7 @@ pub fn make_ticket_body(attempt_idx: u32, pair: &AuthorityPair) -> (TicketId, Ti let ticket_id_input = vrf::ticket_id_input(&randomness, attempt_idx, epoch); let ticket_id_pre_output = pair.as_inner_ref().vrf_pre_output(&ticket_id_input); - let id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output); + let id = vrf::make_ticket_id(&ticket_id_pre_output); // Make a dummy ephemeral public that hopefully is unique within one test instance. // In the tests, the values within the erased public are just used to compare From adb012e283730591721284c76e9f39cd6d80379e Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 18:59:21 +0100 Subject: [PATCH 09/29] Fixed tests --- .../src/data/25_tickets_100_auths.bin | Bin 24728 -> 24503 bytes substrate/frame/sassafras/src/tests.rs | 20 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/substrate/frame/sassafras/src/data/25_tickets_100_auths.bin b/substrate/frame/sassafras/src/data/25_tickets_100_auths.bin index 6e81f216455ae9dc61be31a9edef583a652721a8..5d21edcbef4e7fb61290e1cea0ee9906db25a0d7 100644 GIT binary patch literal 24503 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 PdMp|_R#yrS0=WJMON22F literal 24728 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::exists()); common_assertions(); - println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( Sassafras::randomness_accumulator(), - h2b("9f2b9fd19a772c34d437dcd8b84a927e73a5cb43d3d1cd00093223d60d2b4843"), + h2b("95a508cf10f877cf0457af3503a6cb3192763d5c15a7b9a58e40dc543efae889"), ); // Header data check @@ -337,7 +336,7 @@ fn on_normal_block() { println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( Sassafras::randomness_accumulator(), - h2b("9f2b9fd19a772c34d437dcd8b84a927e73a5cb43d3d1cd00093223d60d2b4843"), + h2b("95a508cf10f877cf0457af3503a6cb3192763d5c15a7b9a58e40dc543efae889"), ); let header = finalize_block(end_block); @@ -346,9 +345,10 @@ fn on_normal_block() { assert!(!ClaimTemporaryData::::exists()); common_assertions(); + println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( Sassafras::randomness_accumulator(), - h2b("be9261adb9686dfd3f23f8a276b7acc7f4beb3137070beb64c282ac22d84cbf0"), + h2b("5465cb257ad20cd4b9400a9fc85af7b1e2e72b59debd8ca06580dfb76bfca394"), ); // Header data check @@ -394,12 +394,12 @@ fn produce_epoch_change_digest_no_config() { println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); assert_eq!( Sassafras::next_randomness(), - h2b("d3a18b857af6ecc7b52f047107e684fff0058b5722d540a296d727e37eaa55b3"), + h2b("c4d374ed47b71e1c29e57143db23861916ff2d0c59ead4c51070d42ff4af2830"), ); println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( Sassafras::randomness_accumulator(), - h2b("bf0f1228f4ff953c8c1bda2cceb668bf86ea05d7ae93e26d021c9690995d5279"), + h2b("c6d84d1f389853959c39271a38010f2f27abe6ff56cc419cf9e89eafcae1ab5e"), ); let header = finalize_block(end_block); @@ -411,12 +411,12 @@ fn produce_epoch_change_digest_no_config() { println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); assert_eq!( Sassafras::next_randomness(), - h2b("d3a18b857af6ecc7b52f047107e684fff0058b5722d540a296d727e37eaa55b3"), + h2b("c4d374ed47b71e1c29e57143db23861916ff2d0c59ead4c51070d42ff4af2830"), ); println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( Sassafras::randomness_accumulator(), - h2b("8a1ceb346036c386d021264b10912c8b656799668004c4a487222462b394cd89"), + h2b("6ca02b90e14ef11b3855069794da7e9d4007526b0588c426c3e3533b0b6ade7a"), ); // Header data check @@ -829,9 +829,9 @@ fn submit_tickets_with_ring_proof_check_works() { // Check state after submission assert_eq!( TicketsMeta::::get(), - TicketsMetadata { unsorted_tickets_count: 16, tickets_count: [0, 0] }, + TicketsMetadata { unsorted_tickets_count: 13, tickets_count: [0, 0] }, ); - assert_eq!(UnsortedSegments::::get(0).len(), 16); + assert_eq!(UnsortedSegments::::get(0).len(), 13); assert_eq!(UnsortedSegments::::get(1).len(), 0); finalize_block(start_block); From 3c4036a2e17bc32490687f83065cf29e15f05bc3 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 19:11:31 +0100 Subject: [PATCH 10/29] Cleanup --- substrate/frame/sassafras/src/lib.rs | 19 +++++++++---------- substrate/frame/sassafras/src/tests.rs | 14 +++++++------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/substrate/frame/sassafras/src/lib.rs b/substrate/frame/sassafras/src/lib.rs index 1f34393bb805e..35539fd35f22c 100644 --- a/substrate/frame/sassafras/src/lib.rs +++ b/substrate/frame/sassafras/src/lib.rs @@ -199,6 +199,12 @@ pub mod pallet { #[pallet::getter(fn randomness_accumulator)] pub(crate) type RandomnessAccumulator = StorageValue<_, Randomness, ValueQuery>; + /// Per slot randomness used to feed the randomness accumulator. + /// + /// The value is ephemeral and is cleared on block finalization. + #[pallet::storage] + pub(crate) type SlotRandomness = StorageValue<_, Randomness>; + /// The configuration for the current epoch. #[pallet::storage] #[pallet::getter(fn config)] @@ -271,12 +277,6 @@ pub mod pallet { #[pallet::storage] pub type RingVerifierData = StorageValue<_, vrf::RingVerifierKey>; - /// Slot claim VRF pre-output used to generate per-slot randomness. - /// - /// The value is ephemeral and is cleared on block finalization. - #[pallet::storage] - pub(crate) type ClaimTemporaryData = StorageValue<_, vrf::VrfPreOutput>; - /// Genesis configuration for Sassafras protocol. #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] @@ -323,8 +323,8 @@ pub mod pallet { Self::post_genesis_initialize(claim.slot); } - let randomness_pre_output = claim.vrf_signature.pre_output; - ClaimTemporaryData::::put(randomness_pre_output); + let randomness = claim.vrf_signature.pre_output.make_bytes(); + SlotRandomness::::put(randomness); let trigger_weight = T::EpochChangeTrigger::trigger::(block_num); @@ -336,9 +336,8 @@ pub mod pallet { // to the accumulator. If we've determined that this block was the first in // a new epoch, the changeover logic has already occurred at this point // (i.e. `enact_epoch_change` has already been called). - let randomness_pre_output = ClaimTemporaryData::::take() + let randomness = SlotRandomness::::take() .expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed"); - let randomness = randomness_pre_output.make_bytes(); Self::deposit_slot_randomness(&randomness); // Check if we are in the epoch's second half. diff --git a/substrate/frame/sassafras/src/tests.rs b/substrate/frame/sassafras/src/tests.rs index 0eaceb55f7b06..193ec675a8d08 100644 --- a/substrate/frame/sassafras/src/tests.rs +++ b/substrate/frame/sassafras/src/tests.rs @@ -261,7 +261,7 @@ fn on_first_block_after_genesis() { // Post-initialization status - assert!(ClaimTemporaryData::::exists()); + assert!(SlotRandomness::::exists()); common_assertions(); println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( @@ -273,7 +273,7 @@ fn on_first_block_after_genesis() { // Post-finalization status - assert!(!ClaimTemporaryData::::exists()); + assert!(!SlotRandomness::::exists()); common_assertions(); assert_eq!( Sassafras::randomness_accumulator(), @@ -331,7 +331,7 @@ fn on_normal_block() { // Post-initialization status - assert!(ClaimTemporaryData::::exists()); + assert!(SlotRandomness::::exists()); common_assertions(); println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( @@ -343,7 +343,7 @@ fn on_normal_block() { // Post-finalization status - assert!(!ClaimTemporaryData::::exists()); + assert!(!SlotRandomness::::exists()); common_assertions(); println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); assert_eq!( @@ -389,7 +389,7 @@ fn produce_epoch_change_digest_no_config() { // Post-initialization status - assert!(ClaimTemporaryData::::exists()); + assert!(SlotRandomness::::exists()); common_assertions(); println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); assert_eq!( @@ -406,7 +406,7 @@ fn produce_epoch_change_digest_no_config() { // Post-finalization status - assert!(!ClaimTemporaryData::::exists()); + assert!(!SlotRandomness::::exists()); common_assertions(); println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); assert_eq!( @@ -670,7 +670,7 @@ fn block_allowed_to_skip_epochs() { // Post-initialization status - assert!(ClaimTemporaryData::::exists()); + assert!(SlotRandomness::::exists()); assert_eq!(Sassafras::genesis_slot(), start_slot); assert_eq!(Sassafras::current_slot(), start_slot + offset); assert_eq!(Sassafras::epoch_index(), 4); From 6156d403064f6c84bb0f619dd49890842cc14d8d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Feb 2025 23:06:40 +0100 Subject: [PATCH 11/29] remove from default feats --- substrate/primitives/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index b556798708604..3c81b53d7354a 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -85,7 +85,7 @@ harness = false bench = false [features] -default = ["std", "bandersnatch-experimental"] +default = ["std"] std = [ "ark-ec-vrfs?/std", From a07c51a8ea5cc9e0f35cb55685c200e487f41d3f Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 27 Feb 2025 00:07:39 +0100 Subject: [PATCH 12/29] more cleanup --- substrate/primitives/core/src/bandersnatch.rs | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 4c588e33fc1de..7c3ae2abe6218 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -31,9 +31,9 @@ use ark_ec_vrfs::{ ark_ec::CurveGroup, ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, }, - suites::bandersnatch::te as bandersnatch, + suites::bandersnatch::{self, BandersnatchSha512Ell2, Secret}, + Suite, }; -use bandersnatch::{BandersnatchSha512Ell2 as BandersnatchSuite, Secret}; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; @@ -146,9 +146,8 @@ impl TraitPair for Pair { #[cfg(feature = "full_crypto")] fn sign(&self, data: &[u8]) -> Signature { - use ark_ec_vrfs::Suite; - use bandersnatch::BandersnatchSha512Ell2; - let input = bandersnatch::Input::new(data).unwrap(); + let input = bandersnatch::Input::new(data).expect("Hash to curve can't fail; qed"); + let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); let gk = BandersnatchSha512Ell2::generator() * k; let c = BandersnatchSha512Ell2::challenge( @@ -159,13 +158,11 @@ impl TraitPair for Pair { let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; bandersnatch::IetfProof { c, s } .serialize_compressed(&mut raw_signature.as_mut_slice()) - .unwrap(); + .expect("serialization length is constant and checked by test; qed"); Signature::from_raw(raw_signature) } fn verify>(signature: &Signature, data: M, public: &Public) -> bool { - use ark_ec_vrfs::Suite; - use bandersnatch::BandersnatchSha512Ell2; let Ok(signature) = bandersnatch::IetfProof::deserialize_compressed(&signature.0[..]) else { return false @@ -217,17 +214,9 @@ pub mod vrf { /// This object is used to produce an arbitrary number of verifiable pseudo random /// bytes and is often called pre-output to emphasize that this is not the actual /// output of the VRF but an object capable of generating the output. - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub struct VrfPreOutput(pub(super) bandersnatch::Output); - // Workaround until traits are not implemented for newtypes https://github.com/davxy/ark-ec-vrfs/issues/41 - impl PartialEq for VrfPreOutput { - fn eq(&self, other: &Self) -> bool { - self.0 .0 == other.0 .0 - } - } - impl Eq for VrfPreOutput {} - impl Encode for VrfPreOutput { fn encode(&self) -> Vec { let mut bytes = [0; PREOUT_SERIALIZED_SIZE]; @@ -384,11 +373,10 @@ pub mod vrf { pub mod ring_vrf { use super::{vrf::*, *}; use ark_ec_vrfs::ring::{Prover, Verifier}; - use bandersnatch::{RingContext as RingContextImpl, VerifierKey as VerifierKeyImpl}; + use bandersnatch::{RingContext as RingContextImpl, RingVerifierKey as RingVerifierKeyImpl}; pub use bandersnatch::{RingProver, RingVerifier}; // Max size of serialized ring-vrf context given `domain_len`. - // TODO @davxy: test this pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize { // const G1_POINT_COMPRESSED_SIZE: usize = 48; // const G2_POINT_COMPRESSED_SIZE: usize = 96; @@ -396,7 +384,7 @@ pub mod ring_vrf { const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); + let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); let g1_points_num = 3 * domain_size as usize + 1; OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + @@ -412,7 +400,7 @@ pub mod ring_vrf { RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE; /// Ring verifier key - pub struct RingVerifierKey(VerifierKeyImpl); + pub struct RingVerifierKey(RingVerifierKeyImpl); impl Encode for RingVerifierKey { fn encode(&self) -> Vec { @@ -427,7 +415,7 @@ pub mod ring_vrf { fn decode(input: &mut R) -> Result { let mut buf = vec![0; RING_VERIFIER_KEY_SERIALIZED_SIZE]; input.read(&mut buf[..])?; - let vk = VerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice()) + let vk = RingVerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice()) .map_err(|_| "RingVerifierKey decode error")?; Ok(RingVerifierKey(vk)) } @@ -623,12 +611,12 @@ mod tests { #[test] fn backend_assumptions_sanity_check() { - use bandersnatch::{Input, RingContext as RingContextImpl, Secret}; + use bandersnatch::{Input, RingContext as RingContextImpl}; const OVERHEAD_SIZE: usize = 257; let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); - let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); + let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); From df014b85fe8ca050103cdaca57d003bff98dd8bd Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 27 Feb 2025 15:08:12 +0100 Subject: [PATCH 13/29] Lock to rev --- Cargo.lock | 9 +++++---- Cargo.toml | 6 ++---- substrate/primitives/core/src/bandersnatch.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40598b7b259ed..9bc36051bdc3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -574,7 +574,8 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" -version = "0.1.0" +version = "0.1.1" +source = "git+https://github.com/davxy/ark-ec-vrfs?rev=526bf7c#526bf7c88ff81fde061386a97bceeedd06322de8" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", @@ -16813,7 +16814,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -21081,7 +21082,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.13.0", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -21127,7 +21128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2 1.0.93", "quote 1.0.38", "syn 2.0.98", diff --git a/Cargo.toml b/Cargo.toml index 8ef00acb34fb3..fa1afb522bd62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -615,7 +615,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { version = "0.1.0", default-features = false } +ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "526bf7c", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } @@ -1434,6 +1434,7 @@ overflow-checks = true # # This list is ordered alphabetically. [profile.dev.package] +ark-ec-vrfs = { opt-level = 3 } blake2 = { opt-level = 3 } blake2b_simd = { opt-level = 3 } chacha20poly1305 = { opt-level = 3 } @@ -1478,6 +1479,3 @@ wasmi = { opt-level = 3 } x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } - -[patch.crates-io] -ark-ec-vrfs = { path = "/mnt/ssd/develop/personal/ark-ec-vrfs" } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 7c3ae2abe6218..127720366b26e 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -574,7 +574,7 @@ pub mod ring_vrf { /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let Ok(proof) = - bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) + bandersnatch::Proof::deserialize_compressed_unchecked(self.proof.as_slice()) else { return false }; From 9826d2820b980962a79e2f63fa3be23dd95d3c93 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Feb 2025 16:28:03 +0100 Subject: [PATCH 14/29] Cleanup --- Cargo.lock | 2 +- Cargo.toml | 2 +- substrate/primitives/core/src/bandersnatch.rs | 60 +++++++++---------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bc36051bdc3e..09e43714bed67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" version = "0.1.1" -source = "git+https://github.com/davxy/ark-ec-vrfs?rev=526bf7c#526bf7c88ff81fde061386a97bceeedd06322de8" +source = "git+https://github.com/davxy/ark-ec-vrfs?rev=4785f4c#4785f4c877f7910515bd46dfb7693cea1c0b0d4f" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index fa1afb522bd62..ec3788ca0f5b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -615,7 +615,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "526bf7c", default-features = false } +ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "4785f4c", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 127720366b26e..64e6bdd7b9316 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -31,7 +31,7 @@ use ark_ec_vrfs::{ ark_ec::CurveGroup, ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, }, - suites::bandersnatch::{self, BandersnatchSha512Ell2, Secret}, + suites::bandersnatch::{self, BandersnatchSha512Ell2 as BandersnatchSuite, Secret}, Suite, }; use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; @@ -42,9 +42,6 @@ use alloc::vec::Vec; /// Identifier used to match public keys against bandersnatch-vrf keys. pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); -/// Context used to produce a plain signature without any VRF input/output. -pub const SIGNING_CTX: &[u8] = b"BandersnatchSigningContext"; - /// The byte length of secret key seed. pub const SEED_SERIALIZED_SIZE: usize = 32; @@ -67,10 +64,7 @@ impl CryptoType for Public { type Pair = Pair; } -/// Bandersnatch signature. -/// -/// The signature is created via [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript -/// `label`. +/// Bandersnatch Schnorr signature. pub type Signature = SignatureBytes; impl CryptoType for Signature { @@ -85,6 +79,7 @@ type Seed = [u8; SEED_SERIALIZED_SIZE]; pub struct Pair { secret: Secret, seed: Seed, + prefix: Seed, } impl Pair { @@ -108,8 +103,12 @@ impl TraitPair for Pair { } let mut seed = [0; SEED_SERIALIZED_SIZE]; seed.copy_from_slice(seed_slice); + let h = ark_ec_vrfs::utils::hash::<::Hasher>(&seed); + // Extract and cache the high half. + let mut prefix = [0; SEED_SERIALIZED_SIZE]; + prefix.copy_from_slice(&h[32..64]); let secret = Secret::from_seed(&seed); - Ok(Pair { secret, seed }) + Ok(Pair { secret, seed, prefix }) } /// Derive a child key from a series of given (hard) junctions. @@ -146,14 +145,13 @@ impl TraitPair for Pair { #[cfg(feature = "full_crypto")] fn sign(&self, data: &[u8]) -> Signature { - let input = bandersnatch::Input::new(data).expect("Hash to curve can't fail; qed"); - - let k = BandersnatchSha512Ell2::nonce(&self.secret.scalar, input); - let gk = BandersnatchSha512Ell2::generator() * k; - let c = BandersnatchSha512Ell2::challenge( - &[&gk.into_affine(), &self.secret.public.0, &input.0], - &[], - ); + // Deterministic nonce for plain Schnorr signature. + // Inspired by ed25519 + let h = + &ark_ec_vrfs::utils::hash::<::Hasher>(&self.prefix)[..32]; + let k = ark_ec_vrfs::codec::scalar_decode::(h); + let gk = BandersnatchSuite::generator() * k; + let c = BandersnatchSuite::challenge(&[&gk.into_affine(), &self.secret.public.0], data); let s = k + c * self.secret.scalar; let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE]; bandersnatch::IetfProof { c, s } @@ -170,15 +168,14 @@ impl TraitPair for Pair { let Ok(public) = bandersnatch::Public::deserialize_compressed(&public.0[..]) else { return false }; - let input = bandersnatch::Input::new(data.as_ref()).expect("Can't fail"); - let gs = BandersnatchSha512Ell2::generator() * signature.s; + let gs = BandersnatchSuite::generator() * signature.s; let yc = public.0 * signature.c; let rv = gs - yc; - let cv = BandersnatchSha512Ell2::challenge(&[&rv.into_affine(), &public.0, &input.0], &[]); + let cv = BandersnatchSuite::challenge(&[&rv.into_affine(), &public.0], data.as_ref()); signature.c == cv } - /// Return a vector filled with the seed (32 bytes). + /// Return a vector filled with the seed. fn to_raw_vec(&self) -> Vec { self.seed().to_vec() } @@ -204,16 +201,16 @@ pub mod vrf { impl VrfInput { /// Construct a new VRF input. - pub fn new(data: impl AsRef<[u8]>) -> Self { - Self(bandersnatch::Input::new(data.as_ref()).expect("Can't fail")) + /// + /// Hash to Curve (H2C) using Elligator2. + pub fn new(data: &[u8]) -> Self { + Self(bandersnatch::Input::new(data).expect("H2C for Bandersnatch can't fail; qed")) } } /// VRF pre-output derived from [`VrfInput`] using a [`VrfSecret`]. /// - /// This object is used to produce an arbitrary number of verifiable pseudo random - /// bytes and is often called pre-output to emphasize that this is not the actual - /// output of the VRF but an object capable of generating the output. + /// This object is hashed to produce the actual VRF output. #[derive(Clone, Debug, PartialEq, Eq)] pub struct VrfPreOutput(pub(super) bandersnatch::Output); @@ -384,7 +381,7 @@ pub mod ring_vrf { const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); + let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); let g1_points_num = 3 * domain_size as usize + 1; OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + @@ -404,9 +401,10 @@ pub mod ring_vrf { impl Encode for RingVerifierKey { fn encode(&self) -> Vec { - const ERR_STR: &str = "serialization length is constant and checked by test; qed"; let mut buf = Vec::with_capacity(RING_VERIFIER_KEY_SERIALIZED_SIZE); - self.0.serialize_compressed(&mut buf).expect(ERR_STR); + self.0 + .serialize_compressed(&mut buf) + .expect("serialization length is constant and checked by test; qed"); buf } } @@ -574,7 +572,7 @@ pub mod ring_vrf { /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let Ok(proof) = - bandersnatch::Proof::deserialize_compressed_unchecked(self.proof.as_slice()) + bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) else { return false }; @@ -616,7 +614,7 @@ mod tests { let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); - let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); + let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); From 172011340be0910f5bf716f894ae8bed0f171cc2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Feb 2025 16:47:10 +0100 Subject: [PATCH 15/29] Bump ark-ec-vrf rev --- Cargo.lock | 2 +- Cargo.toml | 2 +- substrate/primitives/core/src/bandersnatch.rs | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5cfdb2b8a1b3..5461b22f76762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,7 +526,7 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" version = "0.1.1" -source = "git+https://github.com/davxy/ark-ec-vrfs?rev=4785f4c#4785f4c877f7910515bd46dfb7693cea1c0b0d4f" +source = "git+https://github.com/davxy/ark-ec-vrfs?rev=d8b87b2#d8b87b2b32d8620182afaf4a623190ecae797b3a" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 55420848ac16e..c2d004393e93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -614,7 +614,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "4785f4c", default-features = false } +ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "d8b87b2", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 64e6bdd7b9316..cd66ab9cab9c3 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -26,10 +26,11 @@ use crate::crypto::{ ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, }; +use alloc::vec::Vec; use ark_ec_vrfs::{ - prelude::{ - ark_ec::CurveGroup, - ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, + ark::{ + ec::CurveGroup, + serialize::{CanonicalDeserialize, CanonicalSerialize}, }, suites::bandersnatch::{self, BandersnatchSha512Ell2 as BandersnatchSuite, Secret}, Suite, @@ -37,8 +38,6 @@ use ark_ec_vrfs::{ use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; -use alloc::vec::Vec; - /// Identifier used to match public keys against bandersnatch-vrf keys. pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); From 4d24263047b2f5082a5da97879b3c3916516a9e1 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Feb 2025 17:51:07 +0100 Subject: [PATCH 16/29] Use crates.io --- Cargo.lock | 3 ++- Cargo.toml | 2 +- substrate/primitives/core/src/bandersnatch.rs | 6 +++--- substrate/primitives/keystore/src/testing.rs | 11 +++-------- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5461b22f76762..ef8a757b5f860 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,7 +526,8 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" version = "0.1.1" -source = "git+https://github.com/davxy/ark-ec-vrfs?rev=d8b87b2#d8b87b2b32d8620182afaf4a623190ecae797b3a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5f2414d02d20c44288aae80c512e410ab660ff6829a721ab0cb28f8aca2d55" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index c2d004393e93c..5264279fef352 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -614,7 +614,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { git = "https://github.com/davxy/ark-ec-vrfs", rev = "d8b87b2", default-features = false } +ark-ec-vrfs = { version = "0.1.1", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index cd66ab9cab9c3..015b98d32c503 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -28,9 +28,9 @@ use crate::crypto::{ }; use alloc::vec::Vec; use ark_ec_vrfs::{ - ark::{ - ec::CurveGroup, - serialize::{CanonicalDeserialize, CanonicalSerialize}, + reexports::{ + ark_ec::CurveGroup, + ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, }, suites::bandersnatch::{self, BandersnatchSha512Ell2 as BandersnatchSuite, Secret}, Suite, diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 745f42e3477ab..7939ee81005a0 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -524,10 +524,7 @@ mod tests { let secret_uri = "//Alice"; let key_pair = bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); - - let in1 = bandersnatch::vrf::VrfInput::new("in", "foo"); - let sign_data = - bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", Some("m1"), Some(in1)); + let sign_data = bandersnatch::vrf::VrfSignData::new(b"vrf_input", b"aux_data"); let result = store.bandersnatch_vrf_sign(BANDERSNATCH, &key_pair.public(), &sign_data); assert!(result.unwrap().is_none()); @@ -555,15 +552,13 @@ mod tests { .collect(); let prover_idx = 3; - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx); let secret_uri = "//Alice"; let pair = bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); pks[prover_idx] = pair.public(); - let in1 = bandersnatch::vrf::VrfInput::new("in1", "foo"); - let sign_data = - bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", &["m1", "m2"], [in1]); + let sign_data = bandersnatch::vrf::VrfSignData::new(b"vrf_input", b"aux_data"); let result = store.bandersnatch_ring_vrf_sign(BANDERSNATCH, &pair.public(), &sign_data, &prover); From af82993690fe884ce321fecfd4f5257dde92ba14 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Feb 2025 18:04:54 +0100 Subject: [PATCH 17/29] Fix --- substrate/primitives/core/src/bandersnatch.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 015b98d32c503..46cfe3bd1ebba 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -26,7 +26,7 @@ use crate::crypto::{ ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, }; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use ark_ec_vrfs::{ reexports::{ ark_ec::CurveGroup, @@ -188,7 +188,7 @@ impl CryptoType for Pair { pub mod vrf { use super::*; use crate::crypto::VrfCrypto; - use ark_ec_vrfs::ietf::{Prover, Verifier}; + use ark_ec_vrfs::ietf::{Prover as _, Verifier as _}; /// [`VrfSignature`] serialized size. pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize = @@ -278,7 +278,7 @@ pub mod vrf { impl VrfSignData { /// Construct a new data to be signed. pub fn new(vrf_input_data: &[u8], aux_data: &[u8]) -> Self { - Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_owned() } + Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_vec() } } } @@ -368,7 +368,7 @@ pub mod vrf { /// Bandersnatch Ring-VRF types and operations. pub mod ring_vrf { use super::{vrf::*, *}; - use ark_ec_vrfs::ring::{Prover, Verifier}; + use ark_ec_vrfs::ring::{Prover as _, Verifier as _}; use bandersnatch::{RingContext as RingContextImpl, RingVerifierKey as RingVerifierKeyImpl}; pub use bandersnatch::{RingProver, RingVerifier}; From 5c1cc4121bff23f2b125ed7cdbe69b06696e4517 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sat, 1 Mar 2025 07:35:31 +0100 Subject: [PATCH 18/29] Resolve warnings --- substrate/primitives/core/src/bandersnatch.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 46cfe3bd1ebba..7c1011aef4c82 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -78,6 +78,8 @@ type Seed = [u8; SEED_SERIALIZED_SIZE]; pub struct Pair { secret: Secret, seed: Seed, + // This is only read back in the sign operaton + #[allow(dead_code)] prefix: Seed, } @@ -146,8 +148,8 @@ impl TraitPair for Pair { fn sign(&self, data: &[u8]) -> Signature { // Deterministic nonce for plain Schnorr signature. // Inspired by ed25519 - let h = - &ark_ec_vrfs::utils::hash::<::Hasher>(&self.prefix)[..32]; + let h_in = [&self.prefix[..32], data].concat(); + let h = &ark_ec_vrfs::utils::hash::<::Hasher>(&h_in)[..32]; let k = ark_ec_vrfs::codec::scalar_decode::(h); let gk = BandersnatchSuite::generator() * k; let c = BandersnatchSuite::challenge(&[&gk.into_affine(), &self.secret.public.0], data); @@ -188,7 +190,6 @@ impl CryptoType for Pair { pub mod vrf { use super::*; use crate::crypto::VrfCrypto; - use ark_ec_vrfs::ietf::{Prover as _, Verifier as _}; /// [`VrfSignature`] serialized size. pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize = @@ -306,6 +307,7 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature { + use ark_ec_vrfs::ietf::Prover; let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); @@ -331,6 +333,7 @@ pub mod vrf { impl VrfPublic for Public { fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool { + use ark_ec_vrfs::ietf::Verifier; let Ok(public) = bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) else { @@ -368,7 +371,6 @@ pub mod vrf { /// Bandersnatch Ring-VRF types and operations. pub mod ring_vrf { use super::{vrf::*, *}; - use ark_ec_vrfs::ring::{Prover as _, Verifier as _}; use bandersnatch::{RingContext as RingContextImpl, RingVerifierKey as RingVerifierKeyImpl}; pub use bandersnatch::{RingProver, RingVerifier}; @@ -552,6 +554,7 @@ pub mod ring_vrf { /// signing [`Pair`] is part of the ring from which the [`RingProver`] has /// been constructed. If not, the produced signature is just useless. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { + use ark_ec_vrfs::ring::Prover; let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = @@ -570,6 +573,7 @@ pub mod ring_vrf { /// The signature is verifiable if it has been produced by a member of the ring /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + use ark_ec_vrfs::ring::Verifier; let Ok(proof) = bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) else { From 5c8cdab7f0ae123006c1267a3f3cc2978cc11e6d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 20 Mar 2025 16:25:15 +0100 Subject: [PATCH 19/29] Bump ark-ec-vrfs version --- Cargo.lock | 4 +- Cargo.toml | 2 +- substrate/primitives/core/src/bandersnatch.rs | 37 ++++++++++--------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef8a757b5f860..50d9fc97e64db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,9 +525,9 @@ dependencies = [ [[package]] name = "ark-ec-vrfs" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5f2414d02d20c44288aae80c512e410ab660ff6829a721ab0cb28f8aca2d55" +checksum = "d12ec0c78bba84a70864811b8e8abde8ee9ac3fc4e4ef977cc1b9a2ec9d3127c" dependencies = [ "ark-bls12-381 0.5.0", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 5264279fef352..3355c6d216403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -614,7 +614,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { version = "0.1.1", default-features = false } +ark-ec-vrfs = { version = "0.1.2", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 7c1011aef4c82..a7bb295819eac 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -371,19 +371,16 @@ pub mod vrf { /// Bandersnatch Ring-VRF types and operations. pub mod ring_vrf { use super::{vrf::*, *}; - use bandersnatch::{RingContext as RingContextImpl, RingVerifierKey as RingVerifierKeyImpl}; + use bandersnatch::{RingProofParams, RingVerifierKey as RingVerifierKeyImpl}; pub use bandersnatch::{RingProver, RingVerifier}; // Max size of serialized ring-vrf context given `domain_len`. pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize { - // const G1_POINT_COMPRESSED_SIZE: usize = 48; - // const G2_POINT_COMPRESSED_SIZE: usize = 96; const G1_POINT_UNCOMPRESSED_SIZE: usize = 96; const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let domain_size = ark_ec_vrfs::ring::domain_size::(ring_size); - let g1_points_num = 3 * domain_size as usize + 1; + let g1_points_num = ark_ec_vrfs::ring::pcs_domain_size::(ring_size); OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE @@ -439,12 +436,12 @@ pub mod ring_vrf { /// /// Generic parameter `R` represents the ring size. #[derive(Clone)] - pub struct RingContext(RingContextImpl); + pub struct RingContext(RingProofParams); impl RingContext { /// Build an dummy instance for testing purposes. pub fn new_testing() -> Self { - Self(RingContextImpl::from_seed(R, [0; 32])) + Self(RingProofParams::from_seed(R, [0; 32])) } /// Get the keyset max size. @@ -479,7 +476,7 @@ pub mod ring_vrf { /// retain the full `RingContext` for ring signature verification. Instead, the /// `VerifierKey` contains only the essential information needed to verify ring proofs. pub fn verifier_no_context(verifier_key: RingVerifierKey) -> RingVerifier { - RingContextImpl::verifier_no_context(verifier_key.0, R) + RingProofParams::verifier_no_context(verifier_key.0, R) } fn make_ring_vector(public_keys: &[Public]) -> Vec { @@ -488,7 +485,7 @@ pub mod ring_vrf { .iter() .map(|pk| { AffinePoint::deserialize_compressed_unchecked(pk.as_slice()) - .unwrap_or(RingContextImpl::padding_point()) + .unwrap_or(RingProofParams::padding_point()) }) .collect() } @@ -508,7 +505,7 @@ pub mod ring_vrf { fn decode(input: &mut I) -> Result { let mut buf = vec![0; ring_context_serialized_size(R)]; input.read(&mut buf[..])?; - let ctx = RingContextImpl::deserialize_uncompressed_unchecked(buf.as_slice()) + let ctx = RingProofParams::deserialize_uncompressed_unchecked(buf.as_slice()) .map_err(|_| "RingContext decode error")?; Ok(RingContext(ctx)) } @@ -612,13 +609,19 @@ mod tests { #[test] fn backend_assumptions_sanity_check() { - use bandersnatch::{Input, RingContext as RingContextImpl}; - const OVERHEAD_SIZE: usize = 257; - - let ctx = RingContextImpl::from_seed(TEST_RING_SIZE, [0_u8; 32]); - - let domain_size = ark_ec_vrfs::ring::domain_size::(TEST_RING_SIZE); - assert_eq!(ctx.max_ring_size(), domain_size - OVERHEAD_SIZE); + use bandersnatch::{Input, RingProofParams}; + + let ctx = RingProofParams::from_seed(TEST_RING_SIZE, [0_u8; 32]); + + let domain_size = ark_ec_vrfs::ring::pcs_domain_size::(TEST_RING_SIZE); + assert_eq!(domain_size, ctx.pcs.powers_in_g1.len()); + let domain_size2 = + ark_ec_vrfs::ring::pcs_domain_size::(ctx.max_ring_size()); + assert_eq!(domain_size, domain_size2); + assert_eq!( + ark_ec_vrfs::ring::max_ring_size_from_pcs_domain_size::(domain_size), + ctx.max_ring_size() + ); assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE)); From 42503c650426ccad35c4d690d34757df2bfe184a Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 20 Mar 2025 16:27:07 +0100 Subject: [PATCH 20/29] Doc --- substrate/primitives/consensus/sassafras/src/vrf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/primitives/consensus/sassafras/src/vrf.rs b/substrate/primitives/consensus/sassafras/src/vrf.rs index 13b6205423493..4a18978d3db34 100644 --- a/substrate/primitives/consensus/sassafras/src/vrf.rs +++ b/substrate/primitives/consensus/sassafras/src/vrf.rs @@ -34,7 +34,7 @@ pub const RING_SIZE: usize = 1024; /// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_SIZE`]. pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext; -/// TODO +/// Input for slot claim pub fn slot_claim_input(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput { let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()] .concat(); From 555ae8d70f8d92efc92df00688d4333499042ff2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Mar 2025 09:09:16 +0100 Subject: [PATCH 21/29] Backend renamed --- Cargo.lock | 40 +++++++++---------- Cargo.toml | 4 +- substrate/primitives/core/Cargo.toml | 6 +-- substrate/primitives/core/src/bandersnatch.rs | 35 ++++++++-------- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50d9fc97e64db..a2e9a13ee74c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,25 +523,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ec-vrfs" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d12ec0c78bba84a70864811b8e8abde8ee9ac3fc4e4ef977cc1b9a2ec9d3127c" -dependencies = [ - "ark-bls12-381 0.5.0", - "ark-ec 0.5.0", - "ark-ed-on-bls12-381-bandersnatch 0.5.0", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "digest 0.10.7", - "rand_chacha 0.3.1", - "sha2 0.10.8", - "w3f-ring-proof", - "zeroize", -] - [[package]] name = "ark-ed-on-bls12-377" version = "0.4.0" @@ -887,6 +868,25 @@ dependencies = [ "sha3 0.10.8", ] +[[package]] +name = "ark-vrf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9501da18569b2afe0eb934fb7afd5a247d238b94116155af4dd068f319adfe6d" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ed-on-bls12-381-bandersnatch 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_chacha 0.3.1", + "sha2 0.10.8", + "w3f-ring-proof", + "zeroize", +] + [[package]] name = "array-bytes" version = "6.2.2" @@ -22356,7 +22356,7 @@ dependencies = [ name = "sp-core" version = "28.0.0" dependencies = [ - "ark-ec-vrfs", + "ark-vrf", "array-bytes", "bitflags 1.3.2", "blake2 0.10.6", diff --git a/Cargo.toml b/Cargo.toml index 3355c6d216403..ac0286776fb3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -614,7 +614,7 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-ec-vrfs = { version = "0.1.2", default-features = false } +ark-vrf = { version = "0.1.0", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } @@ -1432,7 +1432,7 @@ overflow-checks = true # # This list is ordered alphabetically. [profile.dev.package] -ark-ec-vrfs = { opt-level = 3 } +ark-vrf = { opt-level = 3 } blake2 = { opt-level = 3 } blake2b_simd = { opt-level = 3 } chacha20poly1305 = { opt-level = 3 } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 3c81b53d7354a..32a4bd5739922 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -70,7 +70,7 @@ secp256k1 = { features = [ # bls crypto w3f-bls = { optional = true, workspace = true } # bandersnatch crypto -ark-ec-vrfs = { optional = true, workspace = true, features = ["bandersnatch", "ring"] } +ark-vrf = { optional = true, workspace = true, features = ["bandersnatch", "ring"] } [dev-dependencies] criterion = { workspace = true, default-features = true } @@ -88,7 +88,7 @@ bench = false default = ["std"] std = [ - "ark-ec-vrfs?/std", + "ark-vrf?/std", "bip39/rand", "bip39/std", "blake2/std", @@ -163,4 +163,4 @@ bls-experimental = ["w3f-bls"] # This feature adds Bandersnatch crypto primitives. # It should not be used in production since the implementation and interface may still # be subject to significant changes. -bandersnatch-experimental = ["ark-ec-vrfs"] +bandersnatch-experimental = ["ark-vrf"] diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index a7bb295819eac..b3086208c2353 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -27,7 +27,7 @@ use crate::crypto::{ PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic, }; use alloc::{vec, vec::Vec}; -use ark_ec_vrfs::{ +use ark_vrf::{ reexports::{ ark_ec::CurveGroup, ark_serialize::{CanonicalDeserialize, CanonicalSerialize}, @@ -104,7 +104,7 @@ impl TraitPair for Pair { } let mut seed = [0; SEED_SERIALIZED_SIZE]; seed.copy_from_slice(seed_slice); - let h = ark_ec_vrfs::utils::hash::<::Hasher>(&seed); + let h = ark_vrf::utils::hash::<::Hasher>(&seed); // Extract and cache the high half. let mut prefix = [0; SEED_SERIALIZED_SIZE]; prefix.copy_from_slice(&h[32..64]); @@ -149,8 +149,8 @@ impl TraitPair for Pair { // Deterministic nonce for plain Schnorr signature. // Inspired by ed25519 let h_in = [&self.prefix[..32], data].concat(); - let h = &ark_ec_vrfs::utils::hash::<::Hasher>(&h_in)[..32]; - let k = ark_ec_vrfs::codec::scalar_decode::(h); + let h = &ark_vrf::utils::hash::<::Hasher>(&h_in)[..32]; + let k = ark_vrf::codec::scalar_decode::(h); let gk = BandersnatchSuite::generator() * k; let c = BandersnatchSuite::challenge(&[&gk.into_affine(), &self.secret.public.0], data); let s = k + c * self.secret.scalar; @@ -307,7 +307,7 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature { - use ark_ec_vrfs::ietf::Prover; + use ark_vrf::ietf::Prover; let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data); @@ -333,15 +333,15 @@ pub mod vrf { impl VrfPublic for Public { fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool { - use ark_ec_vrfs::ietf::Verifier; + use ark_vrf::ietf::Verifier; let Ok(public) = bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice()) else { return false }; - let Ok(proof) = ark_ec_vrfs::ietf::Proof::deserialize_compressed_unchecked( - signature.proof.as_slice(), - ) else { + let Ok(proof) = + ark_vrf::ietf::Proof::deserialize_compressed_unchecked(signature.proof.as_slice()) + else { return false }; public @@ -380,7 +380,7 @@ pub mod ring_vrf { const G2_POINT_UNCOMPRESSED_SIZE: usize = 192; const OVERHEAD_SIZE: usize = 16; const G2_POINTS_NUM: usize = 2; - let g1_points_num = ark_ec_vrfs::ring::pcs_domain_size::(ring_size); + let g1_points_num = ark_vrf::ring::pcs_domain_size::(ring_size); OVERHEAD_SIZE + g1_points_num * G1_POINT_UNCOMPRESSED_SIZE + G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE @@ -551,7 +551,7 @@ pub mod ring_vrf { /// signing [`Pair`] is part of the ring from which the [`RingProver`] has /// been constructed. If not, the produced signature is just useless. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { - use ark_ec_vrfs::ring::Prover; + use ark_vrf::ring::Prover; let pre_output_impl = self.secret.output(data.vrf_input.0); let pre_output = VrfPreOutput(pre_output_impl); let proof_impl = @@ -570,7 +570,7 @@ pub mod ring_vrf { /// The signature is verifiable if it has been produced by a member of the ring /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { - use ark_ec_vrfs::ring::Verifier; + use ark_vrf::ring::Verifier; let Ok(proof) = bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice()) else { @@ -613,13 +613,12 @@ mod tests { let ctx = RingProofParams::from_seed(TEST_RING_SIZE, [0_u8; 32]); - let domain_size = ark_ec_vrfs::ring::pcs_domain_size::(TEST_RING_SIZE); + let domain_size = ark_vrf::ring::pcs_domain_size::(TEST_RING_SIZE); assert_eq!(domain_size, ctx.pcs.powers_in_g1.len()); - let domain_size2 = - ark_ec_vrfs::ring::pcs_domain_size::(ctx.max_ring_size()); + let domain_size2 = ark_vrf::ring::pcs_domain_size::(ctx.max_ring_size()); assert_eq!(domain_size, domain_size2); assert_eq!( - ark_ec_vrfs::ring::max_ring_size_from_pcs_domain_size::(domain_size), + ark_vrf::ring::max_ring_size_from_pcs_domain_size::(domain_size), ctx.max_ring_size() ); @@ -645,13 +644,13 @@ mod tests { let ring_prover = ctx.prover(prover_key, prover_key_index); { - use ark_ec_vrfs::ietf::Prover; + use ark_vrf::ietf::Prover; let proof = secret.prove(input, preout, &[]); assert_eq!(proof.compressed_size(), SIGNATURE_SERIALIZED_SIZE); } { - use ark_ec_vrfs::ring::Prover; + use ark_vrf::ring::Prover; let proof = secret.prove(input, preout, &[], &ring_prover); assert_eq!(proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE); } From df12918e0624eda51da7a0e20ce47a3830747200 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 1 Apr 2025 17:28:41 +0200 Subject: [PATCH 22/29] Lock update --- Cargo.lock | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d81f4f240cf9..be807feec2144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,15 +303,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.11" @@ -20096,7 +20087,7 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", "zombienet-sdk", ] @@ -24426,7 +24417,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] From 7b9a15a55f5bd9c6b9666218dff6f05347e89963 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 1 Apr 2025 18:09:40 +0200 Subject: [PATCH 23/29] Fix static keyring pks --- substrate/primitives/keyring/src/bandersnatch.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/substrate/primitives/keyring/src/bandersnatch.rs b/substrate/primitives/keyring/src/bandersnatch.rs index 64d3c314124d5..b82cb166c7d7d 100644 --- a/substrate/primitives/keyring/src/bandersnatch.rs +++ b/substrate/primitives/keyring/src/bandersnatch.rs @@ -143,21 +143,21 @@ impl From for [u8; PUBLIC_RAW_LEN] { fn from(k: Keyring) -> Self { match k { Keyring::Alice => - hex2array!("9c8af77d3a4e3f6f076853922985b9e6724fc9675329087f47aff1ceaaae772180"), + hex2array!("4d8e57b723e8bb4eca5c28d79cb95b9e84b4e2319d9851d45504014633e55d01"), Keyring::Bob => - hex2array!("1abfbb76dc8374a1a6d93d59a5c81f07c18835f4681a6258aa0f514d363bff4780"), + hex2array!("aa6daf4784d581804d8f5cc1edb2ad171dbdf9c5188ddc071b11c3479c150c37"), Keyring::Charlie => - hex2array!("0f4a9990aca3d39a7cd8bf187e2e81a9ea6f9cedb2db405f2fffff384c5dd02680"), + hex2array!("331d681392223b35b92319e059d3dbc2869b2ef74400a70e678b4a5108c81ec0"), Keyring::Dave => - hex2array!("bd7a87d4dfa89926a408b5acbed554ae3b053fa3532531053295cbabf07d337000"), + hex2array!("374384c19a877040c84bb07fcf3aac74ff7fafface0b1c01a93fd2ddbf5c1660"), Keyring::Eve => - hex2array!("f992d5b8eac8fc004d521bee6edc1174cfa7fae3a1baec8262511ee351f9f85e00"), + hex2array!("14bdd9381e80c07b75b8f1e92d6b2e4652e5135beaad1eedb1b81ff01ee562ad"), Keyring::Ferdie => - hex2array!("1ce2613e89bc5c8e358aad884099cfb576a61176f2f9968cd0d486a04457245180"), + hex2array!("e13bd31b0575076479914c16c5ad69779f206375dbf19519219eeba3b10cc063"), Keyring::One => - hex2array!("a29e03ac273e521274d8e501a6242abd2ab393d7e197221a9113bdf8e2e5b34d00"), + hex2array!("03466a4de97ae18bc4604a3c40dfbddc6bac9f707c9b3c31a94a2c1725a03253"), Keyring::Two => - hex2array!("f968d47e819ddb18a9d0f2ebd16501680b1a3f07ee375c6f81310e5f99a04f4d00"), + hex2array!("0fda0d1336e8d6ee687ebf6d14eaa9b92b5601068e6159222623c8e14c004293"), } } } From f0fd53eda59c2b3e1bee69fb8345608fe075c2d3 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:15:18 +0000 Subject: [PATCH 24/29] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump major' --- prdoc/pr_7669.prdoc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 prdoc/pr_7669.prdoc diff --git a/prdoc/pr_7669.prdoc b/prdoc/pr_7669.prdoc new file mode 100644 index 0000000000000..5f0e184326aa4 --- /dev/null +++ b/prdoc/pr_7669.prdoc @@ -0,0 +1,20 @@ +title: Introduce ark-ec-vrfs +doc: +- audience: Runtime Dev + description: |- + Superseeds `bandersnatch_vrfs` with [ark-vrf](https://crates.io/crates/ark-vrf) + + - Same crypto as JAM + - With a spec: github.com/davxy/bandersnatch-vrf-spec + - Published on crates.io + + https://github.com/paritytech/polkadot-sdk/pull/7670 follow up + + NOTE: this crypto is under experimental feat +crates: +- name: sp-core + bump: major +- name: sp-keystore + bump: major +- name: sp-keyring + bump: major From 1ef9407b4f79713b2d0ed414072647830812f74a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 1 Apr 2025 18:18:49 +0200 Subject: [PATCH 25/29] toml formatting Signed-off-by: Oliver Tale-Yazdi --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7c724b423848e..8d16593324b74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -620,12 +620,12 @@ ark-bls12-381-ext = { version = "0.4.1", default-features = false } ark-bw6-761 = { version = "0.4.0", default-features = false } ark-bw6-761-ext = { version = "0.4.1", default-features = false } ark-ec = { version = "0.4.2", default-features = false } -ark-vrf = { version = "0.1.0", default-features = false } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false } ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } ark-ed-on-bls12-381-bandersnatch-ext = { version = "0.4.1", default-features = false } ark-scale = { version = "0.0.12", default-features = false } +ark-vrf = { version = "0.1.0", default-features = false } array-bytes = { version = "6.2.2", default-features = false } arrayvec = { version = "0.7.4" } assert_cmd = { version = "2.0.14" } From ac11b523a4f4f8fbcc7c15d3961703558ee04f42 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 1 Apr 2025 18:19:05 +0200 Subject: [PATCH 26/29] Add bandersnatch feature to Zepter check Signed-off-by: Oliver Tale-Yazdi --- .config/zepter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/zepter.yaml b/.config/zepter.yaml index 24441e90b1a05..8bd03268e9f9f 100644 --- a/.config/zepter.yaml +++ b/.config/zepter.yaml @@ -12,7 +12,7 @@ workflows: # Check that `A` activates the features of `B`. 'propagate-feature', # These are the features to check: - '--features=try-runtime,runtime-benchmarks,std', + '--features=try-runtime,runtime-benchmarks,std,bandersnatch-experimental', # Do not try to add a new section into `[features]` of `A` only because `B` expose that feature. There are edge-cases where this is still needed, but we can add them manually. '--left-side-feature-missing=ignore', # Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on. From a3a5da0f4aa825bda264ee6e47c1e4b515db77b1 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 1 Apr 2025 18:19:16 +0200 Subject: [PATCH 27/29] Propagate missign features Signed-off-by: Oliver Tale-Yazdi --- substrate/client/keystore/Cargo.toml | 1 + substrate/primitives/io/Cargo.toml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index e46fafbc3729c..8a5fa29c5485d 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -41,6 +41,7 @@ bls-experimental = [ # It should not be used in production since the implementation and interface may still # be subject to significant changes. bandersnatch-experimental = [ + "sp-application-crypto/bandersnatch-experimental", "sp-core/bandersnatch-experimental", "sp-keystore/bandersnatch-experimental", ] diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 3b0078c66022c..b7cc7e91ce82c 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -104,4 +104,7 @@ bls-experimental = ["sp-keystore/bls-experimental"] # This feature adds Bandersnatch crypto primitives. # It should not be used in production since the implementation and interface may still # be subject to significant changes. -bandersnatch-experimental = ["sp-keystore/bandersnatch-experimental"] +bandersnatch-experimental = [ + "sp-core/bandersnatch-experimental", + "sp-keystore/bandersnatch-experimental", +] From 704c9b1268b786dd41fc28d799cf1d98a0114278 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 1 Apr 2025 18:19:25 +0200 Subject: [PATCH 28/29] Exclude from umbrella Signed-off-by: Oliver Tale-Yazdi --- substrate/frame/sassafras/Cargo.toml | 3 +++ substrate/primitives/consensus/sassafras/Cargo.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index dd091b6f8ed79..571ccfcfbd831 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -10,6 +10,9 @@ description = "Consensus extension module for Sassafras consensus." readme = "README.md" publish = false +[package.metadata.polkadot-sdk] +exclude-from-umbrella = true + [lints] workspace = true diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index eb9dfd34c595c..6e094bfc1edc1 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -11,6 +11,9 @@ documentation = "https://docs.rs/sp-consensus-sassafras" readme = "README.md" publish = false +[package.metadata.polkadot-sdk] +exclude-from-umbrella = true + [lints] workspace = true From bbd4b1fba6decf5cef117ca52cb08d2d6cdf9868 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 1 Apr 2025 18:46:57 +0200 Subject: [PATCH 29/29] doc build Signed-off-by: Oliver Tale-Yazdi --- substrate/primitives/core/src/bandersnatch.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index b3086208c2353..891af7f72f59b 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -285,7 +285,8 @@ pub mod vrf { /// VRF signature. /// - /// Includes both the VRF proof and the pre-output generated from the [`VrfSignData::input`]. + /// Includes both the VRF proof and the pre-output generated from the + /// [`VrfSignData::vrf_input`]. /// /// Refer to [`VrfSignData`] for more details. #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -390,7 +391,7 @@ pub mod ring_vrf { pub const RING_VERIFIER_KEY_SERIALIZED_SIZE: usize = 384; /// [`RingProof`] serialized size. pub(crate) const RING_PROOF_SERIALIZED_SIZE: usize = 752; - /// [`RingVrrfSignature`] serialized size. + /// [`RingVrfSignature`] serialized size. pub const RING_SIGNATURE_SERIALIZED_SIZE: usize = RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE;