Migrate from Poseidon2 to Poseidon1 signature scheme#275
Migrate from Poseidon2 to Poseidon1 signature scheme#275pablodeymo wants to merge 2 commits intodevnet4-phase4-networkfrom
Conversation
Switch XMSS instantiation from SIGTopLevelTargetSumLifetime32Dim64Base8 (Poseidon2, V=64) to SchemeAbortingTargetSumLifetime32Dim46Base8 (Poseidon1, V=46) to match leanMultisig's aggregation circuit. Dependency changes: - Bump leansig to devnet4 branch (Poseidon1 internally) - Bump lean-multisig to devnet4 HEAD (fd88140) - Add leansig_wrapper for XmssPublicKey/XmssSignature types - Remove ethereum_ssz (replaced by postcard+lz4 serialization) - Bump rand from 0.9 to 0.10 (leansig dependency) API adaptation in ethlambda-crypto: - xmss_aggregate_signatures -> xmss_aggregate (with children + log_inv_rate) - xmss_verify_aggregated_signatures -> xmss_verify_aggregation - Devnet2XmssAggregateSignature -> AggregatedXMSS - SSZ serialization -> AggregatedXMSS::serialize()/deserialize() SIGNATURE_SIZE updated from 3112 to 2536 bytes (V=46 vs V=64). Cross-client ream tests marked #[ignore] pending Poseidon1 vectors. leanSpec bump and fixture regeneration to follow in next commit.
- Bump LEAN_SPEC_COMMIT_HASH to leanSpec HEAD which includes the Poseidon1
migration and lexicographic tiebreaker fix (no more patch needed)
- Remove patches/ directory
- Resolve headRootLabel/latestJustifiedRootLabel/latestFinalizedRootLabel
from block registry when explicit root hash is not provided in fixtures
- Adapt signature test types: 'message' -> 'block' field in TestSignedBlock,
use SIGNATURE_SIZE constant instead of hardcoded 3112
- Adapt STF test types: justifiedSlots changed from Vec<u64> to Vec<bool>
(proper bitlist format)
- Update CI to generate prod keys locally (upstream keys are still Poseidon2)
and remove prod key download/caching steps
All 123 leanSpec fixtures pass. All Rust tests pass.
🤖 Kimi Code ReviewThis PR updates ethlambda to align with the devnet4 specification, particularly migrating from Poseidon2-based XMSS signatures to Poseidon1 with aborting hypercube message hashing. The changes are substantial but well-structured. Critical Consensus Changes1. Signature Scheme Migration (crates/common/types/src/signature.rs:13-15)
2. Signature Size Constant (crates/common/types/src/signature.rs:28-31)
3. Cross-Client Test Vectors (crates/common/crypto/src/lib.rs:336-357)
API & Dependency Updates4. lean-multisig API Migration (crates/common/crypto/src/lib.rs:95-99)
5. Serialization Change (crates/common/crypto/src/lib.rs:105)
Test Infrastructure6. State Transition Test Fix (crates/blockchain/state_transition/tests/stf_spectests.rs:197-202)
7. Fork Choice Label Resolution (crates/blockchain/tests/forkchoice_spectests.rs:191-210)
Build & CI8. CI Cleanup (.github/workflows/ci.yml:81-106)
9. Patch Removal (patches/fix-lexicographic-tiebreaker-test.patch)
Minor Issues10. Error Message Formatting (crates/blockchain/tests/signature_types.rs:127-131)
11. Docstring Accuracy (crates/common/crypto/src/lib.rs:116-121)
SummaryThe PR correctly implements the devnet4 signature scheme changes. The primary risks are:
Recommendation: Add a TODO comment in Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewNow I have enough context. Let me write the review. Review: Migrate from Poseidon2 to Poseidon1 signature schemeOverall this is a well-scoped migration that tracks upstream leansig/leanSpec changes. The fixture harness adaptations and label-resolution improvements are clean. A few points need attention before merging. Critical
The old API returned let (_sorted_pubkeys, aggregate) = xmss_aggregate(&[], raw_xmss, &message.0, slot, 1);
Two things need addressing:
SignificantFloating branch reference for
-leansig = { git = "...", rev = "73bedc26ed961b110df7ac2e234dc11361a4bf25" }
+leansig = { git = "...", branch = "devnet4" }A Discarded
let (_sorted_pubkeys, aggregate) = xmss_aggregate(&[], raw_xmss, &message.0, slot, 1);The function returns a sorted permutation of the input pubkeys alongside the aggregate proof. If This needs explicit confirmation from the lean-multisig docs: does Hardcoded
xmss_aggregate(&[], raw_xmss, &message.0, slot, 1)The last parameter controls proof rate (likely FRI/STARK blowup factor). Its value directly affects proof size and soundness. There's no comment explaining what Moderate
let post_slots: Vec<bool> = (0..justified_slots.data.len())
.map(|i| post_state.justified_slots.get(i) == Some(true))
.collect();The range is bounded by Cross-client ream test vectors ignored with no tracking issue
Three Minor
Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Codex Code Review
I did not find a clear production-path bug in the XMSS wrapper from the local code alone, but I could not run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR migrates ethlambda from Poseidon2 (
Confidence Score: 4/5Two non-ignored tests that decode Poseidon2 byte vectors through the new Poseidon1 deserializer will fail in CI; fix required before merge. The core migration (types, crypto crate API, test harness format changes) is correct and well-structured. However, two active non-ignored tests in crypto/src/lib.rs will fail because they round-trip 3112-byte Poseidon2 data through the new 2536-byte Poseidon1 deserializer, and the ignored aggregation test helper is silently broken. crates/common/crypto/src/lib.rs — the cross-client decode tests and the test helper LeanSignatureScheme alias both reference the old Poseidon2 scheme.
|
| Filename | Overview |
|---|---|
| crates/common/crypto/src/lib.rs | Core crypto layer migrated to Poseidon1 API (xmss_aggregate, AggregatedXMSS::serialize); two non-ignored tests decode Poseidon2 vectors with the new 2536-byte scheme and will fail, and the test helper still generates keys with the old Poseidon2 type alias. |
| crates/common/types/src/signature.rs | Signature scheme correctly migrated to SchemeAbortingTargetSumLifetime32Dim46Base8 (Poseidon1) and SIGNATURE_SIZE updated from 3112 to 2536 bytes. |
| .github/workflows/ci.yml | CI fixture generation command diverges from Makefile: uses --fork=Devnet -n 2 without -o fixtures; the missing output flag may cause fixtures to land in a non-cached directory. |
| crates/blockchain/tests/forkchoice_spectests.rs | Test harness correctly extended to resolve headRootLabel/latestJustifiedRootLabel/latestFinalizedRootLabel from block_registry when explicit root not provided. |
| crates/blockchain/tests/signature_types.rs | TestSignedBlock now accepts both 'block' and 'message' field names via serde alias, correctly adapting to renamed fixture field. |
| crates/blockchain/state_transition/tests/types.rs | justified_slots changed from Vec to Vec correctly, matching the new fixture format. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Gossip Signature\nXmssSignature - 2536 bytes] --> B[ethlambda-crypto\naggregate_signatures]
B --> C[xmss_aggregate\nlean-multisig fd88140]
C --> D[AggregatedXMSS::serialize\npostcard+lz4]
D --> E[ByteListMiB\nstored as proof]
E --> F[verify_aggregated_signature]
F --> G[AggregatedXMSS::deserialize]
G --> H[xmss_verify_aggregation\nlean-multisig]
H --> I{Valid?}
I -->|Yes| J[Block accepted]
I -->|No| K[VerificationError]
subgraph Scheme
S1["Old: SIGTopLevelTargetSumLifetime32Dim64Base8\nPoseidon2 - 3112 bytes"]
S2["New: SchemeAbortingTargetSumLifetime32Dim46Base8\nPoseidon1 - 2536 bytes"]
S1 -->|Replaced by| S2
end
Comments Outside Diff (3)
-
crates/common/crypto/src/lib.rs, line 156 (link)Test helper uses old Poseidon2 scheme — byte conversion will fail when un-ignored
The local
LeanSignatureSchemealias still points to the oldSIGTopLevelTargetSumLifetime32Dim64Base8(Poseidon2, 3112-byte signatures).generate_keypair_and_signgenerates a signature with this type and then feeds the raw bytes toValidatorSignature::from_bytes(), which now expects 2536 bytes (Poseidon1). The conversion will fail with a parse error.The tests that call this helper (
test_aggregate_single_signature,test_aggregate_multiple_signatures, etc.) are currently#[ignore]d, so they don't break CI today, but they are silently broken and will all fail when the ignore is removed. Update the local alias to the Poseidon1 type used inethlambda-types:Prompt To Fix With AI
This is a comment left during a code review. Path: crates/common/crypto/src/lib.rs Line: 156 Comment: **Test helper uses old Poseidon2 scheme — byte conversion will fail when un-ignored** The local `LeanSignatureScheme` alias still points to the old `SIGTopLevelTargetSumLifetime32Dim64Base8` (Poseidon2, 3112-byte signatures). `generate_keypair_and_sign` generates a signature with this type and then feeds the raw bytes to `ValidatorSignature::from_bytes()`, which now expects 2536 bytes (Poseidon1). The conversion will fail with a parse error. The tests that call this helper (`test_aggregate_single_signature`, `test_aggregate_multiple_signatures`, etc.) are currently `#[ignore]`d, so they don't break CI today, but they are silently broken and will all fail when the ignore is removed. Update the local alias to the Poseidon1 type used in `ethlambda-types`: How can I resolve this? If you propose a fix, please make it concise.
-
crates/common/crypto/src/lib.rs, line 322-332 (link)Active decode tests will fail with new signature size
test_cross_client_decode_ream_signature(line 329) is not marked#[ignore], butREAM_SIGNATURE_HEXencodes a 3112-byte Poseidon2 signature. After this PR,ValidatorSignature::from_bytes()calls the Poseidon1 scheme's deserializer which expects 2536 bytes — the decode will fail and this test will error in CI. The three verify tests are correctly ignored, but these two decode tests need the same#[ignore = "Poseidon2 test vectors - needs Poseidon1 vectors from ream"]annotation, or Poseidon1 test vectors must replace the constants.Prompt To Fix With AI
This is a comment left during a code review. Path: crates/common/crypto/src/lib.rs Line: 322-332 Comment: **Active decode tests will fail with new signature size** `test_cross_client_decode_ream_signature` (line 329) is not marked `#[ignore]`, but `REAM_SIGNATURE_HEX` encodes a 3112-byte Poseidon2 signature. After this PR, `ValidatorSignature::from_bytes()` calls the Poseidon1 scheme's deserializer which expects 2536 bytes — the decode will fail and this test will error in CI. The three _verify_ tests are correctly ignored, but these two decode tests need the same `#[ignore = "Poseidon2 test vectors - needs Poseidon1 vectors from ream"]` annotation, or Poseidon1 test vectors must replace the constants. How can I resolve this? If you propose a fix, please make it concise.
-
.github/workflows/ci.yml, line 87 (link)CI fixture command diverges from Makefile
The CI fixture generation command is
uv run fill --fork=Devnet --scheme prod -n 2while the Makefile usesuv run fill --fork devnet --scheme=prod -o fixtures. The CI command omits-o fixturesand adds-n 2. If thefilltool doesn't default output tofixtures/, the cache pathleanSpec/fixtureswill be empty and subsequent test runs will fail to find fixtures. Confirm the default output path matchesleanSpec/fixtures, or add-o fixturesexplicitly for consistency.Prompt To Fix With AI
This is a comment left during a code review. Path: .github/workflows/ci.yml Line: 87 Comment: **CI fixture command diverges from Makefile** The CI fixture generation command is `uv run fill --fork=Devnet --scheme prod -n 2` while the Makefile uses `uv run fill --fork devnet --scheme=prod -o fixtures`. The CI command omits `-o fixtures` and adds `-n 2`. If the `fill` tool doesn't default output to `fixtures/`, the cache path `leanSpec/fixtures` will be empty and subsequent test runs will fail to find fixtures. Confirm the default output path matches `leanSpec/fixtures`, or add `-o fixtures` explicitly for consistency. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/common/crypto/src/lib.rs
Line: 156
Comment:
**Test helper uses old Poseidon2 scheme — byte conversion will fail when un-ignored**
The local `LeanSignatureScheme` alias still points to the old `SIGTopLevelTargetSumLifetime32Dim64Base8` (Poseidon2, 3112-byte signatures). `generate_keypair_and_sign` generates a signature with this type and then feeds the raw bytes to `ValidatorSignature::from_bytes()`, which now expects 2536 bytes (Poseidon1). The conversion will fail with a parse error.
The tests that call this helper (`test_aggregate_single_signature`, `test_aggregate_multiple_signatures`, etc.) are currently `#[ignore]`d, so they don't break CI today, but they are silently broken and will all fail when the ignore is removed. Update the local alias to the Poseidon1 type used in `ethlambda-types`:
```suggestion
type LeanSignatureScheme = leansig::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::SchemeAbortingTargetSumLifetime32Dim46Base8;
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: crates/common/crypto/src/lib.rs
Line: 322-332
Comment:
**Active decode tests will fail with new signature size**
`test_cross_client_decode_ream_signature` (line 329) is not marked `#[ignore]`, but `REAM_SIGNATURE_HEX` encodes a 3112-byte Poseidon2 signature. After this PR, `ValidatorSignature::from_bytes()` calls the Poseidon1 scheme's deserializer which expects 2536 bytes — the decode will fail and this test will error in CI. The three _verify_ tests are correctly ignored, but these two decode tests need the same `#[ignore = "Poseidon2 test vectors - needs Poseidon1 vectors from ream"]` annotation, or Poseidon1 test vectors must replace the constants.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/workflows/ci.yml
Line: 87
Comment:
**CI fixture command diverges from Makefile**
The CI fixture generation command is `uv run fill --fork=Devnet --scheme prod -n 2` while the Makefile uses `uv run fill --fork devnet --scheme=prod -o fixtures`. The CI command omits `-o fixtures` and adds `-n 2`. If the `fill` tool doesn't default output to `fixtures/`, the cache path `leanSpec/fixtures` will be empty and subsequent test runs will fail to find fixtures. Confirm the default output path matches `leanSpec/fixtures`, or add `-o fixtures` explicitly for consistency.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Bump leanSpec to HEAD (8767088), remove ..." | Re-trigger Greptile
Motivation
Poseidon2 is not cryptographically safe. The upstream leanSpec, leansig, and leanMultisig libraries have already migrated to Poseidon1. This PR aligns ethlambda with the new scheme.
Description
Dependency changes
leansigto devnet4 branch (Poseidon1 internally)lean-multisigto devnet4 HEAD (fd88140)leansig_wrapperforXmssPublicKey/XmssSignaturetypesethereum_sszfrom crypto crate (replaced by postcard+lz4 serialization)randfrom 0.9 to 0.10Signature scheme change
SIGTopLevelTargetSumLifetime32Dim64Base8(V=64, TopLevelPoseidon) toSchemeAbortingTargetSumLifetime32Dim46Base8(V=46, AbortingHypercube)SIGNATURE_SIZEupdated from 3112 to 2536 bytesAPI adaptation in ethlambda-crypto
xmss_aggregate_signatures->xmss_aggregate(with children + log_inv_rate params)xmss_verify_aggregated_signatures->xmss_verify_aggregationDevnet2XmssAggregateSignature->AggregatedXMSSAggregatedXMSS::serialize()/deserialize()leanSpec bump
patches/directory (no longer needed)headRootLabelfrom block registry when explicit root not providedjustifiedSlotschanged fromVec<u64>toVec<bool>(proper bitlist)SignedBlock.messagerenamed toSignedBlock.blockin fixturesCross-client tests
#[ignore]pending Poseidon1 vectors from ream teamHow to Test
make fmt make lint make test