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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion bin/ethlambda/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ jemalloc = ["dep:tikv-jemallocator"]
# Shadow simulator compatibility: single-threaded tokio runtime and no jemalloc.
# The quinn-udp UDP fallback is a Cargo `[patch]` (which cannot be feature-gated),
# injected at build time by `shadow/build.sh` / `make shadow-build`.
shadow-integration = []
shadow-integration = ["ethlambda-crypto/shadow-integration"]

[dependencies]
ethlambda-blockchain.workspace = true
ethlambda-crypto.workspace = true
ethlambda-network-api.workspace = true
ethlambda-p2p.workspace = true
ethlambda-types.workspace = true
Expand Down
42 changes: 42 additions & 0 deletions bin/ethlambda/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,46 @@ pub(crate) struct CliOptions {
/// `on_block`.
#[arg(long, default_value = "3")]
pub(crate) max_attestations_per_block: usize,
/// Shadow-simulator sim-cost + fake-XMSS flags (only under the
/// `shadow-integration` feature).
#[cfg(feature = "shadow-integration")]
#[command(flatten)]
pub(crate) shadow: ShadowOptions,
}

/// Shadow-simulator sim-cost + fake-XMSS flags. Compiled only under the
/// `shadow-integration` feature.
#[cfg(feature = "shadow-integration")]
#[derive(Debug, clap::Args)]
pub(crate) struct ShadowOptions {
/// Shadow sim only: replace the XMSS aggregation prover/verifier with a
/// deterministic stub (no leanVM proving/verifying). Off by default.
#[arg(long, default_value = "false")]
pub(crate) shadow_xmss_fake: bool,

/// Shadow sim only: signatures aggregated per second. Injects a sleep of
/// n/rate seconds into aggregation so its CPU cost shows up on Shadow's
/// virtual clock. Unset or <= 0 disables.
#[arg(long)]
pub(crate) shadow_xmss_aggregate_signatures_rate: Option<f64>,

/// Shadow sim only: signatures verified per aggregate per second; injects
/// a sleep of n/rate seconds into verification. Unset or <= 0 disables.
#[arg(long)]
pub(crate) shadow_xmss_verify_aggregated_signatures_rate: Option<f64>,

/// Shadow sim only: Type-1 components merged into a Type-2 per second;
/// injects a sleep of n/rate seconds into the proposal Type-2 merge.
/// Unset or <= 0 disables.
#[arg(long)]
pub(crate) shadow_xmss_merge_rate: Option<f64>,

/// Shadow sim only: byte length of each fake stub proof. Defaults to 32
/// KiB; capped at the 512 KiB on-wire proof limit.
#[arg(
long,
default_value_t = ethlambda_crypto::shadow_cost::DEFAULT_FAKE_PROOF_SIZE as u64,
value_parser = clap::value_parser!(u64).range(1..=524_288)
)]
pub(crate) shadow_xmss_fake_proof_size: u64,
}
27 changes: 27 additions & 0 deletions bin/ethlambda/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ async fn main() -> eyre::Result<()> {

let options = CliOptions::parse();

#[cfg(feature = "shadow-integration")]
init_shadow_cost(&options.shadow);

// Initialize metrics
ethlambda_blockchain::metrics::init();
ethlambda_blockchain::metrics::set_node_info("ethlambda", version::CLIENT_VERSION);
Expand Down Expand Up @@ -301,6 +304,30 @@ async fn main() -> eyre::Result<()> {
Ok(())
}

/// Apply the Shadow-simulator sim-cost / fake-XMSS configuration from the CLI.
///
/// Compiled only under the `shadow-integration` feature. Call once at startup,
/// before any consensus/aggregation work, so the fake-proof and sim-cost hooks
/// are installed before the first signing or aggregation path runs.
#[cfg(feature = "shadow-integration")]
fn init_shadow_cost(shadow: &cli::ShadowOptions) {
info!(
fake = shadow.shadow_xmss_fake,
aggregate_rate = ?shadow.shadow_xmss_aggregate_signatures_rate,
verify_rate = ?shadow.shadow_xmss_verify_aggregated_signatures_rate,
merge_rate = ?shadow.shadow_xmss_merge_rate,
fake_proof_size = shadow.shadow_xmss_fake_proof_size,
"Applying Shadow XMSS sim-cost / fake-XMSS config"
);
ethlambda_crypto::shadow_cost::init(
shadow.shadow_xmss_fake,
shadow.shadow_xmss_aggregate_signatures_rate,
shadow.shadow_xmss_verify_aggregated_signatures_rate,
shadow.shadow_xmss_merge_rate,
shadow.shadow_xmss_fake_proof_size as usize,
);
}

/// Boot the binary in Hive test-driver mode.
///
/// Skips every consensus/p2p subsystem and just exposes the
Expand Down
3 changes: 3 additions & 0 deletions crates/common/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ leansig.workspace = true
thiserror.workspace = true
rand.workspace = true

[features]
shadow-integration = []

[dev-dependencies]
hex.workspace = true
94 changes: 90 additions & 4 deletions crates/common/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use lean_multisig::{
use leansig_wrapper::{XmssPublicKey as LeanSigPubKey, XmssSignature as LeanSigSignature};
use thiserror::Error;

#[cfg(feature = "shadow-integration")]
pub mod shadow_cost;

/// log(1/rate) for the WHIR commitment scheme used inside lean-multisig.
const LOG_INV_RATE: usize = 2;

Expand Down Expand Up @@ -163,6 +166,19 @@ pub fn aggregate_signatures(
return Err(AggregationError::EmptyInput);
}

#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
let agg_n = public_keys.len();
let count_bytes = public_keys.len().to_le_bytes();
let slot_bytes = slot.to_le_bytes();
let dummy = crate::shadow_cost::fill_fake_proof(
crate::shadow_cost::fake_proof_size(),
&[&message.0, &slot_bytes, &count_bytes],
);
crate::shadow_cost::sleep(crate::shadow_cost::aggregate_delay(agg_n));
return Ok(dummy);
}

ensure_prover_ready();

let raw_xmss: Vec<(LeanSigPubKey, LeanSigSignature)> = public_keys
Expand All @@ -174,7 +190,8 @@ pub fn aggregate_signatures(
let proof = aggregate_single_message_signatures(&[], raw_xmss, message.0, slot, LOG_INV_RATE)
.map_err(|err| AggregationError::ProverFailure(err.to_string()))?;

compress_type1_to_byte_list(&proof)
let result = compress_type1_to_byte_list(&proof)?;
Ok(result)
}

/// Aggregate both existing Type-1 proofs (children) and raw XMSS signatures.
Expand All @@ -201,6 +218,22 @@ pub fn aggregate_mixed(
return Err(AggregationError::InsufficientChildren(children.len()));
}

#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
let agg_n = raw_public_keys.len();
let count_bytes = raw_public_keys.len().to_le_bytes();
let slot_bytes = slot.to_le_bytes();
let mut parts: Vec<&[u8]> = vec![&message.0, &slot_bytes];
for (_, proof) in &children {
parts.push(proof.iter().as_slice());
}
parts.push(&count_bytes);
let dummy =
crate::shadow_cost::fill_fake_proof(crate::shadow_cost::fake_proof_size(), &parts);
crate::shadow_cost::sleep(crate::shadow_cost::aggregate_delay(agg_n));
return Ok(dummy);
}

ensure_prover_ready();

let children_native: Vec<LMType1> = children
Expand All @@ -224,7 +257,8 @@ pub fn aggregate_mixed(
)
.map_err(|err| AggregationError::ProverFailure(err.to_string()))?;

compress_type1_to_byte_list(&proof)
let result = compress_type1_to_byte_list(&proof)?;
Ok(result)
}

/// Recursively aggregate two or more already-aggregated Type-1 proofs into one.
Expand All @@ -240,6 +274,20 @@ pub fn aggregate_proofs(
return Err(AggregationError::InsufficientChildren(children.len()));
}

#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
let agg_n = children.len();
let slot_bytes = slot.to_le_bytes();
let mut parts: Vec<&[u8]> = vec![&message.0, &slot_bytes];
for (_, proof) in &children {
parts.push(proof.iter().as_slice());
}
let dummy =
crate::shadow_cost::fill_fake_proof(crate::shadow_cost::fake_proof_size(), &parts);
crate::shadow_cost::sleep(crate::shadow_cost::aggregate_delay(agg_n));
return Ok(dummy);
}

ensure_prover_ready();

let children_native: Vec<LMType1> = children
Expand All @@ -257,7 +305,8 @@ pub fn aggregate_proofs(
)
.map_err(|err| AggregationError::ProverFailure(err.to_string()))?;

compress_type1_to_byte_list(&proof)
let result = compress_type1_to_byte_list(&proof)?;
Ok(result)
}

/// Verify a Type-1 aggregated signature proof.
Expand All @@ -272,6 +321,14 @@ pub fn verify_aggregated_signature(
message: &H256,
slot: u32,
) -> Result<(), VerificationError> {
// Skip the real verifier under fake-XMSS; otherwise verify for real.
#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
let verify_n = public_keys.len();
// Model verify cost on the virtual clock (no-op unless a rate is set).
crate::shadow_cost::sleep(crate::shadow_cost::verify_delay(verify_n));
return Ok(());
}
ensure_verifier_ready();

let lean_pubkeys = into_lean_pubkeys(public_keys);
Expand Down Expand Up @@ -310,6 +367,21 @@ pub fn merge_type_1s_into_type_2(
return Err(AggregationError::EmptyInput);
}

#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
let merge_n = type_1s.len();
let count_bytes = type_1s.len().to_le_bytes();
let mut parts: Vec<&[u8]> = Vec::with_capacity(type_1s.len() + 1);
for (_, proof) in &type_1s {
parts.push(proof.iter().as_slice());
}
parts.push(&count_bytes);
let dummy =
crate::shadow_cost::fill_fake_proof(crate::shadow_cost::fake_proof_size(), &parts);
crate::shadow_cost::sleep(crate::shadow_cost::merge_delay(merge_n));
return Ok(dummy);
}

ensure_prover_ready();

let type_1s_native: Vec<LMType1> = type_1s
Expand All @@ -321,7 +393,8 @@ pub fn merge_type_1s_into_type_2(
let merged = merge_single_message_aggregates(type_1s_native, LOG_INV_RATE)
.map_err(|err| AggregationError::ProverFailure(err.to_string()))?;

compress_type2_to_byte_list(&merged)
let result = compress_type2_to_byte_list(&merged)?;
Ok(result)
}

/// Verify a Type-2 merged proof against the per-component expected bindings.
Expand All @@ -341,6 +414,11 @@ pub fn verify_type_2_signature(
});
}

#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
return Ok(());
}

ensure_verifier_ready();

let pubkeys_per_info: Vec<Vec<LeanSigPubKey>> = pubkeys_per_component
Expand Down Expand Up @@ -391,6 +469,14 @@ pub fn split_type_2_by_message(
pubkeys_per_component: Vec<Vec<ValidatorPublicKey>>,
message: &H256,
) -> Result<ByteList512KiB, AggregationError> {
#[cfg(feature = "shadow-integration")]
if crate::shadow_cost::fake_xmss() {
return Ok(crate::shadow_cost::fill_fake_proof(
crate::shadow_cost::fake_proof_size(),
&[proof_data, &message.0],
));
}
Comment thread
MegaRedHand marked this conversation as resolved.

ensure_prover_ready();

let pubkeys_per_info: Vec<Vec<LeanSigPubKey>> = pubkeys_per_component
Expand Down
Loading