diff --git a/Cargo.lock b/Cargo.lock index ddd13c0c..a0e2fc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "air" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "field", + "poly", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -652,19 +661,18 @@ dependencies = [ [[package]] name = "backend" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "mt-air", - "mt-fiat-shamir", - "mt-field", - "mt-koala-bear", - "mt-poly", - "mt-sumcheck", - "mt-symetric", - "mt-utils", - "mt-whir", +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "air", + "fiat-shamir", + "field", + "koala-bear", "parallel", - "tracing", + "poly", + "sumcheck", + "symetric", + "utils", + "whir", "zk-alloc", ] @@ -1166,12 +1174,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "const-str" version = "0.4.3" @@ -1601,7 +1603,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", + "const-oid", "pem-rfc7468", "zeroize", ] @@ -1732,7 +1734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", + "const-oid", "crypto-common 0.1.7", "subtle", ] @@ -1744,7 +1746,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", - "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -2048,7 +2049,6 @@ dependencies = [ "ethlambda-test-fixtures", "ethlambda-types", "hex", - "leansig", "libssz", "libssz-types", "rand 0.10.1", @@ -2065,13 +2065,13 @@ dependencies = [ name = "ethlambda-crypto" version = "0.1.0" dependencies = [ + "ethereum_ssz", "ethlambda-types", "hex", - "lean-multisig", - "leansig", - "leansig_wrapper", "rand 0.10.1", + "rec_aggregation", "thiserror 2.0.18", + "xmss", ] [[package]] @@ -2170,7 +2170,6 @@ name = "ethlambda-storage" version = "0.1.0" dependencies = [ "ethlambda-types", - "leansig", "libssz", "libssz-derive", "libssz-types", @@ -2199,18 +2198,20 @@ name = "ethlambda-types" version = "0.1.0" dependencies = [ "datatest-stable 0.3.3", + "ethereum_ssz", "ethlambda-test-fixtures", "hex", - "leansig", "libssz", "libssz-derive", "libssz-merkle", "libssz-types", + "postcard", "rand 0.10.1", "serde", "serde_json", "serde_yaml_ng", "thiserror 2.0.18", + "xmss", ] [[package]] @@ -2560,6 +2561,31 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fiat-shamir" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "field", + "koala-bear", + "parallel", + "serde", + "symetric", + "utils", +] + +[[package]] +name = "field" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "parallel", + "paste", + "rand 0.10.1", + "serde", + "utils", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2962,7 +2988,7 @@ dependencies = [ "hash32", "rustc_version 0.4.1", "serde", - "spin 0.9.8", + "spin", "stable_deref_trait", ] @@ -3618,6 +3644,18 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "koala-bear" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "field", + "paste", + "rand 0.10.1", + "serde", + "utils", +] + [[package]] name = "konst" version = "0.2.20" @@ -3644,7 +3682,7 @@ dependencies = [ "serde_arrays", "sha2", "sp1_bls12_381", - "spin 0.9.8", + "spin", ] [[package]] @@ -3668,126 +3706,43 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lean-multisig" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "backend", - "clap", - "lean_vm", - "leansig_wrapper", - "libc", - "rand 0.10.1", - "rec_aggregation", - "serde_json", - "sub_protocols", - "system-info", - "utils", - "zk-alloc", -] - [[package]] name = "lean_compiler" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "backend", "include_dir", "lean_vm", "pest", "pest_derive", - "rand 0.10.1", "sub_protocols", - "tracing", - "utils", + "xmss", ] [[package]] name = "lean_prover" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "backend", - "itertools 0.14.0", "lean_compiler", "lean_vm", - "pest", - "pest_derive", "rand 0.10.1", "serde", "sub_protocols", "tracing", - "utils", + "xmss", ] [[package]] name = "lean_vm" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "backend", - "itertools 0.14.0", - "leansig_wrapper", - "pest", - "pest_derive", - "rand 0.10.1", - "serde", "tracing", - "utils", -] - -[[package]] -name = "leansig" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanSig?branch=devnet4#15cbdd43ec8525aa43fea2f42cafc5ed366084ae" -dependencies = [ - "dashmap", - "ethereum_ssz", - "num-bigint 0.4.6", - "num-traits", - "p3-baby-bear", - "p3-field 0.5.1", - "p3-koala-bear 0.5.1", - "p3-symmetric 0.5.1", - "rand 0.10.1", - "rayon", - "serde", - "sha3 0.10.9", - "thiserror 2.0.18", -] - -[[package]] -name = "leansig_fast_keygen" -version = "0.1.0" -source = "git+https://github.com/TomWambsgans/leanSig?branch=devnet4-fast-keygen#0fa9e19b8946ef50a34f3d50d82918b98bcfa4a5" -dependencies = [ - "dashmap", - "ethereum_ssz", - "num-bigint 0.4.6", - "num-traits", - "p3-baby-bear", - "p3-field 0.5.1", - "p3-koala-bear 0.5.1", - "p3-symmetric 0.5.1", - "rand 0.10.1", - "rayon", - "serde", - "sha3 0.10.9", - "thiserror 2.0.18", -] - -[[package]] -name = "leansig_wrapper" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "backend", - "ethereum_ssz", - "leansig", - "leansig_fast_keygen", - "p3-field 0.5.1", - "rand 0.10.1", + "xmss", ] [[package]] @@ -4637,15 +4592,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lz4_flex" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" -dependencies = [ - "twox-hash", -] - [[package]] name = "malachite" version = "0.6.1" @@ -4812,127 +4758,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "mt-air" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "mt-field", - "mt-poly", -] - -[[package]] -name = "mt-fiat-shamir" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "mt-field", - "mt-koala-bear", - "mt-symetric", - "mt-utils", - "parallel", - "serde", - "tracing", -] - -[[package]] -name = "mt-field" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "itertools 0.14.0", - "mt-utils", - "num-bigint 0.3.3", - "parallel", - "paste", - "rand 0.10.1", - "serde", - "tracing", -] - -[[package]] -name = "mt-koala-bear" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "itertools 0.14.0", - "mt-field", - "mt-utils", - "num-bigint 0.3.3", - "paste", - "rand 0.10.1", - "serde", - "tracing", -] - -[[package]] -name = "mt-poly" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "itertools 0.14.0", - "mt-field", - "mt-utils", - "parallel", - "rand 0.10.1", - "serde", - "system-info", - "zk-alloc", -] - -[[package]] -name = "mt-sumcheck" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "mt-air", - "mt-fiat-shamir", - "mt-field", - "mt-poly", - "parallel", - "tracing", - "zk-alloc", -] - -[[package]] -name = "mt-symetric" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "mt-field", - "mt-koala-bear", - "parallel", - "zk-alloc", -] - -[[package]] -name = "mt-utils" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "serde", -] - -[[package]] -name = "mt-whir" -version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" -dependencies = [ - "itertools 0.14.0", - "mt-fiat-shamir", - "mt-field", - "mt-koala-bear", - "mt-poly", - "mt-sumcheck", - "mt-symetric", - "mt-utils", - "parallel", - "rand 0.10.1", - "system-info", - "tracing", - "zk-alloc", -] - [[package]] name = "multiaddr" version = "0.18.2" @@ -5310,21 +5135,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "p3-baby-bear" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-challenger 0.5.1", - "p3-field 0.5.1", - "p3-mds 0.5.1", - "p3-monty-31", - "p3-poseidon1", - "p3-poseidon2 0.5.1", - "p3-symmetric 0.5.1", - "rand 0.10.1", -] - [[package]] name = "p3-bn254-fr" version = "0.3.3-succinct" @@ -5333,9 +5143,9 @@ checksum = "577200e3fa7e49e2b21e940a6dc7399dc63acb8581da088558cdf7c455adafc0" dependencies = [ "ff", "num-bigint 0.4.6", - "p3-field 0.3.3-succinct", - "p3-poseidon2 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field", + "p3-poseidon2", + "p3-symmetric", "rand 0.8.6", "serde", ] @@ -5346,51 +5156,24 @@ version = "0.3.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75358edd6e2562752c01f5064a66d88144a3e75ace0407166dbdf8a727597f52" dependencies = [ - "p3-field 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", "serde", "tracing", ] -[[package]] -name = "p3-challenger" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-field 0.5.1", - "p3-maybe-rayon 0.5.1", - "p3-monty-31", - "p3-symmetric 0.5.1", - "p3-util 0.5.1", - "tracing", -] - [[package]] name = "p3-dft" version = "0.3.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "761f1e1b014f2b1b69bd0309124e233d64aa3590e6a41ee786000dd849506d51" dependencies = [ - "p3-field 0.3.3-succinct", - "p3-matrix 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-util 0.3.3-succinct", - "tracing", -] - -[[package]] -name = "p3-dft" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "itertools 0.14.0", - "p3-field 0.5.1", - "p3-matrix 0.5.1", - "p3-maybe-rayon 0.5.1", - "p3-util 0.5.1", - "spin 0.10.0", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", "tracing", ] @@ -5403,26 +5186,11 @@ dependencies = [ "itertools 0.12.1", "num-bigint 0.4.6", "num-traits", - "p3-util 0.3.3-succinct", + "p3-util", "rand 0.8.6", "serde", ] -[[package]] -name = "p3-field" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "itertools 0.14.0", - "num-bigint 0.4.6", - "p3-maybe-rayon 0.5.1", - "p3-util 0.5.1", - "paste", - "rand 0.10.1", - "serde", - "tracing", -] - [[package]] name = "p3-koala-bear" version = "0.3.3-succinct" @@ -5431,30 +5199,15 @@ checksum = "6cea0ba3389b034b6088d566aea8b57aa29dd2e180966e0c8056f61331c92b4e" dependencies = [ "cfg-if 1.0.4", "num-bigint 0.4.6", - "p3-field 0.3.3-succinct", - "p3-mds 0.3.3-succinct", - "p3-poseidon2 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", "rand 0.8.6", "rustc_version 0.4.1", "serde", ] -[[package]] -name = "p3-koala-bear" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-challenger 0.5.1", - "p3-field 0.5.1", - "p3-mds 0.5.1", - "p3-monty-31", - "p3-poseidon1", - "p3-poseidon2 0.5.1", - "p3-symmetric 0.5.1", - "rand 0.10.1", -] - [[package]] name = "p3-matrix" version = "0.3.3-succinct" @@ -5462,39 +5215,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fae5cc6ce726cc265cc687c1214e3f1ac1f5c6e973442286ba00d1e75da1c3cb" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-field", + "p3-maybe-rayon", + "p3-util", "rand 0.8.6", "serde", "tracing", ] -[[package]] -name = "p3-matrix" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "itertools 0.14.0", - "p3-field 0.5.1", - "p3-maybe-rayon 0.5.1", - "p3-util 0.5.1", - "rand 0.10.1", - "serde", - "tracing", -] - [[package]] name = "p3-maybe-rayon" version = "0.3.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac1d2f102cf8c71dba1b449575c99697781fcc028831e83d2245787bd7a650" -[[package]] -name = "p3-maybe-rayon" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" - [[package]] name = "p3-mds" version = "0.3.3-succinct" @@ -5502,59 +5236,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f072643e385d65fb9eb089ee6824b320417f78671a0db748566e057e28b250e" dependencies = [ "itertools 0.12.1", - "p3-dft 0.3.3-succinct", - "p3-field 0.3.3-succinct", - "p3-matrix 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", "rand 0.8.6", ] -[[package]] -name = "p3-mds" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-dft 0.5.1", - "p3-field 0.5.1", - "p3-symmetric 0.5.1", - "p3-util 0.5.1", - "rand 0.10.1", -] - -[[package]] -name = "p3-monty-31" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "itertools 0.14.0", - "num-bigint 0.4.6", - "p3-dft 0.5.1", - "p3-field 0.5.1", - "p3-matrix 0.5.1", - "p3-maybe-rayon 0.5.1", - "p3-mds 0.5.1", - "p3-poseidon1", - "p3-poseidon2 0.5.1", - "p3-symmetric 0.5.1", - "p3-util 0.5.1", - "paste", - "rand 0.10.1", - "serde", - "spin 0.10.0", - "tracing", -] - -[[package]] -name = "p3-poseidon1" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-field 0.5.1", - "p3-symmetric 0.5.1", - "rand 0.10.1", -] - [[package]] name = "p3-poseidon2" version = "0.3.3-succinct" @@ -5562,25 +5251,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00cc4b6e8a439f79541b0910a016da9e6e12a05a24309bbb713e1db0db396952" dependencies = [ "gcd", - "p3-field 0.3.3-succinct", - "p3-mds 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field", + "p3-mds", + "p3-symmetric", "rand 0.8.6", "serde", ] -[[package]] -name = "p3-poseidon2" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "p3-field 0.5.1", - "p3-mds 0.5.1", - "p3-symmetric 0.5.1", - "p3-util 0.5.1", - "rand 0.10.1", -] - [[package]] name = "p3-symmetric" version = "0.3.3-succinct" @@ -5588,18 +5265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebff7fea7deb08a57ccf731a0ed39df25cc66a0e0c2d92c4472c4dee02ee21" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.3-succinct", - "serde", -] - -[[package]] -name = "p3-symmetric" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "itertools 0.14.0", - "p3-field 0.5.1", - "p3-util 0.5.1", + "p3-field", "serde", ] @@ -5612,15 +5278,6 @@ dependencies = [ "serde", ] -[[package]] -name = "p3-util" -version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb2b654acddef22" -dependencies = [ - "serde", - "transpose", -] - [[package]] name = "pairing" version = "0.23.0" @@ -5633,7 +5290,7 @@ dependencies = [ [[package]] name = "parallel" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "system-info", ] @@ -5825,6 +5482,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "field", + "parallel", + "rand 0.10.1", + "serde", + "system-info", + "utils", + "zk-alloc", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -6397,25 +6068,20 @@ dependencies = [ [[package]] name = "rec_aggregation" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "backend", "include_dir", "lean_compiler", "lean_prover", "lean_vm", - "leansig_wrapper", - "lz4_flex", "objc2", "objc2-foundation", "postcard", - "rand 0.10.1", "serde", - "sha3 0.11.0", "sub_protocols", "tracing", - "utils", - "zk-alloc", + "xmss", ] [[package]] @@ -7110,7 +6776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a473c3a06b466dd0708829415a8a9fab451740da066e07862c8c098904aaad6" dependencies = [ "itertools 0.14.0", - "p3-field 0.3.3-succinct", + "p3-field", "serde", ] @@ -7136,7 +6802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4e80df718cef7d3100658dc8b46fafcc994b814421ec9a7d0763a6ee1e5070c" dependencies = [ "futures", - "p3-challenger 0.3.3-succinct", + "p3-challenger", "serde", "slop-algebra", "slop-symmetric", @@ -7149,7 +6815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6586b1c0e66c503e4026a8cb007349fa99c2466957c5b09d18fe658d1391ed8" dependencies = [ "lazy_static", - "p3-koala-bear 0.3.3-succinct", + "p3-koala-bear", "serde", "slop-algebra", "slop-challenger", @@ -7163,7 +6829,7 @@ version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c956b11fff1b8a071fa4ba982dc35e458cff1620dc7b33d9cf22d8df30895f79" dependencies = [ - "p3-poseidon2 0.3.3-succinct", + "p3-poseidon2", ] [[package]] @@ -7181,7 +6847,7 @@ version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955145ad6e3a1d083a428f9274071cfbb44c3b29013aae9d6c4c29fb7328cfc0" dependencies = [ - "p3-symmetric 0.3.3-succinct", + "p3-symmetric", ] [[package]] @@ -7373,15 +7039,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.7.3" @@ -7410,12 +7067,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f446288b699d66d0fd2e30d1cfe7869194312524b3b9252594868ed26ef056a" -[[package]] -name = "strength_reduce" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - [[package]] name = "strsim" version = "0.11.1" @@ -7446,12 +7097,11 @@ dependencies = [ [[package]] name = "sub_protocols" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "backend", "lean_vm", "tracing", - "utils", ] [[package]] @@ -7460,6 +7110,31 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "sumcheck" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "air", + "fiat-shamir", + "field", + "parallel", + "poly", + "tracing", + "zk-alloc", +] + +[[package]] +name = "symetric" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "field", + "koala-bear", + "parallel", + "zk-alloc", +] + [[package]] name = "syn" version = "1.0.109" @@ -7540,7 +7215,7 @@ dependencies = [ [[package]] name = "system-info" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "libc", ] @@ -7951,28 +7626,12 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "transpose" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" -dependencies = [ - "num-integer", - "strength_reduce", -] - [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - [[package]] name = "typenum" version = "1.20.0" @@ -8089,10 +7748,10 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ - "backend", - "tracing", + "parallel", + "serde", "tracing-forest", "tracing-subscriber", ] @@ -8350,6 +8009,25 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "whir" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "fiat-shamir", + "field", + "koala-bear", + "parallel", + "poly", + "rand 0.10.1", + "sumcheck", + "symetric", + "system-info", + "tracing", + "utils", + "zk-alloc", +] + [[package]] name = "wide" version = "0.7.33" @@ -8889,6 +8567,18 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "xmss" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" +dependencies = [ + "backend", + "ethereum_ssz", + "postcard", + "rand 0.10.1", + "serde", +] + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -9055,7 +8745,7 @@ dependencies = [ [[package]] name = "zk-alloc" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30fdddbbf8ae26a333116c68cec7026" +source = "git+https://github.com/leanEthereum/leanVM.git?rev=a73ab114721441aa833384e791f3b83801257456#a73ab114721441aa833384e791f3b83801257456" dependencies = [ "libc", "parallel", diff --git a/Cargo.toml b/Cargo.toml index 100b1a4a..97cc5a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,8 +55,19 @@ prometheus = "0.14" clap = { version = "4.3", features = ["derive", "env"] } -# XMSS signatures -leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "devnet4" } +# XMSS signatures + recursive aggregation (leanVM). +# leanVM's `main` internalized XMSS (dropping the external leanSig dependency) +# and reworked the aggregation API (see "Xmss api rework"). +# Pinned to the current `main` HEAD (branch `main`, commit a73ab11) for +# reproducible builds; bump the rev to track main forward. +xmss = { git = "https://github.com/leanEthereum/leanVM.git", rev = "a73ab114721441aa833384e791f3b83801257456" } +rec_aggregation = { git = "https://github.com/leanEthereum/leanVM.git", rev = "a73ab114721441aa833384e791f3b83801257456" } + +# SSZ codec used by leanVM's xmss pubkey/signature types (ethereum_ssz, not libssz). +ssz = { package = "ethereum_ssz", version = "0.10" } + +# Secret-key (de)serialization for the leanVM xmss key format. +postcard = { version = "1.1.3", features = ["alloc"] } # SSZ implementation libssz = "0.2.2" diff --git a/bin/ethlambda/src/checkpoint_sync.rs b/bin/ethlambda/src/checkpoint_sync.rs index ff466ec1..0cbcf90e 100644 --- a/bin/ethlambda/src/checkpoint_sync.rs +++ b/bin/ethlambda/src/checkpoint_sync.rs @@ -415,16 +415,16 @@ mod tests { fn create_test_validator() -> Validator { Validator { - attestation_pubkey: [1u8; 52], - proposal_pubkey: [11u8; 52], + attestation_pubkey: [1u8; 32], + proposal_pubkey: [11u8; 32], index: 0, } } fn create_different_validator() -> Validator { Validator { - attestation_pubkey: [2u8; 52], - proposal_pubkey: [22u8; 52], + attestation_pubkey: [2u8; 32], + proposal_pubkey: [22u8; 32], index: 0, } } @@ -432,8 +432,8 @@ mod tests { fn create_validators_with_indices(count: usize) -> Vec { (0..count) .map(|i| Validator { - attestation_pubkey: [i as u8 + 1; 52], - proposal_pubkey: [i as u8 + 101; 52], + attestation_pubkey: [i as u8 + 1; 32], + proposal_pubkey: [i as u8 + 101; 32], index: i as u64, }) .collect() diff --git a/bin/ethlambda/src/main.rs b/bin/ethlambda/src/main.rs index 33e242e9..1c866b5f 100644 --- a/bin/ethlambda/src/main.rs +++ b/bin/ethlambda/src/main.rs @@ -479,8 +479,8 @@ fn read_bootnodes(bootnodes_path: impl AsRef) -> eyre::Result Vec { (0..n) .map(|i| Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect() } - /// A cheap-but-real XMSS signature (tiny lifetime, cached) for tests that - /// only need `ValidatorSignature::from_bytes` to succeed. `resolve_job` - /// never checks signature validity, only that it clones and carries a - /// resolvable id — mirrors `ethlambda_storage::store::tests::make_dummy_sig`. + /// A structurally valid XMSS signature for tests that only need + /// `ValidatorSignature::from_bytes` to succeed. `resolve_job` never checks + /// signature validity, only that it clones and carries a resolvable id — + /// mirrors `ethlambda_storage::store::tests::make_dummy_sig`. An all-zero + /// blob decodes as a valid (unverifiable) signature. fn dummy_sig() -> ValidatorSignature { - use ethlambda_types::signature::LeanSignatureScheme; - use leansig::{serialization::Serializable, signature::SignatureScheme}; - use rand::{SeedableRng, rngs::StdRng}; - - static CACHED_SIG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { - let mut rng = StdRng::seed_from_u64(42); - let lifetime = 1 << 5; // small for speed - let (_pk, sk) = LeanSignatureScheme::key_gen(&mut rng, 0, lifetime); - let sig = LeanSignatureScheme::sign(&sk, 0, &[0u8; 32]).unwrap(); - sig.to_bytes() - }); - - ValidatorSignature::from_bytes(&CACHED_SIG).expect("cached test signature") + use ethlambda_types::signature::SIGNATURE_SIZE; + ValidatorSignature::from_bytes(&vec![0u8; SIGNATURE_SIZE]) + .expect("all-zero test signature decodes") } /// A `HashedAttestationData` over default (all-zero) data for `resolve_job` diff --git a/crates/blockchain/src/block_builder.rs b/crates/blockchain/src/block_builder.rs index 787c55b4..91935f08 100644 --- a/crates/blockchain/src/block_builder.rs +++ b/crates/blockchain/src/block_builder.rs @@ -973,8 +973,8 @@ mod tests { let validators: Vec<_> = (0..NUM_VALIDATORS) .map(|i| ethlambda_types::state::Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect(); @@ -1133,8 +1133,8 @@ mod tests { let validators: Vec<_> = (0..NUM_VALIDATORS) .map(|i| ethlambda_types::state::Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect(); @@ -1262,8 +1262,8 @@ mod tests { let validators: Vec<_> = (0..NUM_VALIDATORS) .map(|i| ethlambda_types::state::Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect(); @@ -1566,8 +1566,8 @@ mod tests { let validators: Vec<_> = (0..NUM_VALIDATORS) .map(|i| ethlambda_types::state::Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect(); @@ -1693,8 +1693,8 @@ mod tests { let validators: Vec<_> = (0..NUM_VALIDATORS) .map(|i| ethlambda_types::state::Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect(); diff --git a/crates/blockchain/src/reaggregate.rs b/crates/blockchain/src/reaggregate.rs index f47d6c51..92ff3108 100644 --- a/crates/blockchain/src/reaggregate.rs +++ b/crates/blockchain/src/reaggregate.rs @@ -122,11 +122,8 @@ pub fn reaggregate_from_block( // Step 1: SNARK-split this attestation's component out of the block's // merged multi-message aggregate proof. let merged_bytes = signed_block.proof.proof_bytes(); - let split_bytes = match ethlambda_crypto::split_type_2_by_message( - merged_bytes, - pubkeys_per_component.clone(), - &data_root, - ) { + let split_bytes = match ethlambda_crypto::split_type_2_by_message(merged_bytes, &data_root) + { Ok(bytes) => bytes, Err(err) => { debug!(%err, data_root = %ethlambda_types::ShortRoot(&data_root.0), diff --git a/crates/blockchain/state_transition/src/lib.rs b/crates/blockchain/state_transition/src/lib.rs index 403b170a..69448a7c 100644 --- a/crates/blockchain/state_transition/src/lib.rs +++ b/crates/blockchain/state_transition/src/lib.rs @@ -623,8 +623,8 @@ mod tests { fn make_validators(n: usize) -> Vec { (0..n) .map(|i| Validator { - attestation_pubkey: [i as u8; 52], - proposal_pubkey: [i as u8; 52], + attestation_pubkey: [i as u8; 32], + proposal_pubkey: [i as u8; 32], index: i as u64, }) .collect() diff --git a/crates/common/crypto/Cargo.toml b/crates/common/crypto/Cargo.toml index 5ec86eca..83291605 100644 --- a/crates/common/crypto/Cargo.toml +++ b/crates/common/crypto/Cargo.toml @@ -12,12 +12,11 @@ version.workspace = true [dependencies] ethlambda-types.workspace = true +# rec_aggregation is leanVM's recursive XMSS aggregation prover/verifier; +# xmss provides the public-key/signature types its API consumes. +rec_aggregation.workspace = true +xmss.workspace = true -lean-multisig = { git = "https://github.com/leanEthereum/leanVM.git", rev = "e2592df" } -# leansig_wrapper provides XmssPublicKey/XmssSignature types used by lean-multisig's public API -leansig_wrapper = { git = "https://github.com/leanEthereum/leanVM.git", rev = "e2592df" } - -leansig.workspace = true thiserror.workspace = true rand.workspace = true @@ -26,3 +25,4 @@ shadow-integration = [] [dev-dependencies] hex.workspace = true +ssz.workspace = true diff --git a/crates/common/crypto/src/lib.rs b/crates/common/crypto/src/lib.rs index 4e081445..ca1dfa61 100644 --- a/crates/common/crypto/src/lib.rs +++ b/crates/common/crypto/src/lib.rs @@ -1,37 +1,37 @@ -use std::sync::Once; - use ethlambda_types::{ block::ByteList512KiB, primitives::H256, - signature::{ValidatorPublicKey, ValidatorSignature}, + signature::{LeanSigPublicKey, LeanSigSignature, ValidatorPublicKey, ValidatorSignature}, }; -use lean_multisig::{ - MultiMessageAggregateSignature as LMType2, ProofError, - SingleMessageAggregateSignature as LMType1, aggregate_single_message_signatures, - merge_single_message_aggregates, setup_prover, setup_verifier, split_multi_message_aggregate, - verify_multi_message_aggregate, verify_single_message_aggregate, +use rec_aggregation::{ + MultiMessageAggregateSignature as LMType2, SingleMessageAggregateSignature as LMType1, + aggregate_single_message_signatures, init_aggregation_bytecode, + merge_single_message_aggregates, split_multi_message_aggregate, verify_multi_message_aggregate, + verify_single_message_aggregate, }; -use leansig_wrapper::{XmssPublicKey as LeanSigPubKey, XmssSignature as LeanSigSignature}; use thiserror::Error; #[cfg(feature = "shadow-integration")] pub mod shadow_cost; -/// log(1/rate) for the WHIR commitment scheme used inside lean-multisig. +/// log(1/rate) for the WHIR commitment scheme used inside the aggregation prover. const LOG_INV_RATE: usize = 2; -// Lazy initialization for prover and verifier setup -static PROVER_INIT: Once = Once::new(); -static VERIFIER_INIT: Once = Once::new(); - -/// Ensure the prover is initialized. Safe to call multiple times. +/// Ensure the aggregation bytecode is compiled. Safe to call multiple times. +/// +/// leanVM's `main` replaced the old `setup_prover`/`setup_verifier` split with a +/// single self-referential aggregation bytecode used by both the prover and the +/// verifier; `init_aggregation_bytecode` is idempotent (`OnceLock::get_or_init`). pub fn ensure_prover_ready() { - PROVER_INIT.call_once(setup_prover); + init_aggregation_bytecode(); } -/// Ensure the verifier is initialized. Safe to call multiple times. +/// Ensure the aggregation bytecode is compiled. Safe to call multiple times. +/// +/// Deserializing any proof (`from_bytes`) also requires the bytecode, so this +/// must run before any verification path. pub fn ensure_verifier_ready() { - VERIFIER_INIT.call_once(setup_verifier); + init_aggregation_bytecode(); } /// Error type for signature aggregation operations. @@ -81,7 +81,10 @@ pub enum VerificationError { DeserializationFailed, #[error("verification failed: {0}")] - ProofError(#[from] ProofError), + VerificationFailed(String), + + #[error("aggregate binds a different validator set than expected at component {0}")] + PublicKeySetMismatch(usize), #[error( "(message, slot) mismatch: proof binds {got_slot}/{got_msg:?}, expected {expected_slot}/{expected_msg:?}" @@ -107,33 +110,45 @@ pub enum VerificationError { // Helpers // ===================================================================== -fn into_lean_pubkeys(pubkeys: Vec) -> Vec { - pubkeys +/// The sorted, de-duplicated set of native pubkeys, matching the canonical form +/// leanVM's aggregation embeds inside a proof's `info.pubkeys`. +fn sorted_dedup_pubkeys(pubkeys: Vec) -> Vec { + let mut keys: Vec = pubkeys .into_iter() .map(ValidatorPublicKey::into_inner) - .collect() + .collect(); + keys.sort(); + keys.dedup(); + keys } -/// Decompress a stored Type-1 proof (without-pubkeys form) into a native -/// `SingleMessageAggregateSignature` by attaching the resolved validator pubkeys. +/// Deserialize a stored Type-1 proof. +/// +/// leanVM's `main` embeds the participant pubkeys inside the serialized proof +/// (`info.pubkeys`), so decoding no longer needs the caller-supplied pubkeys. +/// They are still cross-checked against the proof's embedded set as an +/// integrity guard. fn decompress_type1( pubkeys: Vec, proof_bytes: &ByteList512KiB, index: usize, ) -> Result { - let lean_pks = into_lean_pubkeys(pubkeys); - LMType1::decompress_without_pubkeys(proof_bytes.iter().as_slice(), lean_pks) - .ok_or(AggregationError::ChildDeserializationFailed(index)) + let sig = LMType1::from_bytes(proof_bytes.iter().as_slice()) + .ok_or(AggregationError::ChildDeserializationFailed(index))?; + if sig.info.pubkeys != sorted_dedup_pubkeys(pubkeys) { + return Err(AggregationError::ChildDeserializationFailed(index)); + } + Ok(sig) } fn compress_type1_to_byte_list(sig: &LMType1) -> Result { - let serialized = sig.compress_without_pubkeys(); + let serialized = sig.to_bytes(); let len = serialized.len(); ByteList512KiB::try_from(serialized).map_err(|_| AggregationError::ProofTooBig(len)) } fn compress_type2_to_byte_list(sig: &LMType2) -> Result { - let serialized = sig.compress_without_pubkeys(); + let serialized = sig.to_bytes(); let len = serialized.len(); ByteList512KiB::try_from(serialized).map_err(|_| AggregationError::ProofTooBig(len)) } @@ -144,12 +159,14 @@ fn compress_type2_to_byte_list(sig: &LMType2) -> Result, signatures: Vec, @@ -181,7 +198,7 @@ pub fn aggregate_signatures( ensure_prover_ready(); - let raw_xmss: Vec<(LeanSigPubKey, LeanSigSignature)> = public_keys + let raw_xmss: Vec<(LeanSigPublicKey, LeanSigSignature)> = public_keys .into_iter() .zip(signatures) .map(|(pk, sig)| (pk.into_inner(), sig.into_inner())) @@ -242,7 +259,7 @@ pub fn aggregate_mixed( .map(|(i, (pubkeys, proof_bytes))| decompress_type1(pubkeys, &proof_bytes, i)) .collect::>()?; - let raw_xmss: Vec<(LeanSigPubKey, LeanSigSignature)> = raw_public_keys + let raw_xmss: Vec<(LeanSigPublicKey, LeanSigSignature)> = raw_public_keys .into_iter() .zip(raw_signatures) .map(|(pk, sig)| (pk.into_inner(), sig.into_inner())) @@ -331,20 +348,25 @@ pub fn verify_aggregated_signature( } ensure_verifier_ready(); - let lean_pubkeys = into_lean_pubkeys(public_keys); - let sig = LMType1::decompress_without_pubkeys(proof_data.iter().as_slice(), lean_pubkeys) + let sig = LMType1::from_bytes(proof_data.iter().as_slice()) .ok_or(VerificationError::DeserializationFailed)?; - if sig.info.without_pubkeys.message != message.0 || sig.info.without_pubkeys.slot != slot { + if sig.info.message != message.0 || sig.info.slot != slot { return Err(VerificationError::BindingMismatch { expected_msg: *message, expected_slot: slot, - got_msg: H256(sig.info.without_pubkeys.message), - got_slot: sig.info.without_pubkeys.slot, + got_msg: H256(sig.info.message), + got_slot: sig.info.slot, }); } - verify_single_message_aggregate(&sig)?; + // The proof embeds its participant set; bind it to the caller's expectation. + if sig.info.pubkeys != sorted_dedup_pubkeys(public_keys) { + return Err(VerificationError::PublicKeySetMismatch(0)); + } + + verify_single_message_aggregate(&sig) + .map_err(|err| VerificationError::VerificationFailed(format!("{err:?}")))?; Ok(()) } @@ -355,11 +377,11 @@ pub fn verify_aggregated_signature( /// Merge many independent Type-1 multi-signatures into a single Type-2 proof. /// /// Each input is `(participant_pubkeys, type_1_proof_bytes)` where the bytes -/// are the `compress_without_pubkeys()` form of a `SingleMessageAggregateSignature`. +/// are the `to_bytes()` form of a `SingleMessageAggregateSignature`. The pubkeys +/// are also embedded in the proof; the caller-supplied set is cross-checked. /// -/// The returned blob is the `compress_without_pubkeys()` form of the resulting -/// `MultiMessageAggregateSignature`. A verifier decoding it back needs the per-component -/// pubkey sets in the same order. +/// The returned blob is the `to_bytes()` form of the resulting +/// `MultiMessageAggregateSignature`, with each component's pubkeys embedded. pub fn merge_type_1s_into_type_2( type_1s: Vec<(Vec, ByteList512KiB)>, ) -> Result { @@ -421,13 +443,12 @@ pub fn verify_type_2_signature( ensure_verifier_ready(); - let pubkeys_per_info: Vec> = pubkeys_per_component + let expected_pubkeys_per_info: Vec> = pubkeys_per_component .into_iter() - .map(into_lean_pubkeys) + .map(sorted_dedup_pubkeys) .collect(); - let sig = LMType2::decompress_without_pubkeys(proof_data, pubkeys_per_info) - .ok_or(VerificationError::DeserializationFailed)?; + let sig = LMType2::from_bytes(proof_data).ok_or(VerificationError::DeserializationFailed)?; if sig.info.len() != expected_bindings.len() { return Err(VerificationError::Type2ComponentCountMismatch { @@ -439,34 +460,35 @@ pub fn verify_type_2_signature( for (idx, ((expected_msg, expected_slot), info)) in expected_bindings.iter().zip(sig.info.iter()).enumerate() { - if info.without_pubkeys.message != expected_msg.0 - || info.without_pubkeys.slot != *expected_slot - { + if info.message != expected_msg.0 || info.slot != *expected_slot { return Err(VerificationError::BindingMismatch { expected_msg: *expected_msg, expected_slot: *expected_slot, - got_msg: H256(info.without_pubkeys.message), - got_slot: info.without_pubkeys.slot, + got_msg: H256(info.message), + got_slot: info.slot, }); } - let _ = idx; // index reserved for richer diagnostics if needed + // The proof embeds each component's participant set; bind it to the + // caller's expectation. + if info.pubkeys != expected_pubkeys_per_info[idx] { + return Err(VerificationError::PublicKeySetMismatch(idx)); + } } - verify_multi_message_aggregate(&sig)?; + verify_multi_message_aggregate(&sig) + .map_err(|err| VerificationError::VerificationFailed(format!("{err:?}")))?; Ok(()) } /// Split (disaggregate) a Type-2 merged proof into a single Type-1 proof for /// the component bound to `message`. Generates a fresh SNARK; expensive. /// -/// Mirrors leanSpec PR #717 `split_multi_message_aggregate_by_message`: the caller -/// supplies the expected message (an attestation data root or the block -/// root) and the wrapper locates the unique matching component inside the -/// decompressed proof. Returns the `compress_without_pubkeys()` form of the -/// resulting Type-1. +/// Mirrors `split_multi_message_aggregate_by_message`: the caller supplies the +/// expected message (an attestation data root or the block root) and the +/// wrapper locates the unique matching component inside the decoded proof. +/// Returns the `to_bytes()` form of the resulting Type-1. pub fn split_type_2_by_message( proof_data: &[u8], - pubkeys_per_component: Vec>, message: &H256, ) -> Result { #[cfg(feature = "shadow-integration")] @@ -479,19 +501,15 @@ pub fn split_type_2_by_message( ensure_prover_ready(); - let pubkeys_per_info: Vec> = pubkeys_per_component - .into_iter() - .map(into_lean_pubkeys) - .collect(); - - let type_2 = LMType2::decompress_without_pubkeys(proof_data, pubkeys_per_info) - .ok_or(AggregationError::DeserializationFailed)?; + // leanVM embeds each component's pubkeys in the serialized proof, so decoding + // no longer needs caller-supplied pubkey sets. + let type_2 = LMType2::from_bytes(proof_data).ok_or(AggregationError::DeserializationFailed)?; let matches: Vec = type_2 .info .iter() .enumerate() - .filter_map(|(i, info)| (info.without_pubkeys.message == message.0).then_some(i)) + .filter_map(|(i, info)| (info.message == message.0).then_some(i)) .collect(); let index = match matches.as_slice() { [i] => *i, @@ -508,43 +526,43 @@ pub fn split_type_2_by_message( #[cfg(test)] mod tests { use super::*; - use ethlambda_types::signature::LeanSignatureScheme; - use leansig::{serialization::Serializable, signature::SignatureScheme}; - use rand::{SeedableRng, rngs::StdRng}; + use ssz::Encode as _; + use xmss::{xmss_key_gen_from_seed, xmss_sign}; /// Generate a test keypair and sign a message. /// /// Note: This is slow because XMSS key generation is computationally expensive. - /// TODO: move to pre-generated keys fn generate_keypair_and_sign( seed: u64, activation_epoch: u32, signing_epoch: u32, message: &H256, ) -> (ValidatorPublicKey, ValidatorSignature) { - let mut rng = StdRng::seed_from_u64(seed); + let mut seed_bytes = [0u8; 32]; + seed_bytes[..8].copy_from_slice(&seed.to_le_bytes()); - // Use a small lifetime for faster test key generation - let log_lifetime = 5; // 2^5 = 32 epochs - let lifetime = 1 << log_lifetime; + // Small active range (starting at the activation epoch and covering the + // signing slot) for fast key generation. + let num_active_slots = 64u64; + let (pk, sk) = + xmss_key_gen_from_seed(seed_bytes, activation_epoch as u64, num_active_slots) + .expect("valid activation range"); - let (pk, sk) = LeanSignatureScheme::key_gen(&mut rng, activation_epoch as usize, lifetime); + let sig = xmss_sign(&sk, signing_epoch, &message.0).expect("sign"); - let sig = LeanSignatureScheme::sign(&sk, signing_epoch, &message.0).unwrap(); - - // Convert to ethlambda types via bytes - let pk_bytes = pk.to_bytes(); - let sig_bytes = sig.to_bytes(); - - let validator_pk = ValidatorPublicKey::from_bytes(&pk_bytes).unwrap(); - let validator_sig = ValidatorSignature::from_bytes(&sig_bytes).unwrap(); + // Convert to ethlambda types via SSZ wire bytes. + let validator_pk = ValidatorPublicKey::from_bytes(&pk.as_ssz_bytes()).unwrap(); + let validator_sig = ValidatorSignature::from_bytes(&sig.as_ssz_bytes()).unwrap(); (validator_pk, validator_sig) } #[test] + #[ignore = "slow: compiles the leanVM aggregation bytecode (needs a release-sized stack)"] fn test_setup_is_idempotent() { - // Should not panic when called multiple times + // Should not panic when called multiple times. The first call compiles + // the self-referential aggregation bytecode; subsequent calls are cheap + // (`OnceLock::get_or_init`). ensure_prover_ready(); ensure_prover_ready(); ensure_verifier_ready(); @@ -681,12 +699,7 @@ mod tests { ) .expect("verify type-2"); - let split = split_type_2_by_message( - merged.iter().as_slice(), - vec![vec![pk_a.clone()], vec![pk_b.clone()]], - &msg_a, - ) - .expect("split"); + let split = split_type_2_by_message(merged.iter().as_slice(), &msg_a).expect("split"); verify_aggregated_signature(&split, vec![pk_a.clone()], &msg_a, slot_a) .expect("verify split"); diff --git a/crates/common/test-fixtures/src/common.rs b/crates/common/test-fixtures/src/common.rs index 6293b897..5bad0bab 100644 --- a/crates/common/test-fixtures/src/common.rs +++ b/crates/common/test-fixtures/src/common.rs @@ -330,7 +330,12 @@ where let pubkey: ValidatorPubkeyBytes = hex::decode(value.strip_prefix("0x").unwrap_or(&value)) .map_err(|_| D::Error::custom("ValidatorPubkey value is not valid hex"))? .try_into() - .map_err(|_| D::Error::custom("ValidatorPubkey length != 52"))?; + .map_err(|_| { + D::Error::custom(format!( + "ValidatorPubkey length != {}", + ethlambda_types::signature::PUBLIC_KEY_SIZE + )) + })?; Ok(pubkey) } diff --git a/crates/common/types/Cargo.toml b/crates/common/types/Cargo.toml index 1de09a67..e7a733d6 100644 --- a/crates/common/types/Cargo.toml +++ b/crates/common/types/Cargo.toml @@ -14,7 +14,9 @@ thiserror.workspace = true serde.workspace = true hex.workspace = true -leansig.workspace = true +xmss.workspace = true +ssz.workspace = true +postcard.workspace = true libssz.workspace = true libssz-derive.workspace = true diff --git a/crates/common/types/src/attestation.rs b/crates/common/types/src/attestation.rs index 0d68addb..f8706697 100644 --- a/crates/common/types/src/attestation.rs +++ b/crates/common/types/src/attestation.rs @@ -58,34 +58,15 @@ pub struct SignedAttestation { /// XMSS signature as a fixed-length byte vector (`SIGNATURE_SIZE` bytes). pub type XmssSignature = SszVector; -/// SSZ offset (in bytes) of the `path` body inside an XMSS `Signature` container. -/// -/// Layout: 4-byte path offset + 28-byte rho + 4-byte hashes offset = 36. -const SIGNATURE_PATH_OFFSET: u32 = 36; - -/// SSZ offset (in bytes) of the `hashes` body inside an XMSS `Signature`. -/// -/// `path` body is 4-byte siblings offset + LOG_LIFETIME (32) siblings × 32-byte -/// digest = 1028, starting at byte 36, so hashes start at 36 + 1028 = 1064. -const SIGNATURE_HASHES_OFFSET: u32 = 1064; - -/// SSZ offset (in bytes) of the `siblings` list inside the `path` container. -const SIGNATURE_PATH_SIBLINGS_OFFSET: u32 = 4; - -/// Build a placeholder XMSS signature that decodes as a structurally valid -/// leanSpec `Signature` container of all-zero hashes. +/// Build a placeholder XMSS signature of all-zero field elements. /// /// Used for genesis-style anchor blocks that were never proposed and therefore -/// have no real signature. Parent containers inline this as an opaque -/// `SIGNATURE_SIZE`-byte blob; consumers that decode the inner `Signature` -/// container see `path = HashTreeOpening { siblings = [0; 32] }`, `rho = 0`, -/// `hashes = [0; 46]`. Matches ream's `Signature::blank()` so the wire format -/// is byte-identical across clients. +/// have no real signature. leanVM's `XmssSignature` SSZ-encodes as a +/// fixed-length sequence of field elements (no variable-length offsets), and a +/// zero word is a valid canonical field element, so an all-zero blob decodes as +/// a structurally valid (but unverifiable) signature. pub fn blank_xmss_signature() -> XmssSignature { - let mut bytes = vec![0u8; SIGNATURE_SIZE]; - bytes[..4].copy_from_slice(&SIGNATURE_PATH_OFFSET.to_le_bytes()); - bytes[32..36].copy_from_slice(&SIGNATURE_HASHES_OFFSET.to_le_bytes()); - bytes[36..40].copy_from_slice(&SIGNATURE_PATH_SIBLINGS_OFFSET.to_le_bytes()); + let bytes = vec![0u8; SIGNATURE_SIZE]; XmssSignature::try_from(bytes).expect("size matches SIGNATURE_SIZE") } @@ -311,35 +292,14 @@ mod tests { assert!(!bits_is_subset(&a, &b)); } - /// Guard the three SSZ offsets at fixed byte positions so a one-off in any - /// constant doesn't silently produce a blob that still has the right outer - /// length but decodes incorrectly at the inner `Signature` container level. + /// The blank placeholder is exactly `SIGNATURE_SIZE` all-zero bytes and + /// decodes back as a structurally valid signature. #[test] - fn blank_xmss_signature_has_expected_ssz_offsets() { + fn blank_xmss_signature_is_zero_and_decodes() { let sig = blank_xmss_signature(); let bytes: Vec = sig.into_iter().collect(); assert_eq!(bytes.len(), SIGNATURE_SIZE); - assert_eq!( - u32::from_le_bytes(bytes[0..4].try_into().unwrap()), - SIGNATURE_PATH_OFFSET, - ); - assert_eq!( - u32::from_le_bytes(bytes[32..36].try_into().unwrap()), - SIGNATURE_HASHES_OFFSET, - ); - assert_eq!( - u32::from_le_bytes(bytes[36..40].try_into().unwrap()), - SIGNATURE_PATH_SIBLINGS_OFFSET, - ); - - // Everything outside the three offset slots must be zero — the - // placeholder is "all-zero hashes" once the offsets locate them. - for (i, b) in bytes.iter().enumerate() { - let in_offset_slot = matches!(i, 0..4 | 32..36 | 36..40); - if !in_offset_slot { - assert_eq!(*b, 0, "non-offset byte at index {i} should be zero"); - } - } + assert!(bytes.iter().all(|b| *b == 0)); } } diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index 5c5508a2..c2d69174 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -48,12 +48,13 @@ pub type ByteList512KiB = ByteList<524_288>; /// A merged proof covering multiple messages with a single proof blob. /// -/// Also known as a *type-2* proof: the lean-multisig term for an aggregate -/// binding several distinct messages, each with its own participant set. +/// Also known as a *type-2* proof: leanVM's term for an aggregate binding +/// several distinct messages, each with its own participant set. /// -/// The proof bytes use lean-multisig's compact public-key-free -/// representation. SSZ encoding this container adds the offset required for -/// its variable-length field. +/// The proof bytes are the `to_bytes()` (postcard) form of leanVM's +/// `MultiMessageAggregateSignature`, which embeds each component's participant +/// pubkeys. SSZ encoding this container adds the offset required for its +/// variable-length field. #[derive(Debug, Default, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)] pub struct MultiMessageAggregate { /// Serialized multi-message aggregate proof bytes. @@ -66,7 +67,7 @@ impl MultiMessageAggregate { Self { proof } } - /// Copy raw lean-multisig proof bytes into the bounded SSZ container. + /// Copy raw leanVM aggregation proof bytes into the bounded SSZ container. pub fn from_bytes(bytes: &[u8]) -> Result { let len = bytes.len(); ByteList512KiB::try_from(bytes.to_vec()) @@ -74,7 +75,7 @@ impl MultiMessageAggregate { .map_err(|_| MultiMessageAggregateError::ProofTooLarge(len)) } - /// Return the raw lean-multisig proof bytes. + /// Return the raw leanVM aggregation proof bytes. pub fn proof_bytes(&self) -> &[u8] { self.proof.iter().as_slice() } @@ -98,7 +99,7 @@ pub enum MultiMessageAggregateError { // from the surrounding block body (attestation `data` + slot for body // components, block root + slot for the proposer component). // -// `MultiMessageAggregate` carries the raw lean-multisig type-2 bytes. +// `MultiMessageAggregate` carries the raw leanVM type-2 bytes. // Component participant bitfields come from // `block.body.attestations[i].aggregation_bits` (and `block.proposer_index` for // the trailing proposer entry). @@ -110,8 +111,8 @@ pub const MAX_ATTESTATIONS_DATA: usize = 8; /// A single-message proof aggregating signatures from many validators. /// -/// Also known as a *type-1* proof: the lean-multisig term for an aggregate -/// where every participant signs the same message for the same slot. +/// Also known as a *type-1* proof: leanVM's term for an aggregate where every +/// participant signs the same message for the same slot. /// /// Used: /// - as a gossip-level `SignedAggregatedAttestation.proof`, @@ -119,15 +120,16 @@ pub const MAX_ATTESTATIONS_DATA: usize = 8; /// - as one of the components fed into `merge_type_1s_into_type_2` when /// building a block proof. /// -/// `participants` and `proof` are independent fields: the proof bytes are -/// the lean-multisig `compress_without_pubkeys()` form; `participants` is -/// the bitfield identifying which validators are bound by the proof. The -/// verifier resolves pubkeys from `participants` at verify time. +/// `participants` and `proof` are independent fields: the proof bytes are the +/// `to_bytes()` form of leanVM's `SingleMessageAggregateSignature` (which +/// embeds the participant pubkeys); `participants` is the bitfield identifying +/// which validators are bound. The verifier resolves the expected pubkeys from +/// `participants` and cross-checks them against the set embedded in the proof. #[derive(Debug, Clone, SszEncode, SszDecode, HashTreeRoot)] pub struct SingleMessageAggregate { /// Bitfield identifying validators bound by this proof. pub participants: AggregationBits, - /// Aggregated proof bytes in lean-multisig compact (no-pubkeys) form. + /// Aggregated proof bytes in leanVM's `to_bytes()` form (pubkeys embedded). pub proof: ByteList512KiB, } diff --git a/crates/common/types/src/genesis.rs b/crates/common/types/src/genesis.rs index 27baebf6..02b2bbab 100644 --- a/crates/common/types/src/genesis.rs +++ b/crates/common/types/src/genesis.rs @@ -44,7 +44,11 @@ where let bytes = hex::decode(s).map_err(|_| D::Error::custom(format!("pubkey is not valid hex: {s}")))?; bytes.try_into().map_err(|v: Vec| { - D::Error::custom(format!("pubkey has length {} (expected 52)", v.len())) + D::Error::custom(format!( + "pubkey has length {} (expected {})", + v.len(), + crate::signature::PUBLIC_KEY_SIZE + )) }) } @@ -56,12 +60,12 @@ mod tests { state::{State, Validator}, }; - const ATT_PUBKEY_A: &str = "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d2d25ca7b19604f5a180037046cf2a02e1da4a800"; - const PROP_PUBKEY_A: &str = "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940fa03e4c012e8a0e656b731934aeac124a5337e333"; - const ATT_PUBKEY_B: &str = "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05e143e305017847445859650d6dd06e6efb3f8410"; - const PROP_PUBKEY_B: &str = "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d2d25ca7b19604f5a180037046cf2a02e1da4a800"; - const ATT_PUBKEY_C: &str = "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940fa03e4c012e8a0e656b731934aeac124a5337e333"; - const PROP_PUBKEY_C: &str = "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05e143e305017847445859650d6dd06e6efb3f8410"; + const ATT_PUBKEY_A: &str = "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d"; + const PROP_PUBKEY_A: &str = "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940f"; + const ATT_PUBKEY_B: &str = "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05"; + const PROP_PUBKEY_B: &str = "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d"; + const ATT_PUBKEY_C: &str = "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940f"; + const PROP_PUBKEY_C: &str = "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05"; const TEST_CONFIG_YAML: &str = r#"# Genesis Settings GENESIS_TIME: 1770407233 @@ -74,12 +78,12 @@ VALIDATOR_COUNT: 3 # Genesis Validator Pubkeys GENESIS_VALIDATORS: - - attestation_pubkey: "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d2d25ca7b19604f5a180037046cf2a02e1da4a800" - proposal_pubkey: "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940fa03e4c012e8a0e656b731934aeac124a5337e333" - - attestation_pubkey: "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05e143e305017847445859650d6dd06e6efb3f8410" - proposal_pubkey: "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d2d25ca7b19604f5a180037046cf2a02e1da4a800" - - attestation_pubkey: "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940fa03e4c012e8a0e656b731934aeac124a5337e333" - proposal_pubkey: "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05e143e305017847445859650d6dd06e6efb3f8410" + - attestation_pubkey: "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d" + proposal_pubkey: "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940f" + - attestation_pubkey: "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05" + proposal_pubkey: "cd323f232b34ab26d6db7402c886e74ca81cfd3a0c659d2fe022356f25592f7d" + - attestation_pubkey: "b7b0f72e24801b02bda64073cb4de6699a416b37dfead227d7ca3922647c940f" + proposal_pubkey: "8d9cbc508b20ef43e165f8559c1bdd18aaeda805ef565a4f9ffd6e4fbed01c05" "#; #[test] @@ -146,7 +150,7 @@ GENESIS_VALIDATORS: // Pin the state root so SSZ layout changes are caught immediately. let expected_state_root = crate::primitives::H256::from_slice( - &hex::decode("babcdc9235a29dfc0d605961df51cfc85732f85291c2beea8b7510a92ec458fe") + &hex::decode("3e8c8507e94e045327c2fc66a58db374805cb490a087b3101bb13a9b8b611b54") .unwrap(), ); assert_eq!(root, expected_state_root, "state root mismatch"); @@ -155,7 +159,7 @@ GENESIS_VALIDATORS: block.state_root = root; let block_root = block.hash_tree_root(); let expected_block_root = crate::primitives::H256::from_slice( - &hex::decode("66a8beaa81d2aaeac7212d4bf8f5fea2bd22d479566a33a83c891661c21235ef") + &hex::decode("ba3502921697db025b3a6d7c05fbaf58e52155575438cca9794e22e6e9872090") .unwrap(), ); assert_eq!(block_root, expected_block_root, "block root mismatch"); diff --git a/crates/common/types/src/signature.rs b/crates/common/types/src/signature.rs index fa9c8da8..766a756d 100644 --- a/crates/common/types/src/signature.rs +++ b/crates/common/types/src/signature.rs @@ -1,34 +1,32 @@ use std::ops::Range; -use leansig::{ - serialization::Serializable, - signature::{SignatureScheme, SignatureSchemeSecretKey as _, SigningError}, +use ssz::{Decode, Encode}; +use xmss::{ + PUB_KEY_SSZ_LEN, SIGNATURE_SSZ_LEN, XmssPublicKey, XmssSecretKey, XmssSignature, + XmssSignatureError, xmss_sign, xmss_verify, }; use crate::primitives::H256; -/// The XMSS signature scheme used for validator signatures. -/// -/// This is a post-quantum secure signature scheme based on hash functions. -/// Uses Poseidon1 hashing with an aborting hypercube message hash, -/// 32-bit lifetime (2^32 signatures per key), dimension 46, and base 8. -pub type LeanSignatureScheme = leansig::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::SIGAbortingTargetSumLifetime32Dim46Base8; - -/// The public key type from the leansig library. -pub type LeanSigPublicKey = ::PublicKey; +/// The public key type from leanVM's xmss crate. +pub type LeanSigPublicKey = XmssPublicKey; -/// The signature type from the leansig library. -pub type LeanSigSignature = ::Signature; +/// The signature type from leanVM's xmss crate. +pub type LeanSigSignature = XmssSignature; -/// The secret key type from the leansig library. -pub type LeanSigSecretKey = ::SecretKey; +/// The secret key type from leanVM's xmss crate. +pub type LeanSigSecretKey = XmssSecretKey; pub type Signature = LeanSigSignature; -/// Size of an XMSS signature in bytes. +/// Size of an SSZ-encoded XMSS signature in bytes. /// -/// Computed from: path(32*8*4) + rho(7*4) + hashes(46*8*4) + ssz_offsets(3*4) = 2536 -pub const SIGNATURE_SIZE: usize = 2536; +/// Sourced from leanVM's xmss crate rather than hardcoded, so it tracks the +/// scheme parameters (`WOTS_SIG_SIZE_FE`, `LOG_LIFETIME`, `XMSS_DIGEST_LEN`). +pub const SIGNATURE_SIZE: usize = SIGNATURE_SSZ_LEN; + +/// Size of an SSZ-encoded XMSS public key in bytes. +pub const PUBLIC_KEY_SIZE: usize = PUB_KEY_SSZ_LEN; /// Error returned when parsing signature or key bytes fails. #[derive(Debug, Clone, thiserror::Error)] @@ -41,18 +39,20 @@ pub struct ValidatorSignature { } impl ValidatorSignature { + /// Parse from the SSZ-encoded wire form (`SIGNATURE_SIZE` bytes). pub fn from_bytes(bytes: &[u8]) -> Result { - let sig = LeanSigSignature::from_bytes(bytes) + let sig = LeanSigSignature::from_ssz_bytes(bytes) .map_err(|e| SignatureParseError(format!("{e:?}")))?; Ok(Self { inner: sig }) } + /// Encode to the SSZ wire form (`SIGNATURE_SIZE` bytes). pub fn to_bytes(&self) -> Vec { - self.inner.to_bytes() + self.inner.as_ssz_bytes() } pub fn is_valid(&self, pubkey: &ValidatorPublicKey, slot: u32, message: &H256) -> bool { - LeanSignatureScheme::verify(&pubkey.inner, slot, &message.0, &self.inner) + xmss_verify(&pubkey.inner, slot, &message.0, &self.inner).is_ok() } pub fn into_inner(self) -> LeanSigSignature { @@ -66,14 +66,16 @@ pub struct ValidatorPublicKey { } impl ValidatorPublicKey { + /// Parse from the SSZ-encoded wire form (`PUBLIC_KEY_SIZE` bytes). pub fn from_bytes(bytes: &[u8]) -> Result { - let pk = LeanSigPublicKey::from_bytes(bytes) + let pk = LeanSigPublicKey::from_ssz_bytes(bytes) .map_err(|e| SignatureParseError(format!("{e:?}")))?; Ok(Self { inner: pk }) } + /// Encode to the SSZ wire form (`PUBLIC_KEY_SIZE` bytes). pub fn to_bytes(&self) -> Vec { - self.inner.to_bytes() + self.inner.as_ssz_bytes() } pub fn into_inner(self) -> LeanSigPublicKey { @@ -87,100 +89,116 @@ pub struct ValidatorSecretKey { } impl ValidatorSecretKey { + /// Parse from the postcard-encoded key file produced by the genesis generator. + /// + /// leanVM's `XmssSecretKey` uses serde (postcard) rather than the SSZ-style + /// `Serializable` of the old leanSig scheme. pub fn from_bytes(bytes: &[u8]) -> Result { - let sk = LeanSigSecretKey::from_bytes(bytes) + let sk = postcard::from_bytes::(bytes) .map_err(|e| SignatureParseError(format!("{e:?}")))?; Ok(Self { inner: sk }) } - /// Sign a message with this private key. + /// Serialize the secret key to its postcard key-file form. + pub fn to_bytes(&self) -> Result, SignatureParseError> { + postcard::to_allocvec(&self.inner).map_err(|e| SignatureParseError(format!("{e:?}"))) + } + + /// The public key derived from this secret key. + pub fn public_key(&self) -> ValidatorPublicKey { + ValidatorPublicKey { + inner: self.inner.public_key(), + } + } + + /// Sign a message at `slot`. /// - /// The slot is used as part of the XMSS signature scheme to track - /// one-time signature usage. - pub fn sign(&self, slot: u32, message: &H256) -> Result { - let sig = LeanSignatureScheme::sign(&self.inner, slot, &message.0)?; + /// The slot indexes the one-time XMSS leaf; never sign two different + /// messages at the same slot. + pub fn sign( + &self, + slot: u32, + message: &H256, + ) -> Result { + let sig = xmss_sign(&self.inner, slot, &message.0)?; Ok(ValidatorSignature { inner: sig }) } - /// Returns true if the key is prepared to sign at the given slot. + /// Returns true if the key can sign at the given slot. /// - /// XMSS keys maintain a sliding window of two bottom trees. Only slots - /// within this window can be signed without advancing the preparation. + /// leanVM's xmss keys cover a fixed activation range (fixed at key + /// generation); there is no sliding preparation window as in the old + /// leanSig scheme. pub fn is_prepared_for(&self, slot: u32) -> bool { - self.inner.get_prepared_interval().contains(&(slot as u64)) + self.inner.activation_slots().contains(&slot) } - /// Returns the slot range currently covered by the prepared window. + /// The half-open slot range this key can sign for. pub fn get_prepared_interval(&self) -> Range { - self.inner.get_prepared_interval() + let range = self.inner.activation_slots(); + (*range.start() as u64)..(*range.end() as u64 + 1) } - /// Advance the prepared window forward by one bottom tree. + /// No-op retained for API compatibility. /// - /// Each call slides the window by sqrt(LIFETIME) = 65,536 slots. - /// If the window is already at the end of the key's activation interval, - /// this is a no-op. - pub fn advance_preparation(&mut self) { - self.inner.advance_preparation(); - } + /// The old leanSig scheme advanced a two-bottom-tree preparation window; + /// leanVM's xmss keys have a fixed activation range and warm their signing + /// cache on demand inside `sign`, so there is nothing to advance. + pub fn advance_preparation(&mut self) {} } #[cfg(test)] mod tests { use super::*; - use leansig::serialization::Serializable; - use rand::{SeedableRng, rngs::StdRng}; - - const LEAVES_PER_BOTTOM_TREE: u32 = 1 << 16; // 65,536 - - /// Generate a ValidatorSecretKey with 3 bottom trees so advance_preparation can be tested. - /// - /// This is slow (~minutes) because it computes 3 bottom trees of 65,536 leaves each. - fn generate_key_with_three_bottom_trees() -> ValidatorSecretKey { - let mut rng = StdRng::seed_from_u64(42); - // Request enough active epochs for 3 bottom trees (> 2 * 65,536) - let num_active_epochs = (LEAVES_PER_BOTTOM_TREE as usize) * 2 + 1; - let (_pk, sk) = LeanSignatureScheme::key_gen(&mut rng, 0, num_active_epochs); - let sk_bytes = sk.to_bytes(); - ValidatorSecretKey::from_bytes(&sk_bytes).expect("valid secret key") + use xmss::xmss_key_gen_from_seed; + + /// Generate a validator key pair over a small activation range. + fn generate_key( + seed: [u8; 32], + activation_slot: u64, + num_active_slots: u64, + ) -> ValidatorSecretKey { + let (_pk, sk) = xmss_key_gen_from_seed(seed, activation_slot, num_active_slots) + .expect("valid activation range"); + ValidatorSecretKey { inner: sk } } #[test] - #[ignore = "slow: generates production-size XMSS key (~minutes)"] - fn test_advance_preparation_duration() { - println!("Generating XMSS key with 3 bottom trees (this takes a while)..."); - let keygen_start = std::time::Instant::now(); - let mut sk = generate_key_with_three_bottom_trees(); - println!("Key generation took: {:?}", keygen_start.elapsed()); - - // Initial window covers [0, 131072) - assert!(sk.is_prepared_for(0)); - assert!(sk.is_prepared_for(LEAVES_PER_BOTTOM_TREE - 1)); - assert!(sk.is_prepared_for(2 * LEAVES_PER_BOTTOM_TREE - 1)); - assert!(!sk.is_prepared_for(2 * LEAVES_PER_BOTTOM_TREE)); + #[ignore = "slow: XMSS key generation and signing"] + fn sign_verify_round_trip() { + let sk = generate_key([7u8; 32], 0, 64); + let pk = sk.public_key(); - // Time the advance_preparation call - let advance_start = std::time::Instant::now(); - sk.advance_preparation(); - let advance_duration = advance_start.elapsed(); + assert!(sk.is_prepared_for(0)); + assert!(sk.is_prepared_for(63)); + assert!(!sk.is_prepared_for(64)); + assert_eq!(sk.get_prepared_interval(), 0..64); - println!("advance_preparation() took: {advance_duration:?}"); + let message = H256::from([42u8; 32]); + let slot = 10u32; + let sig = sk.sign(slot, &message).expect("sign"); + assert!(sig.is_valid(&pk, slot, &message)); + assert!(!sig.is_valid(&pk, slot, &H256::from([43u8; 32]))); + assert!(!sig.is_valid(&pk, slot + 1, &message)); + } - // Window should now cover [65536, 196608) - assert!(!sk.is_prepared_for(0)); - assert!(sk.is_prepared_for(LEAVES_PER_BOTTOM_TREE)); - assert!(sk.is_prepared_for(3 * LEAVES_PER_BOTTOM_TREE - 1)); + #[test] + #[ignore = "slow: XMSS key generation and signing"] + fn sign_out_of_range_fails() { + let sk = generate_key([9u8; 32], 100, 32); + let message = H256::from([1u8; 32]); + // Slot 0 is outside the key's activation range [100, 132). + assert!(sk.sign(0, &message).is_err()); + } - // Verify signing works in the new window - let message = H256::from([42u8; 32]); - let slot = 2 * LEAVES_PER_BOTTOM_TREE; // slot 131,072 — the one that crashed the devnet - let sign_start = std::time::Instant::now(); - let result = sk.sign(slot, &message); - println!("Signing at slot {slot} took: {:?}", sign_start.elapsed()); - assert!( - result.is_ok(), - "signing should succeed after advance: {}", - result.err().map_or(String::new(), |e| e.to_string()) - ); + #[test] + #[ignore = "slow: XMSS key generation"] + fn public_key_ssz_round_trip() { + let sk = generate_key([3u8; 32], 0, 16); + let pk = sk.public_key(); + let bytes = pk.to_bytes(); + assert_eq!(bytes.len(), PUBLIC_KEY_SIZE); + let parsed = ValidatorPublicKey::from_bytes(&bytes).expect("round trip"); + assert_eq!(parsed.to_bytes(), bytes); } } diff --git a/crates/common/types/src/state.rs b/crates/common/types/src/state.rs index 26ff110d..58494922 100644 --- a/crates/common/types/src/state.rs +++ b/crates/common/types/src/state.rs @@ -6,7 +6,7 @@ use crate::{ block::{Block, BlockBody, BlockHeader}, checkpoint::Checkpoint, primitives::{self, H256}, - signature::{SignatureParseError, ValidatorPublicKey}, + signature::{PUBLIC_KEY_SIZE, SignatureParseError, ValidatorPublicKey}, }; // Convenience trait for calling hash_tree_root() without a hasher argument @@ -95,7 +95,7 @@ impl Validator { } } -pub type ValidatorPubkeyBytes = [u8; 52]; +pub type ValidatorPubkeyBytes = [u8; PUBLIC_KEY_SIZE]; impl State { pub fn from_genesis(genesis_time: u64, validators: Vec) -> Self { diff --git a/crates/net/rpc/src/genesis.rs b/crates/net/rpc/src/genesis.rs index aa7472c9..6ab0b59e 100644 --- a/crates/net/rpc/src/genesis.rs +++ b/crates/net/rpc/src/genesis.rs @@ -42,8 +42,8 @@ mod tests { async fn genesis_returns_time_and_validator_count() { // Build a state with 3 validators so the assertion is non-vacuous. let dummy_validator = |index: u64| Validator { - attestation_pubkey: [0u8; 52], - proposal_pubkey: [0u8; 52], + attestation_pubkey: [0u8; 32], + proposal_pubkey: [0u8; 32], index, }; let validators = vec![dummy_validator(0), dummy_validator(1), dummy_validator(2)]; diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 7381a53e..2bc1968a 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -24,5 +24,4 @@ lru.workspace = true [dev-dependencies] tempfile = "3" -leansig.workspace = true rand.workspace = true diff --git a/crates/storage/src/state_diff.rs b/crates/storage/src/state_diff.rs index 1e9e5028..261619f8 100644 --- a/crates/storage/src/state_diff.rs +++ b/crates/storage/src/state_diff.rs @@ -248,13 +248,13 @@ mod tests { fn base_state() -> State { let validators = vec![ Validator { - attestation_pubkey: [1u8; 52], - proposal_pubkey: [2u8; 52], + attestation_pubkey: [1u8; 32], + proposal_pubkey: [2u8; 32], index: 0, }, Validator { - attestation_pubkey: [3u8; 52], - proposal_pubkey: [4u8; 52], + attestation_pubkey: [3u8; 32], + proposal_pubkey: [4u8; 32], index: 1, }, ]; diff --git a/crates/storage/src/store.rs b/crates/storage/src/store.rs index 83f1e8fc..577b9c50 100644 --- a/crates/storage/src/store.rs +++ b/crates/storage/src/store.rs @@ -2009,8 +2009,8 @@ mod tests { /// post-state's `latest_block_header`). fn sample_state(slot: u64, parent_root: H256, hbh: Vec) -> State { let validators = vec![Validator { - attestation_pubkey: [7u8; 52], - proposal_pubkey: [9u8; 52], + attestation_pubkey: [7u8; 32], + proposal_pubkey: [9u8; 32], index: 0, }]; let mut state = State::from_genesis(1_000, validators); @@ -2695,19 +2695,12 @@ mod tests { // ============ GossipSignatureBuffer Tests ============ fn make_dummy_sig() -> ValidatorSignature { - use ethlambda_types::signature::LeanSignatureScheme; - use leansig::{serialization::Serializable, signature::SignatureScheme}; - use rand::{SeedableRng, rngs::StdRng}; - - static CACHED_SIG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { - let mut rng = StdRng::seed_from_u64(42); - let lifetime = 1 << 5; // small for speed - let (_pk, sk) = LeanSignatureScheme::key_gen(&mut rng, 0, lifetime); - let sig = LeanSignatureScheme::sign(&sk, 0, &[0u8; 32]).unwrap(); - sig.to_bytes() - }); - - ValidatorSignature::from_bytes(&CACHED_SIG).expect("cached test signature") + // These tests never check signature validity, only that a signature + // decodes and carries through the buffers. An all-zero blob is a + // structurally valid (unverifiable) XMSS signature. + use ethlambda_types::signature::SIGNATURE_SIZE; + ValidatorSignature::from_bytes(&vec![0u8; SIGNATURE_SIZE]) + .expect("all-zero test signature decodes") } #[test]