Skip to content
Merged
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
777 changes: 372 additions & 405 deletions lean_client/Cargo.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions lean_client/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ docker: ./target/x86_64-unknown-linux-gnu/release/lean_client ./target/aarch64-u
.

.PHONY: docker-local
docker-local: ./target/x86_64-unknown-linux-gnu/release/lean_client ./target/aarch64-unknown-linux-gnu/release/lean_client
# docker-local: ./target/x86_64-unknown-linux-gnu/release/lean_client ./target/aarch64-unknown-linux-gnu/release/lean_client
docker-local: ./target/x86_64-unknown-linux-gnu/release/lean_client
@mkdir -p ./bin/amd64
@cp ./target/x86_64-unknown-linux-gnu/release/lean_client ./bin/amd64/lean_client
@mkdir -p ./bin/arm64
@cp ./target/aarch64-unknown-linux-gnu/release/lean_client ./bin/arm64/lean_client
# @mkdir -p ./bin/arm64
# @cp ./target/aarch64-unknown-linux-gnu/release/lean_client ./bin/arm64/lean_client
Comment on lines +81 to +86
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented lines

docker build \
--file Dockerfile \
--build-arg COMMIT_SHA=$(COMMIT_SHA) \
Expand Down
7 changes: 5 additions & 2 deletions lean_client/containers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ env-config = { path = "../env-config", default-features = false }
ssz = { workspace = true }
serde = { workspace = true }
ssz_derive = { workspace = true }
tracing = "0.1"
typenum = "1"
serde_json = "1.0"
serde_yaml = "0.9"
hex = "0.4.3"
sha2 = "0.10"
leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "main" }
lean-multisig = { git = "https://github.com/leanEthereum/leanMultisig", branch = "main" }
leansig = { git = "https://github.com/leanEthereum/leanSig", rev = "73bedc26ed961b110df7ac2e234dc11361a4bf25" }
lean-multisig = { git = "https://github.com/leanEthereum/leanMultisig", rev = "e4474138487eeb1ed7c2e1013674fe80ac9f3165" }
Comment on lines +25 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! wanted to do the same thing 👍

anyhow = "1.0.100"
alloy-primitives = "1.5.2"
# ethereum_ssz for lean-multisig types (aliased to avoid conflict with grandine ssz)
eth_ssz = { package = "ethereum_ssz", version = "0.10.0" }

[dev-dependencies]
rstest = "0.18"
Expand Down
94 changes: 54 additions & 40 deletions lean_client/containers/src/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{Checkpoint, Slot, Uint64};
use leansig::serialization::Serializable;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use ssz::BitList;
use ssz::ByteVector;
Expand Down Expand Up @@ -70,18 +70,18 @@ impl MultisigAggregatedSignature {
/// Uses lean-multisig zkVM to combine multiple signatures into a compact proof.
///
/// # Arguments
/// * `public_keys` - Public keys of the signers
/// * `public_keys` - Slice of validator public keys
/// * `signatures` - Individual XMSS signatures to aggregate
/// * `message` - The 32-byte message that was signed (as 8 field elements)
/// * `message` - The 32-byte message that was signed
/// * `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,
public_keys: &[crate::public_key::PublicKey],
signatures: &[Signature],
message: &[u8; 32],
epoch: u32,
) -> Result<Self, AggregationError> {
if public_keys.is_empty() {
return Err(AggregationError::EmptyInput);
Expand All @@ -90,10 +90,30 @@ impl MultisigAggregatedSignature {
return Err(AggregationError::MismatchedLengths);
}

let proof_bytes =
lean_multisig::xmss_aggregate_signatures(public_keys, signatures, message, epoch)
// Convert to lean-multisig types
let lean_pks: Vec<_> = public_keys
.iter()
.map(|pk| pk.as_lean_sig())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AggregationError::AggregationFailed)?;

let lean_sigs: Vec<_> = signatures
.iter()
.map(|sig| {
// Convert ByteVector to crate::signature::Signature then to lean-sig
let sig_struct = crate::signature::Signature::from(sig.as_bytes());
sig_struct.as_lean_sig()
})
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AggregationError::AggregationFailed)?;

let aggregate_sig =
lean_multisig::xmss_aggregate_signatures(&lean_pks, &lean_sigs, message, epoch)
.map_err(|_| AggregationError::AggregationFailed)?;

// Serialize the aggregate signature using ethereum_ssz (aliased as eth_ssz)
use eth_ssz::Encode;
let proof_bytes = aggregate_sig.as_ssz_bytes();
Self::new(proof_bytes)
}

Expand All @@ -106,23 +126,32 @@ impl MultisigAggregatedSignature {
/// `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,
public_keys: &[crate::public_key::PublicKey],
message: &[u8; 32],
epoch: u32,
) -> Result<(), AggregationError> {
lean_multisig::xmss_verify_aggregated_signatures(
public_keys,
message,
self.0.as_bytes(),
epoch,
)
.map_err(|_| AggregationError::VerificationFailed)
// Use ethereum_ssz (aliased as eth_ssz) for decoding
use eth_ssz::Decode;

// Decode the aggregated signature from SSZ bytes
let aggregate_sig =
lean_multisig::Devnet2XmssAggregateSignature::from_ssz_bytes(self.0.as_bytes())
.map_err(|_| AggregationError::VerificationFailed)?;

// Convert public keys to lean-multisig format
let lean_pks: Vec<_> = public_keys
.iter()
.map(|pk| pk.as_lean_sig())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AggregationError::VerificationFailed)?;

lean_multisig::xmss_verify_aggregated_signatures(&lean_pks, message, &aggregate_sig, 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.
/// This is a convenience method that extracts public keys from validators.
///
/// # Arguments
/// * `validators` - Slice of validator references to extract public keys from
Expand All @@ -135,28 +164,13 @@ impl MultisigAggregatedSignature {
&self,
validators: &[&crate::validator::Validator],
message: &[u8; 32],
epoch: u64,
epoch: u32,
) -> 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]");
let public_keys: Vec<_> = validators.iter().map(|v| v.pubkey).collect();

// Call verify with extracted keys and converted message
self.verify(&public_keys, message_fields, epoch)
// Call verify with extracted keys
self.verify(&public_keys, message, epoch)
}
}

Expand Down
35 changes: 18 additions & 17 deletions lean_client/containers/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,24 +158,24 @@ impl SignedBlockWithAttestation {
);
}

// let attestation_data_root: [u8; 32] =
// hash_tree_root(&aggregated_attestation.data).0.into();
let attestation_data_root: [u8; 32] =
hash_tree_root(&aggregated_attestation.data).0.into();

// 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::<Vec<_>>(),
// &attestation_data_root,
// aggregated_attestation.data.slot.0,
// )
// .expect("Attestation aggregated signature verification failed");
_aggregated_signature_proof
.proof_data
.verify_aggregated_payload(
&validator_ids
.iter()
.map(|vid| validators.get(*vid).expect("validator must exist"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't panic here, throw proper error

.collect::<Vec<_>>(),
&attestation_data_root,
aggregated_attestation.data.slot.0 as u32,
)
.expect("Attestation aggregated signature verification failed");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before, don't panic

}

// Verify the proposer attestation signature (outside the attestation loop)
Expand Down Expand Up @@ -214,11 +214,12 @@ pub fn verify_xmss_signature(
signature: &Signature,
) -> bool {
let epoch = slot.0 as u32;
let signature = crate::signature::Signature::from(signature.as_bytes());

signature
.verify(&public_key, epoch, message_bytes)
.unwrap_or_else(|_| false)
// Create Signature from the raw bytes
let sig = crate::signature::Signature::from(signature.as_bytes());

sig.verify(&public_key, epoch, message_bytes)
.unwrap_or(false)
}

#[cfg(not(feature = "xmss-verify"))]
Expand Down
Loading
Loading