From 0ebf575b0e0319f46f7450cb220af3fd6cd451ae Mon Sep 17 00:00:00 2001 From: artiomtr <44021713+ArtiomTr@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:51:12 +0200 Subject: [PATCH] Updates for devnet-2 Various updates, squashed from feature/multisig-aggregation branch. Co-authored-by: Julius Mieliauskas Co-authored-by: Nojus Sandovas Co-authored-by: Darius Spr <108625236+Dariusspr@users.noreply.github.com> Co-authored-by: LiudasBaronas1 <144480589+LiudasBaronas1@users.noreply.github.com> --- lean_client/Cargo.lock | 1715 ++++++++++++----- lean_client/Cargo.toml | 8 +- lean_client/ENVIRONMENT_SELECTION.md | 26 - lean_client/Makefile | 2 +- lean_client/chain/src/config.rs | 1 + lean_client/containers/Cargo.toml | 15 +- lean_client/containers/src/attestation.rs | 247 ++- lean_client/containers/src/block.rs | 237 +-- lean_client/containers/src/lib.rs | 6 +- lean_client/containers/src/public_key.rs | 207 ++ lean_client/containers/src/serde_helpers.rs | 295 +-- lean_client/containers/src/signature.rs | 121 ++ lean_client/containers/src/state.rs | 788 ++++---- lean_client/containers/src/validator.rs | 77 +- .../tests/test_vectors/block_processing.rs | 11 +- .../containers/tests/test_vectors/mod.rs | 4 + .../containers/tests/test_vectors/runner.rs | 35 +- .../unit_tests/attestation_aggregation.rs | 1 - .../containers/tests/unit_tests/common.rs | 24 +- .../containers/tests/unit_tests/mod.rs | 7 +- .../tests/unit_tests/state_basic.rs | 4 + .../tests/unit_tests/state_justifications.rs | 4 + .../tests/unit_tests/state_process.rs | 69 - .../tests/unit_tests/state_transition.rs | 35 +- lean_client/env-config/Cargo.toml | 4 - lean_client/fork_choice/Cargo.toml | 11 +- lean_client/fork_choice/src/handlers.rs | 188 +- lean_client/fork_choice/src/store.rs | 96 +- .../tests/fork_choice_test_vectors.rs | 1090 +++-------- .../fork_choice/tests/unit_tests/votes.rs | 455 ++--- lean_client/networking/Cargo.toml | 2 - lean_client/networking/src/network/service.rs | 6 - lean_client/networking/src/types.rs | 11 - lean_client/src/main.rs | 36 +- .../test_invalid_signature.json | 58 +- ...test_proposer_and_attester_signatures.json | 1635 +++++++++++----- .../test_proposer_signature.json | 1326 +++++++++++-- lean_client/validator/Cargo.toml | 3 - lean_client/validator/src/keys.rs | 2 +- lean_client/validator/src/lib.rs | 114 +- 40 files changed, 5500 insertions(+), 3476 deletions(-) delete mode 100644 lean_client/ENVIRONMENT_SELECTION.md create mode 100644 lean_client/containers/src/public_key.rs create mode 100644 lean_client/containers/src/signature.rs diff --git a/lean_client/Cargo.lock b/lean_client/Cargo.lock index 5d46d69..3aad917 100644 --- a/lean_client/Cargo.lock +++ b/lean_client/Cargo.lock @@ -51,13 +51,25 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] +[[package]] +name = "air" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "multilinear-toolkit", + "p3-air", + "p3-util 0.3.0", + "tracing", + "utils", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -66,9 +78,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-primitives" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" +checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" dependencies = [ "alloy-rlp", "bytes", @@ -76,14 +88,15 @@ dependencies = [ "const-hex", "derive_more", "foldhash 0.2.0", - "hashbrown 0.16.0", - "indexmap 2.11.4", + "hashbrown 0.16.1", + "indexmap 2.13.0", "itoa", "k256", "keccak-asm", "paste", "proptest", "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash", "serde", @@ -110,6 +123,15 @@ dependencies = [ "libc", ] +[[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.21" @@ -142,22 +164,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -169,7 +191,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arithmetic" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "easy-ext", "typenum", @@ -260,7 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -298,7 +320,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -388,7 +410,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", ] @@ -400,7 +422,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -412,7 +434,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -448,7 +470,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -459,7 +481,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -514,7 +536,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -523,6 +545,20 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backend" +version = "0.3.0" +source = "git+https://github.com/leanEthereum/multilinear-toolkit.git#62766141561550c3540f9f644085fec53d721f16" +dependencies = [ + "fiat-shamir", + "itertools 0.14.0", + "p3-field 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "rayon", + "tracing", +] + [[package]] name = "base-x" version = "0.2.11" @@ -553,9 +589,18 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] [[package]] name = "bit-set" @@ -586,9 +631,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -611,15 +656,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -646,9 +682,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byte-slice-cast" @@ -664,18 +700,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.2.38" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "shlex", @@ -683,9 +719,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -746,9 +782,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -756,9 +792,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -775,14 +811,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -790,6 +826,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -801,9 +846,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", @@ -825,9 +870,9 @@ checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -843,12 +888,25 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constraints-folder" +version = "0.3.0" +source = "git+https://github.com/leanEthereum/multilinear-toolkit.git#62766141561550c3540f9f644085fec53d721f16" +dependencies = [ + "fiat-shamir", + "p3-air", + "p3-field 0.3.0", +] + [[package]] name = "containers" version = "0.1.0" dependencies = [ + "alloy-primitives", + "anyhow", "env-config", "hex", + "lean-multisig", "leansig", "pretty_assertions", "rstest", @@ -964,9 +1022,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1006,7 +1064,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1030,7 +1088,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1041,7 +1099,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1060,15 +1118,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-encoding-macro" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1076,12 +1134,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1121,12 +1179,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1159,7 +1217,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.106", + "syn 2.0.114", "unicode-xid", ] @@ -1184,7 +1242,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -1229,14 +1287,14 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dyn-clone" @@ -1298,7 +1356,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1354,7 +1412,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1374,7 +1432,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1394,7 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1517,11 +1575,22 @@ 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/fiat-shamir.git#bcf23c766f2e930acf11e68777449483a55af077" +dependencies = [ + "p3-challenger 0.3.0", + "p3-field 0.3.0", + "p3-koala-bear 0.3.0", + "serde", +] + [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "fixed-hash" @@ -1559,12 +1628,7 @@ version = "0.1.0" dependencies = [ "containers", "env-config", - "serde", - "serde_json", "ssz", - "ssz_derive", - "ssz_rs", - "typenum", ] [[package]] @@ -1659,7 +1723,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1723,28 +1787,28 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -1777,9 +1841,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1787,7 +1851,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1822,23 +1886,24 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] name = "hashing" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "ethereum-types", "generic-array", "hex-literal", - "sha2 0.10.9 (git+https://github.com/grandinetech/universal-precompiles.git?tag=sha2-v0.10.9-up.1)", + "sha2 0.10.9 (git+https://github.com/grandinetech/universal-precompiles.git?tag=sha2-v0.10.9-up.2)", ] [[package]] @@ -1873,9 +1938,9 @@ dependencies = [ [[package]] name = "hex-literal" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hex_fmt" @@ -1902,7 +1967,7 @@ dependencies = [ "rand 0.9.2", "ring", "socket2 0.5.10", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tokio", "tracing", @@ -1925,7 +1990,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -1950,12 +2015,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1990,9 +2054,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -2012,9 +2076,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "bytes", "futures-channel", @@ -2043,7 +2107,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.0", + "windows-core 0.62.2", ] [[package]] @@ -2103,9 +2167,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -2117,9 +2181,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -2252,7 +2316,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2268,12 +2332,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2349,15 +2413,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -2402,6 +2466,20 @@ 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/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "clap", + "multilinear-toolkit", + "p3-koala-bear 0.3.0", + "poseidon_circuit", + "rec_aggregation", + "whir-p3", + "xmss", +] + [[package]] name = "lean_client" version = "0.1.0" @@ -2411,7 +2489,7 @@ dependencies = [ "containers", "fork-choice", "hex", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "networking", "tokio", "tracing", @@ -2419,31 +2497,120 @@ dependencies = [ "validator", ] +[[package]] +name = "lean_compiler" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "air", + "lean_vm", + "lookup", + "multilinear-toolkit", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "pest", + "pest_derive", + "rand 0.9.2", + "sub_protocols", + "tracing", + "utils", + "whir-p3", + "xmss", +] + +[[package]] +name = "lean_prover" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "air", + "itertools 0.14.0", + "lean_compiler", + "lean_vm", + "lookup", + "multilinear-toolkit", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "pest", + "pest_derive", + "poseidon_circuit", + "rand 0.9.2", + "sub_protocols", + "tracing", + "utils", + "whir-p3", + "witness_generation", + "xmss", +] + +[[package]] +name = "lean_vm" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "air", + "colored", + "derive_more", + "itertools 0.14.0", + "lookup", + "multilinear-toolkit", + "num_enum", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "pest", + "pest_derive", + "rand 0.9.2", + "strum", + "sub_protocols", + "thiserror 2.0.17", + "tracing", + "utils", + "whir-p3", + "xmss", +] + [[package]] name = "leansig" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanSig?branch=main#d9610e7fbbc75197f134e065df79acc630994706" +source = "git+https://github.com/leanEthereum/leanSig?branch=main#ae12a5feb25d917c42b6466444ebd56ec115a629" dependencies = [ "dashmap", "ethereum_ssz", "num-bigint", "num-traits", - "p3-baby-bear", - "p3-field", - "p3-koala-bear", - "p3-symmetric", + "p3-baby-bear 0.4.1", + "p3-field 0.4.1", + "p3-koala-bear 0.4.1", + "p3-symmetric 0.4.1", "rand 0.9.2", "rayon", "serde", "sha3", - "thiserror 2.0.16", + "thiserror 2.0.17", ] +[[package]] +name = "lib-c" +version = "0.13.0" +source = "git+https://github.com/0xPolygonHermez/zisk.git?tag=v0.13.0#ea1ed4c518992a170fc59ec19f1228eb4829a9e1" + [[package]] name = "libc" -version = "0.2.175" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" @@ -2461,14 +2628,14 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.16", + "getrandom 0.2.17", "libp2p-allow-block-list", "libp2p-connection-limits", - "libp2p-core 0.43.1", + "libp2p-core 0.43.2", "libp2p-dns", "libp2p-gossipsub", "libp2p-identify", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "libp2p-mdns", "libp2p-metrics", "libp2p-noise", @@ -2481,7 +2648,7 @@ dependencies = [ "multiaddr 0.18.2", "pin-project", "rw-stream-sink 0.4.0", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -2490,8 +2657,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a" dependencies = [ - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", ] @@ -2501,8 +2668,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0" dependencies = [ - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", ] @@ -2536,15 +2703,15 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.43.1" +version = "0.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d28e2d2def7c344170f5c6450c0dbe3dfef655610dbfde2f6ac28a527abbe36" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" dependencies = [ "either", "fnv", "futures", "futures-timer", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "multiaddr 0.18.2", "multihash 0.19.3", "multistream-select 0.13.0", @@ -2553,7 +2720,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "rw-stream-sink 0.4.0", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "unsigned-varint 0.8.0", "web-time", @@ -2568,8 +2735,8 @@ dependencies = [ "async-trait", "futures", "hickory-resolver", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "parking_lot", "smallvec", "tracing", @@ -2590,11 +2757,11 @@ dependencies = [ "fnv", "futures", "futures-timer", - "getrandom 0.2.16", + "getrandom 0.2.17", "hashlink", "hex_fmt", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", "quick-protobuf", "quick-protobuf-codec", @@ -2616,13 +2783,13 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", ] @@ -2646,9 +2813,9 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" dependencies = [ "asn1_der", "bs58 0.5.1", @@ -2659,7 +2826,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "sha2 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "zeroize", ] @@ -2673,8 +2840,8 @@ dependencies = [ "futures", "hickory-proto", "if-watch", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", "rand 0.8.5", "smallvec", @@ -2690,10 +2857,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7" dependencies = [ "futures", - "libp2p-core 0.43.1", + "libp2p-core 0.43.2", "libp2p-gossipsub", "libp2p-identify", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "libp2p-swarm", "pin-project", "prometheus-client", @@ -2727,15 +2894,15 @@ dependencies = [ "asynchronous-codec 0.7.0", "bytes", "futures", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "multiaddr 0.18.2", "multihash 0.19.3", "quick-protobuf", "rand 0.8.5", "snow", "static_assertions", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "x25519-dalek", "zeroize", @@ -2750,15 +2917,15 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-tls", "quinn", "rand 0.8.5", "ring", "rustls", "socket2 0.5.10", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -2772,8 +2939,8 @@ dependencies = [ "async-trait", "futures", "futures-bounded", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm", "rand 0.8.5", "smallvec", @@ -2790,8 +2957,8 @@ dependencies = [ "fnv", "futures", "futures-timer", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "libp2p-swarm-derive", "lru", "multistream-select 0.13.0", @@ -2810,7 +2977,7 @@ checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" dependencies = [ "heck", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2823,7 +2990,7 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core 0.43.1", + "libp2p-core 0.43.2", "socket2 0.5.10", "tokio", "tracing", @@ -2837,13 +3004,13 @@ checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" dependencies = [ "futures", "futures-rustls", - "libp2p-core 0.43.1", - "libp2p-identity 0.2.12", + "libp2p-core 0.43.2", + "libp2p-identity 0.2.13", "rcgen", "ring", "rustls", "rustls-webpki", - "thiserror 2.0.16", + "thiserror 2.0.17", "x509-parser", "yasna", ] @@ -2857,7 +3024,7 @@ dependencies = [ "futures", "futures-timer", "igd-next", - "libp2p-core 0.43.1", + "libp2p-core 0.43.2", "libp2p-swarm", "tokio", "tracing", @@ -2871,8 +3038,8 @@ checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" dependencies = [ "either", "futures", - "libp2p-core 0.43.1", - "thiserror 2.0.16", + "libp2p-core 0.43.2", + "thiserror 2.0.17", "tracing", "yamux 0.12.1", "yamux 0.13.8", @@ -2901,9 +3068,24 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lookup" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "multilinear-toolkit", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "tracing", + "utils", + "whir-p3", +] [[package]] name = "lru" @@ -2942,9 +3124,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "minimal-lexical" @@ -2954,20 +3136,20 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.1", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "moka" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -2975,7 +3157,6 @@ dependencies = [ "equivalent", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", "smallvec", "tagptr", "uuid", @@ -3015,7 +3196,7 @@ dependencies = [ "arrayref", "byteorder", "data-encoding", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "multibase", "multihash 0.19.3", "percent-encoding", @@ -3072,6 +3253,20 @@ dependencies = [ "synstructure 0.12.6", ] +[[package]] +name = "multilinear-toolkit" +version = "0.3.0" +source = "git+https://github.com/leanEthereum/multilinear-toolkit.git#62766141561550c3540f9f644085fec53d721f16" +dependencies = [ + "backend", + "constraints-folder", + "fiat-shamir", + "p3-field 0.3.0", + "p3-util 0.3.0", + "rayon", + "sumcheck", +] + [[package]] name = "multistream-select" version = "0.12.1" @@ -3148,7 +3343,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -3178,7 +3373,7 @@ dependencies = [ "futures", "hex", "libp2p", - "libp2p-identity 0.2.12", + "libp2p-identity 0.2.13", "libp2p-mplex", "parking_lot", "rand 0.8.5", @@ -3226,7 +3421,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3274,6 +3469,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -3305,176 +3522,399 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "p3-air" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "p3-field 0.3.0", + "p3-matrix 0.3.0", +] + [[package]] name = "p3-baby-bear" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "p3-field", - "p3-mds", - "p3-monty-31", - "p3-poseidon2", - "p3-symmetric", + "p3-field 0.3.0", + "p3-mds 0.3.0", + "p3-monty-31 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", "rand 0.9.2", ] [[package]] -name = "p3-dft" +name = "p3-baby-bear" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-challenger 0.4.1", + "p3-field 0.4.1", + "p3-mds 0.4.1", + "p3-monty-31 0.4.1", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-challenger" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "spin", + "p3-field 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", "tracing", ] [[package]] -name = "p3-field" -version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +name = "p3-challenger" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" dependencies = [ - "itertools 0.14.0", - "num-bigint", - "p3-maybe-rayon", - "p3-util", - "paste", - "rand 0.9.2", - "serde", + "p3-field 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-monty-31 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", "tracing", ] [[package]] -name = "p3-koala-bear" +name = "p3-commit" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "p3-field", - "p3-monty-31", - "p3-poseidon2", - "p3-symmetric", - "rand 0.9.2", + "itertools 0.14.0", + "p3-challenger 0.3.0", + "p3-dft 0.3.0", + "p3-field 0.3.0", + "p3-matrix 0.3.0", + "p3-util 0.3.0", + "serde", ] [[package]] -name = "p3-matrix" +name = "p3-dft" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ "itertools 0.14.0", - "p3-field", - "p3-maybe-rayon", - "p3-util", - "rand 0.9.2", - "serde", + "p3-field 0.3.0", + "p3-matrix 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-util 0.3.0", "tracing", - "transpose", ] [[package]] -name = "p3-maybe-rayon" -version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +name = "p3-dft" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.4.1", + "p3-matrix 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", + "spin", + "tracing", +] [[package]] -name = "p3-mds" +name = "p3-field" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "p3-dft", - "p3-field", - "p3-symmetric", - "p3-util", + "itertools 0.14.0", + "num-bigint", + "p3-maybe-rayon 0.3.0", + "p3-util 0.3.0", + "paste", "rand 0.9.2", + "serde", + "tracing", ] [[package]] -name = "p3-monty-31" -version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +name = "p3-field" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" dependencies = [ "itertools 0.14.0", "num-bigint", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", - "p3-util", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", "paste", "rand 0.9.2", "serde", - "spin", "tracing", - "transpose", ] [[package]] -name = "p3-poseidon2" +name = "p3-interpolation" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "p3-field", - "p3-mds", - "p3-symmetric", - "p3-util", - "rand 0.9.2", + "p3-field 0.3.0", + "p3-matrix 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-util 0.3.0", ] [[package]] -name = "p3-symmetric" +name = "p3-koala-bear" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ "itertools 0.14.0", - "p3-field", + "num-bigint", + "p3-field 0.3.0", + "p3-monty-31 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", "serde", ] [[package]] -name = "p3-util" +name = "p3-koala-bear" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-challenger 0.4.1", + "p3-field 0.4.1", + "p3-monty-31 0.4.1", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-matrix" version = "0.3.0" -source = "git+https://github.com/Plonky3/Plonky3.git?rev=a33a312#a33a31274a5e78bb5fbe3f82ffd2c294e17fa830" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ + "itertools 0.14.0", + "p3-field 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", "serde", + "tracing", + "transpose", ] [[package]] -name = "parity-scale-codec" -version = "3.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +name = "p3-matrix" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", + "itertools 0.14.0", + "p3-field 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", "serde", + "tracing", + "transpose", ] [[package]] -name = "parity-scale-codec-derive" -version = "3.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +name = "p3-maybe-rayon" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", + "rayon", ] [[package]] -name = "parking" -version = "2.2.1" +name = "p3-maybe-rayon" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" + +[[package]] +name = "p3-mds" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "p3-dft 0.3.0", + "p3-field 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", +] + +[[package]] +name = "p3-mds" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-dft 0.4.1", + "p3-field 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-merkle-tree" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "itertools 0.14.0", + "p3-commit", + "p3-field 0.3.0", + "p3-matrix 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "serde", + "tracing", +] + +[[package]] +name = "p3-monty-31" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-dft 0.3.0", + "p3-field 0.3.0", + "p3-matrix 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-mds 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "paste", + "rand 0.9.2", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-monty-31" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-dft 0.4.1", + "p3-field 0.4.1", + "p3-matrix 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-mds 0.4.1", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "paste", + "rand 0.9.2", + "serde", + "spin", + "tracing", + "transpose", +] + +[[package]] +name = "p3-poseidon2" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "p3-field 0.3.0", + "p3-mds 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-field 0.4.1", + "p3-mds 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-symmetric" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.3.0", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.4.1", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.3.0" +source = "git+https://github.com/TomWambsgans/Plonky3.git?branch=lean-multisig#1db9df28abd6db586eaa891af2416d94d1b026ae" +dependencies = [ + "rayon", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "serde", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" @@ -3525,14 +3965,47 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.3" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -3550,7 +4023,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3586,7 +4059,7 @@ dependencies = [ "hermit-abi", "pin-project-lite", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3614,9 +4087,25 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "poseidon_circuit" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "multilinear-toolkit", + "p3-koala-bear 0.3.0", + "p3-monty-31 0.3.0", + "p3-poseidon2 0.3.0", + "rand 0.9.2", + "sub_protocols", + "tracing", + "utils", + "whir-p3", +] [[package]] name = "potential_utf" @@ -3710,9 +4199,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -3737,19 +4226,18 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "proptest" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.4", - "lazy_static", + "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -3803,7 +4291,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.1", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -3816,7 +4304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -3824,7 +4312,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -3846,9 +4334,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -3883,7 +4371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.4", "serde", ] @@ -3904,7 +4392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.4", ] [[package]] @@ -3913,16 +4401,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "serde", ] @@ -3932,7 +4420,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.4", +] + +[[package]] +name = "rapidhash" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" +dependencies = [ + "rustversion", ] [[package]] @@ -3968,40 +4465,68 @@ dependencies = [ "yasna", ] +[[package]] +name = "rec_aggregation" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "air", + "bincode", + "lean_compiler", + "lean_prover", + "lean_vm", + "lookup", + "multilinear-toolkit", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "serde", + "serde_json", + "sub_protocols", + "tracing", + "utils", + "whir-p3", + "xmss", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -4011,9 +4536,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -4022,9 +4547,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "relative-path" @@ -4040,9 +4565,9 @@ checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" [[package]] name = "resolv-conf" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" @@ -4062,7 +4587,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -4103,7 +4628,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.106", + "syn 2.0.114", "unicode-ident", ] @@ -4127,9 +4652,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.0" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -4200,22 +4725,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "once_cell", "ring", @@ -4227,9 +4752,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -4288,9 +4813,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "schemars" @@ -4306,9 +4831,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -4362,9 +4887,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -4372,42 +4897,42 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_utils" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "const-hex", "generic-array", @@ -4422,19 +4947,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", + "schemars 1.2.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -4442,14 +4966,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4458,26 +4982,13 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.9" @@ -4492,11 +5003,12 @@ dependencies = [ [[package]] name = "sha2" version = "0.10.9" -source = "git+https://github.com/grandinetech/universal-precompiles.git?tag=sha2-v0.10.9-up.1#dab12204de2e6a40f7a4c93b59347f60174c6953" +source = "git+https://github.com/grandinetech/universal-precompiles.git?tag=sha2-v0.10.9-up.2#7d57ea01cd5fe5f6458142ce6ac269cc44b425bd" dependencies = [ "cfg-if", "cpufeatures", "digest 0.10.7", + "ziskos", ] [[package]] @@ -4536,10 +5048,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4630,7 +5143,7 @@ dependencies = [ [[package]] name = "ssz" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "arithmetic", "bit_field", @@ -4653,7 +5166,7 @@ dependencies = [ "static_assertions", "std_ext", "tap", - "thiserror 2.0.16", + "thiserror 2.0.17", "triomphe", "try_from_iterator", "typenum", @@ -4662,7 +5175,7 @@ dependencies = [ [[package]] name = "ssz_derive" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "darling", "easy-ext", @@ -4670,39 +5183,14 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "ssz_rs" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057291e5631f280978fa9c8009390663ca4613359fc1318e36a8c24c392f6d1f" -dependencies = [ - "bitvec", - "hex", - "num-bigint", - "serde", - "sha2 0.9.9", - "ssz_rs_derive", -] - -[[package]] -name = "ssz_rs_derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "syn 2.0.114", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -4713,7 +5201,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "std_ext" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" dependencies = [ "easy-ext", "triomphe", @@ -4731,12 +5219,61 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sub_protocols" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "derive_more", + "lookup", + "multilinear-toolkit", + "p3-util 0.3.0", + "tracing", + "utils", + "whir-p3", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "sumcheck" +version = "0.3.0" +source = "git+https://github.com/leanEthereum/multilinear-toolkit.git#62766141561550c3540f9f644085fec53d721f16" +dependencies = [ + "backend", + "constraints-folder", + "fiat-shamir", + "p3-air", + "p3-field 0.3.0", + "p3-util 0.3.0", + "rayon", +] + [[package]] name = "syn" version = "1.0.109" @@ -4750,9 +5287,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -4779,7 +5316,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4788,7 +5325,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation", "system-configuration-sys", ] @@ -4817,15 +5354,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -4839,11 +5376,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -4854,18 +5391,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4944,9 +5481,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4956,7 +5493,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.6.1", "tokio-macros", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -4967,14 +5504,14 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4995,20 +5532,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -5016,9 +5553,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -5031,9 +5568,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -5043,25 +5580,38 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", ] +[[package]] +name = "tracing-forest" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92bdb3c949c9e81b71f78ba782f956b896019d82cc2f31025d21e04adab4d695" +dependencies = [ + "ansi_term", + "smallvec", + "thiserror 2.0.17", + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -5075,9 +5625,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -5103,9 +5653,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" dependencies = [ "serde", "stable_deref_trait", @@ -5120,7 +5670,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "try_from_iterator" version = "0.0.0" -source = "git+https://github.com/grandinetech/grandine?branch=develop#5bdc78763c8959ad689c79d51d7d59978460bb1e" +source = "git+https://github.com/grandinetech/grandine?branch=develop#8ac065da176067bc4eb8b79ebfc48a6433f52499" [[package]] name = "typenum" @@ -5166,9 +5716,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -5222,9 +5772,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -5244,13 +5794,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utils" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "multilinear-toolkit", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "tracing", + "tracing-forest", + "tracing-subscriber", +] + [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "wasm-bindgen", ] @@ -5263,7 +5830,6 @@ dependencies = [ "env-config", "fork-choice", "leansig", - "serde", "serde_yaml", "tracing", "typenum", @@ -5311,15 +5877,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -5331,9 +5888,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -5342,25 +5899,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5368,22 +5911,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -5398,12 +5941,61 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whir-p3" +version = "0.1.0" +source = "git+https://github.com/TomWambsgans/whir-p3?branch=lean-multisig#04fb1c1f2e3bbd14e6e4aee32621656eb3f3949f" +dependencies = [ + "itertools 0.14.0", + "multilinear-toolkit", + "p3-baby-bear 0.3.0", + "p3-challenger 0.3.0", + "p3-commit", + "p3-dft 0.3.0", + "p3-field 0.3.0", + "p3-interpolation", + "p3-koala-bear 0.3.0", + "p3-matrix 0.3.0", + "p3-maybe-rayon 0.3.0", + "p3-merkle-tree", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "rayon", + "thiserror 2.0.17", + "tracing", + "tracing-forest", + "tracing-subscriber", +] + [[package]] name = "widestring" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.53.0" @@ -5426,44 +6018,44 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", - "windows-result 0.4.0", + "windows-result 0.4.1", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" @@ -5476,18 +6068,18 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -5510,20 +6102,29 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -5561,9 +6162,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.1", @@ -5716,9 +6317,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -5739,6 +6340,35 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "witness_generation" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "air", + "derive_more", + "lean_compiler", + "lean_vm", + "lookup", + "multilinear-toolkit", + "p3-air", + "p3-challenger 0.3.0", + "p3-koala-bear 0.3.0", + "p3-monty-31 0.3.0", + "p3-poseidon2 0.3.0", + "p3-symmetric 0.3.0", + "p3-util 0.3.0", + "pest", + "pest_derive", + "poseidon_circuit", + "rand 0.9.2", + "sub_protocols", + "tracing", + "utils", + "whir-p3", + "xmss", +] + [[package]] name = "writeable" version = "0.6.2" @@ -5779,7 +6409,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", ] @@ -5798,6 +6428,19 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "xmss" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanMultisig?branch=main#72c27460314770dee435adf80494b2d684d3959b" +dependencies = [ + "multilinear-toolkit", + "p3-koala-bear 0.3.0", + "p3-util 0.3.0", + "rand 0.9.2", + "sha3", + "utils", +] + [[package]] name = "yamux" version = "0.12.1" @@ -5863,28 +6506,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure 0.13.2", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5904,7 +6547,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -5919,13 +6562,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5958,5 +6601,27 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "ziskos" +version = "0.13.0" +source = "git+https://github.com/0xPolygonHermez/zisk.git?tag=v0.13.0#ea1ed4c518992a170fc59ec19f1228eb4829a9e1" +dependencies = [ + "cfg-if", + "getrandom 0.2.17", + "lazy_static", + "lib-c", + "num-bigint", + "num-traits", + "rand 0.8.5", + "static_assertions", + "tiny-keccak", ] + +[[package]] +name = "zmij" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" diff --git a/lean_client/Cargo.toml b/lean_client/Cargo.toml index 9e72c43..cb996ba 100644 --- a/lean_client/Cargo.toml +++ b/lean_client/Cargo.toml @@ -34,12 +34,13 @@ serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" snap = "1.1" ssz = { git = "https://github.com/grandinetech/grandine", package = "ssz", branch = "develop" } -ssz-derive = { git = "https://github.com/grandinetech/grandine", package = "ssz_derive", branch = "develop" } +ssz_derive = { git = "https://github.com/grandinetech/grandine", package = "ssz_derive", branch = "develop" } ssz-types = "0.3.0" tokio = { version = "1.0", features = ["full"] } tree-hash = "0.4.0" typenum = "1.19" sha2 = "0.10" +rand = "0.9" [workspace.dev-dependencies] rstest = "0.18.2" @@ -52,10 +53,9 @@ version = "0.1.0" edition = "2021" [features] -default = ["devnet2", "xmss-signing"] +default = ["xmss-signing", "containers/xmss-verify"] xmss-signing = ["validator/xmss-signing"] -devnet1 = ["containers/devnet1", "fork-choice/devnet1", "networking/devnet1", "validator/devnet1"] -devnet2 = ["containers/devnet2", "fork-choice/devnet2", "networking/devnet2", "validator/devnet2"] +xmss-verify = ["containers/xmss-verify"] [dependencies] chain = { path = "./chain" } diff --git a/lean_client/ENVIRONMENT_SELECTION.md b/lean_client/ENVIRONMENT_SELECTION.md deleted file mode 100644 index d906c9d..0000000 --- a/lean_client/ENVIRONMENT_SELECTION.md +++ /dev/null @@ -1,26 +0,0 @@ -### To select which devnet you want to compile - -#### Option A -- Change the default features in root `Cargo.toml`: -```toml -[features] -default = ["devnet1", "<...other features>"] # Change to "devnet2" if needed -devnet1 = [...] -devnet2 = [...] -``` - -#### Option B -- Use the `--no-default-features` flag and specify the desired devnet feature when building or running the project: -```bash -cargo build --no-default-features --features devnet1 # Change to devnet2 -``` - - -### Running tests for a specific devnet - -From root directory, use the following command: -```bash -cargo test -p --no-default-features --features devnet1 # Change to devnet2 -``` - -Use `` to specify the crate you want to test. \ No newline at end of file diff --git a/lean_client/Makefile b/lean_client/Makefile index 47eda46..2c466b9 100644 --- a/lean_client/Makefile +++ b/lean_client/Makefile @@ -28,7 +28,7 @@ check-format: .PHONY: test test: - cargo test --workspace --features devnet2,xmss-signing --no-fail-fast + cargo test --workspace --all-features --no-fail-fast .PHONY: generate-test-vectors generate-test-vectors: diff --git a/lean_client/chain/src/config.rs b/lean_client/chain/src/config.rs index 6dd26fb..1d762de 100644 --- a/lean_client/chain/src/config.rs +++ b/lean_client/chain/src/config.rs @@ -11,6 +11,7 @@ impl BasisPoint { None } } + #[inline] pub fn get(&self) -> u64 { self.0 diff --git a/lean_client/containers/Cargo.toml b/lean_client/containers/Cargo.toml index 29e8ecd..0927f7e 100644 --- a/lean_client/containers/Cargo.toml +++ b/lean_client/containers/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" [features] -xmss-verify = ["leansig"] +xmss-verify = [] default = [] -devnet1 = ["env-config/devnet1"] -devnet2 = ["env-config/devnet2"] [lib] name = "containers" @@ -15,15 +13,18 @@ path = "src/lib.rs" [dependencies] env-config = { path = "../env-config", default-features = false } -ssz = { git = "https://github.com/grandinetech/grandine", package = "ssz", branch = "develop", submodules = true } -ssz_derive = { git = "https://github.com/grandinetech/grandine", package = "ssz_derive", branch = "develop", submodules = false } +ssz = { workspace = true } +serde = { workspace = true } +ssz_derive = { workspace = true } typenum = "1" -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9" hex = "0.4.3" sha2 = "0.10" -leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "main", optional = true } +leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "main" } +lean-multisig = { git = "https://github.com/leanEthereum/leanMultisig", branch = "main" } +anyhow = "1.0.100" +alloy-primitives = "1.5.2" [dev-dependencies] rstest = "0.18" diff --git a/lean_client/containers/src/attestation.rs b/lean_client/containers/src/attestation.rs index 6779b0f..ba0596c 100644 --- a/lean_client/containers/src/attestation.rs +++ b/lean_client/containers/src/attestation.rs @@ -1,9 +1,15 @@ use crate::{Checkpoint, Slot, Uint64}; +use leansig::serialization::Serializable; use serde::{Deserialize, Serialize}; use ssz::BitList; use ssz::ByteVector; +use ssz::{SszHash, H256}; use ssz_derive::Ssz; -use typenum::{Prod, Sum, U100, U12, U31}; +use std::collections::HashSet; +use typenum::{Prod, Sum, U100, U1024, U12, U31}; + +// Type-level number for 1 MiB (1048576 = 1024 * 1024) +type U1048576 = Prod; pub type U3100 = Prod; @@ -22,19 +28,191 @@ pub type Attestations = ssz::PersistentList; pub type AggregatedAttestations = ssz::PersistentList; -#[cfg(feature = "devnet1")] -pub type AttestationSignatures = ssz::PersistentList; - -#[cfg(feature = "devnet2")] -pub type AttestationSignatures = ssz::PersistentList; +pub type AttestationSignatures = ssz::PersistentList; -#[cfg(feature = "devnet2")] +/// Legacy naive aggregated signature type (list of individual XMSS signatures). +/// Kept for backwards compatibility but no longer used in wire format. pub type NaiveAggregatedSignature = ssz::PersistentList; +/// Aggregated signature proof from lean-multisig zkVM. +/// +/// This is a variable-length byte list (up to 1 MiB) containing the serialized +/// proof bytes from `xmss_aggregate_signatures()`. The `#[ssz(transparent)]` +/// attribute makes this type serialize directly as a ByteList for SSZ wire format. +#[derive(Clone, Debug, PartialEq, Eq, Default, Ssz, Serialize, Deserialize)] +#[ssz(transparent)] +pub struct MultisigAggregatedSignature( + /// The serialized zkVM proof bytes from lean-multisig aggregation. + #[serde(with = "crate::serde_helpers::byte_list")] + pub ssz::ByteList, +); + +impl MultisigAggregatedSignature { + /// Create a new MultisigAggregatedSignature from proof bytes. + pub fn new(proof: Vec) -> Result { + ssz::ByteList::try_from(proof) + .map(Self) + .map_err(|_| AggregationError::AggregationFailed) + } + + /// Get the proof bytes. + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + /// Check if the signature is empty (no proof). + pub fn is_empty(&self) -> bool { + self.0.as_bytes().is_empty() + } + + /// Aggregate individual XMSS signatures into a single proof. + /// + /// Uses lean-multisig zkVM to combine multiple signatures into a compact proof. + /// + /// # Arguments + /// * `public_keys` - Public keys of the signers + /// * `signatures` - Individual XMSS signatures to aggregate + /// * `message` - The 32-byte message that was signed (as 8 field elements) + /// * `epoch` - The epoch/slot in which signatures were created + /// + /// # Returns + /// Aggregated signature proof, or error if aggregation fails. + pub fn aggregate( + public_keys: &[lean_multisig::XmssPublicKey], + signatures: &[lean_multisig::XmssSignature], + message: [lean_multisig::F; 8], + epoch: u64, + ) -> Result { + if public_keys.is_empty() { + return Err(AggregationError::EmptyInput); + } + if public_keys.len() != signatures.len() { + return Err(AggregationError::MismatchedLengths); + } + + let proof_bytes = + lean_multisig::xmss_aggregate_signatures(public_keys, signatures, message, epoch) + .map_err(|_| AggregationError::AggregationFailed)?; + + Self::new(proof_bytes) + } + + /// Verify the aggregated signature proof against the given public keys and message. + /// + /// Uses lean-multisig zkVM to verify that the aggregated proof is valid + /// for all the given public keys signing the same message at the given epoch. + /// + /// # Returns + /// `Ok(())` if the proof is valid, `Err` with the proof error otherwise. + pub fn verify( + &self, + public_keys: &[lean_multisig::XmssPublicKey], + message: [lean_multisig::F; 8], + epoch: u64, + ) -> Result<(), AggregationError> { + lean_multisig::xmss_verify_aggregated_signatures( + public_keys, + message, + self.0.as_bytes(), + epoch, + ) + .map_err(|_| AggregationError::VerificationFailed) + } + + /// Verify the aggregated payload against validators and message. + /// + /// This is a convenience method that extracts public keys from validators + /// and converts the message bytes to the field element format expected by lean-multisig. + /// + /// # Arguments + /// * `validators` - Slice of validator references to extract public keys from + /// * `message` - 32-byte message (typically attestation data root) + /// * `epoch` - Epoch/slot for proof verification + /// + /// # Returns + /// `Ok(())` if verification succeeds, `Err` otherwise. + pub fn verify_aggregated_payload( + &self, + validators: &[&crate::validator::Validator], + message: &[u8; 32], + epoch: u64, + ) -> Result<(), AggregationError> { + // Extract public keys from validators + let mut public_keys = Vec::new(); + for validator in validators { + // Convert PublicKey to lean_multisig::XmssPublicKey + let lean_sig_pk = validator + .pubkey + .as_lean_sig() + .map_err(|_| AggregationError::VerificationFailed)?; + let pk_bytes = lean_sig_pk.to_bytes(); + // TODO: Implement proper conversion from PublicKey bytes to lean_multisig::XmssPublicKey + // Once lean-multisig API is clarified, convert pk_bytes to XmssPublicKey + todo!("Convert PublicKey to lean_multisig::XmssPublicKey and implement message field conversion"); + } + + // Convert 32-byte message to 8 field elements + // TODO: Implement proper conversion from 32 bytes to 8 field elements + let message_fields = todo!("Convert 32-byte message to [lean_multisig::F; 8]"); + + // Call verify with extracted keys and converted message + self.verify(&public_keys, message_fields, epoch) + } +} + +/// Error types for signature aggregation operations. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AggregationError { + /// No signatures provided for aggregation. + EmptyInput, + /// Public keys and signatures arrays have different lengths. + MismatchedLengths, + /// Aggregation failed in lean-multisig. + AggregationFailed, + /// Verification of aggregated proof failed. + VerificationFailed, +} + +/// Aggregated signature proof with participant tracking. +/// +/// This type combines the participant bitfield with the proof bytes, +/// matches Python's `AggregatedSignatureProof` container structure. +/// Used in `aggregated_payloads` to track which validators are covered by each proof. +#[derive(Clone, Debug, PartialEq, Eq, Default, Ssz, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AggregatedSignatureProof { + /// Bitfield indicating which validators' signatures are included. + pub participants: AggregationBits, + /// The raw aggregated proof bytes from lean-multisig. + pub proof_data: MultisigAggregatedSignature, +} + +impl AggregatedSignatureProof { + /// Create a new AggregatedSignatureProof. + pub fn new(participants: AggregationBits, proof_data: MultisigAggregatedSignature) -> Self { + Self { + participants, + proof_data, + } + } + + pub fn from_aggregation(participant_ids: &[u64], proof: MultisigAggregatedSignature) -> Self { + Self { + participants: AggregationBits::from_validator_indices(participant_ids), + proof_data: proof, + } + } + + /// Get the validator indices covered by this proof. + pub fn get_participant_indices(&self) -> Vec { + self.participants.to_validator_indices() + } +} + /// Bitlist representing validator participation in an attestation. /// Limit is VALIDATOR_REGISTRY_LIMIT (4096). #[derive(Clone, Debug, PartialEq, Eq, Default, Ssz, Serialize, Deserialize)] -pub struct AggregationBits(pub BitList); +pub struct AggregationBits(#[serde(with = "crate::serde_helpers::bitlist")] pub BitList); impl AggregationBits { pub const LIMIT: u64 = 4096; @@ -98,6 +276,34 @@ pub struct AttestationData { pub source: Checkpoint, } +impl AttestationData { + /// Compute the data root bytes for signature lookup. + /// This is the hash tree root of the attestation data. + pub fn data_root_bytes(&self) -> crate::Bytes32 { + crate::Bytes32(ssz::SszHash::hash_tree_root(self)) + } +} + +/// Key for looking up individual validator signatures. +/// Used to index signature caches by (validator, attestation_data_root) pairs. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SignatureKey { + /// The validator who produced the signature. + pub validator_id: u64, + /// The hash of the signed attestation data. + pub data_root: crate::Bytes32, +} + +impl SignatureKey { + /// Create a new signature key. + pub fn new(validator_id: u64, data_root: crate::Bytes32) -> Self { + Self { + validator_id, + data_root, + } + } +} + /// Validator specific attestation wrapping shared attestation data. #[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -111,17 +317,14 @@ pub struct Attestation { /// Validator attestation bundled with its signature. #[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)] pub struct SignedAttestation { - #[cfg(feature = "devnet2")] pub validator_id: u64, - #[cfg(feature = "devnet2")] pub message: AttestationData, - #[cfg(feature = "devnet1")] - pub message: Attestation, pub signature: Signature, } /// Aggregated attestation consisting of participation bits and message. #[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AggregatedAttestation { /// Bitfield indicating which validators participated in the aggregation. pub aggregation_bits: AggregationBits, @@ -158,16 +361,16 @@ impl AggregatedAttestation { .collect() } - pub fn to_plain(&self) -> Vec { - let validator_indices = self.aggregation_bits.to_validator_indices(); - - validator_indices - .into_iter() - .map(|validator_id| Attestation { - validator_id: Uint64(validator_id), - data: self.data.clone(), - }) - .collect() + /// Returns true if the provided list contains duplicate AttestationData. + pub fn has_duplicate_data(attestations: &AggregatedAttestations) -> bool { + let mut seen: HashSet = HashSet::new(); + for attestation in attestations { + let root = attestation.data.hash_tree_root(); + if !seen.insert(root) { + return true; + } + } + false } } diff --git a/lean_client/containers/src/block.rs b/lean_client/containers/src/block.rs index 0acf1b2..52d6d59 100644 --- a/lean_client/containers/src/block.rs +++ b/lean_client/containers/src/block.rs @@ -1,13 +1,10 @@ -use crate::{Attestation, Attestations, Bytes32, Signature, Slot, State, ValidatorIndex}; +use crate::{ + Attestation, Bytes32, MultisigAggregatedSignature, Signature, Slot, State, ValidatorIndex, +}; use serde::{Deserialize, Serialize}; use ssz_derive::Ssz; -#[cfg(feature = "xmss-verify")] -use leansig::signature::generalized_xmss::instantiations_poseidon::lifetime_2_to_the_20::target_sum::SIGTargetSumLifetime20W2NoOff; -use ssz::{PersistentList, SszHash}; -use typenum::U4096; use crate::attestation::{AggregatedAttestations, AttestationSignatures}; -use crate::validator::BlsPublicKey; /// The body of a block, containing payload data. /// @@ -15,11 +12,8 @@ use crate::validator::BlsPublicKey; /// separately in BlockSignatures to match the spec architecture. #[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)] pub struct BlockBody { - #[cfg(feature = "devnet2")] + #[serde(with = "crate::serde_helpers::aggregated_attestations")] pub attestations: AggregatedAttestations, - #[cfg(feature = "devnet1")] - #[serde(with = "crate::serde_helpers")] - pub attestations: Attestations, } #[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)] @@ -53,8 +47,11 @@ pub struct BlockWithAttestation { } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Ssz, Deserialize, Default)] +#[serde(rename_all = "camelCase")] pub struct BlockSignatures { + #[serde(with = "crate::serde_helpers::attestation_signatures")] pub attestation_signatures: AttestationSignatures, + #[serde(with = "crate::serde_helpers::signature")] pub proposer_signature: Signature, } @@ -67,10 +64,6 @@ pub struct SignedBlockWithAttestation { /// Aggregated signature payload for the block. /// /// Signatures remain in attestation order followed by the proposer signature. - #[cfg(feature = "devnet1")] - #[serde(with = "crate::serde_helpers::block_signatures")] - pub signature: PersistentList, - #[cfg(feature = "devnet2")] pub signature: BlockSignatures, } @@ -128,95 +121,9 @@ impl SignedBlockWithAttestation { /// /// - Spec: /// - XMSS Library: - #[cfg(feature = "devnet1")] - pub fn verify_signatures(&self, parent_state: State) -> bool { - // Unpack the signed block components - let block = &self.message.block; - let signatures = &self.signature; - - // Combine all attestations that need verification - // - // This creates a single list containing both: - // 1. Block body attestations (from other validators) - // 2. Proposer attestation (from the block producer) - let mut all_attestations: Vec = Vec::new(); - - // Collect block body attestations - let mut i: u64 = 0; - loop { - match block.body.attestations.get(i) { - Ok(a) => all_attestations.push(a.clone()), - Err(_) => break, - } - i += 1; - } - - // Append proposer attestation - all_attestations.push(self.message.proposer_attestation.clone()); - - // Collect signatures into a Vec - let mut signatures_vec: Vec = Vec::new(); - let mut j: u64 = 0; - loop { - match signatures.get(j) { - Ok(s) => signatures_vec.push(s.clone()), - Err(_) => break, - } - j += 1; - } - - // Verify signature count matches attestation count - // - // Each attestation must have exactly one corresponding signature. - // - // The ordering must be preserved: - // 1. Block body attestations, - // 2. The proposer attestation. - assert_eq!( - signatures_vec.len(), - all_attestations.len(), - "Number of signatures does not match number of attestations" - ); - - let validators = &parent_state.validators; - let num_validators = validators.len_u64(); - - // Verify each attestation signature - for (attestation, signature) in all_attestations.iter().zip(signatures_vec.iter()) { - // Ensure validator exists in the active set - assert!( - attestation.validator_id.0 < num_validators, - "Validator index out of range" - ); - - let validator = validators - .get(attestation.validator_id.0) - .expect("validator must exist"); - - // Verify the XMSS signature - // - // This cryptographically proves that: - // - The validator possesses the secret key for their public key - // - The attestation has not been tampered with - // - The signature was created at the correct epoch (slot) - - let message_bytes: [u8; 32] = hash_tree_root(attestation).0.into(); - - assert!( - verify_xmss_signature( - validator.pubkey.0.as_bytes(), - attestation.data.slot, - &message_bytes, - &signature, - ), - "Attestation signature verification failed" - ); - } - - true - } - - #[cfg(feature = "devnet2")] + /// Verifies all attestation signatures using lean-multisig aggregated proofs. + /// Each attestation has a single `MultisigAggregatedSignature` proof that covers + /// all participating validators. pub fn verify_signatures(&self, parent_state: State) -> bool { // Unpack the signed block components let block = &self.message.block; @@ -228,14 +135,14 @@ impl SignedBlockWithAttestation { assert_eq!( aggregated_attestations.len_u64(), attestation_signatures.len_u64(), - "Number of signatures does not match number of attestations" + "Attestation signature groups must align with block body attestations" ); let validators = &parent_state.validators; let num_validators = validators.len_u64(); - // Verify each attestation signature - for (aggregated_attestation, aggregated_signature) in (&aggregated_attestations) + // Verify each aggregated attestation's zkVM proof + for (aggregated_attestation, _aggregated_signature_proof) in (&aggregated_attestations) .into_iter() .zip((&attestation_signatures).into_iter()) { @@ -243,67 +150,57 @@ impl SignedBlockWithAttestation { .aggregation_bits .to_validator_indices(); - assert_eq!( - aggregated_signature.len_u64(), - validator_ids.len() as u64, - "Aggregated attestation signature count mismatch" - ); - - let attestation_root = aggregated_attestation.data.hash_tree_root(); - - // Loop through zipped validator IDs and their corresponding signatures - // Verify each individual signature within the aggregated attestation - for (validator_id, signature) in - validator_ids.iter().zip(aggregated_signature.into_iter()) - { - // Ensure validator exists in the active set + // Ensure all validators exist in the active set + for validator_id in &validator_ids { assert!( *validator_id < num_validators, "Validator index out of range" ); - - let validator = validators.get(*validator_id).expect("validator must exist"); - - // Get the actual payload root for the attestation data - let attestation_root: [u8; 32] = - hash_tree_root(&aggregated_attestation.data).0.into(); - - // Verify the XMSS signature - assert!( - verify_xmss_signature( - validator.pubkey.0.as_bytes(), - aggregated_attestation.data.slot, - &attestation_root, - signature, - ), - "Attestation signature verification failed" - ); } - // Verify the proposer attestation signature - let proposer_attestation = self.message.proposer_attestation.clone(); - let proposer_signature = signatures.proposer_signature; + // let attestation_data_root: [u8; 32] = + // hash_tree_root(&aggregated_attestation.data).0.into(); - assert!( - proposer_attestation.validator_id.0 < num_validators, - "Proposer index out of range" - ); + // Verify the lean-multisig aggregated proof for this attestation + // + // The proof verifies that all validators in aggregation_bits signed + // the same attestation_data_root at the given epoch (slot). + // TODO + // aggregated_signature_proof + // .verify_aggregated_payload( + // &validator_ids + // .iter() + // .map(|vid| validators.get(*vid).expect("validator must exist")) + // .collect::>(), + // &attestation_data_root, + // aggregated_attestation.data.slot.0, + // ) + // .expect("Attestation aggregated signature verification failed"); + } - let proposer = validators - .get(proposer_attestation.validator_id.0) - .expect("proposer must exist"); + // Verify the proposer attestation signature (outside the attestation loop) + let proposer_attestation = &self.message.proposer_attestation; + let proposer_signature = &signatures.proposer_signature; - let proposer_root: [u8; 32] = hash_tree_root(&proposer_attestation).0.into(); - assert!( - verify_xmss_signature( - proposer.pubkey.0.as_bytes(), - proposer_attestation.data.slot, - &proposer_root, - &proposer_signature, - ), - "Proposer attestation signature verification failed" - ); - } + assert!( + proposer_attestation.validator_id.0 < num_validators, + "Proposer index out of range" + ); + + let proposer = validators + .get(proposer_attestation.validator_id.0) + .expect("proposer must exist"); + + let proposer_root: [u8; 32] = hash_tree_root(&proposer_attestation.data).0.into(); + assert!( + verify_xmss_signature( + proposer.pubkey, + proposer_attestation.data.slot, + &proposer_root, + proposer_signature, + ), + "Proposer attestation signature verification failed" + ); true } @@ -311,34 +208,22 @@ impl SignedBlockWithAttestation { #[cfg(feature = "xmss-verify")] pub fn verify_xmss_signature( - pubkey_bytes: &[u8], + public_key: crate::public_key::PublicKey, slot: Slot, message_bytes: &[u8; 32], signature: &Signature, ) -> bool { - use leansig::serialization::Serializable; - use leansig::signature::SignatureScheme; - let epoch = slot.0 as u32; + let signature = crate::signature::Signature::from(signature.as_bytes()); - type PubKey = ::PublicKey; - let pubkey = match PubKey::from_bytes(pubkey_bytes) { - Ok(pk) => pk, - Err(_) => return false, - }; - - type Sig = ::Signature; - let sig = match Sig::from_bytes(signature.as_bytes()) { - Ok(s) => s, - Err(_) => return false, - }; - - SIGTargetSumLifetime20W2NoOff::verify(&pubkey, epoch, message_bytes, &sig) + signature + .verify(&public_key, epoch, message_bytes) + .unwrap_or_else(|_| false) } #[cfg(not(feature = "xmss-verify"))] pub fn verify_xmss_signature( - _pubkey_bytes: &[u8], + _public_key: crate::public_key::PublicKey, _slot: Slot, _message_bytes: &[u8; 32], _signature: &Signature, diff --git a/lean_client/containers/src/lib.rs b/lean_client/containers/src/lib.rs index f0590ca..0125a08 100644 --- a/lean_client/containers/src/lib.rs +++ b/lean_client/containers/src/lib.rs @@ -2,7 +2,9 @@ pub mod attestation; pub mod block; pub mod checkpoint; pub mod config; +pub mod public_key; pub mod serde_helpers; +pub mod signature; pub mod slot; pub mod state; pub mod status; @@ -11,8 +13,10 @@ pub mod validator; pub use attestation::{ AggregatedAttestation, AggregatedSignatures, AggregationBits, Attestation, AttestationData, - Attestations, Signature, SignedAggregatedAttestation, SignedAttestation, + Attestations, Signature, SignatureKey, SignedAggregatedAttestation, SignedAttestation, }; + +pub use attestation::{AggregatedSignatureProof, MultisigAggregatedSignature}; pub use block::{ Block, BlockBody, BlockHeader, BlockWithAttestation, SignedBlock, SignedBlockWithAttestation, }; diff --git a/lean_client/containers/src/public_key.rs b/lean_client/containers/src/public_key.rs new file mode 100644 index 0000000..114b17c --- /dev/null +++ b/lean_client/containers/src/public_key.rs @@ -0,0 +1,207 @@ +use alloy_primitives::{hex::{self, ToHexExt}}; +use anyhow::{anyhow}; +use leansig::{serialization::Serializable, signature::SignatureScheme}; +use leansig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8; +use serde::{Deserialize, Deserializer, Serialize}; +use ssz::{SszSize, SszRead, SszWrite, SszHash, Size, WriteError, ReadError, H256}; + +const PUBLIC_KEY_SIZE: usize = 52; +pub type LeanSigPublicKey = + ::PublicKey; + +// This is a wrapper class for storing public keys, implementation based on Ream client +#[derive(Debug, PartialEq, Clone, Eq, Hash, Copy)] +pub struct PublicKey { + pub inner: [u8; PUBLIC_KEY_SIZE], +} + +impl From<&[u8]> for PublicKey { + fn from(value: &[u8]) -> Self { + // Handle potential length panics or ensure slice is correct size + let mut inner = [0u8; PUBLIC_KEY_SIZE]; + let len = value.len().min(PUBLIC_KEY_SIZE); + inner[..len].copy_from_slice(&value[..len]); + Self { inner } + } +} + +impl Default for PublicKey { + fn default() -> Self { + Self { + inner: [0u8; PUBLIC_KEY_SIZE], + } + } +} + +impl SszSize for PublicKey { + const SIZE: Size = Size::Fixed { + size: PUBLIC_KEY_SIZE, + }; +} + +// 2. Define how to write (Serialize) +impl SszWrite for PublicKey { + fn write_fixed(&self, _bytes: &mut [u8]) { + panic!("SszWrite::write_fixed must be implemented for fixed-size types"); + } + + fn write_variable(&self, _bytes: &mut Vec) -> Result<(), WriteError> { + panic!("SszWrite::write_variable must be implemented for variable-size types"); + } + + fn to_ssz(&self) -> Result, WriteError> { + match Self::SIZE { + Size::Fixed { size } => { + let mut bytes = vec![0; size]; + self.write_fixed(bytes.as_mut_slice()); + Ok(bytes) + } + Size::Variable { minimum_size } => { + let mut bytes = Vec::with_capacity(minimum_size); + self.write_variable(&mut bytes)?; + Ok(bytes) + } + } + } +} + +impl SszRead for PublicKey { + fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { + // For a fixed-size struct, we must ensure we have exactly + // the number of bytes required by our SszSize implementation. + if bytes.len() != PUBLIC_KEY_SIZE { + return Err(ReadError::FixedSizeMismatch { + expected: PUBLIC_KEY_SIZE, + actual: bytes.len(), + }); + } + + let mut inner = [0u8; PUBLIC_KEY_SIZE]; + inner.copy_from_slice(bytes); + + Ok(Self { inner }) + } + fn from_ssz(context: &C, bytes: impl AsRef<[u8]>) -> Result { + let bytes_ref = bytes.as_ref(); + + // SSZ fixed-size validation + if bytes_ref.len() != PUBLIC_KEY_SIZE { + return Err(ReadError::FixedSizeMismatch { + expected: PUBLIC_KEY_SIZE, + actual: bytes_ref.len(), + }); + } + + Self::from_ssz_unchecked(context, bytes_ref) + } +} + +impl SszHash for PublicKey { + type PackingFactor = typenum::U1; + + fn hash_tree_root(&self) -> H256 { + // Simple implementation: hash the inner bytes directly + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(&self.inner); + let result = hasher.finalize(); + H256::from_slice(&result) + } +} + +impl PublicKey { + pub fn new(inner: [u8; PUBLIC_KEY_SIZE]) -> Self { + Self { inner } + } + + pub fn from_lean_sig(public_key: LeanSigPublicKey) -> Result { + let bytes = public_key.to_bytes(); + // Ensure we fit into 52 bytes + if bytes.len() != PUBLIC_KEY_SIZE { + return Err(anyhow!( + "LeanSigPublicKey length mismatch: expected 52, got {}", + bytes.len() + )); + } + let mut inner = [0u8; PUBLIC_KEY_SIZE]; + inner.copy_from_slice(&bytes); + Ok(Self { inner }) + } + + pub fn as_lean_sig(&self) -> anyhow::Result { + LeanSigPublicKey::from_bytes(&self.inner) + .map_err(|err| anyhow!("Failed to decode LeanSigPublicKey from SSZ: {err:?}")) + } + + pub fn from_hex>(s: S) -> anyhow::Result { + let s = s.as_ref(); + + // Allow optional 0x prefix + let s = s.strip_prefix("0x").unwrap_or(s); + + let bytes = hex::decode(s).map_err(|e| anyhow!("Invalid hex public key: {e}"))?; + + if bytes.len() != 52 { + return Err(anyhow!( + "PublicKey hex length mismatch: expected 52 bytes, got {}", + bytes.len() + )); + } + + // Validate structure via LeanSig + let lean_pk = LeanSigPublicKey::from_bytes(&bytes) + .map_err(|e| anyhow!("Invalid XMSS public key encoding: {e:?}"))?; + + Self::from_lean_sig(lean_pk) + } + + pub fn debug_roundtrip(&self) -> anyhow::Result<()> { + let pk = self.as_lean_sig()?; + let re = pk.to_bytes(); + + anyhow::ensure!( + re.as_slice() == self.inner.as_slice(), + "PublicKey roundtrip mismatch: decoded->encoded bytes differ" + ); + + Ok(()) + } + + pub fn fingerprint_hex(&self) -> String { + use alloy_primitives::hex::ToHexExt; + let take = self.inner.len().min(12); + format!("0x{}", ToHexExt::encode_hex(&self.inner[..take].iter())) + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!( + "0x{}", + self.as_lean_sig() + .map_err(serde::ser::Error::custom)? + .to_bytes() + .encode_hex() + )) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let result: String = Deserialize::deserialize(deserializer)?; + let result = hex::decode(&result).map_err(serde::de::Error::custom)?; + + Self::from_lean_sig( + LeanSigPublicKey::from_bytes(&result) + .map_err(|err| anyhow!("Convert to error, with error trait implemented {err:?}")) + .map_err(serde::de::Error::custom)?, + ) + .map_err(serde::de::Error::custom) + } +} diff --git a/lean_client/containers/src/serde_helpers.rs b/lean_client/containers/src/serde_helpers.rs index 01604e5..3f3aa86 100644 --- a/lean_client/containers/src/serde_helpers.rs +++ b/lean_client/containers/src/serde_helpers.rs @@ -120,7 +120,7 @@ pub mod signature { siblings: DataWrapper>>>, } - pub fn deserialize_single<'de, D>(deserializer: D) -> Result + pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -130,7 +130,7 @@ pub mod signature { let value = Value::deserialize(deserializer)?; // Check if it's a hex string (normal format) - if let Value::String(hex_str) = &value { + if let Value::String(hex_str) = value { let hex_str = hex_str.trim_start_matches("0x"); let bytes = hex::decode(hex_str) .map_err(|e| D::Error::custom(format!("Invalid hex string: {}", e)))?; @@ -140,36 +140,96 @@ pub mod signature { } // Otherwise, parse as structured XMSS signature - let xmss_sig: XmssSignature = serde_json::from_value(value) + let xmss_sig: XmssSignature = serde_json::from_value(value.clone()) .map_err(|e| D::Error::custom(format!("Failed to parse XMSS signature: {}", e)))?; - // Serialize the XMSS signature to bytes - // Format: siblings (variable length) + rho (28 bytes) + hashes (variable length) - let mut bytes = Vec::new(); + println!( + "Parsed XMSS Signature | siblings: {:?}", + xmss_sig.path.siblings.data.len() + ); + println!("Parsed XMSS Signature | rho: {:?}", xmss_sig.rho.data.len()); + println!( + "Parsed XMSS Signature | hashes: {:?}", + xmss_sig.hashes.data.len() + ); + + // --- STEP 1: PREPARE DATA BUFFERS --- + + // 1. Serialize Rho (Fixed length) + // RAND_LEN_FE = 7, assuming u32 elements -> 28 bytes + let mut rho_bytes = Vec::new(); + for val in &xmss_sig.rho.data { + rho_bytes.extend_from_slice(&val.to_le_bytes()); + } + let rho_len = rho_bytes.len(); // Should be 28 (7 * 4) - // Write siblings + // 2. Serialize Path/Siblings (Variable length) + let mut path_bytes = Vec::new(); + // Prepend 4 bytes (containing 4) as an offset which would come with real SSZ serialization + let inner_offset: u32 = 4; + path_bytes.extend_from_slice(&inner_offset.to_le_bytes()); // [04 00 00 00] for sibling in &xmss_sig.path.siblings.data { for val in &sibling.data { - bytes.extend_from_slice(&val.to_le_bytes()); + path_bytes.extend_from_slice(&val.to_le_bytes()); } } - // Write rho (7 u32s = 28 bytes) - for val in &xmss_sig.rho.data { - bytes.extend_from_slice(&val.to_le_bytes()); - } - - // Write hashes + // 3. Serialize Hashes (Variable length) + let mut hashes_bytes = Vec::new(); for hash in &xmss_sig.hashes.data { for val in &hash.data { - bytes.extend_from_slice(&val.to_le_bytes()); + hashes_bytes.extend_from_slice(&val.to_le_bytes()); } } - // Pad or truncate to 3112 bytes - bytes.resize(3112, 0); + // --- STEP 2: CALCULATE OFFSETS --- + + // The fixed part contains: + // 1. Path Offset (4 bytes) + // 2. Rho Data (rho_len bytes) + // 3. Hashes Offset (4 bytes) + let fixed_part_size = 4 + rho_len + 4; + + // Offset to 'path' starts immediately after the fixed part + let offset_path = fixed_part_size as u32; + + // Offset to 'hashes' starts after 'path' data + let offset_hashes = offset_path + (path_bytes.len() as u32); + + // --- STEP 3: CONSTRUCT FINAL SSZ BYTES --- + + // Print all offsets and lengths for debugging + println!( + "SSZ Offsets | offset_path: {} | offset_hashes: {}", + offset_path, offset_hashes + ); + println!( + "SSZ Lengths | rho_len: {} | path_len: {} | hashes_len: {}", + rho_len, + path_bytes.len(), + hashes_bytes.len() + ); - Signature::try_from(bytes.as_slice()) + let mut ssz_bytes = Vec::new(); + + // 1. Write Offset to Path (u32, Little Endian) + ssz_bytes.extend_from_slice(&offset_path.to_le_bytes()); + + // 2. Write Rho Data (Fixed) + ssz_bytes.extend_from_slice(&rho_bytes); + + // 3. Write Offset to Hashes (u32, Little Endian) + ssz_bytes.extend_from_slice(&offset_hashes.to_le_bytes()); + + // 4. Write Path Data (Variable) + ssz_bytes.extend_from_slice(&path_bytes); + + // 5. Write Hashes Data (Variable) + ssz_bytes.extend_from_slice(&hashes_bytes); + + println!("Total SSZ Bytes Length: {}", ssz_bytes.len()); + + Signature::try_from(ssz_bytes.as_slice()) .map_err(|_| D::Error::custom("Failed to create signature")) } @@ -183,139 +243,142 @@ pub mod signature { } } -/// Custom deserializer for BlockSignatures that handles the {"data": [sig, ...]} format +/// Custom deserializer for AttestationSignatures that handles the {"data": [sig, ...]} format /// where each signature can be either hex string or structured XMSS format -pub mod block_signatures { +pub mod attestation_signatures { use super::*; - use crate::block::BlockSignatures; - use crate::Signature; - use serde_json::Value; + use crate::attestation::AttestationSignatures; + use crate::AggregatedSignatureProof; + use serde::de::Error; use ssz::PersistentList; use typenum::U4096; + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let outer: DataWrapper> = + DataWrapper::deserialize(deserializer)?; - /// Structured XMSS signature format from test vectors - #[derive(Deserialize, Clone)] - struct XmssSignature { - path: XmssPath, - rho: DataWrapper>, - hashes: DataWrapper>>>, - } - - #[derive(Deserialize, Clone)] - struct XmssPath { - siblings: DataWrapper>>>, - } - - fn parse_single_signature(value: &Value) -> Result { - // Check if it's a hex string (normal format) - if let Value::String(hex_str) = value { - let hex_str = hex_str.trim_start_matches("0x"); - let bytes = hex::decode(hex_str).map_err(|e| format!("Invalid hex string: {}", e))?; - - return Signature::try_from(bytes.as_slice()) - .map_err(|_| "Invalid signature length".to_string()); - } - - // Otherwise, parse as structured XMSS signature - let xmss_sig: XmssSignature = serde_json::from_value(value.clone()) - .map_err(|e| format!("Failed to parse XMSS signature: {}", e))?; - - // Serialize the XMSS signature to bytes - // Format: siblings (variable length) + rho (28 bytes) + hashes (variable length) - let mut bytes = Vec::new(); - - // Write siblings - for sibling in &xmss_sig.path.siblings.data { - for val in &sibling.data { - bytes.extend_from_slice(&val.to_le_bytes()); - } - } - - // Write rho (7 u32s = 28 bytes) - for val in &xmss_sig.rho.data { - bytes.extend_from_slice(&val.to_le_bytes()); - } + let mut out: PersistentList = PersistentList::default(); - // Write hashes - for hash in &xmss_sig.hashes.data { - for val in &hash.data { - bytes.extend_from_slice(&val.to_le_bytes()); - } + for aggregated_proof in outer.data.into_iter() { + out.push(aggregated_proof).map_err(|e| { + D::Error::custom(format!( + "AttestationSignatures push aggregated entry failed: {e:?}" + )) + })?; } - // Pad or truncate to 3112 bytes - bytes.resize(3112, 0); + Ok(out) + } - Signature::try_from(bytes.as_slice()).map_err(|_| "Failed to create signature".to_string()) + pub fn serialize(_value: &AttestationSignatures, _serializer: S) -> Result + where + S: Serializer, + { + // let mut inner: Vec = Vec::new(); + // + // // inner.push(format!("0x{}", hex::encode(sig.as_bytes()))); + // for sig in value.into_iter() { + // inner.push(format!("0x{}", hex::encode(sig.as_bytes()))); + // } + // + // DataWrapper { data: inner }.serialize(serializer) + // TODO: implement serialization + Err(serde::ser::Error::custom( + "AttestationSignatures serialization not implemented for devnet2", + )) } +} - #[cfg(feature = "devnet1")] - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result, D::Error> +/// Serde helper for ssz::ByteList - serializes as hex string +pub mod byte_list { + use super::*; + use ssz::ByteList; + use typenum::Unsigned; + + pub fn deserialize<'de, D, N>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, + N: Unsigned, { use serde::de::Error; - // Parse the {"data": [...]} wrapper - let wrapper: DataWrapper> = DataWrapper::deserialize(deserializer)?; + println!("Deserializing ByteList..."); - let mut signatures = PersistentList::default(); + // First, try to parse as a JSON value to inspect the structure + // let value = Value::deserialize(deserializer)?; + let wrapper = DataWrapper::::deserialize(deserializer)?; - for (idx, sig_value) in wrapper.data.into_iter().enumerate() { - let sig = parse_single_signature(&sig_value) - .map_err(|e| D::Error::custom(format!("Signature {}: {}", idx, e)))?; - signatures - .push(sig) - .map_err(|e| D::Error::custom(format!("Signature {} push failed: {:?}", idx, e)))?; - } + println!("Wrapper data length: {}", wrapper.data.len()); + + // Check if it's a hex string (normal format) + match wrapper.data { + hex_str => { + let hex_str = hex_str.trim_start_matches("0x"); + + if hex_str.is_empty() { + return Ok(ByteList::default()); + } - Ok(signatures) + let bytes = hex::decode(hex_str) + .map_err(|e| D::Error::custom(format!("Invalid hex string: {}", e)))?; + + println!("Decoded ByteList bytes length: {}", bytes.len()); + + return ByteList::try_from(bytes) + .map_err(|_| D::Error::custom("ByteList exceeds maximum length")); + } + } } - #[cfg(feature = "devnet2")] - pub fn deserialize<'de, D>(_: D) -> Result + pub fn serialize(value: &ByteList, serializer: S) -> Result where - D: Deserializer<'de>, + S: Serializer, + N: Unsigned, { - Err(serde::de::Error::custom( - "BlockSignatures deserialization not implemented for devnet2", - )) + let hex_str = format!("0x{}", hex::encode(value.as_bytes())); + hex_str.serialize(serializer) } +} - #[cfg(feature = "devnet1")] - pub fn serialize( - value: &PersistentList, - serializer: S, - ) -> Result +/// Custom deserializer for AggregatedAttestations that handles the {"data": [sig, ...]} format +/// where each signature can be either hex string or structured XMSS format +pub mod aggregated_attestations { + use super::*; + use crate::attestation::AggregatedAttestations; + use crate::AggregatedAttestation; + use serde::de::Error; + use ssz::PersistentList; + use typenum::U4096; + + pub fn deserialize<'de, D>(deserializer: D) -> Result where - S: Serializer, + D: Deserializer<'de>, { - // Collect all signatures as hex strings - let mut sigs: Vec = Vec::new(); - let mut i = 0u64; - loop { - match value.get(i) { - Ok(sig) => { - sigs.push(format!("0x{}", hex::encode(sig.as_bytes()))); - i += 1; - } - Err(_) => break, - } + let outer: DataWrapper> = + DataWrapper::deserialize(deserializer)?; + + let mut out: PersistentList = PersistentList::default(); + + for aggregated_attestations in outer.data.into_iter() { + out.push(aggregated_attestations).map_err(|e| { + D::Error::custom(format!( + "AggregatedAttestations push aggregated entry failed: {e:?}" + )) + })?; } - let wrapper = DataWrapper { data: sigs }; - wrapper.serialize(serializer) + Ok(out) } - #[cfg(feature = "devnet2")] - pub fn serialize(_value: &BlockSignatures, _serializer: S) -> Result + pub fn serialize(_value: &AggregatedAttestations, _serializer: S) -> Result where S: Serializer, { + // TODO: implement serialization Err(serde::ser::Error::custom( - "BlockSignatures serialization not implemented for devnet2", + "AttestationSignatures serialization not implemented for devnet2", )) } } diff --git a/lean_client/containers/src/signature.rs b/lean_client/containers/src/signature.rs new file mode 100644 index 0000000..ab39873 --- /dev/null +++ b/lean_client/containers/src/signature.rs @@ -0,0 +1,121 @@ +use alloy_primitives::hex::ToHexExt; +use anyhow::anyhow; +use leansig::{MESSAGE_LENGTH, serialization::Serializable, signature::SignatureScheme}; +use leansig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8; +use serde::{Deserialize, Deserializer, Serialize}; +use crate::public_key::{PublicKey}; + +const SIGNATURE_SIZE: usize = 3112; + +type LeanSigSignature = ::Signature; + +/// Wrapper around a fixed-size serialized hash-based signature. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Signature { + pub inner: [u8; SIGNATURE_SIZE], +} + +impl From<&[u8]> for Signature { + fn from(value: &[u8]) -> Self { + // Handle potential length panics or ensure slice is correct size + let mut inner = [0u8; SIGNATURE_SIZE]; + let len = value.len().min(SIGNATURE_SIZE); + inner[..len].copy_from_slice(&value[..len]); + Self { inner } + } +} + +impl Signature { + pub fn new(inner: [u8; SIGNATURE_SIZE]) -> Self { + Self { inner } + } + + pub fn from_lean_sig(signature: LeanSigSignature) -> Result { + let bytes = signature.to_bytes(); + // Ensure we fit into 3112 bytes + if bytes.len() != 3112 { + return Err(anyhow!( + "LeanSigSignature length mismatch: expected 3112, got {}", + bytes.len() + )); + } + let mut inner = [0u8; SIGNATURE_SIZE]; + inner.copy_from_slice(&bytes); + Ok(Self { inner }) + } + + pub fn as_lean_sig(&self) -> anyhow::Result { + println!("Converting Signature to LeanSigSignature..."); + LeanSigSignature::from_bytes(&self.inner) + .map_err(|err| anyhow!("Failed to decode LeanSigSignature from SSZ: {err:?}")) + } + + pub fn verify( + &self, + public_key: &PublicKey, + epoch: u32, + message: &[u8; MESSAGE_LENGTH], + ) -> anyhow::Result { + Ok( + ::verify( + &public_key.as_lean_sig()?, + epoch, + message, + &self.as_lean_sig()?, + ), + ) + } + + /// Debug helper: decode using leansig, then re-encode and ensure bytes match. + pub fn debug_roundtrip(&self) -> anyhow::Result<()> { + let sig = self.as_lean_sig()?; + let re = sig.to_bytes(); + + anyhow::ensure!( + re.as_slice() == self.inner.as_slice(), + "Signature roundtrip mismatch: decoded->encoded bytes differ" + ); + + Ok(()) + } + + /// Debug helper: short stable fingerprint for logs. + pub fn fingerprint_hex(&self) -> String { + use alloy_primitives::hex::ToHexExt; + let bytes = self.inner.as_slice(); + let take = bytes.len().min(12); + format!("0x{}", ToHexExt::encode_hex(&bytes[..take].iter())) + } +} + +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!( + "0x{}", + self.as_lean_sig() + .map_err(serde::ser::Error::custom)? + .to_bytes() + .encode_hex() + )) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let result: String = Deserialize::deserialize(deserializer)?; + let result = alloy_primitives::hex::decode(&result).map_err(serde::de::Error::custom)?; + + Self::from_lean_sig( + LeanSigSignature::from_bytes(&result) + .map_err(|err| anyhow!("Convert to error, with error trait implemented {err:?}")) + .map_err(serde::de::Error::custom)?, + ) + .map_err(serde::de::Error::custom) + } +} diff --git a/lean_client/containers/src/state.rs b/lean_client/containers/src/state.rs index fa13011..5bb8a9e 100644 --- a/lean_client/containers/src/state.rs +++ b/lean_client/containers/src/state.rs @@ -1,19 +1,16 @@ -use crate::attestation::AggregatedAttestations; -use crate::block::BlockSignatures; +use crate::attestation::{AggregatedAttestation, AggregatedAttestations}; use crate::validator::Validator; use crate::{ block::{hash_tree_root, Block, BlockBody, BlockHeader, SignedBlockWithAttestation}, - Attestation, Attestations, Bytes32, Checkpoint, Config, Signature, SignedAttestation, Slot, - Uint64, ValidatorIndex, + Attestation, Bytes32, Checkpoint, Config, Signature, Slot, Uint64, ValidatorIndex, }; use crate::{ HistoricalBlockHashes, JustificationRoots, JustificationsValidators, JustifiedSlots, Validators, }; use serde::{Deserialize, Serialize}; -use ssz::{PersistentList as List, PersistentList}; +use ssz::PersistentList as List; use ssz_derive::Ssz; use std::collections::BTreeMap; -use typenum::U4096; pub const VALIDATOR_REGISTRY_LIMIT: usize = 1 << 12; // 4096 pub const JUSTIFICATION_ROOTS_LIMIT: usize = 1 << 18; // 262144 @@ -111,7 +108,7 @@ impl State { let mut validators = List::default(); for i in 0..num_validators.0 { let validator = Validator { - pubkey: crate::validator::BlsPublicKey::default(), + pubkey: crate::public_key::PublicKey::default(), index: Uint64(i), }; validators.push(validator).expect("Failed to add validator"); @@ -149,9 +146,21 @@ impl State { (self.slot.0 % num_validators) == (index.0 % num_validators) } + /// Get the number of validators (since PersistentList doesn't have len()) + pub fn validator_count(&self) -> usize { + let mut count: u64 = 0; + loop { + match self.validators.get(count) { + Ok(_) => count += 1, + Err(_) => break, + } + } + count as usize + } + pub fn get_justifications(&self) -> BTreeMap> { // Use actual validator count, matching leanSpec - let num_validators = self.validators.len_usize(); + let num_validators = self.validator_count(); (&self.justifications_roots) .into_iter() .enumerate() @@ -174,7 +183,7 @@ impl State { pub fn with_justifications(mut self, map: BTreeMap>) -> Self { // Use actual validator count, matching leanSpec - let num_validators = self.validators.len_usize(); + let num_validators = self.validator_count(); let mut roots: Vec<_> = map.keys().cloned().collect(); roots.sort(); @@ -288,26 +297,12 @@ impl State { pub fn process_block(&self, block: &Block) -> Result { let state = self.process_block_header(block)?; - #[cfg(feature = "devnet1")] - let state_after_ops = state.process_attestations(&block.body.attestations); - #[cfg(feature = "devnet2")] - let state_after_ops = { - let mut unaggregated_attestations = Attestations::default(); - for aggregated_attestation in &block.body.attestations { - let plain_attestations = aggregated_attestation.to_plain(); - // For each attestatio in the vector, push to the list - for attestation in plain_attestations { - unaggregated_attestations - .push(attestation) - .map_err(|e| format!("Failed to push attestation: {:?}", e))?; - } - } - state.process_attestations(&unaggregated_attestations) - }; - // State root validation is handled by state_transition_with_validation when needed + if AggregatedAttestation::has_duplicate_data(&block.body.attestations) { + return Err("Block contains duplicate AttestationData".to_string()); + } - Ok(state_after_ops) + Ok(state.process_attestations(&block.body.attestations)) } pub fn process_block_header(&self, block: &Block) -> Result { @@ -397,143 +392,150 @@ impl State { }) } - pub fn process_attestations(&self, attestations: &Attestations) -> Self { + pub fn process_attestations(&self, attestations: &AggregatedAttestations) -> Self { let mut justifications = self.get_justifications(); let mut latest_justified = self.latest_justified.clone(); let mut latest_finalized = self.latest_finalized.clone(); - // Store initial finalized slot for justifiability checks (per leanSpec) let initial_finalized_slot = self.latest_finalized.slot; let justified_slots = self.justified_slots.clone(); - // PersistentList doesn't expose iter; convert to Vec for simple iteration for now - // Build a temporary Vec by probing sequentially until index error - let mut votes_vec: Vec = Vec::new(); - let mut i: u64 = 0; - loop { - match attestations.get(i) { - Ok(v) => votes_vec.push(v.clone()), - Err(_) => break, - } - i += 1; - } - - // Create mutable working BitList for justified_slots tracking let mut justified_slots_working = Vec::new(); for i in 0..justified_slots.len() { justified_slots_working.push(justified_slots.get(i).map(|b| *b).unwrap_or(false)); } - for attestation in votes_vec.iter() { - let vote = attestation.data.clone(); - let target_slot = vote.target.slot; - let source_slot = vote.source.slot; - let target_root = vote.target.root; - let source_root = vote.source.root; - - let target_slot_int = target_slot.0 as usize; - let source_slot_int = source_slot.0 as usize; - - let source_is_justified = justified_slots_working - .get(source_slot_int) - .copied() - .unwrap_or(false); - let target_already_justified = justified_slots_working - .get(target_slot_int) - .copied() - .unwrap_or(false); - - let source_root_matches_history = self - .historical_block_hashes - .get(source_slot_int as u64) - .map(|root| *root == source_root) - .unwrap_or(false); - - let target_root_matches_history = self - .historical_block_hashes - .get(target_slot_int as u64) - .map(|root| *root == target_root) - .unwrap_or(false); - - let target_is_after_source = target_slot > source_slot; - // Use initial_finalized_slot per leanSpec (not the mutating local copy) - let target_is_justifiable = target_slot.is_justifiable_after(initial_finalized_slot); - - // leanSpec logic: skip if BOTH source and target roots don't match history - // i.e., continue if EITHER matches - let roots_valid = source_root_matches_history || target_root_matches_history; - - let is_valid_vote = source_is_justified - && !target_already_justified - && roots_valid - && target_is_after_source - && target_is_justifiable; - - if !is_valid_vote { - continue; - } + for aggregated_attestation in attestations { + let validator_ids = aggregated_attestation + .aggregation_bits + .to_validator_indices(); + self.process_single_attestation( + &aggregated_attestation.data, + &validator_ids, + &mut justifications, + &mut latest_justified, + &mut latest_finalized, + &mut justified_slots_working, + initial_finalized_slot, + ); + } - if !justifications.contains_key(&target_root) { - // Use actual validator count, not VALIDATOR_REGISTRY_LIMIT - // This matches leanSpec: justifications[target.root] = [Boolean(False)] * self.validators.count - let num_validators = self.validators.len_usize(); - justifications.insert(target_root, vec![false; num_validators]); - } + self.finalize_attestation_processing( + justifications, + latest_justified, + latest_finalized, + justified_slots_working, + ) + } - let validator_id = attestation.validator_id.0 as usize; + /// Process a single attestation's votes. + fn process_single_attestation( + &self, + vote: &crate::attestation::AttestationData, + validator_ids: &[u64], + justifications: &mut BTreeMap>, + latest_justified: &mut Checkpoint, + latest_finalized: &mut Checkpoint, + justified_slots_working: &mut Vec, + initial_finalized_slot: Slot, + ) { + let target_slot = vote.target.slot; + let source_slot = vote.source.slot; + let target_root = vote.target.root; + let source_root = vote.source.root; + + let target_slot_int = target_slot.0 as usize; + let source_slot_int = source_slot.0 as usize; + + let source_is_justified = justified_slots_working + .get(source_slot_int) + .copied() + .unwrap_or(false); + let target_already_justified = justified_slots_working + .get(target_slot_int) + .copied() + .unwrap_or(false); + + let source_root_matches = self + .historical_block_hashes + .get(source_slot_int as u64) + .map(|r| *r == source_root) + .unwrap_or(false); + let target_root_matches = self + .historical_block_hashes + .get(target_slot_int as u64) + .map(|r| *r == target_root) + .unwrap_or(false); + + let is_valid_vote = source_is_justified + && !target_already_justified + && (source_root_matches || target_root_matches) + && target_slot > source_slot + && target_slot.is_justifiable_after(initial_finalized_slot); + + if !is_valid_vote { + return; + } + + if !justifications.contains_key(&target_root) { + justifications.insert(target_root, vec![false; self.validator_count()]); + } + + for &validator_id in validator_ids { + let vid = validator_id as usize; if let Some(votes) = justifications.get_mut(&target_root) { - if validator_id < votes.len() && !votes[validator_id] { - votes[validator_id] = true; - - let num_validators = self.validators.len_u64(); - - let count = votes.iter().filter(|&&v| v).count(); - if 3 * count >= 2 * num_validators as usize { - latest_justified = vote.target; - - // Extend justified_slots_working if needed - while justified_slots_working.len() <= target_slot_int { - justified_slots_working.push(false); - } - justified_slots_working[target_slot_int] = true; - - justifications.remove(&target_root); - - let mut is_finalizable = true; - for s in (source_slot_int + 1)..target_slot_int { - // Use initial_finalized_slot per leanSpec - if Slot(s as u64).is_justifiable_after(initial_finalized_slot) { - is_finalizable = false; - break; - } - } - - if is_finalizable { - latest_finalized = vote.source; - } - } + if vid < votes.len() && !votes[vid] { + votes[vid] = true; } } } - let mut new_state = self.clone().with_justifications(justifications); + if let Some(votes) = justifications.get(&target_root) { + let num_validators = self.validators.len_u64() as usize; + let count = votes.iter().filter(|&&v| v).count(); + if 3 * count >= 2 * num_validators { + *latest_justified = vote.target.clone(); + + justified_slots_working.extend(std::iter::repeat_n( + false, + (target_slot_int + 1).saturating_sub(justified_slots_working.len()), + )); + justified_slots_working[target_slot_int] = true; + + justifications.remove(&target_root); + let is_finalizable = (source_slot_int + 1..target_slot_int) + .all(|s| !Slot(s as u64).is_justifiable_after(initial_finalized_slot)); + + if is_finalizable { + *latest_finalized = vote.source.clone(); + } + } + } + } + + fn finalize_attestation_processing( + &self, + justifications: BTreeMap>, + latest_justified: Checkpoint, + latest_finalized: Checkpoint, + justified_slots_working: Vec, + ) -> Self { + let mut new_state = self.clone().with_justifications(justifications); new_state.latest_justified = latest_justified; new_state.latest_finalized = latest_finalized; - // Convert justified_slots_working Vec back to BitList let mut new_justified_slots = JustifiedSlots::with_length(justified_slots_working.len()); for (i, &val) in justified_slots_working.iter().enumerate() { new_justified_slots.set(i, val); } new_state.justified_slots = new_justified_slots; - new_state } /// Build a valid block on top of this state. /// /// Computes the post-state and creates a block with the correct state root. - /// If `available_signed_attestations` and `known_block_roots` are provided, + /// If `available_attestations` and `known_block_roots` are provided, /// performs fixed-point attestation collection: iteratively adds valid /// attestations until no more can be included. This is necessary because /// processing attestations may update the justified checkpoint, which may @@ -545,48 +547,50 @@ impl State { /// * `proposer_index` - Validator index of the proposer /// * `parent_root` - Root of the parent block (must match state after slot processing) /// * `initial_attestations` - Initial attestations to include - /// * `available_signed_attestations` - Optional pool of attestations to collect from + /// * `available_attestations` - Optional pool of attestations to collect from /// * `known_block_roots` - Optional set of known block roots for attestation validation + /// * `gossip_signatures` - Optional map of individual signatures from gossip + /// * `aggregated_payloads` - Optional map of aggregated signature proofs /// /// # Returns /// - /// Tuple of (Block, post-State, collected attestations, signatures) - #[cfg(feature = "devnet1")] + /// Tuple of (Block, post-State, collected aggregated attestations, aggregated proofs) pub fn build_block( &self, slot: Slot, proposer_index: ValidatorIndex, parent_root: Bytes32, initial_attestations: Option>, - available_signed_attestations: Option<&[SignedBlockWithAttestation]>, + available_attestations: Option>, known_block_roots: Option<&std::collections::HashSet>, + gossip_signatures: Option<&std::collections::HashMap>, + aggregated_payloads: Option< + &std::collections::HashMap>, + >, ) -> Result< ( Block, Self, - Vec, - PersistentList, + Vec, + Vec, ), String, > { - // Initialize empty attestation set for iterative collection + use crate::attestation::{AggregatedAttestation, SignatureKey}; + + // Initialize attestation set let mut attestations = initial_attestations.unwrap_or_default(); - let mut signatures = PersistentList::default(); // Advance state to target slot - // Note: parent_root comes from fork choice and is already validated. - // We cannot validate it against the header hash here because process_slots() - // caches the state root in the header, changing its hash. let pre_state = self.process_slots(slot)?; - // Iteratively collect valid attestations using fixed-point algorithm - // - // Continue until no new attestations can be added to the block. - // This ensures we include the maximal valid attestation set. + // Fixed-point attestation collection loop + // Iteratively add valid attestations until no new ones can be added loop { // Create candidate block with current attestation set - let mut attestations_list = Attestations::default(); - for att in &attestations { + let aggregated = AggregatedAttestation::aggregate_by_data(&attestations); + let mut attestations_list = AggregatedAttestations::default(); + for att in &aggregated { attestations_list .push(att.clone()) .map_err(|e| format!("Failed to push attestation: {:?}", e))?; @@ -605,316 +609,262 @@ impl State { // Apply state transition to get the post-block state let post_state = pre_state.process_block(&candidate_block)?; - // No attestation source provided: done after computing post_state - if available_signed_attestations.is_none() || known_block_roots.is_none() { - // Store the post state root in the block - let final_block = Block { - slot, - proposer_index, - parent_root, - state_root: hash_tree_root(&post_state), - body: candidate_block.body, - }; - return Ok((final_block, post_state, attestations, signatures)); - } + // If no available attestations pool, skip fixed-point iteration + let available = match &available_attestations { + Some(avail) => avail, + None => { + // No fixed-point: compute signatures and return + let (aggregated_attestations, aggregated_proofs) = self + .compute_aggregated_signatures( + &attestations, + gossip_signatures, + aggregated_payloads, + )?; + + let mut final_attestations_list = AggregatedAttestations::default(); + for att in &aggregated_attestations { + final_attestations_list + .push(att.clone()) + .map_err(|e| format!("Failed to push attestation: {:?}", e))?; + } - // Find new valid attestations matching post-state justification - let mut new_attestations = Vec::new(); - let mut new_signatures = Vec::new(); + let final_block = Block { + slot, + proposer_index, + parent_root, + state_root: hash_tree_root(&post_state), + body: BlockBody { + attestations: final_attestations_list, + }, + }; + + return Ok(( + final_block, + post_state, + aggregated_attestations, + aggregated_proofs, + )); + } + }; - let available = available_signed_attestations.unwrap(); - let known_roots = known_block_roots.unwrap(); + // Find new valid attestations from available pool + let mut new_attestations: Vec = Vec::new(); + let current_data_roots: std::collections::HashSet<_> = attestations + .iter() + .map(|a| a.data.data_root_bytes()) + .collect(); - for signed_attestation in available { - let att = &signed_attestation.message.proposer_attestation; - let data = &att.data; + for attestation in available { + // Skip if already included + if current_data_roots.contains(&attestation.data.data_root_bytes()) { + continue; + } - // Skip if target block is unknown - if !known_roots.contains(&data.head.root) { + // Validate attestation against post-state + // Source must match post-state's justified checkpoint + if attestation.data.source != post_state.latest_justified { continue; } - // Skip if attestation source does not match post-state's latest justified - if data.source != post_state.latest_justified { + // Target must be after source + if attestation.data.target.slot <= attestation.data.source.slot { continue; } - // Add attestation if not already included - if !attestations.contains(att) { - new_attestations.push(att.clone()); - // Add corresponding signatures from the signed block - // Note: In the actual implementation, you'd need to properly track - // which signatures correspond to which attestations - let mut idx = 0u64; - loop { - match signed_attestation.signature.get(idx) { - Ok(sig) => { - new_signatures.push(sig.clone()); - idx += 1; - } - Err(_) => break, - } + // Target block must be known (if known_block_roots provided) + if let Some(known_roots) = known_block_roots { + if !known_roots.contains(&attestation.data.target.root) { + continue; } } + + // Check if we have a signature for this attestation + let data_root = attestation.data.data_root_bytes(); + let sig_key = SignatureKey::new(attestation.validator_id.0, data_root); + let has_gossip_sig = + gossip_signatures.map_or(false, |gs| gs.contains_key(&sig_key)); + let has_block_proof = + aggregated_payloads.map_or(false, |ap| ap.contains_key(&sig_key)); + + if has_gossip_sig || has_block_proof { + new_attestations.push(attestation.clone()); + } } // Fixed point reached: no new attestations found if new_attestations.is_empty() { - // Store the post state root in the block + // Compute aggregated signatures + let (aggregated_attestations, aggregated_proofs) = self + .compute_aggregated_signatures( + &attestations, + gossip_signatures, + aggregated_payloads, + )?; + + let mut final_attestations_list = AggregatedAttestations::default(); + for att in &aggregated_attestations { + final_attestations_list + .push(att.clone()) + .map_err(|e| format!("Failed to push attestation: {:?}", e))?; + } + let final_block = Block { slot, proposer_index, parent_root, state_root: hash_tree_root(&post_state), - body: candidate_block.body, + body: BlockBody { + attestations: final_attestations_list, + }, }; - return Ok((final_block, post_state, attestations, signatures)); + + return Ok(( + final_block, + post_state, + aggregated_attestations, + aggregated_proofs, + )); } // Add new attestations and continue iteration attestations.extend(new_attestations); - for sig in new_signatures { - signatures - .push(sig) - .map_err(|e| format!("Failed to push signature: {:?}", e))?; - } } } - #[cfg(feature = "devnet2")] - pub fn build_block( + pub fn compute_aggregated_signatures( &self, - _slot: Slot, - _proposer_index: ValidatorIndex, - _parent_root: Bytes32, - _initial_attestations: Option>, - _available_signed_attestations: Option<&[SignedAttestation]>, - _known_block_roots: Option<&std::collections::HashSet>, - ) -> Result<(Block, Self, Vec, BlockSignatures), String> { - Err("build_block is not implemented for devnet2".to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn proposer_round_robin() { - let st = State::generate_genesis(Uint64(0), Uint64(4)); - assert!(State { - config: st.config.clone(), - ..st.clone() - } - .is_proposer(ValidatorIndex(0))); - } - - #[test] - fn slot_justifiability_rules() { - use crate::slot::Slot; - assert!(Slot(1).is_justifiable_after(Slot(0))); - assert!(Slot(9).is_justifiable_after(Slot(0))); // perfect square - assert!(Slot(6).is_justifiable_after(Slot(0))); // pronic (2*3) - } - - #[test] - fn test_hash_tree_root() { - let body = BlockBody { - attestations: List::default(), - }; - let block = Block { - slot: Slot(1), - proposer_index: ValidatorIndex(0), - parent_root: Bytes32(ssz::H256::zero()), - state_root: Bytes32(ssz::H256::zero()), - body, - }; - - let root = hash_tree_root(&block); - assert_ne!(root, Bytes32(ssz::H256::zero())); - } - - #[test] - fn test_process_slots() { - let genesis_state = State::generate_genesis(Uint64(0), Uint64(10)); - let target_slot = Slot(5); - - let new_state = genesis_state.process_slots(target_slot).unwrap(); - - assert_eq!(new_state.slot, target_slot); - let genesis_state_for_hash = genesis_state.clone(); //this is sooooo bad - assert_eq!( - new_state.latest_block_header.state_root, - hash_tree_root(&genesis_state_for_hash) - ); - } + attestations: &[Attestation], + gossip_signatures: Option<&std::collections::HashMap>, + aggregated_payloads: Option< + &std::collections::HashMap>, + >, + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + use crate::attestation::{AggregatedAttestation, AggregationBits, SignatureKey}; + use std::collections::HashSet; + + let mut results: Vec<(AggregatedAttestation, crate::AggregatedSignatureProof)> = Vec::new(); + + // Group individual attestations by data + for aggregated in AggregatedAttestation::aggregate_by_data(attestations) { + let data = &aggregated.data; + let data_root = data.data_root_bytes(); + let validator_ids = aggregated.aggregation_bits.to_validator_indices(); + + // Phase 1: Gossip Collection + // Try to collect individual signatures from gossip network + let mut gossip_ids: Vec = Vec::new(); + let mut _gossip_sigs_collected: Vec = Vec::new(); + let mut remaining: HashSet = HashSet::new(); + + if let Some(gossip_sigs) = gossip_signatures { + for vid in &validator_ids { + let key = SignatureKey::new(*vid, data_root); + if let Some(sig) = gossip_sigs.get(&key) { + gossip_ids.push(*vid); + _gossip_sigs_collected.push(sig.clone()); + } else { + remaining.insert(*vid); + } + } + } else { + // No gossip data: all validators need fallback + remaining = validator_ids.iter().copied().collect(); + } - #[test] - #[cfg(feature = "devnet1")] - fn test_build_block() { - // Create genesis state with validators - let genesis_state = State::generate_genesis(Uint64(0), Uint64(4)); - - // Compute expected parent root after slot processing - let pre_state = genesis_state.process_slots(Slot(1)).unwrap(); - let expected_parent_root = hash_tree_root(&pre_state.latest_block_header); - - // Test 1: Build a simple block without attestations - let result = genesis_state.build_block( - Slot(1), - ValidatorIndex(1), - expected_parent_root, - None, - None, - None, - ); + // If we collected any gossip signatures, create an aggregated proof + // NOTE: This matches Python leanSpec behavior (test_mode=True). + // Python also uses test_mode=True with TODO: "Remove test_mode once leanVM + // supports correct signature encoding." + // Once lean-multisig is fully integrated, this will call: + // MultisigAggregatedSignature::aggregate(public_keys, signatures, message, epoch) + if !gossip_ids.is_empty() { + let participants = AggregationBits::from_validator_indices(&gossip_ids); + + // Create proof placeholder (matches Python test_mode behavior) + // TODO: Call actual aggregation when lean-multisig supports proper encoding + let proof_data = crate::MultisigAggregatedSignature::new(Vec::new()) + .expect("Empty proof should always be valid"); + let proof = crate::AggregatedSignatureProof::new(participants.clone(), proof_data); + + results.push(( + AggregatedAttestation { + aggregation_bits: participants, + data: data.clone(), + }, + proof, + )); + } - assert!(result.is_ok(), "Building simple block should succeed"); - let (block, post_state, attestations, signatures) = result.unwrap(); - - // Verify block properties - assert_eq!(block.slot, Slot(1)); - assert_eq!(block.proposer_index, ValidatorIndex(1)); - assert_eq!(block.parent_root, expected_parent_root); - assert_ne!( - block.state_root, - Bytes32(ssz::H256::zero()), - "State root should be computed" - ); + // Phase 2: Fallback to block proofs using greedy set-cover + // Goal: Cover remaining validators with minimum number of proofs + while !remaining.is_empty() { + let payloads = match aggregated_payloads { + Some(p) => p, + None => break, + }; - // Verify attestations and signatures are empty - assert_eq!(attestations.len(), 0); - // Check signatures by trying to get first element - assert!(signatures.get(0).is_err(), "Signatures should be empty"); - - // Verify post-state has advanced - assert_eq!(post_state.slot, Slot(1)); - // Note: The post-state's latest_block_header.state_root is zero because it will be - // filled in during the next slot processing - assert_eq!( - block.parent_root, expected_parent_root, - "Parent root should match" - ); + // Pick any remaining validator to find candidate proofs + let target_id = *remaining.iter().next().unwrap(); + let key = SignatureKey::new(target_id, data_root); - // Test 2: Build block with initial attestations - let attestation = Attestation { - validator_id: Uint64(0), - data: crate::AttestationData { - slot: Slot(1), - head: Checkpoint { - root: expected_parent_root, - slot: Slot(0), - }, - target: Checkpoint { - root: expected_parent_root, - slot: Slot(1), - }, - source: Checkpoint { - root: expected_parent_root, - slot: Slot(0), - }, - }, - }; + let candidates = match payloads.get(&key) { + Some(proofs) if !proofs.is_empty() => proofs, + _ => break, // No proofs found for this validator + }; - let result = genesis_state.build_block( - Slot(1), - ValidatorIndex(1), - expected_parent_root, - Some(vec![attestation.clone()]), - None, - None, - ); + // Greedy selection: find proof covering most remaining validators + // For each candidate proof, compute intersection with remaining validators + let (best_proof, covered_set) = candidates + .iter() + .map(|proof| { + let proof_validators: HashSet = + proof.get_participant_indices().into_iter().collect(); + let intersection: HashSet = + remaining.intersection(&proof_validators).copied().collect(); + (proof, intersection) + }) + .max_by_key(|(_, intersection)| intersection.len()) + .expect("candidates is non-empty"); - assert!( - result.is_ok(), - "Building block with attestations should succeed" - ); - let (block, _post_state, attestations, _signatures) = result.unwrap(); - - // Verify attestation was included - assert_eq!(attestations.len(), 1); - assert_eq!(attestations[0].validator_id, Uint64(0)); - // Check that attestation list has one element - assert!( - block.body.attestations.get(0).is_ok(), - "Block should contain attestation" - ); - assert!( - block.body.attestations.get(1).is_err(), - "Block should have only one attestation" - ); - } + // Guard: If best proof has zero overlap, stop + if covered_set.is_empty() { + break; + } - #[test] - #[cfg(feature = "devnet1")] - fn test_build_block_advances_state() { - // Create genesis state - let genesis_state = State::generate_genesis(Uint64(0), Uint64(10)); - - // Compute parent root after advancing to target slot - let pre_state = genesis_state.process_slots(Slot(5)).unwrap(); - let parent_root = hash_tree_root(&pre_state.latest_block_header); - - // Build block at slot 5 - // Proposer for slot 5 with 10 validators is (5 % 10) = 5 - let result = - genesis_state.build_block(Slot(5), ValidatorIndex(5), parent_root, None, None, None); - - assert!(result.is_ok()); - let (block, post_state, _, _) = result.unwrap(); - - // Verify state advanced through slots - assert_eq!(post_state.slot, Slot(5)); - assert_eq!(block.slot, Slot(5)); - - // Verify block can be applied to genesis state - let transition_result = genesis_state.state_transition_with_validation( - SignedBlockWithAttestation { - message: crate::BlockWithAttestation { - block: block.clone(), - proposer_attestation: Attestation::default(), - }, - signature: PersistentList::default(), - }, - true, // signatures are considered valid (not validating, just marking as valid) - true, - ); + // Record proof with its actual participants (from the proof itself) + let covered_validators: Vec = best_proof.get_participant_indices(); + let participants = AggregationBits::from_validator_indices(&covered_validators); + + results.push(( + AggregatedAttestation { + aggregation_bits: participants, + data: data.clone(), + }, + best_proof.clone(), + )); + + // Remove covered validators from remaining + for vid in &covered_set { + remaining.remove(vid); + } + } + } - assert!( - transition_result.is_ok(), - "Built block should be valid for state transition" - ); - } + // Handle empty case + if results.is_empty() { + return Ok((Vec::new(), Vec::new())); + } - #[test] - #[cfg(feature = "devnet1")] - fn test_build_block_state_root_matches() { - // Create genesis state - let genesis_state = State::generate_genesis(Uint64(0), Uint64(3)); - - // Compute parent root after advancing to target slot - let pre_state = genesis_state.process_slots(Slot(1)).unwrap(); - let parent_root = hash_tree_root(&pre_state.latest_block_header); - - // Build a block - // Proposer for slot 1 with 3 validators is (1 % 3) = 1 - let result = - genesis_state.build_block(Slot(1), ValidatorIndex(1), parent_root, None, None, None); - - assert!(result.is_ok()); - let (block, post_state, _, _) = result.unwrap(); - - // Verify the state root in block matches the computed post-state - let computed_state_root = hash_tree_root(&post_state); - assert_eq!( - block.state_root, computed_state_root, - "Block state root should match computed post-state root" - ); + // Unzip results into parallel lists + let (aggregated_attestations, aggregated_proofs): (Vec<_>, Vec<_>) = + results.into_iter().unzip(); - // Verify it's not zero - assert_ne!( - block.state_root, - Bytes32(ssz::H256::zero()), - "State root should not be zero" - ); + Ok((aggregated_attestations, aggregated_proofs)) } } diff --git a/lean_client/containers/src/validator.rs b/lean_client/containers/src/validator.rs index 2649f55..a6580da 100644 --- a/lean_client/containers/src/validator.rs +++ b/lean_client/containers/src/validator.rs @@ -1,81 +1,10 @@ -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use ssz::ByteVector; +use serde::{Deserialize, Serialize}; use ssz_derive::Ssz; -use typenum::U52; - -/// BLS public key - 52 bytes (as defined in lean spec) -#[derive(Clone, Debug, PartialEq, Eq, Ssz)] -#[ssz(transparent)] -pub struct BlsPublicKey(pub ByteVector); - -impl Default for BlsPublicKey { - fn default() -> Self { - BlsPublicKey(ByteVector::default()) - } -} - -// Custom serde implementation -impl Serialize for BlsPublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // ByteVector might have to_vec() or similar - // For now, use unsafe to access the underlying bytes - let bytes = unsafe { - std::slice::from_raw_parts(&self.0 as *const ByteVector as *const u8, 52) - }; - let hex_string = format!("0x{}", hex::encode(bytes)); - serializer.serialize_str(&hex_string) - } -} - -impl<'de> Deserialize<'de> for BlsPublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let s = s.strip_prefix("0x").unwrap_or(&s); - - let decoded = hex::decode(s).map_err(serde::de::Error::custom)?; - if decoded.len() != 52 { - return Err(serde::de::Error::custom(format!( - "Expected 52 bytes, got {}", - decoded.len() - ))); - } - - // Create ByteVector from decoded bytes using unsafe - let mut byte_vec = ByteVector::default(); - unsafe { - let dest = &mut byte_vec as *mut ByteVector as *mut u8; - std::ptr::copy_nonoverlapping(decoded.as_ptr(), dest, 52); - } - - Ok(BlsPublicKey(byte_vec)) - } -} - -impl BlsPublicKey { - pub fn from_hex(s: &str) -> Result { - let s = s.strip_prefix("0x").unwrap_or(s); - let decoded = hex::decode(s).map_err(|e| e.to_string())?; - if decoded.len() != 52 { - return Err(format!("Expected 52 bytes, got {}", decoded.len())); - } - let mut byte_vec = ByteVector::default(); - unsafe { - let dest = &mut byte_vec as *mut ByteVector as *mut u8; - std::ptr::copy_nonoverlapping(decoded.as_ptr(), dest, 52); - } - Ok(BlsPublicKey(byte_vec)) - } -} #[derive(Clone, Debug, PartialEq, Eq, Default, Ssz, Serialize, Deserialize)] pub struct Validator { - pub pubkey: BlsPublicKey, + // This now uses new XMSS PublicKey struct + pub pubkey: crate::public_key::PublicKey, #[serde(default)] pub index: crate::Uint64, } diff --git a/lean_client/containers/tests/test_vectors/block_processing.rs b/lean_client/containers/tests/test_vectors/block_processing.rs index 5bbc997..e3325cb 100644 --- a/lean_client/containers/tests/test_vectors/block_processing.rs +++ b/lean_client/containers/tests/test_vectors/block_processing.rs @@ -1,6 +1,14 @@ -// Integration test: All block processing test vectors +// Integration test: All block processing test vectors for devnet2 format +// +// NOTE: These tests are currently disabled because the JSON test vector files +// use a wrapper format (e.g., "validators": {"data": [...]}) that doesn't match +// the Rust deserializer expectations (which expects a direct list). +// The test vectors need to be regenerated by leanSpec to match the expected format, +// or the deserialization logic needs to be updated. + use super::runner::TestRunner; +/* #[test] #[cfg(feature = "devnet1")] fn test_process_first_block_after_genesis() { @@ -79,3 +87,4 @@ fn test_block_with_invalid_state_root() { TestRunner::run_block_processing_test(test_path) .expect("test_block_with_invalid_state_root failed"); } +*/ diff --git a/lean_client/containers/tests/test_vectors/mod.rs b/lean_client/containers/tests/test_vectors/mod.rs index acdc055..5d26d0c 100644 --- a/lean_client/containers/tests/test_vectors/mod.rs +++ b/lean_client/containers/tests/test_vectors/mod.rs @@ -1,3 +1,7 @@ +//! Test vector modules for devnet2 format +//! +//! Contains test runners and test cases for block processing, genesis, and signature verification. + // Test vector modules pub mod block_processing; pub mod genesis; diff --git a/lean_client/containers/tests/test_vectors/runner.rs b/lean_client/containers/tests/test_vectors/runner.rs index f6942fe..7459538 100644 --- a/lean_client/containers/tests/test_vectors/runner.rs +++ b/lean_client/containers/tests/test_vectors/runner.rs @@ -244,7 +244,17 @@ impl TestRunner { println!("\nProcessing single empty block at slot {:?}", block.slot); // Verify it's an empty block (no attestations) - let attestation_count = block.body.attestations.len_u64(); + let attestation_count = { + let mut count = 0u64; + loop { + match block.body.attestations.get(count) { + Ok(_) => count += 1, + Err(_) => break, + } + } + count + }; + if attestation_count > 0 { return Err(format!( "Expected empty block, but found {} attestations", @@ -388,7 +398,17 @@ impl TestRunner { return Err(format!("Block {} parent_root mismatch", idx + 1).into()); } - let attestation_count = block.body.attestations.len_u64(); + // Check if block is empty (no attestations) + let attestation_count = { + let mut count = 0u64; + loop { + match block.body.attestations.get(count) { + Ok(_) => count += 1, + Err(_) => break, + } + } + count + }; // Process the full block (header + operations) let result = state_after_slots.process_block(block); @@ -596,9 +616,11 @@ impl TestRunner { Ok(()) } - /// Test runner for verify_signatures test vectors - /// Tests XMSS signature verification on SignedBlockWithAttestation - #[cfg(feature = "devnet1")] + // Test runner for verify_signatures test vectors + // Tests XMSS signature verification on SignedBlockWithAttestation + // + // NOTE: Disabled until test vector files are regenerated for devnet2 BlockSignatures format. + // The current JSON test vectors use signature.data array instead of attestation_signatures + proposer_signature. pub fn run_verify_signatures_test>( path: P, ) -> Result<(), Box> { @@ -633,9 +655,6 @@ impl TestRunner { signed_block.message.proposer_attestation.validator_id.0 ); - let signature_count = signed_block.signature.len_u64(); - println!(" Signatures: {}", signature_count); - // Check if we expect this test to fail if let Some(ref exception) = test_case.expect_exception { println!(" Expecting exception: {}", exception); diff --git a/lean_client/containers/tests/unit_tests/attestation_aggregation.rs b/lean_client/containers/tests/unit_tests/attestation_aggregation.rs index 72d48b4..5d1e4dc 100644 --- a/lean_client/containers/tests/unit_tests/attestation_aggregation.rs +++ b/lean_client/containers/tests/unit_tests/attestation_aggregation.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "devnet2")] #[cfg(test)] mod tests { use containers::attestation::{ diff --git a/lean_client/containers/tests/unit_tests/common.rs b/lean_client/containers/tests/unit_tests/common.rs index 1a648b8..2981a2c 100644 --- a/lean_client/containers/tests/unit_tests/common.rs +++ b/lean_client/containers/tests/unit_tests/common.rs @@ -1,3 +1,8 @@ +//! Common test utilities for devnet2 format +//! +//! Helper functions for creating test blocks, states, and attestations +//! using the devnet2 data structures. + use containers::block::BlockSignatures; use containers::{ block::{hash_tree_root, Block, BlockBody, BlockHeader}, @@ -23,11 +28,6 @@ pub fn create_block( parent_header: &mut BlockHeader, attestations: Option, ) -> SignedBlockWithAttestation { - #[cfg(feature = "devnet1")] - let body = BlockBody { - attestations: attestations.unwrap_or_else(PersistentList::default), - }; - #[cfg(feature = "devnet2")] let body = BlockBody { attestations: { let attestations_vec = attestations.unwrap_or_default(); @@ -39,9 +39,6 @@ pub fn create_block( let aggregated: Vec = AggregatedAttestation::aggregate_by_data(&attestations_vec); - let aggregated: Vec = - AggregatedAttestation::aggregate_by_data(&attestations_vec); - // Create a new empty PersistentList let mut persistent_list: PersistentList = PersistentList::default(); @@ -55,7 +52,6 @@ pub fn create_block( persistent_list }, - // other BlockBody fields... }; let block_message = Block { @@ -66,16 +62,6 @@ pub fn create_block( body: body, }; - #[cfg(feature = "devnet1")] - let return_value = SignedBlockWithAttestation { - message: BlockWithAttestation { - block: block_message, - proposer_attestation: Attestation::default(), - }, - signature: PersistentList::default(), - }; - - #[cfg(feature = "devnet2")] let return_value = SignedBlockWithAttestation { message: BlockWithAttestation { block: block_message, diff --git a/lean_client/containers/tests/unit_tests/mod.rs b/lean_client/containers/tests/unit_tests/mod.rs index 1bef390..315792d 100644 --- a/lean_client/containers/tests/unit_tests/mod.rs +++ b/lean_client/containers/tests/unit_tests/mod.rs @@ -1,7 +1,12 @@ // tests/unit_tests/mod.rs + +// Modules that work with both devnet1 and devnet2 mod attestation_aggregation; -mod common; mod state_basic; mod state_justifications; + +// TODO: Update these modules for devnet2 data structures +// (SignedAttestation now uses AttestationData directly, BlockSignatures changed, etc.) +mod common; mod state_process; mod state_transition; diff --git a/lean_client/containers/tests/unit_tests/state_basic.rs b/lean_client/containers/tests/unit_tests/state_basic.rs index 085384a..5fa59f3 100644 --- a/lean_client/containers/tests/unit_tests/state_basic.rs +++ b/lean_client/containers/tests/unit_tests/state_basic.rs @@ -1,3 +1,7 @@ +//! State basic tests for devnet2 format +//! +//! Tests for genesis generation, proposer selection, slot rules, and hash tree root. + // tests/state_basic.rs use containers::{ block::{hash_tree_root, BlockBody}, diff --git a/lean_client/containers/tests/unit_tests/state_justifications.rs b/lean_client/containers/tests/unit_tests/state_justifications.rs index afdd220..61bf09c 100644 --- a/lean_client/containers/tests/unit_tests/state_justifications.rs +++ b/lean_client/containers/tests/unit_tests/state_justifications.rs @@ -1,3 +1,7 @@ +//! State justifications tests for devnet2 format +//! +//! Tests for justification get/set operations and roundtrip verification. + // tests/state_justifications.rs use containers::{state::State, types::Bytes32, Config}; use pretty_assertions::assert_eq; diff --git a/lean_client/containers/tests/unit_tests/state_process.rs b/lean_client/containers/tests/unit_tests/state_process.rs index 5df98cf..632b0b5 100644 --- a/lean_client/containers/tests/unit_tests/state_process.rs +++ b/lean_client/containers/tests/unit_tests/state_process.rs @@ -128,72 +128,3 @@ fn test_process_block_header_invalid( let err_msg = result.unwrap_err(); assert!(err_msg.contains(expected_error)); } - -// This test verifies that attestations correctly justify and finalize slots -#[cfg(feature = "devnet1")] -#[test] -fn test_process_attestations_justification_and_finalization() { - let mut state = genesis_state(); - - // Process slot 1 and block - let mut state_at_slot_1 = state.process_slots(Slot(1)).unwrap(); - let block1 = create_block(1, &mut state_at_slot_1.latest_block_header, None); - // Use process_block_header and process_operations separately to avoid state root validation - let state_after_header1 = state_at_slot_1 - .process_block_header(&block1.message.block) - .unwrap(); - state = state_after_header1.process_attestations(&block1.message.block.body.attestations); - - // Process slot 4 and block - let mut state_at_slot_4 = state.process_slots(Slot(4)).unwrap(); - let block4 = create_block(4, &mut state_at_slot_4.latest_block_header, None); - let state_after_header4 = state_at_slot_4 - .process_block_header(&block4.message.block) - .unwrap(); - state = state_after_header4.process_attestations(&block4.message.block.body.attestations); - - // Advance to slot 5 - state = state.process_slots(Slot(5)).unwrap(); - - let genesis_checkpoint = Checkpoint { - root: *state.historical_block_hashes.get(0).unwrap(), - slot: Slot(0), - }; - - let checkpoint4 = Checkpoint { - root: hash_tree_root(&state.latest_block_header), - slot: Slot(4), - }; - - let attestations_for_4: Vec = (0..7) - .map(|i| Attestation { - validator_id: Uint64(i), - data: AttestationData { - slot: Slot(4), - head: checkpoint4.clone(), - target: checkpoint4.clone(), - source: genesis_checkpoint.clone(), - }, - }) - .collect(); - - // Convert Vec to PersistentList - let mut attestations_list: List<_, U4096> = List::default(); - for a in attestations_for_4 { - attestations_list.push(a).unwrap(); - } - - let new_state = state.process_attestations(&attestations_list); - - assert_eq!(new_state.latest_justified, checkpoint4); - let justified_slot_4 = new_state - .justified_slots - .get(4) - .map(|b| *b) - .unwrap_or(false); - assert_eq!(justified_slot_4, true); - assert_eq!(new_state.latest_finalized, genesis_checkpoint); - assert!(!new_state - .get_justifications() - .contains_key(&checkpoint4.root)); -} diff --git a/lean_client/containers/tests/unit_tests/state_transition.rs b/lean_client/containers/tests/unit_tests/state_transition.rs index 7725210..4b763c6 100644 --- a/lean_client/containers/tests/unit_tests/state_transition.rs +++ b/lean_client/containers/tests/unit_tests/state_transition.rs @@ -1,9 +1,16 @@ +//! State transition tests +//! +//! Tests for full state transitions including signature validation +//! and state root verification. + // tests/state_transition.rs use containers::{ - block::{hash_tree_root, Block, BlockWithAttestation, SignedBlockWithAttestation}, + block::{ + hash_tree_root, Block, BlockSignatures, BlockWithAttestation, SignedBlockWithAttestation, + }, state::State, types::{Bytes32, Uint64}, - Attestation, Attestations, Slot, + Attestation, Signature, Slot, }; use pretty_assertions::assert_eq; use rstest::fixture; @@ -31,7 +38,6 @@ fn test_state_transition_full() { // Use process_block_header + process_operations to avoid state root validation during setup let state_after_header = state_at_slot_1.process_block_header(&block).unwrap(); - #[cfg(feature = "devnet1")] let expected_state = state_after_header.process_attestations(&block.body.attestations); #[cfg(feature = "devnet2")] @@ -79,7 +85,6 @@ fn test_state_transition_invalid_signatures() { // Use process_block_header + process_operations to avoid state root validation during setup let state_after_header = state_at_slot_1.process_block_header(&block).unwrap(); - #[cfg(feature = "devnet1")] let expected_state = state_after_header.process_attestations(&block.body.attestations); #[cfg(feature = "devnet2")] @@ -113,7 +118,7 @@ fn test_state_transition_invalid_signatures() { assert_eq!(result.unwrap_err(), "Block signatures must be valid"); } -#[cfg(feature = "devnet1")] +// Test with bad state root using devnet2 BlockSignatures structure #[test] fn test_state_transition_bad_state_root() { let state = genesis_state(); @@ -130,7 +135,10 @@ fn test_state_transition_bad_state_root() { block, proposer_attestation: Attestation::default(), }, - signature: PersistentList::default(), + signature: BlockSignatures { + attestation_signatures: PersistentList::default(), + proposer_signature: Signature::default(), + }, }; let result = state.state_transition(final_signed_block_with_attestation, true); @@ -138,7 +146,6 @@ fn test_state_transition_bad_state_root() { assert_eq!(result.unwrap_err(), "Invalid block state root"); } -#[cfg(feature = "devnet2")] #[test] fn test_state_transition_devnet2() { let state = genesis_state(); @@ -152,22 +159,8 @@ fn test_state_transition_devnet2() { // Process the block header and attestations let state_after_header = state_at_slot_1.process_block_header(&block).unwrap(); - #[cfg(feature = "devnet1")] let expected_state = state_after_header.process_attestations(&block.body.attestations); - #[cfg(feature = "devnet2")] - let expected_state = { - let mut unaggregated_attestations = Attestations::default(); - for aggregated_attestation in &block.body.attestations { - let plain_attestations = aggregated_attestation.to_plain(); - // For each attestatio in the vector, push to the list - for attestation in plain_attestations { - unaggregated_attestations.push(attestation); - } - } - state_after_header.process_attestations(&unaggregated_attestations) - }; - // Ensure the state root matches the expected state let block_with_correct_root = Block { state_root: hash_tree_root(&expected_state), diff --git a/lean_client/env-config/Cargo.toml b/lean_client/env-config/Cargo.toml index 4b761e5..bd3e737 100644 --- a/lean_client/env-config/Cargo.toml +++ b/lean_client/env-config/Cargo.toml @@ -5,8 +5,4 @@ edition.workspace = true authors.workspace = true license.workspace = true -[features] -devnet1 = [] -devnet2 = [] - [dependencies] diff --git a/lean_client/fork_choice/Cargo.toml b/lean_client/fork_choice/Cargo.toml index b16f561..f707648 100644 --- a/lean_client/fork_choice/Cargo.toml +++ b/lean_client/fork_choice/Cargo.toml @@ -5,17 +5,8 @@ edition = "2021" [features] default = [] -devnet1 = ["containers/devnet1", "env-config/devnet1"] -devnet2 = ["containers/devnet2", "env-config/devnet1"] [dependencies] env-config = { path = "../env-config", default-features = false } containers = { path = "../containers", default-features = false } -ssz = { git = "https://github.com/grandinetech/grandine", package = "ssz", branch = "develop"} -ssz_derive = { git = "https://github.com/grandinetech/grandine", package = "ssz_derive", branch = "develop" } -typenum = "1.17.0" -serde = { version = "1.0", features = ["derive"] } - -[dev-dependencies] -ssz_rs = "0.9" -serde_json = "1.0" +ssz = { workspace = true } diff --git a/lean_client/fork_choice/src/handlers.rs b/lean_client/fork_choice/src/handlers.rs index 9f3837d..3054052 100644 --- a/lean_client/fork_choice/src/handlers.rs +++ b/lean_client/fork_choice/src/handlers.rs @@ -1,4 +1,5 @@ use crate::store::*; +use containers::SignatureKey; use containers::{ attestation::SignedAttestation, block::SignedBlockWithAttestation, Bytes32, ValidatorIndex, }; @@ -25,22 +26,9 @@ pub fn on_attestation( signed_attestation: SignedAttestation, is_from_block: bool, ) -> Result<(), String> { - #[cfg(feature = "devnet1")] - let validator_id = ValidatorIndex(signed_attestation.message.validator_id.0); - #[cfg(feature = "devnet1")] - let attestation_slot = signed_attestation.message.data.slot; - #[cfg(feature = "devnet1")] - let source_slot = signed_attestation.message.data.source.slot; - #[cfg(feature = "devnet1")] - let target_slot = signed_attestation.message.data.target.slot; - - #[cfg(feature = "devnet2")] let validator_id = ValidatorIndex(signed_attestation.validator_id); - #[cfg(feature = "devnet2")] let attestation_slot = signed_attestation.message.slot; - #[cfg(feature = "devnet2")] let source_slot = signed_attestation.message.source.slot; - #[cfg(feature = "devnet2")] let target_slot = signed_attestation.message.target.slot; // Validate attestation is not from future @@ -62,20 +50,6 @@ pub fn on_attestation( if is_from_block { // On-chain attestation processing - immediately becomes "known" - #[cfg(feature = "devnet1")] - if store - .latest_known_attestations - .get(&validator_id) - .map_or(true, |existing| { - existing.message.data.slot < attestation_slot - }) - { - store - .latest_known_attestations - .insert(validator_id, signed_attestation.clone()); - } - - #[cfg(feature = "devnet2")] if store .latest_known_attestations .get(&validator_id) @@ -88,31 +62,20 @@ pub fn on_attestation( // Remove from new attestations if superseded if let Some(existing_new) = store.latest_new_attestations.get(&validator_id) { - #[cfg(feature = "devnet1")] - if existing_new.message.data.slot <= attestation_slot { - store.latest_new_attestations.remove(&validator_id); - } - #[cfg(feature = "devnet2")] if existing_new.message.slot <= attestation_slot { store.latest_new_attestations.remove(&validator_id); } } } else { // Network gossip attestation processing - goes to "new" stage - #[cfg(feature = "devnet1")] - if store - .latest_new_attestations - .get(&validator_id) - .map_or(true, |existing| { - existing.message.data.slot < attestation_slot - }) - { - store - .latest_new_attestations - .insert(validator_id, signed_attestation); - } + // Store signature for later aggregation during block building + let data_root = signed_attestation.message.data_root_bytes(); + let sig_key = SignatureKey::new(signed_attestation.validator_id, data_root); + store + .gossip_signatures + .insert(sig_key, signed_attestation.signature.clone()); - #[cfg(feature = "devnet2")] + // Track attestation for fork choice if store .latest_new_attestations .get(&validator_id) @@ -186,97 +149,74 @@ fn process_block_internal( } // Process block body attestations as on-chain (is_from_block=true) - let attestations = &signed_block.message.block.body.attestations; let signatures = &signed_block.signature; - #[cfg(feature = "devnet1")] - { - for i in 0.. { - match (attestations.get(i), signatures.get(i)) { - (Ok(attestation), Ok(signature)) => { - let signed_attestation = SignedAttestation { - message: attestation.clone(), - signature: signature.clone(), - }; - on_attestation(store, signed_attestation, true)?; + let aggregated_attestations = &signed_block.message.block.body.attestations; + let proposer_attestation = &signed_block.message.proposer_attestation; + + // Store aggregated proofs for future block building + // Each attestation_signature proof is indexed by (validator_id, data_root) for each participating validator + for (att_idx, aggregated_attestation) in aggregated_attestations.into_iter().enumerate() { + let data_root = aggregated_attestation.data.data_root_bytes(); + + // Get the corresponding proof from attestation_signatures + if let Ok(proof_data) = signatures.attestation_signatures.get(att_idx as u64) { + // Store proof for each validator in the aggregation + for (bit_idx, bit) in aggregated_attestation.aggregation_bits.0.iter().enumerate() { + if *bit { + let validator_id = bit_idx as u64; + let sig_key = SignatureKey::new(validator_id, data_root); + store + .aggregated_payloads + .entry(sig_key) + .or_insert_with(Vec::new) + .push(proof_data.clone()); } - _ => break, } } - - // Update head BEFORE processing proposer attestation - update_head(store); - - // Process proposer attestation as gossip (is_from_block=false) - // This ensures it goes to "new" attestations and doesn't immediately affect fork choice - let num_body_attestations = attestations.len_u64(); - - // Get proposer signature or use default if not present (for tests) - use containers::attestation::Signature; - let proposer_signature = signatures - .get(num_body_attestations) - .map(|sig| sig.clone()) - .unwrap_or_else(|_| Signature::default()); - - let proposer_signed_attestation = SignedAttestation { - message: signed_block.message.proposer_attestation.clone(), - signature: proposer_signature, - }; - - // Process proposer attestation as if received via gossip (is_from_block=false) - // This ensures it goes to "new" attestations and doesn't immediately affect fork choice - on_attestation(store, proposer_signed_attestation, false)?; - - Ok(()) } - #[cfg(feature = "devnet2")] - { - let aggregated_attestations = &signed_block.message.block.body.attestations; - let attestation_signatures = &signed_block.signature.attestation_signatures; - let proposer_attestation = &signed_block.message.proposer_attestation; - - for (aggregated_attestation, aggregated_signature) in aggregated_attestations - .into_iter() - .zip(attestation_signatures) - { - let validator_ids: Vec = aggregated_attestation - .aggregation_bits - .0 - .iter() - .enumerate() - .filter(|(_, bit)| **bit) - .map(|(index, _)| index as u64) - .collect(); - - for (validator_id, signature) in validator_ids.into_iter().zip(aggregated_signature) { - on_attestation( - store, - SignedAttestation { - validator_id, - message: aggregated_attestation.data.clone(), - signature: *signature, - }, - true, - )?; - } + // Process each aggregated attestation's validators for fork choice + // Note: Signature verification is done in verify_signatures() before on_block() + for aggregated_attestation in aggregated_attestations.into_iter() { + let validator_ids: Vec = aggregated_attestation + .aggregation_bits + .0 + .iter() + .enumerate() + .filter(|(_, bit)| **bit) + .map(|(index, _)| index as u64) + .collect(); + + // Each validator in the aggregation votes for this attestation data + for validator_id in validator_ids { + on_attestation( + store, + SignedAttestation { + validator_id, + message: aggregated_attestation.data.clone(), + // Use a default signature since verification already happened + signature: containers::Signature::default(), + }, + true, + )?; } + } - // Update head BEFORE processing proposer attestation - update_head(store); + // Update head BEFORE processing proposer attestation + update_head(store); - let proposer_signed_attestation = SignedAttestation { - validator_id: proposer_attestation.validator_id.0, - message: proposer_attestation.data.clone(), - signature: signed_block.signature.proposer_signature, - }; + let proposer_signed_attestation = SignedAttestation { + validator_id: proposer_attestation.validator_id.0, + message: proposer_attestation.data.clone(), + signature: signed_block.signature.proposer_signature, + }; - // Process proposer attestation as if received via gossip (is_from_block=false) - // This ensures it goes to "new" attestations and doesn't immediately affect fork choice - on_attestation(store, proposer_signed_attestation, false)?; + // Process proposer attestation as if received via gossip (is_from_block=false) + // This ensures it goes to "new" attestations and doesn't immediately affect fork choice + on_attestation(store, proposer_signed_attestation, false)?; - Ok(()) - } + Ok(()) } fn process_pending_blocks(store: &mut Store, mut roots: Vec) { diff --git a/lean_client/fork_choice/src/store.rs b/lean_client/fork_choice/src/store.rs index 51b26b6..818b57d 100644 --- a/lean_client/fork_choice/src/store.rs +++ b/lean_client/fork_choice/src/store.rs @@ -2,6 +2,7 @@ use containers::{ attestation::SignedAttestation, block::SignedBlockWithAttestation, checkpoint::Checkpoint, config::Config, state::State, Bytes32, Root, Slot, ValidatorIndex, }; +use containers::{AggregatedSignatureProof, Signature, SignatureKey}; use ssz::SszHash; use std::collections::HashMap; pub type Interval = u64; @@ -22,6 +23,10 @@ pub struct Store { pub latest_known_attestations: HashMap, pub latest_new_attestations: HashMap, pub blocks_queue: HashMap>, + + pub gossip_signatures: HashMap, + + pub aggregated_payloads: HashMap>, } pub fn get_forkchoice_store( @@ -62,6 +67,8 @@ pub fn get_forkchoice_store( latest_known_attestations: HashMap::new(), latest_new_attestations: HashMap::new(), blocks_queue: HashMap::new(), + gossip_signatures: HashMap::new(), + aggregated_payloads: HashMap::new(), } } @@ -84,9 +91,6 @@ pub fn get_fork_choice_head( // stage 1: accumulate weights by walking up from each attestation's head for attestation in latest_attestations.values() { - #[cfg(feature = "devnet1")] - let mut curr = attestation.message.data.head.root; - #[cfg(feature = "devnet2")] let mut curr = attestation.message.head.root; if let Some(block) = store.blocks.get(&curr) { @@ -250,3 +254,89 @@ pub fn get_proposal_head(store: &mut Store, slot: Slot) -> Root { accept_new_attestations(store); store.head } + +/// Produce a block and aggregated signature proofs for the target slot. +/// +/// The proposer returns the block and `MultisigAggregatedSignature` proofs aligned +/// with `block.body.attestations` so it can craft `SignedBlockWithAttestation`. +/// +/// # Algorithm Overview +/// 1. **Get Proposal Head**: Retrieve current chain head as parent +/// 2. **Collect Attestations**: Convert known attestations to plain attestations +/// 3. **Build Block**: Use State.build_block with signature caches +/// 4. **Store Block**: Insert block and post-state into Store +/// +/// # Arguments +/// * `store` - Mutable reference to the fork choice store +/// * `slot` - Target slot number for block production +/// * `validator_index` - Index of validator authorized to propose this block +/// +/// # Returns +/// Tuple of (block root, finalized Block, attestation signature proofs) +pub fn produce_block_with_signatures( + store: &mut Store, + slot: Slot, + validator_index: ValidatorIndex, +) -> Result< + ( + Root, + containers::block::Block, + Vec, + ), + String, +> { + use containers::Attestation; + + // Get parent block head + let head_root = get_proposal_head(store, slot); + let head_state = store + .states + .get(&head_root) + .ok_or_else(|| "Head state not found".to_string())? + .clone(); + + // Validate proposer authorization for this slot + let num_validators = head_state.validators.len_u64(); + let expected_proposer = slot.0 % num_validators; + if validator_index.0 != expected_proposer { + return Err(format!( + "Validator {} is not the proposer for slot {} (expected {})", + validator_index.0, slot.0, expected_proposer + )); + } + + // Convert AttestationData to Attestation objects for build_block + let available_attestations: Vec = store + .latest_known_attestations + .iter() + .map(|(validator_idx, signed_att)| Attestation { + validator_id: containers::Uint64(validator_idx.0), + data: signed_att.message.clone(), + }) + .collect(); + + // Get known block roots for attestation validation + let known_block_roots: std::collections::HashSet = + store.blocks.keys().copied().collect(); + + // Build block with fixed-point attestation collection and signature aggregation + let (final_block, final_post_state, _aggregated_attestations, signatures) = head_state + .build_block( + slot, + validator_index, + head_root, + None, // initial_attestations - start with empty, let fixed-point collect + Some(available_attestations), + Some(&known_block_roots), + Some(&store.gossip_signatures), + Some(&store.aggregated_payloads), + )?; + + // Compute block root + let block_root = Bytes32(final_block.hash_tree_root()); + + // Store block and state + store.states.insert(block_root, final_post_state); + + Ok((block_root, final_block, signatures)) +} diff --git a/lean_client/fork_choice/tests/fork_choice_test_vectors.rs b/lean_client/fork_choice/tests/fork_choice_test_vectors.rs index 85683af..6ac6438 100644 --- a/lean_client/fork_choice/tests/fork_choice_test_vectors.rs +++ b/lean_client/fork_choice/tests/fork_choice_test_vectors.rs @@ -1,870 +1,392 @@ -use fork_choice::{ - handlers::{on_attestation, on_block, on_tick}, - store::{get_forkchoice_store, Store}, -}; +//! Fork choice test vectors for devnet2 +//! +//! Integration tests for fork choice rule implementation +//! using devnet2 data structures. use containers::{ - attestation::{Attestation, AttestationData, Signature, SignedAttestation}, - block::{ - hash_tree_root, Block, BlockBody, BlockHeader, BlockWithAttestation, - SignedBlockWithAttestation, - }, + attestation::{AttestationData, SignedAttestation}, + block::{Block, BlockBody, BlockWithAttestation, SignedBlockWithAttestation}, checkpoint::Checkpoint, config::Config, state::State, + validator::Validator, Bytes32, Slot, Uint64, ValidatorIndex, }; - -use serde::Deserialize; -use ssz::{PersistentList, SszHash}; +use fork_choice::store::{get_fork_choice_head, get_forkchoice_store, Store}; +use ssz::SszHash; use std::collections::HashMap; -use std::panic::AssertUnwindSafe; -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestVectorFile { - #[serde(flatten)] - tests: HashMap, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestVector { - #[allow(dead_code)] - network: String, - anchor_state: TestAnchorState, - anchor_block: TestAnchorBlock, - steps: Vec, - #[serde(rename = "_info")] - info: TestInfo, -} +/// Helper to create a genesis store for testing +fn create_genesis_store() -> Store { + let config = Config { genesis_time: 0 }; + let validators = vec![Validator::default(); 10]; + let state = State::generate_genesis_with_validators(Uint64(0), validators); -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestAnchorState { - config: TestConfig, - slot: u64, - latest_block_header: TestBlockHeader, - latest_justified: TestCheckpoint, - latest_finalized: TestCheckpoint, - #[serde(default)] - historical_block_hashes: TestDataWrapper, - #[serde(default)] - justified_slots: TestDataWrapper, - validators: TestDataWrapper, - #[serde(default)] - justifications_roots: TestDataWrapper, - #[serde(default)] - justifications_validators: TestDataWrapper, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestConfig { - genesis_time: u64, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestBlockHeader { - slot: u64, - proposer_index: u64, - parent_root: String, - state_root: String, - body_root: String, -} - -#[derive(Debug, Deserialize)] -struct TestCheckpoint { - root: String, - slot: u64, -} - -#[derive(Debug, Deserialize, Default)] -struct TestDataWrapper { - data: Vec, -} - -#[derive(Debug, Deserialize)] -struct TestValidator { - #[allow(dead_code)] - pubkey: String, - #[allow(dead_code)] - #[serde(default)] - index: u64, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestAnchorBlock { - slot: u64, - proposer_index: u64, - parent_root: String, - state_root: String, - body: TestBlockBody, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestBlock { - slot: u64, - proposer_index: u64, - parent_root: String, - state_root: String, - body: TestBlockBody, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestBlockWithAttestation { - block: TestBlock, - proposer_attestation: TestAttestation, - #[serde(default)] - block_root_label: Option, -} + let block = Block { + slot: Slot(0), + proposer_index: ValidatorIndex(0), + parent_root: Bytes32::default(), + state_root: Bytes32(state.hash_tree_root()), + body: BlockBody::default(), + }; -#[derive(Debug, Deserialize)] -struct TestBlockBody { - attestations: TestDataWrapper, -} + let signed_block = SignedBlockWithAttestation { + message: BlockWithAttestation { + block, + proposer_attestation: Default::default(), + }, + signature: Default::default(), + }; -#[derive(Debug, Deserialize)] -#[serde(untagged)] -enum TestAttestation { - #[serde(rename_all = "camelCase")] - Nested { - validator_id: u64, - data: TestAttestationData, - }, - #[serde(rename_all = "camelCase")] - Flat { - validator_id: u64, - slot: u64, - head: TestCheckpoint, - target: TestCheckpoint, - source: TestCheckpoint, - }, + get_forkchoice_store(state, signed_block, config) } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestAttestationData { +/// Helper to create a signed attestation +fn create_attestation( + validator_id: u64, slot: u64, - head: TestCheckpoint, - target: TestCheckpoint, - source: TestCheckpoint, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestStep { - valid: bool, - #[serde(default)] - checks: Option, - #[serde(rename = "stepType")] - step_type: String, - block: Option, - attestation: Option, - tick: Option, - time: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestChecks { - #[serde(rename = "headSlot")] - head_slot: Option, - #[serde(rename = "headRootLabel")] - head_root_label: Option, - #[serde(rename = "attestationChecks")] - attestation_checks: Option>, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct AttestationCheck { - validator: u64, - #[allow(dead_code)] - #[serde(rename = "attestationSlot")] - attestation_slot: u64, - #[serde(rename = "targetSlot")] - target_slot: Option, - location: String, -} - -#[derive(Debug, Deserialize)] -struct TestInfo { - #[allow(dead_code)] - hash: String, - #[allow(dead_code)] - comment: String, - #[serde(rename = "testId")] - test_id: String, - #[allow(dead_code)] - description: String, - #[allow(dead_code)] - #[serde(rename = "fixtureFormat")] - fixture_format: String, -} - -fn parse_root(hex_str: &str) -> Bytes32 { - let hex = hex_str.trim_start_matches("0x"); - let mut bytes = [0u8; 32]; - - if hex.len() == 64 { - for i in 0..32 { - bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16) - .unwrap_or_else(|_| panic!("Invalid hex at position {}: {}", i, hex)); - } - } else if !hex.chars().all(|c| c == '0') { - panic!("Invalid root length: {} (expected 64 hex chars)", hex.len()); - } - - Bytes32(ssz::H256::from(bytes)) -} - -fn convert_test_checkpoint(test_cp: &TestCheckpoint) -> Checkpoint { - Checkpoint { - root: parse_root(&test_cp.root), - slot: Slot(test_cp.slot), - } -} - -fn convert_test_attestation(test_att: &TestAttestation) -> Attestation { - let (validator_id, slot, head, target, source) = match test_att { - TestAttestation::Nested { validator_id, data } => ( - *validator_id, - data.slot, - &data.head, - &data.target, - &data.source, - ), - TestAttestation::Flat { - validator_id, - slot, + head: Checkpoint, + target: Checkpoint, + source: Checkpoint, +) -> SignedAttestation { + SignedAttestation { + validator_id, + message: AttestationData { + slot: Slot(slot), head, target, source, - } => (*validator_id, *slot, head, target, source), - }; - - Attestation { - validator_id: Uint64(validator_id), - data: AttestationData { - slot: Slot(slot), - head: convert_test_checkpoint(head), - target: convert_test_checkpoint(target), - source: convert_test_checkpoint(source), }, + signature: Default::default(), } } -#[cfg(feature = "devnet1")] -fn convert_test_anchor_block(test_block: &TestAnchorBlock) -> SignedBlockWithAttestation { - let mut attestations = ssz::PersistentList::default(); - - for (i, test_att) in test_block.body.attestations.data.iter().enumerate() { - let signed_vote = convert_test_attestation(test_att); - attestations - .push(signed_vote) - .expect(&format!("Failed to add attestation {}", i)); - } - +/// Helper to add a block to the store +fn add_block(store: &mut Store, slot: u64, parent_root: Bytes32, proposer: u64) -> Bytes32 { let block = Block { - slot: Slot(test_block.slot), - proposer_index: ValidatorIndex(test_block.proposer_index), - parent_root: parse_root(&test_block.parent_root), - state_root: parse_root(&test_block.state_root), - body: BlockBody { attestations }, + slot: Slot(slot), + proposer_index: ValidatorIndex(proposer), + parent_root, + state_root: Bytes32::default(), + body: BlockBody::default(), }; - - // Create proposer attestation - let proposer_attestation = Attestation { - validator_id: Uint64(test_block.proposer_index), - data: AttestationData { - slot: Slot(test_block.slot), - head: Checkpoint { - root: parse_root(&test_block.parent_root), - slot: Slot(test_block.slot), - }, - target: Checkpoint { - root: parse_root(&test_block.parent_root), - slot: Slot(test_block.slot), - }, - source: Checkpoint { - root: parse_root(&test_block.parent_root), - slot: Slot(0), + let block_root = Bytes32(block.hash_tree_root()); + + store.blocks.insert( + block_root, + SignedBlockWithAttestation { + message: BlockWithAttestation { + block, + proposer_attestation: Default::default(), }, + signature: Default::default(), }, - }; + ); - SignedBlockWithAttestation { - message: BlockWithAttestation { - block, - proposer_attestation, - }, - signature: PersistentList::default(), - } + block_root } -#[cfg(feature = "devnet1")] -fn convert_test_block( - test_block_with_att: &TestBlockWithAttestation, -) -> SignedBlockWithAttestation { - let test_block = &test_block_with_att.block; - let mut attestations = ssz::PersistentList::default(); - - for (i, test_att) in test_block.body.attestations.data.iter().enumerate() { - let signed_vote = convert_test_attestation(test_att); - attestations - .push(signed_vote) - .expect(&format!("Failed to add attestation {}", i)); - } - - let block = Block { - slot: Slot(test_block.slot), - proposer_index: ValidatorIndex(test_block.proposer_index), - parent_root: parse_root(&test_block.parent_root), - state_root: parse_root(&test_block.state_root), - body: BlockBody { attestations }, - }; +#[test] +fn test_genesis_state_transition() { + let store = create_genesis_store(); - let proposer_attestation = convert_test_attestation(&test_block_with_att.proposer_attestation); + // Verify genesis state is properly initialized + assert!(!store.head.0.is_zero()); + assert_eq!(store.blocks.len(), 1); + assert_eq!(store.states.len(), 1); - SignedBlockWithAttestation { - message: BlockWithAttestation { - block, - proposer_attestation, - }, - signature: PersistentList::default(), - } + // Genesis should be both justified and finalized + assert_eq!(store.latest_justified.slot, Slot(0)); + assert_eq!(store.latest_finalized.slot, Slot(0)); } -fn initialize_state_from_test(test_state: &TestAnchorState) -> State { - use containers::{ - HistoricalBlockHashes, JustificationRoots, JustificationsValidators, JustifiedSlots, +#[test] +fn test_basic_slot_transition() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + + // Add blocks at slots 1, 2, 3 + let block1_root = add_block(&mut store, 1, genesis_root, 0); + let block2_root = add_block(&mut store, 2, block1_root, 0); + let block3_root = add_block(&mut store, 3, block2_root, 0); + + assert_eq!(store.blocks.len(), 4); + + // Without attestations and min_votes=1, head should stay at genesis + // (no blocks have enough votes to be considered) + let empty_attestations = HashMap::new(); + let head = get_fork_choice_head(&store, genesis_root, &empty_attestations, 1); + assert_eq!(head, genesis_root); + + // With attestation for block3 and min_votes=1, head should follow the voted chain + let mut attestations = HashMap::new(); + let checkpoint = Checkpoint { + root: block3_root, + slot: Slot(3), }; - use ssz::PersistentList as List; - - let config = Config { - genesis_time: test_state.config.genesis_time, + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), }; - let latest_block_header = BlockHeader { - slot: Slot(test_state.latest_block_header.slot), - proposer_index: ValidatorIndex(test_state.latest_block_header.proposer_index), - parent_root: parse_root(&test_state.latest_block_header.parent_root), - state_root: parse_root(&test_state.latest_block_header.state_root), - body_root: parse_root(&test_state.latest_block_header.body_root), - }; + attestations.insert( + ValidatorIndex(0), + create_attestation( + 0, + 3, + checkpoint.clone(), + checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); - let mut historical_block_hashes = HistoricalBlockHashes::default(); - for hash_str in &test_state.historical_block_hashes.data { - historical_block_hashes - .push(parse_root(hash_str)) - .expect("within limit"); - } + // The fork choice should follow the chain with votes to find the heaviest head + let head = get_fork_choice_head(&store, genesis_root, &attestations, 1); - let mut justified_slots = JustifiedSlots::new(false, test_state.justified_slots.data.len()); - for (i, &val) in test_state.justified_slots.data.iter().enumerate() { - if val { - justified_slots.set(i, true); - } - } + // With 1 vote on block3, the entire chain block1->block2->block3 gets 1 vote each + // So head should be block3 (the tip of the voted chain) + assert_eq!(head, block3_root); +} - let mut justifications_roots = JustificationRoots::default(); - for root_str in &test_state.justifications_roots.data { - justifications_roots - .push(parse_root(root_str)) - .expect("within limit"); - } +#[test] +fn test_attestation_processing() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), + }; - let mut justifications_validators = - JustificationsValidators::new(false, test_state.justifications_validators.data.len()); - for (i, &val) in test_state.justifications_validators.data.iter().enumerate() { - if val { - justifications_validators.set(i, true); - } - } + // Create a block + let block1_root = add_block(&mut store, 1, genesis_root, 0); + let block1_checkpoint = Checkpoint { + root: block1_root, + slot: Slot(1), + }; - let mut validators = List::default(); - for test_validator in &test_state.validators.data { - let pubkey = containers::validator::BlsPublicKey::from_hex(&test_validator.pubkey) - .expect("Failed to parse validator pubkey"); - let validator = containers::validator::Validator { - pubkey, - index: containers::Uint64(test_validator.index), - }; - validators.push(validator).expect("Failed to add validator"); + // Process attestations from multiple validators + let mut attestations = HashMap::new(); + for i in 0..5 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 1, + block1_checkpoint.clone(), + block1_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } - State { - config, - slot: Slot(test_state.slot), - latest_block_header, - latest_justified: convert_test_checkpoint(&test_state.latest_justified), - latest_finalized: convert_test_checkpoint(&test_state.latest_finalized), - historical_block_hashes, - justified_slots, - validators, - justifications_roots, - justifications_validators, - } + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + assert_eq!(head, block1_root); } -#[cfg(feature = "devnet1")] -fn verify_checks( - store: &Store, - checks: &Option, - block_labels: &HashMap, - step_idx: usize, -) -> Result<(), String> { - // If no checks provided, nothing to verify - let checks = match checks { - Some(c) => c, - None => return Ok(()), +#[test] +fn test_multiple_attestations() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), }; - if let Some(expected_slot) = checks.head_slot { - let actual_slot = store.blocks[&store.head].message.block.slot.0; - if actual_slot != expected_slot { - return Err(format!( - "Step {}: Head slot mismatch - expected {}, got {}", - step_idx, expected_slot, actual_slot - )); - } - } + // Create a chain of blocks + let block1_root = add_block(&mut store, 1, genesis_root, 0); + let block2_root = add_block(&mut store, 2, block1_root, 0); + let block3_root = add_block(&mut store, 3, block2_root, 0); - if let Some(label) = &checks.head_root_label { - let expected_root = block_labels - .get(label) - .ok_or_else(|| format!("Step {}: Block label '{}' not found", step_idx, label))?; - if &store.head != expected_root { - let actual_slot = store - .blocks - .get(&store.head) - .map(|b| b.message.block.slot.0) - .unwrap_or(0); - let expected_slot = store - .blocks - .get(expected_root) - .map(|b| b.message.block.slot.0) - .unwrap_or(0); - return Err(format!( - "Step {}: Head root mismatch for label '{}' - expected slot {}, got slot {} (known_attestations: {}, new_attestations: {})", - step_idx, label, expected_slot, actual_slot, - store.latest_known_attestations.len(), store.latest_new_attestations.len() - )); - } - } + let block3_checkpoint = Checkpoint { + root: block3_root, + slot: Slot(3), + }; - if let Some(att_checks) = &checks.attestation_checks { - for check in att_checks { - let validator = ValidatorIndex(check.validator); - - match check.location.as_str() { - "new" => { - if !store.latest_new_attestations.contains_key(&validator) { - return Err(format!( - "Step {}: Expected validator {} in new attestations, but not found", - step_idx, check.validator - )); - } - if let Some(target_slot) = check.target_slot { - let attestation = &store.latest_new_attestations[&validator]; - if attestation.message.data.target.slot.0 != target_slot { - return Err(format!( - "Step {}: Validator {} new attestation target slot mismatch - expected {}, got {}", - step_idx, check.validator, target_slot, attestation.message.data.target.slot.0 - )); - } - } - } - "known" => { - if !store.latest_known_attestations.contains_key(&validator) { - return Err(format!( - "Step {}: Expected validator {} in known attestations, but not found", - step_idx, check.validator - )); - } - } - _ => { - return Err(format!( - "Step {}: Unknown attestation location: {}", - step_idx, check.location - )); - } - } - } + // All validators attest to block3 + let mut attestations = HashMap::new(); + for i in 0..10 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 3, + block3_checkpoint.clone(), + block3_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } - Ok(()) + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + assert_eq!(head, block3_root); } -#[cfg(feature = "devnet1")] -fn run_single_test(_test_name: &str, test: TestVector) -> Result<(), String> { - println!(" Running: {}", test.info.test_id); +#[test] +fn test_fork_choice_with_competing_blocks() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), + }; - let mut anchor_state = initialize_state_from_test(&test.anchor_state); - let anchor_block = convert_test_anchor_block(&test.anchor_block); + // Create two competing forks at slot 1 + let fork_a_root = add_block(&mut store, 1, genesis_root, 0); + let fork_b_root = add_block(&mut store, 1, genesis_root, 1); // Different proposer - let body_root = hash_tree_root(&anchor_block.message.block.body); - anchor_state.latest_block_header = BlockHeader { - slot: anchor_block.message.block.slot, - proposer_index: anchor_block.message.block.proposer_index, - parent_root: anchor_block.message.block.parent_root, - state_root: anchor_block.message.block.state_root, - body_root, + let fork_a_checkpoint = Checkpoint { + root: fork_a_root, + slot: Slot(1), }; - - let config = Config { - genesis_time: test.anchor_state.config.genesis_time, + let fork_b_checkpoint = Checkpoint { + root: fork_b_root, + slot: Slot(1), }; - let mut store = get_forkchoice_store(anchor_state, anchor_block, config); - let mut block_labels: HashMap = HashMap::new(); - - for (step_idx, step) in test.steps.iter().enumerate() { - match step.step_type.as_str() { - "block" => { - let test_block = step - .block - .as_ref() - .ok_or_else(|| format!("Step {}: Missing block data", step_idx))?; - - let result = std::panic::catch_unwind(AssertUnwindSafe(|| { - let signed_block = convert_test_block(test_block); - let block_root = Bytes32(signed_block.message.block.hash_tree_root()); - - // Advance time to the block's slot to ensure attestations are processable - // SECONDS_PER_SLOT is 4 (not 12) - let block_time = - store.config.genesis_time + (signed_block.message.block.slot.0 * 4); - on_tick(&mut store, block_time, false); - - on_block(&mut store, signed_block)?; - Ok(block_root) - })); - - let result = match result { - Ok(inner) => inner, - Err(e) => Err(format!("Panic: {:?}", e)), - }; - - if let Ok(block_root) = &result { - if let Some(label) = &test_block.block_root_label { - block_labels.insert(label.clone(), *block_root); - } - } - - if step.valid && result.is_err() { - return Err(format!( - "Step {}: Block should be valid but processing failed: {:?}", - step_idx, - result.err() - )); - } else if !step.valid && result.is_ok() { - return Err(format!( - "Step {}: Block should be invalid but processing succeeded", - step_idx - )); - } - - if step.valid && result.is_ok() { - verify_checks(&store, &step.checks, &block_labels, step_idx)?; - } - } - "tick" | "time" => { - let time_value = step - .tick - .or(step.time) - .ok_or_else(|| format!("Step {}: Missing tick/time data", step_idx))?; - on_tick(&mut store, time_value, false); - - if step.valid { - verify_checks(&store, &step.checks, &block_labels, step_idx)?; - } - } - "attestation" => { - let test_att = step - .attestation - .as_ref() - .ok_or_else(|| format!("Step {}: Missing attestation data", step_idx))?; - - let result = std::panic::catch_unwind(AssertUnwindSafe(|| { - let attestation = convert_test_attestation(test_att); - let signed_attestation = SignedAttestation { - message: attestation, - signature: Signature::default(), - }; - on_attestation(&mut store, signed_attestation, false) - })); - - let result = match result { - Ok(inner) => inner, - Err(e) => Err(format!("Panic: {:?}", e)), - }; - - if step.valid && result.is_err() { - return Err(format!( - "Step {}: Attestation should be valid but processing failed: {:?}", - step_idx, - result.err() - )); - } else if !step.valid && result.is_ok() { - return Err(format!( - "Step {}: Attestation should be invalid but processing succeeded", - step_idx - )); - } - - if step.valid && result.is_ok() { - verify_checks(&store, &step.checks, &block_labels, step_idx)?; - } - } - _ => { - return Err(format!( - "Step {}: Unknown step type: {}", - step_idx, step.step_type - )); - } - } + // 6 validators vote for fork A + let mut attestations = HashMap::new(); + for i in 0..6 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 1, + fork_a_checkpoint.clone(), + fork_a_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } - Ok(()) -} - -#[cfg(feature = "devnet1")] -fn run_test_vector_file(test_path: &str) -> Result<(), String> { - let json_str = std::fs::read_to_string(test_path) - .map_err(|e| format!("Failed to read file {}: {}", test_path, e))?; - - let test_data: TestVectorFile = serde_json::from_str(&json_str) - .map_err(|e| format!("Failed to parse JSON from {}: {}", test_path, e))?; - - for (test_name, test_vector) in test_data.tests { - run_single_test(&test_name, test_vector)?; + // 4 validators vote for fork B + for i in 6..10 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 1, + fork_b_checkpoint.clone(), + fork_b_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } - Ok(()) + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + + // Fork A should win with more votes + assert_eq!(head, fork_a_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_fork_choice_head_vectors() { - let test_dir = "../tests/test_vectors/test_fork_choice/test_fork_choice_head"; - - let entries = - std::fs::read_dir(test_dir).expect(&format!("Failed to read test directory: {}", test_dir)); - - let mut test_count = 0; - let mut pass_count = 0; - let mut fail_count = 0; - - println!("\n=== Fork Choice Head Tests ==="); - - for entry in entries { - let path = entry.unwrap().path(); - if path.extension().map_or(false, |ext| ext == "json") { - test_count += 1; - println!("\nTest file: {:?}", path.file_name().unwrap()); - - match run_test_vector_file(path.to_str().unwrap()) { - Ok(_) => { - println!(" ✓ PASSED"); - pass_count += 1; - } - Err(e) => { - println!(" ✗ FAILED: {}", e); - fail_count += 1; - } - } - } - } +fn test_finality_prevents_reorg() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), + }; - println!("\n=== Summary ==="); - println!( - "Total: {}, Passed: {}, Failed: {}", - test_count, pass_count, fail_count - ); + // Create a finalized chain + let block1_root = add_block(&mut store, 1, genesis_root, 0); + let block2_root = add_block(&mut store, 2, block1_root, 0); - if fail_count > 0 { - panic!("{} test(s) failed", fail_count); - } -} + // Update finalized checkpoint + store.latest_finalized = Checkpoint { + root: block1_root, + slot: Slot(1), + }; -#[test] -#[cfg(feature = "devnet1")] -fn test_attestation_processing_vectors() { - let test_dir = "../tests/test_vectors/test_fork_choice/test_attestation_processing"; - - let entries = - std::fs::read_dir(test_dir).expect(&format!("Failed to read test directory: {}", test_dir)); - - let mut test_count = 0; - let mut pass_count = 0; - let mut fail_count = 0; - - println!("\n=== Attestation Processing Tests ==="); - - for entry in entries { - let path = entry.unwrap().path(); - if path.extension().map_or(false, |ext| ext == "json") { - test_count += 1; - println!("\nTest file: {:?}", path.file_name().unwrap()); - - match run_test_vector_file(path.to_str().unwrap()) { - Ok(_) => { - println!(" ✓ PASSED"); - pass_count += 1; - } - Err(e) => { - println!(" ✗ FAILED: {}", e); - fail_count += 1; - } - } - } - } + // Create competing fork from genesis (should not be chosen due to finality) + let competing_root = add_block(&mut store, 1, genesis_root, 1); - println!("\n=== Summary ==="); - println!( - "Total: {}, Passed: {}, Failed: {}", - test_count, pass_count, fail_count - ); + let block2_checkpoint = Checkpoint { + root: block2_root, + slot: Slot(2), + }; + let competing_checkpoint = Checkpoint { + root: competing_root, + slot: Slot(1), + }; - if fail_count > 0 { - panic!("{} test(s) failed", fail_count); + // More votes for competing fork + let mut attestations = HashMap::new(); + for i in 0..7 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 1, + competing_checkpoint.clone(), + competing_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } -} - -#[test] -#[cfg(feature = "devnet1")] -fn test_fork_choice_reorgs_vectors() { - let test_dir = "../tests/test_vectors/test_fork_choice/test_fork_choice_reorgs"; - - let entries = - std::fs::read_dir(test_dir).expect(&format!("Failed to read test directory: {}", test_dir)); - - let mut test_count = 0; - let mut pass_count = 0; - let mut fail_count = 0; - - println!("\n=== Fork Choice Reorg Tests ==="); - - for entry in entries { - let path = entry.unwrap().path(); - if path.extension().map_or(false, |ext| ext == "json") { - test_count += 1; - println!("\nTest file: {:?}", path.file_name().unwrap()); - - match run_test_vector_file(path.to_str().unwrap()) { - Ok(_) => { - println!(" ✓ PASSED"); - pass_count += 1; - } - Err(e) => { - println!(" ✗ FAILED: {}", e); - fail_count += 1; - } - } - } + for i in 7..10 { + attestations.insert( + ValidatorIndex(i), + create_attestation( + i, + 2, + block2_checkpoint.clone(), + block2_checkpoint.clone(), + genesis_checkpoint.clone(), + ), + ); } - println!("\n=== Summary ==="); - println!( - "Total: {}, Passed: {}, Failed: {}", - test_count, pass_count, fail_count - ); + // Start from finalized block1 + let head = get_fork_choice_head(&store, block1_root, &attestations, 0); - if fail_count > 0 { - panic!("{} test(s) failed", fail_count); - } + // Should follow the chain from block1, not competing fork + assert_eq!(head, block2_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_attestation_target_selection_vectors() { - let test_dir = "../tests/test_vectors/test_fork_choice/test_attestation_target_selection"; - - let entries = - std::fs::read_dir(test_dir).expect(&format!("Failed to read test directory: {}", test_dir)); - - let mut test_count = 0; - let mut pass_count = 0; - let mut fail_count = 0; - - println!("\n=== Attestation Target Selection Tests ==="); - - for entry in entries { - let path = entry.unwrap().path(); - if path.extension().map_or(false, |ext| ext == "json") { - test_count += 1; - println!("\nTest file: {:?}", path.file_name().unwrap()); - - match run_test_vector_file(path.to_str().unwrap()) { - Ok(_) => { - println!(" ✓ PASSED"); - pass_count += 1; - } - Err(e) => { - println!(" ✗ FAILED: {}", e); - fail_count += 1; - } - } - } - } +fn test_attestation_from_future_slot() { + let mut store = create_genesis_store(); + let genesis_root = store.head; + let genesis_checkpoint = Checkpoint { + root: genesis_root, + slot: Slot(0), + }; - println!("\n=== Summary ==="); - println!( - "Total: {}, Passed: {}, Failed: {}", - test_count, pass_count, fail_count + // Create block at slot 1 + let block1_root = add_block(&mut store, 1, genesis_root, 0); + let block1_checkpoint = Checkpoint { + root: block1_root, + slot: Slot(1), + }; + + // Attestation claims to be from slot 100 (future) + // The fork choice still processes it based on what block it points to + let mut attestations = HashMap::new(); + attestations.insert( + ValidatorIndex(0), + create_attestation( + 0, + 100, + block1_checkpoint.clone(), + block1_checkpoint.clone(), + genesis_checkpoint.clone(), + ), ); - if fail_count > 0 { - panic!("{} test(s) failed", fail_count); - } + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + + // Should still follow the attestation to block1 + assert_eq!(head, block1_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_lexicographic_tiebreaker_vectors() { - let test_dir = "../tests/test_vectors/test_fork_choice/test_lexicographic_tiebreaker"; - - let entries = - std::fs::read_dir(test_dir).expect(&format!("Failed to read test directory: {}", test_dir)); - - let mut test_count = 0; - let mut pass_count = 0; - let mut fail_count = 0; - - println!("\n=== Lexicographic Tiebreaker Tests ==="); - - for entry in entries { - let path = entry.unwrap().path(); - if path.extension().map_or(false, |ext| ext == "json") { - test_count += 1; - println!("\nTest file: {:?}", path.file_name().unwrap()); - - match run_test_vector_file(path.to_str().unwrap()) { - Ok(_) => { - println!(" ✓ PASSED"); - pass_count += 1; - } - Err(e) => { - println!(" ✗ FAILED: {}", e); - fail_count += 1; - } - } - } - } +fn test_empty_attestations_returns_root() { + let store = create_genesis_store(); + let genesis_root = store.head; - println!("\n=== Summary ==="); - println!( - "Total: {}, Passed: {}, Failed: {}", - test_count, pass_count, fail_count - ); + let empty_attestations = HashMap::new(); + let head = get_fork_choice_head(&store, genesis_root, &empty_attestations, 0); - if fail_count > 0 { - panic!("{} test(s) failed", fail_count); - } + // With no attestations, should return the provided root + assert_eq!(head, genesis_root); } diff --git a/lean_client/fork_choice/tests/unit_tests/votes.rs b/lean_client/fork_choice/tests/unit_tests/votes.rs index d6c2ad4..a3c9b8a 100644 --- a/lean_client/fork_choice/tests/unit_tests/votes.rs +++ b/lean_client/fork_choice/tests/unit_tests/votes.rs @@ -1,315 +1,258 @@ +//! Vote/attestation unit tests for devnet2 +//! +//! Tests for vote processing and fork choice weight calculations +//! using the devnet2 SignedAttestation structure. + use super::common::create_test_store; use containers::{ - attestation::{Attestation, AttestationData, Signature, SignedAttestation}, + attestation::{AttestationData, SignedAttestation}, + block::{Block, BlockBody, BlockWithAttestation, SignedBlockWithAttestation}, checkpoint::Checkpoint, - Bytes32, Slot, Uint64, ValidatorIndex, + Bytes32, Slot, ValidatorIndex, }; -use fork_choice::handlers::on_attestation; -use fork_choice::store::{accept_new_attestations, INTERVALS_PER_SLOT}; +use fork_choice::store::get_fork_choice_head; +use ssz::SszHash; +use std::collections::HashMap; -#[cfg(feature = "devnet1")] +/// Helper to create a SignedAttestation for devnet2 fn create_signed_attestation( validator_id: u64, - slot: Slot, + slot: u64, head_root: Bytes32, + head_slot: u64, + target_root: Bytes32, + target_slot: u64, + source_root: Bytes32, + source_slot: u64, ) -> SignedAttestation { SignedAttestation { - message: Attestation { - validator_id: Uint64(validator_id), - data: AttestationData { - slot, - head: Checkpoint { - root: head_root, - slot, - }, - target: Checkpoint { - root: head_root, - slot, - }, - source: Checkpoint { - root: Bytes32::default(), - slot: Slot(0), - }, + validator_id, + message: AttestationData { + slot: Slot(slot), + head: Checkpoint { + root: head_root, + slot: Slot(head_slot), + }, + target: Checkpoint { + root: target_root, + slot: Slot(target_slot), + }, + source: Checkpoint { + root: source_root, + slot: Slot(source_slot), }, }, - signature: Signature::default(), + signature: Default::default(), } } #[test] -#[cfg(feature = "devnet1")] -fn test_accept_new_attestations() { - let mut store = create_test_store(); - - // Setup initial known attestations - let val1 = ValidatorIndex(1); - let val2 = ValidatorIndex(2); - let val3 = ValidatorIndex(3); - - store - .latest_known_attestations - .insert(val1, create_signed_attestation(1, Slot(0), store.head)); - - // Val1 updates their attestation to Slot 1 - store - .latest_new_attestations - .insert(val1, create_signed_attestation(1, Slot(1), store.head)); - // Val2 casts a new attestation for Slot 1 - store - .latest_new_attestations - .insert(val2, create_signed_attestation(2, Slot(1), store.head)); - // Val3 casts a new attestation for Slot 2 - store - .latest_new_attestations - .insert(val3, create_signed_attestation(3, Slot(2), store.head)); - - accept_new_attestations(&mut store); - - assert_eq!(store.latest_new_attestations.len(), 0); - assert_eq!(store.latest_known_attestations.len(), 3); - - assert_eq!( - store.latest_known_attestations[&val1].message.data.slot, - Slot(1) - ); - assert_eq!( - store.latest_known_attestations[&val2].message.data.slot, - Slot(1) +fn test_single_vote_updates_head() { + let store = create_test_store(); + let genesis_root = store.head; + + // Create attestation pointing to genesis + let attestation = create_signed_attestation( + 0, // validator_id + 1, // slot + genesis_root, // head_root + 0, // head_slot + genesis_root, // target_root + 0, // target_slot + genesis_root, // source_root + 0, // source_slot ); - assert_eq!( - store.latest_known_attestations[&val3].message.data.slot, - Slot(2) - ); -} -#[test] -#[cfg(feature = "devnet1")] -fn test_accept_new_attestations_multiple() { - let mut store = create_test_store(); - - for i in 0..5 { - store.latest_new_attestations.insert( - ValidatorIndex(i), - create_signed_attestation(i, Slot(i), store.head), - ); - } + let mut attestations = HashMap::new(); + attestations.insert(ValidatorIndex(0), attestation); - assert_eq!(store.latest_new_attestations.len(), 5); - assert_eq!(store.latest_known_attestations.len(), 0); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); - accept_new_attestations(&mut store); - - assert_eq!(store.latest_new_attestations.len(), 0); - assert_eq!(store.latest_known_attestations.len(), 5); + // With only one block, head should still be genesis + assert_eq!(head, genesis_root); } #[test] -fn test_accept_new_attestations_empty() { - let mut store = create_test_store(); - let initial_known = store.latest_known_attestations.len(); +fn test_multiple_votes_same_block() { + let store = create_test_store(); + let genesis_root = store.head; - accept_new_attestations(&mut store); + // Multiple validators vote for same block + let mut attestations = HashMap::new(); + for i in 0..5 { + let attestation = + create_signed_attestation(i, 1, genesis_root, 0, genesis_root, 0, genesis_root, 0); + attestations.insert(ValidatorIndex(i), attestation); + } - assert_eq!(store.latest_new_attestations.len(), 0); - assert_eq!(store.latest_known_attestations.len(), initial_known); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + + // All votes on same block, head unchanged + assert_eq!(head, genesis_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_lifecycle() { +fn test_competing_votes_different_blocks() { let mut store = create_test_store(); - let validator_idx = ValidatorIndex(1); - let slot_0 = Slot(0); - let slot_1 = Slot(1); - - // 1. Attestation from network (gossip) - let signed_attestation_gossip = create_signed_attestation(1, slot_0, store.head); - - on_attestation(&mut store, signed_attestation_gossip.clone(), false) - .expect("Gossip attestation valid"); - - // Should be in new_attestations, not known_attestations - assert!(store.latest_new_attestations.contains_key(&validator_idx)); - assert!(!store.latest_known_attestations.contains_key(&validator_idx)); - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - slot_0 - ); - - // 2. Same attestation included in a block - on_attestation(&mut store, signed_attestation_gossip, true).expect("Block attestation valid"); - - assert!(store.latest_known_attestations.contains_key(&validator_idx)); - assert_eq!( - store.latest_known_attestations[&validator_idx] - .message - .data - .slot, - slot_0 + let genesis_root = store.head; + + // Create two competing blocks at slot 1 + let block_a = Block { + slot: Slot(1), + proposer_index: ValidatorIndex(0), + parent_root: genesis_root, + state_root: Bytes32::default(), + body: BlockBody::default(), + }; + let block_a_root = Bytes32(block_a.hash_tree_root()); + + let mut block_b = block_a.clone(); + block_b.proposer_index = ValidatorIndex(1); // Different proposer to get different root + let block_b_root = Bytes32(block_b.hash_tree_root()); + + store.blocks.insert( + block_a_root, + SignedBlockWithAttestation { + message: BlockWithAttestation { + block: block_a, + proposer_attestation: Default::default(), + }, + signature: Default::default(), + }, ); - // 3. Newer attestation from network - store.time = 1 * INTERVALS_PER_SLOT; // Advance time - let signed_attestation_next = create_signed_attestation(1, slot_1, store.head); - - on_attestation(&mut store, signed_attestation_next, false) - .expect("Next gossip attestation valid"); - - // Should update new_attestations - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - slot_1 - ); - // Known attestations should still be at slot 0 until accepted - assert_eq!( - store.latest_known_attestations[&validator_idx] - .message - .data - .slot, - slot_0 + store.blocks.insert( + block_b_root, + SignedBlockWithAttestation { + message: BlockWithAttestation { + block: block_b, + proposer_attestation: Default::default(), + }, + signature: Default::default(), + }, ); -} -#[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_future_slot() { - let mut store = create_test_store(); - let future_slot = Slot(100); // Far in the future + // 3 votes for block_a, 2 votes for block_b + let mut attestations = HashMap::new(); + for i in 0..3 { + attestations.insert( + ValidatorIndex(i), + create_signed_attestation(i, 1, block_a_root, 1, genesis_root, 0, genesis_root, 0), + ); + } + for i in 3..5 { + attestations.insert( + ValidatorIndex(i), + create_signed_attestation(i, 1, block_b_root, 1, genesis_root, 0, genesis_root, 0), + ); + } - let signed_attestation = create_signed_attestation(1, future_slot, store.head); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); - let result = on_attestation(&mut store, signed_attestation, false); - assert!(result.is_err()); + // Block A should win with more votes + assert_eq!(head, block_a_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_update_vote() { +fn test_vote_weight_accumulation() { let mut store = create_test_store(); - let validator_idx = ValidatorIndex(1); - - // First attestation at slot 0 - let signed_attestation1 = create_signed_attestation(1, Slot(0), store.head); - - on_attestation(&mut store, signed_attestation1, false).expect("First attestation valid"); - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - Slot(0) + let genesis_root = store.head; + + // Create a chain: genesis -> block1 -> block2 + let block1 = Block { + slot: Slot(1), + proposer_index: ValidatorIndex(0), + parent_root: genesis_root, + state_root: Bytes32::default(), + body: BlockBody::default(), + }; + let block1_root = Bytes32(block1.hash_tree_root()); + + let block2 = Block { + slot: Slot(2), + proposer_index: ValidatorIndex(0), + parent_root: block1_root, + state_root: Bytes32::default(), + body: BlockBody::default(), + }; + let block2_root = Bytes32(block2.hash_tree_root()); + + store.blocks.insert( + block1_root, + SignedBlockWithAttestation { + message: BlockWithAttestation { + block: block1, + proposer_attestation: Default::default(), + }, + signature: Default::default(), + }, + ); + store.blocks.insert( + block2_root, + SignedBlockWithAttestation { + message: BlockWithAttestation { + block: block2, + proposer_attestation: Default::default(), + }, + signature: Default::default(), + }, ); - // Advance time to allow slot 1 - store.time = 1 * INTERVALS_PER_SLOT; + // Vote for block2 - should accumulate to block1 as well + let mut attestations = HashMap::new(); + attestations.insert( + ValidatorIndex(0), + create_signed_attestation(0, 2, block2_root, 2, genesis_root, 0, genesis_root, 0), + ); - // Second attestation at slot 1 - let signed_attestation2 = create_signed_attestation(1, Slot(1), store.head); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); - on_attestation(&mut store, signed_attestation2, false).expect("Second attestation valid"); - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - Slot(1) - ); + // Head should be block2 (the one with votes) + assert_eq!(head, block2_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_ignore_old_vote() { - let mut store = create_test_store(); - let validator_idx = ValidatorIndex(1); +fn test_duplicate_vote_uses_latest() { + let store = create_test_store(); + let genesis_root = store.head; - // Advance time - store.time = 2 * INTERVALS_PER_SLOT; + // Same validator can only have one vote in the map (latest wins) + let mut attestations = HashMap::new(); - // Newer attestation first - let signed_attestation_new = create_signed_attestation(1, Slot(2), store.head); - - on_attestation(&mut store, signed_attestation_new, false).expect("New attestation valid"); - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - Slot(2) + // Insert a vote + attestations.insert( + ValidatorIndex(0), + create_signed_attestation(0, 1, genesis_root, 0, genesis_root, 0, genesis_root, 0), ); - // Older attestation second - let signed_attestation_old = create_signed_attestation(1, Slot(1), store.head); - - on_attestation(&mut store, signed_attestation_old, false) - .expect("Old attestation processed but ignored"); - // Should still be slot 2 - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - Slot(2) + // "Update" with same validator - only latest is kept + attestations.insert( + ValidatorIndex(0), + create_signed_attestation(0, 2, genesis_root, 0, genesis_root, 0, genesis_root, 0), ); -} - -#[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_from_block_supersedes_new() { - let mut store = create_test_store(); - let validator_idx = ValidatorIndex(1); - - // First, add attestation via gossip - let signed_attestation1 = create_signed_attestation(1, Slot(0), store.head); - on_attestation(&mut store, signed_attestation1, false).expect("Gossip attestation valid"); - - assert!(store.latest_new_attestations.contains_key(&validator_idx)); - assert!(!store.latest_known_attestations.contains_key(&validator_idx)); - // Then, add same attestation via block (on-chain) - let signed_attestation2 = create_signed_attestation(1, Slot(0), store.head); - on_attestation(&mut store, signed_attestation2, true).expect("Block attestation valid"); + // Should only have 1 attestation + assert_eq!(attestations.len(), 1); - // Should move from new to known - assert!(!store.latest_new_attestations.contains_key(&validator_idx)); - assert!(store.latest_known_attestations.contains_key(&validator_idx)); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + assert_eq!(head, genesis_root); } #[test] -#[cfg(feature = "devnet1")] -fn test_on_attestation_newer_from_block_removes_older_new() { - let mut store = create_test_store(); - let validator_idx = ValidatorIndex(1); - - // Add older attestation via gossip - let signed_attestation_gossip = create_signed_attestation(1, Slot(0), store.head); - on_attestation(&mut store, signed_attestation_gossip, false).expect("Gossip attestation valid"); - - assert_eq!( - store.latest_new_attestations[&validator_idx] - .message - .data - .slot, - Slot(0) +fn test_vote_for_unknown_block_ignored() { + let store = create_test_store(); + let genesis_root = store.head; + let unknown_root = Bytes32(ssz::H256::from_slice(&[0xff; 32])); + + // Vote for block that doesn't exist + let mut attestations = HashMap::new(); + attestations.insert( + ValidatorIndex(0), + create_signed_attestation(0, 1, unknown_root, 1, genesis_root, 0, genesis_root, 0), ); - // Add newer attestation via block (on-chain) - store.time = 1 * INTERVALS_PER_SLOT; - let signed_attestation_block = create_signed_attestation(1, Slot(1), store.head); - on_attestation(&mut store, signed_attestation_block, true).expect("Block attestation valid"); - - // New attestation should be removed (superseded by newer on-chain one) - assert!(!store.latest_new_attestations.contains_key(&validator_idx)); - assert_eq!( - store.latest_known_attestations[&validator_idx] - .message - .data - .slot, - Slot(1) - ); + let head = get_fork_choice_head(&store, genesis_root, &attestations, 0); + + // Should still return genesis since unknown block is skipped + assert_eq!(head, genesis_root); } diff --git a/lean_client/networking/Cargo.toml b/lean_client/networking/Cargo.toml index 9025e53..a45e3a9 100644 --- a/lean_client/networking/Cargo.toml +++ b/lean_client/networking/Cargo.toml @@ -5,8 +5,6 @@ edition = "2024" [features] default = [] -devnet1 = ["containers/devnet1", "env-config/devnet1"] -devnet2 = ["containers/devnet2", "env-config/devnet1"] [dependencies] env-config = { path = "../env-config", default-features = false } diff --git a/lean_client/networking/src/network/service.rs b/lean_client/networking/src/network/service.rs index a78bd93..1c97353 100644 --- a/lean_client/networking/src/network/service.rs +++ b/lean_client/networking/src/network/service.rs @@ -373,9 +373,6 @@ where } } Ok(GossipsubMessage::Attestation(signed_attestation)) => { - #[cfg(feature = "devnet1")] - let slot = signed_attestation.message.data.slot.0; - #[cfg(feature = "devnet2")] let slot = signed_attestation.message.slot.0; if let Err(err) = self @@ -598,9 +595,6 @@ where } } OutboundP2pRequest::GossipAttestation(signed_attestation) => { - #[cfg(feature = "devnet1")] - let slot = signed_attestation.message.data.slot.0; - #[cfg(feature = "devnet2")] let slot = signed_attestation.message.slot.0; match signed_attestation.to_ssz() { diff --git a/lean_client/networking/src/types.rs b/lean_client/networking/src/types.rs index bbe7cba..124ca5a 100644 --- a/lean_client/networking/src/types.rs +++ b/lean_client/networking/src/types.rs @@ -102,17 +102,6 @@ impl Display for ChainMessage { signed_block_with_attestation.message.block.slot.0 ) } - #[cfg(feature = "devnet1")] - ChainMessage::ProcessAttestation { - signed_attestation, .. - } => { - write!( - f, - "ProcessAttestation(slot={})", - signed_attestation.message.data.slot.0 - ) - } - #[cfg(feature = "devnet2")] ChainMessage::ProcessAttestation { signed_attestation, .. } => { diff --git a/lean_client/src/main.rs b/lean_client/src/main.rs index 002beca..61a4d14 100644 --- a/lean_client/src/main.rs +++ b/lean_client/src/main.rs @@ -158,7 +158,7 @@ async fn main() { .iter() .enumerate() .map(|(i, v_str)| { - let pubkey = containers::validator::BlsPublicKey::from_hex(v_str) + let pubkey = containers::public_key::PublicKey::from_hex(v_str) .expect("Invalid genesis validator pubkey"); containers::validator::Validator { pubkey, @@ -172,7 +172,7 @@ async fn main() { let num_validators = 3; let validators = (0..num_validators) .map(|i| containers::validator::Validator { - pubkey: containers::validator::BlsPublicKey::default(), + pubkey: containers::public_key::PublicKey::default(), index: Uint64(i as u64), }) .collect(); @@ -214,9 +214,6 @@ async fn main() { block: genesis_block, proposer_attestation: genesis_proposer_attestation, }, - #[cfg(feature = "devnet1")] - signature: PersistentList::default(), - #[cfg(feature = "devnet2")] signature: BlockSignatures { attestation_signatures: PersistentList::default(), proposer_signature: Signature::default(), @@ -428,9 +425,6 @@ async fn main() { if last_attestation_slot != Some(current_slot) { let attestations = vs.create_attestations(&store, Slot(current_slot)); for signed_att in attestations { - #[cfg(feature = "devnet1")] - let validator_id = signed_att.message.validator_id.0; - #[cfg(feature = "devnet2")] let validator_id = signed_att.validator_id; info!( slot = current_slot, @@ -438,19 +432,6 @@ async fn main() { "Broadcasting attestation" ); - #[cfg(feature = "devnet1")] - match on_attestation(&mut store, signed_att.clone(), false) { - Ok(()) => { - if let Err(e) = chain_outbound_sender.send( - OutboundP2pRequest::GossipAttestation(signed_att) - ) { - warn!("Failed to gossip attestation: {}", e); - } - } - Err(e) => warn!("Error processing own attestation: {}", e), - } - - #[cfg(feature = "devnet2")] match on_attestation(&mut store, signed_att.clone(), false) { Ok(()) => { if let Err(e) = chain_outbound_sender.send( @@ -546,22 +527,9 @@ async fn main() { should_gossip, .. } => { - #[cfg(feature = "devnet1")] - let att_slot = signed_attestation.message.data.slot.0; - #[cfg(feature = "devnet1")] - let source_slot = signed_attestation.message.data.source.slot.0; - #[cfg(feature = "devnet1")] - let target_slot = signed_attestation.message.data.target.slot.0; - #[cfg(feature = "devnet1")] - let validator_id = signed_attestation.message.validator_id.0; - - #[cfg(feature = "devnet2")] let att_slot = signed_attestation.message.slot.0; - #[cfg(feature = "devnet2")] let source_slot = signed_attestation.message.source.slot.0; - #[cfg(feature = "devnet2")] let target_slot = signed_attestation.message.target.slot.0; - #[cfg(feature = "devnet2")] let validator_id = signed_attestation.validator_id; info!( diff --git a/lean_client/tests/test_vectors/test_verify_signatures/test_invalid_signatures/test_invalid_signature.json b/lean_client/tests/test_vectors/test_verify_signatures/test_invalid_signatures/test_invalid_signature.json index a4cd2af..ef3fcf3 100644 --- a/lean_client/tests/test_vectors/test_verify_signatures/test_invalid_signatures/test_invalid_signature.json +++ b/lean_client/tests/test_vectors/test_verify_signatures/test_invalid_signatures/test_invalid_signature.json @@ -1,6 +1,7 @@ { "tests/consensus/devnet/verify_signatures/test_invalid_signatures.py::test_invalid_signature[fork_Devnet][fork_Devnet-verify_signatures_test]": { "network": "Devnet", + "leanEnv": "prod", "anchorState": { "config": { "genesisTime": 0 @@ -30,7 +31,7 @@ "validators": { "data": [ { - "pubkey": "0xb40ec9783b56572d99965130ad1cec4f066df43a0fd89d22d420535c62b52711cdaf7f5f696c6b1e4df66a6bab48bb51abcdd77e", + "pubkey": "0x2c7aa65d0b45c01e3ba7155997d3001613775f1e1b85fd1ab65dcd72c65e105f2552c43a311a290af56fa0262dfebe10745ea837", "index": 0 } ] @@ -47,8 +48,8 @@ "block": { "slot": 1, "proposerIndex": 0, - "parentRoot": "0x7ee509d36952a8f41f5dc5b4627487f4d523c9333ef7af2a692ae12867eeee16", - "stateRoot": "0xe9dd253c1cff8e8719113e66970f54a44c885997f3e57ba6dc993861eca4f40c", + "parentRoot": "0x438b1cbc01c3c69b14f8853c6463d28b58118798f414f3be472aae4cd77dd572", + "stateRoot": "0x38d7edaf9c08ffb285eb3e1e64456fbe430d4c4a4bab9b0bf29565b74da5c620", "body": { "attestations": { "data": [] @@ -60,52 +61,53 @@ "data": { "slot": 1, "head": { - "root": "0xf0c95e7f668652482cc00916c57ab6c84b435a29d030f682b92d95f9f3add62a", + "root": "0x6f2ebcd6e5eb1b34823a5fb5867ee63984905cc670722a01a060894b9b2cec3f", "slot": 1 }, "target": { - "root": "0xf0c95e7f668652482cc00916c57ab6c84b435a29d030f682b92d95f9f3add62a", + "root": "0x6f2ebcd6e5eb1b34823a5fb5867ee63984905cc670722a01a060894b9b2cec3f", "slot": 1 }, "source": { - "root": "0x7ee509d36952a8f41f5dc5b4627487f4d523c9333ef7af2a692ae12867eeee16", + "root": "0x438b1cbc01c3c69b14f8853c6463d28b58118798f414f3be472aae4cd77dd572", "slot": 0 } } } }, "signature": { - "data": [ - { - "path": { - "siblings": { - "data": [] - } - }, - "rho": { - "data": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ] - }, - "hashes": { + "attestationSignatures": { + "data": [] + }, + "proposerSignature": { + "path": { + "siblings": { "data": [] } + }, + "rho": { + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "hashes": { + "data": [] } - ] + } } }, "expectException": "AssertionError", "_info": { - "hash": "0x7befd11b0a26c4bfbd4c40d5b4c21d7a510cc2ed899aa3f494b7c4d9574fd7d3", + "hash": "0x8d97e6b6a601e10856ae70720ffad8cd392dab8169fe158054edac0bc0f0e49a", "comment": "`leanSpec` generated test", "testId": "tests/consensus/devnet/verify_signatures/test_invalid_signatures.py::test_invalid_signature[fork_Devnet]", - "description": "Test that invalid signatures are properly rejected during verification.\n\n Scenario\n --------\n - Single block at slot 1\n - Proposer attestation has an invalid signature\n - No additional attestations (only proposer attestation)\n\n Expected Behavior\n -----------------\n 1. Proposer's signature in SignedBlockWithAttestation is rejected\n\n Why This Matters\n ----------------\n This test verifies the negative case:\n - Signature verification actually validates cryptographic correctness\n not just structural correctness.\n - Invalid signatures are caught, not silently accepted", + "description": "Test that invalid signatures are properly rejected during verification.\n\nScenario\n--------\n- Single block at slot 1\n- Proposer attestation has an invalid signature\n- No additional attestations (only proposer attestation)\n\nExpected Behavior\n-----------------\n1. Proposer's signature in SignedBlockWithAttestation is rejected\n\nWhy This Matters\n----------------\nThis test verifies the negative case:\n- Signature verification actually validates cryptographic correctness\n not just structural correctness.\n- Invalid signatures are caught, not silently accepted", "fixtureFormat": "verify_signatures_test" } } diff --git a/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_and_attester_signatures.json b/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_and_attester_signatures.json index 306a4a2..ad8df6f 100644 --- a/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_and_attester_signatures.json +++ b/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_and_attester_signatures.json @@ -1,6 +1,7 @@ { "tests/consensus/devnet/verify_signatures/test_valid_signatures.py::test_proposer_and_attester_signatures[fork_Devnet][fork_Devnet-verify_signatures_test]": { "network": "Devnet", + "leanEnv": "prod", "anchorState": { "config": { "genesisTime": 0 @@ -30,15 +31,15 @@ "validators": { "data": [ { - "pubkey": "0xb40ec9783b56572d99965130ad1cec4f066df43a0fd89d22d420535c62b52711cdaf7f5f696c6b1e4df66a6bab48bb51abcdd77e", + "pubkey": "0x2c7aa65d0b45c01e3ba7155997d3001613775f1e1b85fd1ab65dcd72c65e105f2552c43a311a290af56fa0262dfebe10745ea837", "index": 0 }, { - "pubkey": "0xc5ed4b6fa46e83281e29201b878ad52b4a9b8a2f3c9d8017cfd9b81e8097e1372a097e62be37194c39e2f36f76e8906d2e818238", + "pubkey": "0x67047f6113b87f6aea3a0e6c108a8d7dcc4b5e479dda2a1cb79397705340bd6227f1ea63ee39c91fff4599375b749425ae65922e", "index": 1 }, { - "pubkey": "0x0cc8a378cdeb1a3c4b87156bcba3e87d31fb8c5f1c3ce74b4370001086b8bd5a502ea12d0878536b19cb6769b7285e1c0504fb3e", + "pubkey": "0x235cec31602fa34e41069453a2a56568d4c3ff62e31066659b90722e595e93255637eb0d42f98542e79c60031c2f05396bc74146", "index": 2 } ] @@ -55,31 +56,19 @@ "block": { "slot": 1, "proposerIndex": 1, - "parentRoot": "0x9e3b89451933da29e3697c588770a4d63c900e0a8af56e2a4e0777abdd355450", - "stateRoot": "0x85222dc92460f8b51fe414fb34ef9f35247653eb6036b1268261a91ba617cda8", + "parentRoot": "0x0b530309ddcb6e08a7ddbe1558a772ddea639cec05c2ddff23b597411df0745e", + "stateRoot": "0x92f8e374cbfbb7a91bee6a58e0c60cb6781bc4ba4bea028be0e46e5146b9e034", "body": { "attestations": { "data": [ { - "validatorId": 0, - "data": { - "slot": 1, - "head": { - "root": "0x0000000000000000000000000000000000000000000000000000000000000000", - "slot": 0 - }, - "target": { - "root": "0x0000000000000000000000000000000000000000000000000000000000000000", - "slot": 0 - }, - "source": { - "root": "0x0000000000000000000000000000000000000000000000000000000000000000", - "slot": 0 - } - } - }, - { - "validatorId": 2, + "aggregationBits": { + "data": [ + true, + false, + true + ] + }, "data": { "slot": 1, "head": { @@ -105,531 +94,1219 @@ "data": { "slot": 1, "head": { - "root": "0xde28efe59b19913717bb76a32611fe839ad69a4b3c0915d79c8304b35b57ddf7", + "root": "0xc18aff973b62478f234db568edc4811cd2a59885fa7f712e593b9956dde7fa5a", "slot": 1 }, "target": { - "root": "0xde28efe59b19913717bb76a32611fe839ad69a4b3c0915d79c8304b35b57ddf7", + "root": "0xc18aff973b62478f234db568edc4811cd2a59885fa7f712e593b9956dde7fa5a", "slot": 1 }, "source": { - "root": "0x9e3b89451933da29e3697c588770a4d63c900e0a8af56e2a4e0777abdd355450", + "root": "0x0b530309ddcb6e08a7ddbe1558a772ddea639cec05c2ddff23b597411df0745e", "slot": 0 } } } }, "signature": { - "data": [ - { - "path": { - "siblings": { + "attestationSignatures": { + "data": [ + { + "participants": { "data": [ - { - "data": [ - 1715018400, - 48710923, - 1748245036, - 163403131, - 924640484, - 1566519705, - 1860210712, - 1236746232 - ] - }, - { - "data": [ - 318044943, - 399009750, - 1038257959, - 729848679, - 1449298444, - 436326364, - 977163460, - 1497861895 - ] - }, - { - "data": [ - 1468833114, - 1734637349, - 1929839981, - 1267639175, - 796117685, - 47500478, - 1956344905, - 1320986094 - ] - }, - { - "data": [ - 1266260145, - 725202725, - 218929017, - 126358625, - 921715766, - 1979527002, - 1695252564, - 1220353106 - ] - }, - { - "data": [ - 298307958, - 2042817198, - 1699263182, - 1453266496, - 1023068754, - 224889272, - 2049483392, - 1399154486 - ] - }, - { - "data": [ - 1430973325, - 1579483336, - 1154958176, - 318946268, - 1584562777, - 1947187050, - 886182999, - 1154818886 - ] - }, - { - "data": [ - 1056622773, - 601147086, - 1222204938, - 264848405, - 1363314459, - 109131915, - 517301456, - 938514082 - ] - }, - { - "data": [ - 483358618, - 57732057, - 329296853, - 1352276692, - 88248225, - 1800662461, - 2098999624, - 2064134886 - ] - } + true, + false, + true ] + }, + "proofData": { + "data": "0x00" } - }, - "rho": { - "data": [ - 1133244814, - 826948562, - 694211053, - 360187930, - 342494093, - 526958919, - 549074822 - ] - }, - "hashes": { + } + ] + }, + "proposerSignature": { + "path": { + "siblings": { "data": [ { "data": [ - 780469602, - 1390643088, - 2027017214, - 1431163446, - 1529907007, - 365863100, - 1195111668, - 1880854250 + 1291233891, + 901611691, + 1515447763, + 518859210, + 691456689, + 1723616255, + 1740655893, + 1397350123 ] }, { "data": [ - 1605769376, - 741812234, - 1163184354, - 1147446555, - 871882010, - 948907942, - 551347671, - 840722750 + 1456518562, + 484811598, + 2010092021, + 282492124, + 1770180907, + 1769233778, + 494579282, + 1699827616 ] }, { "data": [ - 1181134299, - 1236421381, - 185118722, - 573142269, - 160921481, - 1510683126, - 294606954, - 1927123925 + 704838371, + 2081776305, + 559183133, + 1733361779, + 263725425, + 344472513, + 2086779080, + 1240527530 ] }, { "data": [ - 1347741188, - 1460449909, - 596275218, - 1289700342, - 1411024602, - 1833568587, - 1711725928, - 6783578 + 1039415328, + 308461932, + 2043838459, + 611597666, + 1580071319, + 1516124162, + 698977291, + 1585930624 ] - } - ] - } - }, - { - "path": { - "siblings": { - "data": [ - { - "data": [ - 245987955, - 1574460560, - 1525707632, - 123960916, - 1086053397, - 2080663024, - 366379460, - 1998029054 - ] - }, - { - "data": [ - 2019630499, - 1360353298, - 1175664713, - 1796753426, - 1565903239, - 1735318974, - 1982905657, - 614050054 - ] - }, - { - "data": [ - 1454884048, - 454543812, - 1015264795, - 356242932, - 501670992, - 463708403, - 459704849, - 1579831167 - ] - }, - { - "data": [ - 665199378, - 1891601081, - 858563106, - 596194897, - 332003681, - 2038255694, - 1339684636, - 1387152804 - ] - }, - { - "data": [ - 543025152, - 786491463, - 1123029687, - 1264918908, - 984563024, - 1188331300, - 680922823, - 1816896828 - ] - }, - { - "data": [ - 1326664843, - 1319602875, - 1528705430, - 227865620, - 708371624, - 1637263589, - 1037139620, - 1485301417 - ] - }, - { - "data": [ - 1192630398, - 484414116, - 313965305, - 196703586, - 1501710664, - 1683049995, - 904524380, - 1174797428 - ] - }, - { - "data": [ - 2063074693, - 101123966, - 607293596, - 477669941, - 1941778158, - 1668669356, - 1179339209, - 864743993 - ] - } - ] - } - }, - "rho": { - "data": [ - 2045446833, - 446323957, - 1182968240, - 517145103, - 1767538833, - 631690441, - 1778446117 - ] - }, - "hashes": { - "data": [ + }, { "data": [ - 423057391, - 388700883, - 1083440537, - 359923932, - 388595029, - 1736053252, - 350530532, - 731868998 + 250662127, + 1008190943, + 983708486, + 1247986374, + 1886580775, + 1647509743, + 1550488627, + 1260597451 ] }, { "data": [ - 1090832151, - 533474659, - 809763430, - 507768385, - 98359830, - 1734162267, - 1243455951, - 1715081891 + 416883050, + 188953242, + 1182024076, + 59202244, + 1978179518, + 1739410190, + 679526947, + 861617775 ] }, { "data": [ - 634986620, - 1063163068, - 1040321224, - 1267702389, - 627871860, - 228913093, - 921478258, - 1324923786 + 1378484229, + 241452936, + 163273125, + 436861107, + 388667496, + 1797577532, + 443869040, + 906552846 ] }, { "data": [ - 786393727, - 991293795, - 1375401462, - 1980764312, - 680859391, - 1131959594, - 396370528, - 1759770981 + 799696786, + 1508418299, + 1856736097, + 1058842462, + 269359710, + 1099230447, + 749521578, + 520853695 ] - } - ] - } - }, - { - "path": { - "siblings": { - "data": [ - { - "data": [ - 1331122823, - 1643080471, - 525470619, - 2031212805, - 2098748101, - 1243499988, - 4104831, - 2063254342 - ] - }, - { - "data": [ - 744813374, - 813288082, - 613270960, - 679006507, - 533419743, - 993872253, - 286847881, - 1451300746 - ] - }, - { - "data": [ - 11079834, - 1639687748, - 1929217599, - 202276490, - 40122498, - 1566411931, - 675580467, - 904231754 - ] - }, - { - "data": [ - 2020782635, - 1574539412, - 1138761573, - 422111916, - 1389054530, - 1510501223, - 197381584, - 402775535 - ] - }, - { - "data": [ - 264438681, - 1087279288, - 263416095, - 1860660250, - 1429146526, - 2094709316, - 867692041, - 1662763959 - ] - }, - { - "data": [ - 544815286, - 311733778, - 799237190, - 2102805408, - 1661418854, - 1157854114, - 1855929130, - 744137327 - ] - }, - { - "data": [ - 1462886854, - 1889836598, - 2029637687, - 53625032, - 673267735, - 2047374486, - 49833992, - 1921530767 - ] - }, - { - "data": [ - 110758107, - 12788259, - 1920648715, - 413804969, - 419452419, - 1819780529, - 1147494825, - 526331496 - ] - } - ] - } - }, - "rho": { - "data": [ - 754265996, - 879244860, - 1486259768, - 2046100849, - 517389142, - 1321641711, - 698992751 - ] - }, - "hashes": { - "data": [ + }, { "data": [ - 1782166851, - 755483088, - 705928616, - 1054809239, - 1035991143, - 598933101, - 107624567, - 580522477 + 439422050, + 1286915872, + 1358195170, + 840970194, + 1786302274, + 893515818, + 370457729, + 1427474800 ] }, { "data": [ - 191975122, - 1845573414, - 1060661118, - 3844096, - 767890828, - 1256682430, - 1322161263, - 1960290303 + 2007504140, + 418866321, + 198684106, + 1996996870, + 1261455552, + 2118739919, + 56436890, + 1806594952 ] }, { "data": [ - 1186545818, - 1781761501, - 1739225478, - 720736896, - 819060565, - 601088943, - 1836159403, - 4774455 + 128324476, + 61519552, + 113352202, + 1839954511, + 2112962791, + 1831734346, + 1400107873, + 849776052 ] }, { "data": [ - 523507152, - 640464516, - 1715695303, - 1682124987, - 1442709818, - 495961733, - 1030632883, - 2056248017 + 694706906, + 2057189854, + 1419202856, + 2020454400, + 1916412209, + 304600413, + 1119212112, + 988476852 + ] + }, + { + "data": [ + 1607491401, + 916185160, + 150839959, + 1915584536, + 1192630676, + 166752571, + 44618577, + 1961530862 + ] + }, + { + "data": [ + 1949287225, + 1766709295, + 928506169, + 833212136, + 35771750, + 71835570, + 1852857681, + 1205452729 + ] + }, + { + "data": [ + 1644443152, + 1520132256, + 1370044265, + 851862297, + 261020286, + 1001533477, + 571576626, + 907308311 + ] + }, + { + "data": [ + 1557846841, + 210200770, + 685212717, + 1586976910, + 463743886, + 395493034, + 1562290362, + 1016157604 + ] + }, + { + "data": [ + 208831676, + 1180089898, + 2064964824, + 1411007716, + 1673605982, + 1643551528, + 1539845891, + 704493341 + ] + }, + { + "data": [ + 895079925, + 877096130, + 2081347331, + 124656629, + 1179296144, + 1491205760, + 356412314, + 926452265 + ] + }, + { + "data": [ + 1422286144, + 984088526, + 1135304910, + 162305405, + 1064769342, + 1110991338, + 104215457, + 1422827345 + ] + }, + { + "data": [ + 912789203, + 2010420420, + 429286304, + 295855493, + 2084240709, + 1193367228, + 1021205972, + 560375846 + ] + }, + { + "data": [ + 489627247, + 396093595, + 475714912, + 573904495, + 382358549, + 668148792, + 1416579671, + 1444313453 + ] + }, + { + "data": [ + 1345967333, + 723445075, + 2048740831, + 153155071, + 10838758, + 1236457738, + 18985351, + 1138484833 + ] + }, + { + "data": [ + 993666071, + 473860152, + 482974488, + 1244638895, + 1287107597, + 1492708811, + 1976127099, + 653628523 + ] + }, + { + "data": [ + 791701066, + 885184677, + 106955182, + 1572481724, + 1456627534, + 1452427937, + 490533016, + 991293345 + ] + }, + { + "data": [ + 578639661, + 1631171923, + 268843612, + 1788996364, + 1746080381, + 1046103170, + 455298193, + 562115509 + ] + }, + { + "data": [ + 235948816, + 1018300141, + 1002498336, + 201831066, + 1124789148, + 1994905284, + 561014981, + 1257286951 + ] + }, + { + "data": [ + 1045385620, + 1058192257, + 938515492, + 573527403, + 989948080, + 1342850602, + 1832637791, + 358929324 + ] + }, + { + "data": [ + 1902152809, + 252599905, + 623219565, + 758434560, + 640896011, + 207032991, + 835792870, + 1665795896 + ] + }, + { + "data": [ + 723715685, + 587576367, + 853971724, + 1144944495, + 873175376, + 498689849, + 43297292, + 819091873 + ] + }, + { + "data": [ + 280016656, + 437742428, + 255947140, + 349343920, + 1615039346, + 1488983802, + 1389523623, + 1912297556 + ] + }, + { + "data": [ + 901881368, + 1526942608, + 852049680, + 731288118, + 661508501, + 2010590000, + 868332352, + 1726397935 + ] + }, + { + "data": [ + 1349685696, + 2130099561, + 1462674991, + 1479393751, + 1013840091, + 1746802826, + 422993280, + 544780634 ] } ] } + }, + "rho": { + "data": [ + 1708063334, + 500631902, + 912463888, + 1859022035, + 2093407176, + 589622141, + 421358791 + ] + }, + "hashes": { + "data": [ + { + "data": [ + 1258012936, + 1759805387, + 1282523131, + 1087625667, + 2066885140, + 347413941, + 912179848, + 1182795675 + ] + }, + { + "data": [ + 2120621302, + 1146272237, + 220452956, + 1654122849, + 667076721, + 647293161, + 1473822684, + 610014272 + ] + }, + { + "data": [ + 588127966, + 1623774910, + 1981302286, + 1654815645, + 1739263748, + 476540846, + 1430988502, + 371554021 + ] + }, + { + "data": [ + 147575978, + 584959873, + 482088843, + 547413274, + 878230111, + 947438052, + 2065600800, + 1725116311 + ] + }, + { + "data": [ + 782401603, + 181111304, + 74795908, + 1495556562, + 2014424927, + 2103029287, + 626628827, + 915290649 + ] + }, + { + "data": [ + 1386657693, + 226764475, + 170886560, + 1391287227, + 241686273, + 1439085926, + 1299696477, + 224457038 + ] + }, + { + "data": [ + 156077062, + 984761927, + 636114116, + 2128285193, + 804007702, + 145764472, + 1829941080, + 897763155 + ] + }, + { + "data": [ + 832369921, + 238965180, + 160945039, + 1145687264, + 1389349131, + 1691977522, + 979195797, + 524772744 + ] + }, + { + "data": [ + 1305584230, + 1558936225, + 439307137, + 1771754638, + 875067293, + 1165888195, + 1802707435, + 1814799779 + ] + }, + { + "data": [ + 1584322090, + 1505637739, + 1431439512, + 2049043333, + 2031336532, + 1994220632, + 535798250, + 1003107923 + ] + }, + { + "data": [ + 536723909, + 1389453682, + 407278690, + 1778319839, + 1777727737, + 1948491210, + 1489215087, + 94425788 + ] + }, + { + "data": [ + 1939722636, + 1550150715, + 601277655, + 1185348003, + 205771634, + 1131394685, + 1434984545, + 971649311 + ] + }, + { + "data": [ + 1895618555, + 1937165542, + 1924755874, + 1626462217, + 1247425034, + 1370792545, + 10993310, + 259827571 + ] + }, + { + "data": [ + 960895278, + 1441935738, + 564122556, + 476582278, + 1152905344, + 187751495, + 1256558054, + 1184773204 + ] + }, + { + "data": [ + 1142655319, + 976140656, + 1227333160, + 2129498013, + 867861598, + 1790717609, + 866871241, + 1774362003 + ] + }, + { + "data": [ + 310323031, + 1437920355, + 878272104, + 722971220, + 1015159963, + 409582600, + 512066076, + 854680398 + ] + }, + { + "data": [ + 1078600753, + 1684508279, + 316224361, + 1222713314, + 336701486, + 1165314551, + 997088307, + 1438291675 + ] + }, + { + "data": [ + 1317260158, + 1861218639, + 1224575160, + 421550610, + 765270751, + 1827053147, + 289080869, + 538182924 + ] + }, + { + "data": [ + 191317502, + 911206352, + 127176973, + 1283200174, + 122086992, + 2069722074, + 1696651747, + 1805703619 + ] + }, + { + "data": [ + 1585522895, + 580813326, + 1019407832, + 475961126, + 2007366427, + 808496979, + 1181091986, + 697679912 + ] + }, + { + "data": [ + 1512587243, + 1963077188, + 1954992331, + 1545360150, + 1178760012, + 1515958126, + 705452917, + 2114456876 + ] + }, + { + "data": [ + 2049583212, + 1094272227, + 1039456282, + 787530139, + 1640279372, + 1330559514, + 240146549, + 1313599913 + ] + }, + { + "data": [ + 1716678544, + 878739389, + 47637648, + 1124863294, + 1855735812, + 648435874, + 1372962920, + 1357760622 + ] + }, + { + "data": [ + 1700444351, + 164566502, + 397969528, + 335079975, + 293991016, + 1078783808, + 326444266, + 1217021268 + ] + }, + { + "data": [ + 545406936, + 53426137, + 424114470, + 1111280438, + 618160273, + 1802384583, + 1667812411, + 783526014 + ] + }, + { + "data": [ + 305021847, + 844199180, + 1053002307, + 573437770, + 1003609966, + 752594751, + 1962990311, + 1014845114 + ] + }, + { + "data": [ + 1726913971, + 580369471, + 1458334500, + 153335379, + 781417921, + 1461588083, + 1878087297, + 1976620351 + ] + }, + { + "data": [ + 1342240957, + 1951347581, + 927989919, + 979795407, + 446565280, + 1833560107, + 1611077456, + 1708982869 + ] + }, + { + "data": [ + 1555733412, + 183459902, + 1845209457, + 294206894, + 487746799, + 1581300684, + 1098936144, + 1463828258 + ] + }, + { + "data": [ + 1627490533, + 1198683786, + 347829445, + 868233249, + 668381542, + 1667170279, + 1843656481, + 2118868916 + ] + }, + { + "data": [ + 25335971, + 1947476635, + 1202969731, + 1324303434, + 840681315, + 1530295647, + 73829885, + 2084868034 + ] + }, + { + "data": [ + 1384538139, + 1830543638, + 1993528212, + 829245670, + 987182524, + 1984193286, + 1630629317, + 671245330 + ] + }, + { + "data": [ + 1350732214, + 1458554923, + 1967947691, + 1326432866, + 2116862031, + 1830754813, + 1993865530, + 1629953044 + ] + }, + { + "data": [ + 1146542130, + 280817620, + 386152006, + 1428960819, + 1210084215, + 452674181, + 14651754, + 888508333 + ] + }, + { + "data": [ + 1560045092, + 1296963539, + 284985770, + 1434652130, + 229612754, + 1450040209, + 1958058095, + 1037043393 + ] + }, + { + "data": [ + 2020927885, + 18361940, + 653582762, + 1686120847, + 1597265575, + 28912714, + 443462147, + 870096418 + ] + }, + { + "data": [ + 1695586982, + 3373096, + 104141097, + 1042336897, + 994168241, + 1453130775, + 511038748, + 965536893 + ] + }, + { + "data": [ + 1605236949, + 127566776, + 21238712, + 434461941, + 1022175801, + 1240317127, + 1122138289, + 1008747176 + ] + }, + { + "data": [ + 2102377138, + 1530162129, + 909575023, + 1237305669, + 511960395, + 2038778105, + 287638646, + 545475552 + ] + }, + { + "data": [ + 123969828, + 595339923, + 285763600, + 1913417170, + 1555092419, + 2103200507, + 568212685, + 726567164 + ] + }, + { + "data": [ + 811207331, + 1566057452, + 346845733, + 1405783110, + 296074182, + 686180472, + 1562194090, + 1331754094 + ] + }, + { + "data": [ + 1195458345, + 1015303239, + 1769326913, + 1798476475, + 1959426322, + 263548056, + 1086173773, + 616986172 + ] + }, + { + "data": [ + 1679743849, + 267745726, + 813229082, + 1802821399, + 1106957379, + 681723311, + 38255328, + 119212296 + ] + }, + { + "data": [ + 538262759, + 561853307, + 1220138601, + 648920532, + 96368560, + 1848614699, + 564258293, + 877652518 + ] + }, + { + "data": [ + 236889586, + 1945633712, + 888366492, + 1363228903, + 1535081845, + 1843716973, + 870982648, + 2828223 + ] + }, + { + "data": [ + 1813717520, + 157322480, + 353732586, + 1058663683, + 767198951, + 185375665, + 1574055980, + 434808322 + ] + }, + { + "data": [ + 379825016, + 1815775610, + 718153065, + 878888419, + 2004655473, + 329280888, + 1716255418, + 2005381073 + ] + }, + { + "data": [ + 1590446408, + 1173249277, + 2092549673, + 208887188, + 912239485, + 796567703, + 274938304, + 390283874 + ] + }, + { + "data": [ + 779263010, + 747574741, + 1434583711, + 1620835829, + 1551673235, + 1284998639, + 679093843, + 1406669023 + ] + }, + { + "data": [ + 1291148586, + 1081265798, + 1526996412, + 391781492, + 1711281276, + 1313014433, + 1384242624, + 623027609 + ] + }, + { + "data": [ + 953015683, + 934645423, + 1771313714, + 470438654, + 1645632988, + 1239732071, + 688694286, + 1693593789 + ] + }, + { + "data": [ + 1677902343, + 45276613, + 2103891579, + 1112027086, + 161866262, + 1434591076, + 1951598120, + 772846762 + ] + }, + { + "data": [ + 551705762, + 1871931766, + 1065697665, + 283086151, + 2053411512, + 1094840383, + 1766312832, + 256750162 + ] + }, + { + "data": [ + 429680689, + 125824827, + 1965715718, + 2057352154, + 1776082615, + 2118510694, + 176499827, + 1212838505 + ] + }, + { + "data": [ + 1556722792, + 1298468122, + 657266497, + 1348176792, + 97032780, + 432903656, + 1713460397, + 236087742 + ] + }, + { + "data": [ + 268618238, + 781464872, + 1629401850, + 2084984500, + 1651362468, + 871068115, + 1722302961, + 712459750 + ] + }, + { + "data": [ + 1361188658, + 539241318, + 1966654298, + 1775313608, + 143728868, + 1546419295, + 665328088, + 2041591993 + ] + }, + { + "data": [ + 1660687505, + 535693535, + 1188238224, + 1910372027, + 441895189, + 208597526, + 1637375248, + 1439508567 + ] + }, + { + "data": [ + 1139697001, + 555025515, + 1728548969, + 1245647761, + 1604041527, + 1752808772, + 1419902779, + 1640507729 + ] + }, + { + "data": [ + 1960240298, + 666218643, + 1280441627, + 1940051826, + 1775703419, + 598652016, + 140095253, + 829013884 + ] + }, + { + "data": [ + 303840967, + 363183783, + 2079196084, + 1077588941, + 1843884294, + 229585661, + 84469314, + 1923645935 + ] + }, + { + "data": [ + 1487940169, + 725658218, + 1422188831, + 2055497525, + 1396855667, + 456348791, + 1027525060, + 1026406513 + ] + }, + { + "data": [ + 1435497940, + 276128380, + 1036933776, + 678450869, + 1197285788, + 816650348, + 240096989, + 898816825 + ] + }, + { + "data": [ + 248028907, + 940423932, + 2017860464, + 1112538086, + 866251675, + 135676603, + 1729849157, + 73108520 + ] + } + ] } - ] + } } }, "_info": { - "hash": "0x85747e3680f0686a8ee60ad657c0ea06878219df31e568b1d6ae6c58e5007fe7", + "hash": "0x879f5976fdea34463877eebb31b5f5c7c966720d6a103273499e11d7dec42c05", "comment": "`leanSpec` generated test", "testId": "tests/consensus/devnet/verify_signatures/test_valid_signatures.py::test_proposer_and_attester_signatures[fork_Devnet]", - "description": "Test valid proposer and attester signatures in SignedBlockWithAttestation.\n\n Scenario\n --------\n - Single block at slot 1\n - 3 validators in the genesis state\n - 2 additional attestations from validators 0 and 2 (in addition to proposer)\n - Verifies that all signatures are generated correctly\n\n Expected Behavior\n -----------------\n 1. Proposer's signature in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n 2. Attester's signatures in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n\n Why This Matters\n ----------------\n This test verifies multi-validator signature scenarios:\n - Multiple XMSS keys are generated for different validators\n - Attestations from non-proposer validators are correctly verified\n - Signature aggregation works with multiple attestations (signature positions are correct)", + "description": "Test valid proposer and attester signatures in SignedBlockWithAttestation.\n\nScenario\n--------\n- Single block at slot 1\n- 3 validators in the genesis state\n- 2 additional attestations from validators 0 and 2 (in addition to proposer)\n- Verifies that all signatures are generated correctly\n\nExpected Behavior\n-----------------\n1. Proposer's signature in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n2. Attester's signatures in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n\nWhy This Matters\n----------------\nThis test verifies multi-validator signature scenarios:\n- Multiple XMSS keys are generated for different validators\n- Attestations from non-proposer validators are correctly verified\n- Signature aggregation works with multiple attestations (signature positions are correct)", "fixtureFormat": "verify_signatures_test" } } diff --git a/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_signature.json b/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_signature.json index 96dc2bb..14fd63e 100644 --- a/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_signature.json +++ b/lean_client/tests/test_vectors/test_verify_signatures/test_valid_signatures/test_proposer_signature.json @@ -1,6 +1,7 @@ { "tests/consensus/devnet/verify_signatures/test_valid_signatures.py::test_proposer_signature[fork_Devnet][fork_Devnet-verify_signatures_test]": { "network": "Devnet", + "leanEnv": "prod", "anchorState": { "config": { "genesisTime": 0 @@ -30,11 +31,11 @@ "validators": { "data": [ { - "pubkey": "0xb40ec9783b56572d99965130ad1cec4f066df43a0fd89d22d420535c62b52711cdaf7f5f696c6b1e4df66a6bab48bb51abcdd77e", + "pubkey": "0x2c7aa65d0b45c01e3ba7155997d3001613775f1e1b85fd1ab65dcd72c65e105f2552c43a311a290af56fa0262dfebe10745ea837", "index": 0 }, { - "pubkey": "0xc5ed4b6fa46e83281e29201b878ad52b4a9b8a2f3c9d8017cfd9b81e8097e1372a097e62be37194c39e2f36f76e8906d2e818238", + "pubkey": "0x67047f6113b87f6aea3a0e6c108a8d7dcc4b5e479dda2a1cb79397705340bd6227f1ea63ee39c91fff4599375b749425ae65922e", "index": 1 } ] @@ -51,8 +52,8 @@ "block": { "slot": 1, "proposerIndex": 1, - "parentRoot": "0x2a63e3011f0b83fd7134abc2829e011dd57789ea5e44c1766d803ed877d391b7", - "stateRoot": "0xe4c2b970f38f0cd022017f58e06cff629700138c939cd5ddb19906c0de2f1c46", + "parentRoot": "0x25f6beaf778b5d1cad9d33b9bc39f7d2ed521cb3f03d8b086b6af8408617635a", + "stateRoot": "0xf8947796ac01c5ab946e51321a263b897421f03497964007001ca86e15ee4c8d", "body": { "attestations": { "data": [] @@ -64,197 +65,1206 @@ "data": { "slot": 1, "head": { - "root": "0xc185996796d67eb0a7d76c91209a0fee3f0e168ec2f571dafba2cc27377081a9", + "root": "0x16e2e465a92b9d884438754ce66e3b04e25bce25bd76ab182f8dba7502a4aca6", "slot": 1 }, "target": { - "root": "0xc185996796d67eb0a7d76c91209a0fee3f0e168ec2f571dafba2cc27377081a9", + "root": "0x16e2e465a92b9d884438754ce66e3b04e25bce25bd76ab182f8dba7502a4aca6", "slot": 1 }, "source": { - "root": "0x2a63e3011f0b83fd7134abc2829e011dd57789ea5e44c1766d803ed877d391b7", + "root": "0x25f6beaf778b5d1cad9d33b9bc39f7d2ed521cb3f03d8b086b6af8408617635a", "slot": 0 } } } }, "signature": { - "data": [ - { - "path": { - "siblings": { - "data": [ - { - "data": [ - 1331122823, - 1643080471, - 525470619, - 2031212805, - 2098748101, - 1243499988, - 4104831, - 2063254342 - ] - }, - { - "data": [ - 744813374, - 813288082, - 613270960, - 679006507, - 533419743, - 993872253, - 286847881, - 1451300746 - ] - }, - { - "data": [ - 11079834, - 1639687748, - 1929217599, - 202276490, - 40122498, - 1566411931, - 675580467, - 904231754 - ] - }, - { - "data": [ - 2020782635, - 1574539412, - 1138761573, - 422111916, - 1389054530, - 1510501223, - 197381584, - 402775535 - ] - }, - { - "data": [ - 264438681, - 1087279288, - 263416095, - 1860660250, - 1429146526, - 2094709316, - 867692041, - 1662763959 - ] - }, - { - "data": [ - 544815286, - 311733778, - 799237190, - 2102805408, - 1661418854, - 1157854114, - 1855929130, - 744137327 - ] - }, - { - "data": [ - 1462886854, - 1889836598, - 2029637687, - 53625032, - 673267735, - 2047374486, - 49833992, - 1921530767 - ] - }, - { - "data": [ - 110758107, - 12788259, - 1920648715, - 413804969, - 419452419, - 1819780529, - 1147494825, - 526331496 - ] - } - ] - } - }, - "rho": { - "data": [ - 822936441, - 1691280078, - 133155482, - 639707584, - 188230399, - 56199352, - 1344091725 - ] - }, - "hashes": { + "attestationSignatures": { + "data": [] + }, + "proposerSignature": { + "path": { + "siblings": { "data": [ { "data": [ - 1313453765, - 509566156, - 1191425365, - 1394696109, - 578431309, - 849344093, - 1900015578, - 1268473644 + 1291233891, + 901611691, + 1515447763, + 518859210, + 691456689, + 1723616255, + 1740655893, + 1397350123 + ] + }, + { + "data": [ + 1456518562, + 484811598, + 2010092021, + 282492124, + 1770180907, + 1769233778, + 494579282, + 1699827616 + ] + }, + { + "data": [ + 704838371, + 2081776305, + 559183133, + 1733361779, + 263725425, + 344472513, + 2086779080, + 1240527530 + ] + }, + { + "data": [ + 1039415328, + 308461932, + 2043838459, + 611597666, + 1580071319, + 1516124162, + 698977291, + 1585930624 + ] + }, + { + "data": [ + 250662127, + 1008190943, + 983708486, + 1247986374, + 1886580775, + 1647509743, + 1550488627, + 1260597451 + ] + }, + { + "data": [ + 416883050, + 188953242, + 1182024076, + 59202244, + 1978179518, + 1739410190, + 679526947, + 861617775 + ] + }, + { + "data": [ + 1378484229, + 241452936, + 163273125, + 436861107, + 388667496, + 1797577532, + 443869040, + 906552846 + ] + }, + { + "data": [ + 799696786, + 1508418299, + 1856736097, + 1058842462, + 269359710, + 1099230447, + 749521578, + 520853695 + ] + }, + { + "data": [ + 439422050, + 1286915872, + 1358195170, + 840970194, + 1786302274, + 893515818, + 370457729, + 1427474800 + ] + }, + { + "data": [ + 2007504140, + 418866321, + 198684106, + 1996996870, + 1261455552, + 2118739919, + 56436890, + 1806594952 + ] + }, + { + "data": [ + 128324476, + 61519552, + 113352202, + 1839954511, + 2112962791, + 1831734346, + 1400107873, + 849776052 + ] + }, + { + "data": [ + 694706906, + 2057189854, + 1419202856, + 2020454400, + 1916412209, + 304600413, + 1119212112, + 988476852 + ] + }, + { + "data": [ + 1607491401, + 916185160, + 150839959, + 1915584536, + 1192630676, + 166752571, + 44618577, + 1961530862 + ] + }, + { + "data": [ + 1949287225, + 1766709295, + 928506169, + 833212136, + 35771750, + 71835570, + 1852857681, + 1205452729 ] }, { "data": [ - 1366485659, - 1605565996, - 671158305, - 1622538715, - 394010590, - 1825233468, - 1018705001, - 1071542400 + 1644443152, + 1520132256, + 1370044265, + 851862297, + 261020286, + 1001533477, + 571576626, + 907308311 ] }, { "data": [ - 1186545818, - 1781761501, - 1739225478, - 720736896, - 819060565, - 601088943, - 1836159403, - 4774455 + 1557846841, + 210200770, + 685212717, + 1586976910, + 463743886, + 395493034, + 1562290362, + 1016157604 ] }, { "data": [ - 462810434, - 24773107, - 467766012, - 716711919, - 1288811897, - 436514417, - 852671571, - 2110518257 + 208831676, + 1180089898, + 2064964824, + 1411007716, + 1673605982, + 1643551528, + 1539845891, + 704493341 + ] + }, + { + "data": [ + 895079925, + 877096130, + 2081347331, + 124656629, + 1179296144, + 1491205760, + 356412314, + 926452265 + ] + }, + { + "data": [ + 1422286144, + 984088526, + 1135304910, + 162305405, + 1064769342, + 1110991338, + 104215457, + 1422827345 + ] + }, + { + "data": [ + 912789203, + 2010420420, + 429286304, + 295855493, + 2084240709, + 1193367228, + 1021205972, + 560375846 + ] + }, + { + "data": [ + 489627247, + 396093595, + 475714912, + 573904495, + 382358549, + 668148792, + 1416579671, + 1444313453 + ] + }, + { + "data": [ + 1345967333, + 723445075, + 2048740831, + 153155071, + 10838758, + 1236457738, + 18985351, + 1138484833 + ] + }, + { + "data": [ + 993666071, + 473860152, + 482974488, + 1244638895, + 1287107597, + 1492708811, + 1976127099, + 653628523 + ] + }, + { + "data": [ + 791701066, + 885184677, + 106955182, + 1572481724, + 1456627534, + 1452427937, + 490533016, + 991293345 + ] + }, + { + "data": [ + 578639661, + 1631171923, + 268843612, + 1788996364, + 1746080381, + 1046103170, + 455298193, + 562115509 + ] + }, + { + "data": [ + 235948816, + 1018300141, + 1002498336, + 201831066, + 1124789148, + 1994905284, + 561014981, + 1257286951 + ] + }, + { + "data": [ + 1045385620, + 1058192257, + 938515492, + 573527403, + 989948080, + 1342850602, + 1832637791, + 358929324 + ] + }, + { + "data": [ + 1902152809, + 252599905, + 623219565, + 758434560, + 640896011, + 207032991, + 835792870, + 1665795896 + ] + }, + { + "data": [ + 723715685, + 587576367, + 853971724, + 1144944495, + 873175376, + 498689849, + 43297292, + 819091873 + ] + }, + { + "data": [ + 280016656, + 437742428, + 255947140, + 349343920, + 1615039346, + 1488983802, + 1389523623, + 1912297556 + ] + }, + { + "data": [ + 901881368, + 1526942608, + 852049680, + 731288118, + 661508501, + 2010590000, + 868332352, + 1726397935 + ] + }, + { + "data": [ + 1349685696, + 2130099561, + 1462674991, + 1479393751, + 1013840091, + 1746802826, + 422993280, + 544780634 ] } ] } + }, + "rho": { + "data": [ + 716203521, + 581420617, + 1286685526, + 1194695558, + 1641401137, + 1997614743, + 496514183 + ] + }, + "hashes": { + "data": [ + { + "data": [ + 1007398320, + 1251646982, + 1612967266, + 537381478, + 93386731, + 1083280595, + 1721356609, + 1286371566 + ] + }, + { + "data": [ + 170535041, + 1162715015, + 694935546, + 306154186, + 1318628375, + 1972574425, + 1164163541, + 93198021 + ] + }, + { + "data": [ + 975961833, + 531824562, + 2035811416, + 1364843342, + 322668550, + 458809832, + 349604618, + 2092803528 + ] + }, + { + "data": [ + 654742765, + 1159278164, + 1972996313, + 523872121, + 1805821337, + 124485604, + 1193056330, + 807117735 + ] + }, + { + "data": [ + 919568569, + 2007737629, + 1957321079, + 1976407600, + 533410046, + 2111488436, + 1620339375, + 801239907 + ] + }, + { + "data": [ + 1248809976, + 857619471, + 1558705607, + 1000070635, + 536413566, + 1646494315, + 1742462035, + 361896064 + ] + }, + { + "data": [ + 1984415707, + 2120523866, + 460541868, + 1401766703, + 191855557, + 62277793, + 839423381, + 1933336793 + ] + }, + { + "data": [ + 720993176, + 565557239, + 144294658, + 1965029513, + 1320601105, + 1033475156, + 924580775, + 1891983182 + ] + }, + { + "data": [ + 2107882818, + 42805544, + 1038376944, + 1485088008, + 413502243, + 595063755, + 208296301, + 137522358 + ] + }, + { + "data": [ + 1584322090, + 1505637739, + 1431439512, + 2049043333, + 2031336532, + 1994220632, + 535798250, + 1003107923 + ] + }, + { + "data": [ + 536723909, + 1389453682, + 407278690, + 1778319839, + 1777727737, + 1948491210, + 1489215087, + 94425788 + ] + }, + { + "data": [ + 1472227604, + 777167222, + 1014016292, + 2108428626, + 1572854930, + 936945519, + 1087148360, + 239564935 + ] + }, + { + "data": [ + 1895618555, + 1937165542, + 1924755874, + 1626462217, + 1247425034, + 1370792545, + 10993310, + 259827571 + ] + }, + { + "data": [ + 1633133337, + 1030126208, + 1634656506, + 1714432182, + 923722773, + 1592896262, + 1045605719, + 1004996167 + ] + }, + { + "data": [ + 1142655319, + 976140656, + 1227333160, + 2129498013, + 867861598, + 1790717609, + 866871241, + 1774362003 + ] + }, + { + "data": [ + 310323031, + 1437920355, + 878272104, + 722971220, + 1015159963, + 409582600, + 512066076, + 854680398 + ] + }, + { + "data": [ + 562156248, + 407660686, + 123830526, + 1475625115, + 2009710949, + 611229674, + 227976023, + 1979398321 + ] + }, + { + "data": [ + 1317260158, + 1861218639, + 1224575160, + 421550610, + 765270751, + 1827053147, + 289080869, + 538182924 + ] + }, + { + "data": [ + 1373100955, + 1325801240, + 242486397, + 1594220983, + 1224055938, + 1039685355, + 1369882156, + 2105201870 + ] + }, + { + "data": [ + 882140143, + 972100438, + 1911957230, + 1132707040, + 1927731179, + 1221461913, + 7060907, + 1965761976 + ] + }, + { + "data": [ + 1512587243, + 1963077188, + 1954992331, + 1545360150, + 1178760012, + 1515958126, + 705452917, + 2114456876 + ] + }, + { + "data": [ + 2049583212, + 1094272227, + 1039456282, + 787530139, + 1640279372, + 1330559514, + 240146549, + 1313599913 + ] + }, + { + "data": [ + 1405173867, + 1816274123, + 914189354, + 1390194868, + 1875873291, + 729884524, + 1391291848, + 712390226 + ] + }, + { + "data": [ + 598163318, + 669166894, + 2095183582, + 2020485494, + 1353088122, + 288048132, + 219781410, + 2002759719 + ] + }, + { + "data": [ + 545406936, + 53426137, + 424114470, + 1111280438, + 618160273, + 1802384583, + 1667812411, + 783526014 + ] + }, + { + "data": [ + 880153551, + 1844784642, + 958326447, + 604503257, + 1004388263, + 410341879, + 1891463524, + 1293918805 + ] + }, + { + "data": [ + 501558010, + 740311244, + 1050231400, + 540493697, + 1939025889, + 865101441, + 450874438, + 556623367 + ] + }, + { + "data": [ + 14617393, + 1741137626, + 1232402672, + 1297835855, + 180473396, + 296331666, + 678243236, + 1349472775 + ] + }, + { + "data": [ + 1555733412, + 183459902, + 1845209457, + 294206894, + 487746799, + 1581300684, + 1098936144, + 1463828258 + ] + }, + { + "data": [ + 359145510, + 91313123, + 46174230, + 237526521, + 2072622391, + 1873031895, + 797080303, + 1306795272 + ] + }, + { + "data": [ + 150143807, + 1240854373, + 2107166892, + 1239041875, + 199965884, + 143519120, + 877927432, + 463537849 + ] + }, + { + "data": [ + 1279960983, + 1843209453, + 1821049971, + 1629719253, + 1678547126, + 1467417183, + 2126155977, + 29064399 + ] + }, + { + "data": [ + 2087574544, + 1055748285, + 1878614231, + 639879396, + 1324171225, + 673620585, + 1341934876, + 558856819 + ] + }, + { + "data": [ + 1584783469, + 1012855306, + 401348507, + 2091865749, + 1821226690, + 741147898, + 257249483, + 1263361762 + ] + }, + { + "data": [ + 1067822535, + 1891280943, + 813430117, + 1276106738, + 1193107083, + 1477156918, + 1539220620, + 96504038 + ] + }, + { + "data": [ + 2020927885, + 18361940, + 653582762, + 1686120847, + 1597265575, + 28912714, + 443462147, + 870096418 + ] + }, + { + "data": [ + 1393564690, + 2099610787, + 1433569094, + 1415341075, + 667347082, + 534542891, + 2086215618, + 311924734 + ] + }, + { + "data": [ + 1605236949, + 127566776, + 21238712, + 434461941, + 1022175801, + 1240317127, + 1122138289, + 1008747176 + ] + }, + { + "data": [ + 855427493, + 1207280363, + 122948393, + 1858476858, + 717680189, + 297650565, + 1852129145, + 498572861 + ] + }, + { + "data": [ + 123969828, + 595339923, + 285763600, + 1913417170, + 1555092419, + 2103200507, + 568212685, + 726567164 + ] + }, + { + "data": [ + 479007558, + 124272114, + 1475575201, + 470022555, + 470436106, + 1311385231, + 790477535, + 123444182 + ] + }, + { + "data": [ + 1851254867, + 1287818641, + 1976227070, + 2000161771, + 366470125, + 1781542280, + 130581790, + 1069901828 + ] + }, + { + "data": [ + 1246563804, + 1413964901, + 928709195, + 135099607, + 1933313698, + 942190559, + 1583299962, + 405273537 + ] + }, + { + "data": [ + 15025568, + 798031475, + 1319692199, + 626923173, + 639980038, + 1042881228, + 1087868684, + 1534522887 + ] + }, + { + "data": [ + 236889586, + 1945633712, + 888366492, + 1363228903, + 1535081845, + 1843716973, + 870982648, + 2828223 + ] + }, + { + "data": [ + 668744770, + 1762680521, + 777619164, + 842438923, + 2088233233, + 1413163967, + 2016710389, + 1505732360 + ] + }, + { + "data": [ + 428137177, + 180423701, + 422464102, + 2076710433, + 1729838960, + 535452591, + 908577683, + 35856264 + ] + }, + { + "data": [ + 1967540513, + 1519776050, + 2036007845, + 893366942, + 2089822704, + 856708567, + 52673874, + 1680933072 + ] + }, + { + "data": [ + 981208312, + 1084818190, + 677102979, + 2063847281, + 1364366814, + 1457677810, + 1899058168, + 1563619590 + ] + }, + { + "data": [ + 1291148586, + 1081265798, + 1526996412, + 391781492, + 1711281276, + 1313014433, + 1384242624, + 623027609 + ] + }, + { + "data": [ + 200567419, + 388962948, + 476521573, + 687024916, + 1833415520, + 1730904880, + 443398259, + 436157135 + ] + }, + { + "data": [ + 707230432, + 1154831848, + 281037294, + 1768624406, + 430016495, + 1598391594, + 533135163, + 1005066409 + ] + }, + { + "data": [ + 1575751610, + 754993504, + 976076502, + 28864881, + 203441435, + 1815755927, + 2032423475, + 1425030338 + ] + }, + { + "data": [ + 1562339170, + 1308262206, + 27630180, + 395740765, + 2013010851, + 1820364392, + 1927685629, + 104952625 + ] + }, + { + "data": [ + 1337781915, + 946317826, + 736367261, + 1158536580, + 1619326108, + 291263633, + 543599065, + 2111323116 + ] + }, + { + "data": [ + 252041760, + 1817414500, + 1222652907, + 729393086, + 1201147464, + 204350039, + 1423174574, + 766473914 + ] + }, + { + "data": [ + 35290624, + 543962937, + 163333824, + 329618781, + 896461446, + 346705117, + 690957194, + 1923687483 + ] + }, + { + "data": [ + 2113056063, + 1632595436, + 1471738161, + 732872543, + 1177191142, + 1142007075, + 1194993142, + 1559467243 + ] + }, + { + "data": [ + 1982272307, + 992599898, + 912060509, + 756026196, + 1317365254, + 900172012, + 1887616520, + 86671560 + ] + }, + { + "data": [ + 2046630080, + 888883591, + 84025767, + 77874323, + 1407868461, + 443546501, + 203936347, + 412038982 + ] + }, + { + "data": [ + 1135271242, + 490412616, + 1384616381, + 596851392, + 371643044, + 98437837, + 390163320, + 374739258 + ] + }, + { + "data": [ + 1465916835, + 1762678201, + 1111080241, + 460605314, + 1685336972, + 120785414, + 1657833535, + 1990363046 + ] + }, + { + "data": [ + 1622723517, + 1868134833, + 1357640922, + 2093289686, + 1317449817, + 1456398689, + 865029087, + 990587183 + ] + }, + { + "data": [ + 989042468, + 1523246160, + 349574826, + 1340646482, + 1579176982, + 725957665, + 514279194, + 572149168 + ] + } + ] } - ] + } } }, "_info": { - "hash": "0x5f7869a9585bd146ac848c4df3ef584b6c52c70fb6a3f6bc0ca6f91cbc6c0d11", + "hash": "0xfd453261ae39cec510a775702a42457e8aefbf018d4e32cad082b20bede7a8ae", "comment": "`leanSpec` generated test", "testId": "tests/consensus/devnet/verify_signatures/test_valid_signatures.py::test_proposer_signature[fork_Devnet]", - "description": "Test valid proposer signature in SignedBlockWithAttestation.\n\n Scenario\n --------\n - Single block at slot 1\n - No additional attestations (only proposer attestation)\n\n Expected Behavior\n -----------------\n 1. Proposer's signature in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n\n Why This Matters\n ----------------\n This is the most basic signature generation test. It verifies:\n - XMSS key generation works\n - Signature aggregation includes proposer signature", + "description": "Test valid proposer signature in SignedBlockWithAttestation.\n\nScenario\n--------\n- Single block at slot 1\n- No additional attestations (only proposer attestation)\n\nExpected Behavior\n-----------------\n1. Proposer's signature in SignedBlockWithAttestation can be verified against\n the validator's pubkey in the state\n\nWhy This Matters\n----------------\nThis is the most basic signature generation test. It verifies:\n- XMSS key generation works\n- Signature aggregation includes proposer signature", "fixtureFormat": "verify_signatures_test" } } diff --git a/lean_client/validator/Cargo.toml b/lean_client/validator/Cargo.toml index 8311b9d..f38a6d6 100644 --- a/lean_client/validator/Cargo.toml +++ b/lean_client/validator/Cargo.toml @@ -6,12 +6,9 @@ edition = "2021" [features] default = ["xmss-signing"] xmss-signing = ["leansig"] -devnet1 = ["containers/devnet1", "fork-choice/devnet1", "env-config/devnet1"] -devnet2 = ["containers/devnet2", "fork-choice/devnet2", "env-config/devnet1"] [dependencies] env-config = { path = "../env-config", default-features = false } -serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" containers = { path = "../containers" } fork-choice = { path = "../fork_choice" } diff --git a/lean_client/validator/src/keys.rs b/lean_client/validator/src/keys.rs index 392fd95..7680102 100644 --- a/lean_client/validator/src/keys.rs +++ b/lean_client/validator/src/keys.rs @@ -96,7 +96,7 @@ impl KeyManager { .into()); } - // Convert to ByteVector using unsafe pointer copy (same pattern as BlsPublicKey) + // Convert to ByteVector using unsafe pointer copy (same pattern as PublicKey) let mut byte_vec: ByteVector = ByteVector::default(); unsafe { let dest = &mut byte_vec as *mut ByteVector as *mut u8; diff --git a/lean_client/validator/src/lib.rs b/lean_client/validator/src/lib.rs index 752cda8..14ab340 100644 --- a/lean_client/validator/src/lib.rs +++ b/lean_client/validator/src/lib.rs @@ -2,10 +2,8 @@ use std::collections::HashMap; use std::path::Path; -use containers::attestation::AggregatedAttestations; -#[cfg(feature = "devnet2")] -use containers::attestation::NaiveAggregatedSignature; use containers::block::BlockSignatures; +use containers::ssz; use containers::{ attestation::{Attestation, AttestationData, Signature, SignedAttestation}, block::{hash_tree_root, BlockWithAttestation, SignedBlockWithAttestation}, @@ -176,9 +174,6 @@ impl ValidatorService { .latest_new_attestations .values() .filter(|att| { - #[cfg(feature = "devnet1")] - let data = &att.message.data; - #[cfg(feature = "devnet2")] let data = &att.message; // Source must match the parent state's justified checkpoint (not store's!) let source_matches = data.source == parent_state.latest_justified; @@ -191,8 +186,7 @@ impl ValidatorService { }) .collect(); - #[cfg(feature = "devnet1")] - let valid_attestations: Vec = valid_signed_attestations + let valid_attestations: Vec = valid_signed_attestations .iter() .map(|att| att.message.clone()) .collect(); @@ -211,16 +205,6 @@ impl ValidatorService { ); // Build block with collected attestations (empty body - attestations go to state) - #[cfg(feature = "devnet1")] - let (block, _post_state, _collected_atts, sigs) = parent_state.build_block( - slot, - proposer_index, - parent_root, - Some(valid_attestations), - None, - None, - )?; - #[cfg(feature = "devnet2")] let (block, _post_state, _collected_atts, sigs) = { let valid_attestations: Vec = valid_attestations .iter() @@ -236,28 +220,12 @@ impl ValidatorService { Some(valid_attestations), None, None, + None, + None, )? }; - // Collect signatures from the attestations we included - #[cfg(feature = "devnet1")] - let mut signatures = sigs; - #[cfg(feature = "devnet2")] - let mut signatures = sigs.attestation_signatures; - for signed_att in &valid_signed_attestations { - #[cfg(feature = "devnet1")] - signatures - .push(signed_att.signature.clone()) - .map_err(|e| format!("Failed to add attestation signature: {:?}", e))?; - #[cfg(feature = "devnet2")] - { - // TODO: Use real aggregation instead of naive placeholder when spec is more up to date - let aggregated_sig: NaiveAggregatedSignature = NaiveAggregatedSignature::default(); - signatures - .push(aggregated_sig) - .map_err(|e| format!("Failed to add attestation signature: {:?}", e))?; - } - } + let signatures = sigs; info!( slot = block.slot.0, @@ -269,6 +237,8 @@ impl ValidatorService { ); // Sign the proposer attestation + let proposer_signature: Signature; + if let Some(ref key_manager) = self.key_manager { // Sign proposer attestation with XMSS let message = hash_tree_root(&proposer_attestation); @@ -276,19 +246,7 @@ impl ValidatorService { match key_manager.sign(proposer_index.0, epoch, &message.0.into()) { Ok(sig) => { - #[cfg(feature = "devnet1")] - signatures - .push(sig) - .map_err(|e| format!("Failed to add proposer signature: {:?}", e))?; - #[cfg(feature = "devnet2")] - { - // TODO: Use real aggregation instead of naive placeholder when spec is more up to date - let aggregated_sig: NaiveAggregatedSignature = - NaiveAggregatedSignature::default(); - signatures - .push(aggregated_sig) - .map_err(|e| format!("Failed to add proposer signature: {:?}", e))?; - } + proposer_signature = sig; info!(proposer = proposer_index.0, "Signed proposer attestation"); } Err(e) => { @@ -298,19 +256,28 @@ impl ValidatorService { } else { // No key manager - use zero signature warn!("Building block with zero signature (no key manager)"); + proposer_signature = Signature::default(); } + // Convert signatures to PersistentList for BlockSignatures + // Extract proof_data from AggregatedSignatureProof for wire format + let attestation_signatures = { + let mut list = ssz::PersistentList::default(); + for proof in signatures { + list.push(proof) + .map_err(|e| format!("Failed to add attestation signature: {:?}", e))?; + } + list + }; + let signed_block = SignedBlockWithAttestation { message: BlockWithAttestation { block, proposer_attestation, }, - #[cfg(feature = "devnet1")] - signature: signatures, - #[cfg(feature = "devnet2")] signature: BlockSignatures { - attestation_signatures: signatures, - proposer_signature: Signature::default(), + attestation_signatures, + proposer_signature, }, }; @@ -351,15 +318,11 @@ impl ValidatorService { .validator_indices .iter() .filter_map(|&idx| { - #[cfg(feature = "devnet1")] - let attestation = Attestation { - validator_id: Uint64(idx), - data: AttestationData { - slot, - head: head_checkpoint.clone(), - target: vote_target.clone(), - source: store.latest_justified.clone(), - }, + let attestation = AttestationData { + slot, + head: head_checkpoint.clone(), + target: vote_target.clone(), + source: store.latest_justified.clone(), }; #[cfg(feature = "devnet2")] @@ -407,24 +370,11 @@ impl ValidatorService { Signature::default() }; - { - #[cfg(feature = "devnet1")] - { - Some(SignedAttestation { - message: attestation, - signature, - }) - } - - #[cfg(feature = "devnet2")] - { - Some(SignedAttestation { - validator_id: idx, - message: attestation, - signature, - }) - } - } + Some(SignedAttestation { + validator_id: idx, + message: attestation, + signature, + }) }) .collect() }