From ce8572ec3921084bedd9366c44be5d84b7fb7489 Mon Sep 17 00:00:00 2001 From: Pablo Deymonnaz Date: Wed, 18 Feb 2026 16:21:12 -0300 Subject: [PATCH 1/3] Bump leanSpec commit to 8b7636b Updates the pinned leanSpec commit hash to pick up the latest spec changes. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b9d74f5..f4fddc6 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ docker-build: ## 🐳 Build the Docker image -t ghcr.io/lambdaclass/ethlambda:$(DOCKER_TAG) . @echo -LEAN_SPEC_COMMIT_HASH:=b39472e73f8a7d603cc13d14426eed14c6eff6f1 +LEAN_SPEC_COMMIT_HASH:=8b7636bb8a95fe4bec414cc4c24e74079e6256b6 leanSpec: git clone https://github.com/leanEthereum/leanSpec.git --single-branch From 1f69319b2995aaf780310a440ef9600baf0f33ff Mon Sep 17 00:00:00 2001 From: Pablo Deymonnaz Date: Wed, 18 Feb 2026 17:27:33 -0300 Subject: [PATCH 2/3] Fix signature fixture deserialization for new leanSpec hex format leanSpec commit 0340cc1 changed XMSS Signature serialization from structured JSON (path/rho/hashes sub-objects) to hex-encoded SSZ bytes ("0x..."). Replace the old ProposerSignature struct and its SSZ reconstruction logic with a simple hex deserializer (deser_xmss_hex), matching the existing deser_pubkey_hex pattern. --- crates/blockchain/tests/signature_types.rs | 148 +++------------------ 1 file changed, 17 insertions(+), 131 deletions(-) diff --git a/crates/blockchain/tests/signature_types.rs b/crates/blockchain/tests/signature_types.rs index aab9922..1d8dede 100644 --- a/crates/blockchain/tests/signature_types.rs +++ b/crates/blockchain/tests/signature_types.rs @@ -7,41 +7,12 @@ use ethlambda_types::block::{ AttestationSignatures, Block as EthBlock, BlockBody as EthBlockBody, BlockSignatures, BlockWithAttestation, SignedBlockWithAttestation, }; -use ethlambda_types::primitives::{ - BitList, H256, VariableList, - ssz::{Decode as SszDecode, Encode as SszEncode}, -}; +use ethlambda_types::primitives::{BitList, H256, VariableList}; use ethlambda_types::state::{Checkpoint as EthCheckpoint, State, ValidatorPubkeyBytes}; use serde::Deserialize; -use ssz_types::FixedVector; -use ssz_types::typenum::{U28, U32}; use std::collections::HashMap; use std::path::Path; -// ============================================================================ -// SSZ Types matching leansig's GeneralizedXMSSSignature structure -// ============================================================================ - -/// A single hash digest (8 field elements = 32 bytes) -pub type HashDigest = FixedVector; - -/// Randomness (7 field elements = 28 bytes) -pub type Rho = FixedVector; - -/// SSZ-compatible HashTreeOpening matching leansig's structure -#[derive(Clone, SszEncode, SszDecode)] -pub struct SszHashTreeOpening { - pub co_path: Vec, -} - -/// SSZ-compatible XMSS Signature matching leansig's GeneralizedXMSSSignature -#[derive(Clone, SszEncode, SszDecode)] -pub struct SszXmssSignature { - pub path: SszHashTreeOpening, - pub rho: Rho, - pub hashes: Vec, -} - /// Root struct for verify signatures test vectors #[derive(Debug, Clone, Deserialize)] pub struct VerifySignaturesTestVector { @@ -217,7 +188,7 @@ impl From for SignedBlockWithAttestation { proposer_attestation: value.message.proposer_attestation.into(), }; - let proposer_signature = value.signature.proposer_signature.to_xmss_signature(); + let proposer_signature = value.signature.proposer_signature; // Convert attestation signatures to AggregatedSignatureProof. // Each proof contains the participants bitfield from the test data. @@ -378,110 +349,12 @@ impl From for EthAttestation { #[derive(Debug, Clone, Deserialize)] #[allow(dead_code)] pub struct TestSignatureBundle { - #[serde(rename = "proposerSignature")] - pub proposer_signature: ProposerSignature, + #[serde(rename = "proposerSignature", deserialize_with = "deser_xmss_hex")] + pub proposer_signature: XmssSignature, #[serde(rename = "attestationSignatures")] pub attestation_signatures: Container, } -/// XMSS signature structure as it appears in JSON -#[derive(Debug, Clone, Deserialize)] -pub struct ProposerSignature { - pub path: SignaturePath, - pub rho: RhoData, - pub hashes: HashesData, -} - -impl ProposerSignature { - /// Convert to XmssSignature (FixedVector of bytes). - /// - /// Constructs an SSZ-encoded signature matching leansig's GeneralizedXMSSSignature format. - pub fn to_xmss_signature(&self) -> XmssSignature { - // Build SSZ types from JSON data - let ssz_sig = self.to_ssz_signature(); - - // Encode to SSZ bytes - let bytes = ssz_sig.as_ssz_bytes(); - - // Pad to exactly SignatureSize bytes (3112) - let sig_size = 3112; - let mut padded = bytes.clone(); - padded.resize(sig_size, 0); - - XmssSignature::new(padded).expect("signature size mismatch") - } - - /// Convert to SSZ signature type - fn to_ssz_signature(&self) -> SszXmssSignature { - // Convert path siblings to HashDigest (Vec of 32 bytes each) - let co_path: Vec = self - .path - .siblings - .data - .iter() - .map(|sibling| { - let bytes: Vec = sibling - .data - .iter() - .flat_map(|&val| val.to_le_bytes()) - .collect(); - HashDigest::new(bytes).expect("Invalid sibling length") - }) - .collect(); - - // Convert rho (7 field elements = 28 bytes) - let rho_bytes: Vec = self - .rho - .data - .iter() - .flat_map(|&val| val.to_le_bytes()) - .collect(); - let rho = Rho::new(rho_bytes).expect("Invalid rho length"); - - // Convert hashes to HashDigest - let hashes: Vec = self - .hashes - .data - .iter() - .map(|hash| { - let bytes: Vec = hash - .data - .iter() - .flat_map(|&val| val.to_le_bytes()) - .collect(); - HashDigest::new(bytes).expect("Invalid hash length") - }) - .collect(); - - SszXmssSignature { - path: SszHashTreeOpening { co_path }, - rho, - hashes, - } - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct SignaturePath { - pub siblings: Container, -} - -#[derive(Debug, Clone, Deserialize)] -#[allow(dead_code)] -pub struct HashElement { - pub data: [u32; 8], -} - -#[derive(Debug, Clone, Deserialize)] -pub struct RhoData { - pub data: [u32; 7], -} - -#[derive(Debug, Clone, Deserialize)] -pub struct HashesData { - pub data: Vec, -} - /// Attestation signature from a validator /// Note: proofData is for future SNARK aggregation, currently just placeholder #[derive(Debug, Clone, Deserialize)] @@ -526,3 +399,16 @@ where .map_err(|_| D::Error::custom("ValidatorPubkey length != 52"))?; Ok(pubkey) } + +pub fn deser_xmss_hex<'de, D>(d: D) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::de::Error; + + let value = String::deserialize(d)?; + let bytes = hex::decode(value.strip_prefix("0x").unwrap_or(&value)) + .map_err(|_| D::Error::custom("XmssSignature value is not valid hex"))?; + XmssSignature::new(bytes) + .map_err(|_| D::Error::custom("XmssSignature length != 3112")) +} From e0b9aafa2d74f21277262272578205bb73dd2296 Mon Sep 17 00:00:00 2001 From: Pablo Deymonnaz Date: Wed, 18 Feb 2026 17:33:05 -0300 Subject: [PATCH 3/3] Run cargo fmt --- crates/blockchain/tests/signature_types.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/blockchain/tests/signature_types.rs b/crates/blockchain/tests/signature_types.rs index 1d8dede..fab5b43 100644 --- a/crates/blockchain/tests/signature_types.rs +++ b/crates/blockchain/tests/signature_types.rs @@ -409,6 +409,5 @@ where let value = String::deserialize(d)?; let bytes = hex::decode(value.strip_prefix("0x").unwrap_or(&value)) .map_err(|_| D::Error::custom("XmssSignature value is not valid hex"))?; - XmssSignature::new(bytes) - .map_err(|_| D::Error::custom("XmssSignature length != 3112")) + XmssSignature::new(bytes).map_err(|_| D::Error::custom("XmssSignature length != 3112")) }