diff --git a/Cargo.lock b/Cargo.lock index ddd13c0c..7b514a8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2169,6 +2169,7 @@ dependencies = [ name = "ethlambda-storage" version = "0.1.0" dependencies = [ + "ethlambda-crypto", "ethlambda-types", "leansig", "libssz", @@ -2201,7 +2202,6 @@ dependencies = [ "datatest-stable 0.3.3", "ethlambda-test-fixtures", "hex", - "leansig", "libssz", "libssz-derive", "libssz-merkle", diff --git a/bin/ethlambda/src/main.rs b/bin/ethlambda/src/main.rs index 33e242e9..df4893e6 100644 --- a/bin/ethlambda/src/main.rs +++ b/bin/ethlambda/src/main.rs @@ -36,6 +36,7 @@ use cli::CliOptions; use ethlambda_blockchain::MILLISECONDS_PER_SLOT; use ethlambda_blockchain::block_builder::ProposerConfig; use ethlambda_blockchain::key_manager::ValidatorKeyPair; +use ethlambda_crypto::signature::ValidatorSecretKey; use ethlambda_network_api::{InitBlockChain, InitP2P, ToBlockChainToP2PRef, ToP2PToBlockChainRef}; use ethlambda_p2p::{ Bootnode, P2P, PeerId, SwarmConfig, attestation_subscription_subnets, build_swarm, parse_enrs, @@ -44,7 +45,6 @@ use ethlambda_types::primitives::{H256, HashTreeRoot as _}; use ethlambda_types::{ aggregator::AggregatorController, genesis::GenesisConfig, - signature::ValidatorSecretKey, state::{State, ValidatorPubkeyBytes}, }; use eyre::WrapErr; diff --git a/crates/blockchain/src/aggregation.rs b/crates/blockchain/src/aggregation.rs index 2a62b2a6..4d5049b8 100644 --- a/crates/blockchain/src/aggregation.rs +++ b/crates/blockchain/src/aggregation.rs @@ -20,13 +20,13 @@ use std::collections::{HashMap, HashSet}; use std::time::{Duration, Instant, SystemTime}; use ethlambda_crypto::aggregate_mixed; +use ethlambda_crypto::signature::{ValidatorPublicKey, ValidatorSignature}; use ethlambda_storage::Store; use ethlambda_types::{ ShortRoot, attestation::{AggregationBits, AttestationData, HashedAttestationData}, block::{ByteList512KiB, SingleMessageAggregate}, primitives::H256, - signature::{ValidatorPublicKey, ValidatorSignature}, state::Validator, }; use spawned_concurrency::message::Message; @@ -425,7 +425,7 @@ fn resolve_job( let Some(validator) = validators.get(*vid as usize) else { continue; }; - let Ok(pubkey) = validator.get_attestation_pubkey() else { + let Ok(pubkey) = ValidatorPublicKey::from_bytes(&validator.attestation_pubkey) else { continue; }; raw_by_id.insert(*vid, (pubkey, sig.clone())); @@ -492,7 +492,10 @@ fn resolve_child_pubkeys( let participant_ids: Vec = proof.participant_indices().collect(); let child_pubkeys: Vec = participant_ids .iter() - .filter_map(|&vid| validators.get(vid as usize)?.get_attestation_pubkey().ok()) + .filter_map(|&vid| { + let v = validators.get(vid as usize)?; + ValidatorPublicKey::from_bytes(&v.attestation_pubkey).ok() + }) .collect(); if child_pubkeys.len() != participant_ids.len() { warn!( @@ -796,7 +799,7 @@ mod tests { /// never checks signature validity, only that it clones and carries a /// resolvable id — mirrors `ethlambda_storage::store::tests::make_dummy_sig`. fn dummy_sig() -> ValidatorSignature { - use ethlambda_types::signature::LeanSignatureScheme; + use ethlambda_crypto::signature::LeanSignatureScheme; use leansig::{serialization::Serializable, signature::SignatureScheme}; use rand::{SeedableRng, rngs::StdRng}; diff --git a/crates/blockchain/src/block_builder.rs b/crates/blockchain/src/block_builder.rs index 787c55b4..4f0d62a6 100644 --- a/crates/blockchain/src/block_builder.rs +++ b/crates/blockchain/src/block_builder.rs @@ -15,7 +15,7 @@ use std::{ time::Instant, }; -use ethlambda_crypto::aggregate_proofs; +use ethlambda_crypto::{aggregate_proofs, signature::ValidatorPublicKey}; use ethlambda_state_transition::{ attestation_data_matches_chain, justified_slots_ops, process_block, process_slots, slot_is_justifiable_after, @@ -657,11 +657,11 @@ fn compact_attestations( let pubkeys = proof .participant_indices() .map(|vid| { - head_state + let validator = head_state .validators .get(vid as usize) - .ok_or(StoreError::InvalidValidatorIndex)? - .get_attestation_pubkey() + .ok_or(StoreError::InvalidValidatorIndex)?; + ValidatorPublicKey::from_bytes(&validator.attestation_pubkey) .map_err(|_| StoreError::PubkeyDecodingFailed(vid)) }) .collect::, _>>()?; diff --git a/crates/blockchain/src/key_manager.rs b/crates/blockchain/src/key_manager.rs index cb58c354..eb2777a8 100644 --- a/crates/blockchain/src/key_manager.rs +++ b/crates/blockchain/src/key_manager.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use std::time::Instant; +use ethlambda_crypto::signature::{ValidatorSecretKey, ValidatorSignature}; use ethlambda_types::{ attestation::{AttestationData, XmssSignature}, primitives::{H256, HashTreeRoot as _}, - signature::{ValidatorSecretKey, ValidatorSignature}, }; use tracing::{info, warn}; diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index b2929ab6..abace771 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::time::{Duration, Instant, SystemTime}; +use ethlambda_crypto::signature::{ValidatorPublicKey, ValidatorSignature}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; use ethlambda_state_transition::is_proposer; use ethlambda_storage::{ALL_TABLES, Store}; @@ -10,7 +11,6 @@ use ethlambda_types::{ attestation::{SignedAggregatedAttestation, SignedAttestation}, block::{ByteList512KiB, MultiMessageAggregate, SignedBlock}, primitives::{H256, HashTreeRoot as _}, - signature::{ValidatorPublicKey, ValidatorSignature}, }; use crate::aggregation::{ @@ -763,7 +763,10 @@ impl BlockChainServer { // Decode the proposer's proposal pubkey once and reuse it both for the // singleton single-message aggregate wrap and for the multi-message // aggregate merge inputs. - let Ok(proposer_pubkey) = proposer_validator.get_proposal_pubkey().inspect_err( + let Ok(proposer_pubkey) = ValidatorPublicKey::from_bytes( + &proposer_validator.proposal_pubkey, + ) + .inspect_err( |err| error!(%slot, %validator_id, %err, "Failed to decode proposer proposal pubkey"), ) else { metrics::inc_block_building_failures(); @@ -802,7 +805,7 @@ impl BlockChainServer { resolve_failed = true; break; }; - match validator.get_attestation_pubkey() { + match ValidatorPublicKey::from_bytes(&validator.attestation_pubkey) { Ok(pk) => pubkeys.push(pk), Err(err) => { error!(%slot, %validator_id, vid, %err, "Failed to decode attestation pubkey"); diff --git a/crates/blockchain/src/reaggregate.rs b/crates/blockchain/src/reaggregate.rs index f47d6c51..64e762e7 100644 --- a/crates/blockchain/src/reaggregate.rs +++ b/crates/blockchain/src/reaggregate.rs @@ -24,6 +24,7 @@ use std::collections::HashSet; +use ethlambda_crypto::signature::ValidatorPublicKey; use ethlambda_storage::Store; use ethlambda_types::{ attestation::{ @@ -32,7 +33,6 @@ use ethlambda_types::{ }, block::{SignedBlock, SingleMessageAggregate}, primitives::{H256, HashTreeRoot as _}, - signature::ValidatorPublicKey, }; use tracing::{debug, warn}; @@ -83,7 +83,9 @@ pub fn reaggregate_from_block( warn!(vid, "Reaggregation aborted: participant out of range"); return Vec::new(); } - let Ok(pk) = validators[vid as usize].get_attestation_pubkey() else { + let Ok(pk) = + ValidatorPublicKey::from_bytes(&validators[vid as usize].attestation_pubkey) + else { warn!(vid, "Reaggregation aborted: bad attestation pubkey"); return Vec::new(); }; @@ -94,7 +96,8 @@ pub fn reaggregate_from_block( if block.proposer_index >= num_validators { return Vec::new(); } - let Ok(proposer_pubkey) = validators[block.proposer_index as usize].get_proposal_pubkey() + let Ok(proposer_pubkey) = + ValidatorPublicKey::from_bytes(&validators[block.proposer_index as usize].proposal_pubkey) else { return Vec::new(); }; @@ -161,7 +164,9 @@ pub fn reaggregate_from_block( bad = true; break; } - match validators[vid as usize].get_attestation_pubkey() { + match ValidatorPublicKey::from_bytes( + &validators[vid as usize].attestation_pubkey, + ) { Ok(pk) => pubkeys.push(pk), Err(_) => { bad = true; diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 2bdf46b0..1a6d3884 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, HashSet}; +use ethlambda_crypto::signature::{ValidatorPublicKey, ValidatorSignature}; use ethlambda_state_transition::{is_proposer, slot_is_justifiable_after}; use ethlambda_storage::{ForkCheckpoints, Store}; use ethlambda_types::{ @@ -11,7 +12,6 @@ use ethlambda_types::{ block::{Block, BlockHeader, SignedBlock, SingleMessageAggregate}, checkpoint::Checkpoint, primitives::{H256, HashTreeRoot as _}, - signature::{ValidatorPublicKey, ValidatorSignature}, state::{HISTORICAL_ROOTS_LIMIT, State}, }; use tracing::{info, trace, warn}; @@ -418,9 +418,10 @@ pub fn on_gossip_attestation( if validator_id >= target_state.validators.len() as u64 { return Err(StoreError::InvalidValidatorIndex); } - let validator_pubkey = target_state.validators[validator_id as usize] - .get_attestation_pubkey() - .map_err(|_| StoreError::PubkeyDecodingFailed(validator_id))?; + let validator_pubkey = ValidatorPublicKey::from_bytes( + &target_state.validators[validator_id as usize].attestation_pubkey, + ) + .map_err(|_| StoreError::PubkeyDecodingFailed(validator_id))?; // Verify the validator's XMSS signature let slot: u32 = attestation.data.slot.try_into().expect("slot exceeds u32"); @@ -513,8 +514,7 @@ fn on_gossip_aggregated_attestation_core( let pubkeys: Vec<_> = participant_indices .iter() .map(|&vid| { - validators[vid as usize] - .get_attestation_pubkey() + ValidatorPublicKey::from_bytes(&validators[vid as usize].attestation_pubkey) .map_err(|_| StoreError::PubkeyDecodingFailed(vid)) }) .collect::>()?; @@ -1142,8 +1142,7 @@ pub fn verify_block_signatures( let validator = validators .get(vid as usize) .ok_or(StoreError::InvalidValidatorIndex)?; - let pk = validator - .get_attestation_pubkey() + let pk = ValidatorPublicKey::from_bytes(&validator.attestation_pubkey) .map_err(|_| StoreError::PubkeyDecodingFailed(vid))?; pubkeys.push(pk); } @@ -1156,8 +1155,7 @@ pub fn verify_block_signatures( let proposer_validator = validators .get(block.proposer_index as usize) .ok_or(StoreError::InvalidValidatorIndex)?; - let proposer_pubkey = proposer_validator - .get_proposal_pubkey() + let proposer_pubkey = ValidatorPublicKey::from_bytes(&proposer_validator.proposal_pubkey) .map_err(|_| StoreError::PubkeyDecodingFailed(block.proposer_index))?; pubkeys_per_component.push(vec![proposer_pubkey]); let block_slot_u32 = diff --git a/crates/common/crypto/src/lib.rs b/crates/common/crypto/src/lib.rs index 4e081445..51d7d282 100644 --- a/crates/common/crypto/src/lib.rs +++ b/crates/common/crypto/src/lib.rs @@ -1,10 +1,8 @@ use std::sync::Once; -use ethlambda_types::{ - block::ByteList512KiB, - primitives::H256, - signature::{ValidatorPublicKey, ValidatorSignature}, -}; +use ethlambda_types::{block::ByteList512KiB, primitives::H256}; + +use crate::signature::{ValidatorPublicKey, ValidatorSignature}; use lean_multisig::{ MultiMessageAggregateSignature as LMType2, ProofError, SingleMessageAggregateSignature as LMType1, aggregate_single_message_signatures, @@ -14,6 +12,8 @@ use lean_multisig::{ use leansig_wrapper::{XmssPublicKey as LeanSigPubKey, XmssSignature as LeanSigSignature}; use thiserror::Error; +pub mod signature; + #[cfg(feature = "shadow-integration")] pub mod shadow_cost; @@ -508,7 +508,7 @@ pub fn split_type_2_by_message( #[cfg(test)] mod tests { use super::*; - use ethlambda_types::signature::LeanSignatureScheme; + use crate::signature::LeanSignatureScheme; use leansig::{serialization::Serializable, signature::SignatureScheme}; use rand::{SeedableRng, rngs::StdRng}; diff --git a/crates/common/types/src/signature.rs b/crates/common/crypto/src/signature.rs similarity index 97% rename from crates/common/types/src/signature.rs rename to crates/common/crypto/src/signature.rs index fa9c8da8..39515e53 100644 --- a/crates/common/types/src/signature.rs +++ b/crates/common/crypto/src/signature.rs @@ -1,12 +1,14 @@ +//! Validator XMSS signatures, public/secret keys, and the leansig-backed +//! primitives behind them. + use std::ops::Range; +use ethlambda_types::primitives::H256; use leansig::{ serialization::Serializable, signature::{SignatureScheme, SignatureSchemeSecretKey as _, SigningError}, }; -use crate::primitives::H256; - /// The XMSS signature scheme used for validator signatures. /// /// This is a post-quantum secure signature scheme based on hash functions. @@ -25,11 +27,6 @@ pub type LeanSigSecretKey = ::SecretKey; pub type Signature = LeanSigSignature; -/// Size of an XMSS signature in bytes. -/// -/// Computed from: path(32*8*4) + rho(7*4) + hashes(46*8*4) + ssz_offsets(3*4) = 2536 -pub const SIGNATURE_SIZE: usize = 2536; - /// Error returned when parsing signature or key bytes fails. #[derive(Debug, Clone, thiserror::Error)] #[error("signature parse error: {0}")] diff --git a/crates/common/test-fixtures/src/common.rs b/crates/common/test-fixtures/src/common.rs index 6293b897..6705292b 100644 --- a/crates/common/test-fixtures/src/common.rs +++ b/crates/common/test-fixtures/src/common.rs @@ -2,12 +2,11 @@ use ethlambda_types::{ attestation::{ AggregatedAttestation as DomainAggregatedAttestation, AggregationBits as DomainAggregationBits, AttestationData as DomainAttestationData, - XmssSignature, + SIGNATURE_SIZE, XmssSignature, }, block::{Block as DomainBlock, BlockBody as DomainBlockBody}, checkpoint::Checkpoint as DomainCheckpoint, primitives::H256, - signature::SIGNATURE_SIZE, state::{ ChainConfig, JustificationValidators, JustifiedSlots, State, Validator as DomainValidator, ValidatorPubkeyBytes, diff --git a/crates/common/types/Cargo.toml b/crates/common/types/Cargo.toml index 1de09a67..383f77a3 100644 --- a/crates/common/types/Cargo.toml +++ b/crates/common/types/Cargo.toml @@ -14,8 +14,6 @@ thiserror.workspace = true serde.workspace = true hex.workspace = true -leansig.workspace = true - libssz.workspace = true libssz-derive.workspace = true libssz-merkle.workspace = true diff --git a/crates/common/types/src/attestation.rs b/crates/common/types/src/attestation.rs index 0d68addb..91d00105 100644 --- a/crates/common/types/src/attestation.rs +++ b/crates/common/types/src/attestation.rs @@ -7,7 +7,6 @@ use crate::{ block::SingleMessageAggregate, checkpoint::Checkpoint, primitives::{H256, HashTreeRoot as _}, - signature::SIGNATURE_SIZE, }; /// Validator specific attestation wrapping shared attestation data. @@ -55,6 +54,13 @@ pub struct SignedAttestation { pub signature: XmssSignature, } +/// Size of an XMSS signature in bytes. +/// +/// Computed from: path(32*8*4) + rho(7*4) + hashes(46*8*4) + ssz_offsets(3*4) = 2536. +/// This is the SSZ wire size, independent of the `leansig` scheme itself, so it +/// lives here (leansig-free) rather than in `ethlambda-crypto`. +pub const SIGNATURE_SIZE: usize = 2536; + /// XMSS signature as a fixed-length byte vector (`SIGNATURE_SIZE` bytes). pub type XmssSignature = SszVector; diff --git a/crates/common/types/src/lib.rs b/crates/common/types/src/lib.rs index 6db9a590..88ba98b9 100644 --- a/crates/common/types/src/lib.rs +++ b/crates/common/types/src/lib.rs @@ -5,7 +5,6 @@ pub mod checkpoint; pub mod constants; pub mod genesis; pub mod primitives; -pub mod signature; pub mod state; /// Display helper for truncated root hashes (8 hex chars) diff --git a/crates/common/types/src/state.rs b/crates/common/types/src/state.rs index 26ff110d..6cc25bb4 100644 --- a/crates/common/types/src/state.rs +++ b/crates/common/types/src/state.rs @@ -6,7 +6,6 @@ use crate::{ block::{Block, BlockBody, BlockHeader}, checkpoint::Checkpoint, primitives::{self, H256}, - signature::{SignatureParseError, ValidatorPublicKey}, }; // Convenience trait for calling hash_tree_root() without a hasher argument @@ -85,16 +84,6 @@ where serializer.serialize_str(&hex::encode(pubkey)) } -impl Validator { - pub fn get_attestation_pubkey(&self) -> Result { - ValidatorPublicKey::from_bytes(&self.attestation_pubkey) - } - - pub fn get_proposal_pubkey(&self) -> Result { - ValidatorPublicKey::from_bytes(&self.proposal_pubkey) - } -} - pub type ValidatorPubkeyBytes = [u8; 52]; impl State { diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 7381a53e..035d802a 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -10,6 +10,7 @@ rust-version.workspace = true version.workspace = true [dependencies] +ethlambda-crypto.workspace = true ethlambda-types.workspace = true tracing.workspace = true diff --git a/crates/storage/src/store.rs b/crates/storage/src/store.rs index 83f1e8fc..d5cac0ed 100644 --- a/crates/storage/src/store.rs +++ b/crates/storage/src/store.rs @@ -7,6 +7,7 @@ use lru::LruCache; use crate::api::{StorageBackend, StorageReadView, StorageWriteBatch, Table}; use crate::error::Error; +use ethlambda_crypto::signature::ValidatorSignature; use ethlambda_types::{ attestation::{AggregationBits, AttestationData, HashedAttestationData, bits_is_subset}, block::{ @@ -14,7 +15,6 @@ use ethlambda_types::{ }, checkpoint::Checkpoint, primitives::{H256, HashTreeRoot as _}, - signature::ValidatorSignature, state::{ChainConfig, State, anchor_pair_is_consistent}, }; use libssz::{SszDecode, SszEncode}; @@ -2695,7 +2695,7 @@ mod tests { // ============ GossipSignatureBuffer Tests ============ fn make_dummy_sig() -> ValidatorSignature { - use ethlambda_types::signature::LeanSignatureScheme; + use ethlambda_crypto::signature::LeanSignatureScheme; use leansig::{serialization::Serializable, signature::SignatureScheme}; use rand::{SeedableRng, rngs::StdRng};