Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
147 changes: 16 additions & 131 deletions crates/blockchain/tests/signature_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8, U32>;

/// Randomness (7 field elements = 28 bytes)
pub type Rho = FixedVector<u8, U28>;

/// SSZ-compatible HashTreeOpening matching leansig's structure
#[derive(Clone, SszEncode, SszDecode)]
pub struct SszHashTreeOpening {
pub co_path: Vec<HashDigest>,
}

/// SSZ-compatible XMSS Signature matching leansig's GeneralizedXMSSSignature
#[derive(Clone, SszEncode, SszDecode)]
pub struct SszXmssSignature {
pub path: SszHashTreeOpening,
pub rho: Rho,
pub hashes: Vec<HashDigest>,
}

/// Root struct for verify signatures test vectors
#[derive(Debug, Clone, Deserialize)]
pub struct VerifySignaturesTestVector {
Expand Down Expand Up @@ -217,7 +188,7 @@ impl From<TestSignedBlockWithAttestation> 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.
Expand Down Expand Up @@ -378,110 +349,12 @@ impl From<ProposerAttestation> 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<AttestationSignature>,
}

/// 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<u8> of 32 bytes each)
let co_path: Vec<HashDigest> = self
.path
.siblings
.data
.iter()
.map(|sibling| {
let bytes: Vec<u8> = 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<u8> = 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<HashDigest> = self
.hashes
.data
.iter()
.map(|hash| {
let bytes: Vec<u8> = 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<HashElement>,
}

#[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<HashElement>,
}

/// Attestation signature from a validator
/// Note: proofData is for future SNARK aggregation, currently just placeholder
#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -526,3 +399,15 @@ where
.map_err(|_| D::Error::custom("ValidatorPubkey length != 52"))?;
Ok(pubkey)
}

pub fn deser_xmss_hex<'de, D>(d: D) -> Result<XmssSignature, D::Error>
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"))
}
Loading