Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
36c388e
feat(crypto)!: track leanVM main (internalized XMSS + reworked aggreg…
MegaRedHand Jul 24, 2026
6b41823
build(deps): pin leanVM to main HEAD (a73ab11) instead of tracking th…
MegaRedHand Jul 24, 2026
5e67159
Merge branch 'main' into build/leanvm-track-main
MegaRedHand Jul 24, 2026
cd293eb
build(deps): bump leanVM pin to c83b40f
MegaRedHand Jul 27, 2026
d68b246
feat(crypto)!: keep pubkeys off the proof wire
MegaRedHand Jul 27, 2026
db7bc13
fix(crypto): use leanVM's facade and prove one at a time
TomWambsgans Jul 27, 2026
02e7099
Merge branch 'main' into build/leanvm-track-main
MegaRedHand Jul 28, 2026
29a858e
Merge branch 'main' into build/leanvm-track-main
MegaRedHand Jul 29, 2026
fcd4a42
refactor(types): drop xmss dep, static-assert wire sizes in crypto
MegaRedHand Jul 29, 2026
7fb26d2
refactor(crypto): report both sizes when the XMSS wire-size guard fires
MegaRedHand Jul 29, 2026
80080a0
build(deps): track leanVM a5909d1 and prove on the system allocator
MegaRedHand Jul 30, 2026
64b109a
refactor(crypto): hand out prover setup and the permit together
MegaRedHand Jul 30, 2026
97cbe1d
refactor(crypto): hold the prover permit only across the prove call
MegaRedHand Jul 30, 2026
80930d2
refactor(crypto): let the decoding paths acquire the prover up front
MegaRedHand Jul 30, 2026
3760b9d
feat(crypto): make the prover arena an opt-in
MegaRedHand Jul 30, 2026
31817c8
feat(cli): add --prover-arena to opt into the leanVM arena
MegaRedHand Jul 30, 2026
e2a66a8
refactor(crypto): initialize leanVM once at startup instead of lazily
MegaRedHand Jul 30, 2026
afe5e67
Merge branch 'main' into build/leanvm-track-main
MegaRedHand Jul 30, 2026
a93b49e
Merge branch 'main' into build/leanvm-track-main
MegaRedHand Jul 31, 2026
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
687 changes: 198 additions & 489 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,18 @@ prometheus = "0.14"

clap = { version = "4.3", features = ["derive", "env"] }

# XMSS signatures
leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "devnet4" }
# XMSS signatures + recursive aggregation (leanVM).
# leanVM's `main` internalized XMSS (dropping the external leanSig dependency)
# and reworked the aggregation API (see "Xmss api rework").
# Pinned to a `main` commit for reproducible builds; bump the revs to track main.
xmss = { git = "https://github.com/leanEthereum/leanVM.git", rev = "a5909d18647de6aed38640c098d9177fab2bf36a" }
lean-multisig = { git = "https://github.com/leanEthereum/leanVM.git", rev = "a5909d18647de6aed38640c098d9177fab2bf36a" }

# SSZ codec used by leanVM's xmss pubkey/signature types (ethereum_ssz, not libssz).
ssz = { package = "ethereum_ssz", version = "0.10" }

# Secret-key (de)serialization for the leanVM xmss key format.
postcard = { version = "1.1.3", features = ["alloc"] }

# SSZ implementation
libssz = "0.2.2"
Expand Down
12 changes: 6 additions & 6 deletions bin/ethlambda/src/checkpoint_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,25 +415,25 @@ mod tests {

fn create_test_validator() -> Validator {
Validator {
attestation_pubkey: [1u8; 52],
proposal_pubkey: [11u8; 52],
attestation_pubkey: [1u8; 32],
proposal_pubkey: [11u8; 32],
index: 0,
}
}

fn create_different_validator() -> Validator {
Validator {
attestation_pubkey: [2u8; 52],
proposal_pubkey: [22u8; 52],
attestation_pubkey: [2u8; 32],
proposal_pubkey: [22u8; 32],
index: 0,
}
}

fn create_validators_with_indices(count: usize) -> Vec<Validator> {
(0..count)
.map(|i| Validator {
attestation_pubkey: [i as u8 + 1; 52],
proposal_pubkey: [i as u8 + 101; 52],
attestation_pubkey: [i as u8 + 1; 32],
proposal_pubkey: [i as u8 + 101; 32],
index: i as u64,
})
.collect()
Expand Down
11 changes: 11 additions & 0 deletions bin/ethlambda/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ pub(crate) struct CliOptions {
/// coverage.
#[arg(long, default_value = "false")]
pub(crate) enable_proposer_aggregation: bool,
/// Prove on leanVM's bump arena instead of the system allocator.
///
/// Buys proving throughput with memory: the arena never returns pages to
/// the OS, so RSS ratchets up to the process's allocation high-water mark
/// and stays there for the lifetime of the node. Off by default so a
/// long-lived node keeps bounded memory; worth enabling on hosts with
/// memory to spare where proving latency is the constraint.
///
/// Read once at startup: the allocator is fixed before the first proof.
#[arg(long, default_value = "false")]
pub(crate) prover_arena: bool,
/// Maximum number of distinct attestations to pack when building a block.
///
/// Bounds how many distinct `AttestationData` entries the proposer includes
Expand Down
19 changes: 16 additions & 3 deletions bin/ethlambda/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ async fn main() -> eyre::Result<()> {
#[cfg(feature = "shadow-integration")]
init_shadow_cost(&options.shadow);

// Compiles the aggregation bytecode and fixes the prover's allocator. Ahead of the
// test-driver branch below, which verifies signatures, and of every consensus path.
info!(
arena = options.prover_arena,
"Initializing leanVM prover and verifier"
);
ethlambda_crypto::init_leanvm(options.prover_arena);

// Initialize metrics
ethlambda_blockchain::metrics::init();
ethlambda_blockchain::metrics::set_node_info("ethlambda", version::CLIENT_VERSION);
Expand Down Expand Up @@ -479,8 +487,8 @@ fn read_bootnodes(bootnodes_path: impl AsRef<Path>) -> eyre::Result<Vec<Bootnode
#[derive(Debug, Deserialize, Clone)]
struct AnnotatedValidator {
index: u64,
/// Parsed for hex-format validation only; not cross-checked against the
/// loaded secret key since leansig doesn't expose any pk getters.
/// Parsed for hex-format validation only; not currently cross-checked
/// against the loaded secret key's derived public key.
#[serde(rename = "pubkey_hex", deserialize_with = "deser_pubkey_hex")]
_pubkey_hex: ValidatorPubkeyBytes,
privkey_file: PathBuf,
Expand All @@ -496,7 +504,12 @@ where
let pubkey: ValidatorPubkeyBytes = hex::decode(&value)
.map_err(|_| D::Error::custom("ValidatorPubkey value is not valid hex"))?
.try_into()
.map_err(|_| D::Error::custom("ValidatorPubkey length != 52"))?;
.map_err(|_| {
D::Error::custom(format!(
"ValidatorPubkey length != {}",
ethlambda_types::state::PUBLIC_KEY_SIZE
))
})?;
Ok(pubkey)
}

Expand Down
1 change: 0 additions & 1 deletion crates/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ hex = { workspace = true }
libssz.workspace = true
libssz-types.workspace = true
datatest-stable = "0.3.3"
leansig.workspace = true
rand.workspace = true

[[test]]
Expand Down
29 changes: 10 additions & 19 deletions crates/blockchain/src/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,31 +792,22 @@ mod tests {
fn make_validators(n: usize) -> Vec<Validator> {
(0..n)
.map(|i| Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect()
}

/// A cheap-but-real XMSS signature (tiny lifetime, cached) for tests that
/// only need `ValidatorSignature::from_bytes` to succeed. `resolve_job`
/// never checks signature validity, only that it clones and carries a
/// resolvable id — mirrors `ethlambda_storage::store::tests::make_dummy_sig`.
/// A structurally valid XMSS signature for tests that only need
/// `ValidatorSignature::from_bytes` to succeed. `resolve_job` never checks
/// signature validity, only that it clones and carries a resolvable id —
/// mirrors `ethlambda_storage::store::tests::make_dummy_sig`. An all-zero
/// blob decodes as a valid (unverifiable) signature.
fn dummy_sig() -> ValidatorSignature {
use ethlambda_crypto::signature::LeanSignatureScheme;
use leansig::{serialization::Serializable, signature::SignatureScheme};
use rand::{SeedableRng, rngs::StdRng};

static CACHED_SIG: std::sync::LazyLock<Vec<u8>> = std::sync::LazyLock::new(|| {
let mut rng = StdRng::seed_from_u64(42);
let lifetime = 1 << 5; // small for speed
let (_pk, sk) = LeanSignatureScheme::key_gen(&mut rng, 0, lifetime);
let sig = LeanSignatureScheme::sign(&sk, 0, &[0u8; 32]).unwrap();
sig.to_bytes()
});

ValidatorSignature::from_bytes(&CACHED_SIG).expect("cached test signature")
use ethlambda_types::attestation::SIGNATURE_SIZE;
ValidatorSignature::from_bytes(&vec![0u8; SIGNATURE_SIZE])
.expect("all-zero test signature decodes")
}

/// A `HashedAttestationData` over default (all-zero) data for `resolve_job`
Expand Down
20 changes: 10 additions & 10 deletions crates/blockchain/src/block_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,8 @@ mod tests {

let validators: Vec<_> = (0..NUM_VALIDATORS)
.map(|i| ethlambda_types::state::Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect();
Expand Down Expand Up @@ -1133,8 +1133,8 @@ mod tests {

let validators: Vec<_> = (0..NUM_VALIDATORS)
.map(|i| ethlambda_types::state::Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect();
Expand Down Expand Up @@ -1262,8 +1262,8 @@ mod tests {

let validators: Vec<_> = (0..NUM_VALIDATORS)
.map(|i| ethlambda_types::state::Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect();
Expand Down Expand Up @@ -1566,8 +1566,8 @@ mod tests {

let validators: Vec<_> = (0..NUM_VALIDATORS)
.map(|i| ethlambda_types::state::Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect();
Expand Down Expand Up @@ -1693,8 +1693,8 @@ mod tests {

let validators: Vec<_> = (0..NUM_VALIDATORS)
.map(|i| ethlambda_types::state::Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect();
Expand Down
4 changes: 2 additions & 2 deletions crates/blockchain/state_transition/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,8 @@ mod tests {
fn make_validators(n: usize) -> Vec<Validator> {
(0..n)
.map(|i| Validator {
attestation_pubkey: [i as u8; 52],
proposal_pubkey: [i as u8; 52],
attestation_pubkey: [i as u8; 32],
proposal_pubkey: [i as u8; 32],
index: i as u64,
})
.collect()
Expand Down
4 changes: 4 additions & 0 deletions crates/blockchain/tests/signature_spectests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const SUPPORTED_FIXTURE_FORMAT: &str = "verify_signatures_test";
const SKIP_TESTS: &[&str] = &[];

fn run(path: &Path) -> datatest_stable::Result<()> {
// These fixtures verify real signatures, so they need the backend a binary would set
// up at startup. Idempotent, so calling it per fixture costs nothing after the first.
ethlambda_crypto::init_leanvm(false);

let tests = VerifySignaturesTestVector::from_file(path)?;

for (name, test) in tests.tests {
Expand Down
11 changes: 6 additions & 5 deletions crates/common/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ version.workspace = true
[dependencies]
ethlambda-types.workspace = true

xmss.workspace = true
lean-multisig.workspace = true
ssz.workspace = true
postcard.workspace = true

lean-multisig = { git = "https://github.com/leanEthereum/leanVM.git", rev = "e2592df" }
# leansig_wrapper provides XmssPublicKey/XmssSignature types used by lean-multisig's public API
leansig_wrapper = { git = "https://github.com/leanEthereum/leanVM.git", rev = "e2592df" }

leansig.workspace = true
thiserror.workspace = true
rand.workspace = true
tracing.workspace = true

[features]
shadow-integration = []

[dev-dependencies]
hex.workspace = true
ssz.workspace = true
Loading
Loading