From e649462d0f3733237ef4292e5183834a20f326c9 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 4 Apr 2026 19:25:27 +0200 Subject: [PATCH 01/66] adapt old MTU-XMSS branch --- Cargo.lock | 28 +- Cargo.toml | 1 + crates/rec_aggregation/main.py | 37 ++- crates/rec_aggregation/src/benchmark.rs | 2 +- crates/rec_aggregation/src/compilation.rs | 20 +- crates/rec_aggregation/src/lib.rs | 127 ++++++-- crates/rec_aggregation/utils.py | 7 + crates/rec_aggregation/xmss_aggregate.py | 379 +++++++++++++++------- crates/xmss/Cargo.toml | 1 + crates/xmss/src/lib.rs | 60 +++- crates/xmss/src/signers_cache.rs | 1 + crates/xmss/src/wots.rs | 153 ++++++--- crates/xmss/src/xmss.rs | 121 ++++--- crates/xmss/tests/xmss_tests.rs | 19 +- 14 files changed, 686 insertions(+), 270 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc523c9ea..970ab5f07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,9 +386,9 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -417,6 +417,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -497,9 +506,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "lock_api" @@ -949,6 +958,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1355,6 +1374,7 @@ dependencies = [ "postcard", "rand", "serde", + "sha3", "utils", ] diff --git a/Cargo.toml b/Cargo.toml index 01fb3c8e0..7e2fa4e67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ tracing-subscriber = { version = "0.3.23", features = ["std", "env-filter"] } tracing-forest = { version = "0.3.0", features = ["ansi", "smallvec"] } postcard = { version = "1.1.3", features = ["alloc"] } lz4_flex = "0.13.0" +sha3 = "0.10.8" [features] prox-gaps-conjecture = ["rec_aggregation/prox-gaps-conjecture"] diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index f7635c311..0ee843be7 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -8,14 +8,13 @@ MAX_N_DUPS = 2**15 INNER_PUB_MEM_SIZE = 2**INNER_PUBLIC_MEMORY_LOG_SIZE -BYTECODE_CLAIM_OFFSET = 1 + DIGEST_LEN + 2 + MESSAGE_LEN + N_MERKLE_CHUNKS - +BYTECODE_CLAIM_OFFSET = 1 + DIGEST_LEN + 2 + MESSAGE_LEN + N_MERKLE_CHUNKS + DIGEST_LEN def main(): debug_assert(MAX_N_SIGS + MAX_N_DUPS <= 2**16) # because of range checking, TODO increase pub_mem = NONRESERVED_PROGRAM_INPUT_START n_sigs = pub_mem[0] - assert n_sigs != 0 + assert 1 < n_sigs assert n_sigs - 1 < MAX_N_SIGS pubkeys_hash_expected = pub_mem + 1 message = pubkeys_hash_expected + DIGEST_LEN @@ -23,6 +22,7 @@ def main(): slot_lo = slot_ptr[0] slot_hi = slot_ptr[1] merkle_chunks_for_slot = slot_ptr + 2 + tweaks_hash_expected = merkle_chunks_for_slot + N_MERKLE_CHUNKS bytecode_claim_output = pub_mem + BYTECODE_CLAIM_OFFSET priv_start: Imu @@ -34,12 +34,17 @@ def main(): n_dup = priv_start[1] assert n_dup < MAX_N_SIGS # TODO increase all_pubkeys = priv_start[2] - sub_slice_starts = priv_start + 3 + tweak_table = priv_start[3] + sub_slice_starts = priv_start + 4 bytecode_sumcheck_proof = sub_slice_starts[n_recursions + 1] source_0 = sub_slice_starts[0] n_raw_xmss = source_0[0] + # Verify tweak table hash + computed_tweaks_hash = slice_hash(tweak_table, TWEAK_TABLE_SIZE_FE_PADDED / DIGEST_LEN) + copy_8(computed_tweaks_hash, tweaks_hash_expected) + # 1->1 optimization if n_recursions == 1: assert n_dup == 0 @@ -53,6 +58,7 @@ def main(): proof_transcript = inner_pub_mem + INNER_PUB_MEM_SIZE non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sigs, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem) copy_8(non_reserved_inner + 1, pubkeys_hash_expected) + copy_8(tweaks_hash_expected, non_reserved_inner + 1 + DIGEST_LEN + MESSAGE_LEN + 2 + N_MERKLE_CHUNKS) bytecode_claims = Array(2) bytecode_claims[0] = non_reserved_inner + BYTECODE_CLAIM_OFFSET bytecode_claims[1] = recursion(inner_pub_mem, proof_transcript, bytecode_value_hint) @@ -60,7 +66,7 @@ def main(): return # General path - computed_pubkeys_hash = slice_hash_with_iv_dynamic_unroll(all_pubkeys, n_sigs * DIGEST_LEN, MAX_LOG_MEMORY_SIZE) + computed_pubkeys_hash = slice_hash_with_iv_dynamic_unroll(all_pubkeys, n_sigs * PUB_KEY_SIZE, MAX_LOG_MEMORY_SIZE) copy_8(computed_pubkeys_hash, pubkeys_hash_expected) # Buffer for partition verification @@ -76,10 +82,10 @@ def main(): assert idx < n_total buffer[idx] = i # Verify raw XMSS signatures - pk = all_pubkeys + idx * DIGEST_LEN + pk = all_pubkeys + idx * PUB_KEY_SIZE sig = Array(SIG_SIZE) hint_xmss(sig) - xmss_verify(pk, message, sig, slot_lo, slot_hi, merkle_chunks_for_slot) + xmss_verify(pk, message, sig, tweak_table, merkle_chunks_for_slot) counter: Mut = n_raw_xmss @@ -101,22 +107,25 @@ def main(): assert idx0 < n_total buffer[idx0] = counter counter += 1 - pk0 = all_pubkeys + idx0 * DIGEST_LEN - running_hash: Mut = Array(DIGEST_LEN) - poseidon16_compress(ZERO_VEC_PTR, pk0, running_hash) + + # Copy subset pub keys to contiguous buffer for flat hashing + sub_pubkeys = Array(n_sub * PUB_KEY_SIZE) + copy_9(all_pubkeys + idx0 * PUB_KEY_SIZE, sub_pubkeys) for j in dynamic_unroll(1, n_sub, log2_ceil(MAX_N_SIGS)): idx = sub_indices[j] assert idx < n_total buffer[idx] = counter counter += 1 - pk = all_pubkeys + idx * DIGEST_LEN - new_hash = Array(DIGEST_LEN) - poseidon16_compress(running_hash, pk, new_hash) - running_hash = new_hash + copy_9(all_pubkeys + idx * PUB_KEY_SIZE, sub_pubkeys + j * PUB_KEY_SIZE) + + running_hash = slice_hash_with_iv_dynamic_unroll(sub_pubkeys, n_sub * PUB_KEY_SIZE, MAX_LOG_MEMORY_SIZE) non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sub, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem) copy_8(running_hash, non_reserved_inner + 1) + # Verify inner tweaks hash matches ours + inner_msg = non_reserved_inner + 1 + DIGEST_LEN + copy_8(tweaks_hash_expected, inner_msg + MESSAGE_LEN + 2 + N_MERKLE_CHUNKS) # Collect inner bytecode claim from inner pub mem bytecode_claims[2 * rec_idx] = non_reserved_inner + BYTECODE_CLAIM_OFFSET diff --git a/crates/rec_aggregation/src/benchmark.rs b/crates/rec_aggregation/src/benchmark.rs index 522bef144..47c4eda5c 100644 --- a/crates/rec_aggregation/src/benchmark.rs +++ b/crates/rec_aggregation/src/benchmark.rs @@ -247,7 +247,7 @@ fn build_aggregation( let children: Vec<(&[XmssPublicKey], AggregatedXMSS)> = child_pub_keys_list .iter() .zip(child_aggs) - .map(|(pks, agg)| (pks.as_slice(), agg)) + .map(|(pks, agg): (&Vec, AggregatedXMSS)| (pks.as_slice(), agg)) .collect(); let time = Instant::now(); diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 3748ac6ad..b41671d83 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -11,7 +11,7 @@ use std::sync::OnceLock; use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W}; +use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W}; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; @@ -31,10 +31,16 @@ fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> let bytecode_point_n_vars = inner_program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); let claim_data_size = (bytecode_point_n_vars + 1) * DIMENSION; let claim_data_size_padded = claim_data_size.next_multiple_of(DIGEST_LEN); - // pub_input layout: n_sigs(1) + slice_hash(8) + slot_low(1) + slot_high(1) - // + message + merkle_chunks_for_slot + bytecode_claim_padded + bytecode_hash(8) - let pub_input_size = - 1 + DIGEST_LEN + 2 + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + claim_data_size_padded + DIGEST_LEN; + // pub_input layout: n_sigs(1) + slice_hash(8) + message + slot_low(1) + slot_high(1) + // + merkle_chunks_for_slot + tweaks_hash(8) + bytecode_claim_padded + bytecode_hash(8) + let pub_input_size = 1 + + DIGEST_LEN + + 2 + + MESSAGE_LEN_FE + + N_MERKLE_CHUNKS_FOR_SLOT + + DIGEST_LEN + + claim_data_size_padded + + DIGEST_LEN; let inner_public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); let replacements = build_replacements( inner_program_log_size, @@ -350,6 +356,10 @@ fn build_replacements( replacements.insert("LOG_LIFETIME_PLACEHOLDER".to_string(), LOG_LIFETIME.to_string()); replacements.insert("MESSAGE_LEN_PLACEHOLDER".to_string(), MESSAGE_LEN_FE.to_string()); replacements.insert("RANDOMNESS_LEN_PLACEHOLDER".to_string(), RANDOMNESS_LEN_FE.to_string()); + replacements.insert( + "PUBLIC_PARAM_LEN_FE_PLACEHOLDER".to_string(), + PUBLIC_PARAM_LEN_FE.to_string(), + ); replacements.insert( "MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER".to_string(), MERKLE_LEVELS_PER_CHUNK_FOR_SLOT.to_string(), diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 9fc484f98..927bcae0c 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -7,7 +7,7 @@ use lean_prover::verify_execution::verify_execution; use lean_vm::*; use tracing::instrument; use utils::{build_prover_state, get_poseidon16, poseidon_compress_slice, poseidon16_compress_pair}; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, SIG_SIZE_FE, XmssPublicKey, XmssSignature, slot_to_field_elements}; +use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUB_KEY_FLAT_SIZE, SIG_SIZE_FE, V, W, XmssPublicKey, XmssSignature}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -19,6 +19,23 @@ mod compilation; const MERKLE_LEVELS_PER_CHUNK_FOR_SLOT: usize = 4; const N_MERKLE_CHUNKS_FOR_SLOT: usize = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK_FOR_SLOT; +const CHAIN_LENGTH: usize = 1 << W; + +// Tweak types (must match xmss crate) +const TWEAK_TYPE_CHAIN: usize = 0; +const TWEAK_TYPE_WOTS_PK: usize = 1; +const TWEAK_TYPE_MERKLE: usize = 2; +const TWEAK_TYPE_ENCODING: usize = 3; + +/// Number of 2-FE tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + (V-1) wots_pk + LOG_LIFETIME merkle +const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME; +/// Size of the tweak table in field elements (2 FE per tweak) +const TWEAK_TABLE_SIZE_FE_PADDED: usize = (N_TWEAKS * 2).next_multiple_of(DIGEST_LEN); + +const TWEAKS_HASHING_USE_IV: bool = false; // fixed size → no IV needed + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Digest(pub [F; DIGEST_LEN]); #[derive(Debug, Clone)] pub struct AggregationTopology { @@ -33,9 +50,51 @@ pub(crate) fn count_signers(topology: &AggregationTopology, overlap: usize) -> u topology.raw_xmss + child_count - overlap * n_overlaps } -pub fn hash_pubkeys(pub_keys: &[XmssPublicKey]) -> [F; DIGEST_LEN] { - let flat: Vec = pub_keys.iter().flat_map(|pk| pk.merkle_root.iter().copied()).collect(); - poseidon_compress_slice(&flat, true) +pub fn hash_pubkeys(pub_keys: &[XmssPublicKey]) -> Digest { + let flat: Vec = pub_keys.iter().flat_map(|pk| pk.flaten().into_iter()).collect(); + Digest(poseidon_compress_slice(&flat, true)) +} + +fn make_tweak_values(tweak_type: usize, sub_position: usize, index: u32) -> [F; 2] { + let index_lo = (index & 0xFFFF) as usize; + let index_hi = (index >> 16) as usize; + [ + F::from_usize((tweak_type << 26) + (index_hi << 10) + sub_position), + F::from_usize(index_lo), + ] +} + +/// Build a flat tweak table for the given slot. Layout: +/// encoding_tweak(2) | chain_tweaks(V * CHAIN_LENGTH * 2) | wots_pk_tweaks((V-1) * 2) | merkle_tweaks(LOG_LIFETIME * 2) +fn compute_tweak_table(slot: u32) -> Vec { + let mut table = Vec::new(); + + // Encoding tweak + let tw = make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot); + table.extend_from_slice(&tw); + + // Chain tweaks: for chain i, step s → make_tweak(CHAIN, i*CHAIN_LENGTH + s, slot) + for i in 0..V { + for s in 0..CHAIN_LENGTH { + let tw = make_tweak_values(TWEAK_TYPE_CHAIN, i * CHAIN_LENGTH + s, slot); + table.extend_from_slice(&tw); + } + } + + // WOTS_PK tweaks: for sub_pos p = 0..V-2 + for p in 0..V - 1 { + let tw = make_tweak_values(TWEAK_TYPE_WOTS_PK, p, slot); + table.extend_from_slice(&tw); + } + + // Merkle tweaks: for level 0..LOG_LIFETIME-1 + for level in 0..LOG_LIFETIME { + let parent_index = ((slot as u64) >> (level + 1)) as u32; + let tw = make_tweak_values(TWEAK_TYPE_MERKLE, level + 1, parent_index); + table.extend_from_slice(&tw); + } + table.resize(TWEAK_TABLE_SIZE_FE_PADDED, F::ZERO); + table } fn compute_merkle_chunks_for_slot(slot: u32) -> Vec { @@ -59,6 +118,7 @@ fn build_non_reserved_public_input( slice_hash: &[F; DIGEST_LEN], message: &[F; MESSAGE_LEN_FE], slot: u32, + tweaks_hash: &[F; DIGEST_LEN], bytecode_claim_output: &[F], bytecode_hash: &[F; DIGEST_LEN], ) -> Vec { @@ -66,10 +126,10 @@ fn build_non_reserved_public_input( pi.push(F::from_usize(n_sigs)); pi.extend_from_slice(slice_hash); pi.extend_from_slice(message); - let [slot_lo, slot_hi] = slot_to_field_elements(slot); - pi.push(slot_lo); - pi.push(slot_hi); + pi.push(F::from_usize((slot & 0xFFFF) as usize)); + pi.push(F::from_usize(((slot >> 16) & 0xFFFF) as usize)); pi.extend(compute_merkle_chunks_for_slot(slot)); + pi.extend_from_slice(tweaks_hash); pi.extend_from_slice(bytecode_claim_output); pi.extend(std::iter::repeat_n( F::ZERO, @@ -131,12 +191,15 @@ impl AggregatedXMSS { assert_eq!(bytecode_claim_output.len(), bytecode_claim_size); let slice_hash = hash_pubkeys(pub_keys); + let tweak_table = compute_tweak_table(slot); + let tweaks_hash = poseidon_compress_slice(&tweak_table, TWEAKS_HASHING_USE_IV); build_non_reserved_public_input( pub_keys.len(), - &slice_hash, + &slice_hash.0, message, slot, + &tweaks_hash, &bytecode_claim_output, &bytecode.hash, ) @@ -167,7 +230,7 @@ pub fn xmss_aggregate( log_inv_rate: usize, ) -> (Vec, AggregatedXMSS) { raw_xmss.sort_by(|(a, _), (b, _)| a.cmp(b)); - raw_xmss.dedup_by(|(a, _), (b, _)| a.merkle_root == b.merkle_root); + raw_xmss.dedup_by(|(a, _), (b, _)| a == b); let n_recursions = children.len(); let raw_count = raw_xmss.len(); @@ -187,6 +250,10 @@ pub fn xmss_aggregate( global_pub_keys.dedup(); let n_sigs = global_pub_keys.len(); + // Compute tweak table and its hash + let tweak_table = compute_tweak_table(slot); + let tweaks_hash = poseidon_compress_slice(&tweak_table, TWEAKS_HASHING_USE_IV); + // Verify child proofs let mut child_pub_inputs = vec![]; let mut child_bytecode_evals = vec![]; @@ -201,7 +268,7 @@ pub fn xmss_aggregate( // Bytecode sumcheck reduction let (bytecode_claim_output, bytecode_point, final_sumcheck_transcript) = if n_recursions > 0 { - let bytecode_claim_offset = 1 + DIGEST_LEN + 2 + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT; + let bytecode_claim_offset = 1 + DIGEST_LEN + 2 + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + DIGEST_LEN; let mut claims = vec![]; for (i, _child) in children.iter().enumerate() { let first_claim = extract_bytecode_claim_from_public_input( @@ -274,18 +341,19 @@ pub fn xmss_aggregate( let slice_hash = hash_pubkeys(&global_pub_keys); let non_reserved_public_input = build_non_reserved_public_input( n_sigs, - &slice_hash, + &slice_hash.0, message, slot, + &tweaks_hash, &bytecode_claim_output, &bytecode.hash, ); let public_memory = build_public_memory(&non_reserved_public_input); // Build private input - // Layout: [n_recursions, n_dup, ptr_pubkeys, ptr_source_0..n_recursions, ptr_bytecode_sumcheck, - // global_pubkeys, dup_pubkeys, source_blocks..., bytecode_sumcheck_proof] - let header_size = n_recursions + 5; + // Layout: [n_recursions, n_dup, ptr_pubkeys, ptr_tweak_table, ptr_source_0..n_recursions, ptr_bytecode_sumcheck, + // global_pubkeys, dup_pubkeys, tweak_table, source_blocks..., bytecode_sumcheck_proof] + let header_size = n_recursions + 6; let pubkeys_start = public_memory.len() + header_size; // Build source blocks (also discovers duplicate pub_keys) @@ -308,15 +376,15 @@ pub fn xmss_aggregate( } // Sources 1..n_recursions: recursive children - for (i, (child_pub_keys, _)) in children.iter().enumerate() { + for (i, (child_pub_keys, _child)) in children.iter().enumerate() { let mut block = vec![F::from_usize(child_pub_keys.len())]; - for pubkey in *child_pub_keys { - if claimed.insert(pubkey.clone()) { - let pos = global_pub_keys.binary_search(pubkey).unwrap(); + for key in *child_pub_keys { + if claimed.insert(key.clone()) { + let pos = global_pub_keys.binary_search(key).unwrap(); block.push(F::from_usize(pos)); } else { block.push(F::from_usize(n_sigs + dup_pub_keys.len())); - dup_pub_keys.push(pubkey.clone()); + dup_pub_keys.push(key.clone()); } } @@ -332,10 +400,11 @@ pub fn xmss_aggregate( } let n_dup = dup_pub_keys.len(); - let pubkeys_block_size = n_sigs * DIGEST_LEN + n_dup * DIGEST_LEN; + let pubkeys_block_size = n_sigs * PUB_KEY_FLAT_SIZE + n_dup * PUB_KEY_FLAT_SIZE; + let tweak_table_ptr = pubkeys_start + pubkeys_block_size; // Compute absolute memory addresses for each source block - let sources_start = pubkeys_start + pubkeys_block_size; + let sources_start = tweak_table_ptr + TWEAK_TABLE_SIZE_FE_PADDED; let mut offset = sources_start; let mut source_ptrs: Vec = vec![]; for block in &source_blocks { @@ -344,10 +413,12 @@ pub fn xmss_aggregate( } let bytecode_sumcheck_proof_ptr = offset; - let mut private_input = vec![]; - private_input.push(F::from_usize(n_recursions)); - private_input.push(F::from_usize(n_dup)); - private_input.push(F::from_usize(pubkeys_start)); + let mut private_input = vec![ + F::from_usize(n_recursions), + F::from_usize(n_dup), + F::from_usize(pubkeys_start), + F::from_usize(tweak_table_ptr), + ]; for &ptr in &source_ptrs { private_input.push(F::from_usize(ptr)); } @@ -355,18 +426,18 @@ pub fn xmss_aggregate( assert_eq!(private_input.len(), header_size); for pk in &global_pub_keys { - private_input.extend_from_slice(&pk.merkle_root); + private_input.extend_from_slice(&pk.flaten()); } for pk in &dup_pub_keys { - private_input.extend_from_slice(&pk.merkle_root); + private_input.extend_from_slice(&pk.flaten()); } + private_input.extend_from_slice(&tweak_table); for block in &source_blocks { private_input.extend_from_slice(block); } private_input.extend_from_slice(&final_sumcheck_transcript); // Build Merkle paths from all child proofs (one Vec per hint_merkle call in whir.py) - // Each opening produces two entries: leaf_data, then the flattened path. let merkle_paths: Vec> = child_raw_proofs .iter() .flat_map(|p| p.merkle_openings.iter()) diff --git a/crates/rec_aggregation/utils.py b/crates/rec_aggregation/utils.py index 18242cebd..9d2989c82 100644 --- a/crates/rec_aggregation/utils.py +++ b/crates/rec_aggregation/utils.py @@ -344,6 +344,13 @@ def copy_8(a, b): return +@inline +def copy_9(a, b): + dot_product_ee(a, ONE_EF_PTR, b) + dot_product_ee(a + (9 - DIM), ONE_EF_PTR, b + (9 - DIM)) + return + + @inline def copy_16(a, b): dot_product_ee(a, ONE_EF_PTR, b) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 88ecdce93..d2d28dbc8 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -9,124 +9,236 @@ LOG_LIFETIME = LOG_LIFETIME_PLACEHOLDER MESSAGE_LEN = MESSAGE_LEN_PLACEHOLDER RANDOMNESS_LEN = RANDOMNESS_LEN_PLACEHOLDER -SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * DIGEST_LEN -NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) # 24 should be divisible by W (works for W=2,3,4) +PUBLIC_PARAM_LEN_FE = PUBLIC_PARAM_LEN_FE_PLACEHOLDER +PUB_KEY_SIZE = DIM + PUBLIC_PARAM_LEN_FE +PP_IN_LEFT = DIGEST_LEN - DIM +SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * DIM +NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK +# Tweak table layout +TWEAK_LEN = 2 +N_TWEAKS = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME +TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(N_TWEAKS * TWEAK_LEN, DIGEST_LEN) +TWEAK_ENCODING_OFFSET = 0 +TWEAK_CHAIN_OFFSET = 1 * TWEAK_LEN +TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN +TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + (V - 1) * TWEAK_LEN + +# Buffer size for the hash-chaining trick: 3 prefix + DIGEST_LEN hash output. +# Hash output goes to buf + 3, then buf[0..3] is set to the prefix (pp or tweak). +# buf[0..8] = [prefix(3) | hash[0..5]] can then be used directly as the next hash input, +# avoiding a separate Array(DIGEST_LEN) allocation and copy_5. +BUF_SIZE = 3 + DIGEST_LEN + + +@inline +def build_left_fn(pp, data, out): + out[0] = pp[0] + out[1] = pp[1] + out[2] = pp[2] + copy_5(data, out + 3) + return + @inline -def xmss_verify(merkle_root, message, signature, slot_lo, slot_hi, merkle_chunks): - # signature: randomness | chain_tips | merkle_path - # return the hashed xmss public key +def build_right_fn(pp3, tweak, data, out): + out[0] = pp3 + out[1] = tweak[0] + out[2] = tweak[1] + copy_5(data, out + 3) + return + + +@inline +def build_right_chain_fn(pp3, tweak, out): + # Chain hash: data is all zeros + out[0] = pp3 + out[1] = tweak[0] + out[2] = tweak[1] + copy_5(ZERO_VEC_PTR, out + 3) + return + + +@inline +def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): + # pub_key: PUB_KEY_SIZE FE = merkle_root(DIM) | public_param(PUBLIC_PARAM_LEN_FE) + # signature: randomness(RANDOMNESS_LEN) | chain_tips(V * DIM) | merkle_path(LOG_LIFETIME * DIM) + + public_param = pub_key + DIM + pp3 = public_param[PP_IN_LEFT] randomness = signature chain_starts = signature + RANDOMNESS_LEN - merkle_path = chain_starts + V * DIGEST_LEN - - # 1) We encode message_hash + randomness into the layer of the hypercube with target sum = TARGET_SUM + merkle_path = chain_starts + V * DIM + # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) + # poseidon16_compress(pre_compressed, pub_key[0:8]) + # poseidon16_compress(intermediate, [pub_key[8] | zeros]) + encoding_tweak = tweak_table + TWEAK_ENCODING_OFFSET a_input_right = Array(DIGEST_LEN) - b_input = Array(DIGEST_LEN * 2) a_input_right[0] = message[DIGEST_LEN] - copy_7(randomness, a_input_right + 1) - poseidon16_compress(message, a_input_right, b_input) - b_input[DIGEST_LEN] = slot_lo - b_input[DIGEST_LEN + 1] = slot_hi - copy_6(merkle_root, b_input + DIGEST_LEN + 2) + copy_5(randomness, a_input_right + 1) + a_input_right[1 + RANDOMNESS_LEN] = encoding_tweak[0] + a_input_right[1 + RANDOMNESS_LEN + 1] = encoding_tweak[1] + pre_compressed = Array(DIGEST_LEN) + poseidon16_compress(message, a_input_right, pre_compressed) + + pp_input = Array(DIGEST_LEN) + pp_input[0] = public_param[0] + pp_input[1] = public_param[1] + pp_input[2] = public_param[2] + pp_input[3] = pp3 + pp_input[4] = 0 + pp_input[5] = 0 + pp_input[6] = 0 + pp_input[7] = 0 encoding_fe = Array(DIGEST_LEN) - poseidon16_compress(b_input, b_input + DIGEST_LEN, encoding_fe) + poseidon16_compress(pre_compressed, pp_input, encoding_fe) - encoding = Array(NUM_ENCODING_FE * 24 / (2 * W)) + encoding = Array(NUM_ENCODING_FE * 24 / W) remaining = Array(NUM_ENCODING_FE) - hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, 2 * W) + # TODO: decompose by chunks of 2.w bits (or even 3.w bits) and use a big match on the w^2 (or w^3) possibilities + hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, W) # check that the decomposition is correct for i in unroll(0, NUM_ENCODING_FE): - for j in unroll(0, 24 / (2 * W)): - assert encoding[i * (24 / (2 * W)) + j] < CHAIN_LENGTH**2 + for j in unroll(0, 24 / W): + assert encoding[i * (24 / W) + j] < CHAIN_LENGTH - assert remaining[i] < 2**7 - 1 # ensures uniformity + prevent overflow + assert remaining[i] < 2**7 - 1 partial_sum: Mut = remaining[i] * 2**24 - for j in unroll(0, 24 / (2 * W)): - partial_sum += encoding[i * (24 / (2 * W)) + j] * (CHAIN_LENGTH**2) ** j + for j in unroll(0, 24 / W): + partial_sum += encoding[i * (24 / W) + j] * CHAIN_LENGTH**j assert partial_sum == encoding_fe[i] - # grinding - debug_assert(V_GRINDING % 2 == 0) - debug_assert(V % 2 == 0) - for i in unroll(V / 2, (V + V_GRINDING) / 2): - assert encoding[i] == CHAIN_LENGTH**2 - 1 + # we need to check the target sum + target_sum: Mut = encoding[0] + for i in unroll(1, V): + target_sum += encoding[i] + assert target_sum == TARGET_SUM - target_sum: Mut = 0 + # grinding + for i in unroll(V, V + V_GRINDING): + assert encoding[i] == CHAIN_LENGTH - 1 + # 2) Chain hashes -> recover WOTS public key wots_public_key = Array(V * DIGEST_LEN) + MAX_CHAIN_HASHES = CHAIN_LENGTH - 1 + + for i in unroll(0, V): + num_hashes = (CHAIN_LENGTH - 1) - encoding[i] + chain_start = chain_starts + i * DIM + chain_end = wots_public_key + i * DIGEST_LEN + chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN + + # Pre-allocate all buffers (constant allocation regardless of num_hashes) + ch_left = Array(DIGEST_LEN) + ch_right = Array(DIGEST_LEN) + ch_bufs = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) + ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) + ch_rights = Array((MAX_CHAIN_HASHES - 1) * DIGEST_LEN) + ch_right_last = Array(DIGEST_LEN) - local_zero_buff = Array(DIGEST_LEN) - set_to_8_zeros(local_zero_buff) - - for i in unroll(0, V / 2): - # num_hashes = (CHAIN_LENGTH - 1) - encoding[i] - chain_start = chain_starts + i * (DIGEST_LEN * 2) - chain_end = wots_public_key + i * (DIGEST_LEN * 2) - pair_chain_length_sum_ptr = Array(1) match_range( - encoding[i], range(0, CHAIN_LENGTH**2), lambda n: chain_hash(chain_start, n, chain_end, pair_chain_length_sum_ptr, local_zero_buff) + num_hashes, + range(0, 1), + lambda _: copy_5(chain_start, chain_end), + range(1, CHAIN_LENGTH), + lambda n: chain_hash_pa(chain_start, n, chain_end, public_param, pp3, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last), ) - target_sum += pair_chain_length_sum_ptr[0] - - assert target_sum == TARGET_SUM - - wots_pubkey_hashed = slice_hash(wots_public_key, V) - xmss_merkle_verify(wots_pubkey_hashed, merkle_path, merkle_chunks, merkle_root) + # 3) Hash WOTS public key + wots_pk_tweaks = tweak_table + TWEAK_WOTS_PK_OFFSET + expected_leaf = wots_pk_hash(wots_public_key, public_param, pp3, wots_pk_tweaks) + # 4) Merkle verification + merkle_tweaks = tweak_table + TWEAK_MERKLE_OFFSET + xmss_merkle_verify(expected_leaf, merkle_path, merkle_chunks, pub_key, public_param, pp3, merkle_tweaks) return @inline -def chain_hash(input_left, n, output_left, pair_chain_length_sum_ptr, local_zero_buff): - debug_assert(n < CHAIN_LENGTH**2) +def chain_hash_pa(input, n, output, public_param, pp3, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last): + # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility) + starting_step = CHAIN_LENGTH - 1 - n - raw_left = n % CHAIN_LENGTH - raw_right = (n - raw_left) / CHAIN_LENGTH + # First hash: build left and right from scratch + build_left_fn(public_param, input, ch_left) + build_right_chain_fn(pp3, chain_i_tweaks + starting_step * TWEAK_LEN, ch_right) - n_left = (CHAIN_LENGTH - 1) - raw_left - if n_left == 0: - copy_8(input_left, output_left) - elif n_left == 1: - poseidon16_compress(input_left, local_zero_buff, output_left) - else: - states_left = Array((n_left - 1) * DIGEST_LEN) - poseidon16_compress(input_left, local_zero_buff, states_left) - for i in unroll(1, n_left - 1): - poseidon16_compress(states_left + (i - 1) * DIGEST_LEN, local_zero_buff, states_left + i * DIGEST_LEN) - poseidon16_compress(states_left + (n_left - 2) * DIGEST_LEN, local_zero_buff, output_left) - - n_right = (CHAIN_LENGTH - 1) - raw_right - debug_assert(raw_right < CHAIN_LENGTH) - input_right = input_left + DIGEST_LEN - output_right = output_left + DIGEST_LEN - if n_right == 0: - copy_8(input_right, output_right) - elif n_right == 1: - poseidon16_compress(input_right, local_zero_buff, output_right) + if n == 1: + poseidon16_compress(ch_left, ch_right, output) else: - states_right = Array((n_right - 1) * DIGEST_LEN) - poseidon16_compress(input_right, local_zero_buff, states_right) - for i in unroll(1, n_right - 1): - poseidon16_compress(states_right + (i - 1) * DIGEST_LEN, local_zero_buff, states_right + i * DIGEST_LEN) - poseidon16_compress(states_right + (n_right - 2) * DIGEST_LEN, local_zero_buff, output_right) + # Buffer trick: hash output goes to buf + 3, then pp is prepended at buf[0..3]. + ch_buf_idx[0] = ch_bufs + poseidon16_compress(ch_left, ch_right, ch_bufs + 3) + ch_bufs[0] = public_param[0] + ch_bufs[1] = public_param[1] + ch_bufs[2] = public_param[2] + + for j in unroll(1, n - 1): + ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE + cur_buf = ch_buf_idx[j] + right_j = ch_rights + (j - 1) * DIGEST_LEN + build_right_chain_fn(pp3, chain_i_tweaks + (starting_step + j) * TWEAK_LEN, right_j) + poseidon16_compress(ch_buf_idx[j - 1], right_j, cur_buf + 3) + cur_buf[0] = public_param[0] + cur_buf[1] = public_param[1] + cur_buf[2] = public_param[2] + + # Final hash + build_right_chain_fn(pp3, chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN, ch_right_last) + poseidon16_compress(ch_buf_idx[n - 2], ch_right_last, output) + return - pair_chain_length_sum_ptr[0] = raw_left + raw_right - return +def wots_pk_hash(wots_public_key, public_param, pp3, wots_pk_tweaks): + # Sequential hash over V elements at DIGEST_LEN stride + # poseidon16_compress(build_left(pp, wots[0]), build_right(tweak[0], wots[1])) -> h + # poseidon16_compress([pp | h], build_right(tweak[i], wots[i+1])) for i=1..V-2 + # Final: poseidon16_compress([pp | h], build_right(tweak[V-2], wots[V-1])) + + # First hash: build from scratch + left0 = Array(DIGEST_LEN) + build_left_fn(public_param, wots_public_key, left0) + right0 = Array(DIGEST_LEN) + build_right_fn(pp3, wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) + + # Buffer trick for intermediate states + bufs = Array((V - 2) * BUF_SIZE) + buf_indexes = Array(V - 2) + buf_indexes[0] = bufs + + poseidon16_compress(left0, right0, bufs + 3) + bufs[0] = public_param[0] + bufs[1] = public_param[1] + bufs[2] = public_param[2] + + for i in unroll(1, V - 2): + buf_indexes[i] = buf_indexes[i - 1] + BUF_SIZE + cur_buf = buf_indexes[i] + right_i = Array(DIGEST_LEN) + build_right_fn(pp3, wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) + poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + 3) + cur_buf[0] = public_param[0] + cur_buf[1] = public_param[1] + cur_buf[2] = public_param[2] + + # Final hash + result = Array(DIGEST_LEN) + right_last = Array(DIGEST_LEN) + build_right_fn(pp3, wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) + poseidon16_compress(buf_indexes[V - 3], right_last, result) + + return result @inline -def do_4_merkle_levels(b, state_in, path_chunk, state_out): - # Extract bits of b (compile-time; each division is exact so field div == integer div) +def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, pp3, merkle_tweaks_chunk): + # b encodes 4 is_left bits; path elements at DIM stride b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -135,71 +247,112 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out): r3 = (r2 - b2) / 2 b3 = r3 % 2 - temps = Array(3 * DIGEST_LEN) - - # Level 0: state_in -> temps - if b0 == 0: - poseidon16_compress(path_chunk, state_in, temps) + # Level 0: build from external state_in and path_chunk (no prior hash to reuse) + left0 = Array(DIGEST_LEN) + right0 = Array(DIGEST_LEN) + if b0 == 1: + build_left_fn(public_param, state_in, left0) + build_right_fn(pp3, merkle_tweaks_chunk, path_chunk, right0) else: - poseidon16_compress(state_in, path_chunk, temps) + build_left_fn(public_param, path_chunk, left0) + build_right_fn(pp3, merkle_tweaks_chunk, state_in, right0) + + # Buffer trick: hash output to buf + 3, then prepend prefix. + # If state goes left: buf = [pp | hash[0..5]], used as left input. + # If state goes right: buf = [pp3, tw0, tw1 | hash[0..5]], used as right input. + buf0 = Array(BUF_SIZE) + poseidon16_compress(left0, right0, buf0 + 3) # Level 1 - if b1 == 0: - poseidon16_compress(path_chunk + 1 * DIGEST_LEN, temps, temps + DIGEST_LEN) + other1 = Array(DIGEST_LEN) + buf1 = Array(BUF_SIZE) + if b1 == 1: + buf0[0] = public_param[0] + buf0[1] = public_param[1] + buf0[2] = public_param[2] + build_right_fn(pp3, merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * DIM, other1) + poseidon16_compress(buf0, other1, buf1 + 3) else: - poseidon16_compress(temps, path_chunk + 1 * DIGEST_LEN, temps + DIGEST_LEN) + buf0[0] = pp3 + buf0[1] = merkle_tweaks_chunk[1 * TWEAK_LEN] + buf0[2] = merkle_tweaks_chunk[1 * TWEAK_LEN + 1] + build_left_fn(public_param, path_chunk + 1 * DIM, other1) + poseidon16_compress(other1, buf0, buf1 + 3) # Level 2 - if b2 == 0: - poseidon16_compress(path_chunk + 2 * DIGEST_LEN, temps + DIGEST_LEN, temps + 2 * DIGEST_LEN) + other2 = Array(DIGEST_LEN) + buf2 = Array(BUF_SIZE) + if b2 == 1: + buf1[0] = public_param[0] + buf1[1] = public_param[1] + buf1[2] = public_param[2] + build_right_fn(pp3, merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * DIM, other2) + poseidon16_compress(buf1, other2, buf2 + 3) else: - poseidon16_compress(temps + DIGEST_LEN, path_chunk + 2 * DIGEST_LEN, temps + 2 * DIGEST_LEN) - - # Level 3: -> state_out - if b3 == 0: - poseidon16_compress(path_chunk + 3 * DIGEST_LEN, temps + 2 * DIGEST_LEN, state_out) + buf1[0] = pp3 + buf1[1] = merkle_tweaks_chunk[2 * TWEAK_LEN] + buf1[2] = merkle_tweaks_chunk[2 * TWEAK_LEN + 1] + build_left_fn(public_param, path_chunk + 2 * DIM, other2) + poseidon16_compress(other2, buf1, buf2 + 3) + + # Level 3 -> state_out + other3 = Array(DIGEST_LEN) + if b3 == 1: + buf2[0] = public_param[0] + buf2[1] = public_param[1] + buf2[2] = public_param[2] + build_right_fn(pp3, merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * DIM, other3) + poseidon16_compress(buf2, other3, state_out) else: - poseidon16_compress(temps + 2 * DIGEST_LEN, path_chunk + 3 * DIGEST_LEN, state_out) + buf2[0] = pp3 + buf2[1] = merkle_tweaks_chunk[3 * TWEAK_LEN] + buf2[2] = merkle_tweaks_chunk[3 * TWEAK_LEN + 1] + build_left_fn(public_param, path_chunk + 3 * DIM, other3) + poseidon16_compress(other3, buf2, state_out) return @inline -def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root): +def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, public_param, pp3, merkle_tweaks): states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) - # First chunk: leaf_digest -> states - match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states)) + # First chunk + match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states, public_param, pp3, merkle_tweaks)) - # Middle chunks + state_indexes = Array(N_MERKLE_CHUNKS - 1) + state_indexes[0] = states for j in unroll(1, N_MERKLE_CHUNKS - 1): + state_indexes[j] = state_indexes[j - 1] + DIGEST_LEN match_range( merkle_chunks[j], range(0, 16), lambda b: do_4_merkle_levels( - b, states + (j - 1) * DIGEST_LEN, merkle_path + j * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, states + j * DIGEST_LEN + b, + state_indexes[j - 1], + merkle_path + j * MERKLE_LEVELS_PER_CHUNK * DIM, + state_indexes[j], + public_param, + pp3, + merkle_tweaks + j * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, ), ) - # Last chunk: -> expected_root + # Last chunk: write to temp, then assert match with expected_root (write-once) + last_output = Array(DIGEST_LEN) match_range( merkle_chunks[N_MERKLE_CHUNKS - 1], range(0, 16), lambda b: do_4_merkle_levels( - b, states + (N_MERKLE_CHUNKS - 2) * DIGEST_LEN, merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, expected_root + b, + state_indexes[N_MERKLE_CHUNKS - 2], + merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * DIM, + last_output, + public_param, + pp3, + merkle_tweaks + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, ), ) - return - -@inline -def copy_7(x, y): - dot_product_ee(x, ONE_EF_PTR, y) - dot_product_ee(x + (7 - DIM), ONE_EF_PTR, y + (7 - DIM)) - return - - -@inline -def copy_6(x, y): - dot_product_ee(x, ONE_EF_PTR, y) - y[DIM] = x[DIM] + # Assert computed root == expected (first DIM elements) + copy_5(last_output, expected_root) return diff --git a/crates/xmss/Cargo.toml b/crates/xmss/Cargo.toml index 6b54442f9..86c6ed3b7 100644 --- a/crates/xmss/Cargo.toml +++ b/crates/xmss/Cargo.toml @@ -14,6 +14,7 @@ backend.workspace = true serde.workspace = true lz4_flex.workspace = true postcard.workspace = true +sha3.workspace = true [dev-dependencies] postcard.workspace = true \ No newline at end of file diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 7e5fe8d21..19138d2ef 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -6,10 +6,13 @@ pub use wots::*; mod xmss; pub use xmss::*; -pub(crate) const DIGEST_SIZE: usize = 8; +pub(crate) const DIGEST_SIZE: usize = 5; +pub(crate) const TWEAK_LEN: usize = 2; type F = KoalaBear; type Digest = [F; DIGEST_SIZE]; +type PublicParam = [F; PUBLIC_PARAM_LEN_FE]; +type Randomness = [F; RANDOMNESS_LEN_FE]; // WOTS pub const V: usize = 42; @@ -19,8 +22,59 @@ pub const NUM_CHAIN_HASHES: usize = 110; pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; pub const V_GRINDING: usize = 2; pub const LOG_LIFETIME: usize = 32; -pub const RANDOMNESS_LEN_FE: usize = 7; +pub const RANDOMNESS_LEN_FE: usize = 5; pub const MESSAGE_LEN_FE: usize = 9; -pub const TRUNCATED_MERKLE_ROOT_LEN_FE: usize = 6; +pub const PUBLIC_PARAM_LEN_FE: usize = 4; +pub const PP_IN_LEFT: usize = 8 - DIGEST_SIZE; // = 3 +pub const PUB_KEY_FLAT_SIZE: usize = DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 + +const _: () = assert!(PP_IN_LEFT + DIGEST_SIZE == 8); +const _: () = assert!((PUBLIC_PARAM_LEN_FE - PP_IN_LEFT) + TWEAK_LEN + DIGEST_SIZE == 8); pub const SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + (V + LOG_LIFETIME) * DIGEST_SIZE; + +// Tweak: domain separation within each hash. +pub(crate) const TWEAK_TYPE_CHAIN: usize = 0; +pub(crate) const TWEAK_TYPE_WOTS_PK: usize = 1; +pub(crate) const TWEAK_TYPE_MERKLE: usize = 2; +pub(crate) const TWEAK_TYPE_ENCODING: usize = 3; + +use backend::PrimeCharacteristicRing; +use utils::poseidon16_compress; + +/// index = slot or node_index in Merkle tree +pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> [F; TWEAK_LEN] { + assert!(tweak_type < 4); + assert!(sub_position < 1 << 10); + let index_lo = (index & 0xFFFF) as usize; + let index_hi = (index >> 16) as usize; + [ + F::from_usize((tweak_type << 26) + (index_hi << 10) + sub_position), + F::from_usize(index_lo), + ] +} + +/// [public_param[0..3](3) | data(5)] +pub(crate) fn build_left(public_param: &PublicParam, data: &Digest) -> [F; 8] { + let mut left = [F::default(); 8]; + left[..PP_IN_LEFT].copy_from_slice(&public_param[..PP_IN_LEFT]); + left[PP_IN_LEFT..].copy_from_slice(data); + left +} + +/// [pp[3](1) | tweak(2) | data(5)] +pub(crate) fn build_right(public_param: &PublicParam, tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { + let pp_right = PUBLIC_PARAM_LEN_FE - PP_IN_LEFT; + let mut right = [F::default(); 8]; + right[..pp_right].copy_from_slice(&public_param[PP_IN_LEFT..]); + right[pp_right..pp_right + TWEAK_LEN].copy_from_slice(&tweak); + right[pp_right + TWEAK_LEN..].copy_from_slice(data); + right +} + +fn poseidon16_compress_with_trace(a: [F; 8], b: [F; 8], poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>) -> [F; 8] { + let input: [F; 16] = [a, b].concat().try_into().unwrap(); + let output = poseidon16_compress(input); + poseidon_16_trace.push((input, output)); + output +} diff --git a/crates/xmss/src/signers_cache.rs b/crates/xmss/src/signers_cache.rs index adaa1038c..aef4a2f00 100644 --- a/crates/xmss/src/signers_cache.rs +++ b/crates/xmss/src/signers_cache.rs @@ -36,6 +36,7 @@ fn cache_footprint(first_pubkey: &XmssPublicKey) -> u64 { BENCHMARK_SLOT.hash(&mut hasher); message_for_benchmark().hash(&mut hasher); first_pubkey.merkle_root.hash(&mut hasher); + first_pubkey.public_param.hash(&mut hasher); hasher.finish() } diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index e34bf9a70..2f96b8224 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -21,18 +21,20 @@ pub struct WotsSignature { bound(serialize = "F: Serialize", deserialize = "F: Deserialize<'de>") )] pub chain_tips: [Digest; V], - pub randomness: [F; RANDOMNESS_LEN_FE], + pub randomness: Randomness, } impl WotsSecretKey { - pub fn random(rng: &mut impl CryptoRng) -> Self { - Self::new(rng.random()) + pub fn random(rng: &mut impl CryptoRng, public_param: PublicParam, slot: u32) -> Self { + Self::new(rng.random(), public_param, slot) } - pub fn new(pre_images: [Digest; V]) -> Self { + pub fn new(pre_images: [Digest; V], public_param: PublicParam, slot: u32) -> Self { Self { pre_images, - public_key: WotsPublicKey(std::array::from_fn(|i| iterate_hash(&pre_images[i], CHAIN_LENGTH - 1))), + public_key: WotsPublicKey(std::array::from_fn(|i| { + iterate_hash(&pre_images[i], CHAIN_LENGTH - 1, public_param, slot, i, 0) + })), } } @@ -44,16 +46,24 @@ impl WotsSecretKey { &self, message: &[F; MESSAGE_LEN_FE], slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - randomness: [F; RANDOMNESS_LEN_FE], + xmss_pub_key: &XmssPublicKey, + randomness: Randomness, ) -> WotsSignature { - let encoding = wots_encode(message, slot, truncated_merkle_root, &randomness).unwrap(); - self.sign_with_encoding(randomness, &encoding) + let encoding = wots_encode(message, slot, xmss_pub_key, &randomness).unwrap(); + self.sign_with_encoding(randomness, &encoding, xmss_pub_key.public_param, slot) } - fn sign_with_encoding(&self, randomness: [F; RANDOMNESS_LEN_FE], encoding: &[u8; V]) -> WotsSignature { + fn sign_with_encoding( + &self, + randomness: Randomness, + encoding: &[u8; V], + public_param: PublicParam, + slot: u32, + ) -> WotsSignature { WotsSignature { - chain_tips: std::array::from_fn(|i| iterate_hash(&self.pre_images[i], encoding[i] as usize)), + chain_tips: std::array::from_fn(|i| { + iterate_hash(&self.pre_images[i], encoding[i] as usize, public_param, slot, i, 0) + }), randomness, } } @@ -64,40 +74,89 @@ impl WotsSignature { &self, message: &[F; MESSAGE_LEN_FE], slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], + xmss_pub_key: &XmssPublicKey, signature: &Self, ) -> Option { - let encoding = wots_encode(message, slot, truncated_merkle_root, &signature.randomness)?; + let encoding = wots_encode(message, slot, xmss_pub_key, &signature.randomness)?; Some(WotsPublicKey(std::array::from_fn(|i| { - iterate_hash(&self.chain_tips[i], CHAIN_LENGTH - 1 - encoding[i] as usize) + iterate_hash( + &self.chain_tips[i], + CHAIN_LENGTH - 1 - encoding[i] as usize, + xmss_pub_key.public_param, + slot, + i, + encoding[i] as usize, + ) }))) } } impl WotsPublicKey { - pub fn hash(&self) -> Digest { - let init = poseidon16_compress_pair(&self.0[0], &self.0[1]); - self.0[2..] - .iter() - .fold(init, |digest, chunk| poseidon16_compress_pair(&digest, chunk)) + pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { + let left = build_left(&public_param, &self.0[0]); + let right = build_right(&public_param, make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot), &self.0[1]); + let mut running_hash: Digest = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap(); + for i in 2..V { + let left = build_left(&public_param, &running_hash); + let right = build_right(&public_param, make_tweak(TWEAK_TYPE_WOTS_PK, i - 1, slot), &self.0[i]); + running_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap(); + } + running_hash } } -pub fn iterate_hash(a: &Digest, n: usize) -> Digest { - (0..n).fold(*a, |acc, _| poseidon16_compress_pair(&acc, &Default::default())) +pub fn iterate_hash( + a: &Digest, + n: usize, + public_param: PublicParam, + slot: u32, + chain_index: usize, + start_step: usize, +) -> Digest { + (0..n).fold(*a, |acc, j| { + let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); + let left = build_left(&public_param, &acc); + let right = build_right(&public_param, tweak, &Default::default()); + poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap() + }) +} + +pub fn iterate_hash_with_poseidon_trace( + a: &Digest, + n: usize, + poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>, + public_param: PublicParam, + slot: u32, + chain_index: usize, + start_step: usize, +) -> Digest { + (0..n).fold(*a, |acc, j| { + let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); + let left = build_left(&public_param, &acc); + let right = build_right(&public_param, tweak, &Default::default()); + poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..DIGEST_SIZE] + .try_into() + .unwrap() + }) } pub fn find_randomness_for_wots_encoding( message: &[F; MESSAGE_LEN_FE], slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], + xmss_pub_key: &XmssPublicKey, rng: &mut impl CryptoRng, -) -> ([F; RANDOMNESS_LEN_FE], [u8; V], usize) { +) -> (Randomness, [u8; V], usize) { let mut num_iters = 0; loop { num_iters += 1; let randomness = rng.random(); - if let Some(encoding) = wots_encode(message, slot, truncated_merkle_root, &randomness) { + if let Some(encoding) = wots_encode(message, slot, xmss_pub_key, &randomness) { return (randomness, encoding, num_iters); } } @@ -106,24 +165,29 @@ pub fn find_randomness_for_wots_encoding( pub fn wots_encode( message: &[F; MESSAGE_LEN_FE], slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - randomness: &[F; RANDOMNESS_LEN_FE], + xmss_pub_key: &XmssPublicKey, + randomness: &Randomness, ) -> Option<[u8; V]> { - // Encode slot as 2 field elements (16 bits each) - let [slot_lo, slot_hi] = slot_to_field_elements(slot); - - // A = poseidon(message (9 fe), randomness (7 fe)) - let mut a_input_right = [F::default(); 8]; - a_input_right[0] = message[8]; - a_input_right[1..1 + RANDOMNESS_LEN_FE].copy_from_slice(randomness); - let a = poseidon16_compress_pair(message[..8].try_into().unwrap(), &a_input_right); - - // B = poseidon(A (8 fe), slot (2 fe), truncated_merkle_root (6 fe)) - let mut b_input_right = [F::default(); 8]; - b_input_right[0] = slot_lo; - b_input_right[1] = slot_hi; - b_input_right[2..8].copy_from_slice(truncated_merkle_root); - let compressed = poseidon16_compress_pair(&a, &b_input_right); + wots_encode_with_poseidon_trace(message, slot, xmss_pub_key, randomness, &mut Vec::new()) +} + +pub fn wots_encode_with_poseidon_trace( + message: &[F; MESSAGE_LEN_FE], + slot: u32, + xmss_pub_key: &XmssPublicKey, + randomness: &Randomness, + poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>, +) -> Option<[u8; V]> { + let mut first_input_right = [F::default(); 8]; + first_input_right[0] = message[8]; + first_input_right[1..1 + RANDOMNESS_LEN_FE].copy_from_slice(randomness); + first_input_right[1 + RANDOMNESS_LEN_FE..].copy_from_slice(&make_tweak(TWEAK_TYPE_ENCODING, 0, slot)); + let pre_compressed = + poseidon16_compress_with_trace(message[..8].try_into().unwrap(), first_input_right, poseidon_16_trace); + + let mut pp_input = [F::default(); 8]; + pp_input[..PUBLIC_PARAM_LEN_FE].copy_from_slice(&xmss_pub_key.public_param); + let compressed = poseidon16_compress_with_trace(pre_compressed, pp_input, poseidon_16_trace); if compressed.iter().any(|&kb| kb == -F::ONE) { // ensures uniformity of encoding @@ -163,10 +227,3 @@ fn is_valid_encoding(encoding: &[u8]) -> bool { } true } - -pub fn slot_to_field_elements(slot: u32) -> [F; 2] { - [ - F::from_usize((slot & 0xFFFF) as usize), - F::from_usize(((slot >> 16) & 0xFFFF) as usize), - ] -} diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index 88f9e6f22..9daa5068f 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -1,6 +1,7 @@ use backend::*; use rand::{CryptoRng, RngExt, SeedableRng, rngs::StdRng}; use serde::{Deserialize, Serialize}; +use sha3::{Digest as Sha3Digest, Keccak256}; use utils::poseidon16_compress_pair; use crate::*; @@ -9,7 +10,8 @@ use crate::*; pub struct XmssSecretKey { pub(crate) slot_start: u32, // inclusive pub(crate) slot_end: u32, // inclusive - pub(crate) seed: [u8; 20], + pub(crate) public_param: PublicParam, + pub(crate) seed: [u8; 32], // At level l, stored indices go from (slot_start >> l) to (slot_end >> l). pub(crate) merkle_tree: Vec>, } @@ -23,25 +25,43 @@ pub struct XmssSignature { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct XmssPublicKey { pub merkle_root: Digest, + pub public_param: PublicParam, } -fn gen_wots_secret_key(seed: &[u8; 20], slot: u32) -> WotsSecretKey { - let mut rng_seed = [0u8; 32]; - rng_seed[..20].copy_from_slice(seed); - rng_seed[20] = 0x00; - rng_seed[21..25].copy_from_slice(&slot.to_le_bytes()); - let mut rng = StdRng::from_seed(rng_seed); - WotsSecretKey::random(&mut rng) +impl XmssPublicKey { + pub fn flaten(&self) -> [F; PUB_KEY_FLAT_SIZE] { + let mut output = [F::default(); PUB_KEY_FLAT_SIZE]; + output[..DIGEST_SIZE].copy_from_slice(&self.merkle_root); + output[DIGEST_SIZE..].copy_from_slice(&self.public_param); + output + } +} + +fn gen_wots_secret_key(seed: &[u8; 32], slot: u32, public_param: PublicParam) -> WotsSecretKey { + let mut hasher = Keccak256::new(); + hasher.update(b"wots_secret_key"); + hasher.update(seed); + hasher.update(slot.to_le_bytes()); + let mut rng = StdRng::from_seed(hasher.finalize().into()); + WotsSecretKey::random(&mut rng, public_param, slot) +} + +fn gen_public_param(seed: &[u8; 32]) -> PublicParam { + let mut hasher = Keccak256::new(); + hasher.update(b"public_param"); + hasher.update(seed); + let mut rng = StdRng::from_seed(hasher.finalize().into()); + rng.random() } /// Deterministic pseudo-random digest for an out-of-range tree node. -fn gen_random_node(seed: &[u8; 20], level: usize, index: u32) -> Digest { - let mut rng_seed = [0u8; 32]; - rng_seed[..20].copy_from_slice(seed); - rng_seed[20] = 0x01; - rng_seed[21] = level as u8; - rng_seed[22..26].copy_from_slice(&index.to_le_bytes()); - let mut rng = StdRng::from_seed(rng_seed); +fn gen_random_node(seed: &[u8; 32], level: usize, index: u64) -> Digest { + let mut hasher = Keccak256::new(); + hasher.update(b"random_node"); + hasher.update(seed); + hasher.update((level as u64).to_le_bytes()); + hasher.update(index.to_le_bytes()); + let mut rng = StdRng::from_seed(hasher.finalize().into()); rng.random() } @@ -51,20 +71,20 @@ pub enum XmssKeyGenError { } pub fn xmss_key_gen( - seed: [u8; 20], + seed: [u8; 32], slot_start: u32, slot_end: u32, ) -> Result<(XmssSecretKey, XmssPublicKey), XmssKeyGenError> { - if slot_start > slot_end { + if slot_start > slot_end || slot_end as u64 >= (1 << LOG_LIFETIME) { return Err(XmssKeyGenError::InvalidRange); } - let perm = default_koalabear_poseidon1_16(); + let public_param: PublicParam = gen_public_param(&seed); // Level 0: WOTS leaf hashes for slots in [slot_start, slot_end] let leaves: Vec = (slot_start..=slot_end) .into_par_iter() .map(|slot| { - let wots = gen_wots_secret_key(&seed, slot); - wots.public_key().hash() + let wots = gen_wots_secret_key(&seed, slot, public_param); + wots.public_key().hash(public_param, slot) }) .collect(); let mut merkle_tree = vec![leaves]; @@ -72,10 +92,10 @@ pub fn xmss_key_gen( // At level l, we store nodes with index in [(slot_start >> l), (slot_end >> l)]. // Children outside [slot_start, slot_end]'s subtree are replaced by gen_random_node. for level in 1..=LOG_LIFETIME { - let base = u64::from(slot_start) >> level; - let top = u64::from(slot_end) >> level; - let prev_base = u64::from(slot_start) >> (level - 1); - let prev_top = u64::from(slot_end) >> (level - 1); + let base: u64 = (slot_start as u64) >> level; + let top: u64 = (slot_end as u64) >> level; + let prev_base: u64 = (slot_start as u64) >> (level - 1); + let prev_top: u64 = (slot_end as u64) >> (level - 1); let nodes: Vec = { let prev = &merkle_tree[level - 1]; (base..=top) @@ -86,16 +106,19 @@ pub fn xmss_key_gen( let left = if left_idx >= prev_base && left_idx <= prev_top { prev[(left_idx - prev_base) as usize] } else { - assert!(left_idx < 1u64 << 32); - gen_random_node(&seed, level - 1, left_idx as u32) + gen_random_node(&seed, level - 1, left_idx) }; let right = if right_idx >= prev_base && right_idx <= prev_top { prev[(right_idx - prev_base) as usize] } else { - assert!(right_idx < 1u64 << 32); - gen_random_node(&seed, level - 1, right_idx as u32) + gen_random_node(&seed, level - 1, right_idx) }; - compress(&perm, [left, right]) + let poseidon_left = build_left(&public_param, &left); + let poseidon_right = + build_right(&public_param, make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &right); + poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..DIGEST_SIZE] + .try_into() + .unwrap() }) .collect() }; @@ -103,10 +126,12 @@ pub fn xmss_key_gen( } let pub_key = XmssPublicKey { merkle_root: merkle_tree.last().unwrap()[0], + public_param, }; let secret_key = XmssSecretKey { slot_start, slot_end, + public_param, seed, merkle_tree, }; @@ -124,9 +149,7 @@ pub fn xmss_sign( message: &[F; MESSAGE_LEN_FE], slot: u32, ) -> Result { - let merkle_root = secret_key.public_key().merkle_root; - let truncated_merkle_root = merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); - let (randomness, _, _) = find_randomness_for_wots_encoding(message, slot, truncated_merkle_root, rng); + let (randomness, _, _) = find_randomness_for_wots_encoding(message, slot, &secret_key.public_key(), rng); xmss_sign_with_randomness(secret_key, message, slot, randomness) } @@ -139,15 +162,13 @@ pub fn xmss_sign_with_randomness( if slot < secret_key.slot_start || slot > secret_key.slot_end { return Err(XmssSignatureError::SlotOutOfRange); } - let wots_secret_key = gen_wots_secret_key(&secret_key.seed, slot); - let merkle_root = secret_key.public_key().merkle_root; - let truncated_merkle_root = merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); - let wots_signature = wots_secret_key.sign_with_randomness(message, slot, &truncated_merkle_root, randomness); + let wots_secret_key = gen_wots_secret_key(&secret_key.seed, slot, secret_key.public_param); + let wots_signature = wots_secret_key.sign_with_randomness(message, slot, &secret_key.public_key(), randomness); let merkle_proof = (0..LOG_LIFETIME) .map(|level| { - let neighbour_index = (slot >> level) ^ 1; - let base = secret_key.slot_start >> level; - let top = secret_key.slot_end >> level; + let neighbour_index = ((slot as u64) >> level) ^ 1; + let base = (secret_key.slot_start as u64) >> level; + let top = (secret_key.slot_end as u64) >> level; if neighbour_index >= base && neighbour_index <= top { secret_key.merkle_tree[level][(neighbour_index - base) as usize] } else { @@ -165,6 +186,7 @@ impl XmssSecretKey { pub fn public_key(&self) -> XmssPublicKey { XmssPublicKey { merkle_root: self.merkle_tree.last().unwrap()[0], + public_param: self.public_param, } } } @@ -181,21 +203,30 @@ pub fn xmss_verify( signature: &XmssSignature, slot: u32, ) -> Result<(), XmssVerifyError> { - let truncated_merkle_root = pub_key.merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); let wots_public_key = signature .wots_signature - .recover_public_key(message, slot, &truncated_merkle_root, &signature.wots_signature) + .recover_public_key(message, slot, pub_key, &signature.wots_signature) .ok_or(XmssVerifyError::InvalidWots)?; - let mut current_hash = wots_public_key.hash(); + let mut current_hash = wots_public_key.hash(pub_key.public_param, slot); if signature.merkle_proof.len() != LOG_LIFETIME { return Err(XmssVerifyError::InvalidMerklePath); } for (level, neighbour) in signature.merkle_proof.iter().enumerate() { - let is_left = ((slot >> level) & 1) == 0; + let is_left = (((slot as u64) >> level) & 1) == 0; + let parent_index = ((slot as u64) >> (level + 1)) as u32; + let tweak = make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index); if is_left { - current_hash = poseidon16_compress_pair(¤t_hash, neighbour); + let left = build_left(&pub_key.public_param, ¤t_hash); + let right = build_right(&pub_key.public_param, tweak, neighbour); + current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap(); } else { - current_hash = poseidon16_compress_pair(neighbour, ¤t_hash); + let left = build_left(&pub_key.public_param, neighbour); + let right = build_right(&pub_key.public_param, tweak, ¤t_hash); + current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap(); } } if current_hash == pub_key.merkle_root { diff --git a/crates/xmss/tests/xmss_tests.rs b/crates/xmss/tests/xmss_tests.rs index 40bbb6377..0fb08e01d 100644 --- a/crates/xmss/tests/xmss_tests.rs +++ b/crates/xmss/tests/xmss_tests.rs @@ -6,7 +6,7 @@ type F = KoalaBear; #[test] fn test_xmss_serialize_deserialize() { - let keygen_seed: [u8; 20] = std::array::from_fn(|i| i as u8); + let keygen_seed: [u8; 32] = std::array::from_fn(|i| i as u8); let message: [F; MESSAGE_LEN_FE] = std::array::from_fn(|i| F::from_usize(i * 3 + 7)); let slot_start = 100; let slot_end = 115; @@ -28,14 +28,12 @@ fn test_xmss_serialize_deserialize() { #[test] fn keygen_sign_verify() { - let keygen_seed: [u8; 20] = std::array::from_fn(|i| i as u8); + let keygen_seed: [u8; 32] = std::array::from_fn(|i| i as u8); let message: [F; MESSAGE_LEN_FE] = std::array::from_fn(|i| F::from_usize(i * 3 + 7)); - let slot_start = 100; - let slot_end = 115; - let (sk, pk) = xmss_key_gen(keygen_seed, slot_start, slot_end).unwrap(); - for slot in slot_start..=slot_end { - let sig = xmss_sign(&mut StdRng::seed_from_u64(u64::from(slot)), &sk, &message, slot).unwrap(); + for slot in [0, 1234, u32::MAX] { + let (sk, pk) = xmss_key_gen(keygen_seed, slot.saturating_sub(1), slot.saturating_add(2)).unwrap(); + let sig = xmss_sign(&mut StdRng::seed_from_u64(slot as u64), &sk, &message, slot).unwrap(); xmss_verify(&pk, &message, &sig, slot).unwrap(); } } @@ -44,15 +42,18 @@ fn keygen_sign_verify() { #[ignore] fn encoding_grinding_bits() { let n = 100; + let xmss_pub_key = XmssPublicKey { + merkle_root: Default::default(), + public_param: Default::default(), + }; let total_iters = (0..n) .into_par_iter() .map(|i| { let message: [F; MESSAGE_LEN_FE] = Default::default(); let slot = i as u32; - let truncated_merkle_root: [F; TRUNCATED_MERKLE_ROOT_LEN_FE] = Default::default(); let mut rng = StdRng::seed_from_u64(i as u64); let (_randomness, _encoding, num_iters) = - find_randomness_for_wots_encoding(&message, slot, &truncated_merkle_root, &mut rng); + find_randomness_for_wots_encoding(&message, slot, &xmss_pub_key, &mut rng); num_iters }) .sum::(); From 9e9e7cd73bf674542f41c8d479a6dd0d7856aba8 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 5 Apr 2026 11:43:18 +0200 Subject: [PATCH 02/66] digests of 4 --- crates/rec_aggregation/main.py | 14 +- crates/rec_aggregation/src/compilation.rs | 5 +- crates/rec_aggregation/utils.py | 7 - crates/rec_aggregation/xmss_aggregate.py | 228 +++++++++++----------- crates/xmss/src/lib.rs | 20 +- crates/xmss/src/wots.rs | 8 +- crates/xmss/src/xmss.rs | 7 +- 7 files changed, 141 insertions(+), 148 deletions(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 0ee843be7..05f219a7c 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -107,19 +107,19 @@ def main(): assert idx0 < n_total buffer[idx0] = counter counter += 1 - - # Copy subset pub keys to contiguous buffer for flat hashing - sub_pubkeys = Array(n_sub * PUB_KEY_SIZE) - copy_9(all_pubkeys + idx0 * PUB_KEY_SIZE, sub_pubkeys) + pk0 = all_pubkeys + idx0 * PUB_KEY_SIZE + running_hash: Mut = Array(DIGEST_LEN) + poseidon16_compress(ZERO_VEC_PTR, pk0, running_hash) for j in dynamic_unroll(1, n_sub, log2_ceil(MAX_N_SIGS)): idx = sub_indices[j] assert idx < n_total buffer[idx] = counter counter += 1 - copy_9(all_pubkeys + idx * PUB_KEY_SIZE, sub_pubkeys + j * PUB_KEY_SIZE) - - running_hash = slice_hash_with_iv_dynamic_unroll(sub_pubkeys, n_sub * PUB_KEY_SIZE, MAX_LOG_MEMORY_SIZE) + pk = all_pubkeys + idx * PUB_KEY_SIZE + new_hash = Array(DIGEST_LEN) + poseidon16_compress(running_hash, pk, new_hash) + running_hash = new_hash non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sub, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem) copy_8(running_hash, non_reserved_inner + 1) diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index b41671d83..2682f4cd3 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -11,7 +11,9 @@ use std::sync::OnceLock; use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W}; +use xmss::{ + DIGEST_SIZE, LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, +}; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; @@ -364,6 +366,7 @@ fn build_replacements( "MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER".to_string(), MERKLE_LEVELS_PER_CHUNK_FOR_SLOT.to_string(), ); + replacements.insert("XMSS_DIGEST_SIZE_PLACEHOLDER".to_string(), DIGEST_SIZE.to_string()); // Bytecode zero eval replacements.insert( diff --git a/crates/rec_aggregation/utils.py b/crates/rec_aggregation/utils.py index 9d2989c82..18242cebd 100644 --- a/crates/rec_aggregation/utils.py +++ b/crates/rec_aggregation/utils.py @@ -344,13 +344,6 @@ def copy_8(a, b): return -@inline -def copy_9(a, b): - dot_product_ee(a, ONE_EF_PTR, b) - dot_product_ee(a + (9 - DIM), ONE_EF_PTR, b + (9 - DIM)) - return - - @inline def copy_16(a, b): dot_product_ee(a, ONE_EF_PTR, b) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index d2d28dbc8..acde0ac7c 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -10,9 +10,10 @@ MESSAGE_LEN = MESSAGE_LEN_PLACEHOLDER RANDOMNESS_LEN = RANDOMNESS_LEN_PLACEHOLDER PUBLIC_PARAM_LEN_FE = PUBLIC_PARAM_LEN_FE_PLACEHOLDER -PUB_KEY_SIZE = DIM + PUBLIC_PARAM_LEN_FE -PP_IN_LEFT = DIGEST_LEN - DIM -SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * DIM +XMSS_DIGEST = XMSS_DIGEST_SIZE_PLACEHOLDER +PUB_KEY_SIZE = XMSS_DIGEST + PUBLIC_PARAM_LEN_FE +PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST +SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * XMSS_DIGEST NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK @@ -26,55 +27,55 @@ TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + (V - 1) * TWEAK_LEN -# Buffer size for the hash-chaining trick: 3 prefix + DIGEST_LEN hash output. -# Hash output goes to buf + 3, then buf[0..3] is set to the prefix (pp or tweak). -# buf[0..8] = [prefix(3) | hash[0..5]] can then be used directly as the next hash input, -# avoiding a separate Array(DIGEST_LEN) allocation and copy_5. -BUF_SIZE = 3 + DIGEST_LEN +# Buffer size for the hash-chaining trick: PP_IN_LEFT prefix + DIGEST_LEN hash output. +# Hash output goes to buf + PP_IN_LEFT, then buf[0..PP_IN_LEFT] is set to the prefix. +# buf[0..8] = [prefix(PP_IN_LEFT) | hash[0..XMSS_DIGEST]] is a valid left input. +BUF_SIZE = PP_IN_LEFT + DIGEST_LEN @inline def build_left_fn(pp, data, out): - out[0] = pp[0] - out[1] = pp[1] - out[2] = pp[2] - copy_5(data, out + 3) + for k in unroll(0, PP_IN_LEFT): + out[k] = pp[k] + for k in unroll(0, XMSS_DIGEST): + out[PP_IN_LEFT + k] = data[k] return @inline -def build_right_fn(pp3, tweak, data, out): - out[0] = pp3 - out[1] = tweak[0] - out[2] = tweak[1] - copy_5(data, out + 3) +def build_right_fn(tweak, data, out): + # [tweak(2) | zeros(2) | data(XMSS_DIGEST)] + out[0] = tweak[0] + out[1] = tweak[1] + out[2] = 0 + out[3] = 0 + for k in unroll(0, XMSS_DIGEST): + out[4 + k] = data[k] return @inline -def build_right_chain_fn(pp3, tweak, out): - # Chain hash: data is all zeros - out[0] = pp3 - out[1] = tweak[0] - out[2] = tweak[1] - copy_5(ZERO_VEC_PTR, out + 3) +def build_right_chain_fn(tweak, out): + # Chain hash: data is all zeros. [tweak(2) | zeros(6)] + out[0] = tweak[0] + out[1] = tweak[1] + for k in unroll(2, DIGEST_LEN): + out[k] = 0 return @inline def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): - # pub_key: PUB_KEY_SIZE FE = merkle_root(DIM) | public_param(PUBLIC_PARAM_LEN_FE) - # signature: randomness(RANDOMNESS_LEN) | chain_tips(V * DIM) | merkle_path(LOG_LIFETIME * DIM) + # pub_key: PUB_KEY_SIZE FE = merkle_root(XMSS_DIGEST) | public_param(PUBLIC_PARAM_LEN_FE) + # signature: randomness(RANDOMNESS_LEN) | chain_tips(V * XMSS_DIGEST) | merkle_path(LOG_LIFETIME * XMSS_DIGEST) - public_param = pub_key + DIM - pp3 = public_param[PP_IN_LEFT] + public_param = pub_key + XMSS_DIGEST randomness = signature chain_starts = signature + RANDOMNESS_LEN - merkle_path = chain_starts + V * DIM + merkle_path = chain_starts + V * XMSS_DIGEST # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) - # poseidon16_compress(pre_compressed, pub_key[0:8]) - # poseidon16_compress(intermediate, [pub_key[8] | zeros]) + # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = tweak_table + TWEAK_ENCODING_OFFSET a_input_right = Array(DIGEST_LEN) a_input_right[0] = message[DIGEST_LEN] @@ -85,14 +86,10 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): poseidon16_compress(message, a_input_right, pre_compressed) pp_input = Array(DIGEST_LEN) - pp_input[0] = public_param[0] - pp_input[1] = public_param[1] - pp_input[2] = public_param[2] - pp_input[3] = pp3 - pp_input[4] = 0 - pp_input[5] = 0 - pp_input[6] = 0 - pp_input[7] = 0 + for k in unroll(0, PUBLIC_PARAM_LEN_FE): + pp_input[k] = public_param[k] + for k in unroll(PUBLIC_PARAM_LEN_FE, DIGEST_LEN): + pp_input[k] = 0 encoding_fe = Array(DIGEST_LEN) poseidon16_compress(pre_compressed, pp_input, encoding_fe) @@ -130,7 +127,7 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): for i in unroll(0, V): num_hashes = (CHAIN_LENGTH - 1) - encoding[i] - chain_start = chain_starts + i * DIM + chain_start = chain_starts + i * XMSS_DIGEST chain_end = wots_public_key + i * DIGEST_LEN chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN @@ -145,100 +142,117 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): match_range( num_hashes, range(0, 1), - lambda _: copy_5(chain_start, chain_end), + lambda _: copy_xmss_digest(chain_start, chain_end), range(1, CHAIN_LENGTH), - lambda n: chain_hash_pa(chain_start, n, chain_end, public_param, pp3, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last), + lambda n: chain_hash_pa(chain_start, n, chain_end, public_param, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last), ) # 3) Hash WOTS public key wots_pk_tweaks = tweak_table + TWEAK_WOTS_PK_OFFSET - expected_leaf = wots_pk_hash(wots_public_key, public_param, pp3, wots_pk_tweaks) + expected_leaf = wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks) # 4) Merkle verification merkle_tweaks = tweak_table + TWEAK_MERKLE_OFFSET - xmss_merkle_verify(expected_leaf, merkle_path, merkle_chunks, pub_key, public_param, pp3, merkle_tweaks) + xmss_merkle_verify(expected_leaf, merkle_path, merkle_chunks, pub_key, public_param, merkle_tweaks) return @inline -def chain_hash_pa(input, n, output, public_param, pp3, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last): +def copy_xmss_digest(src, dst): + # Copy XMSS_DIGEST elements from src to dst (within a DIGEST_LEN-strided destination) + for k in unroll(0, XMSS_DIGEST): + dst[k] = src[k] + return + + +@inline +def chain_hash_pa(input, n, output, public_param, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last): # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility) starting_step = CHAIN_LENGTH - 1 - n # First hash: build left and right from scratch build_left_fn(public_param, input, ch_left) - build_right_chain_fn(pp3, chain_i_tweaks + starting_step * TWEAK_LEN, ch_right) + build_right_chain_fn(chain_i_tweaks + starting_step * TWEAK_LEN, ch_right) if n == 1: poseidon16_compress(ch_left, ch_right, output) else: - # Buffer trick: hash output goes to buf + 3, then pp is prepended at buf[0..3]. + # Buffer trick: hash output goes to buf + PP_IN_LEFT, then pp is prepended. ch_buf_idx[0] = ch_bufs - poseidon16_compress(ch_left, ch_right, ch_bufs + 3) - ch_bufs[0] = public_param[0] - ch_bufs[1] = public_param[1] - ch_bufs[2] = public_param[2] + poseidon16_compress(ch_left, ch_right, ch_bufs + PP_IN_LEFT) + for k in unroll(0, PP_IN_LEFT): + ch_bufs[k] = public_param[k] for j in unroll(1, n - 1): ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE cur_buf = ch_buf_idx[j] right_j = ch_rights + (j - 1) * DIGEST_LEN - build_right_chain_fn(pp3, chain_i_tweaks + (starting_step + j) * TWEAK_LEN, right_j) - poseidon16_compress(ch_buf_idx[j - 1], right_j, cur_buf + 3) - cur_buf[0] = public_param[0] - cur_buf[1] = public_param[1] - cur_buf[2] = public_param[2] + build_right_chain_fn(chain_i_tweaks + (starting_step + j) * TWEAK_LEN, right_j) + poseidon16_compress(ch_buf_idx[j - 1], right_j, cur_buf + PP_IN_LEFT) + for k in unroll(0, PP_IN_LEFT): + cur_buf[k] = public_param[k] # Final hash - build_right_chain_fn(pp3, chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN, ch_right_last) + build_right_chain_fn(chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN, ch_right_last) poseidon16_compress(ch_buf_idx[n - 2], ch_right_last, output) return -def wots_pk_hash(wots_public_key, public_param, pp3, wots_pk_tweaks): +def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): # Sequential hash over V elements at DIGEST_LEN stride - # poseidon16_compress(build_left(pp, wots[0]), build_right(tweak[0], wots[1])) -> h - # poseidon16_compress([pp | h], build_right(tweak[i], wots[i+1])) for i=1..V-2 - # Final: poseidon16_compress([pp | h], build_right(tweak[V-2], wots[V-1])) # First hash: build from scratch left0 = Array(DIGEST_LEN) build_left_fn(public_param, wots_public_key, left0) right0 = Array(DIGEST_LEN) - build_right_fn(pp3, wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) + build_right_fn(wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) # Buffer trick for intermediate states bufs = Array((V - 2) * BUF_SIZE) buf_indexes = Array(V - 2) buf_indexes[0] = bufs - poseidon16_compress(left0, right0, bufs + 3) - bufs[0] = public_param[0] - bufs[1] = public_param[1] - bufs[2] = public_param[2] + poseidon16_compress(left0, right0, bufs + PP_IN_LEFT) + for k in unroll(0, PP_IN_LEFT): + bufs[k] = public_param[k] for i in unroll(1, V - 2): buf_indexes[i] = buf_indexes[i - 1] + BUF_SIZE cur_buf = buf_indexes[i] right_i = Array(DIGEST_LEN) - build_right_fn(pp3, wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) - poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + 3) - cur_buf[0] = public_param[0] - cur_buf[1] = public_param[1] - cur_buf[2] = public_param[2] + build_right_fn(wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) + poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + PP_IN_LEFT) + for k in unroll(0, PP_IN_LEFT): + cur_buf[k] = public_param[k] # Final hash result = Array(DIGEST_LEN) right_last = Array(DIGEST_LEN) - build_right_fn(pp3, wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) + build_right_fn(wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) poseidon16_compress(buf_indexes[V - 3], right_last, result) return result @inline -def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, pp3, merkle_tweaks_chunk): - # b encodes 4 is_left bits; path elements at DIM stride +def set_buf_prefix_left(buf, public_param): + for k in unroll(0, PP_IN_LEFT): + buf[k] = public_param[k] + return + + +@inline +def set_buf_prefix_right(buf, tweak): + buf[0] = tweak[0] + buf[1] = tweak[1] + buf[2] = 0 + buf[3] = 0 + return + + +@inline +def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_tweaks_chunk): + # b encodes 4 is_left bits; path elements at XMSS_DIGEST stride b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -252,72 +266,58 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, pp3, me right0 = Array(DIGEST_LEN) if b0 == 1: build_left_fn(public_param, state_in, left0) - build_right_fn(pp3, merkle_tweaks_chunk, path_chunk, right0) + build_right_fn(merkle_tweaks_chunk, path_chunk, right0) else: build_left_fn(public_param, path_chunk, left0) - build_right_fn(pp3, merkle_tweaks_chunk, state_in, right0) + build_right_fn(merkle_tweaks_chunk, state_in, right0) - # Buffer trick: hash output to buf + 3, then prepend prefix. - # If state goes left: buf = [pp | hash[0..5]], used as left input. - # If state goes right: buf = [pp3, tw0, tw1 | hash[0..5]], used as right input. + # Buffer trick: hash output to buf + PP_IN_LEFT, then prepend prefix. buf0 = Array(BUF_SIZE) - poseidon16_compress(left0, right0, buf0 + 3) + poseidon16_compress(left0, right0, buf0 + PP_IN_LEFT) # Level 1 other1 = Array(DIGEST_LEN) buf1 = Array(BUF_SIZE) if b1 == 1: - buf0[0] = public_param[0] - buf0[1] = public_param[1] - buf0[2] = public_param[2] - build_right_fn(pp3, merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * DIM, other1) - poseidon16_compress(buf0, other1, buf1 + 3) + set_buf_prefix_left(buf0, public_param) + build_right_fn(merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * XMSS_DIGEST, other1) + poseidon16_compress(buf0, other1, buf1 + PP_IN_LEFT) else: - buf0[0] = pp3 - buf0[1] = merkle_tweaks_chunk[1 * TWEAK_LEN] - buf0[2] = merkle_tweaks_chunk[1 * TWEAK_LEN + 1] - build_left_fn(public_param, path_chunk + 1 * DIM, other1) - poseidon16_compress(other1, buf0, buf1 + 3) + set_buf_prefix_right(buf0, merkle_tweaks_chunk + 1 * TWEAK_LEN) + build_left_fn(public_param, path_chunk + 1 * XMSS_DIGEST, other1) + poseidon16_compress(other1, buf0, buf1 + PP_IN_LEFT) # Level 2 other2 = Array(DIGEST_LEN) buf2 = Array(BUF_SIZE) if b2 == 1: - buf1[0] = public_param[0] - buf1[1] = public_param[1] - buf1[2] = public_param[2] - build_right_fn(pp3, merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * DIM, other2) - poseidon16_compress(buf1, other2, buf2 + 3) + set_buf_prefix_left(buf1, public_param) + build_right_fn(merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * XMSS_DIGEST, other2) + poseidon16_compress(buf1, other2, buf2 + PP_IN_LEFT) else: - buf1[0] = pp3 - buf1[1] = merkle_tweaks_chunk[2 * TWEAK_LEN] - buf1[2] = merkle_tweaks_chunk[2 * TWEAK_LEN + 1] - build_left_fn(public_param, path_chunk + 2 * DIM, other2) - poseidon16_compress(other2, buf1, buf2 + 3) + set_buf_prefix_right(buf1, merkle_tweaks_chunk + 2 * TWEAK_LEN) + build_left_fn(public_param, path_chunk + 2 * XMSS_DIGEST, other2) + poseidon16_compress(other2, buf1, buf2 + PP_IN_LEFT) # Level 3 -> state_out other3 = Array(DIGEST_LEN) if b3 == 1: - buf2[0] = public_param[0] - buf2[1] = public_param[1] - buf2[2] = public_param[2] - build_right_fn(pp3, merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * DIM, other3) + set_buf_prefix_left(buf2, public_param) + build_right_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) poseidon16_compress(buf2, other3, state_out) else: - buf2[0] = pp3 - buf2[1] = merkle_tweaks_chunk[3 * TWEAK_LEN] - buf2[2] = merkle_tweaks_chunk[3 * TWEAK_LEN + 1] - build_left_fn(public_param, path_chunk + 3 * DIM, other3) + set_buf_prefix_right(buf2, merkle_tweaks_chunk + 3 * TWEAK_LEN) + build_left_fn(public_param, path_chunk + 3 * XMSS_DIGEST, other3) poseidon16_compress(other3, buf2, state_out) return @inline -def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, public_param, pp3, merkle_tweaks): +def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, public_param, merkle_tweaks): states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) # First chunk - match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states, public_param, pp3, merkle_tweaks)) + match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states, public_param, merkle_tweaks)) state_indexes = Array(N_MERKLE_CHUNKS - 1) state_indexes[0] = states @@ -329,10 +329,9 @@ def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, p lambda b: do_4_merkle_levels( b, state_indexes[j - 1], - merkle_path + j * MERKLE_LEVELS_PER_CHUNK * DIM, + merkle_path + j * MERKLE_LEVELS_PER_CHUNK * XMSS_DIGEST, state_indexes[j], public_param, - pp3, merkle_tweaks + j * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, ), ) @@ -345,14 +344,13 @@ def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, p lambda b: do_4_merkle_levels( b, state_indexes[N_MERKLE_CHUNKS - 2], - merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * DIM, + merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * XMSS_DIGEST, last_output, public_param, - pp3, merkle_tweaks + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, ), ) - # Assert computed root == expected (first DIM elements) - copy_5(last_output, expected_root) + # Assert computed root == expected (first XMSS_DIGEST elements) + copy_xmss_digest(last_output, expected_root) return diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 19138d2ef..63eb4f6ac 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -6,7 +6,7 @@ pub use wots::*; mod xmss; pub use xmss::*; -pub(crate) const DIGEST_SIZE: usize = 5; +pub const DIGEST_SIZE: usize = 4; pub(crate) const TWEAK_LEN: usize = 2; type F = KoalaBear; @@ -29,7 +29,8 @@ pub const PP_IN_LEFT: usize = 8 - DIGEST_SIZE; // = 3 pub const PUB_KEY_FLAT_SIZE: usize = DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 const _: () = assert!(PP_IN_LEFT + DIGEST_SIZE == 8); -const _: () = assert!((PUBLIC_PARAM_LEN_FE - PP_IN_LEFT) + TWEAK_LEN + DIGEST_SIZE == 8); +// Right layout: [tweak(2) | zeros(2) | data(DIGEST_SIZE)] +const _: () = assert!(TWEAK_LEN + 2 + DIGEST_SIZE == 8); pub const SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + (V + LOG_LIFETIME) * DIGEST_SIZE; @@ -54,21 +55,20 @@ pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> ] } -/// [public_param[0..3](3) | data(5)] +/// [public_param(4) | data(4)] pub(crate) fn build_left(public_param: &PublicParam, data: &Digest) -> [F; 8] { let mut left = [F::default(); 8]; - left[..PP_IN_LEFT].copy_from_slice(&public_param[..PP_IN_LEFT]); + left[..PP_IN_LEFT].copy_from_slice(public_param); left[PP_IN_LEFT..].copy_from_slice(data); left } -/// [pp[3](1) | tweak(2) | data(5)] -pub(crate) fn build_right(public_param: &PublicParam, tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { - let pp_right = PUBLIC_PARAM_LEN_FE - PP_IN_LEFT; +/// [tweak(2) | zeros(2) | data(4)] +pub(crate) fn build_right(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { let mut right = [F::default(); 8]; - right[..pp_right].copy_from_slice(&public_param[PP_IN_LEFT..]); - right[pp_right..pp_right + TWEAK_LEN].copy_from_slice(&tweak); - right[pp_right + TWEAK_LEN..].copy_from_slice(data); + right[..TWEAK_LEN].copy_from_slice(&tweak); + // right[TWEAK_LEN..TWEAK_LEN+2] = zeros (default) + right[8 - DIGEST_SIZE..].copy_from_slice(data); right } diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 2f96b8224..e7f934d4f 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -94,13 +94,13 @@ impl WotsSignature { impl WotsPublicKey { pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { let left = build_left(&public_param, &self.0[0]); - let right = build_right(&public_param, make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot), &self.0[1]); + let right = build_right(make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot), &self.0[1]); let mut running_hash: Digest = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); for i in 2..V { let left = build_left(&public_param, &running_hash); - let right = build_right(&public_param, make_tweak(TWEAK_TYPE_WOTS_PK, i - 1, slot), &self.0[i]); + let right = build_right(make_tweak(TWEAK_TYPE_WOTS_PK, i - 1, slot), &self.0[i]); running_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); @@ -120,7 +120,7 @@ pub fn iterate_hash( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left(&public_param, &acc); - let right = build_right(&public_param, tweak, &Default::default()); + let right = build_right(tweak, &Default::default()); poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap() @@ -139,7 +139,7 @@ pub fn iterate_hash_with_poseidon_trace( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left(&public_param, &acc); - let right = build_right(&public_param, tweak, &Default::default()); + let right = build_right(tweak, &Default::default()); poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..DIGEST_SIZE] .try_into() .unwrap() diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index 9daa5068f..136068dbb 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -114,8 +114,7 @@ pub fn xmss_key_gen( gen_random_node(&seed, level - 1, right_idx) }; let poseidon_left = build_left(&public_param, &left); - let poseidon_right = - build_right(&public_param, make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &right); + let poseidon_right = build_right(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &right); poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..DIGEST_SIZE] .try_into() .unwrap() @@ -217,13 +216,13 @@ pub fn xmss_verify( let tweak = make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index); if is_left { let left = build_left(&pub_key.public_param, ¤t_hash); - let right = build_right(&pub_key.public_param, tweak, neighbour); + let right = build_right(tweak, neighbour); current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); } else { let left = build_left(&pub_key.public_param, neighbour); - let right = build_right(&pub_key.public_param, tweak, ¤t_hash); + let right = build_right(tweak, ¤t_hash); current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); From c43ac8f253bfb79372191fdda14757fab00de0c0 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 5 Apr 2026 12:19:57 +0200 Subject: [PATCH 03/66] wip --- crates/rec_aggregation/xmss_aggregate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index acde0ac7c..2b6a46964 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -57,10 +57,11 @@ def build_right_fn(tweak, data, out): @inline def build_right_chain_fn(tweak, out): # Chain hash: data is all zeros. [tweak(2) | zeros(6)] + # out must be Array(DIGEST_LEN) or more; set_to_5_zeros(out + 3) writes out[3..8]. out[0] = tweak[0] out[1] = tweak[1] - for k in unroll(2, DIGEST_LEN): - out[k] = 0 + out[2] = 0 + set_to_5_zeros(out + 3) return @@ -85,11 +86,11 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) - pp_input = Array(DIGEST_LEN) + # pp_input layout: [public_param(4) | zeros(4)]. Allocate 9 so set_to_5_zeros can write positions 4..8. + pp_input = Array(DIGEST_LEN + 1) for k in unroll(0, PUBLIC_PARAM_LEN_FE): pp_input[k] = public_param[k] - for k in unroll(PUBLIC_PARAM_LEN_FE, DIGEST_LEN): - pp_input[k] = 0 + set_to_5_zeros(pp_input + PUBLIC_PARAM_LEN_FE) encoding_fe = Array(DIGEST_LEN) poseidon16_compress(pre_compressed, pp_input, encoding_fe) From f76b4cb3559d8cf683856918010aa5e7307bf84b Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 5 Apr 2026 17:14:13 +0200 Subject: [PATCH 04/66] wip --- crates/rec_aggregation/main.py | 4 ++- crates/rec_aggregation/xmss_aggregate.py | 31 ++++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 05f219a7c..817144a68 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -83,8 +83,10 @@ def main(): buffer[idx] = i # Verify raw XMSS signatures pk = all_pubkeys + idx * PUB_KEY_SIZE - sig = Array(SIG_SIZE) + # 1 extra element for safe copy_5 reads past the last merkle path element + sig = Array(SIG_SIZE + 1) hint_xmss(sig) + sig[SIG_SIZE] = 0 xmss_verify(pk, message, sig, tweak_table, merkle_chunks_for_slot) counter: Mut = n_raw_xmss diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 2b6a46964..4efa02af0 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -35,22 +35,23 @@ @inline def build_left_fn(pp, data, out): + # out must be Array(DIGEST_LEN + 1) or more + # data must have at least 5 readable elements in memory. for k in unroll(0, PP_IN_LEFT): out[k] = pp[k] - for k in unroll(0, XMSS_DIGEST): - out[PP_IN_LEFT + k] = data[k] + copy_5(data, out + PP_IN_LEFT) return @inline def build_right_fn(tweak, data, out): # [tweak(2) | zeros(2) | data(XMSS_DIGEST)] + # out must be Array(DIGEST_LEN + 1) or more; copy_5(data, out + 4) writes out[4..9]. out[0] = tweak[0] out[1] = tweak[1] out[2] = 0 out[3] = 0 - for k in unroll(0, XMSS_DIGEST): - out[4 + k] = data[k] + copy_5(data, out + 4) return @@ -133,7 +134,7 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN # Pre-allocate all buffers (constant allocation regardless of num_hashes) - ch_left = Array(DIGEST_LEN) + ch_left = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn ch_right = Array(DIGEST_LEN) ch_bufs = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) @@ -143,7 +144,7 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): match_range( num_hashes, range(0, 1), - lambda _: copy_xmss_digest(chain_start, chain_end), + lambda _: copy_5(chain_start, chain_end), range(1, CHAIN_LENGTH), lambda n: chain_hash_pa(chain_start, n, chain_end, public_param, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last), ) @@ -203,9 +204,9 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): # Sequential hash over V elements at DIGEST_LEN stride # First hash: build from scratch - left0 = Array(DIGEST_LEN) + left0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn build_left_fn(public_param, wots_public_key, left0) - right0 = Array(DIGEST_LEN) + right0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn build_right_fn(wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) # Buffer trick for intermediate states @@ -220,7 +221,7 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): for i in unroll(1, V - 2): buf_indexes[i] = buf_indexes[i - 1] + BUF_SIZE cur_buf = buf_indexes[i] - right_i = Array(DIGEST_LEN) + right_i = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn build_right_fn(wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + PP_IN_LEFT) for k in unroll(0, PP_IN_LEFT): @@ -228,7 +229,7 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): # Final hash result = Array(DIGEST_LEN) - right_last = Array(DIGEST_LEN) + right_last = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn build_right_fn(wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) poseidon16_compress(buf_indexes[V - 3], right_last, result) @@ -263,8 +264,8 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ b3 = r3 % 2 # Level 0: build from external state_in and path_chunk (no prior hash to reuse) - left0 = Array(DIGEST_LEN) - right0 = Array(DIGEST_LEN) + left0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn / build_right_fn + right0 = Array(DIGEST_LEN + 1) if b0 == 1: build_left_fn(public_param, state_in, left0) build_right_fn(merkle_tweaks_chunk, path_chunk, right0) @@ -277,7 +278,7 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ poseidon16_compress(left0, right0, buf0 + PP_IN_LEFT) # Level 1 - other1 = Array(DIGEST_LEN) + other1 = Array(DIGEST_LEN + 1) # +1 for copy_5 buf1 = Array(BUF_SIZE) if b1 == 1: set_buf_prefix_left(buf0, public_param) @@ -289,7 +290,7 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ poseidon16_compress(other1, buf0, buf1 + PP_IN_LEFT) # Level 2 - other2 = Array(DIGEST_LEN) + other2 = Array(DIGEST_LEN + 1) # +1 for copy_5 buf2 = Array(BUF_SIZE) if b2 == 1: set_buf_prefix_left(buf1, public_param) @@ -301,7 +302,7 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ poseidon16_compress(other2, buf1, buf2 + PP_IN_LEFT) # Level 3 -> state_out - other3 = Array(DIGEST_LEN) + other3 = Array(DIGEST_LEN + 1) # +1 for copy_5 if b3 == 1: set_buf_prefix_left(buf2, public_param) build_right_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) From 964099fab27b4e5b6ffa8ee6ef08cbe864b4ecef Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 5 Apr 2026 17:42:54 +0200 Subject: [PATCH 05/66] w --- crates/rec_aggregation/xmss_aggregate.py | 76 ++++++++++++++---------- crates/xmss/src/lib.rs | 16 +++++ crates/xmss/src/wots.rs | 10 ++-- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 4efa02af0..c55444222 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -56,13 +56,23 @@ def build_right_fn(tweak, data, out): @inline -def build_right_chain_fn(tweak, out): - # Chain hash: data is all zeros. [tweak(2) | zeros(6)] - # out must be Array(DIGEST_LEN) or more; set_to_5_zeros(out + 3) writes out[3..8]. - out[0] = tweak[0] - out[1] = tweak[1] - out[2] = 0 - set_to_5_zeros(out + 3) +def build_chain_right(public_param, out): + # Shared chain-hash right input: [public_param(4) | zeros(4)] + # Built once per xmss_verify and reused for every chain hash. + # out must be Array(DIGEST_LEN + 1) so set_to_5_zeros can write positions 4..8. + for k in unroll(0, PUBLIC_PARAM_LEN_FE): + out[k] = public_param[k] + set_to_5_zeros(out + PUBLIC_PARAM_LEN_FE) + return + + +@inline +def set_chain_left_prefix(cur_buf, tweak): + # Writes [tweak(2) | zeros(2)] to cur_buf[0..4]. + cur_buf[0] = tweak[0] + cur_buf[1] = tweak[1] + cur_buf[2] = 0 + cur_buf[3] = 0 return @@ -127,6 +137,9 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): wots_public_key = Array(V * DIGEST_LEN) MAX_CHAIN_HASHES = CHAIN_LENGTH - 1 + chain_right = Array(DIGEST_LEN + 1) + build_chain_right(public_param, chain_right) + for i in unroll(0, V): num_hashes = (CHAIN_LENGTH - 1) - encoding[i] chain_start = chain_starts + i * XMSS_DIGEST @@ -134,19 +147,16 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN # Pre-allocate all buffers (constant allocation regardless of num_hashes) - ch_left = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn - ch_right = Array(DIGEST_LEN) + ch_left_first = Array(DIGEST_LEN + 1) # L_0 = [tweak_0, zeros, input], +1 for copy_5 ch_bufs = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) - ch_rights = Array((MAX_CHAIN_HASHES - 1) * DIGEST_LEN) - ch_right_last = Array(DIGEST_LEN) match_range( num_hashes, range(0, 1), lambda _: copy_5(chain_start, chain_end), range(1, CHAIN_LENGTH), - lambda n: chain_hash_pa(chain_start, n, chain_end, public_param, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last), + lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right, ch_left_first, ch_bufs, ch_buf_idx), ) # 3) Hash WOTS public key @@ -168,35 +178,39 @@ def copy_xmss_digest(src, dst): @inline -def chain_hash_pa(input, n, output, public_param, chain_i_tweaks, ch_left, ch_right, ch_bufs, ch_buf_idx, ch_rights, ch_right_last): - # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility) +def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, ch_bufs, ch_buf_idx): + # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility). + # Chain hash layout: left = [tweak(2) | zeros(2) | data(4)], right = chain_right (shared). + # Buffer of size BUF_SIZE = 12 = [prefix(4) | hash_output(8)] where the hash_output's + # first 4 elements (the digest) land at positions 4..8 of the buffer, so buf[0..8] forms + # a valid left input for the NEXT hash (prefix = [tweak_next, zeros]). starting_step = CHAIN_LENGTH - 1 - n - # First hash: build left and right from scratch - build_left_fn(public_param, input, ch_left) - build_right_chain_fn(chain_i_tweaks + starting_step * TWEAK_LEN, ch_right) + # Build L_0 = [tweak_0, zeros, input] + first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN + set_chain_left_prefix(ch_left_first, first_tweak) + copy_5(input, ch_left_first + 4) if n == 1: - poseidon16_compress(ch_left, ch_right, output) + poseidon16_compress(ch_left_first, chain_right, output) else: - # Buffer trick: hash output goes to buf + PP_IN_LEFT, then pp is prepended. + # Hash 0: L_0 → ch_bufs + 4 (writes ch_bufs[4..12], digest at ch_bufs[4..8]) ch_buf_idx[0] = ch_bufs - poseidon16_compress(ch_left, ch_right, ch_bufs + PP_IN_LEFT) - for k in unroll(0, PP_IN_LEFT): - ch_bufs[k] = public_param[k] + poseidon16_compress(ch_left_first, chain_right, ch_bufs + 4) + # Write L_1 prefix = [tweak_1, zeros] to ch_bufs[0..4] + next_tweak = chain_i_tweaks + (starting_step + 1) * TWEAK_LEN + set_chain_left_prefix(ch_bufs, next_tweak) + # Hashes 1..n-2: buf[j-1] → buf[j] + 4 for j in unroll(1, n - 1): ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE cur_buf = ch_buf_idx[j] - right_j = ch_rights + (j - 1) * DIGEST_LEN - build_right_chain_fn(chain_i_tweaks + (starting_step + j) * TWEAK_LEN, right_j) - poseidon16_compress(ch_buf_idx[j - 1], right_j, cur_buf + PP_IN_LEFT) - for k in unroll(0, PP_IN_LEFT): - cur_buf[k] = public_param[k] - - # Final hash - build_right_chain_fn(chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN, ch_right_last) - poseidon16_compress(ch_buf_idx[n - 2], ch_right_last, output) + poseidon16_compress(ch_buf_idx[j - 1], chain_right, cur_buf + 4) + cur_tweak = chain_i_tweaks + (starting_step + j + 1) * TWEAK_LEN + set_chain_left_prefix(cur_buf, cur_tweak) + + # Final hash: buf[n-2] → output + poseidon16_compress(ch_buf_idx[n - 2], chain_right, output) return diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 63eb4f6ac..b7afcdf13 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -72,6 +72,22 @@ pub(crate) fn build_right(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { right } +/// Chain-hash-specific left layout: [tweak(2) | zeros(2) | data(4)]. +pub(crate) fn build_left_chain(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { + let mut left = [F::default(); 8]; + left[..TWEAK_LEN].copy_from_slice(&tweak); + // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) + left[8 - DIGEST_SIZE..].copy_from_slice(data); + left +} + +/// Chain-hash-specific right layout: [public_param(4) | zeros(4)]. +pub(crate) fn build_right_chain_pp(public_param: &PublicParam) -> [F; 8] { + let mut right = [F::default(); 8]; + right[..PUBLIC_PARAM_LEN_FE].copy_from_slice(public_param); + right +} + fn poseidon16_compress_with_trace(a: [F; 8], b: [F; 8], poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>) -> [F; 8] { let input: [F; 16] = [a, b].concat().try_into().unwrap(); let output = poseidon16_compress(input); diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index e7f934d4f..53997a72c 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -117,10 +117,11 @@ pub fn iterate_hash( chain_index: usize, start_step: usize, ) -> Digest { + // Chain hash layout: left = [tweak | zeros | data], right = [pp | zeros] (constant). + let right = build_right_chain_pp(&public_param); (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); - let left = build_left(&public_param, &acc); - let right = build_right(tweak, &Default::default()); + let left = build_left_chain(tweak, &acc); poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap() @@ -136,10 +137,11 @@ pub fn iterate_hash_with_poseidon_trace( chain_index: usize, start_step: usize, ) -> Digest { + // Chain hash layout: left = [tweak | zeros | data], right = [pp | zeros] (constant). + let right = build_right_chain_pp(&public_param); (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); - let left = build_left(&public_param, &acc); - let right = build_right(tweak, &Default::default()); + let left = build_left_chain(tweak, &acc); poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..DIGEST_SIZE] .try_into() .unwrap() From 3a630cc605ebe89a43372fd644d0897b115d5654 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 5 Apr 2026 19:49:35 +0200 Subject: [PATCH 06/66] w --- crates/rec_aggregation/src/lib.rs | 31 +++--- crates/rec_aggregation/xmss_aggregate.py | 131 +++++++++++++++-------- 2 files changed, 104 insertions(+), 58 deletions(-) diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 927bcae0c..57b5bac82 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -27,10 +27,11 @@ const TWEAK_TYPE_WOTS_PK: usize = 1; const TWEAK_TYPE_MERKLE: usize = 2; const TWEAK_TYPE_ENCODING: usize = 3; -/// Number of 2-FE tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + (V-1) wots_pk + LOG_LIFETIME merkle +/// Number of tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + (V-1) wots_pk + LOG_LIFETIME merkle const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME; -/// Size of the tweak table in field elements (2 FE per tweak) -const TWEAK_TABLE_SIZE_FE_PADDED: usize = (N_TWEAKS * 2).next_multiple_of(DIGEST_LEN); +/// Each tweak is stored as a 5-FE slot [0, tw[0], tw[1], 0, 0] so that we can use copy_5 +const TWEAK_SLOT_SIZE: usize = 5; +const TWEAK_TABLE_SIZE_FE_PADDED: usize = (N_TWEAKS * TWEAK_SLOT_SIZE).next_multiple_of(DIGEST_LEN); const TWEAKS_HASHING_USE_IV: bool = false; // fixed size → no IV needed @@ -64,34 +65,38 @@ fn make_tweak_values(tweak_type: usize, sub_position: usize, index: u32) -> [F; ] } -/// Build a flat tweak table for the given slot. Layout: -/// encoding_tweak(2) | chain_tweaks(V * CHAIN_LENGTH * 2) | wots_pk_tweaks((V-1) * 2) | merkle_tweaks(LOG_LIFETIME * 2) +/// Each tweak is stored as a 5-FE slot: [0, tw[0], tw[1], 0, 0] fn compute_tweak_table(slot: u32) -> Vec { let mut table = Vec::new(); + let push_padded = |table: &mut Vec, tweak_type: usize, sub_position: usize, index: u32| { + let tw = make_tweak_values(tweak_type, sub_position, index); + table.push(F::ZERO); + table.push(tw[0]); + table.push(tw[1]); + table.push(F::ZERO); + table.push(F::ZERO); + }; + // Encoding tweak - let tw = make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot); - table.extend_from_slice(&tw); + push_padded(&mut table, TWEAK_TYPE_ENCODING, 0, slot); // Chain tweaks: for chain i, step s → make_tweak(CHAIN, i*CHAIN_LENGTH + s, slot) for i in 0..V { for s in 0..CHAIN_LENGTH { - let tw = make_tweak_values(TWEAK_TYPE_CHAIN, i * CHAIN_LENGTH + s, slot); - table.extend_from_slice(&tw); + push_padded(&mut table, TWEAK_TYPE_CHAIN, i * CHAIN_LENGTH + s, slot); } } // WOTS_PK tweaks: for sub_pos p = 0..V-2 for p in 0..V - 1 { - let tw = make_tweak_values(TWEAK_TYPE_WOTS_PK, p, slot); - table.extend_from_slice(&tw); + push_padded(&mut table, TWEAK_TYPE_WOTS_PK, p, slot); } // Merkle tweaks: for level 0..LOG_LIFETIME-1 for level in 0..LOG_LIFETIME { let parent_index = ((slot as u64) >> (level + 1)) as u32; - let tw = make_tweak_values(TWEAK_TYPE_MERKLE, level + 1, parent_index); - table.extend_from_slice(&tw); + push_padded(&mut table, TWEAK_TYPE_MERKLE, level + 1, parent_index); } table.resize(TWEAK_TABLE_SIZE_FE_PADDED, F::ZERO); table diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index c55444222..dcf75a5cd 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -18,19 +18,25 @@ MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK -# Tweak table layout -TWEAK_LEN = 2 +# Tweak table layout: each tweak is stored as a 5-FE padded slot [0, tw[0], tw[1], 0, 0]. +# Convention: tweak pointers always point to the tweak VALUE (offset +1 within the slot). +# Individual access: ptr[0] = tw[0], ptr[1] = tw[1] (unchanged from the unpadded version). +# Copy_5 access: copy_5(ptr - 1, dst) reads the 5-element slot [0, tw[0], tw[1], 0, 0]. +TWEAK_LEN = 5 # stride / slot size in the padded table N_TWEAKS = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(N_TWEAKS * TWEAK_LEN, DIGEST_LEN) -TWEAK_ENCODING_OFFSET = 0 -TWEAK_CHAIN_OFFSET = 1 * TWEAK_LEN +TWEAK_ENCODING_OFFSET = 1 # skip the leading zero of slot 0 +TWEAK_CHAIN_OFFSET = TWEAK_ENCODING_OFFSET + TWEAK_LEN TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + (V - 1) * TWEAK_LEN -# Buffer size for the hash-chaining trick: PP_IN_LEFT prefix + DIGEST_LEN hash output. -# Hash output goes to buf + PP_IN_LEFT, then buf[0..PP_IN_LEFT] is set to the prefix. -# buf[0..8] = [prefix(PP_IN_LEFT) | hash[0..XMSS_DIGEST]] is a valid left input. -BUF_SIZE = PP_IN_LEFT + DIGEST_LEN +# Buffer size for the hash-chaining trick. +# Each slot is [extra(1) | prefix(4) | hash_output(8)] = 13 elements. +# The "extra" position at offset 0 is the landing spot for copy_5's leading zero when +# writing a padded tweak from the table. The effective buffer (hash-input pointer) is at +# offset 1; poseidon writes its output at offset 5 (= 1 + PP_IN_LEFT). +# Hash input for the next iteration = slot[1..9] = [prefix(4) | digest(4)]. +BUF_SIZE = 1 + PP_IN_LEFT + DIGEST_LEN @inline @@ -46,11 +52,12 @@ def build_left_fn(pp, data, out): @inline def build_right_fn(tweak, data, out): # [tweak(2) | zeros(2) | data(XMSS_DIGEST)] - # out must be Array(DIGEST_LEN + 1) or more; copy_5(data, out + 4) writes out[4..9]. - out[0] = tweak[0] - out[1] = tweak[1] - out[2] = 0 - out[3] = 0 + # `tweak` is a pointer to the tweak VALUE (slot_start + 1). copy_5(tweak - 1, out - 1) + # writes the padded slot [0, tw(2), 0, 0] to out[-1..4]; the leading 0 lands at the + # "extra" slot before `out`. + # `out` must be allocated as `alloc + 1` where alloc is Array(DIGEST_LEN + 2) or more + # (so that out[-1] and out[0..9] are all valid writable memory). + copy_5(tweak - 1, out - 1) copy_5(data, out + 4) return @@ -89,11 +96,13 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = tweak_table + TWEAK_ENCODING_OFFSET - a_input_right = Array(DIGEST_LEN) + # Allocate 11 elements so the 2nd copy_5 (which reads [tw(2), 0, 0, 0] from the padded + # table and writes 5 elements at offset 6) can safely write positions 6..11. + a_input_right = Array(1 + RANDOMNESS_LEN + TWEAK_LEN) a_input_right[0] = message[DIGEST_LEN] copy_5(randomness, a_input_right + 1) - a_input_right[1 + RANDOMNESS_LEN] = encoding_tweak[0] - a_input_right[1 + RANDOMNESS_LEN + 1] = encoding_tweak[1] + # encoding_tweak points to the tweak VALUE; reading 5 elements gives [tw(2), 0, 0, 0]. + copy_5(encoding_tweak, a_input_right + 1 + RANDOMNESS_LEN) pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) @@ -146,9 +155,15 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): chain_end = wots_public_key + i * DIGEST_LEN chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN - # Pre-allocate all buffers (constant allocation regardless of num_hashes) - ch_left_first = Array(DIGEST_LEN + 1) # L_0 = [tweak_0, zeros, input], +1 for copy_5 - ch_bufs = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) + # Pre-allocate all buffers (constant allocation regardless of num_hashes). + # Each slot starts with an "extra" element at offset 0, used as the landing spot + # for copy_5's leading zero when writing the padded tweak prefix. + # We allocate 1 extra element BEFORE the first slot and expose pointers pointing + # at slot_start + 1 (the hash-input position). Thus `ptr - 1` is always valid. + ch_left_first_alloc = Array(BUF_SIZE) + ch_left_first = ch_left_first_alloc + 1 + ch_bufs_alloc = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) + ch_bufs = ch_bufs_alloc + 1 ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) match_range( @@ -181,25 +196,34 @@ def copy_xmss_digest(src, dst): def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, ch_bufs, ch_buf_idx): # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility). # Chain hash layout: left = [tweak(2) | zeros(2) | data(4)], right = chain_right (shared). - # Buffer of size BUF_SIZE = 12 = [prefix(4) | hash_output(8)] where the hash_output's - # first 4 elements (the digest) land at positions 4..8 of the buffer, so buf[0..8] forms - # a valid left input for the NEXT hash (prefix = [tweak_next, zeros]). + # + # Each slot is BUF_SIZE = 13: [extra(1) | prefix(4) | hash_output(8)]. + # The "hash-input" pointer for a slot is slot_start + 1 (skipping the extra). + # copy_5 writes to slot_start (5 elements, leading zero lands in extra slot). + # Poseidon writes its output at slot_start + 5 (= hash_input_ptr + 4), so the digest + # lands at hash_input_ptr[4..8]; together with the prefix at hash_input_ptr[0..4], + # the slot's hash_input_ptr[0..8] forms a valid left input for the NEXT hash. + # + # ch_left_first, ch_bufs, ch_buf_idx all store HASH-INPUT pointers (= slot_start + 1). + # copy_5 destination = ptr - 1 (the slot start). starting_step = CHAIN_LENGTH - 1 - n # Build L_0 = [tweak_0, zeros, input] + # first_tweak is a pointer to the tweak VALUE (slot_start + 1); copy_5 reads from + # first_tweak - 1 (the slot_start including its leading zero). first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - set_chain_left_prefix(ch_left_first, first_tweak) - copy_5(input, ch_left_first + 4) + copy_5(first_tweak - 1, ch_left_first - 1) # writes ch_left_first[-1..4] + copy_5(input, ch_left_first + 4) # writes ch_left_first[4..9] = input(4) + extra if n == 1: poseidon16_compress(ch_left_first, chain_right, output) else: - # Hash 0: L_0 → ch_bufs + 4 (writes ch_bufs[4..12], digest at ch_bufs[4..8]) + # Hash 0: L_0 → ch_bufs[0] + 4 (writes bufs[0][4..12], digest at bufs[0][4..8]) ch_buf_idx[0] = ch_bufs poseidon16_compress(ch_left_first, chain_right, ch_bufs + 4) - # Write L_1 prefix = [tweak_1, zeros] to ch_bufs[0..4] + # Write L_1 prefix via copy_5 of padded tweak_1 to ch_bufs - 1 (the extra slot). next_tweak = chain_i_tweaks + (starting_step + 1) * TWEAK_LEN - set_chain_left_prefix(ch_bufs, next_tweak) + copy_5(next_tweak - 1, ch_bufs - 1) # Hashes 1..n-2: buf[j-1] → buf[j] + 4 for j in unroll(1, n - 1): @@ -207,7 +231,7 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, cur_buf = ch_buf_idx[j] poseidon16_compress(ch_buf_idx[j - 1], chain_right, cur_buf + 4) cur_tweak = chain_i_tweaks + (starting_step + j + 1) * TWEAK_LEN - set_chain_left_prefix(cur_buf, cur_tweak) + copy_5(cur_tweak - 1, cur_buf - 1) # Final hash: buf[n-2] → output poseidon16_compress(ch_buf_idx[n - 2], chain_right, output) @@ -217,10 +241,13 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): # Sequential hash over V elements at DIGEST_LEN stride - # First hash: build from scratch - left0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn + # First hash: build from scratch. build_right_fn's copy_5 prefix trick needs + # an "extra" slot at ptr - 1, so we allocate (DIGEST_LEN + 2) and offset by +1. + left0_alloc = Array(DIGEST_LEN + 2) + left0 = left0_alloc + 1 build_left_fn(public_param, wots_public_key, left0) - right0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn + right0_alloc = Array(DIGEST_LEN + 2) + right0 = right0_alloc + 1 build_right_fn(wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) # Buffer trick for intermediate states @@ -235,7 +262,8 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): for i in unroll(1, V - 2): buf_indexes[i] = buf_indexes[i - 1] + BUF_SIZE cur_buf = buf_indexes[i] - right_i = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn + right_i_alloc = Array(DIGEST_LEN + 2) + right_i = right_i_alloc + 1 build_right_fn(wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + PP_IN_LEFT) for k in unroll(0, PP_IN_LEFT): @@ -243,7 +271,8 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): # Final hash result = Array(DIGEST_LEN) - right_last = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_right_fn + right_last_alloc = Array(DIGEST_LEN + 2) + right_last = right_last_alloc + 1 build_right_fn(wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) poseidon16_compress(buf_indexes[V - 3], right_last, result) @@ -259,10 +288,11 @@ def set_buf_prefix_left(buf, public_param): @inline def set_buf_prefix_right(buf, tweak): - buf[0] = tweak[0] - buf[1] = tweak[1] - buf[2] = 0 - buf[3] = 0 + # `tweak` points to the tweak VALUE (slot_start + 1). copy_5 reads the padded slot + # [0, tw[0], tw[1], 0, 0] and writes buf[-1..4]; the leading 0 lands at the "extra" + # slot before buf, and buf[0..4] = [tw[0], tw[1], 0, 0] — the desired prefix. + # buf must be `alloc + 1` where alloc has at least BUF_SIZE + 1 elements. + copy_5(tweak - 1, buf - 1) return @@ -278,8 +308,12 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ b3 = r3 % 2 # Level 0: build from external state_in and path_chunk (no prior hash to reuse) - left0 = Array(DIGEST_LEN + 1) # +1 for copy_5 in build_left_fn / build_right_fn - right0 = Array(DIGEST_LEN + 1) + # All `Array(DIGEST_LEN + 2)` allocations reserve 1 element at offset 0 as the "extra" + # landing spot for build_right_fn's copy_5 prefix trick; the effective pointer is alloc+1. + left0_alloc = Array(DIGEST_LEN + 2) + left0 = left0_alloc + 1 + right0_alloc = Array(DIGEST_LEN + 2) + right0 = right0_alloc + 1 if b0 == 1: build_left_fn(public_param, state_in, left0) build_right_fn(merkle_tweaks_chunk, path_chunk, right0) @@ -288,12 +322,16 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ build_right_fn(merkle_tweaks_chunk, state_in, right0) # Buffer trick: hash output to buf + PP_IN_LEFT, then prepend prefix. - buf0 = Array(BUF_SIZE) + # buf*_alloc has a leading "extra" slot so set_buf_prefix_right can use copy_5. + buf0_alloc = Array(BUF_SIZE + 1) + buf0 = buf0_alloc + 1 poseidon16_compress(left0, right0, buf0 + PP_IN_LEFT) # Level 1 - other1 = Array(DIGEST_LEN + 1) # +1 for copy_5 - buf1 = Array(BUF_SIZE) + other1_alloc = Array(DIGEST_LEN + 2) + other1 = other1_alloc + 1 + buf1_alloc = Array(BUF_SIZE + 1) + buf1 = buf1_alloc + 1 if b1 == 1: set_buf_prefix_left(buf0, public_param) build_right_fn(merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * XMSS_DIGEST, other1) @@ -304,8 +342,10 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ poseidon16_compress(other1, buf0, buf1 + PP_IN_LEFT) # Level 2 - other2 = Array(DIGEST_LEN + 1) # +1 for copy_5 - buf2 = Array(BUF_SIZE) + other2_alloc = Array(DIGEST_LEN + 2) + other2 = other2_alloc + 1 + buf2_alloc = Array(BUF_SIZE + 1) + buf2 = buf2_alloc + 1 if b2 == 1: set_buf_prefix_left(buf1, public_param) build_right_fn(merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * XMSS_DIGEST, other2) @@ -316,7 +356,8 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ poseidon16_compress(other2, buf1, buf2 + PP_IN_LEFT) # Level 3 -> state_out - other3 = Array(DIGEST_LEN + 1) # +1 for copy_5 + other3_alloc = Array(DIGEST_LEN + 2) + other3 = other3_alloc + 1 if b3 == 1: set_buf_prefix_left(buf2, public_param) build_right_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) From a76952ca1421958ba45f7311c61fc52985fb2def Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Tue, 7 Apr 2026 17:59:12 +0200 Subject: [PATCH 07/66] wip --- crates/lean_compiler/snark_lib.py | 9 ++- crates/lean_compiler/src/a_simplify_lang.rs | 9 +-- .../lean_compiler/src/instruction_encoder.rs | 8 ++- .../src/parser/parsers/function.rs | 4 +- crates/lean_prover/src/test_zkvm.rs | 19 +++++- crates/lean_prover/src/trace_gen.rs | 18 +++++ crates/lean_vm/src/isa/instruction.rs | 14 ++-- crates/lean_vm/src/tables/poseidon_16/mod.rs | 67 +++++++++++++++---- .../src/tables/poseidon_16/trace_gen.rs | 9 ++- 9 files changed, 126 insertions(+), 31 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 8eaa1d05f..63c9e53a8 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -77,8 +77,13 @@ def pop(self): NONRESERVED_PROGRAM_INPUT_START = 50 -def poseidon16_compress(left, right, output, mode): - _ = left, right, output, mode +def poseidon16_compress(left, right, output): + _ = left, right, output + + +def poseidon16_compress_half(left, right, output): + """Poseidon16 compression outputting only the first 4 FE (last 4 unconstrained).""" + _ = left, right, output def add_be(a, b, result, length=None): diff --git a/crates/lean_compiler/src/a_simplify_lang.rs b/crates/lean_compiler/src/a_simplify_lang.rs index 484422173..a45b3db5c 100644 --- a/crates/lean_compiler/src/a_simplify_lang.rs +++ b/crates/lean_compiler/src/a_simplify_lang.rs @@ -5,8 +5,8 @@ use crate::{ }; use backend::PrimeCharacteristicRing; use lean_vm::{ - Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, PrecompileArgs, PrecompileCompTimeArgs, - SourceLocation, Table, TableT, + Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, PrecompileArgs, + PrecompileCompTimeArgs, SourceLocation, Table, TableT, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -2152,7 +2152,8 @@ fn simplify_lines( } // Special handling for poseidon16 precompile - if function_name == Table::poseidon16().name() { + if function_name == Table::poseidon16().name() || function_name == POSEIDON16_HALF_NAME { + let half_output = function_name == POSEIDON16_HALF_NAME; if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" @@ -2172,7 +2173,7 @@ fn simplify_lines( arg_0: simplified_args[0].clone(), arg_1: simplified_args[1].clone(), res: simplified_args[2].clone(), - data: PrecompileCompTimeArgs::Poseidon16, + data: PrecompileCompTimeArgs::Poseidon16 { half_output }, })); continue; } diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index c97a4c3eb..28ba68088 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -48,7 +48,13 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { } Instruction::Precompile(precompile) => { let precompile_data = match &precompile.data { - PrecompileCompTimeArgs::Poseidon16 => POSEIDON_PRECOMPILE_DATA, + PrecompileCompTimeArgs::Poseidon16 { half_output } => { + if *half_output { + POSEIDON_HALF_PRECOMPILE_DATA + } else { + POSEIDON_PRECOMPILE_DATA + } + } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); mode.flag_encoding() + EXT_OP_LEN_MULTIPLIER * size diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index 139ecf991..72b104706 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -9,7 +9,7 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_NAME}; +use lean_vm::{CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_NAME}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -35,7 +35,7 @@ fn is_reserved_function_name(name: &str) -> bool { return true; } // Check precompile names (poseidon16, extension_op functions) - if name == POSEIDON16_NAME { + if name == POSEIDON16_NAME || name == POSEIDON16_HALF_NAME { return true; } if ExtensionOpMode::from_name(name).is_some() { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index fcb20307c..45a0bce2e 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -12,11 +12,19 @@ DIM = 5 N = 11 M = 3 DIGEST_LEN = 8 +HALF_DIGEST_LEN = 4 def main(): pub_start = NONRESERVED_PROGRAM_INPUT_START poseidon16_compress(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, pub_start + 6 * DIGEST_LEN) + # poseidon16_compress_half: only first 4 FE constrained + full_out = pub_start + 6 * DIGEST_LEN + half_out = pub_start + 80 + poseidon16_compress_half(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, half_out) + for i in unroll(0, HALF_DIGEST_LEN): + assert full_out[i] == half_out[i] + base_ptr = pub_start + 88 ext_a_ptr = pub_start + 88 + N ext_b_ptr = pub_start + 88 + N * (DIM + 1) @@ -58,9 +66,18 @@ def main(): // Poseidon test data let poseidon_16_compress_input: [F; 16] = rng.random(); public_input[32..48].copy_from_slice(&poseidon_16_compress_input); - public_input[48..56].copy_from_slice(&poseidon16_compress(poseidon_16_compress_input)[..8]); + let poseidon_output = poseidon16_compress(poseidon_16_compress_input); + public_input[48..56].copy_from_slice(&poseidon_output[..8]); let poseidon_24_input: [F; 24] = rng.random(); public_input[56..80].copy_from_slice(&poseidon_24_input); + // poseidon16_compress_half output at offset 80: first 4 = hash, last 4 = arbitrary pre-existing data + public_input[80..84].copy_from_slice(&poseidon_output[..4]); + public_input[84..88].copy_from_slice(&[ + F::from_usize(111), + F::from_usize(222), + F::from_usize(333), + F::from_usize(444), + ]); // Extension op operands: base[N], ext_a[N], ext_b[N] let base_slice: [F; N] = rng.random(); diff --git a/crates/lean_prover/src/trace_gen.rs b/crates/lean_prover/src/trace_gen.rs index 557cc5ed6..bfb1a1b4c 100644 --- a/crates/lean_prover/src/trace_gen.rs +++ b/crates/lean_prover/src/trace_gen.rs @@ -106,6 +106,24 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul let poseidon_trace = traces.get_mut(&Table::poseidon16()).unwrap(); fill_trace_poseidon_16(&mut poseidon_trace.columns); + // For half_output rows, override last 4 output columns with actual memory values + // (the AIR doesn't constrain them, but the lookup checks against memory) + { + let half_output_col = poseidon_trace.columns[POSEIDON_16_COL_HALF_OUTPUT].clone(); + let res_col = poseidon_trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].clone(); + for j in HALF_DIGEST_LEN..DIGEST_LEN { + poseidon_trace.columns[POSEIDON_16_COL_OUTPUT_START + j] + .par_iter_mut() + .zip(&half_output_col) + .zip(&res_col) + .for_each(|((out, &half), &res)| { + if half == F::ONE { + *out = memory_padded[res.to_usize() + j]; + } + }); + } + } + let extension_op_trace = traces.get_mut(&Table::extension_op()).unwrap(); fill_trace_extension_op(extension_op_trace, &memory_padded); diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index 53455065e..e703dcc33 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -63,21 +63,21 @@ pub struct PrecompileArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PrecompileCompTimeArgs { - Poseidon16, + Poseidon16 { half_output: bool }, ExtensionOp { size: S, mode: ExtensionOpMode }, } impl PrecompileCompTimeArgs { pub fn table(&self) -> Table { match self { - Self::Poseidon16 => Table::poseidon16(), + Self::Poseidon16 { .. } => Table::poseidon16(), Self::ExtensionOp { .. } => Table::extension_op(), } } pub fn map_size(self, f: impl FnOnce(S) -> T) -> PrecompileCompTimeArgs { match self { - Self::Poseidon16 => PrecompileCompTimeArgs::Poseidon16, + Self::Poseidon16 { half_output } => PrecompileCompTimeArgs::Poseidon16 { half_output }, Self::ExtensionOp { size, mode } => PrecompileCompTimeArgs::ExtensionOp { size: f(size), mode }, } } @@ -233,8 +233,12 @@ impl Display for PrecompileArgs { data, } = self; match data { - PrecompileCompTimeArgs::Poseidon16 => { - write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})") + PrecompileCompTimeArgs::Poseidon16 { half_output } => { + if *half_output { + write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)") + } else { + write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})") + } } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { write!(f, "{}({arg_0}, {arg_1}, {res}, {size})", mode.name()) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index fa116c876..900946e0b 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -89,16 +89,22 @@ const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; -pub const POSEIDON_PRECOMPILE_DATA: usize = 1; // domain separation: Poseidon16=1, Poseidon24=2 or 3 or 4, ExtensionOp>=8 +pub const POSEIDON_PRECOMPILE_DATA: usize = 1; +pub const POSEIDON_HALF_PRECOMPILE_DATA: usize = 2; pub const POSEIDON_16_COL_FLAG: ColIndex = 0; pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = 1; pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 2; pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; -pub const POSEIDON_16_COL_INPUT_START: ColIndex = 4; +pub const POSEIDON_16_COL_HALF_OUTPUT: ColIndex = 4; +pub const POSEIDON_16_COL_INPUT_START: ColIndex = 5; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; +/// Non-committed columns: +pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16(); pub const POSEIDON16_NAME: &str = "poseidon16_compress"; +pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; +pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Poseidon16Precompile; @@ -130,12 +136,16 @@ impl TableT for Poseidon16Precompile { ] } + fn n_columns_total(&self) -> usize { + num_cols_total_poseidon_16() + } + fn bus(&self) -> Bus { Bus { direction: BusDirection::Pull, selector: POSEIDON_16_COL_FLAG, data: vec![ - BusData::Constant(POSEIDON_PRECOMPILE_DATA), + BusData::Column(POSEIDON_16_COL_PRECOMPILE_DATA), BusData::Column(POSEIDON_16_COL_INDEX_INPUT_LEFT), BusData::Column(POSEIDON_16_COL_INDEX_INPUT_RIGHT), BusData::Column(POSEIDON_16_COL_INDEX_INPUT_RES), @@ -154,9 +164,10 @@ impl TableT for Poseidon16Precompile { arg_a: F, arg_b: F, index_res_a: F, - _: PrecompileCompTimeArgs, + args: PrecompileCompTimeArgs, ctx: &mut InstructionContext<'_, M>, ) -> Result<(), RunnerError> { + let half_output = matches!(args, PrecompileCompTimeArgs::Poseidon16 { half_output: true }); let trace = ctx.traces.get_mut(&self.table()).unwrap(); let arg0 = ctx.memory.get_slice(arg_a.to_usize(), DIGEST_LEN)?; @@ -168,17 +179,27 @@ impl TableT for Poseidon16Precompile { let output = poseidon16_compress(input); - let res_a: [F; DIGEST_LEN] = output[..DIGEST_LEN].try_into().unwrap(); - - ctx.memory.set_slice(index_res_a.to_usize(), &res_a)?; + if half_output { + ctx.memory + .set_slice(index_res_a.to_usize(), &output[..HALF_DIGEST_LEN])?; + } else { + ctx.memory.set_slice(index_res_a.to_usize(), &output)?; + } trace.columns[POSEIDON_16_COL_FLAG].push(F::ONE); trace.columns[POSEIDON_16_COL_INDEX_INPUT_LEFT].push(arg_a); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); + trace.columns[POSEIDON_16_COL_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); for (i, value) in input.iter().enumerate() { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } + // Non-committed columns + trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(if half_output { + POSEIDON_HALF_PRECOMPILE_DATA + } else { + POSEIDON_PRECOMPILE_DATA + })); // the rest of the trace is filled at the end of the execution (to get parallelism + SIMD) @@ -192,13 +213,13 @@ impl Air for Poseidon16Precompile { num_cols_poseidon_16() } fn degree_air(&self) -> usize { - 9 + 10 // Last 4 output constraints are gated by (1 - half_output), raising degree from 9 to 10 } fn down_column_indexes(&self) -> Vec { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 76 + BUS as usize + 77 } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -210,13 +231,16 @@ impl Air for Poseidon16Precompile { unsafe { std::ptr::read(&shorts[0]) } }; - // Bus data: [POSEIDON_PRECOMPILE_DATA (constant), a, b, res] + // Reconstruct precompile_data from half_output: precompile_data = 1 + 4 * half_output + let precompile_data_reconstructed = AB::IF::ONE + cols.half_output; + + // Bus data: [precompile_data, a, b, res] if BUS { builder.eval_virtual_column(eval_virtual_bus_column::( extra_data, cols.flag, &[ - AB::IF::from_usize(POSEIDON_PRECOMPILE_DATA), + precompile_data_reconstructed, cols.index_a, cols.index_b, cols.index_res, @@ -225,7 +249,7 @@ impl Air for Poseidon16Precompile { } else { builder.declare_values(std::slice::from_ref(&cols.flag)); builder.declare_values(&[ - AB::IF::from_usize(POSEIDON_PRECOMPILE_DATA), + precompile_data_reconstructed, cols.index_a, cols.index_b, cols.index_res, @@ -233,6 +257,7 @@ impl Air for Poseidon16Precompile { } builder.assert_bool(cols.flag); + builder.assert_bool(cols.half_output); eval_poseidon1_16(builder, &cols) } @@ -245,6 +270,7 @@ pub(super) struct Poseidon1Cols16 { pub index_a: T, pub index_b: T, pub index_res: T, + pub half_output: T, pub inputs: [T; WIDTH], pub beginning_full_rounds: [[T; WIDTH]; HALF_INITIAL_FULL_ROUNDS], @@ -308,6 +334,7 @@ fn eval_poseidon1_16(builder: &mut AB, local: &Poseidon1Cols16 usize { size_of::>() } +pub const fn num_cols_total_poseidon_16() -> usize { + num_cols_poseidon_16() + 1 // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA +} + #[inline] fn eval_2_full_rounds_16( state: &mut [AB::IF; WIDTH], @@ -347,6 +378,7 @@ fn eval_last_2_full_rounds_16( outputs: &[AB::IF; WIDTH / 2], round_constants_1: &[F; WIDTH], round_constants_2: &[F; WIDTH], + half_output: AB::IF, builder: &mut AB, ) { for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { @@ -363,8 +395,15 @@ fn eval_last_2_full_rounds_16( for (state_i, init_state_i) in state.iter_mut().zip(initial_state) { *state_i += *init_state_i; } - for (state_i, output_i) in state.iter_mut().zip(outputs) { - builder.assert_eq(*state_i, *output_i); + for (idx, (state_i, output_i)) in state.iter_mut().zip(outputs).enumerate() { + if idx < HALF_DIGEST_LEN { + // First 4 outputs: always constrained + builder.assert_eq(*state_i, *output_i); + } else { + // Last 4 outputs: constrained only when half_output = 0 + let one_minus_half = AB::IF::ONE - half_output; + builder.assert_zero(one_minus_half * (*state_i - *output_i)); + } *state_i = *output_i; } } diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index 04a7455c5..befa2f3b4 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -2,7 +2,7 @@ use tracing::instrument; use crate::{ F, ZERO_VEC_PTR, - tables::{Poseidon1Cols16, WIDTH, num_cols_poseidon_16}, + tables::{Poseidon1Cols16, WIDTH, num_cols_poseidon_16, num_cols_total_poseidon_16}, }; use backend::*; @@ -42,7 +42,9 @@ pub fn fill_trace_poseidon_16(trace: &mut [Vec]) { } pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { - let mut row = vec![F::ZERO; num_cols_poseidon_16()]; + // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA + let n_cols_total = num_cols_total_poseidon_16(); + let mut row = vec![F::ZERO; n_cols_total]; let ptrs: Vec<*mut F> = (0..num_cols_poseidon_16()) .map(|i| unsafe { row.as_mut_ptr().add(i) }) .collect(); @@ -53,6 +55,9 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { *perm.index_a = F::from_usize(ZERO_VEC_PTR); *perm.index_b = F::from_usize(ZERO_VEC_PTR); *perm.index_res = F::from_usize(null_hash_ptr); + *perm.half_output = F::ZERO; + // Non-committed column for padding rows + row[num_cols_poseidon_16()] = F::from_usize(crate::POSEIDON_PRECOMPILE_DATA); generate_trace_rows_for_perm(perm); row From 5725b9ac09b14c418976f870ad182ec297a76811 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 00:04:39 +0200 Subject: [PATCH 08/66] wip --- crates/lean_compiler/snark_lib.py | 10 ++ crates/lean_compiler/src/a_simplify_lang.rs | 22 +++-- .../lean_compiler/src/instruction_encoder.rs | 11 +-- .../src/parser/parsers/function.rs | 14 ++- crates/lean_prover/src/test_zkvm.rs | 26 ++++++ crates/lean_vm/src/isa/instruction.rs | 27 ++++-- crates/lean_vm/src/tables/poseidon_16/mod.rs | 91 ++++++++++++++++--- .../src/tables/poseidon_16/trace_gen.rs | 8 +- 8 files changed, 170 insertions(+), 39 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 63c9e53a8..46dfc3972 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -86,6 +86,16 @@ def poseidon16_compress_half(left, right, output): _ = left, right, output +def poseidon16_compress_zero_rsuffix(left, right, output): + """Poseidon16 compression with the last 4 FE of the right input zeroed.""" + _ = left, right, output + + +def poseidon16_compress_half_zero_rsuffix(left, right, output): + """Poseidon16 compression with zeroed right suffix and half output.""" + _ = left, right, output + + def add_be(a, b, result, length=None): _ = a, b, result, length diff --git a/crates/lean_compiler/src/a_simplify_lang.rs b/crates/lean_compiler/src/a_simplify_lang.rs index a45b3db5c..7ffbbee3b 100644 --- a/crates/lean_compiler/src/a_simplify_lang.rs +++ b/crates/lean_compiler/src/a_simplify_lang.rs @@ -5,8 +5,9 @@ use crate::{ }; use backend::PrimeCharacteristicRing; use lean_vm::{ - Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, PrecompileArgs, - PrecompileCompTimeArgs, SourceLocation, Table, TableT, + Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, + POSEIDON16_HALF_ZERO_RSUFFIX_NAME, POSEIDON16_ZERO_RSUFFIX_NAME, PrecompileArgs, PrecompileCompTimeArgs, + SourceLocation, Table, TableT, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -2151,9 +2152,15 @@ fn simplify_lines( continue; } - // Special handling for poseidon16 precompile - if function_name == Table::poseidon16().name() || function_name == POSEIDON16_HALF_NAME { - let half_output = function_name == POSEIDON16_HALF_NAME; + // Special handling for poseidon16 precompile variants + let poseidon16_variant = match function_name { + _ if function_name == Table::poseidon16().name() => Some((false, false)), + _ if function_name == POSEIDON16_HALF_NAME => Some((true, false)), + _ if function_name == POSEIDON16_ZERO_RSUFFIX_NAME => Some((false, true)), + _ if function_name == POSEIDON16_HALF_ZERO_RSUFFIX_NAME => Some((true, true)), + _ => None, + }; + if let Some((half_output, zero_right_suffix)) = poseidon16_variant { if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" @@ -2173,7 +2180,10 @@ fn simplify_lines( arg_0: simplified_args[0].clone(), arg_1: simplified_args[1].clone(), res: simplified_args[2].clone(), - data: PrecompileCompTimeArgs::Poseidon16 { half_output }, + data: PrecompileCompTimeArgs::Poseidon16 { + half_output, + zero_right_suffix, + }, })); continue; } diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index 28ba68088..a9eda167e 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -48,13 +48,10 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { } Instruction::Precompile(precompile) => { let precompile_data = match &precompile.data { - PrecompileCompTimeArgs::Poseidon16 { half_output } => { - if *half_output { - POSEIDON_HALF_PRECOMPILE_DATA - } else { - POSEIDON_PRECOMPILE_DATA - } - } + PrecompileCompTimeArgs::Poseidon16 { + half_output, + zero_right_suffix, + } => POSEIDON_PRECOMPILE_DATA + *half_output as usize + 2 * *zero_right_suffix as usize, PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); mode.flag_encoding() + EXT_OP_LEN_MULTIPLIER * size diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index 72b104706..1a22f0f8e 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -9,7 +9,10 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_NAME}; +use lean_vm::{ + CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_HALF_ZERO_RSUFFIX_NAME, POSEIDON16_NAME, + POSEIDON16_ZERO_RSUFFIX_NAME, +}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -35,7 +38,14 @@ fn is_reserved_function_name(name: &str) -> bool { return true; } // Check precompile names (poseidon16, extension_op functions) - if name == POSEIDON16_NAME || name == POSEIDON16_HALF_NAME { + if [ + POSEIDON16_NAME, + POSEIDON16_HALF_NAME, + POSEIDON16_ZERO_RSUFFIX_NAME, + POSEIDON16_HALF_ZERO_RSUFFIX_NAME, + ] + .contains(&name) + { return true; } if ExtensionOpMode::from_name(name).is_some() { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index 45a0bce2e..d8d7a23fb 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -25,6 +25,17 @@ def main(): for i in unroll(0, HALF_DIGEST_LEN): assert full_out[i] == half_out[i] + # poseidon16_compress_zero_rsuffix: last 4 right inputs zeroed in hash computation + # (the right input's last 4 FE are random/non-zero in memory) + zrs_out = pub_start + 1400 + poseidon16_compress_zero_rsuffix(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, zrs_out) + + # poseidon16_compress_half_zero_rsuffix: both features combined + hzrs_out = pub_start + 1410 + poseidon16_compress_half_zero_rsuffix(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, hzrs_out) + for i in unroll(0, HALF_DIGEST_LEN): + assert zrs_out[i] == hzrs_out[i] + base_ptr = pub_start + 88 ext_a_ptr = pub_start + 88 + N ext_b_ptr = pub_start + 88 + N * (DIM + 1) @@ -79,6 +90,21 @@ def main(): F::from_usize(444), ]); + // poseidon16_compress_zero_rsuffix: hash with last 4 right inputs zeroed + // Note: poseidon_16_compress_input[12..16] are random non-zero — that's the tricky part + let mut zrs_input = poseidon_16_compress_input; + zrs_input[12..16].fill(F::ZERO); + let zrs_output = poseidon16_compress(zrs_input); + public_input[1400..1408].copy_from_slice(&zrs_output[..8]); + // poseidon16_compress_half_zero_rsuffix: first 4 = same as zrs, last 4 = arbitrary + public_input[1410..1414].copy_from_slice(&zrs_output[..4]); + public_input[1414..1418].copy_from_slice(&[ + F::from_usize(555), + F::from_usize(666), + F::from_usize(777), + F::from_usize(888), + ]); + // Extension op operands: base[N], ext_a[N], ext_b[N] let base_slice: [F; N] = rng.random(); let ext_a_slice: [EF; N] = rng.random(); diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index e703dcc33..63a6d3bef 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -63,7 +63,7 @@ pub struct PrecompileArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PrecompileCompTimeArgs { - Poseidon16 { half_output: bool }, + Poseidon16 { half_output: bool, zero_right_suffix: bool }, ExtensionOp { size: S, mode: ExtensionOpMode }, } @@ -77,7 +77,13 @@ impl PrecompileCompTimeArgs { pub fn map_size(self, f: impl FnOnce(S) -> T) -> PrecompileCompTimeArgs { match self { - Self::Poseidon16 { half_output } => PrecompileCompTimeArgs::Poseidon16 { half_output }, + Self::Poseidon16 { + half_output, + zero_right_suffix, + } => PrecompileCompTimeArgs::Poseidon16 { + half_output, + zero_right_suffix, + }, Self::ExtensionOp { size, mode } => PrecompileCompTimeArgs::ExtensionOp { size: f(size), mode }, } } @@ -233,12 +239,17 @@ impl Display for PrecompileArgs { data, } = self; match data { - PrecompileCompTimeArgs::Poseidon16 { half_output } => { - if *half_output { - write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)") - } else { - write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})") - } + PrecompileCompTimeArgs::Poseidon16 { + half_output, + zero_right_suffix, + } => { + let suffix = match (*half_output, *zero_right_suffix) { + (false, false) => "", + (true, false) => "_half", + (false, true) => "_zero_rsuffix", + (true, true) => "_half_zero_rsuffix", + }; + write!(f, "{POSEIDON16_NAME}{suffix}({arg_0}, {arg_1}, {res})") } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { write!(f, "{}({arg_0}, {arg_1}, {res}, {size})", mode.name()) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 900946e0b..69f7bcf0c 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -89,21 +89,27 @@ const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; +/// PRECOMPILE_DATA encoding: 1 + half_output + 2 * zero_right_suffix pub const POSEIDON_PRECOMPILE_DATA: usize = 1; pub const POSEIDON_HALF_PRECOMPILE_DATA: usize = 2; +pub const POSEIDON_ZERO_RSUFFIX_PRECOMPILE_DATA: usize = 3; +pub const POSEIDON_HALF_ZERO_RSUFFIX_PRECOMPILE_DATA: usize = 4; pub const POSEIDON_16_COL_FLAG: ColIndex = 0; pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = 1; pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 2; pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; pub const POSEIDON_16_COL_HALF_OUTPUT: ColIndex = 4; -pub const POSEIDON_16_COL_INPUT_START: ColIndex = 5; +pub const POSEIDON_16_COL_ZERO_RIGHT_SUFFIX: ColIndex = 5; +pub const POSEIDON_16_COL_INPUT_START: ColIndex = 6; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; /// Non-committed columns: pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16(); pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; +pub const POSEIDON16_ZERO_RSUFFIX_NAME: &str = "poseidon16_compress_zero_rsuffix"; +pub const POSEIDON16_HALF_ZERO_RSUFFIX_NAME: &str = "poseidon16_compress_half_zero_rsuffix"; pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -167,7 +173,13 @@ impl TableT for Poseidon16Precompile { args: PrecompileCompTimeArgs, ctx: &mut InstructionContext<'_, M>, ) -> Result<(), RunnerError> { - let half_output = matches!(args, PrecompileCompTimeArgs::Poseidon16 { half_output: true }); + let (half_output, zero_right_suffix) = match args { + PrecompileCompTimeArgs::Poseidon16 { + half_output, + zero_right_suffix, + } => (half_output, zero_right_suffix), + _ => unreachable!(), + }; let trace = ctx.traces.get_mut(&self.table()).unwrap(); let arg0 = ctx.memory.get_slice(arg_a.to_usize(), DIGEST_LEN)?; @@ -177,7 +189,13 @@ impl TableT for Poseidon16Precompile { input[..DIGEST_LEN].copy_from_slice(&arg0); input[DIGEST_LEN..].copy_from_slice(&arg1); - let output = poseidon16_compress(input); + // Zero the last 4 right inputs for the hash computation + let mut hash_input = input; + if zero_right_suffix { + hash_input[DIGEST_LEN + HALF_DIGEST_LEN..].fill(F::ZERO); + } + + let output = poseidon16_compress(hash_input); if half_output { ctx.memory @@ -191,15 +209,13 @@ impl TableT for Poseidon16Precompile { trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); trace.columns[POSEIDON_16_COL_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); + trace.columns[POSEIDON_16_COL_ZERO_RIGHT_SUFFIX].push(if zero_right_suffix { F::ONE } else { F::ZERO }); for (i, value) in input.iter().enumerate() { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } - // Non-committed columns - trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(if half_output { - POSEIDON_HALF_PRECOMPILE_DATA - } else { - POSEIDON_PRECOMPILE_DATA - })); + // Non-committed precompile_data + let precompile_data = POSEIDON_PRECOMPILE_DATA + half_output as usize + 2 * zero_right_suffix as usize; + trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(precompile_data)); // the rest of the trace is filled at the end of the execution (to get parallelism + SIMD) @@ -219,7 +235,7 @@ impl Air for Poseidon16Precompile { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 77 + BUS as usize + 79 // 76 perm + 3 booleans } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -231,8 +247,9 @@ impl Air for Poseidon16Precompile { unsafe { std::ptr::read(&shorts[0]) } }; - // Reconstruct precompile_data from half_output: precompile_data = 1 + 4 * half_output - let precompile_data_reconstructed = AB::IF::ONE + cols.half_output; + // Reconstruct precompile_data: 1 + half_output + 2 * zero_right_suffix + let precompile_data_reconstructed = + AB::IF::ONE + cols.half_output + cols.zero_right_suffix * AB::IF::from_usize(2); // Bus data: [precompile_data, a, b, res] if BUS { @@ -258,6 +275,7 @@ impl Air for Poseidon16Precompile { builder.assert_bool(cols.flag); builder.assert_bool(cols.half_output); + builder.assert_bool(cols.zero_right_suffix); eval_poseidon1_16(builder, &cols) } @@ -271,6 +289,7 @@ pub(super) struct Poseidon1Cols16 { pub index_b: T, pub index_res: T, pub half_output: T, + pub zero_right_suffix: T, pub inputs: [T; WIDTH], pub beginning_full_rounds: [[T; WIDTH]; HALF_INITIAL_FULL_ROUNDS], @@ -280,10 +299,24 @@ pub(super) struct Poseidon1Cols16 { } fn eval_poseidon1_16(builder: &mut AB, local: &Poseidon1Cols16) { - let mut state: [_; WIDTH] = local.inputs; - let initial_constants = poseidon1_initial_constants(); - for round in 0..HALF_INITIAL_FULL_ROUNDS { + + // First pair of full rounds: compute both paths (normal and zeroed right suffix) + // and merge using the boolean flag. + { + let mut state_normal: [_; WIDTH] = local.inputs; + compute_2_full_rounds_16::(&mut state_normal, &initial_constants[0], &initial_constants[1]); + let mut state_zeroed: [_; WIDTH] = local.inputs; + compute_2_full_rounds_16::(&mut state_zeroed, &initial_constants[0], &initial_constants[1]); + for i in 0..WIDTH { + let merged = state_normal[i] + local.zero_right_suffix * (state_zeroed[i] - state_normal[i]); + builder.assert_eq(merged, local.beginning_full_rounds[0][i]); + } + } + + // Continue from committed columns (degree 1) + let mut state: [_; WIDTH] = local.beginning_full_rounds[0]; + for round in 1..HALF_INITIAL_FULL_ROUNDS { eval_2_full_rounds_16( &mut state, &local.beginning_full_rounds[round], @@ -347,6 +380,34 @@ pub const fn num_cols_total_poseidon_16() -> usize { num_cols_poseidon_16() + 1 // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA } +/// Compute 2 full rounds without asserting. When `ZERO_SUFFIX`, elements 12-15 +/// are treated as zero: round 1 cubes become pure field constants `c^3` (no symbolic +/// muls), and the MDS of those 4 slots only contributes constant corrections. +#[inline] +fn compute_2_full_rounds_16( + state: &mut [AB::IF; WIDTH], + round_constants_1: &[F; WIDTH], + round_constants_2: &[F; WIDTH], +) { + for (i, (s, r)) in state.iter_mut().zip(round_constants_1.iter()).enumerate() { + if ZERO_SUFFIX && i >= DIGEST_LEN + HALF_DIGEST_LEN { + // (0 + c)^3 = c^3: pure field constant, no symbolic mul + let c3 = r.cube(); + *s = mul_kb(AB::IF::ONE, c3); + } else { + add_kb(s, *r); + *s = s.cube(); + } + } + mds_air_16(state); + // Round 2: all 16 elements are now symbolic (MDS mixed), no shortcut + for (s, r) in state.iter_mut().zip(round_constants_2.iter()) { + add_kb(s, *r); + *s = s.cube(); + } + mds_air_16(state); +} + #[inline] fn eval_2_full_rounds_16( state: &mut [AB::IF; WIDTH], diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index befa2f3b4..023db290d 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -1,7 +1,7 @@ use tracing::instrument; use crate::{ - F, ZERO_VEC_PTR, + DIGEST_LEN, F, HALF_DIGEST_LEN, ZERO_VEC_PTR, tables::{Poseidon1Cols16, WIDTH, num_cols_poseidon_16, num_cols_total_poseidon_16}, }; use backend::*; @@ -56,6 +56,7 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { *perm.index_b = F::from_usize(ZERO_VEC_PTR); *perm.index_res = F::from_usize(null_hash_ptr); *perm.half_output = F::ZERO; + *perm.zero_right_suffix = F::ZERO; // Non-committed column for padding rows row[num_cols_poseidon_16()] = F::from_usize(crate::POSEIDON_PRECOMPILE_DATA); @@ -66,6 +67,11 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseidon1Cols16<&mut F>) { let inputs: [F; WIDTH] = std::array::from_fn(|i| *perm.inputs[i]); let mut state = inputs; + // Zero last 4 right inputs when zero_right_suffix=1 (works for both scalar and SIMD packed) + let one_minus_zrs = F::ONE - *perm.zero_right_suffix; + for j in 0..HALF_DIGEST_LEN { + state[DIGEST_LEN + HALF_DIGEST_LEN + j] = state[DIGEST_LEN + HALF_DIGEST_LEN + j] * one_minus_zrs; + } // No initial linear layer for Poseidon1 (unlike Poseidon2) From 5740a88439649bfeec3a37a832585d7fb7149f8a Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 07:26:03 +0200 Subject: [PATCH 09/66] w --- .../src/tables/poseidon_16/trace_gen.rs | 2 +- crates/rec_aggregation/xmss_aggregate.py | 66 ++++++++----------- crates/xmss/src/wots.rs | 30 +++++---- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index 023db290d..4f89d6867 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -70,7 +70,7 @@ fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseido // Zero last 4 right inputs when zero_right_suffix=1 (works for both scalar and SIMD packed) let one_minus_zrs = F::ONE - *perm.zero_right_suffix; for j in 0..HALF_DIGEST_LEN { - state[DIGEST_LEN + HALF_DIGEST_LEN + j] = state[DIGEST_LEN + HALF_DIGEST_LEN + j] * one_minus_zrs; + state[DIGEST_LEN + HALF_DIGEST_LEN + j] *= one_minus_zrs; } // No initial linear layer for Poseidon1 (unlike Poseidon2) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index dcf75a5cd..e2f72da48 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -239,44 +239,34 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): - # Sequential hash over V elements at DIGEST_LEN stride - - # First hash: build from scratch. build_right_fn's copy_5 prefix trick needs - # an "extra" slot at ptr - 1, so we allocate (DIGEST_LEN + 2) and offset by +1. - left0_alloc = Array(DIGEST_LEN + 2) - left0 = left0_alloc + 1 - build_left_fn(public_param, wots_public_key, left0) - right0_alloc = Array(DIGEST_LEN + 2) - right0 = right0_alloc + 1 - build_right_fn(wots_pk_tweaks, wots_public_key + DIGEST_LEN, right0) - - # Buffer trick for intermediate states - bufs = Array((V - 2) * BUF_SIZE) - buf_indexes = Array(V - 2) - buf_indexes[0] = bufs - - poseidon16_compress(left0, right0, bufs + PP_IN_LEFT) - for k in unroll(0, PP_IN_LEFT): - bufs[k] = public_param[k] - - for i in unroll(1, V - 2): - buf_indexes[i] = buf_indexes[i - 1] + BUF_SIZE - cur_buf = buf_indexes[i] - right_i_alloc = Array(DIGEST_LEN + 2) - right_i = right_i_alloc + 1 - build_right_fn(wots_pk_tweaks + i * TWEAK_LEN, wots_public_key + (i + 1) * DIGEST_LEN, right_i) - poseidon16_compress(buf_indexes[i - 1], right_i, cur_buf + PP_IN_LEFT) - for k in unroll(0, PP_IN_LEFT): - cur_buf[k] = public_param[k] - - # Final hash - result = Array(DIGEST_LEN) - right_last_alloc = Array(DIGEST_LEN + 2) - right_last = right_last_alloc + 1 - build_right_fn(wots_pk_tweaks + (V - 2) * TWEAK_LEN, wots_public_key + (V - 1) * DIGEST_LEN, right_last) - poseidon16_compress(buf_indexes[V - 3], right_last, result) - - return result + # Sponge-like hash of V public key digests. + # IV = [tweak(2) | pp(4) | 00(2)], then ingest 8 FE per step (2 digests). + # V must be even. Digests in wots_public_key are at stride DIGEST_LEN (=8). + N_CHUNKS = V / 2 + + # Build IV: [tweak(2) | 0 | pp(4) | 0] + iv = Array(DIGEST_LEN) + iv[0] = wots_pk_tweaks[0] + iv[1] = wots_pk_tweaks[1] + iv[2] = 0 + for k in unroll(0, PUBLIC_PARAM_LEN_FE): + iv[3 + k] = public_param[k] + iv[7] = 0 + + # Ingest V digests, 2 at a time (8 FE per chunk) + # Each chunk packs pk[2i] and pk[2i+1] (each XMSS_DIGEST FE, at DIGEST_LEN stride) + chunks = Array(N_CHUNKS * DIGEST_LEN) + for i in unroll(0, N_CHUNKS): + for k in unroll(0, XMSS_DIGEST): + chunks[i * DIGEST_LEN + k] = wots_public_key[(2 * i) * DIGEST_LEN + k] + chunks[i * DIGEST_LEN + XMSS_DIGEST + k] = wots_public_key[(2 * i + 1) * DIGEST_LEN + k] + + states = Array(N_CHUNKS * DIGEST_LEN) + poseidon16_compress(iv, chunks, states) + for i in unroll(1, N_CHUNKS): + poseidon16_compress(states + (i - 1) * DIGEST_LEN, chunks + i * DIGEST_LEN, states + i * DIGEST_LEN) + + return states + (N_CHUNKS - 1) * DIGEST_LEN @inline diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 53997a72c..db03a182e 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -92,20 +92,26 @@ impl WotsSignature { } impl WotsPublicKey { + /// Sponge-like hash of V public key digests. + /// IV = [tweak(2) | pp(4) | 00(2)], then ingest 8 FE per step (2 digests at a time). + /// Final output truncated to DIGEST_SIZE (4 FE). pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { - let left = build_left(&public_param, &self.0[0]); - let right = build_right(make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot), &self.0[1]); - let mut running_hash: Digest = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] - .try_into() - .unwrap(); - for i in 2..V { - let left = build_left(&public_param, &running_hash); - let right = build_right(make_tweak(TWEAK_TYPE_WOTS_PK, i - 1, slot), &self.0[i]); - running_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] - .try_into() - .unwrap(); + assert!(V % 2 == 0); + // IV: [tweak(2) | 0 | pp(4) | 0] + let tweak = make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot); + let mut state = [F::default(); 8]; + state[..TWEAK_LEN].copy_from_slice(&tweak); + // state[2] = 0 (default) + state[3..3 + PUBLIC_PARAM_LEN_FE].copy_from_slice(&public_param); + // state[7] = 0 (default) + + for i in (0..V).step_by(2) { + let mut chunk = [F::default(); 8]; + chunk[..DIGEST_SIZE].copy_from_slice(&self.0[i]); + chunk[DIGEST_SIZE..].copy_from_slice(&self.0[i + 1]); + state = poseidon16_compress_pair(&state, &chunk); } - running_hash + state[..DIGEST_SIZE].try_into().unwrap() } } From ced35b01ce26e1d72acccc30d057469d9cc618e0 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 07:59:07 +0200 Subject: [PATCH 10/66] Revert "wip" This reverts commit 5725b9ac09b14c418976f870ad182ec297a76811. --- crates/lean_compiler/snark_lib.py | 10 -- crates/lean_compiler/src/a_simplify_lang.rs | 22 ++--- .../lean_compiler/src/instruction_encoder.rs | 11 ++- .../src/parser/parsers/function.rs | 14 +-- crates/lean_prover/src/test_zkvm.rs | 26 ------ crates/lean_vm/src/isa/instruction.rs | 27 ++---- crates/lean_vm/src/tables/poseidon_16/mod.rs | 91 +++---------------- .../src/tables/poseidon_16/trace_gen.rs | 8 +- 8 files changed, 39 insertions(+), 170 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 46dfc3972..63c9e53a8 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -86,16 +86,6 @@ def poseidon16_compress_half(left, right, output): _ = left, right, output -def poseidon16_compress_zero_rsuffix(left, right, output): - """Poseidon16 compression with the last 4 FE of the right input zeroed.""" - _ = left, right, output - - -def poseidon16_compress_half_zero_rsuffix(left, right, output): - """Poseidon16 compression with zeroed right suffix and half output.""" - _ = left, right, output - - def add_be(a, b, result, length=None): _ = a, b, result, length diff --git a/crates/lean_compiler/src/a_simplify_lang.rs b/crates/lean_compiler/src/a_simplify_lang.rs index 7ffbbee3b..a45b3db5c 100644 --- a/crates/lean_compiler/src/a_simplify_lang.rs +++ b/crates/lean_compiler/src/a_simplify_lang.rs @@ -5,9 +5,8 @@ use crate::{ }; use backend::PrimeCharacteristicRing; use lean_vm::{ - Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, - POSEIDON16_HALF_ZERO_RSUFFIX_NAME, POSEIDON16_ZERO_RSUFFIX_NAME, PrecompileArgs, PrecompileCompTimeArgs, - SourceLocation, Table, TableT, + Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, PrecompileArgs, + PrecompileCompTimeArgs, SourceLocation, Table, TableT, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -2152,15 +2151,9 @@ fn simplify_lines( continue; } - // Special handling for poseidon16 precompile variants - let poseidon16_variant = match function_name { - _ if function_name == Table::poseidon16().name() => Some((false, false)), - _ if function_name == POSEIDON16_HALF_NAME => Some((true, false)), - _ if function_name == POSEIDON16_ZERO_RSUFFIX_NAME => Some((false, true)), - _ if function_name == POSEIDON16_HALF_ZERO_RSUFFIX_NAME => Some((true, true)), - _ => None, - }; - if let Some((half_output, zero_right_suffix)) = poseidon16_variant { + // Special handling for poseidon16 precompile + if function_name == Table::poseidon16().name() || function_name == POSEIDON16_HALF_NAME { + let half_output = function_name == POSEIDON16_HALF_NAME; if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" @@ -2180,10 +2173,7 @@ fn simplify_lines( arg_0: simplified_args[0].clone(), arg_1: simplified_args[1].clone(), res: simplified_args[2].clone(), - data: PrecompileCompTimeArgs::Poseidon16 { - half_output, - zero_right_suffix, - }, + data: PrecompileCompTimeArgs::Poseidon16 { half_output }, })); continue; } diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index a9eda167e..28ba68088 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -48,10 +48,13 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { } Instruction::Precompile(precompile) => { let precompile_data = match &precompile.data { - PrecompileCompTimeArgs::Poseidon16 { - half_output, - zero_right_suffix, - } => POSEIDON_PRECOMPILE_DATA + *half_output as usize + 2 * *zero_right_suffix as usize, + PrecompileCompTimeArgs::Poseidon16 { half_output } => { + if *half_output { + POSEIDON_HALF_PRECOMPILE_DATA + } else { + POSEIDON_PRECOMPILE_DATA + } + } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); mode.flag_encoding() + EXT_OP_LEN_MULTIPLIER * size diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index 1a22f0f8e..72b104706 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -9,10 +9,7 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{ - CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_HALF_ZERO_RSUFFIX_NAME, POSEIDON16_NAME, - POSEIDON16_ZERO_RSUFFIX_NAME, -}; +use lean_vm::{CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_NAME}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -38,14 +35,7 @@ fn is_reserved_function_name(name: &str) -> bool { return true; } // Check precompile names (poseidon16, extension_op functions) - if [ - POSEIDON16_NAME, - POSEIDON16_HALF_NAME, - POSEIDON16_ZERO_RSUFFIX_NAME, - POSEIDON16_HALF_ZERO_RSUFFIX_NAME, - ] - .contains(&name) - { + if name == POSEIDON16_NAME || name == POSEIDON16_HALF_NAME { return true; } if ExtensionOpMode::from_name(name).is_some() { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index d8d7a23fb..45a0bce2e 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -25,17 +25,6 @@ def main(): for i in unroll(0, HALF_DIGEST_LEN): assert full_out[i] == half_out[i] - # poseidon16_compress_zero_rsuffix: last 4 right inputs zeroed in hash computation - # (the right input's last 4 FE are random/non-zero in memory) - zrs_out = pub_start + 1400 - poseidon16_compress_zero_rsuffix(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, zrs_out) - - # poseidon16_compress_half_zero_rsuffix: both features combined - hzrs_out = pub_start + 1410 - poseidon16_compress_half_zero_rsuffix(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, hzrs_out) - for i in unroll(0, HALF_DIGEST_LEN): - assert zrs_out[i] == hzrs_out[i] - base_ptr = pub_start + 88 ext_a_ptr = pub_start + 88 + N ext_b_ptr = pub_start + 88 + N * (DIM + 1) @@ -90,21 +79,6 @@ def main(): F::from_usize(444), ]); - // poseidon16_compress_zero_rsuffix: hash with last 4 right inputs zeroed - // Note: poseidon_16_compress_input[12..16] are random non-zero — that's the tricky part - let mut zrs_input = poseidon_16_compress_input; - zrs_input[12..16].fill(F::ZERO); - let zrs_output = poseidon16_compress(zrs_input); - public_input[1400..1408].copy_from_slice(&zrs_output[..8]); - // poseidon16_compress_half_zero_rsuffix: first 4 = same as zrs, last 4 = arbitrary - public_input[1410..1414].copy_from_slice(&zrs_output[..4]); - public_input[1414..1418].copy_from_slice(&[ - F::from_usize(555), - F::from_usize(666), - F::from_usize(777), - F::from_usize(888), - ]); - // Extension op operands: base[N], ext_a[N], ext_b[N] let base_slice: [F; N] = rng.random(); let ext_a_slice: [EF; N] = rng.random(); diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index 63a6d3bef..e703dcc33 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -63,7 +63,7 @@ pub struct PrecompileArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PrecompileCompTimeArgs { - Poseidon16 { half_output: bool, zero_right_suffix: bool }, + Poseidon16 { half_output: bool }, ExtensionOp { size: S, mode: ExtensionOpMode }, } @@ -77,13 +77,7 @@ impl PrecompileCompTimeArgs { pub fn map_size(self, f: impl FnOnce(S) -> T) -> PrecompileCompTimeArgs { match self { - Self::Poseidon16 { - half_output, - zero_right_suffix, - } => PrecompileCompTimeArgs::Poseidon16 { - half_output, - zero_right_suffix, - }, + Self::Poseidon16 { half_output } => PrecompileCompTimeArgs::Poseidon16 { half_output }, Self::ExtensionOp { size, mode } => PrecompileCompTimeArgs::ExtensionOp { size: f(size), mode }, } } @@ -239,17 +233,12 @@ impl Display for PrecompileArgs { data, } = self; match data { - PrecompileCompTimeArgs::Poseidon16 { - half_output, - zero_right_suffix, - } => { - let suffix = match (*half_output, *zero_right_suffix) { - (false, false) => "", - (true, false) => "_half", - (false, true) => "_zero_rsuffix", - (true, true) => "_half_zero_rsuffix", - }; - write!(f, "{POSEIDON16_NAME}{suffix}({arg_0}, {arg_1}, {res})") + PrecompileCompTimeArgs::Poseidon16 { half_output } => { + if *half_output { + write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)") + } else { + write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})") + } } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { write!(f, "{}({arg_0}, {arg_1}, {res}, {size})", mode.name()) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 69f7bcf0c..900946e0b 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -89,27 +89,21 @@ const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; -/// PRECOMPILE_DATA encoding: 1 + half_output + 2 * zero_right_suffix pub const POSEIDON_PRECOMPILE_DATA: usize = 1; pub const POSEIDON_HALF_PRECOMPILE_DATA: usize = 2; -pub const POSEIDON_ZERO_RSUFFIX_PRECOMPILE_DATA: usize = 3; -pub const POSEIDON_HALF_ZERO_RSUFFIX_PRECOMPILE_DATA: usize = 4; pub const POSEIDON_16_COL_FLAG: ColIndex = 0; pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = 1; pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 2; pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; pub const POSEIDON_16_COL_HALF_OUTPUT: ColIndex = 4; -pub const POSEIDON_16_COL_ZERO_RIGHT_SUFFIX: ColIndex = 5; -pub const POSEIDON_16_COL_INPUT_START: ColIndex = 6; +pub const POSEIDON_16_COL_INPUT_START: ColIndex = 5; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; /// Non-committed columns: pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16(); pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; -pub const POSEIDON16_ZERO_RSUFFIX_NAME: &str = "poseidon16_compress_zero_rsuffix"; -pub const POSEIDON16_HALF_ZERO_RSUFFIX_NAME: &str = "poseidon16_compress_half_zero_rsuffix"; pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -173,13 +167,7 @@ impl TableT for Poseidon16Precompile { args: PrecompileCompTimeArgs, ctx: &mut InstructionContext<'_, M>, ) -> Result<(), RunnerError> { - let (half_output, zero_right_suffix) = match args { - PrecompileCompTimeArgs::Poseidon16 { - half_output, - zero_right_suffix, - } => (half_output, zero_right_suffix), - _ => unreachable!(), - }; + let half_output = matches!(args, PrecompileCompTimeArgs::Poseidon16 { half_output: true }); let trace = ctx.traces.get_mut(&self.table()).unwrap(); let arg0 = ctx.memory.get_slice(arg_a.to_usize(), DIGEST_LEN)?; @@ -189,13 +177,7 @@ impl TableT for Poseidon16Precompile { input[..DIGEST_LEN].copy_from_slice(&arg0); input[DIGEST_LEN..].copy_from_slice(&arg1); - // Zero the last 4 right inputs for the hash computation - let mut hash_input = input; - if zero_right_suffix { - hash_input[DIGEST_LEN + HALF_DIGEST_LEN..].fill(F::ZERO); - } - - let output = poseidon16_compress(hash_input); + let output = poseidon16_compress(input); if half_output { ctx.memory @@ -209,13 +191,15 @@ impl TableT for Poseidon16Precompile { trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); trace.columns[POSEIDON_16_COL_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); - trace.columns[POSEIDON_16_COL_ZERO_RIGHT_SUFFIX].push(if zero_right_suffix { F::ONE } else { F::ZERO }); for (i, value) in input.iter().enumerate() { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } - // Non-committed precompile_data - let precompile_data = POSEIDON_PRECOMPILE_DATA + half_output as usize + 2 * zero_right_suffix as usize; - trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(precompile_data)); + // Non-committed columns + trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(if half_output { + POSEIDON_HALF_PRECOMPILE_DATA + } else { + POSEIDON_PRECOMPILE_DATA + })); // the rest of the trace is filled at the end of the execution (to get parallelism + SIMD) @@ -235,7 +219,7 @@ impl Air for Poseidon16Precompile { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 79 // 76 perm + 3 booleans + BUS as usize + 77 } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -247,9 +231,8 @@ impl Air for Poseidon16Precompile { unsafe { std::ptr::read(&shorts[0]) } }; - // Reconstruct precompile_data: 1 + half_output + 2 * zero_right_suffix - let precompile_data_reconstructed = - AB::IF::ONE + cols.half_output + cols.zero_right_suffix * AB::IF::from_usize(2); + // Reconstruct precompile_data from half_output: precompile_data = 1 + 4 * half_output + let precompile_data_reconstructed = AB::IF::ONE + cols.half_output; // Bus data: [precompile_data, a, b, res] if BUS { @@ -275,7 +258,6 @@ impl Air for Poseidon16Precompile { builder.assert_bool(cols.flag); builder.assert_bool(cols.half_output); - builder.assert_bool(cols.zero_right_suffix); eval_poseidon1_16(builder, &cols) } @@ -289,7 +271,6 @@ pub(super) struct Poseidon1Cols16 { pub index_b: T, pub index_res: T, pub half_output: T, - pub zero_right_suffix: T, pub inputs: [T; WIDTH], pub beginning_full_rounds: [[T; WIDTH]; HALF_INITIAL_FULL_ROUNDS], @@ -299,24 +280,10 @@ pub(super) struct Poseidon1Cols16 { } fn eval_poseidon1_16(builder: &mut AB, local: &Poseidon1Cols16) { - let initial_constants = poseidon1_initial_constants(); + let mut state: [_; WIDTH] = local.inputs; - // First pair of full rounds: compute both paths (normal and zeroed right suffix) - // and merge using the boolean flag. - { - let mut state_normal: [_; WIDTH] = local.inputs; - compute_2_full_rounds_16::(&mut state_normal, &initial_constants[0], &initial_constants[1]); - let mut state_zeroed: [_; WIDTH] = local.inputs; - compute_2_full_rounds_16::(&mut state_zeroed, &initial_constants[0], &initial_constants[1]); - for i in 0..WIDTH { - let merged = state_normal[i] + local.zero_right_suffix * (state_zeroed[i] - state_normal[i]); - builder.assert_eq(merged, local.beginning_full_rounds[0][i]); - } - } - - // Continue from committed columns (degree 1) - let mut state: [_; WIDTH] = local.beginning_full_rounds[0]; - for round in 1..HALF_INITIAL_FULL_ROUNDS { + let initial_constants = poseidon1_initial_constants(); + for round in 0..HALF_INITIAL_FULL_ROUNDS { eval_2_full_rounds_16( &mut state, &local.beginning_full_rounds[round], @@ -380,34 +347,6 @@ pub const fn num_cols_total_poseidon_16() -> usize { num_cols_poseidon_16() + 1 // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA } -/// Compute 2 full rounds without asserting. When `ZERO_SUFFIX`, elements 12-15 -/// are treated as zero: round 1 cubes become pure field constants `c^3` (no symbolic -/// muls), and the MDS of those 4 slots only contributes constant corrections. -#[inline] -fn compute_2_full_rounds_16( - state: &mut [AB::IF; WIDTH], - round_constants_1: &[F; WIDTH], - round_constants_2: &[F; WIDTH], -) { - for (i, (s, r)) in state.iter_mut().zip(round_constants_1.iter()).enumerate() { - if ZERO_SUFFIX && i >= DIGEST_LEN + HALF_DIGEST_LEN { - // (0 + c)^3 = c^3: pure field constant, no symbolic mul - let c3 = r.cube(); - *s = mul_kb(AB::IF::ONE, c3); - } else { - add_kb(s, *r); - *s = s.cube(); - } - } - mds_air_16(state); - // Round 2: all 16 elements are now symbolic (MDS mixed), no shortcut - for (s, r) in state.iter_mut().zip(round_constants_2.iter()) { - add_kb(s, *r); - *s = s.cube(); - } - mds_air_16(state); -} - #[inline] fn eval_2_full_rounds_16( state: &mut [AB::IF; WIDTH], diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index 4f89d6867..befa2f3b4 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -1,7 +1,7 @@ use tracing::instrument; use crate::{ - DIGEST_LEN, F, HALF_DIGEST_LEN, ZERO_VEC_PTR, + F, ZERO_VEC_PTR, tables::{Poseidon1Cols16, WIDTH, num_cols_poseidon_16, num_cols_total_poseidon_16}, }; use backend::*; @@ -56,7 +56,6 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { *perm.index_b = F::from_usize(ZERO_VEC_PTR); *perm.index_res = F::from_usize(null_hash_ptr); *perm.half_output = F::ZERO; - *perm.zero_right_suffix = F::ZERO; // Non-committed column for padding rows row[num_cols_poseidon_16()] = F::from_usize(crate::POSEIDON_PRECOMPILE_DATA); @@ -67,11 +66,6 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseidon1Cols16<&mut F>) { let inputs: [F; WIDTH] = std::array::from_fn(|i| *perm.inputs[i]); let mut state = inputs; - // Zero last 4 right inputs when zero_right_suffix=1 (works for both scalar and SIMD packed) - let one_minus_zrs = F::ONE - *perm.zero_right_suffix; - for j in 0..HALF_DIGEST_LEN { - state[DIGEST_LEN + HALF_DIGEST_LEN + j] *= one_minus_zrs; - } // No initial linear layer for Poseidon1 (unlike Poseidon2) From 0677d7e59fc1fef7421e8b302ac6ebf4e5d7bcc3 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 16:05:58 +0200 Subject: [PATCH 11/66] harcoded left input, first 4 FE, of poseidon, for tweaks --- crates/lean_compiler/snark_lib.py | 13 +++ crates/lean_compiler/src/a_simplify_lang.rs | 42 +++++-- .../lean_compiler/src/instruction_encoder.rs | 16 ++- .../src/parser/parsers/function.rs | 11 +- crates/lean_prover/src/test_zkvm.rs | 41 +++++++ crates/lean_prover/src/trace_gen.rs | 2 +- crates/lean_vm/src/isa/instruction.rs | 41 +++++-- crates/lean_vm/src/tables/poseidon_16/mod.rs | 105 ++++++++++++++---- .../src/tables/poseidon_16/trace_gen.rs | 8 +- src/prove_poseidons.rs | 12 +- 10 files changed, 238 insertions(+), 53 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 63c9e53a8..7dec590d3 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -86,6 +86,19 @@ def poseidon16_compress_half(left, right, output): _ = left, right, output +def poseidon16_compress_hardcoded_left_4(left, right, output, offset): + """Poseidon16 compression where the first 4 FE of the left input are read from + memory[offset..offset+4] instead of memory[left..left+4]. The last 4 FE of the + left input still come from memory[left+4..left+8]. `offset` must be a compile-time + constant expression.""" + _ = left, right, output, offset + + +def poseidon16_compress_half_hardcoded_left_4(left, right, output, offset): + """Composition of `poseidon16_compress_half` and `poseidon16_compress_hardcoded_left_4`.""" + _ = left, right, output, offset + + def add_be(a, b, result, length=None): _ = a, b, result, length diff --git a/crates/lean_compiler/src/a_simplify_lang.rs b/crates/lean_compiler/src/a_simplify_lang.rs index a45b3db5c..e4c914ab9 100644 --- a/crates/lean_compiler/src/a_simplify_lang.rs +++ b/crates/lean_compiler/src/a_simplify_lang.rs @@ -5,8 +5,9 @@ use crate::{ }; use backend::PrimeCharacteristicRing; use lean_vm::{ - Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_NAME, PrecompileArgs, - PrecompileCompTimeArgs, SourceLocation, Table, TableT, + Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, + POSEIDON16_HALF_NAME, POSEIDON16_HARDCODED_LEFT_4_NAME, PrecompileArgs, PrecompileCompTimeArgs, SourceLocation, + Table, TableT, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -2151,17 +2152,30 @@ fn simplify_lines( continue; } - // Special handling for poseidon16 precompile - if function_name == Table::poseidon16().name() || function_name == POSEIDON16_HALF_NAME { - let half_output = function_name == POSEIDON16_HALF_NAME; + // Special handling for poseidon16 precompile (4 variants) + if function_name == Table::poseidon16().name() + || function_name == POSEIDON16_HALF_NAME + || function_name == POSEIDON16_HARDCODED_LEFT_4_NAME + || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME + { + let half_output = function_name == POSEIDON16_HALF_NAME + || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME; + let is_hardcoded = function_name == POSEIDON16_HARDCODED_LEFT_4_NAME + || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME; if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" )); } - if args.len() != 3 { + let expected_args = if is_hardcoded { 4 } else { 3 }; + if args.len() != expected_args { + let signature = if is_hardcoded { + "(ptr_a, ptr_b, ptr_res, offset)" + } else { + "(ptr_a, ptr_b, ptr_res)" + }; return Err(format!( - "Precompile {function_name} expects 3 arguments (ptr_a, ptr_b, ptr_res), got {}, at {location}", + "Precompile {function_name} expects {expected_args} arguments {signature}, got {}, at {location}", args.len() )); } @@ -2169,11 +2183,23 @@ fn simplify_lines( .iter() .map(|arg| simplify_expr(ctx, state, const_malloc, arg, &mut res)) .collect::, _>>()?; + let hardcoded_left_4 = if is_hardcoded { + Some(simplified_args[3].as_constant().ok_or_else(|| { + format!( + "{function_name}: offset argument must be a compile-time constant, at {location}" + ) + })?) + } else { + None + }; res.push(SimpleLine::Precompile(PrecompileArgs { arg_0: simplified_args[0].clone(), arg_1: simplified_args[1].clone(), res: simplified_args[2].clone(), - data: PrecompileCompTimeArgs::Poseidon16 { half_output }, + data: PrecompileCompTimeArgs::Poseidon16 { + half_output, + hardcoded_left_4, + }, })); continue; } diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index 28ba68088..7009a4193 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -48,12 +48,16 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { } Instruction::Precompile(precompile) => { let precompile_data = match &precompile.data { - PrecompileCompTimeArgs::Poseidon16 { half_output } => { - if *half_output { - POSEIDON_HALF_PRECOMPILE_DATA - } else { - POSEIDON_PRECOMPILE_DATA - } + PrecompileCompTimeArgs::Poseidon16 { + half_output, + hardcoded_left_4, + } => { + let flag = hardcoded_left_4.is_some() as usize; + let offset = hardcoded_left_4.unwrap_or(0); + POSEIDON_PRECOMPILE_DATA + + POSEIDON_HALF_OUTPUT_SHIFT * (*half_output as usize) + + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag + + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index 72b104706..9a746fa4b 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -9,7 +9,10 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_NAME, POSEIDON16_NAME}; +use lean_vm::{ + CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_NAME, + POSEIDON16_HARDCODED_LEFT_4_NAME, POSEIDON16_NAME, +}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -35,7 +38,11 @@ fn is_reserved_function_name(name: &str) -> bool { return true; } // Check precompile names (poseidon16, extension_op functions) - if name == POSEIDON16_NAME || name == POSEIDON16_HALF_NAME { + if name == POSEIDON16_NAME + || name == POSEIDON16_HALF_NAME + || name == POSEIDON16_HARDCODED_LEFT_4_NAME + || name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME + { return true; } if ExtensionOpMode::from_name(name).is_some() { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index 45a0bce2e..5b084c701 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -25,6 +25,27 @@ def main(): for i in unroll(0, HALF_DIGEST_LEN): assert full_out[i] == half_out[i] + # poseidon16_compress_hardcoded_left_4: first 4 FE of left input come from + # memory[pub_start + 1500 .. pub_start + 1504] instead of memory[left .. left+4]. + hardcoded_full_out = pub_start + 1504 + poseidon16_compress_hardcoded_left_4( + pub_start + 4 * DIGEST_LEN, + pub_start + 5 * DIGEST_LEN, + hardcoded_full_out, + pub_start + 1500 + ) + + # Same, but only first 4 FE of the output are constrained. + hardcoded_half_out = pub_start + 1512 + poseidon16_compress_half_hardcoded_left_4( + pub_start + 4 * DIGEST_LEN, + pub_start + 5 * DIGEST_LEN, + hardcoded_half_out, + pub_start + 1500 + ) + for i in unroll(0, HALF_DIGEST_LEN): + assert hardcoded_full_out[i] == hardcoded_half_out[i] + base_ptr = pub_start + 88 ext_a_ptr = pub_start + 88 + N ext_b_ptr = pub_start + 88 + N * (DIM + 1) @@ -79,6 +100,26 @@ def main(): F::from_usize(444), ]); + // Hardcoded-left-4 test data, placed at public_input[1500..1520]. + // The 4-element prefix lives at offset 1500. The hardcoded variant computes + // Poseidon(prefix || original_input[4..8], original_input[8..16]). + let hardcoded_prefix: [F; 4] = rng.random(); + public_input[1500..1504].copy_from_slice(&hardcoded_prefix); + let mut hardcoded_input = [F::ZERO; 16]; + hardcoded_input[..4].copy_from_slice(&hardcoded_prefix); + hardcoded_input[4..16].copy_from_slice(&poseidon_16_compress_input[4..16]); + let hardcoded_output = poseidon16_compress(hardcoded_input); + // Full output at 1504..1512 + public_input[1504..1512].copy_from_slice(&hardcoded_output); + // Half output at 1512..1520: first 4 = hash, last 4 = arbitrary pre-existing data + public_input[1512..1516].copy_from_slice(&hardcoded_output[..4]); + public_input[1516..1520].copy_from_slice(&[ + F::from_usize(555), + F::from_usize(666), + F::from_usize(777), + F::from_usize(888), + ]); + // Extension op operands: base[N], ext_a[N], ext_b[N] let base_slice: [F; N] = rng.random(); let ext_a_slice: [EF; N] = rng.random(); diff --git a/crates/lean_prover/src/trace_gen.rs b/crates/lean_prover/src/trace_gen.rs index bfb1a1b4c..4b71dad3b 100644 --- a/crates/lean_prover/src/trace_gen.rs +++ b/crates/lean_prover/src/trace_gen.rs @@ -109,7 +109,7 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul // For half_output rows, override last 4 output columns with actual memory values // (the AIR doesn't constrain them, but the lookup checks against memory) { - let half_output_col = poseidon_trace.columns[POSEIDON_16_COL_HALF_OUTPUT].clone(); + let half_output_col = poseidon_trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].clone(); let res_col = poseidon_trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].clone(); for j in HALF_DIGEST_LEN..DIGEST_LEN { poseidon_trace.columns[POSEIDON_16_COL_OUTPUT_START + j] diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index e703dcc33..7e4b0e586 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -63,8 +63,16 @@ pub struct PrecompileArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PrecompileCompTimeArgs { - Poseidon16 { half_output: bool }, - ExtensionOp { size: S, mode: ExtensionOpMode }, + Poseidon16 { + half_output: bool, + /// `None` = standard variant. `Some(offset)` = the first 4 elements of the left input + /// are read from `memory[offset..offset+4]` instead of `memory[index_left..index_left+4]`. + hardcoded_left_4: Option, + }, + ExtensionOp { + size: S, + mode: ExtensionOpMode, + }, } impl PrecompileCompTimeArgs { @@ -75,9 +83,15 @@ impl PrecompileCompTimeArgs { } } - pub fn map_size(self, f: impl FnOnce(S) -> T) -> PrecompileCompTimeArgs { + pub fn map_size(self, mut f: impl FnMut(S) -> T) -> PrecompileCompTimeArgs { match self { - Self::Poseidon16 { half_output } => PrecompileCompTimeArgs::Poseidon16 { half_output }, + Self::Poseidon16 { + half_output, + hardcoded_left_4, + } => PrecompileCompTimeArgs::Poseidon16 { + half_output, + hardcoded_left_4: hardcoded_left_4.map(&mut f), + }, Self::ExtensionOp { size, mode } => PrecompileCompTimeArgs::ExtensionOp { size: f(size), mode }, } } @@ -233,13 +247,18 @@ impl Display for PrecompileArgs { data, } = self; match data { - PrecompileCompTimeArgs::Poseidon16 { half_output } => { - if *half_output { - write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)") - } else { - write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})") - } - } + PrecompileCompTimeArgs::Poseidon16 { + half_output, + hardcoded_left_4, + } => match (*half_output, hardcoded_left_4) { + (false, None) => write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})"), + (true, None) => write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)"), + (false, Some(off)) => write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, hardcoded_left_4={off})"), + (true, Some(off)) => write!( + f, + "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half, hardcoded_left_4={off})" + ), + }, PrecompileCompTimeArgs::ExtensionOp { size, mode } => { write!(f, "{}({arg_0}, {arg_1}, {res}, {size})", mode.name()) } diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 900946e0b..6930e940a 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -89,21 +89,42 @@ const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; +/// Encoding of `PRECOMPILE_DATA` for the Poseidon16 precompile: +/// +/// ```text +/// precompile_data = +/// POSEIDON_PRECOMPILE_DATA +/// + POSEIDON_HALF_OUTPUT_SHIFT * flag_half_output +/// + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag_hardcoded_left_4 +/// + flag_hardcoded_left_4 * POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_hardcoded +/// ``` +/// +/// the last multiplication by flag_hardcoded_left_4 is crucial for soundness. +/// - when flag_hardcoded_left_4 = 1, offset_hardcoded is constrained to be < 2^MAX_LOG_MEMORY_SIZE -> no overflow modulo p +/// - but when flag_hardcoded_left_4 = 0, there isnt this constraint, so we need to need to prevent an attacker to use an overflow to "spoof" the precompile_data encoding pub const POSEIDON_PRECOMPILE_DATA: usize = 1; -pub const POSEIDON_HALF_PRECOMPILE_DATA: usize = 2; +pub const POSEIDON_HALF_OUTPUT_SHIFT: usize = 1 << 1; +pub const POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT: usize = 1 << 2; +pub const POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT: usize = 1 << 3; pub const POSEIDON_16_COL_FLAG: ColIndex = 0; pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = 1; pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 2; pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; -pub const POSEIDON_16_COL_HALF_OUTPUT: ColIndex = 4; -pub const POSEIDON_16_COL_INPUT_START: ColIndex = 5; +pub const POSEIDON_16_COL_FLAG_HALF_OUTPUT: ColIndex = 4; +pub const POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4: ColIndex = 5; +pub const POSEIDON_16_COL_OFFSET_HARDCODED: ColIndex = 6; +pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST: ColIndex = 7; +pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND: ColIndex = 8; +pub const POSEIDON_16_COL_INPUT_START: ColIndex = 9; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; /// Non-committed columns: pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16(); pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; +pub const POSEIDON16_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_hardcoded_left_4"; +pub const POSEIDON16_HALF_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_half_hardcoded_left_4"; pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -121,8 +142,13 @@ impl TableT for Poseidon16Precompile { fn lookups(&self) -> Vec { vec![ LookupIntoMemory { - index: POSEIDON_16_COL_INDEX_INPUT_LEFT, - values: (POSEIDON_16_COL_INPUT_START..POSEIDON_16_COL_INPUT_START + DIGEST_LEN).collect(), + index: POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST, + values: (POSEIDON_16_COL_INPUT_START..POSEIDON_16_COL_INPUT_START + HALF_DIGEST_LEN).collect(), + }, + LookupIntoMemory { + index: POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND, + values: (POSEIDON_16_COL_INPUT_START + HALF_DIGEST_LEN..POSEIDON_16_COL_INPUT_START + DIGEST_LEN) + .collect(), }, LookupIntoMemory { index: POSEIDON_16_COL_INDEX_INPUT_RIGHT, @@ -167,14 +193,24 @@ impl TableT for Poseidon16Precompile { args: PrecompileCompTimeArgs, ctx: &mut InstructionContext<'_, M>, ) -> Result<(), RunnerError> { - let half_output = matches!(args, PrecompileCompTimeArgs::Poseidon16 { half_output: true }); + let PrecompileCompTimeArgs::Poseidon16 { + half_output, + hardcoded_left_4, + } = args + else { + unreachable!("Poseidon16 table called with non-Poseidon16 args"); + }; let trace = ctx.traces.get_mut(&self.table()).unwrap(); - let arg0 = ctx.memory.get_slice(arg_a.to_usize(), DIGEST_LEN)?; + let arg_a_usize = arg_a.to_usize(); + let left_first_addr = hardcoded_left_4.unwrap_or(arg_a_usize); + let arg0_first = ctx.memory.get_slice(left_first_addr, HALF_DIGEST_LEN)?; + let arg0_second = ctx.memory.get_slice(arg_a_usize + HALF_DIGEST_LEN, HALF_DIGEST_LEN)?; let arg1 = ctx.memory.get_slice(arg_b.to_usize(), DIGEST_LEN)?; let mut input = [F::ZERO; DIGEST_LEN * 2]; - input[..DIGEST_LEN].copy_from_slice(&arg0); + input[..HALF_DIGEST_LEN].copy_from_slice(&arg0_first); + input[HALF_DIGEST_LEN..DIGEST_LEN].copy_from_slice(&arg0_second); input[DIGEST_LEN..].copy_from_slice(&arg1); let output = poseidon16_compress(input); @@ -186,20 +222,27 @@ impl TableT for Poseidon16Precompile { ctx.memory.set_slice(index_res_a.to_usize(), &output)?; } + let flag_hardcoded = hardcoded_left_4.is_some(); + let offset_hardcoded = hardcoded_left_4.unwrap_or(0); + trace.columns[POSEIDON_16_COL_FLAG].push(F::ONE); trace.columns[POSEIDON_16_COL_INDEX_INPUT_LEFT].push(arg_a); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); - trace.columns[POSEIDON_16_COL_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); + trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); + trace.columns[POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4].push(if flag_hardcoded { F::ONE } else { F::ZERO }); + trace.columns[POSEIDON_16_COL_OFFSET_HARDCODED].push(F::from_usize(offset_hardcoded)); + trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST].push(F::from_usize(left_first_addr)); + trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND].push(arg_a + F::from_usize(HALF_DIGEST_LEN)); for (i, value) in input.iter().enumerate() { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } // Non-committed columns - trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(if half_output { - POSEIDON_HALF_PRECOMPILE_DATA - } else { - POSEIDON_PRECOMPILE_DATA - })); + let precompile_data = POSEIDON_PRECOMPILE_DATA + + POSEIDON_HALF_OUTPUT_SHIFT * (half_output as usize) + + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * (flag_hardcoded as usize) + + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_hardcoded; + trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(precompile_data)); // the rest of the trace is filled at the end of the execution (to get parallelism + SIMD) @@ -219,7 +262,7 @@ impl Air for Poseidon16Precompile { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 77 + BUS as usize + 80 } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -231,8 +274,12 @@ impl Air for Poseidon16Precompile { unsafe { std::ptr::read(&shorts[0]) } }; - // Reconstruct precompile_data from half_output: precompile_data = 1 + 4 * half_output - let precompile_data_reconstructed = AB::IF::ONE + cols.half_output; + let precompile_data_reconstructed = AB::IF::ONE + + cols.flag_half_output * AB::F::from_usize(POSEIDON_HALF_OUTPUT_SHIFT) + + cols.flag_hardcoded_left_4 * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT) + + cols.flag_hardcoded_left_4 + * cols.offset_hardcoded + * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT); // Bus data: [precompile_data, a, b, res] if BUS { @@ -257,7 +304,21 @@ impl Air for Poseidon16Precompile { } builder.assert_bool(cols.flag); - builder.assert_bool(cols.half_output); + builder.assert_bool(cols.flag_half_output); + builder.assert_bool(cols.flag_hardcoded_left_4); + + // effective_index_left_first = index_a * (1 - flag) + offset * flag + builder.assert_eq( + cols.effective_index_left_first, + cols.index_a * (AB::IF::ONE - cols.flag_hardcoded_left_4) + + cols.offset_hardcoded * cols.flag_hardcoded_left_4, + ); + + // effective_index_left_second = index_a + 4 + builder.assert_eq( + cols.effective_index_left_second, + cols.index_a + AB::F::from_usize(HALF_DIGEST_LEN), + ); eval_poseidon1_16(builder, &cols) } @@ -270,7 +331,11 @@ pub(super) struct Poseidon1Cols16 { pub index_a: T, pub index_b: T, pub index_res: T, - pub half_output: T, + pub flag_half_output: T, + pub flag_hardcoded_left_4: T, + pub offset_hardcoded: T, + pub effective_index_left_first: T, + pub effective_index_left_second: T, pub inputs: [T; WIDTH], pub beginning_full_rounds: [[T; WIDTH]; HALF_INITIAL_FULL_ROUNDS], @@ -334,7 +399,7 @@ fn eval_poseidon1_16(builder: &mut AB, local: &Poseidon1Cols16 Vec { *perm.index_a = F::from_usize(ZERO_VEC_PTR); *perm.index_b = F::from_usize(ZERO_VEC_PTR); *perm.index_res = F::from_usize(null_hash_ptr); - *perm.half_output = F::ZERO; + *perm.flag_half_output = F::ZERO; + *perm.flag_hardcoded_left_4 = F::ZERO; + *perm.offset_hardcoded = F::ZERO; + *perm.effective_index_left_first = F::from_usize(ZERO_VEC_PTR); + *perm.effective_index_left_second = F::from_usize(ZERO_VEC_PTR + HALF_DIGEST_LEN); // Non-committed column for padding rows row[num_cols_poseidon_16()] = F::from_usize(crate::POSEIDON_PRECOMPILE_DATA); diff --git a/src/prove_poseidons.rs b/src/prove_poseidons.rs index ee3a88998..5369b9bff 100644 --- a/src/prove_poseidons.rs +++ b/src/prove_poseidons.rs @@ -1,9 +1,10 @@ use air::{check_air_validity, prove_air, verify_air}; use backend::*; use lean_vm::{ - EF, ExtraDataForBuses, F, POSEIDON_16_COL_FLAG, POSEIDON_16_COL_INDEX_INPUT_LEFT, POSEIDON_16_COL_INDEX_INPUT_RES, - POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, Poseidon16Precompile, ZERO_VEC_PTR, - fill_trace_poseidon_16, num_cols_poseidon_16, + EF, ExtraDataForBuses, F, HALF_DIGEST_LEN, POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST, + POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND, POSEIDON_16_COL_FLAG, POSEIDON_16_COL_INDEX_INPUT_LEFT, + POSEIDON_16_COL_INDEX_INPUT_RES, POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, + Poseidon16Precompile, ZERO_VEC_PTR, fill_trace_poseidon_16, num_cols_poseidon_16, }; use rand::{RngExt, SeedableRng, rngs::StdRng}; use utils::{ @@ -32,6 +33,11 @@ pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { trace[POSEIDON_16_COL_INDEX_INPUT_RES] = (0..n_rows).map(|_| F::ZERO).collect(); // useless trace[POSEIDON_16_COL_INDEX_INPUT_LEFT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); trace[POSEIDON_16_COL_INDEX_INPUT_RIGHT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); + // Hardcoded-left-4 feature disabled: effective_index_first = index_left, second = index_left + 4 + trace[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); + trace[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND] = (0..n_rows) + .map(|_| F::from_usize(ZERO_VEC_PTR + HALF_DIGEST_LEN)) + .collect(); fill_trace_poseidon_16(&mut trace); let whir_config = WhirConfigBuilder { From 16c79a348eff4c5495f928b7d4a9cff51f6e19f9 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 16:27:59 +0200 Subject: [PATCH 12/66] tweak always at left --- crates/rec_aggregation/xmss_aggregate.py | 77 ++++++++++++------------ crates/xmss/src/lib.rs | 27 +++++---- crates/xmss/src/xmss.rs | 12 ++-- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index e2f72da48..35bcc5fb9 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -40,17 +40,7 @@ @inline -def build_left_fn(pp, data, out): - # out must be Array(DIGEST_LEN + 1) or more - # data must have at least 5 readable elements in memory. - for k in unroll(0, PP_IN_LEFT): - out[k] = pp[k] - copy_5(data, out + PP_IN_LEFT) - return - - -@inline -def build_right_fn(tweak, data, out): +def build_left_fn(tweak, data, out): # [tweak(2) | zeros(2) | data(XMSS_DIGEST)] # `tweak` is a pointer to the tweak VALUE (slot_start + 1). copy_5(tweak - 1, out - 1) # writes the padded slot [0, tw(2), 0, 0] to out[-1..4]; the leading 0 lands at the @@ -62,6 +52,17 @@ def build_right_fn(tweak, data, out): return +@inline +def build_right_fn(pp, data, out): + # [public_param(4) | data(XMSS_DIGEST)] + # out must be Array(DIGEST_LEN + 1) or more. + # data must have at least 5 readable elements in memory. + for k in unroll(0, PP_IN_LEFT): + out[k] = pp[k] + copy_5(data, out + PP_IN_LEFT) + return + + @inline def build_chain_right(public_param, out): # Shared chain-hash right input: [public_param(4) | zeros(4)] @@ -270,14 +271,8 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): @inline -def set_buf_prefix_left(buf, public_param): - for k in unroll(0, PP_IN_LEFT): - buf[k] = public_param[k] - return - - -@inline -def set_buf_prefix_right(buf, tweak): +def set_buf_prefix_left(buf, tweak): + # Writes [tweak(2) | zeros(2)] to buf[0..4] — the LEFT-input prefix. # `tweak` points to the tweak VALUE (slot_start + 1). copy_5 reads the padded slot # [0, tw[0], tw[1], 0, 0] and writes buf[-1..4]; the leading 0 lands at the "extra" # slot before buf, and buf[0..4] = [tw[0], tw[1], 0, 0] — the desired prefix. @@ -286,6 +281,14 @@ def set_buf_prefix_right(buf, tweak): return +@inline +def set_buf_prefix_right(buf, public_param): + # Writes [pp(4)] to buf[0..4] — the RIGHT-input prefix. + for k in unroll(0, PP_IN_LEFT): + buf[k] = public_param[k] + return + + @inline def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_tweaks_chunk): # b encodes 4 is_left bits; path elements at XMSS_DIGEST stride @@ -299,20 +302,20 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ # Level 0: build from external state_in and path_chunk (no prior hash to reuse) # All `Array(DIGEST_LEN + 2)` allocations reserve 1 element at offset 0 as the "extra" - # landing spot for build_right_fn's copy_5 prefix trick; the effective pointer is alloc+1. + # landing spot for build_left_fn's copy_5 prefix trick; the effective pointer is alloc+1. left0_alloc = Array(DIGEST_LEN + 2) left0 = left0_alloc + 1 right0_alloc = Array(DIGEST_LEN + 2) right0 = right0_alloc + 1 if b0 == 1: - build_left_fn(public_param, state_in, left0) - build_right_fn(merkle_tweaks_chunk, path_chunk, right0) + build_left_fn(merkle_tweaks_chunk, state_in, left0) + build_right_fn(public_param, path_chunk, right0) else: - build_left_fn(public_param, path_chunk, left0) - build_right_fn(merkle_tweaks_chunk, state_in, right0) + build_left_fn(merkle_tweaks_chunk, path_chunk, left0) + build_right_fn(public_param, state_in, right0) # Buffer trick: hash output to buf + PP_IN_LEFT, then prepend prefix. - # buf*_alloc has a leading "extra" slot so set_buf_prefix_right can use copy_5. + # buf*_alloc has a leading "extra" slot so set_buf_prefix_left can use copy_5. buf0_alloc = Array(BUF_SIZE + 1) buf0 = buf0_alloc + 1 poseidon16_compress(left0, right0, buf0 + PP_IN_LEFT) @@ -323,12 +326,12 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ buf1_alloc = Array(BUF_SIZE + 1) buf1 = buf1_alloc + 1 if b1 == 1: - set_buf_prefix_left(buf0, public_param) - build_right_fn(merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * XMSS_DIGEST, other1) + set_buf_prefix_left(buf0, merkle_tweaks_chunk + 1 * TWEAK_LEN) + build_right_fn(public_param, path_chunk + 1 * XMSS_DIGEST, other1) poseidon16_compress(buf0, other1, buf1 + PP_IN_LEFT) else: - set_buf_prefix_right(buf0, merkle_tweaks_chunk + 1 * TWEAK_LEN) - build_left_fn(public_param, path_chunk + 1 * XMSS_DIGEST, other1) + set_buf_prefix_right(buf0, public_param) + build_left_fn(merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * XMSS_DIGEST, other1) poseidon16_compress(other1, buf0, buf1 + PP_IN_LEFT) # Level 2 @@ -337,24 +340,24 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ buf2_alloc = Array(BUF_SIZE + 1) buf2 = buf2_alloc + 1 if b2 == 1: - set_buf_prefix_left(buf1, public_param) - build_right_fn(merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * XMSS_DIGEST, other2) + set_buf_prefix_left(buf1, merkle_tweaks_chunk + 2 * TWEAK_LEN) + build_right_fn(public_param, path_chunk + 2 * XMSS_DIGEST, other2) poseidon16_compress(buf1, other2, buf2 + PP_IN_LEFT) else: - set_buf_prefix_right(buf1, merkle_tweaks_chunk + 2 * TWEAK_LEN) - build_left_fn(public_param, path_chunk + 2 * XMSS_DIGEST, other2) + set_buf_prefix_right(buf1, public_param) + build_left_fn(merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * XMSS_DIGEST, other2) poseidon16_compress(other2, buf1, buf2 + PP_IN_LEFT) # Level 3 -> state_out other3_alloc = Array(DIGEST_LEN + 2) other3 = other3_alloc + 1 if b3 == 1: - set_buf_prefix_left(buf2, public_param) - build_right_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) + set_buf_prefix_left(buf2, merkle_tweaks_chunk + 3 * TWEAK_LEN) + build_right_fn(public_param, path_chunk + 3 * XMSS_DIGEST, other3) poseidon16_compress(buf2, other3, state_out) else: - set_buf_prefix_right(buf2, merkle_tweaks_chunk + 3 * TWEAK_LEN) - build_left_fn(public_param, path_chunk + 3 * XMSS_DIGEST, other3) + set_buf_prefix_right(buf2, public_param) + build_left_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) poseidon16_compress(other3, buf2, state_out) return diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index b7afcdf13..31b3dfc00 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -25,11 +25,14 @@ pub const LOG_LIFETIME: usize = 32; pub const RANDOMNESS_LEN_FE: usize = 5; pub const MESSAGE_LEN_FE: usize = 9; pub const PUBLIC_PARAM_LEN_FE: usize = 4; -pub const PP_IN_LEFT: usize = 8 - DIGEST_SIZE; // = 3 +/// Length of the non-data prefix in either Poseidon input. Both inputs reserve the +/// first `INPUT_PREFIX_LEN` field elements for metadata (tweak/zeros on the left, +/// public_param on the right) and place the 4-element data digest right after it. +pub const INPUT_PREFIX_LEN: usize = 8 - DIGEST_SIZE; // = 4 pub const PUB_KEY_FLAT_SIZE: usize = DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 -const _: () = assert!(PP_IN_LEFT + DIGEST_SIZE == 8); -// Right layout: [tweak(2) | zeros(2) | data(DIGEST_SIZE)] +const _: () = assert!(INPUT_PREFIX_LEN + DIGEST_SIZE == 8); +// Left layout (with tweak): [tweak(2) | zeros(2) | data(DIGEST_SIZE)] const _: () = assert!(TWEAK_LEN + 2 + DIGEST_SIZE == 8); pub const SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + (V + LOG_LIFETIME) * DIGEST_SIZE; @@ -55,20 +58,20 @@ pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> ] } -/// [public_param(4) | data(4)] -pub(crate) fn build_left(public_param: &PublicParam, data: &Digest) -> [F; 8] { +/// [tweak(2) | zeros(2) | data(4)] +pub(crate) fn build_left(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { let mut left = [F::default(); 8]; - left[..PP_IN_LEFT].copy_from_slice(public_param); - left[PP_IN_LEFT..].copy_from_slice(data); + left[..TWEAK_LEN].copy_from_slice(&tweak); + // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) + left[8 - DIGEST_SIZE..].copy_from_slice(data); left } -/// [tweak(2) | zeros(2) | data(4)] -pub(crate) fn build_right(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { +/// [public_param(4) | data(4)] +pub(crate) fn build_right(public_param: &PublicParam, data: &Digest) -> [F; 8] { let mut right = [F::default(); 8]; - right[..TWEAK_LEN].copy_from_slice(&tweak); - // right[TWEAK_LEN..TWEAK_LEN+2] = zeros (default) - right[8 - DIGEST_SIZE..].copy_from_slice(data); + right[..INPUT_PREFIX_LEN].copy_from_slice(public_param); + right[INPUT_PREFIX_LEN..].copy_from_slice(data); right } diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index 136068dbb..395e1f8a0 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -113,8 +113,8 @@ pub fn xmss_key_gen( } else { gen_random_node(&seed, level - 1, right_idx) }; - let poseidon_left = build_left(&public_param, &left); - let poseidon_right = build_right(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &right); + let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &left); + let poseidon_right = build_right(&public_param, &right); poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..DIGEST_SIZE] .try_into() .unwrap() @@ -215,14 +215,14 @@ pub fn xmss_verify( let parent_index = ((slot as u64) >> (level + 1)) as u32; let tweak = make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index); if is_left { - let left = build_left(&pub_key.public_param, ¤t_hash); - let right = build_right(tweak, neighbour); + let left = build_left(tweak, ¤t_hash); + let right = build_right(&pub_key.public_param, neighbour); current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); } else { - let left = build_left(&pub_key.public_param, neighbour); - let right = build_right(tweak, ¤t_hash); + let left = build_left(tweak, neighbour); + let right = build_right(&pub_key.public_param, ¤t_hash); current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] .try_into() .unwrap(); From 53407a5fde253a7461d914d33421d6df7e7efa81 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 16:43:28 +0200 Subject: [PATCH 13/66] new convention index left --- crates/lean_prover/src/test_zkvm.rs | 27 ++++++++++++++------ crates/lean_vm/src/tables/poseidon_16/mod.rs | 26 +++++++++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index 5b084c701..0c78cd58a 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -25,11 +25,14 @@ def main(): for i in unroll(0, HALF_DIGEST_LEN): assert full_out[i] == half_out[i] - # poseidon16_compress_hardcoded_left_4: first 4 FE of left input come from - # memory[pub_start + 1500 .. pub_start + 1504] instead of memory[left .. left+4]. + # poseidon16_compress_hardcoded_left_4: with the new convention, only 4 FE are read + # at the left pointer (the 4-element data digest at pub_start + 1496) and the first + # 4 FE of the left input come from memory[pub_start + 1500 .. pub_start + 1504] + # (the hardcoded prefix). + hardcoded_left = pub_start + 1496 hardcoded_full_out = pub_start + 1504 poseidon16_compress_hardcoded_left_4( - pub_start + 4 * DIGEST_LEN, + hardcoded_left, pub_start + 5 * DIGEST_LEN, hardcoded_full_out, pub_start + 1500 @@ -38,7 +41,7 @@ def main(): # Same, but only first 4 FE of the output are constrained. hardcoded_half_out = pub_start + 1512 poseidon16_compress_half_hardcoded_left_4( - pub_start + 4 * DIGEST_LEN, + hardcoded_left, pub_start + 5 * DIGEST_LEN, hardcoded_half_out, pub_start + 1500 @@ -100,14 +103,22 @@ def main(): F::from_usize(444), ]); - // Hardcoded-left-4 test data, placed at public_input[1500..1520]. - // The 4-element prefix lives at offset 1500. The hardcoded variant computes - // Poseidon(prefix || original_input[4..8], original_input[8..16]). + // Hardcoded-left-4 test data, placed at public_input[1496..1520]. + // - The 4-element data digest lives at offset 1496..1500 (the "left" pointer). + // - The 4-element hardcoded prefix lives at offset 1500..1504. + // The hardcoded variant computes + // Poseidon(prefix(4) || data(4), original_input[8..16]) + // i.e. only 4 elements are read at the left pointer (matching the new convention + // where flag_hardcoded_left_4 = 1 reads m[index_a..index_a+4] for the second half + // of the left input and m[offset..offset+4] for the first half). + let hardcoded_data: [F; 4] = rng.random(); let hardcoded_prefix: [F; 4] = rng.random(); + public_input[1496..1500].copy_from_slice(&hardcoded_data); public_input[1500..1504].copy_from_slice(&hardcoded_prefix); let mut hardcoded_input = [F::ZERO; 16]; hardcoded_input[..4].copy_from_slice(&hardcoded_prefix); - hardcoded_input[4..16].copy_from_slice(&poseidon_16_compress_input[4..16]); + hardcoded_input[4..8].copy_from_slice(&hardcoded_data); + hardcoded_input[8..16].copy_from_slice(&poseidon_16_compress_input[8..16]); let hardcoded_output = poseidon16_compress(hardcoded_input); // Full output at 1504..1512 public_input[1504..1512].copy_from_slice(&hardcoded_output); diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 6930e940a..3443ae3f3 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -203,9 +203,20 @@ impl TableT for Poseidon16Precompile { let trace = ctx.traces.get_mut(&self.table()).unwrap(); let arg_a_usize = arg_a.to_usize(); + let flag_hardcoded = hardcoded_left_4.is_some(); + // Convention: + // flag = 0: left input = m[arg_a..arg_a+8] (split as [arg_a..+4], [arg_a+4..+8]) + // flag = 1: left input = m[offset..offset+4] | m[arg_a..arg_a+4] + // (i.e. arg_a now points to a 4-element data digest, and the first 4 + // elements come from the hardcoded prefix at `offset`) let left_first_addr = hardcoded_left_4.unwrap_or(arg_a_usize); + let left_second_addr = if flag_hardcoded { + arg_a_usize + } else { + arg_a_usize + HALF_DIGEST_LEN + }; let arg0_first = ctx.memory.get_slice(left_first_addr, HALF_DIGEST_LEN)?; - let arg0_second = ctx.memory.get_slice(arg_a_usize + HALF_DIGEST_LEN, HALF_DIGEST_LEN)?; + let arg0_second = ctx.memory.get_slice(left_second_addr, HALF_DIGEST_LEN)?; let arg1 = ctx.memory.get_slice(arg_b.to_usize(), DIGEST_LEN)?; let mut input = [F::ZERO; DIGEST_LEN * 2]; @@ -222,7 +233,6 @@ impl TableT for Poseidon16Precompile { ctx.memory.set_slice(index_res_a.to_usize(), &output)?; } - let flag_hardcoded = hardcoded_left_4.is_some(); let offset_hardcoded = hardcoded_left_4.unwrap_or(0); trace.columns[POSEIDON_16_COL_FLAG].push(F::ONE); @@ -233,7 +243,7 @@ impl TableT for Poseidon16Precompile { trace.columns[POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4].push(if flag_hardcoded { F::ONE } else { F::ZERO }); trace.columns[POSEIDON_16_COL_OFFSET_HARDCODED].push(F::from_usize(offset_hardcoded)); trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST].push(F::from_usize(left_first_addr)); - trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND].push(arg_a + F::from_usize(HALF_DIGEST_LEN)); + trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND].push(F::from_usize(left_second_addr)); for (i, value) in input.iter().enumerate() { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } @@ -314,10 +324,16 @@ impl Air for Poseidon16Precompile { + cols.offset_hardcoded * cols.flag_hardcoded_left_4, ); - // effective_index_left_second = index_a + 4 + // effective_index_left_second = index_a + (1 - flag_hardcoded_left_4) * 4 + // - When the hardcoded flag is disabled (legacy path) the second half of the left + // input is read from m[index_a + 4 .. index_a + 8] (the upper half of an 8-element + // chunk pointed to by index_a). + // - When the hardcoded flag is set, only a 4-element data digest is read from + // m[index_a .. index_a + 4]; the first 4 inputs are supplied from the hardcoded + // m[offset .. offset + 4]. builder.assert_eq( cols.effective_index_left_second, - cols.index_a + AB::F::from_usize(HALF_DIGEST_LEN), + cols.index_a + (AB::IF::ONE - cols.flag_hardcoded_left_4) * AB::F::from_usize(HALF_DIGEST_LEN), ); eval_poseidon1_16(builder, &cols) From cf35002ca6bca82a4dec759fc2bf170ac2b4f306 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 17:11:49 +0200 Subject: [PATCH 14/66] wip --- crates/rec_aggregation/main.py | 21 ++- crates/rec_aggregation/src/compilation.rs | 10 ++ crates/rec_aggregation/src/lib.rs | 40 +++-- crates/rec_aggregation/xmss_aggregate.py | 194 +++++++++------------- 4 files changed, 126 insertions(+), 139 deletions(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 817144a68..c59274596 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -27,15 +27,22 @@ def main(): priv_start: Imu hint_private_input_start(priv_start) - - n_recursions = priv_start[0] + debug_assert(priv_start == TWEAK_TABLE_ADDR) + # Private input layout: [tweak_table (FIXED size, lives at the compile-time + # address TWEAK_TABLE_ADDR), header, pubkeys, source_blocks, ...]. + # Use the compile-time constant for the tweak table — this is what enables + # poseidon16_compress_hardcoded_left_4 in xmss_aggregate.py to embed every + # tweak's absolute address into the bytecode. + tweak_table = TWEAK_TABLE_ADDR + header = priv_start + TWEAK_TABLE_SIZE_FE_PADDED + + n_recursions = header[0] assert n_recursions <= MAX_RECURSIONS - n_dup = priv_start[1] + n_dup = header[1] assert n_dup < MAX_N_SIGS # TODO increase - all_pubkeys = priv_start[2] - tweak_table = priv_start[3] - sub_slice_starts = priv_start + 4 + all_pubkeys = header[2] + sub_slice_starts = header + 3 bytecode_sumcheck_proof = sub_slice_starts[n_recursions + 1] source_0 = sub_slice_starts[0] @@ -87,7 +94,7 @@ def main(): sig = Array(SIG_SIZE + 1) hint_xmss(sig) sig[SIG_SIZE] = 0 - xmss_verify(pk, message, sig, tweak_table, merkle_chunks_for_slot) + xmss_verify(pk, message, sig, merkle_chunks_for_slot) counter: Mut = n_raw_xmss diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 2682f4cd3..b658884ff 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -44,11 +44,16 @@ fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> + claim_data_size_padded + DIGEST_LEN; let inner_public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); + // The private input layout in lib.rs places the tweak table at the very start of + // private input. Private input starts at `public_memory_size = 1 << inner_public_memory_log_size`, + // so the tweak table sits at this exact compile-time address. + let tweak_table_address_in_memory = 1usize << inner_public_memory_log_size; let replacements = build_replacements( inner_program_log_size, inner_public_memory_log_size, bytecode_zero_eval, pub_input_size, + tweak_table_address_in_memory, ); let filepath = Path::new(env!("CARGO_MANIFEST_DIR")) @@ -84,6 +89,7 @@ fn build_replacements( inner_public_memory_log_size: usize, bytecode_zero_eval: F, pub_input_size: usize, + tweak_table_address_in_memory: usize, ) -> BTreeMap { let mut replacements = BTreeMap::new(); @@ -155,6 +161,10 @@ fn build_replacements( if too_much_grinding { tracing::info!("Warning: Too much grinding for WHIR folding"); // TODO } + replacements.insert( + "TWEAK_TABLE_ADDR_PLACEHOLDER".to_string(), + tweak_table_address_in_memory.to_string(), + ); replacements.insert( "WHIR_FIRST_RS_REDUCTION_FACTOR_PLACEHOLDER".to_string(), RS_DOMAIN_INITIAL_REDUCTION_FACTOR.to_string(), diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 57b5bac82..74f9badff 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -356,10 +356,17 @@ pub fn xmss_aggregate( let public_memory = build_public_memory(&non_reserved_public_input); // Build private input - // Layout: [n_recursions, n_dup, ptr_pubkeys, ptr_tweak_table, ptr_source_0..n_recursions, ptr_bytecode_sumcheck, - // global_pubkeys, dup_pubkeys, tweak_table, source_blocks..., bytecode_sumcheck_proof] - let header_size = n_recursions + 6; - let pubkeys_start = public_memory.len() + header_size; + // Layout: [tweak_table (FIXED size, sits at the FIXED address public_memory.len() so the + // .py code can address it via the TWEAK_TABLE_ADDR compile-time constant), + // n_recursions, n_dup, ptr_pubkeys, ptr_source_0..n_recursions, ptr_bytecode_sumcheck, + // global_pubkeys, dup_pubkeys, source_blocks..., bytecode_sumcheck_proof] + // + // We dropped `ptr_tweak_table` from the header because the address is now a + // compile-time constant; main.py reads tweak_table = TWEAK_TABLE_ADDR directly. + let tweak_table_ptr = public_memory.len(); + let header_size = n_recursions + 5; + let header_start = tweak_table_ptr + TWEAK_TABLE_SIZE_FE_PADDED; + let pubkeys_start = header_start + header_size; // Build source blocks (also discovers duplicate pub_keys) let mut claimed: HashSet = HashSet::new(); @@ -406,10 +413,9 @@ pub fn xmss_aggregate( let n_dup = dup_pub_keys.len(); let pubkeys_block_size = n_sigs * PUB_KEY_FLAT_SIZE + n_dup * PUB_KEY_FLAT_SIZE; - let tweak_table_ptr = pubkeys_start + pubkeys_block_size; - // Compute absolute memory addresses for each source block - let sources_start = tweak_table_ptr + TWEAK_TABLE_SIZE_FE_PADDED; + // Compute absolute memory addresses for each source block (placed after the pubkeys block). + let sources_start = pubkeys_start + pubkeys_block_size; let mut offset = sources_start; let mut source_ptrs: Vec = vec![]; for block in &source_blocks { @@ -418,25 +424,29 @@ pub fn xmss_aggregate( } let bytecode_sumcheck_proof_ptr = offset; - let mut private_input = vec![ - F::from_usize(n_recursions), - F::from_usize(n_dup), - F::from_usize(pubkeys_start), - F::from_usize(tweak_table_ptr), - ]; + // Tweak table sits at the very start of private input (FIXED address). + let mut private_input: Vec = Vec::with_capacity(TWEAK_TABLE_SIZE_FE_PADDED); + private_input.extend_from_slice(&tweak_table); + assert_eq!(private_input.len(), TWEAK_TABLE_SIZE_FE_PADDED); + + // Header (variable-length only via n_recursions; lives right after the tweak table). + private_input.push(F::from_usize(n_recursions)); + private_input.push(F::from_usize(n_dup)); + private_input.push(F::from_usize(pubkeys_start)); for &ptr in &source_ptrs { private_input.push(F::from_usize(ptr)); } private_input.push(F::from_usize(bytecode_sumcheck_proof_ptr)); - assert_eq!(private_input.len(), header_size); + assert_eq!(private_input.len(), TWEAK_TABLE_SIZE_FE_PADDED + header_size); + // Pubkeys block. for pk in &global_pub_keys { private_input.extend_from_slice(&pk.flaten()); } for pk in &dup_pub_keys { private_input.extend_from_slice(&pk.flaten()); } - private_input.extend_from_slice(&tweak_table); + // Source blocks (already addressed by source_ptrs above). for block in &source_blocks { private_input.extend_from_slice(block); } diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 35bcc5fb9..c2ce76b47 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -17,6 +17,7 @@ NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK +TWEAK_TABLE_ADDR = TWEAK_TABLE_ADDR_PLACEHOLDER # Tweak table layout: each tweak is stored as a 5-FE padded slot [0, tw[0], tw[1], 0, 0]. # Convention: tweak pointers always point to the tweak VALUE (offset +1 within the slot). @@ -39,19 +40,6 @@ BUF_SIZE = 1 + PP_IN_LEFT + DIGEST_LEN -@inline -def build_left_fn(tweak, data, out): - # [tweak(2) | zeros(2) | data(XMSS_DIGEST)] - # `tweak` is a pointer to the tweak VALUE (slot_start + 1). copy_5(tweak - 1, out - 1) - # writes the padded slot [0, tw(2), 0, 0] to out[-1..4]; the leading 0 lands at the - # "extra" slot before `out`. - # `out` must be allocated as `alloc + 1` where alloc is Array(DIGEST_LEN + 2) or more - # (so that out[-1] and out[0..9] are all valid writable memory). - copy_5(tweak - 1, out - 1) - copy_5(data, out + 4) - return - - @inline def build_right_fn(pp, data, out): # [public_param(4) | data(XMSS_DIGEST)] @@ -75,19 +63,15 @@ def build_chain_right(public_param, out): @inline -def set_chain_left_prefix(cur_buf, tweak): - # Writes [tweak(2) | zeros(2)] to cur_buf[0..4]. - cur_buf[0] = tweak[0] - cur_buf[1] = tweak[1] - cur_buf[2] = 0 - cur_buf[3] = 0 - return - - -@inline -def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): +def xmss_verify(pub_key, message, signature, merkle_chunks): # pub_key: PUB_KEY_SIZE FE = merkle_root(XMSS_DIGEST) | public_param(PUBLIC_PARAM_LEN_FE) # signature: randomness(RANDOMNESS_LEN) | chain_tips(V * XMSS_DIGEST) | merkle_path(LOG_LIFETIME * XMSS_DIGEST) + # + # The tweak table lives at the compile-time constant address TWEAK_TABLE_ADDR + # (asserted at the top of main.py), so every tweak slot has a compile-time absolute + # address. This lets us pass tweak offsets straight to + # poseidon16_compress_hardcoded_left_4 without ever copying tweak prefixes into + # per-hash buffers. public_param = pub_key + XMSS_DIGEST randomness = signature @@ -96,7 +80,7 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) - encoding_tweak = tweak_table + TWEAK_ENCODING_OFFSET + encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET # Allocate 11 elements so the 2nd copy_5 (which reads [tw(2), 0, 0, 0] from the padded # table and writes 5 elements at offset 6) can safely write positions 6..11. a_input_right = Array(1 + RANDOMNESS_LEN + TWEAK_LEN) @@ -154,15 +138,13 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): num_hashes = (CHAIN_LENGTH - 1) - encoding[i] chain_start = chain_starts + i * XMSS_DIGEST chain_end = wots_public_key + i * DIGEST_LEN - chain_i_tweaks = tweak_table + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN - - # Pre-allocate all buffers (constant allocation regardless of num_hashes). - # Each slot starts with an "extra" element at offset 0, used as the landing spot - # for copy_5's leading zero when writing the padded tweak prefix. - # We allocate 1 extra element BEFORE the first slot and expose pointers pointing - # at slot_start + 1 (the hash-input position). Thus `ptr - 1` is always valid. - ch_left_first_alloc = Array(BUF_SIZE) - ch_left_first = ch_left_first_alloc + 1 + chain_i_tweaks = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN + + # Pre-allocate intermediate-digest buffers (one BUF_SIZE-strided slot per + # chain hash; only the [4..8] sub-slot of each slot is actually used to hold + # the 4-element digest). We don't need a separate ch_left_first buffer anymore + # because the first hash reads its 4-element LEFT data straight from `input` + # via poseidon16_compress_hardcoded_left_4. ch_bufs_alloc = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) ch_bufs = ch_bufs_alloc + 1 ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) @@ -172,15 +154,17 @@ def xmss_verify(pub_key, message, signature, tweak_table, merkle_chunks): range(0, 1), lambda _: copy_5(chain_start, chain_end), range(1, CHAIN_LENGTH), - lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right, ch_left_first, ch_bufs, ch_buf_idx), + lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right, ch_bufs, ch_buf_idx), ) # 3) Hash WOTS public key - wots_pk_tweaks = tweak_table + TWEAK_WOTS_PK_OFFSET + wots_pk_tweaks = TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET expected_leaf = wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks) - # 4) Merkle verification - merkle_tweaks = tweak_table + TWEAK_MERKLE_OFFSET + # 4) Merkle verification — merkle_tweaks is now a compile-time constant absolute + # address, which lets do_4_merkle_levels feed tweak offsets straight into + # poseidon16_compress_hardcoded_left_4. + merkle_tweaks = TWEAK_TABLE_ADDR + TWEAK_MERKLE_OFFSET xmss_merkle_verify(expected_leaf, merkle_path, merkle_chunks, pub_key, public_param, merkle_tweaks) return @@ -194,48 +178,40 @@ def copy_xmss_digest(src, dst): @inline -def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_left_first, ch_bufs, ch_buf_idx): - # Uses pre-allocated buffers (zero internal allocation for parallel_range compatibility). +def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_bufs, ch_buf_idx): # Chain hash layout: left = [tweak(2) | zeros(2) | data(4)], right = chain_right (shared). + # The LEFT prefix [tweak(2) | 00] is read directly from the tweak table by + # poseidon16_compress_hardcoded_left_4 — no per-hash buffer copies needed. # - # Each slot is BUF_SIZE = 13: [extra(1) | prefix(4) | hash_output(8)]. - # The "hash-input" pointer for a slot is slot_start + 1 (skipping the extra). - # copy_5 writes to slot_start (5 elements, leading zero lands in extra slot). - # Poseidon writes its output at slot_start + 5 (= hash_input_ptr + 4), so the digest - # lands at hash_input_ptr[4..8]; together with the prefix at hash_input_ptr[0..4], - # the slot's hash_input_ptr[0..8] forms a valid left input for the NEXT hash. + # `chain_i_tweaks` is a compile-time constant address (TWEAK_TABLE_ADDR + chain_offset), + # so each `chain_i_tweaks + step * TWEAK_LEN` is also a compile-time constant; the + # hardcoded_left_4 precompile bakes the absolute tweak slot address into the bytecode. # - # ch_left_first, ch_bufs, ch_buf_idx all store HASH-INPUT pointers (= slot_start + 1). - # copy_5 destination = ptr - 1 (the slot start). + # Each ch_buf slot still spans BUF_SIZE elements (the legacy stride is preserved), + # but we only use the digest sub-slot at offset 4: bufs[j][4..8] holds the j-th hash + # output; the next hash uses `bufs[j] + 4` as its 4-element LEFT data. starting_step = CHAIN_LENGTH - 1 - n - # Build L_0 = [tweak_0, zeros, input] - # first_tweak is a pointer to the tweak VALUE (slot_start + 1); copy_5 reads from - # first_tweak - 1 (the slot_start including its leading zero). - first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - copy_5(first_tweak - 1, ch_left_first - 1) # writes ch_left_first[-1..4] - copy_5(input, ch_left_first + 4) # writes ch_left_first[4..9] = input(4) + extra - if n == 1: - poseidon16_compress(ch_left_first, chain_right, output) + # Single hash: input → output, with tweak_{starting_step} as the LEFT prefix. + first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN + poseidon16_compress_hardcoded_left_4(input, chain_right, output, first_tweak) else: - # Hash 0: L_0 → ch_bufs[0] + 4 (writes bufs[0][4..12], digest at bufs[0][4..8]) + # Hash 0: input → ch_bufs[0] + 4 ch_buf_idx[0] = ch_bufs - poseidon16_compress(ch_left_first, chain_right, ch_bufs + 4) - # Write L_1 prefix via copy_5 of padded tweak_1 to ch_bufs - 1 (the extra slot). - next_tweak = chain_i_tweaks + (starting_step + 1) * TWEAK_LEN - copy_5(next_tweak - 1, ch_bufs - 1) + first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN + poseidon16_compress_hardcoded_left_4(input, chain_right, ch_bufs + 4, first_tweak) - # Hashes 1..n-2: buf[j-1] → buf[j] + 4 + # Hashes 1..n-2: bufs[j-1] + 4 → bufs[j] + 4 for j in unroll(1, n - 1): ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE cur_buf = ch_buf_idx[j] - poseidon16_compress(ch_buf_idx[j - 1], chain_right, cur_buf + 4) - cur_tweak = chain_i_tweaks + (starting_step + j + 1) * TWEAK_LEN - copy_5(cur_tweak - 1, cur_buf - 1) + cur_tweak = chain_i_tweaks + (starting_step + j) * TWEAK_LEN + poseidon16_compress_hardcoded_left_4(ch_buf_idx[j - 1] + 4, chain_right, cur_buf + 4, cur_tweak) - # Final hash: buf[n-2] → output - poseidon16_compress(ch_buf_idx[n - 2], chain_right, output) + # Final hash: bufs[n-2] + 4 → output + last_tweak = chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN + poseidon16_compress_hardcoded_left_4(ch_buf_idx[n - 2] + 4, chain_right, output, last_tweak) return @@ -270,17 +246,6 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): return states + (N_CHUNKS - 1) * DIGEST_LEN -@inline -def set_buf_prefix_left(buf, tweak): - # Writes [tweak(2) | zeros(2)] to buf[0..4] — the LEFT-input prefix. - # `tweak` points to the tweak VALUE (slot_start + 1). copy_5 reads the padded slot - # [0, tw[0], tw[1], 0, 0] and writes buf[-1..4]; the leading 0 lands at the "extra" - # slot before buf, and buf[0..4] = [tw[0], tw[1], 0, 0] — the desired prefix. - # buf must be `alloc + 1` where alloc has at least BUF_SIZE + 1 elements. - copy_5(tweak - 1, buf - 1) - return - - @inline def set_buf_prefix_right(buf, public_param): # Writes [pp(4)] to buf[0..4] — the RIGHT-input prefix. @@ -291,7 +256,18 @@ def set_buf_prefix_right(buf, public_param): @inline def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_tweaks_chunk): - # b encodes 4 is_left bits; path elements at XMSS_DIGEST stride + # b encodes 4 is_left bits; path elements at XMSS_DIGEST stride. + # + # `merkle_tweaks_chunk` MUST be a compile-time constant absolute address into the + # tweak table — the per-level `+ k * TWEAK_LEN` offsets are baked into + # poseidon16_compress_hardcoded_left_4 so the LEFT input prefix + # `[tweak(2) | 00]` is read directly from the tweak table without ever + # being copied into a per-hash buffer. + # + # Each intermediate level still uses an 8-element `buf*` because when the buffer + # holds the RIGHT input we need [pp(4) | digest(4)] contiguous in memory. When the + # buffer holds the LEFT input we just pass `buf + PP_IN_LEFT` (the 4-element digest + # part) as the precompile's `index_a`. b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -300,65 +276,49 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ r3 = (r2 - b2) / 2 b3 = r3 % 2 - # Level 0: build from external state_in and path_chunk (no prior hash to reuse) - # All `Array(DIGEST_LEN + 2)` allocations reserve 1 element at offset 0 as the "extra" - # landing spot for build_left_fn's copy_5 prefix trick; the effective pointer is alloc+1. - left0_alloc = Array(DIGEST_LEN + 2) - left0 = left0_alloc + 1 - right0_alloc = Array(DIGEST_LEN + 2) - right0 = right0_alloc + 1 - if b0 == 1: - build_left_fn(merkle_tweaks_chunk, state_in, left0) - build_right_fn(public_param, path_chunk, right0) - else: - build_left_fn(merkle_tweaks_chunk, path_chunk, left0) - build_right_fn(public_param, state_in, right0) - - # Buffer trick: hash output to buf + PP_IN_LEFT, then prepend prefix. - # buf*_alloc has a leading "extra" slot so set_buf_prefix_left can use copy_5. + # Level 0 + right0_alloc = Array(DIGEST_LEN + 1) buf0_alloc = Array(BUF_SIZE + 1) buf0 = buf0_alloc + 1 - poseidon16_compress(left0, right0, buf0 + PP_IN_LEFT) + if b0 == 1: + build_right_fn(public_param, path_chunk, right0_alloc) + poseidon16_compress_hardcoded_left_4(state_in, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) + else: + build_right_fn(public_param, state_in, right0_alloc) + poseidon16_compress_hardcoded_left_4(path_chunk, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) # Level 1 - other1_alloc = Array(DIGEST_LEN + 2) - other1 = other1_alloc + 1 buf1_alloc = Array(BUF_SIZE + 1) buf1 = buf1_alloc + 1 if b1 == 1: - set_buf_prefix_left(buf0, merkle_tweaks_chunk + 1 * TWEAK_LEN) - build_right_fn(public_param, path_chunk + 1 * XMSS_DIGEST, other1) - poseidon16_compress(buf0, other1, buf1 + PP_IN_LEFT) + # buf0's digest is the LEFT data; build [pp | path[1]] as the RIGHT input. + right1_alloc = Array(DIGEST_LEN + 1) + build_right_fn(public_param, path_chunk + 1 * XMSS_DIGEST, right1_alloc) + poseidon16_compress_hardcoded_left_4(buf0 + PP_IN_LEFT, right1_alloc, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) else: + # path[1] is the LEFT data; reuse buf0 as the [pp | digest] RIGHT input. set_buf_prefix_right(buf0, public_param) - build_left_fn(merkle_tweaks_chunk + 1 * TWEAK_LEN, path_chunk + 1 * XMSS_DIGEST, other1) - poseidon16_compress(other1, buf0, buf1 + PP_IN_LEFT) + poseidon16_compress_hardcoded_left_4(path_chunk + 1 * XMSS_DIGEST, buf0, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) # Level 2 - other2_alloc = Array(DIGEST_LEN + 2) - other2 = other2_alloc + 1 buf2_alloc = Array(BUF_SIZE + 1) buf2 = buf2_alloc + 1 if b2 == 1: - set_buf_prefix_left(buf1, merkle_tweaks_chunk + 2 * TWEAK_LEN) - build_right_fn(public_param, path_chunk + 2 * XMSS_DIGEST, other2) - poseidon16_compress(buf1, other2, buf2 + PP_IN_LEFT) + right2_alloc = Array(DIGEST_LEN + 1) + build_right_fn(public_param, path_chunk + 2 * XMSS_DIGEST, right2_alloc) + poseidon16_compress_hardcoded_left_4(buf1 + PP_IN_LEFT, right2_alloc, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) else: set_buf_prefix_right(buf1, public_param) - build_left_fn(merkle_tweaks_chunk + 2 * TWEAK_LEN, path_chunk + 2 * XMSS_DIGEST, other2) - poseidon16_compress(other2, buf1, buf2 + PP_IN_LEFT) + poseidon16_compress_hardcoded_left_4(path_chunk + 2 * XMSS_DIGEST, buf1, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) # Level 3 -> state_out - other3_alloc = Array(DIGEST_LEN + 2) - other3 = other3_alloc + 1 if b3 == 1: - set_buf_prefix_left(buf2, merkle_tweaks_chunk + 3 * TWEAK_LEN) - build_right_fn(public_param, path_chunk + 3 * XMSS_DIGEST, other3) - poseidon16_compress(buf2, other3, state_out) + right3_alloc = Array(DIGEST_LEN + 1) + build_right_fn(public_param, path_chunk + 3 * XMSS_DIGEST, right3_alloc) + poseidon16_compress_hardcoded_left_4(buf2 + PP_IN_LEFT, right3_alloc, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) else: set_buf_prefix_right(buf2, public_param) - build_left_fn(merkle_tweaks_chunk + 3 * TWEAK_LEN, path_chunk + 3 * XMSS_DIGEST, other3) - poseidon16_compress(other3, buf2, state_out) + poseidon16_compress_hardcoded_left_4(path_chunk + 3 * XMSS_DIGEST, buf2, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return From f574825782412f08f414f3fbf37dbefbab67c60d Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 17:19:20 +0200 Subject: [PATCH 15/66] w --- crates/rec_aggregation/xmss_aggregate.py | 38 +++++++++++++----------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index c2ce76b47..3a0115aa2 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -181,37 +181,37 @@ def copy_xmss_digest(src, dst): def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_bufs, ch_buf_idx): # Chain hash layout: left = [tweak(2) | zeros(2) | data(4)], right = chain_right (shared). # The LEFT prefix [tweak(2) | 00] is read directly from the tweak table by - # poseidon16_compress_hardcoded_left_4 — no per-hash buffer copies needed. + # poseidon16_compress_half_hardcoded_left_4 — no per-hash buffer copies needed. # # `chain_i_tweaks` is a compile-time constant address (TWEAK_TABLE_ADDR + chain_offset), # so each `chain_i_tweaks + step * TWEAK_LEN` is also a compile-time constant; the # hardcoded_left_4 precompile bakes the absolute tweak slot address into the bytecode. # - # Each ch_buf slot still spans BUF_SIZE elements (the legacy stride is preserved), - # but we only use the digest sub-slot at offset 4: bufs[j][4..8] holds the j-th hash - # output; the next hash uses `bufs[j] + 4` as its 4-element LEFT data. + # Every chain hash only consumes 4 output FE (either as the LEFT data of the next + # chain step or as the final XMSS digest), so we use the `_half_` variant which only + # constrains the first 4 output FE — saves AIR constraints + memory writes per hash. starting_step = CHAIN_LENGTH - 1 - n if n == 1: # Single hash: input → output, with tweak_{starting_step} as the LEFT prefix. first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - poseidon16_compress_hardcoded_left_4(input, chain_right, output, first_tweak) + poseidon16_compress_half_hardcoded_left_4(input, chain_right, output, first_tweak) else: # Hash 0: input → ch_bufs[0] + 4 ch_buf_idx[0] = ch_bufs first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - poseidon16_compress_hardcoded_left_4(input, chain_right, ch_bufs + 4, first_tweak) + poseidon16_compress_half_hardcoded_left_4(input, chain_right, ch_bufs + 4, first_tweak) # Hashes 1..n-2: bufs[j-1] + 4 → bufs[j] + 4 for j in unroll(1, n - 1): ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE cur_buf = ch_buf_idx[j] cur_tweak = chain_i_tweaks + (starting_step + j) * TWEAK_LEN - poseidon16_compress_hardcoded_left_4(ch_buf_idx[j - 1] + 4, chain_right, cur_buf + 4, cur_tweak) + poseidon16_compress_half_hardcoded_left_4(ch_buf_idx[j - 1] + 4, chain_right, cur_buf + 4, cur_tweak) # Final hash: bufs[n-2] + 4 → output last_tweak = chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN - poseidon16_compress_hardcoded_left_4(ch_buf_idx[n - 2] + 4, chain_right, output, last_tweak) + poseidon16_compress_half_hardcoded_left_4(ch_buf_idx[n - 2] + 4, chain_right, output, last_tweak) return @@ -260,10 +260,14 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ # # `merkle_tweaks_chunk` MUST be a compile-time constant absolute address into the # tweak table — the per-level `+ k * TWEAK_LEN` offsets are baked into - # poseidon16_compress_hardcoded_left_4 so the LEFT input prefix + # poseidon16_compress_half_hardcoded_left_4 so the LEFT input prefix # `[tweak(2) | 00]` is read directly from the tweak table without ever # being copied into a per-hash buffer. # + # Every level only consumes 4 output FE downstream (either as the LEFT data of the + # next merkle level or as a 4-element XMSS digest in `state_out`), so we use the + # `_half_` variant which only constrains the first 4 output FE. + # # Each intermediate level still uses an 8-element `buf*` because when the buffer # holds the RIGHT input we need [pp(4) | digest(4)] contiguous in memory. When the # buffer holds the LEFT input we just pass `buf + PP_IN_LEFT` (the 4-element digest @@ -282,10 +286,10 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ buf0 = buf0_alloc + 1 if b0 == 1: build_right_fn(public_param, path_chunk, right0_alloc) - poseidon16_compress_hardcoded_left_4(state_in, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) + poseidon16_compress_half_hardcoded_left_4(state_in, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) else: build_right_fn(public_param, state_in, right0_alloc) - poseidon16_compress_hardcoded_left_4(path_chunk, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) + poseidon16_compress_half_hardcoded_left_4(path_chunk, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) # Level 1 buf1_alloc = Array(BUF_SIZE + 1) @@ -294,11 +298,11 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ # buf0's digest is the LEFT data; build [pp | path[1]] as the RIGHT input. right1_alloc = Array(DIGEST_LEN + 1) build_right_fn(public_param, path_chunk + 1 * XMSS_DIGEST, right1_alloc) - poseidon16_compress_hardcoded_left_4(buf0 + PP_IN_LEFT, right1_alloc, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(buf0 + PP_IN_LEFT, right1_alloc, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) else: # path[1] is the LEFT data; reuse buf0 as the [pp | digest] RIGHT input. set_buf_prefix_right(buf0, public_param) - poseidon16_compress_hardcoded_left_4(path_chunk + 1 * XMSS_DIGEST, buf0, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(path_chunk + 1 * XMSS_DIGEST, buf0, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) # Level 2 buf2_alloc = Array(BUF_SIZE + 1) @@ -306,19 +310,19 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ if b2 == 1: right2_alloc = Array(DIGEST_LEN + 1) build_right_fn(public_param, path_chunk + 2 * XMSS_DIGEST, right2_alloc) - poseidon16_compress_hardcoded_left_4(buf1 + PP_IN_LEFT, right2_alloc, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(buf1 + PP_IN_LEFT, right2_alloc, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) else: set_buf_prefix_right(buf1, public_param) - poseidon16_compress_hardcoded_left_4(path_chunk + 2 * XMSS_DIGEST, buf1, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(path_chunk + 2 * XMSS_DIGEST, buf1, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) # Level 3 -> state_out if b3 == 1: right3_alloc = Array(DIGEST_LEN + 1) build_right_fn(public_param, path_chunk + 3 * XMSS_DIGEST, right3_alloc) - poseidon16_compress_hardcoded_left_4(buf2 + PP_IN_LEFT, right3_alloc, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(buf2 + PP_IN_LEFT, right3_alloc, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) else: set_buf_prefix_right(buf2, public_param) - poseidon16_compress_hardcoded_left_4(path_chunk + 3 * XMSS_DIGEST, buf2, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(path_chunk + 3 * XMSS_DIGEST, buf2, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return From 4fe67704fc7ab829a0e9be2ca9db49f3c4bea30f Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 17:41:18 +0200 Subject: [PATCH 16/66] w --- crates/rec_aggregation/xmss_aggregate.py | 65 ++++++++++++------------ 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 3a0115aa2..97ca3b304 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -129,7 +129,6 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # 2) Chain hashes -> recover WOTS public key wots_public_key = Array(V * DIGEST_LEN) - MAX_CHAIN_HASHES = CHAIN_LENGTH - 1 chain_right = Array(DIGEST_LEN + 1) build_chain_right(public_param, chain_right) @@ -140,21 +139,12 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): chain_end = wots_public_key + i * DIGEST_LEN chain_i_tweaks = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN - # Pre-allocate intermediate-digest buffers (one BUF_SIZE-strided slot per - # chain hash; only the [4..8] sub-slot of each slot is actually used to hold - # the 4-element digest). We don't need a separate ch_left_first buffer anymore - # because the first hash reads its 4-element LEFT data straight from `input` - # via poseidon16_compress_hardcoded_left_4. - ch_bufs_alloc = Array((MAX_CHAIN_HASHES - 1) * BUF_SIZE) - ch_bufs = ch_bufs_alloc + 1 - ch_buf_idx = Array(MAX_CHAIN_HASHES - 1) - match_range( num_hashes, range(0, 1), lambda _: copy_5(chain_start, chain_end), range(1, CHAIN_LENGTH), - lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right, ch_bufs, ch_buf_idx), + lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right), ) # 3) Hash WOTS public key @@ -178,40 +168,51 @@ def copy_xmss_digest(src, dst): @inline -def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right, ch_bufs, ch_buf_idx): - # Chain hash layout: left = [tweak(2) | zeros(2) | data(4)], right = chain_right (shared). - # The LEFT prefix [tweak(2) | 00] is read directly from the tweak table by - # poseidon16_compress_half_hardcoded_left_4 — no per-hash buffer copies needed. +def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): + # Chain of n half-output poseidon compressions: + # D_0 = poseidon([tweak_s | 00 | input(4)], chain_right)[..4] + # D_j = poseidon([tweak_{s+j} | 00 | D_{j-1}], chain_right)[..4] for j in 1..n-2 + # output = poseidon([tweak_{s+n-1} | 00 | D_{n-2}], chain_right)[..4] + # where s = starting_step = CHAIN_LENGTH - 1 - n. # - # `chain_i_tweaks` is a compile-time constant address (TWEAK_TABLE_ADDR + chain_offset), - # so each `chain_i_tweaks + step * TWEAK_LEN` is also a compile-time constant; the - # hardcoded_left_4 precompile bakes the absolute tweak slot address into the bytecode. + # `chain_i_tweaks` is a compile-time constant absolute address into the tweak table, + # so each per-step `chain_i_tweaks + k * TWEAK_LEN` is also compile-time. The LEFT + # prefix [tweak | 00] is read straight from the tweak slot by the hardcoded_left_4 + # precompile — no per-hash buffer copies. Every output is consumed as a 4-element + # digest, so we use the `_half_` variant. # - # Every chain hash only consumes 4 output FE (either as the LEFT data of the next - # chain step or as the final XMSS digest), so we use the `_half_` variant which only - # constrains the first 4 output FE — saves AIR constraints + memory writes per hash. + # Intermediate digests are packed at stride XMSS_DIGEST (= 4) inside `digests`. + # The buffer is sized n * XMSS_DIGEST: (n-1) digests at offsets 0, 4, ..., (n-2)*4 + # plus 4 trailing slots that the last digest's RES lookup reads (vacuously, since + # half_output leaves those columns unconstrained — the prover sets them to whatever + # is at memory[res+4..res+8], which is 0 in write-once memory). starting_step = CHAIN_LENGTH - 1 - n if n == 1: - # Single hash: input → output, with tweak_{starting_step} as the LEFT prefix. first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN poseidon16_compress_half_hardcoded_left_4(input, chain_right, output, first_tweak) else: - # Hash 0: input → ch_bufs[0] + 4 - ch_buf_idx[0] = ch_bufs + digests = Array(n * XMSS_DIGEST) + + # Hash 0: input → digests[0..4] first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4(input, chain_right, ch_bufs + 4, first_tweak) + poseidon16_compress_half_hardcoded_left_4(input, chain_right, digests, first_tweak) - # Hashes 1..n-2: bufs[j-1] + 4 → bufs[j] + 4 + # Hashes 1..n-2: digests[(j-1)*4..j*4] → digests[j*4..(j+1)*4] for j in unroll(1, n - 1): - ch_buf_idx[j] = ch_buf_idx[j - 1] + BUF_SIZE - cur_buf = ch_buf_idx[j] cur_tweak = chain_i_tweaks + (starting_step + j) * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4(ch_buf_idx[j - 1] + 4, chain_right, cur_buf + 4, cur_tweak) - - # Final hash: bufs[n-2] + 4 → output + poseidon16_compress_half_hardcoded_left_4( + digests + (j - 1) * XMSS_DIGEST, + chain_right, + digests + j * XMSS_DIGEST, + cur_tweak, + ) + + # Final hash: digests[(n-2)*4..(n-1)*4] → output last_tweak = chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4(ch_buf_idx[n - 2] + 4, chain_right, output, last_tweak) + poseidon16_compress_half_hardcoded_left_4( + digests + (n - 2) * XMSS_DIGEST, chain_right, output, last_tweak + ) return From 50986aa588210d897dd361486c9d241e12fac828 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 17:51:32 +0200 Subject: [PATCH 17/66] w --- crates/rec_aggregation/xmss_aggregate.py | 28 +++++++++++------------- crates/xmss/src/wots.rs | 13 ++++++----- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 97ca3b304..51326c529 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -147,9 +147,8 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right), ) - # 3) Hash WOTS public key - wots_pk_tweaks = TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET - expected_leaf = wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks) + # 3) Hash WOTS public key (the LEFT-input tweak slot is baked in via TWEAK_TABLE_ADDR) + expected_leaf = wots_pk_hash(wots_public_key, public_param) # 4) Merkle verification — merkle_tweaks is now a compile-time constant absolute # address, which lets do_4_merkle_levels feed tweak offsets straight into @@ -216,21 +215,16 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): return -def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): +@inline +def wots_pk_hash(wots_public_key, public_param): # Sponge-like hash of V public key digests. - # IV = [tweak(2) | pp(4) | 00(2)], then ingest 8 FE per step (2 digests). + # IV = [tweak(2) | 00 | pp(4)] (matches the LEFT-input convention for + # poseidon16_compress_hardcoded_left_4: the first 4 FE come from the wots_pk + # tweak slot at the compile-time address TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET, + # and the next 4 FE come from `public_param` at runtime). # V must be even. Digests in wots_public_key are at stride DIGEST_LEN (=8). N_CHUNKS = V / 2 - # Build IV: [tweak(2) | 0 | pp(4) | 0] - iv = Array(DIGEST_LEN) - iv[0] = wots_pk_tweaks[0] - iv[1] = wots_pk_tweaks[1] - iv[2] = 0 - for k in unroll(0, PUBLIC_PARAM_LEN_FE): - iv[3 + k] = public_param[k] - iv[7] = 0 - # Ingest V digests, 2 at a time (8 FE per chunk) # Each chunk packs pk[2i] and pk[2i+1] (each XMSS_DIGEST FE, at DIGEST_LEN stride) chunks = Array(N_CHUNKS * DIGEST_LEN) @@ -240,7 +234,11 @@ def wots_pk_hash(wots_public_key, public_param, wots_pk_tweaks): chunks[i * DIGEST_LEN + XMSS_DIGEST + k] = wots_public_key[(2 * i + 1) * DIGEST_LEN + k] states = Array(N_CHUNKS * DIGEST_LEN) - poseidon16_compress(iv, chunks, states) + # First hash: LEFT input is [tweak(2) | 00 | pp(4)]; the precompile reads + # [tweak(2) | 00] from the wots_pk tweak slot and [pp(4)] from `public_param`. + poseidon16_compress_hardcoded_left_4( + public_param, chunks, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET + ) for i in unroll(1, N_CHUNKS): poseidon16_compress(states + (i - 1) * DIGEST_LEN, chunks + i * DIGEST_LEN, states + i * DIGEST_LEN) diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index db03a182e..3b47a640e 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -93,17 +93,18 @@ impl WotsSignature { impl WotsPublicKey { /// Sponge-like hash of V public key digests. - /// IV = [tweak(2) | pp(4) | 00(2)], then ingest 8 FE per step (2 digests at a time). - /// Final output truncated to DIGEST_SIZE (4 FE). + /// IV = [tweak(2) | 00(2) | pp(4)], then ingest 8 FE per step (2 digests at a time). + /// The IV layout matches the LEFT-input convention for poseidon16_compress_hardcoded_left_4 + /// — `[tweak(2) | 00]` is read straight from the wots_pk tweak slot, and `[pp(4)]` lives at + /// the runtime public_param pointer. Final output truncated to DIGEST_SIZE (4 FE). pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { assert!(V % 2 == 0); - // IV: [tweak(2) | 0 | pp(4) | 0] + // IV: [tweak(2) | 00 | pp(4)] let tweak = make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot); let mut state = [F::default(); 8]; state[..TWEAK_LEN].copy_from_slice(&tweak); - // state[2] = 0 (default) - state[3..3 + PUBLIC_PARAM_LEN_FE].copy_from_slice(&public_param); - // state[7] = 0 (default) + // state[2..4] = 00 (default) + state[4..4 + PUBLIC_PARAM_LEN_FE].copy_from_slice(&public_param); for i in (0..V).step_by(2) { let mut chunk = [F::default(); 8]; From 670d336e7b76642a21b699e22811d90a87181f71 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 17:59:33 +0200 Subject: [PATCH 18/66] w --- crates/rec_aggregation/xmss_aggregate.py | 35 ++++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 51326c529..042716871 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -128,7 +128,11 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): assert encoding[i] == CHAIN_LENGTH - 1 # 2) Chain hashes -> recover WOTS public key - wots_public_key = Array(V * DIGEST_LEN) + # `wots_public_key` is packed at stride XMSS_DIGEST (= 4): pk[i] lives at offset + # i * XMSS_DIGEST. We over-allocate by (DIGEST_LEN - XMSS_DIGEST) trailing slots so + # the half-output lookup of the LAST chain (which still reads 8 memory cells past + # its 4-element digest) stays in bounds. + wots_public_key = Array(V * XMSS_DIGEST + (DIGEST_LEN - XMSS_DIGEST)) chain_right = Array(DIGEST_LEN + 1) build_chain_right(public_param, chain_right) @@ -136,13 +140,13 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): for i in unroll(0, V): num_hashes = (CHAIN_LENGTH - 1) - encoding[i] chain_start = chain_starts + i * XMSS_DIGEST - chain_end = wots_public_key + i * DIGEST_LEN + chain_end = wots_public_key + i * XMSS_DIGEST chain_i_tweaks = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN match_range( num_hashes, range(0, 1), - lambda _: copy_5(chain_start, chain_end), + lambda _: copy_xmss_digest(chain_start, chain_end), range(1, CHAIN_LENGTH), lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right), ) @@ -217,30 +221,31 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): @inline def wots_pk_hash(wots_public_key, public_param): - # Sponge-like hash of V public key digests. + # Sponge-like hash of V public key digests, packed at stride XMSS_DIGEST = 4 in + # `wots_public_key`. Each 8-FE sponge chunk = pk[2i] || pk[2i+1] is therefore + # already contiguous in memory at `wots_public_key + i * DIGEST_LEN` — we feed + # those pointers straight into poseidon, no copy / no `chunks` array. + # # IV = [tweak(2) | 00 | pp(4)] (matches the LEFT-input convention for # poseidon16_compress_hardcoded_left_4: the first 4 FE come from the wots_pk # tweak slot at the compile-time address TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET, # and the next 4 FE come from `public_param` at runtime). - # V must be even. Digests in wots_public_key are at stride DIGEST_LEN (=8). + # V must be even. N_CHUNKS = V / 2 - # Ingest V digests, 2 at a time (8 FE per chunk) - # Each chunk packs pk[2i] and pk[2i+1] (each XMSS_DIGEST FE, at DIGEST_LEN stride) - chunks = Array(N_CHUNKS * DIGEST_LEN) - for i in unroll(0, N_CHUNKS): - for k in unroll(0, XMSS_DIGEST): - chunks[i * DIGEST_LEN + k] = wots_public_key[(2 * i) * DIGEST_LEN + k] - chunks[i * DIGEST_LEN + XMSS_DIGEST + k] = wots_public_key[(2 * i + 1) * DIGEST_LEN + k] - states = Array(N_CHUNKS * DIGEST_LEN) # First hash: LEFT input is [tweak(2) | 00 | pp(4)]; the precompile reads # [tweak(2) | 00] from the wots_pk tweak slot and [pp(4)] from `public_param`. + # RIGHT input is the first 8-FE chunk (pk[0] || pk[1]) at wots_public_key + 0. poseidon16_compress_hardcoded_left_4( - public_param, chunks, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET + public_param, wots_public_key, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET ) for i in unroll(1, N_CHUNKS): - poseidon16_compress(states + (i - 1) * DIGEST_LEN, chunks + i * DIGEST_LEN, states + i * DIGEST_LEN) + poseidon16_compress( + states + (i - 1) * DIGEST_LEN, + wots_public_key + i * DIGEST_LEN, + states + i * DIGEST_LEN, + ) return states + (N_CHUNKS - 1) * DIGEST_LEN From 00913e75e289fc97f2fe0acca4d4459438366cfa Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 18:18:49 +0200 Subject: [PATCH 19/66] wip --- crates/rec_aggregation/xmss_aggregate.py | 92 ++++++++++++------------ crates/xmss/src/lib.rs | 24 ++++--- crates/xmss/src/xmss.rs | 24 +++---- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 042716871..e9069a4a8 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -260,22 +260,24 @@ def set_buf_prefix_right(buf, public_param): @inline def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_tweaks_chunk): - # b encodes 4 is_left bits; path elements at XMSS_DIGEST stride. + # New merkle hash layout: + # LEFT = [tweak(2) | 00 | pp(4)] ← `[tweak(2) | 00]` is read straight from + # the merkle tweak slot at the compile-time + # address `merkle_tweaks_chunk + level*TWEAK_LEN`, + # and `[pp(4)]` from the runtime `public_param`. + # RIGHT = [left_child(4) | right_child(4)] ← packed contiguously in `buf*` # - # `merkle_tweaks_chunk` MUST be a compile-time constant absolute address into the - # tweak table — the per-level `+ k * TWEAK_LEN` offsets are baked into - # poseidon16_compress_half_hardcoded_left_4 so the LEFT input prefix - # `[tweak(2) | 00]` is read directly from the tweak table without ever - # being copied into a per-hash buffer. + # Each level's half-output digest is written DIRECTLY into the next level's `buf` + # at the correct slot (offset 0 if it's the LEFT child of the next level, offset 4 + # if it's the RIGHT child). The OTHER child slot is filled by copying the next + # path element into it. No copies of pp, no buffer-prefix tricks. # - # Every level only consumes 4 output FE downstream (either as the LEFT data of the - # next merkle level or as a 4-element XMSS digest in `state_out`), so we use the - # `_half_` variant which only constrains the first 4 output FE. - # - # Each intermediate level still uses an 8-element `buf*` because when the buffer - # holds the RIGHT input we need [pp(4) | digest(4)] contiguous in memory. When the - # buffer holds the LEFT input we just pass `buf + PP_IN_LEFT` (the 4-element digest - # part) as the precompile's `index_a`. + # `buf1`/`buf2`/`buf3` are over-allocated to 12 elements (instead of 8) so the + # half-output lookup at the digest still has 8 valid memory cells when the digest + # lands at offset 4. The trailing 4 cells are vacuous (memory[buf+8..buf+12] = 0 + # in write-once memory; the prover sets the unconstrained trace columns to 0). + BUF_SIZE_DEST = DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST) # 8 + 4 = 12 + b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -284,49 +286,47 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ r3 = (r2 - b2) / 2 b3 = r3 % 2 - # Level 0 - right0_alloc = Array(DIGEST_LEN + 1) - buf0_alloc = Array(BUF_SIZE + 1) - buf0 = buf0_alloc + 1 + # Level 0 input: copy state_in and path_chunk into buf0 in merkle order. + buf0 = Array(DIGEST_LEN) if b0 == 1: - build_right_fn(public_param, path_chunk, right0_alloc) - poseidon16_compress_half_hardcoded_left_4(state_in, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) + # state_in is the LEFT child + copy_xmss_digest(state_in, buf0) + copy_xmss_digest(path_chunk, buf0 + XMSS_DIGEST) else: - build_right_fn(public_param, state_in, right0_alloc) - poseidon16_compress_half_hardcoded_left_4(path_chunk, right0_alloc, buf0 + PP_IN_LEFT, merkle_tweaks_chunk) + # path_chunk[0] is the LEFT child + copy_xmss_digest(path_chunk, buf0) + copy_xmss_digest(state_in, buf0 + XMSS_DIGEST) - # Level 1 - buf1_alloc = Array(BUF_SIZE + 1) - buf1 = buf1_alloc + 1 + # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4) + buf1 = Array(BUF_SIZE_DEST) if b1 == 1: - # buf0's digest is the LEFT data; build [pp | path[1]] as the RIGHT input. - right1_alloc = Array(DIGEST_LEN + 1) - build_right_fn(public_param, path_chunk + 1 * XMSS_DIGEST, right1_alloc) - poseidon16_compress_half_hardcoded_left_4(buf0 + PP_IN_LEFT, right1_alloc, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) + copy_xmss_digest(path_chunk + 1 * XMSS_DIGEST, buf1 + XMSS_DIGEST) else: - # path[1] is the LEFT data; reuse buf0 as the [pp | digest] RIGHT input. - set_buf_prefix_right(buf0, public_param) - poseidon16_compress_half_hardcoded_left_4(path_chunk + 1 * XMSS_DIGEST, buf0, buf1 + PP_IN_LEFT, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST, merkle_tweaks_chunk) + copy_xmss_digest(path_chunk + 1 * XMSS_DIGEST, buf1) - # Level 2 - buf2_alloc = Array(BUF_SIZE + 1) - buf2 = buf2_alloc + 1 + # Level 1 hash → buf2 + buf2 = Array(BUF_SIZE_DEST) if b2 == 1: - right2_alloc = Array(DIGEST_LEN + 1) - build_right_fn(public_param, path_chunk + 2 * XMSS_DIGEST, right2_alloc) - poseidon16_compress_half_hardcoded_left_4(buf1 + PP_IN_LEFT, right2_alloc, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) + copy_xmss_digest(path_chunk + 2 * XMSS_DIGEST, buf2 + XMSS_DIGEST) else: - set_buf_prefix_right(buf1, public_param) - poseidon16_compress_half_hardcoded_left_4(path_chunk + 2 * XMSS_DIGEST, buf1, buf2 + PP_IN_LEFT, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST, merkle_tweaks_chunk + 1 * TWEAK_LEN) + copy_xmss_digest(path_chunk + 2 * XMSS_DIGEST, buf2) - # Level 3 -> state_out + # Level 2 hash → buf3 + buf3 = Array(BUF_SIZE_DEST) if b3 == 1: - right3_alloc = Array(DIGEST_LEN + 1) - build_right_fn(public_param, path_chunk + 3 * XMSS_DIGEST, right3_alloc) - poseidon16_compress_half_hardcoded_left_4(buf2 + PP_IN_LEFT, right3_alloc, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) + copy_xmss_digest(path_chunk + 3 * XMSS_DIGEST, buf3 + XMSS_DIGEST) else: - set_buf_prefix_right(buf2, public_param) - poseidon16_compress_half_hardcoded_left_4(path_chunk + 3 * XMSS_DIGEST, buf2, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST, merkle_tweaks_chunk + 2 * TWEAK_LEN) + copy_xmss_digest(path_chunk + 3 * XMSS_DIGEST, buf3) + + # Level 3 hash → state_out (digest always written at offset 0 since the next chunk + # reads state_out as a 4-element pointer regardless). + poseidon16_compress_half_hardcoded_left_4(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 31b3dfc00..5a7c63df7 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -58,20 +58,28 @@ pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> ] } -/// [tweak(2) | zeros(2) | data(4)] -pub(crate) fn build_left(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { +/// [tweak(2) | zeros(2) | public_param(4)] +/// +/// Merkle LEFT input. Matches the LEFT-input convention for +/// `poseidon16_compress_half_hardcoded_left_4`: the first 4 FE come from the merkle +/// tweak slot at a compile-time absolute address, and the next 4 FE come from the +/// runtime `public_param` pointer — so the verifier never copies pp into a per-level +/// buffer. +pub(crate) fn build_left(tweak: [F; TWEAK_LEN], public_param: &PublicParam) -> [F; 8] { let mut left = [F::default(); 8]; left[..TWEAK_LEN].copy_from_slice(&tweak); - // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) - left[8 - DIGEST_SIZE..].copy_from_slice(data); + // left[TWEAK_LEN..INPUT_PREFIX_LEN] = zeros (default) + left[INPUT_PREFIX_LEN..].copy_from_slice(public_param); left } -/// [public_param(4) | data(4)] -pub(crate) fn build_right(public_param: &PublicParam, data: &Digest) -> [F; 8] { +/// [left_child(4) | right_child(4)] +/// +/// Merkle RIGHT input: the two children of the parent node packed contiguously. +pub(crate) fn build_right(left_child: &Digest, right_child: &Digest) -> [F; 8] { let mut right = [F::default(); 8]; - right[..INPUT_PREFIX_LEN].copy_from_slice(public_param); - right[INPUT_PREFIX_LEN..].copy_from_slice(data); + right[..DIGEST_SIZE].copy_from_slice(left_child); + right[DIGEST_SIZE..].copy_from_slice(right_child); right } diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index 395e1f8a0..b21a62139 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -113,8 +113,8 @@ pub fn xmss_key_gen( } else { gen_random_node(&seed, level - 1, right_idx) }; - let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &left); - let poseidon_right = build_right(&public_param, &right); + let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &public_param); + let poseidon_right = build_right(&left, &right); poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..DIGEST_SIZE] .try_into() .unwrap() @@ -214,19 +214,15 @@ pub fn xmss_verify( let is_left = (((slot as u64) >> level) & 1) == 0; let parent_index = ((slot as u64) >> (level + 1)) as u32; let tweak = make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index); - if is_left { - let left = build_left(tweak, ¤t_hash); - let right = build_right(&pub_key.public_param, neighbour); - current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] - .try_into() - .unwrap(); + let left = build_left(tweak, &pub_key.public_param); + let right = if is_left { + build_right(¤t_hash, neighbour) } else { - let left = build_left(tweak, neighbour); - let right = build_right(&pub_key.public_param, ¤t_hash); - current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] - .try_into() - .unwrap(); - } + build_right(neighbour, ¤t_hash) + }; + current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + .try_into() + .unwrap(); } if current_hash == pub_key.merkle_root { Ok(()) From c590d9ca9883544311f5e313b1ae6b4c80a359d2 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 19:02:47 +0200 Subject: [PATCH 20/66] w --- crates/lean_compiler/snark_lib.py | 9 ++- crates/lean_vm/src/execution/runner.rs | 76 ++++++++++++++++------- crates/lean_vm/src/isa/hint.rs | 59 +++++++++++++----- crates/rec_aggregation/main.py | 12 ++-- crates/rec_aggregation/src/compilation.rs | 5 +- crates/rec_aggregation/src/lib.rs | 35 ++++++++--- crates/rec_aggregation/xmss_aggregate.py | 47 +++++++------- crates/xmss/src/lib.rs | 23 ++++--- crates/xmss/src/wots.rs | 10 +-- crates/xmss/src/xmss.rs | 8 +-- 10 files changed, 188 insertions(+), 96 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 7dec590d3..a0e1d7977 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -187,7 +187,14 @@ def hint_log2_ceil(n): return log2_ceil(n) -def hint_xmss(buff): +def hint_wots(buff): + _ = buff + + +def hint_xmss_merkle_node(buff): + """Pull the next 4-element XMSS merkle path node from the prover's flat queue and + write it at `buff`. Lets the merkle hash code place each path neighbour directly + into the merkle hash buffer slot, avoiding per-level copies.""" _ = buff diff --git a/crates/lean_vm/src/execution/runner.rs b/crates/lean_vm/src/execution/runner.rs index 68556d1c8..007f5dc7e 100644 --- a/crates/lean_vm/src/execution/runner.rs +++ b/crates/lean_vm/src/execution/runner.rs @@ -22,8 +22,13 @@ use super::memory::SegmentMemory; pub struct ExecutionWitness<'a> { /// Private field elements loaded into memory after public memory. pub private_input: &'a [F], - /// XMSS signatures, one Vec per signature (each of length SIG_SIZE_FE) - pub xmss_signatures: &'a [Vec], + /// In-memory part of each XMSS signature (`randomness | chain_tips`, of length + /// `WOTS_SIG_SIZE_FE`), consumed by `hint_wots`. The XMSS merkle path is delivered + /// out-of-band via `xmss_merkle_nodes`. + pub wots_signatures: &'a [Vec], + /// Flat queue of XMSS merkle path nodes (4 FE per node), consumed by + /// `hint_xmss_merkle_node` calls in the order they execute at runtime. + pub xmss_merkle_nodes: &'a [F], /// Merkle paths for WHIR recursion, one Vec per hint_merkle call pub merkle_paths: &'a [Vec], } @@ -32,7 +37,8 @@ impl ExecutionWitness<'_> { pub fn empty() -> Self { Self { private_input: &[], - xmss_signatures: &[], + wots_signatures: &[], + xmss_merkle_nodes: &[], merkle_paths: &[], } } @@ -140,7 +146,8 @@ struct ParallelBatchInfo { frame_size: usize, n_args: usize, end_value: MemOrConstant, - xmss_hint_index_at_start: usize, + wots_hint_index_at_start: usize, + xmss_merkle_node_index_at_start: usize, merkle_hint_index_at_start: usize, } @@ -179,7 +186,8 @@ fn run_loop( frame_size: *ap - *fp, n_args: *n_args, end_value: *end_value, - xmss_hint_index_at_start: *hints.xmss_hint_index, + wots_hint_index_at_start: *hints.wots_hint_index, + xmss_merkle_node_index_at_start: *hints.xmss_merkle_node_index, merkle_hint_index_at_start: *hints.merkle_hint_index, }); } @@ -266,7 +274,8 @@ fn execute_bytecode_helper( profiling: bool, ) -> Result { let private_input = witness.private_input; - let xmss_signatures = witness.xmss_signatures; + let wots_signatures = witness.wots_signatures; + let xmss_merkle_nodes = witness.xmss_merkle_nodes; let merkle_paths = witness.merkle_paths; let mut memory = Memory::new(build_public_memory(public_input)); @@ -281,7 +290,8 @@ fn execute_bytecode_helper( let mut pc = STARTING_PC; let mut ap = initial_ap; let mut trace = Trace::new(); - let mut xmss_hint_index = 0; + let mut wots_hint_index = 0; + let mut xmss_merkle_node_index = 0; let mut merkle_hint_index = 0; let mut cpu_cycles_before_new_line = 0; let mut last_checkpoint_cpu_cycles = 0; @@ -297,8 +307,10 @@ fn execute_bytecode_helper( checkpoint_ap: &mut checkpoint_ap, }), private_input_start: public_memory_size, - xmss_signatures, - xmss_hint_index: &mut xmss_hint_index, + wots_signatures, + wots_hint_index: &mut wots_hint_index, + xmss_merkle_nodes, + xmss_merkle_node_index: &mut xmss_merkle_node_index, merkle_paths, merkle_hint_index: &mut merkle_hint_index, }; @@ -320,8 +332,10 @@ fn execute_bytecode_helper( bytecode, &mut memory, &mut trace, - xmss_signatures, - &mut xmss_hint_index, + wots_signatures, + &mut wots_hint_index, + xmss_merkle_nodes, + &mut xmss_merkle_node_index, merkle_paths, &mut merkle_hint_index, &mut pc, @@ -338,9 +352,14 @@ fn execute_bytecode_helper( resolve_deref_hints(&mut memory, &trace.pending_deref_hints); assert_eq!( - xmss_hint_index, - xmss_signatures.len(), - "Not all XMSS hints were consumed" + wots_hint_index, + wots_signatures.len(), + "Not all WOTS hints were consumed" + ); + assert_eq!( + xmss_merkle_node_index * xmss::XMSS_DIGEST_SIZE, + xmss_merkle_nodes.len(), + "Not all XMSS merkle node hints were consumed" ); assert_eq!( merkle_hint_index, @@ -414,8 +433,10 @@ fn handle_parallel_batch( bytecode: &Bytecode, memory: &mut Memory, trace: &mut Trace, - xmss_signatures: &[Vec], - xmss_hint_index: &mut usize, + wots_signatures: &[Vec], + wots_hint_index: &mut usize, + xmss_merkle_nodes: &[F], + xmss_merkle_node_index: &mut usize, merkle_paths: &[Vec], merkle_hint_index: &mut usize, pc: &mut usize, @@ -439,7 +460,8 @@ fn handle_parallel_batch( .collect(); // Measure per-iteration hint consumption from iteration 0. - let xmss_per_iter = *xmss_hint_index - batch.xmss_hint_index_at_start; + let wots_per_iter = *wots_hint_index - batch.wots_hint_index_at_start; + let xmss_merkle_node_per_iter = *xmss_merkle_node_index - batch.xmss_merkle_node_index_at_start; let merkle_per_iter = *merkle_hint_index - batch.merkle_hint_index_at_start; for i in 1..=n_iters { @@ -459,7 +481,8 @@ fn handle_parallel_batch( memory.0.resize(max_addr, None); } - let xmss_base = *xmss_hint_index; + let wots_base = *wots_hint_index; + let xmss_merkle_node_base = *xmss_merkle_node_index; let merkle_base = *merkle_hint_index; let n_par = n_iters - 1; @@ -479,19 +502,25 @@ fn handle_parallel_batch( let seg_start = split_at + i * stride; let mut seg_mem = SegmentMemory::new(shared, seg_slice, seg_start); let fp_i = batch.batch_fp + (i + 1) * stride; - let xmss_sigs = &xmss_signatures[xmss_base + i * xmss_per_iter..xmss_base + (i + 1) * xmss_per_iter]; + let wots_sigs = &wots_signatures[wots_base + i * wots_per_iter..wots_base + (i + 1) * wots_per_iter]; + let xmss_merkle_nodes_seg = &xmss_merkle_nodes[(xmss_merkle_node_base + i * xmss_merkle_node_per_iter) + * xmss::XMSS_DIGEST_SIZE + ..(xmss_merkle_node_base + (i + 1) * xmss_merkle_node_per_iter) * xmss::XMSS_DIGEST_SIZE]; let merkle = &merkle_paths[merkle_base + i * merkle_per_iter..merkle_base + (i + 1) * merkle_per_iter]; let mut seg_trace = Trace::new(); let mut seg_pc = batch.batch_pc; let mut seg_fp = fp_i; let mut seg_ap = fp_i + batch.frame_size; - let mut xmss_idx = 0usize; + let mut wots_idx = 0usize; + let mut xmss_merkle_node_idx = 0usize; let mut merkle_idx = 0usize; let mut hints = HintState { diagnostics: None, private_input_start, - xmss_signatures: xmss_sigs, - xmss_hint_index: &mut xmss_idx, + wots_signatures: wots_sigs, + wots_hint_index: &mut wots_idx, + xmss_merkle_nodes: xmss_merkle_nodes_seg, + xmss_merkle_node_index: &mut xmss_merkle_node_idx, merkle_paths: merkle, merkle_hint_index: &mut merkle_idx, }; @@ -518,7 +547,8 @@ fn handle_parallel_batch( } } - *xmss_hint_index += n_par * xmss_per_iter; + *wots_hint_index += n_par * wots_per_iter; + *xmss_merkle_node_index += n_par * xmss_merkle_node_per_iter; *merkle_hint_index += n_par * merkle_per_iter; *pc = batch.batch_pc; diff --git a/crates/lean_vm/src/isa/hint.rs b/crates/lean_vm/src/isa/hint.rs index 6c7fbc068..b98c6dfc5 100644 --- a/crates/lean_vm/src/isa/hint.rs +++ b/crates/lean_vm/src/isa/hint.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; use std::fmt::{Display, Formatter}; use std::hash::Hash; use utils::{ToUsize, to_big_endian_in_field, to_little_endian_in_field}; -use xmss::SIG_SIZE_FE; +use xmss::{WOTS_SIG_SIZE_FE, XMSS_DIGEST_SIZE}; /// VM hints provide execution guidance and debugging information, but does not appear /// in the verified bytecode. @@ -84,18 +84,26 @@ pub enum CustomHint { LessThan, Log2Ceil, PrivateInputStart, - Xmss, + /// Hint the in-memory part of an XMSS signature: `randomness | chain_tips` (the WOTS + /// part). The XMSS merkle path is hinted separately via `XmssMerkleNode`. + Wots, + /// Pull the next 4-element XMSS merkle node from the prover's flat queue and write + /// it at a runtime-chosen buf address. Lets `do_4_merkle_levels` place each + /// merkle path neighbour directly into the merkle hash buffer at the right slot, + /// avoiding the per-level copy from a contiguous merkle path array. + XmssMerkleNode, Merkle, } -pub const CUSTOM_HINTS: [CustomHint; 8] = [ +pub const CUSTOM_HINTS: [CustomHint; 9] = [ CustomHint::DecomposeBitsXMSS, CustomHint::DecomposeBitsMerkleWhir, CustomHint::DecomposeBits, CustomHint::LessThan, CustomHint::Log2Ceil, CustomHint::PrivateInputStart, - CustomHint::Xmss, + CustomHint::Wots, + CustomHint::XmssMerkleNode, CustomHint::Merkle, ]; @@ -108,7 +116,8 @@ impl CustomHint { Self::LessThan => "hint_less_than", Self::Log2Ceil => "hint_log2_ceil", Self::PrivateInputStart => "hint_private_input_start", - Self::Xmss => "hint_xmss", + Self::Wots => "hint_wots", + Self::XmssMerkleNode => "hint_xmss_merkle_node", Self::Merkle => "hint_merkle", } } @@ -121,7 +130,8 @@ impl CustomHint { Self::LessThan => 3, Self::Log2Ceil => 2, Self::PrivateInputStart => 1, - Self::Xmss => 1, + Self::Wots => 1, + Self::XmssMerkleNode => 1, Self::Merkle => 2, } } @@ -204,18 +214,30 @@ impl CustomHint { let res_ptr = args[0].memory_address(ctx.fp)?; ctx.memory.set(res_ptr, F::from_usize(ctx.hints.private_input_start))?; } - Self::Xmss => { + Self::Wots => { let buf_ptr = args[0].read_value(ctx.memory, ctx.fp)?.to_usize(); - let index = *ctx.hints.xmss_hint_index; + let index = *ctx.hints.wots_hint_index; assert!( - index < ctx.hints.xmss_signatures.len(), - "hint_xmss: not enough XMSS signatures (index={})", + index < ctx.hints.wots_signatures.len(), + "hint_wots: not enough WOTS signatures (index={})", index ); - let sig = &ctx.hints.xmss_signatures[index]; - assert_eq!(sig.len(), SIG_SIZE_FE); + let sig = &ctx.hints.wots_signatures[index]; + assert_eq!(sig.len(), WOTS_SIG_SIZE_FE); ctx.memory.set_slice(buf_ptr, sig)?; - *ctx.hints.xmss_hint_index += 1; + *ctx.hints.wots_hint_index += 1; + } + Self::XmssMerkleNode => { + let buf_ptr = args[0].read_value(ctx.memory, ctx.fp)?.to_usize(); + let index = *ctx.hints.xmss_merkle_node_index; + let start = index * XMSS_DIGEST_SIZE; + assert!( + start + XMSS_DIGEST_SIZE <= ctx.hints.xmss_merkle_nodes.len(), + "hint_xmss_merkle_node: not enough merkle nodes (index={index})" + ); + ctx.memory + .set_slice(buf_ptr, &ctx.hints.xmss_merkle_nodes[start..start + XMSS_DIGEST_SIZE])?; + *ctx.hints.xmss_merkle_node_index += 1; } Self::Merkle => { let buf_ptr = args[0].read_value(ctx.memory, ctx.fp)?.to_usize(); @@ -274,8 +296,15 @@ pub struct DiagnosticState<'a> { pub struct HintState<'a> { pub diagnostics: Option>, pub private_input_start: usize, - pub xmss_signatures: &'a [Vec], - pub xmss_hint_index: &'a mut usize, + /// In-memory part of each XMSS signature: `randomness | chain_tips`. Consumed by + /// `hint_wots`. The merkle path lives in `xmss_merkle_nodes`. + pub wots_signatures: &'a [Vec], + pub wots_hint_index: &'a mut usize, + /// Flat queue of XMSS merkle path nodes, 4 FE per node, ordered by the runtime + /// consumption order of `hint_xmss_merkle_node` calls. The prover provides one + /// node per call; each call writes 4 FE at a runtime-chosen buf address. + pub xmss_merkle_nodes: &'a [F], + pub xmss_merkle_node_index: &'a mut usize, pub merkle_paths: &'a [Vec], pub merkle_hint_index: &'a mut usize, } diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index c59274596..90fae16bb 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -88,12 +88,14 @@ def main(): idx = raw_indices[i] assert idx < n_total buffer[idx] = i - # Verify raw XMSS signatures + # Verify raw XMSS signatures. + # `sig` only carries the WOTS part (`randomness | chain_tips`); the merkle path + # is delivered out-of-band via hint_xmss_merkle_node inside do_4_merkle_levels. pk = all_pubkeys + idx * PUB_KEY_SIZE - # 1 extra element for safe copy_5 reads past the last merkle path element - sig = Array(SIG_SIZE + 1) - hint_xmss(sig) - sig[SIG_SIZE] = 0 + # 1 extra element for safe copy_5 reads past the last chain_tip + sig = Array(WOTS_SIG_SIZE + 1) + hint_wots(sig) + sig[WOTS_SIG_SIZE] = 0 xmss_verify(pk, message, sig, merkle_chunks_for_slot) counter: Mut = n_raw_xmss diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index b658884ff..5e7e1253c 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -12,7 +12,8 @@ use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; use xmss::{ - DIGEST_SIZE, LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, + LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, + XMSS_DIGEST_SIZE, }; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; @@ -376,7 +377,7 @@ fn build_replacements( "MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER".to_string(), MERKLE_LEVELS_PER_CHUNK_FOR_SLOT.to_string(), ); - replacements.insert("XMSS_DIGEST_SIZE_PLACEHOLDER".to_string(), DIGEST_SIZE.to_string()); + replacements.insert("XMSS_DIGEST_SIZE_PLACEHOLDER".to_string(), XMSS_DIGEST_SIZE.to_string()); // Bytecode zero eval replacements.insert( diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 74f9badff..8b41e23eb 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -7,7 +7,7 @@ use lean_prover::verify_execution::verify_execution; use lean_vm::*; use tracing::instrument; use utils::{build_prover_state, get_poseidon16, poseidon_compress_slice, poseidon16_compress_pair}; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUB_KEY_FLAT_SIZE, SIG_SIZE_FE, V, W, XmssPublicKey, XmssSignature}; +use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUB_KEY_FLAT_SIZE, V, W, WOTS_SIG_SIZE_FE, XmssPublicKey, XmssSignature}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -144,17 +144,24 @@ fn build_non_reserved_public_input( pi } -fn encode_xmss_signature(sig: &XmssSignature) -> Vec { +fn encode_wots_signature(sig: &XmssSignature) -> Vec { + // The in-memory signature buffer consumed by `hint_wots` is just the WOTS part: + // `randomness | chain_tips`. The XMSS merkle path is delivered out-of-band via + // `hint_xmss_merkle_node`, see `encode_xmss_merkle_path`. let mut data = vec![]; data.extend(sig.wots_signature.randomness.to_vec()); data.extend(sig.wots_signature.chain_tips.iter().flat_map(|digest| digest.to_vec())); - for neighbor in &sig.merkle_proof { - data.extend(neighbor.to_vec()); - } - assert_eq!(data.len(), SIG_SIZE_FE); + assert_eq!(data.len(), WOTS_SIG_SIZE_FE); data } +/// Flatten the merkle path of an XMSS signature into a 4*LOG_LIFETIME flat vec, in the +/// order it will be consumed by successive `hint_xmss_merkle_node` calls inside +/// `do_4_merkle_levels` (= the natural leaf-to-root order of the merkle path). +fn encode_xmss_merkle_path(sig: &XmssSignature) -> Vec { + sig.merkle_proof.iter().flat_map(|d| d.to_vec()).collect() +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AggregatedXMSS { pub proof: Proof, @@ -373,10 +380,17 @@ pub fn xmss_aggregate( let mut dup_pub_keys: Vec = Vec::new(); let mut source_blocks: Vec> = vec![]; - // Build XMSS signatures (one Vec per signature, consumed by hint_xmss) - let xmss_signatures: Vec> = raw_xmss.iter().map(|(_, sig)| encode_xmss_signature(sig)).collect(); + // Build the WOTS-portion signature buffers (one Vec per signature, consumed by + // hint_wots). The merkle path of each sig is delivered separately via + // hint_xmss_merkle_node; we flatten it here in the order it will be consumed at + // runtime. + let wots_signatures: Vec> = raw_xmss.iter().map(|(_, sig)| encode_wots_signature(sig)).collect(); + let xmss_merkle_nodes: Vec = raw_xmss + .iter() + .flat_map(|(_, sig)| encode_xmss_merkle_path(sig)) + .collect(); - // Source 0: raw XMSS (indices only; signature data goes via hint_xmss) + // Source 0: raw XMSS (indices only; signature data goes via hint_wots) { let mut block = vec![F::from_usize(raw_count)]; for (pk, _) in &raw_xmss { @@ -465,7 +479,8 @@ pub fn xmss_aggregate( let witness = ExecutionWitness { private_input: &private_input, - xmss_signatures: &xmss_signatures, + wots_signatures: &wots_signatures, + xmss_merkle_nodes: &xmss_merkle_nodes, merkle_paths: &merkle_paths, }; let execution_proof = prove_execution(bytecode, &non_reserved_public_input, &witness, &whir_config, false); diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index e9069a4a8..e19f822c7 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -13,7 +13,7 @@ XMSS_DIGEST = XMSS_DIGEST_SIZE_PLACEHOLDER PUB_KEY_SIZE = XMSS_DIGEST + PUBLIC_PARAM_LEN_FE PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST -SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * XMSS_DIGEST +WOTS_SIG_SIZE = RANDOMNESS_LEN + V * XMSS_DIGEST NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK @@ -76,7 +76,9 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): public_param = pub_key + XMSS_DIGEST randomness = signature chain_starts = signature + RANDOMNESS_LEN - merkle_path = chain_starts + V * XMSS_DIGEST + # NOTE: the merkle path is no longer part of `signature`. The prover delivers + # each merkle node directly into a `do_4_merkle_levels` slot via + # `hint_xmss_merkle_node`, see `xmss_merkle_verify` / `do_4_merkle_levels`. # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) @@ -158,7 +160,7 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # address, which lets do_4_merkle_levels feed tweak offsets straight into # poseidon16_compress_hardcoded_left_4. merkle_tweaks = TWEAK_TABLE_ADDR + TWEAK_MERKLE_OFFSET - xmss_merkle_verify(expected_leaf, merkle_path, merkle_chunks, pub_key, public_param, merkle_tweaks) + xmss_merkle_verify(expected_leaf, merkle_chunks, pub_key, public_param, merkle_tweaks) return @@ -259,7 +261,7 @@ def set_buf_prefix_right(buf, public_param): @inline -def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_tweaks_chunk): +def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk): # New merkle hash layout: # LEFT = [tweak(2) | 00 | pp(4)] ← `[tweak(2) | 00]` is read straight from # the merkle tweak slot at the compile-time @@ -269,8 +271,11 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ # # Each level's half-output digest is written DIRECTLY into the next level's `buf` # at the correct slot (offset 0 if it's the LEFT child of the next level, offset 4 - # if it's the RIGHT child). The OTHER child slot is filled by copying the next - # path element into it. No copies of pp, no buffer-prefix tricks. + # if it's the RIGHT child). The OTHER child slot is filled by `hint_xmss_merkle_node`, + # which the prover supplies in level order — so neither the merkle path nor the + # public param are ever copied/duplicated inside `do_4_merkle_levels`. The only + # remaining copy is `state_in` into the level-0 buffer (we don't know `state_in`'s + # address layout statically across chunk boundaries). # # `buf1`/`buf2`/`buf3` are over-allocated to 12 elements (instead of 8) so the # half-output lookup at the digest still has 8 valid memory cells when the digest @@ -286,43 +291,45 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ r3 = (r2 - b2) / 2 b3 = r3 % 2 - # Level 0 input: copy state_in and path_chunk into buf0 in merkle order. + # Level 0 input: copy state_in into buf0 (we don't know its statically); hint the + # level-0 merkle path neighbour into the OTHER slot. buf0 = Array(DIGEST_LEN) if b0 == 1: # state_in is the LEFT child copy_xmss_digest(state_in, buf0) - copy_xmss_digest(path_chunk, buf0 + XMSS_DIGEST) + hint_xmss_merkle_node(buf0 + XMSS_DIGEST) else: - # path_chunk[0] is the LEFT child - copy_xmss_digest(path_chunk, buf0) + # the merkle path neighbour is the LEFT child + hint_xmss_merkle_node(buf0) copy_xmss_digest(state_in, buf0 + XMSS_DIGEST) - # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4) + # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4). + # The path neighbour for level 1 is hinted into the OTHER slot of buf1. buf1 = Array(BUF_SIZE_DEST) if b1 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) - copy_xmss_digest(path_chunk + 1 * XMSS_DIGEST, buf1 + XMSS_DIGEST) + hint_xmss_merkle_node(buf1 + XMSS_DIGEST) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST, merkle_tweaks_chunk) - copy_xmss_digest(path_chunk + 1 * XMSS_DIGEST, buf1) + hint_xmss_merkle_node(buf1) # Level 1 hash → buf2 buf2 = Array(BUF_SIZE_DEST) if b2 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) - copy_xmss_digest(path_chunk + 2 * XMSS_DIGEST, buf2 + XMSS_DIGEST) + hint_xmss_merkle_node(buf2 + XMSS_DIGEST) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST, merkle_tweaks_chunk + 1 * TWEAK_LEN) - copy_xmss_digest(path_chunk + 2 * XMSS_DIGEST, buf2) + hint_xmss_merkle_node(buf2) # Level 2 hash → buf3 buf3 = Array(BUF_SIZE_DEST) if b3 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) - copy_xmss_digest(path_chunk + 3 * XMSS_DIGEST, buf3 + XMSS_DIGEST) + hint_xmss_merkle_node(buf3 + XMSS_DIGEST) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST, merkle_tweaks_chunk + 2 * TWEAK_LEN) - copy_xmss_digest(path_chunk + 3 * XMSS_DIGEST, buf3) + hint_xmss_merkle_node(buf3) # Level 3 hash → state_out (digest always written at offset 0 since the next chunk # reads state_out as a 4-element pointer regardless). @@ -331,11 +338,11 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out, public_param, merkle_ @inline -def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, public_param, merkle_tweaks): +def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, merkle_tweaks): states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) # First chunk - match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states, public_param, merkle_tweaks)) + match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, states, public_param, merkle_tweaks)) state_indexes = Array(N_MERKLE_CHUNKS - 1) state_indexes[0] = states @@ -347,7 +354,6 @@ def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, p lambda b: do_4_merkle_levels( b, state_indexes[j - 1], - merkle_path + j * MERKLE_LEVELS_PER_CHUNK * XMSS_DIGEST, state_indexes[j], public_param, merkle_tweaks + j * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, @@ -362,7 +368,6 @@ def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root, p lambda b: do_4_merkle_levels( b, state_indexes[N_MERKLE_CHUNKS - 2], - merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * XMSS_DIGEST, last_output, public_param, merkle_tweaks + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 5a7c63df7..022cce733 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -6,11 +6,11 @@ pub use wots::*; mod xmss; pub use xmss::*; -pub const DIGEST_SIZE: usize = 4; +pub const XMSS_DIGEST_SIZE: usize = 4; pub(crate) const TWEAK_LEN: usize = 2; type F = KoalaBear; -type Digest = [F; DIGEST_SIZE]; +type Digest = [F; XMSS_DIGEST_SIZE]; type PublicParam = [F; PUBLIC_PARAM_LEN_FE]; type Randomness = [F; RANDOMNESS_LEN_FE]; @@ -28,14 +28,17 @@ pub const PUBLIC_PARAM_LEN_FE: usize = 4; /// Length of the non-data prefix in either Poseidon input. Both inputs reserve the /// first `INPUT_PREFIX_LEN` field elements for metadata (tweak/zeros on the left, /// public_param on the right) and place the 4-element data digest right after it. -pub const INPUT_PREFIX_LEN: usize = 8 - DIGEST_SIZE; // = 4 -pub const PUB_KEY_FLAT_SIZE: usize = DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 +pub const INPUT_PREFIX_LEN: usize = 8 - XMSS_DIGEST_SIZE; // = 4 +pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 -const _: () = assert!(INPUT_PREFIX_LEN + DIGEST_SIZE == 8); +const _: () = assert!(INPUT_PREFIX_LEN + XMSS_DIGEST_SIZE == 8); // Left layout (with tweak): [tweak(2) | zeros(2) | data(DIGEST_SIZE)] -const _: () = assert!(TWEAK_LEN + 2 + DIGEST_SIZE == 8); +const _: () = assert!(TWEAK_LEN + 2 + XMSS_DIGEST_SIZE == 8); -pub const SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + (V + LOG_LIFETIME) * DIGEST_SIZE; +/// In-memory layout consumed by `hint_wots`: just `randomness | chain_tips` (the WOTS +/// part of an XMSS signature). The XMSS merkle path lives in a separate prover-side +/// queue and is consumed by `hint_xmss_merkle_node`. +pub const WOTS_SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + V * XMSS_DIGEST_SIZE; // Tweak: domain separation within each hash. pub(crate) const TWEAK_TYPE_CHAIN: usize = 0; @@ -78,8 +81,8 @@ pub(crate) fn build_left(tweak: [F; TWEAK_LEN], public_param: &PublicParam) -> [ /// Merkle RIGHT input: the two children of the parent node packed contiguously. pub(crate) fn build_right(left_child: &Digest, right_child: &Digest) -> [F; 8] { let mut right = [F::default(); 8]; - right[..DIGEST_SIZE].copy_from_slice(left_child); - right[DIGEST_SIZE..].copy_from_slice(right_child); + right[..XMSS_DIGEST_SIZE].copy_from_slice(left_child); + right[XMSS_DIGEST_SIZE..].copy_from_slice(right_child); right } @@ -88,7 +91,7 @@ pub(crate) fn build_left_chain(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { let mut left = [F::default(); 8]; left[..TWEAK_LEN].copy_from_slice(&tweak); // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) - left[8 - DIGEST_SIZE..].copy_from_slice(data); + left[8 - XMSS_DIGEST_SIZE..].copy_from_slice(data); left } diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 3b47a640e..82ef5a3a5 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -108,11 +108,11 @@ impl WotsPublicKey { for i in (0..V).step_by(2) { let mut chunk = [F::default(); 8]; - chunk[..DIGEST_SIZE].copy_from_slice(&self.0[i]); - chunk[DIGEST_SIZE..].copy_from_slice(&self.0[i + 1]); + chunk[..XMSS_DIGEST_SIZE].copy_from_slice(&self.0[i]); + chunk[XMSS_DIGEST_SIZE..].copy_from_slice(&self.0[i + 1]); state = poseidon16_compress_pair(&state, &chunk); } - state[..DIGEST_SIZE].try_into().unwrap() + state[..XMSS_DIGEST_SIZE].try_into().unwrap() } } @@ -129,7 +129,7 @@ pub fn iterate_hash( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left_chain(tweak, &acc); - poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_SIZE] .try_into() .unwrap() }) @@ -149,7 +149,7 @@ pub fn iterate_hash_with_poseidon_trace( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left_chain(tweak, &acc); - poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..DIGEST_SIZE] + poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..XMSS_DIGEST_SIZE] .try_into() .unwrap() }) diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index b21a62139..3e13306a8 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -31,8 +31,8 @@ pub struct XmssPublicKey { impl XmssPublicKey { pub fn flaten(&self) -> [F; PUB_KEY_FLAT_SIZE] { let mut output = [F::default(); PUB_KEY_FLAT_SIZE]; - output[..DIGEST_SIZE].copy_from_slice(&self.merkle_root); - output[DIGEST_SIZE..].copy_from_slice(&self.public_param); + output[..XMSS_DIGEST_SIZE].copy_from_slice(&self.merkle_root); + output[XMSS_DIGEST_SIZE..].copy_from_slice(&self.public_param); output } } @@ -115,7 +115,7 @@ pub fn xmss_key_gen( }; let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &public_param); let poseidon_right = build_right(&left, &right); - poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..DIGEST_SIZE] + poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..XMSS_DIGEST_SIZE] .try_into() .unwrap() }) @@ -220,7 +220,7 @@ pub fn xmss_verify( } else { build_right(neighbour, ¤t_hash) }; - current_hash = poseidon16_compress_pair(&left, &right)[..DIGEST_SIZE] + current_hash = poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_SIZE] .try_into() .unwrap(); } From 1f0de06849a239795d6ff5d727caa36c64ba47b5 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 19:03:06 +0200 Subject: [PATCH 21/66] w --- crates/lean_vm/src/execution/runner.rs | 6 ++-- crates/lean_vm/src/isa/hint.rs | 8 ++--- crates/rec_aggregation/src/compilation.rs | 5 ++- crates/rec_aggregation/xmss_aggregate.py | 44 +++++++++++------------ crates/xmss/src/lib.rs | 20 +++++------ crates/xmss/src/wots.rs | 10 +++--- crates/xmss/src/xmss.rs | 8 ++--- 7 files changed, 50 insertions(+), 51 deletions(-) diff --git a/crates/lean_vm/src/execution/runner.rs b/crates/lean_vm/src/execution/runner.rs index 007f5dc7e..147dfaf0f 100644 --- a/crates/lean_vm/src/execution/runner.rs +++ b/crates/lean_vm/src/execution/runner.rs @@ -357,7 +357,7 @@ fn execute_bytecode_helper( "Not all WOTS hints were consumed" ); assert_eq!( - xmss_merkle_node_index * xmss::XMSS_DIGEST_SIZE, + xmss_merkle_node_index * xmss::XMSS_DIGEST_LEN, xmss_merkle_nodes.len(), "Not all XMSS merkle node hints were consumed" ); @@ -504,8 +504,8 @@ fn handle_parallel_batch( let fp_i = batch.batch_fp + (i + 1) * stride; let wots_sigs = &wots_signatures[wots_base + i * wots_per_iter..wots_base + (i + 1) * wots_per_iter]; let xmss_merkle_nodes_seg = &xmss_merkle_nodes[(xmss_merkle_node_base + i * xmss_merkle_node_per_iter) - * xmss::XMSS_DIGEST_SIZE - ..(xmss_merkle_node_base + (i + 1) * xmss_merkle_node_per_iter) * xmss::XMSS_DIGEST_SIZE]; + * xmss::XMSS_DIGEST_LEN + ..(xmss_merkle_node_base + (i + 1) * xmss_merkle_node_per_iter) * xmss::XMSS_DIGEST_LEN]; let merkle = &merkle_paths[merkle_base + i * merkle_per_iter..merkle_base + (i + 1) * merkle_per_iter]; let mut seg_trace = Trace::new(); let mut seg_pc = batch.batch_pc; diff --git a/crates/lean_vm/src/isa/hint.rs b/crates/lean_vm/src/isa/hint.rs index b98c6dfc5..dac9ba513 100644 --- a/crates/lean_vm/src/isa/hint.rs +++ b/crates/lean_vm/src/isa/hint.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; use std::fmt::{Display, Formatter}; use std::hash::Hash; use utils::{ToUsize, to_big_endian_in_field, to_little_endian_in_field}; -use xmss::{WOTS_SIG_SIZE_FE, XMSS_DIGEST_SIZE}; +use xmss::{WOTS_SIG_SIZE_FE, XMSS_DIGEST_LEN}; /// VM hints provide execution guidance and debugging information, but does not appear /// in the verified bytecode. @@ -230,13 +230,13 @@ impl CustomHint { Self::XmssMerkleNode => { let buf_ptr = args[0].read_value(ctx.memory, ctx.fp)?.to_usize(); let index = *ctx.hints.xmss_merkle_node_index; - let start = index * XMSS_DIGEST_SIZE; + let start = index * XMSS_DIGEST_LEN; assert!( - start + XMSS_DIGEST_SIZE <= ctx.hints.xmss_merkle_nodes.len(), + start + XMSS_DIGEST_LEN <= ctx.hints.xmss_merkle_nodes.len(), "hint_xmss_merkle_node: not enough merkle nodes (index={index})" ); ctx.memory - .set_slice(buf_ptr, &ctx.hints.xmss_merkle_nodes[start..start + XMSS_DIGEST_SIZE])?; + .set_slice(buf_ptr, &ctx.hints.xmss_merkle_nodes[start..start + XMSS_DIGEST_LEN])?; *ctx.hints.xmss_merkle_node_index += 1; } Self::Merkle => { diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 5e7e1253c..2018fece2 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -12,8 +12,7 @@ use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; use xmss::{ - LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, - XMSS_DIGEST_SIZE, + LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, XMSS_DIGEST_LEN, }; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; @@ -377,7 +376,7 @@ fn build_replacements( "MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER".to_string(), MERKLE_LEVELS_PER_CHUNK_FOR_SLOT.to_string(), ); - replacements.insert("XMSS_DIGEST_SIZE_PLACEHOLDER".to_string(), XMSS_DIGEST_SIZE.to_string()); + replacements.insert("XMSS_DIGEST_LEN_PLACEHOLDER".to_string(), XMSS_DIGEST_LEN.to_string()); // Bytecode zero eval replacements.insert( diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index e19f822c7..fe854ff05 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -10,10 +10,10 @@ MESSAGE_LEN = MESSAGE_LEN_PLACEHOLDER RANDOMNESS_LEN = RANDOMNESS_LEN_PLACEHOLDER PUBLIC_PARAM_LEN_FE = PUBLIC_PARAM_LEN_FE_PLACEHOLDER -XMSS_DIGEST = XMSS_DIGEST_SIZE_PLACEHOLDER -PUB_KEY_SIZE = XMSS_DIGEST + PUBLIC_PARAM_LEN_FE -PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST -WOTS_SIG_SIZE = RANDOMNESS_LEN + V * XMSS_DIGEST +XMSS_DIGEST_LEN = XMSS_DIGEST_LEN_PLACEHOLDER +PUB_KEY_SIZE = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE +PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST_LEN +WOTS_SIG_SIZE = RANDOMNESS_LEN + V * XMSS_DIGEST_LEN NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK @@ -73,7 +73,7 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # poseidon16_compress_hardcoded_left_4 without ever copying tweak prefixes into # per-hash buffers. - public_param = pub_key + XMSS_DIGEST + public_param = pub_key + XMSS_DIGEST_LEN randomness = signature chain_starts = signature + RANDOMNESS_LEN # NOTE: the merkle path is no longer part of `signature`. The prover delivers @@ -134,15 +134,15 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # i * XMSS_DIGEST. We over-allocate by (DIGEST_LEN - XMSS_DIGEST) trailing slots so # the half-output lookup of the LAST chain (which still reads 8 memory cells past # its 4-element digest) stays in bounds. - wots_public_key = Array(V * XMSS_DIGEST + (DIGEST_LEN - XMSS_DIGEST)) + wots_public_key = Array(V * XMSS_DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST_LEN)) chain_right = Array(DIGEST_LEN + 1) build_chain_right(public_param, chain_right) for i in unroll(0, V): num_hashes = (CHAIN_LENGTH - 1) - encoding[i] - chain_start = chain_starts + i * XMSS_DIGEST - chain_end = wots_public_key + i * XMSS_DIGEST + chain_start = chain_starts + i * XMSS_DIGEST_LEN + chain_end = wots_public_key + i * XMSS_DIGEST_LEN chain_i_tweaks = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN match_range( @@ -167,7 +167,7 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): @inline def copy_xmss_digest(src, dst): # Copy XMSS_DIGEST elements from src to dst (within a DIGEST_LEN-strided destination) - for k in unroll(0, XMSS_DIGEST): + for k in unroll(0, XMSS_DIGEST_LEN): dst[k] = src[k] return @@ -197,7 +197,7 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN poseidon16_compress_half_hardcoded_left_4(input, chain_right, output, first_tweak) else: - digests = Array(n * XMSS_DIGEST) + digests = Array(n * XMSS_DIGEST_LEN) # Hash 0: input → digests[0..4] first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN @@ -207,16 +207,16 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): for j in unroll(1, n - 1): cur_tweak = chain_i_tweaks + (starting_step + j) * TWEAK_LEN poseidon16_compress_half_hardcoded_left_4( - digests + (j - 1) * XMSS_DIGEST, + digests + (j - 1) * XMSS_DIGEST_LEN, chain_right, - digests + j * XMSS_DIGEST, + digests + j * XMSS_DIGEST_LEN, cur_tweak, ) # Final hash: digests[(n-2)*4..(n-1)*4] → output last_tweak = chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN poseidon16_compress_half_hardcoded_left_4( - digests + (n - 2) * XMSS_DIGEST, chain_right, output, last_tweak + digests + (n - 2) * XMSS_DIGEST_LEN, chain_right, output, last_tweak ) return @@ -281,7 +281,7 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk # half-output lookup at the digest still has 8 valid memory cells when the digest # lands at offset 4. The trailing 4 cells are vacuous (memory[buf+8..buf+12] = 0 # in write-once memory; the prover sets the unconstrained trace columns to 0). - BUF_SIZE_DEST = DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST) # 8 + 4 = 12 + BUF_SIZE_DEST = DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST_LEN) # 8 + 4 = 12 b0 = b % 2 r1 = (b - b0) / 2 @@ -297,38 +297,38 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk if b0 == 1: # state_in is the LEFT child copy_xmss_digest(state_in, buf0) - hint_xmss_merkle_node(buf0 + XMSS_DIGEST) + hint_xmss_merkle_node(buf0 + XMSS_DIGEST_LEN) else: # the merkle path neighbour is the LEFT child hint_xmss_merkle_node(buf0) - copy_xmss_digest(state_in, buf0 + XMSS_DIGEST) + copy_xmss_digest(state_in, buf0 + XMSS_DIGEST_LEN) # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4). # The path neighbour for level 1 is hinted into the OTHER slot of buf1. buf1 = Array(BUF_SIZE_DEST) if b1 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) - hint_xmss_merkle_node(buf1 + XMSS_DIGEST) + hint_xmss_merkle_node(buf1 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST, merkle_tweaks_chunk) + poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST_LEN, merkle_tweaks_chunk) hint_xmss_merkle_node(buf1) # Level 1 hash → buf2 buf2 = Array(BUF_SIZE_DEST) if b2 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) - hint_xmss_merkle_node(buf2 + XMSS_DIGEST) + hint_xmss_merkle_node(buf2 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 1 * TWEAK_LEN) hint_xmss_merkle_node(buf2) # Level 2 hash → buf3 buf3 = Array(BUF_SIZE_DEST) if b3 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) - hint_xmss_merkle_node(buf3 + XMSS_DIGEST) + hint_xmss_merkle_node(buf3 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 2 * TWEAK_LEN) hint_xmss_merkle_node(buf3) # Level 3 hash → state_out (digest always written at offset 0 since the next chunk diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 022cce733..52a0580ff 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -6,11 +6,11 @@ pub use wots::*; mod xmss; pub use xmss::*; -pub const XMSS_DIGEST_SIZE: usize = 4; +pub const XMSS_DIGEST_LEN: usize = 4; pub(crate) const TWEAK_LEN: usize = 2; type F = KoalaBear; -type Digest = [F; XMSS_DIGEST_SIZE]; +type Digest = [F; XMSS_DIGEST_LEN]; type PublicParam = [F; PUBLIC_PARAM_LEN_FE]; type Randomness = [F; RANDOMNESS_LEN_FE]; @@ -28,17 +28,17 @@ pub const PUBLIC_PARAM_LEN_FE: usize = 4; /// Length of the non-data prefix in either Poseidon input. Both inputs reserve the /// first `INPUT_PREFIX_LEN` field elements for metadata (tweak/zeros on the left, /// public_param on the right) and place the 4-element data digest right after it. -pub const INPUT_PREFIX_LEN: usize = 8 - XMSS_DIGEST_SIZE; // = 4 -pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_SIZE + PUBLIC_PARAM_LEN_FE; // = 9 +pub const INPUT_PREFIX_LEN: usize = 8 - XMSS_DIGEST_LEN; // = 4 +pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE; // = 9 -const _: () = assert!(INPUT_PREFIX_LEN + XMSS_DIGEST_SIZE == 8); +const _: () = assert!(INPUT_PREFIX_LEN + XMSS_DIGEST_LEN == 8); // Left layout (with tweak): [tweak(2) | zeros(2) | data(DIGEST_SIZE)] -const _: () = assert!(TWEAK_LEN + 2 + XMSS_DIGEST_SIZE == 8); +const _: () = assert!(TWEAK_LEN + 2 + XMSS_DIGEST_LEN == 8); /// In-memory layout consumed by `hint_wots`: just `randomness | chain_tips` (the WOTS /// part of an XMSS signature). The XMSS merkle path lives in a separate prover-side /// queue and is consumed by `hint_xmss_merkle_node`. -pub const WOTS_SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + V * XMSS_DIGEST_SIZE; +pub const WOTS_SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + V * XMSS_DIGEST_LEN; // Tweak: domain separation within each hash. pub(crate) const TWEAK_TYPE_CHAIN: usize = 0; @@ -81,8 +81,8 @@ pub(crate) fn build_left(tweak: [F; TWEAK_LEN], public_param: &PublicParam) -> [ /// Merkle RIGHT input: the two children of the parent node packed contiguously. pub(crate) fn build_right(left_child: &Digest, right_child: &Digest) -> [F; 8] { let mut right = [F::default(); 8]; - right[..XMSS_DIGEST_SIZE].copy_from_slice(left_child); - right[XMSS_DIGEST_SIZE..].copy_from_slice(right_child); + right[..XMSS_DIGEST_LEN].copy_from_slice(left_child); + right[XMSS_DIGEST_LEN..].copy_from_slice(right_child); right } @@ -91,7 +91,7 @@ pub(crate) fn build_left_chain(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { let mut left = [F::default(); 8]; left[..TWEAK_LEN].copy_from_slice(&tweak); // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) - left[8 - XMSS_DIGEST_SIZE..].copy_from_slice(data); + left[8 - XMSS_DIGEST_LEN..].copy_from_slice(data); left } diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 82ef5a3a5..66b368651 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -108,11 +108,11 @@ impl WotsPublicKey { for i in (0..V).step_by(2) { let mut chunk = [F::default(); 8]; - chunk[..XMSS_DIGEST_SIZE].copy_from_slice(&self.0[i]); - chunk[XMSS_DIGEST_SIZE..].copy_from_slice(&self.0[i + 1]); + chunk[..XMSS_DIGEST_LEN].copy_from_slice(&self.0[i]); + chunk[XMSS_DIGEST_LEN..].copy_from_slice(&self.0[i + 1]); state = poseidon16_compress_pair(&state, &chunk); } - state[..XMSS_DIGEST_SIZE].try_into().unwrap() + state[..XMSS_DIGEST_LEN].try_into().unwrap() } } @@ -129,7 +129,7 @@ pub fn iterate_hash( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left_chain(tweak, &acc); - poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_SIZE] + poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_LEN] .try_into() .unwrap() }) @@ -149,7 +149,7 @@ pub fn iterate_hash_with_poseidon_trace( (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); let left = build_left_chain(tweak, &acc); - poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..XMSS_DIGEST_SIZE] + poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..XMSS_DIGEST_LEN] .try_into() .unwrap() }) diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index 3e13306a8..f23c45e45 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -31,8 +31,8 @@ pub struct XmssPublicKey { impl XmssPublicKey { pub fn flaten(&self) -> [F; PUB_KEY_FLAT_SIZE] { let mut output = [F::default(); PUB_KEY_FLAT_SIZE]; - output[..XMSS_DIGEST_SIZE].copy_from_slice(&self.merkle_root); - output[XMSS_DIGEST_SIZE..].copy_from_slice(&self.public_param); + output[..XMSS_DIGEST_LEN].copy_from_slice(&self.merkle_root); + output[XMSS_DIGEST_LEN..].copy_from_slice(&self.public_param); output } } @@ -115,7 +115,7 @@ pub fn xmss_key_gen( }; let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &public_param); let poseidon_right = build_right(&left, &right); - poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..XMSS_DIGEST_SIZE] + poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..XMSS_DIGEST_LEN] .try_into() .unwrap() }) @@ -220,7 +220,7 @@ pub fn xmss_verify( } else { build_right(neighbour, ¤t_hash) }; - current_hash = poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_SIZE] + current_hash = poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_LEN] .try_into() .unwrap(); } From 1883d55f7170ae02b7e4dc27fb2137bb5340b008 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 19:56:55 +0200 Subject: [PATCH 22/66] w --- crates/rec_aggregation/xmss_aggregate.py | 118 ++++++++++++++++++----- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index fe854ff05..77a3ea210 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -101,33 +101,37 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): encoding_fe = Array(DIGEST_LEN) poseidon16_compress(pre_compressed, pp_input, encoding_fe) - encoding = Array(NUM_ENCODING_FE * 24 / W) + # Decompose the encoding into chunks of 2*W bits. Each chunk packs the chain step + # counts of two consecutive WOTS chains: chunk i = step_{2i} + CHAIN_LENGTH * step_{2i+1}. + # Compared to the per-W-bit decomposition, this halves the number of `match_range` + # dispatch sites in the chain-hash loop (one match per pair, with CHAIN_LENGTH^2 + # arms, instead of two matches per pair with CHAIN_LENGTH arms each). + encoding = Array(NUM_ENCODING_FE * 24 / (2 * W)) remaining = Array(NUM_ENCODING_FE) - # TODO: decompose by chunks of 2.w bits (or even 3.w bits) and use a big match on the w^2 (or w^3) possibilities - hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, W) + hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, 2 * W) # check that the decomposition is correct for i in unroll(0, NUM_ENCODING_FE): - for j in unroll(0, 24 / W): - assert encoding[i * (24 / W) + j] < CHAIN_LENGTH + for j in unroll(0, 24 / (2 * W)): + assert encoding[i * (24 / (2 * W)) + j] < CHAIN_LENGTH**2 assert remaining[i] < 2**7 - 1 partial_sum: Mut = remaining[i] * 2**24 - for j in unroll(0, 24 / W): - partial_sum += encoding[i * (24 / W) + j] * CHAIN_LENGTH**j + for j in unroll(0, 24 / (2 * W)): + partial_sum += encoding[i * (24 / (2 * W)) + j] * (CHAIN_LENGTH**2) ** j assert partial_sum == encoding_fe[i] - # we need to check the target sum - target_sum: Mut = encoding[0] - for i in unroll(1, V): - target_sum += encoding[i] - assert target_sum == TARGET_SUM - - # grinding - for i in unroll(V, V + V_GRINDING): - assert encoding[i] == CHAIN_LENGTH - 1 + # grinding (V and V_GRINDING must both be even so that grinding values land on + # whole pair-encoded slots) + debug_assert(V % 2 == 0) + debug_assert(V_GRINDING % 2 == 0) + for i in unroll(V / 2, (V + V_GRINDING) / 2): + # Both raw chain counts in this pair must equal CHAIN_LENGTH - 1, i.e. + # encoding[i] == (CHAIN_LENGTH - 1) + CHAIN_LENGTH * (CHAIN_LENGTH - 1) + # == CHAIN_LENGTH**2 - 1. + assert encoding[i] == CHAIN_LENGTH**2 - 1 # 2) Chain hashes -> recover WOTS public key # `wots_public_key` is packed at stride XMSS_DIGEST (= 4): pk[i] lives at offset @@ -139,19 +143,37 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): chain_right = Array(DIGEST_LEN + 1) build_chain_right(public_param, chain_right) - for i in unroll(0, V): - num_hashes = (CHAIN_LENGTH - 1) - encoding[i] - chain_start = chain_starts + i * XMSS_DIGEST_LEN - chain_end = wots_public_key + i * XMSS_DIGEST_LEN - chain_i_tweaks = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + i * CHAIN_LENGTH * TWEAK_LEN + # Each pair (chain 2*i, chain 2*i+1) is dispatched in a single match_range with + # CHAIN_LENGTH^2 arms. The per-pair compile-time chain-step sum is written into + # `pair_sum_ptr` by `chain_hash_pair` and accumulated at runtime into `target_sum`. + target_sum: Mut = 0 + for i in unroll(0, V / 2): + chain_start_a = chain_starts + (2 * i) * XMSS_DIGEST_LEN + chain_start_b = chain_starts + (2 * i + 1) * XMSS_DIGEST_LEN + chain_end_a = wots_public_key + (2 * i) * XMSS_DIGEST_LEN + chain_end_b = wots_public_key + (2 * i + 1) * XMSS_DIGEST_LEN + tweaks_a = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i) * CHAIN_LENGTH * TWEAK_LEN + tweaks_b = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i + 1) * CHAIN_LENGTH * TWEAK_LEN + pair_sum_ptr = Array(1) match_range( - num_hashes, - range(0, 1), - lambda _: copy_xmss_digest(chain_start, chain_end), - range(1, CHAIN_LENGTH), - lambda n: chain_hash_pa(chain_start, n, chain_end, chain_i_tweaks, chain_right), + encoding[i], + range(0, CHAIN_LENGTH**2), + lambda n: chain_hash_pair( + chain_start_a, + chain_start_b, + n, + chain_end_a, + chain_end_b, + tweaks_a, + tweaks_b, + chain_right, + pair_sum_ptr, + ), ) + target_sum += pair_sum_ptr[0] + + assert target_sum == TARGET_SUM # 3) Hash WOTS public key (the LEFT-input tweak slot is baked in via TWEAK_TABLE_ADDR) expected_leaf = wots_pk_hash(wots_public_key, public_param) @@ -221,6 +243,50 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): return +@inline +def chain_hash_pair( + input_a, + input_b, + n, + output_a, + output_b, + tweaks_a, + tweaks_b, + chain_right, + pair_sum_ptr, +): + # Pair-encoded chain hash. `n` is a compile-time constant in [0, CHAIN_LENGTH^2) + # supplied by `match_range`. It packs two raw chain step counts: + # raw_a = n % CHAIN_LENGTH (chain 2*i) + # raw_b = (n - raw_a) / CHAIN_LENGTH (chain 2*i + 1) + # so the inverse is n = raw_a + CHAIN_LENGTH * raw_b, matching the bit layout + # produced by `hint_decompose_bits_xmss(..., 2 * W)`. + # + # We dispatch both chains from a single match arm, halving the number of + # match-arm sites compared to the per-chain version. Both chains share the + # same RIGHT poseidon input ([pp(4) | zeros(4)] in `chain_right`). + # + # `pair_sum_ptr[0]` receives the compile-time constant `raw_a + raw_b`, which + # the caller accumulates into the runtime `target_sum`. + raw_a = n % CHAIN_LENGTH + raw_b = (n - raw_a) / CHAIN_LENGTH + num_hashes_a = (CHAIN_LENGTH - 1) - raw_a + num_hashes_b = (CHAIN_LENGTH - 1) - raw_b + + if num_hashes_a == 0: + copy_xmss_digest(input_a, output_a) + else: + chain_hash_pa(input_a, num_hashes_a, output_a, tweaks_a, chain_right) + + if num_hashes_b == 0: + copy_xmss_digest(input_b, output_b) + else: + chain_hash_pa(input_b, num_hashes_b, output_b, tweaks_b, chain_right) + + pair_sum_ptr[0] = raw_a + raw_b + return + + @inline def wots_pk_hash(wots_public_key, public_param): # Sponge-like hash of V public key digests, packed at stride XMSS_DIGEST = 4 in From 1cda8125ba52d2fbc4c7d0499484a7a7fc176eb9 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 20:59:48 +0200 Subject: [PATCH 23/66] make 1 column virtual --- crates/lean_vm/src/tables/poseidon_16/mod.rs | 67 ++++++------------- .../src/tables/poseidon_16/trace_gen.rs | 13 ++-- src/prove_poseidons.rs | 8 +-- 3 files changed, 34 insertions(+), 54 deletions(-) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 3443ae3f3..2dbc82307 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -108,18 +108,18 @@ pub const POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT: usize = 1 << 2; pub const POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT: usize = 1 << 3; pub const POSEIDON_16_COL_FLAG: ColIndex = 0; -pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = 1; -pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 2; -pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; -pub const POSEIDON_16_COL_FLAG_HALF_OUTPUT: ColIndex = 4; -pub const POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4: ColIndex = 5; -pub const POSEIDON_16_COL_OFFSET_HARDCODED: ColIndex = 6; -pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST: ColIndex = 7; -pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND: ColIndex = 8; -pub const POSEIDON_16_COL_INPUT_START: ColIndex = 9; +pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 1; +pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 2; +pub const POSEIDON_16_COL_FLAG_HALF_OUTPUT: ColIndex = 3; +pub const POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4: ColIndex = 4; +pub const POSEIDON_16_COL_OFFSET_HARDCODED: ColIndex = 5; +pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST: ColIndex = 6; +pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND: ColIndex = 7; +pub const POSEIDON_16_COL_INPUT_START: ColIndex = 8; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; -/// Non-committed columns: -pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16(); +/// Non-committed columns ("virtual"): +pub const POSEIDON_16_COL_INDEX_INPUT_LEFT: ColIndex = num_cols_poseidon_16(); +pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16() + 1; pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; @@ -236,7 +236,6 @@ impl TableT for Poseidon16Precompile { let offset_hardcoded = hardcoded_left_4.unwrap_or(0); trace.columns[POSEIDON_16_COL_FLAG].push(F::ONE); - trace.columns[POSEIDON_16_COL_INDEX_INPUT_LEFT].push(arg_a); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); @@ -248,6 +247,7 @@ impl TableT for Poseidon16Precompile { trace.columns[POSEIDON_16_COL_INPUT_START + i].push(*value); } // Non-committed columns + trace.columns[POSEIDON_16_COL_INDEX_INPUT_LEFT].push(arg_a); let precompile_data = POSEIDON_PRECOMPILE_DATA + POSEIDON_HALF_OUTPUT_SHIFT * (half_output as usize) + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * (flag_hardcoded as usize) @@ -272,7 +272,7 @@ impl Air for Poseidon16Precompile { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 80 + BUS as usize + 79 } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -291,50 +291,27 @@ impl Air for Poseidon16Precompile { * cols.offset_hardcoded * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT); + // effective_index_left_first = index_a * (1 - flag_hardcoded_left_4) + offset * flag_hardcoded_left_4 + let index_a = cols.effective_index_left_second + - (AB::IF::ONE - cols.flag_hardcoded_left_4) * AB::F::from_usize(HALF_DIGEST_LEN); + // Bus data: [precompile_data, a, b, res] if BUS { builder.eval_virtual_column(eval_virtual_bus_column::( extra_data, cols.flag, - &[ - precompile_data_reconstructed, - cols.index_a, - cols.index_b, - cols.index_res, - ], + &[precompile_data_reconstructed, index_a, cols.index_b, cols.index_res], )); } else { builder.declare_values(std::slice::from_ref(&cols.flag)); - builder.declare_values(&[ - precompile_data_reconstructed, - cols.index_a, - cols.index_b, - cols.index_res, - ]); + builder.declare_values(&[precompile_data_reconstructed, index_a, cols.index_b, cols.index_res]); } builder.assert_bool(cols.flag); builder.assert_bool(cols.flag_half_output); builder.assert_bool(cols.flag_hardcoded_left_4); - // effective_index_left_first = index_a * (1 - flag) + offset * flag - builder.assert_eq( - cols.effective_index_left_first, - cols.index_a * (AB::IF::ONE - cols.flag_hardcoded_left_4) - + cols.offset_hardcoded * cols.flag_hardcoded_left_4, - ); - - // effective_index_left_second = index_a + (1 - flag_hardcoded_left_4) * 4 - // - When the hardcoded flag is disabled (legacy path) the second half of the left - // input is read from m[index_a + 4 .. index_a + 8] (the upper half of an 8-element - // chunk pointed to by index_a). - // - When the hardcoded flag is set, only a 4-element data digest is read from - // m[index_a .. index_a + 4]; the first 4 inputs are supplied from the hardcoded - // m[offset .. offset + 4]. - builder.assert_eq( - cols.effective_index_left_second, - cols.index_a + (AB::IF::ONE - cols.flag_hardcoded_left_4) * AB::F::from_usize(HALF_DIGEST_LEN), - ); + builder.assert_zero(cols.flag_hardcoded_left_4 * (cols.offset_hardcoded - cols.effective_index_left_first)); eval_poseidon1_16(builder, &cols) } @@ -344,7 +321,6 @@ impl Air for Poseidon16Precompile { #[derive(Debug)] pub(super) struct Poseidon1Cols16 { pub flag: T, - pub index_a: T, pub index_b: T, pub index_res: T, pub flag_half_output: T, @@ -425,7 +401,8 @@ pub const fn num_cols_poseidon_16() -> usize { } pub const fn num_cols_total_poseidon_16() -> usize { - num_cols_poseidon_16() + 1 // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA + // +2 for non-committed columns: POSEIDON_16_COL_INDEX_INPUT_LEFT, POSEIDON_16_COL_PRECOMPILE_DATA + num_cols_poseidon_16() + 2 } #[inline] diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index 59b3f2bb7..81885ad99 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -2,7 +2,10 @@ use tracing::instrument; use crate::{ F, ZERO_VEC_PTR, - tables::{HALF_DIGEST_LEN, Poseidon1Cols16, WIDTH, num_cols_poseidon_16, num_cols_total_poseidon_16}, + tables::{ + HALF_DIGEST_LEN, POSEIDON_16_COL_INDEX_INPUT_LEFT, POSEIDON_16_COL_PRECOMPILE_DATA, POSEIDON_PRECOMPILE_DATA, + Poseidon1Cols16, WIDTH, num_cols_poseidon_16, num_cols_total_poseidon_16, + }, }; use backend::*; @@ -42,7 +45,7 @@ pub fn fill_trace_poseidon_16(trace: &mut [Vec]) { } pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { - // +1 for non-committed POSEIDON_16_COL_PRECOMPILE_DATA + // +2 for non-committed POSEIDON_16_COL_INDEX_INPUT_LEFT, POSEIDON_16_COL_PRECOMPILE_DATA let n_cols_total = num_cols_total_poseidon_16(); let mut row = vec![F::ZERO; n_cols_total]; let ptrs: Vec<*mut F> = (0..num_cols_poseidon_16()) @@ -52,7 +55,6 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { let perm: &mut Poseidon1Cols16<&mut F> = unsafe { &mut *(ptrs.as_ptr() as *mut Poseidon1Cols16<&mut F>) }; perm.inputs.iter_mut().for_each(|x| **x = F::ZERO); *perm.flag = F::ZERO; - *perm.index_a = F::from_usize(ZERO_VEC_PTR); *perm.index_b = F::from_usize(ZERO_VEC_PTR); *perm.index_res = F::from_usize(null_hash_ptr); *perm.flag_half_output = F::ZERO; @@ -60,8 +62,9 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { *perm.offset_hardcoded = F::ZERO; *perm.effective_index_left_first = F::from_usize(ZERO_VEC_PTR); *perm.effective_index_left_second = F::from_usize(ZERO_VEC_PTR + HALF_DIGEST_LEN); - // Non-committed column for padding rows - row[num_cols_poseidon_16()] = F::from_usize(crate::POSEIDON_PRECOMPILE_DATA); + // Non-committed columns for padding rows + row[POSEIDON_16_COL_INDEX_INPUT_LEFT] = F::from_usize(ZERO_VEC_PTR); + row[POSEIDON_16_COL_PRECOMPILE_DATA] = F::from_usize(POSEIDON_PRECOMPILE_DATA); generate_trace_rows_for_perm(perm); row diff --git a/src/prove_poseidons.rs b/src/prove_poseidons.rs index 5369b9bff..151aff0ff 100644 --- a/src/prove_poseidons.rs +++ b/src/prove_poseidons.rs @@ -2,9 +2,9 @@ use air::{check_air_validity, prove_air, verify_air}; use backend::*; use lean_vm::{ EF, ExtraDataForBuses, F, HALF_DIGEST_LEN, POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST, - POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND, POSEIDON_16_COL_FLAG, POSEIDON_16_COL_INDEX_INPUT_LEFT, - POSEIDON_16_COL_INDEX_INPUT_RES, POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, - Poseidon16Precompile, ZERO_VEC_PTR, fill_trace_poseidon_16, num_cols_poseidon_16, + POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND, POSEIDON_16_COL_FLAG, POSEIDON_16_COL_INDEX_INPUT_RES, + POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, Poseidon16Precompile, ZERO_VEC_PTR, + fill_trace_poseidon_16, num_cols_poseidon_16, }; use rand::{RngExt, SeedableRng, rngs::StdRng}; use utils::{ @@ -31,7 +31,7 @@ pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { } trace[POSEIDON_16_COL_FLAG] = (0..n_rows).map(|_| F::ONE).collect(); trace[POSEIDON_16_COL_INDEX_INPUT_RES] = (0..n_rows).map(|_| F::ZERO).collect(); // useless - trace[POSEIDON_16_COL_INDEX_INPUT_LEFT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); + // INDEX_INPUT_LEFT is non-committed, so it does not exist in this committed-only benchmark trace. trace[POSEIDON_16_COL_INDEX_INPUT_RIGHT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); // Hardcoded-left-4 feature disabled: effective_index_first = index_left, second = index_left + 4 trace[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); From 4c7e974d128663a0f6a747cdadbbdc98c5086051 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 22:10:42 +0200 Subject: [PATCH 24/66] wip --- crates/rec_aggregation/xmss_aggregate.py | 135 +++++++++++++++-------- crates/xmss/src/wots.rs | 13 ++- 2 files changed, 102 insertions(+), 46 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 77a3ea210..bf05a31a2 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -14,6 +14,11 @@ PUB_KEY_SIZE = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST_LEN WOTS_SIG_SIZE = RANDOMNESS_LEN + V * XMSS_DIGEST_LEN +# wots_public_key pair stride: each pair occupies 10 cells +# `[leading_0 | tip_a(4) | tip_b(4) | trailing_0]`. The two zero cells are slack +# used by chain_hash_pair's copy_5 calls — they receive the harmless 5th-cell +# spillover so copy_5 never collides with adjacent chain writes. +WOTS_PK_PAIR_STRIDE = 2 + 2 * XMSS_DIGEST_LEN NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK @@ -134,11 +139,15 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): assert encoding[i] == CHAIN_LENGTH**2 - 1 # 2) Chain hashes -> recover WOTS public key - # `wots_public_key` is packed at stride XMSS_DIGEST (= 4): pk[i] lives at offset - # i * XMSS_DIGEST. We over-allocate by (DIGEST_LEN - XMSS_DIGEST) trailing slots so - # the half-output lookup of the LAST chain (which still reads 8 memory cells past - # its 4-element digest) stays in bounds. - wots_public_key = Array(V * XMSS_DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST_LEN)) + # `wots_public_key` layout: V/2 pairs of stride WOTS_PK_PAIR_STRIDE = 10. Each pair + # is `[leading_0(1) | tip_a(4) | tip_b(4) | trailing_0(1)]`. The leading and trailing + # zero cells are slack used by `chain_hash_pair`'s copy_5 calls in the n==0 case + # (the 5th cell that copy_5 always writes lands in slack, never in the adjacent + # chain's slot). The wots_pk_hash absorbs 8 zeros first (matching the Rust side), + # then iterates over pairs reading 8 cells `[tip_a | tip_b]` per pair from offset + # `i * WOTS_PK_PAIR_STRIDE + 1`. Plus 4 trailing slots so the LAST chain b's + # half-output lookup stays in bounds. + wots_public_key = Array((V / 2) * WOTS_PK_PAIR_STRIDE + 4) chain_right = Array(DIGEST_LEN + 1) build_chain_right(public_param, chain_right) @@ -150,8 +159,8 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): for i in unroll(0, V / 2): chain_start_a = chain_starts + (2 * i) * XMSS_DIGEST_LEN chain_start_b = chain_starts + (2 * i + 1) * XMSS_DIGEST_LEN - chain_end_a = wots_public_key + (2 * i) * XMSS_DIGEST_LEN - chain_end_b = wots_public_key + (2 * i + 1) * XMSS_DIGEST_LEN + chain_end_a = wots_public_key + i * WOTS_PK_PAIR_STRIDE + 1 + chain_end_b = wots_public_key + i * WOTS_PK_PAIR_STRIDE + 1 + XMSS_DIGEST_LEN tweaks_a = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i) * CHAIN_LENGTH * TWEAK_LEN tweaks_b = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i + 1) * CHAIN_LENGTH * TWEAK_LEN pair_sum_ptr = Array(1) @@ -186,14 +195,6 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): return -@inline -def copy_xmss_digest(src, dst): - # Copy XMSS_DIGEST elements from src to dst (within a DIGEST_LEN-strided destination) - for k in unroll(0, XMSS_DIGEST_LEN): - dst[k] = src[k] - return - - @inline def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): # Chain of n half-output poseidon compressions: @@ -274,12 +275,19 @@ def chain_hash_pair( num_hashes_b = (CHAIN_LENGTH - 1) - raw_b if num_hashes_a == 0: - copy_xmss_digest(input_a, output_a) + # Single-cycle copy_5: writes 5 cells [output_a[-1..4]]. The 5th cell is the + # leading-zero slack of this pair (output_a points to pair_start + 1, so + # output_a - 1 = pair_start = leading_0). The slack cell receives input_a[-1] + # (a signature byte) — harmless because nothing else reads it. + copy_5(input_a - 1, output_a - 1) else: chain_hash_pa(input_a, num_hashes_a, output_a, tweaks_a, chain_right) if num_hashes_b == 0: - copy_xmss_digest(input_b, output_b) + # Single-cycle copy_5: writes 5 cells [output_b[0..4]]. The 5th cell lands in + # the trailing-zero slack of this pair (output_b = pair_start + 5; output_b + 4 + # = pair_start + 9 = trailing_0). It receives input_b[4] — harmless. + copy_5(input_b, output_b) else: chain_hash_pa(input_b, num_hashes_b, output_b, tweaks_b, chain_right) @@ -289,33 +297,37 @@ def chain_hash_pair( @inline def wots_pk_hash(wots_public_key, public_param): - # Sponge-like hash of V public key digests, packed at stride XMSS_DIGEST = 4 in - # `wots_public_key`. Each 8-FE sponge chunk = pk[2i] || pk[2i+1] is therefore - # already contiguous in memory at `wots_public_key + i * DIGEST_LEN` — we feed - # those pointers straight into poseidon, no copy / no `chunks` array. + # Sponge-like hash of V public key digests, packed at stride WOTS_PK_PAIR_STRIDE + # in `wots_public_key`. Each pair occupies 10 cells: + # `[leading_0 | tip_a(4) | tip_b(4) | trailing_0]`. The 8-FE sponge chunk for + # pair i is `[tip_a | tip_b]` at offset `i * WOTS_PK_PAIR_STRIDE + 1` (skipping + # the per-pair leading_0 slack). # - # IV = [tweak(2) | 00 | pp(4)] (matches the LEFT-input convention for - # poseidon16_compress_hardcoded_left_4: the first 4 FE come from the wots_pk - # tweak slot at the compile-time address TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET, - # and the next 4 FE come from `public_param` at runtime). + # IV = [tweak(2) | 00 | pp(4)]. We absorb 8 zeros first via hardcoded_left_4 + # against `ZERO_VEC_PTR` (matching the Rust side's `WotsPublicKey::hash`), then + # iterate over pairs in a uniform `for i in 0..N_CHUNKS` loop. The leading + # zero-absorb gives us a uniform loop and lets each pair's slack cells live in + # the buffer (used by `chain_hash_pair` copy_5 calls). # V must be even. N_CHUNKS = V / 2 - states = Array(N_CHUNKS * DIGEST_LEN) - # First hash: LEFT input is [tweak(2) | 00 | pp(4)]; the precompile reads - # [tweak(2) | 00] from the wots_pk tweak slot and [pp(4)] from `public_param`. - # RIGHT input is the first 8-FE chunk (pk[0] || pk[1]) at wots_public_key + 0. + # +1 chunk for the initial zero-absorb output. + states = Array((N_CHUNKS + 1) * DIGEST_LEN) + # Initial absorb of 8 zeros: state[0..8] = poseidon([tweak(2) | 00 | pp(4)], 0(8)). + # The precompile reads [tweak(2) | 00] from the wots_pk tweak slot at the + # compile-time address `TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET` and `[pp(4)]` + # from the runtime `public_param` pointer. poseidon16_compress_hardcoded_left_4( - public_param, wots_public_key, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET + public_param, ZERO_VEC_PTR, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET ) - for i in unroll(1, N_CHUNKS): + for i in unroll(0, N_CHUNKS): poseidon16_compress( - states + (i - 1) * DIGEST_LEN, - wots_public_key + i * DIGEST_LEN, states + i * DIGEST_LEN, + wots_public_key + i * WOTS_PK_PAIR_STRIDE + 1, + states + (i + 1) * DIGEST_LEN, ) - return states + (N_CHUNKS - 1) * DIGEST_LEN + return states + N_CHUNKS * DIGEST_LEN @inline @@ -357,17 +369,34 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk r3 = (r2 - b2) / 2 b3 = r3 % 2 - # Level 0 input: copy state_in into buf0 (we don't know its statically); hint the - # level-0 merkle path neighbour into the OTHER slot. - buf0 = Array(DIGEST_LEN) + # Level 0 input: copy state_in into buf0 (we don't know its address statically); + # hint the level-0 merkle path neighbour into the OTHER slot. + # + # We use copy_5 (1 dot_product_ee precompile call) instead of a 4-write loop in + # both branches. copy_5 writes 5 FE, and dot_product_ee's runtime helper also + # READS all 5 source elements via solve_unknowns, so state_in needs an extra + # readable element on the side opposite the hint slot: + # - b0 == 1: copy_5(state_in - 1, buf0 - 1). Source slack at state_in[-1], + # destination slack at buf0[-1]. state_in[-1] is initialized by the + # previous chunk's `state_out[7] = 0` write (or by `wots_pk_hash`'s + # full output for chunk 0, or by the leading-slot init in + # `xmss_merkle_verify` for chunk 1). + # - b0 == 0: copy_5(state_in, buf0 + 4). Source slack at state_in[4], + # destination slack at buf0[8]. state_in[4] is initialized by the + # previous chunk's `state_out[4] = 0` write (or by full output for + # chunk 0). + # buf0 is over-allocated by 2 (one slack at each end) so a single allocation + # serves both branches. + buf0_alloc = Array(DIGEST_LEN + 2) # 10 elements + buf0 = buf0_alloc + 1 # logical positions [-1..8] if b0 == 1: - # state_in is the LEFT child - copy_xmss_digest(state_in, buf0) + # state_in is the LEFT child → state_in[0..4] lands at buf0[0..4]. + copy_5(state_in - 1, buf0 - 1) hint_xmss_merkle_node(buf0 + XMSS_DIGEST_LEN) else: - # the merkle path neighbour is the LEFT child + # state_in is the RIGHT child → state_in[0..4] lands at buf0[4..8]. hint_xmss_merkle_node(buf0) - copy_xmss_digest(state_in, buf0 + XMSS_DIGEST_LEN) + copy_5(state_in, buf0 + XMSS_DIGEST_LEN) # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4). # The path neighbour for level 1 is hinted into the OTHER slot of buf1. @@ -399,13 +428,24 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk # Level 3 hash → state_out (digest always written at offset 0 since the next chunk # reads state_out as a 4-element pointer regardless). + # The next chunk's level-0 copy_5 reads `state_in[4]` (b0 == 0) or `state_in[-1]` + # (b0 == 1), which lands in this chunk's `state_out[4]` / `state_out[7]`. These + # cells are unwritten by this poseidon (half_output only writes [0..4]); the + # extension_op `solve_unknowns` "copy_5" fast path handles undefined cells + # cell-by-cell and sets both source/dest to zero — no explicit writes needed. poseidon16_compress_half_hardcoded_left_4(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return @inline def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, merkle_tweaks): - states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) + # 1 extra leading slot, initialized to 0, so the b0 == 1 copy_5 in chunk 1 of + # do_4_merkle_levels can read `state_in[-1] = states[-1]` (= the leading slot). + # For chunks j > 1, `state_in[-1]` lands in the previous chunk's state_out[7] + # which is initialized by the explicit write at the end of do_4_merkle_levels. + states_alloc = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN + 1) + states = states_alloc + 1 + states_alloc[0] = 0 # First chunk match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, states, public_param, merkle_tweaks)) @@ -440,6 +480,13 @@ def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, ), ) - # Assert computed root == expected (first XMSS_DIGEST elements) - copy_xmss_digest(last_output, expected_root) + # Assert computed root == expected (first XMSS_DIGEST elements). + # Single-cycle copy_5: cells 0..3 verify the digest against the merkle root in + # `expected_root` (the actual assertion). Cell 4 of `last_output` is undefined + # (do_4_merkle_levels no longer pre-writes the slack), and cell 4 of expected_root + # is `public_param[0]` (defined in pub_mem). The extension_op `solve_unknowns` + # copy_5 fast path handles this cell-by-cell: source unknown + dest known → + # propagate dest into source. Memory[last_output[4]] gets set to public_param[0], + # harmless because nothing else reads it. + copy_5(last_output, expected_root) return diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 66b368651..35f28e817 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -93,8 +93,12 @@ impl WotsSignature { impl WotsPublicKey { /// Sponge-like hash of V public key digests. - /// IV = [tweak(2) | 00(2) | pp(4)], then ingest 8 FE per step (2 digests at a time). - /// The IV layout matches the LEFT-input convention for poseidon16_compress_hardcoded_left_4 + /// IV = [tweak(2) | 00(2) | pp(4)]; first absorb 8 zeros, then ingest 8 FE per step + /// (2 digests at a time). The leading zero-absorb gives the SNARK-side a uniform + /// per-pair loop starting at i=0 and lets each pair live in a 10-FE slot + /// `[leading_0 | tip_a | tip_b | trailing_0]`, with the leading/trailing zero cells + /// serving as copy_5 slack in `chain_hash_pair`. + /// The IV layout matches the LEFT-input convention for `poseidon16_compress_hardcoded_left_4` /// — `[tweak(2) | 00]` is read straight from the wots_pk tweak slot, and `[pp(4)]` lives at /// the runtime public_param pointer. Final output truncated to DIGEST_SIZE (4 FE). pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { @@ -106,6 +110,11 @@ impl WotsPublicKey { // state[2..4] = 00 (default) state[4..4 + PUBLIC_PARAM_LEN_FE].copy_from_slice(&public_param); + // Initial absorb of 8 zeros (matches the SNARK's `poseidon16_compress_hardcoded_left_4` + // call against `ZERO_VEC_PTR` in `wots_pk_hash`). + let zeros = [F::ZERO; 8]; + state = poseidon16_compress_pair(&state, &zeros); + for i in (0..V).step_by(2) { let mut chunk = [F::default(); 8]; chunk[..XMSS_DIGEST_LEN].copy_from_slice(&self.0[i]); From 2139ece0100fcb0c3c8e16e1a391ae45fa91f39e Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 22:18:44 +0200 Subject: [PATCH 25/66] w --- README.md | 2 +- src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a14a6a50b..c8c8a8af2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Machine: M4 Max 48GB (CPU only) ### XMSS aggregation ```bash -cargo run --release -- xmss --n-signatures 1400 +cargo run --release -- xmss --n-signatures 1475 ``` | WHIR rate \ regime | Proven | Conjectured | diff --git a/src/main.rs b/src/main.rs index 0320717d0..1b097a931 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ fn main() { raw_xmss: 0, children: vec![ AggregationTopology { - raw_xmss: 700, + raw_xmss: 740, children: vec![], log_inv_rate, }; @@ -90,7 +90,7 @@ fn main() { raw_xmss: 25, children: vec![ AggregationTopology { - raw_xmss: 1400, + raw_xmss: 1475, children: vec![], log_inv_rate: 1, }; @@ -104,7 +104,7 @@ fn main() { raw_xmss: 0, children: vec![ AggregationTopology { - raw_xmss: 1400, + raw_xmss: 1475, children: vec![], log_inv_rate: 2, }; From 21265bb21a4ee980d7a8e7e5e595eeda2b821a67 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 9 Apr 2026 22:31:26 +0200 Subject: [PATCH 26/66] w --- crates/rec_aggregation/xmss_aggregate.py | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index bf05a31a2..d00ab3cff 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -439,13 +439,21 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk @inline def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, merkle_tweaks): - # 1 extra leading slot, initialized to 0, so the b0 == 1 copy_5 in chunk 1 of - # do_4_merkle_levels can read `state_in[-1] = states[-1]` (= the leading slot). - # For chunks j > 1, `state_in[-1]` lands in the previous chunk's state_out[7] - # which is initialized by the explicit write at the end of do_4_merkle_levels. - states_alloc = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN + 1) + # Stride (XMSS_DIGEST_LEN + 1) = 5 per chunk: 4 digest cells written by the + # half-output Poseidon + 1 slack cell. The slack cell holds harmless writes + # from the next chunk's level-0 copy_5 (which reads `state_in[4]` for b0 == 0 + # or `state_in[-1]` for b0 == 1; with stride 5 both reads land in an adjacent + # chunk's slack cell, never colliding with a written digest cell). + # + # Allocation = 1 leading slack + (N-1) chunks × 5 cells + 4 trailing slack (for + # the LAST stored chunk's half-output Poseidon lookup which reads 8 cells past + # the storage start) = 5 * N_MERKLE_CHUNKS cells. + # + # The leading slack cell does NOT need an explicit `= 0` write — the execute + # fix in `solve_unknowns` handles undefined source/dest cells via the copy_5 + # fast path by setting both to zero. + states_alloc = Array(DIM * N_MERKLE_CHUNKS) states = states_alloc + 1 - states_alloc[0] = 0 # First chunk match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, states, public_param, merkle_tweaks)) @@ -453,7 +461,7 @@ def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, state_indexes = Array(N_MERKLE_CHUNKS - 1) state_indexes[0] = states for j in unroll(1, N_MERKLE_CHUNKS - 1): - state_indexes[j] = state_indexes[j - 1] + DIGEST_LEN + state_indexes[j] = state_indexes[j - 1] + DIM match_range( merkle_chunks[j], range(0, 16), @@ -466,8 +474,8 @@ def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, ), ) - # Last chunk: write to temp, then assert match with expected_root (write-once) - last_output = Array(DIGEST_LEN) + # Last chunk: write to a 5-cell buffer (4 digest cells + 1 slack for copy_5). + last_output = Array(DIM) match_range( merkle_chunks[N_MERKLE_CHUNKS - 1], range(0, 16), From 61ef5e3bfa4d23099e1031bd947219c621254d95 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 11:09:40 +0200 Subject: [PATCH 27/66] remove V_grinding --- crates/rec_aggregation/src/compilation.rs | 3 +- crates/rec_aggregation/xmss_aggregate.py | 14 +---- crates/xmss/params.md | 72 ----------------------- crates/xmss/src/lib.rs | 1 - crates/xmss/src/wots.rs | 8 +-- 5 files changed, 6 insertions(+), 92 deletions(-) delete mode 100644 crates/xmss/params.md diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 2018fece2..34925df90 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -12,7 +12,7 @@ use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; use xmss::{ - LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W, XMSS_DIGEST_LEN, + LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, W, XMSS_DIGEST_LEN, }; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; @@ -362,7 +362,6 @@ fn build_replacements( // XMSS-specific replacements replacements.insert("V_PLACEHOLDER".to_string(), V.to_string()); - replacements.insert("V_GRINDING_PLACEHOLDER".to_string(), V_GRINDING.to_string()); replacements.insert("W_PLACEHOLDER".to_string(), W.to_string()); replacements.insert("TARGET_SUM_PLACEHOLDER".to_string(), TARGET_SUM.to_string()); replacements.insert("LOG_LIFETIME_PLACEHOLDER".to_string(), LOG_LIFETIME.to_string()); diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index d00ab3cff..51f960104 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -2,7 +2,6 @@ from utils import * V = V_PLACEHOLDER -V_GRINDING = V_GRINDING_PLACEHOLDER W = W_PLACEHOLDER CHAIN_LENGTH = 2**W TARGET_SUM = TARGET_SUM_PLACEHOLDER @@ -19,7 +18,7 @@ # used by chain_hash_pair's copy_5 calls — they receive the harmless 5th-cell # spillover so copy_5 never collides with adjacent chain writes. WOTS_PK_PAIR_STRIDE = 2 + 2 * XMSS_DIGEST_LEN -NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) +NUM_ENCODING_FE = div_ceil(V, (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK TWEAK_TABLE_ADDR = TWEAK_TABLE_ADDR_PLACEHOLDER @@ -128,15 +127,7 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): partial_sum += encoding[i * (24 / (2 * W)) + j] * (CHAIN_LENGTH**2) ** j assert partial_sum == encoding_fe[i] - # grinding (V and V_GRINDING must both be even so that grinding values land on - # whole pair-encoded slots) - debug_assert(V % 2 == 0) - debug_assert(V_GRINDING % 2 == 0) - for i in unroll(V / 2, (V + V_GRINDING) / 2): - # Both raw chain counts in this pair must equal CHAIN_LENGTH - 1, i.e. - # encoding[i] == (CHAIN_LENGTH - 1) + CHAIN_LENGTH * (CHAIN_LENGTH - 1) - # == CHAIN_LENGTH**2 - 1. - assert encoding[i] == CHAIN_LENGTH**2 - 1 + # 2) Chain hashes -> recover WOTS public key # `wots_public_key` layout: V/2 pairs of stride WOTS_PK_PAIR_STRIDE = 10. Each pair @@ -155,6 +146,7 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # Each pair (chain 2*i, chain 2*i+1) is dispatched in a single match_range with # CHAIN_LENGTH^2 arms. The per-pair compile-time chain-step sum is written into # `pair_sum_ptr` by `chain_hash_pair` and accumulated at runtime into `target_sum`. + debug_assert(V % 2 == 0) target_sum: Mut = 0 for i in unroll(0, V / 2): chain_start_a = chain_starts + (2 * i) * XMSS_DIGEST_LEN diff --git a/crates/xmss/params.md b/crates/xmss/params.md deleted file mode 100644 index 71b39aee9..000000000 --- a/crates/xmss/params.md +++ /dev/null @@ -1,72 +0,0 @@ -# XMSS parameters (WIP) - -> **Warning:** The current implementation does not match the [leanSig](https://github.com/leanEthereum/leanSig) paper and does not provide 128-bit security in the Standard Model (though it may still be secure in the ROM/QROM). Expect changes in the future. - -## 1. Field and Hash - -**Field:** KoalaBear, p = 2^31 - 2^24 + 1. Each field element fits in a u32. - -**Hash:** Poseidon2 (width 16) in compression mode: `compress: [F; 16] -> [F; 8]`. Applies the Poseidon2 permutation, adds the input (feed-forward), and returns the first 8 elements. - -**Digest:** 8 field elements (~248 bits). Used for tree nodes, and chain values. - -**Chain step:** `chain_step(x) = compress(x, 0)`. Iterated n times: `iterate_hash(x, n) = chain_step^n(x)`. - -## 2. WOTS - -| Parameter | Symbol | Value | -|---|---|---| -| Chains | V | 40 | -| Winternitz parameter | W | 3 | -| Chain length | CHAIN_LENGTH | 2^W = 8 | -| Verifier chain hashes | NUM_CHAIN_HASHES | 120 | -| Signer chain hashes | TARGET_SUM | 160 (= V*(CHAIN_LENGTH-1) - NUM_CHAIN_HASHES) | -| Grinding chains | V_GRINDING | 3 | -| Message length | MESSAGE_LEN_FE | 9 | -| Randomness length | RANDOMNESS_LEN_FE | 7 | -| Truncated root length | TRUNCATED_MERKLE_ROOT_LEN_FE | 6 | - -### 2.1 Encoding - -Converts (message, randomness, slot, truncated_merkle_root) into 40 chain indices via a **fixed-sum encoding** (indices sum to TARGET_SUM, eliminating the need for checksum chains). - -1. `A = compress(message[0..8], [message[8], randomness[0..7]])` -2. `B = compress(A, [slot_lo, slot_hi, merkle_root[0..6]])` where slot is split into two 16-bit field elements. -3. Reject if any element of B equals -1 (uniformity guard). -4. Extract 24 bits per element of B (little-endian), split into 3-bit chunks, take first 43. -5. Valid iff: first 40 sum to 160, last 3 all equal 7. Otherwise retry with new randomness. - -(Note: adding part of the merkle root to the encoding computation contributes to multi-user security via domain-separation, otherwise the security of the encoding W * (V + V_GRINDING) would degrade bellow 128 bits with multiple users.) - -### 2.2 Keys - -- **Secret key:** 40 random pre-image digests. -- **Public key:** `pk[i] = iterate_hash(pre_image[i], 7)` for each chain. -- **Public key hash:** sequential left fold: `compress(compress(...compress(pk[0], pk[1])..., pk[38]), pk[39])` (39 compressions). - -### 2.3 Sign and Verify - -**Sign:** Find randomness r yielding a valid encoding, then `chain_tip[i] = iterate_hash(pre_image[i], encoding[i])`. Signature = (chain_tips, r). - -**Verify (public key recovery):** Recompute encoding from (message, slot, truncated_root, r), then `recovered_pk[i] = iterate_hash(chain_tip[i], 7 - encoding[i])`. - -## 3. XMSS - -**Tree:** Binary Merkle tree of depth LOG_LIFETIME = 32 (2^32 slots). Nodes = `compress(left, right)`. - -### 3.1 Key Generation - -Inputs: seed (32 bytes), slot range [start, end]. Only WOTS leaves for [start, end] are generated; Merkle nodes outside this range are filled with deterministic random digests (derived from the seed). To an observer, the resulting tree is indistinguishable from a full 2^32-leaf tree. - -**Public key:** the Merkle root (single digest). - - -... -TODO - -## 4. Properties - -- public key size: 31 bytes -- num. hashes at signing: < 2^16 (mostly grinding at encoding) -- num. hashes at verification: 2 (encoding) + NUM_CHAIN_HASHES + V + LOG_LIFETIME = 194 -- sig. size : RANDOMNESS_LEN_FE + 8 * (V + LOG_LIFETIME) = 583 field elements = 2.21 KiB \ No newline at end of file diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 52a0580ff..d5336e67e 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -20,7 +20,6 @@ pub const W: usize = 3; pub const CHAIN_LENGTH: usize = 1 << W; pub const NUM_CHAIN_HASHES: usize = 110; pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; -pub const V_GRINDING: usize = 2; pub const LOG_LIFETIME: usize = 32; pub const RANDOMNESS_LEN_FE: usize = 5; pub const MESSAGE_LEN_FE: usize = 9; diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 35f28e817..a8e49bbb2 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -216,7 +216,7 @@ pub fn wots_encode_with_poseidon_trace( .flat_map(|kb| to_little_endian_bits(kb.to_usize(), 24)) .collect::>() .chunks_exact(W) - .take(V + V_GRINDING) + .take(V) .map(|chunk| { chunk .iter() @@ -228,7 +228,7 @@ pub fn wots_encode_with_poseidon_trace( } fn is_valid_encoding(encoding: &[u8]) -> bool { - if encoding.len() != V + V_GRINDING { + if encoding.len() != V { return false; } // All indices must be < CHAIN_LENGTH @@ -239,9 +239,5 @@ fn is_valid_encoding(encoding: &[u8]) -> bool { if encoding[..V].iter().map(|&x| x as usize).sum::() != TARGET_SUM { return false; } - // Last V_GRINDING indices must all be CHAIN_LENGTH-1 (grinding constraint) - if !encoding[V..].iter().all(|&x| x as usize == CHAIN_LENGTH - 1) { - return false; - } true } From a25e199f43a6f7f4ee83bbce8aa4b7fb77c50a08 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 11:16:11 +0200 Subject: [PATCH 28/66] MESSAGE_LEN_FE = 8 --- README.md | 2 +- crates/rec_aggregation/xmss_aggregate.py | 15 ++++---- crates/xmss/src/lib.rs | 10 +---- crates/xmss/src/wots.rs | 49 +++++------------------- src/main.rs | 6 +-- 5 files changed, 22 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index c8c8a8af2..83f5fc68b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Machine: M4 Max 48GB (CPU only) ### XMSS aggregation ```bash -cargo run --release -- xmss --n-signatures 1475 +cargo run --release -- xmss --n-signatures 1500 ``` | WHIR rate \ regime | Proven | Conjectured | diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 51f960104..0ca79d2f3 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -84,16 +84,17 @@ def xmss_verify(pub_key, message, signature, merkle_chunks): # each merkle node directly into a `do_4_merkle_levels` slot via # `hint_xmss_merkle_node`, see `xmss_merkle_verify` / `do_4_merkle_levels`. - # 1) Encode: poseidon16_compress(message[0:8], [msg[8] | randomness(5) | tweak_encoding(2)]) + # 1) Encode: poseidon16_compress(message[0:8], [randomness(5) | tweak_encoding(2) | 0]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET - # Allocate 11 elements so the 2nd copy_5 (which reads [tw(2), 0, 0, 0] from the padded - # table and writes 5 elements at offset 6) can safely write positions 6..11. - a_input_right = Array(1 + RANDOMNESS_LEN + TWEAK_LEN) - a_input_right[0] = message[DIGEST_LEN] - copy_5(randomness, a_input_right + 1) + # Allocate 10 elements so the 2nd copy_5 (which reads [tw(2), 0, 0, 0] from the padded + # table and writes 5 elements at offset 5) can safely write positions 5..10. The + # poseidon reads positions 0..8 = [randomness(5) | tw(2) | 0]; the trailing zero comes + # for free from the padded tweak slot's first zero pad cell. + a_input_right = Array(RANDOMNESS_LEN + TWEAK_LEN) + copy_5(randomness, a_input_right) # encoding_tweak points to the tweak VALUE; reading 5 elements gives [tw(2), 0, 0, 0]. - copy_5(encoding_tweak, a_input_right + 1 + RANDOMNESS_LEN) + copy_5(encoding_tweak, a_input_right + RANDOMNESS_LEN) pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index d5336e67e..de415ff27 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -22,7 +22,7 @@ pub const NUM_CHAIN_HASHES: usize = 110; pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; pub const LOG_LIFETIME: usize = 32; pub const RANDOMNESS_LEN_FE: usize = 5; -pub const MESSAGE_LEN_FE: usize = 9; +pub const MESSAGE_LEN_FE: usize = 8; pub const PUBLIC_PARAM_LEN_FE: usize = 4; /// Length of the non-data prefix in either Poseidon input. Both inputs reserve the /// first `INPUT_PREFIX_LEN` field elements for metadata (tweak/zeros on the left, @@ -46,7 +46,6 @@ pub(crate) const TWEAK_TYPE_MERKLE: usize = 2; pub(crate) const TWEAK_TYPE_ENCODING: usize = 3; use backend::PrimeCharacteristicRing; -use utils::poseidon16_compress; /// index = slot or node_index in Merkle tree pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> [F; TWEAK_LEN] { @@ -100,10 +99,3 @@ pub(crate) fn build_right_chain_pp(public_param: &PublicParam) -> [F; 8] { right[..PUBLIC_PARAM_LEN_FE].copy_from_slice(public_param); right } - -fn poseidon16_compress_with_trace(a: [F; 8], b: [F; 8], poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>) -> [F; 8] { - let input: [F; 16] = [a, b].concat().try_into().unwrap(); - let output = poseidon16_compress(input); - poseidon_16_trace.push((input, output)); - output -} diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index a8e49bbb2..126596bcd 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -144,26 +144,6 @@ pub fn iterate_hash( }) } -pub fn iterate_hash_with_poseidon_trace( - a: &Digest, - n: usize, - poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>, - public_param: PublicParam, - slot: u32, - chain_index: usize, - start_step: usize, -) -> Digest { - // Chain hash layout: left = [tweak | zeros | data], right = [pp | zeros] (constant). - let right = build_right_chain_pp(&public_param); - (0..n).fold(*a, |acc, j| { - let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); - let left = build_left_chain(tweak, &acc); - poseidon16_compress_with_trace(left, right, poseidon_16_trace)[..XMSS_DIGEST_LEN] - .try_into() - .unwrap() - }) -} - pub fn find_randomness_for_wots_encoding( message: &[F; MESSAGE_LEN_FE], slot: u32, @@ -186,26 +166,15 @@ pub fn wots_encode( xmss_pub_key: &XmssPublicKey, randomness: &Randomness, ) -> Option<[u8; V]> { - wots_encode_with_poseidon_trace(message, slot, xmss_pub_key, randomness, &mut Vec::new()) -} - -pub fn wots_encode_with_poseidon_trace( - message: &[F; MESSAGE_LEN_FE], - slot: u32, - xmss_pub_key: &XmssPublicKey, - randomness: &Randomness, - poseidon_16_trace: &mut Vec<([F; 16], [F; 8])>, -) -> Option<[u8; V]> { - let mut first_input_right = [F::default(); 8]; - first_input_right[0] = message[8]; - first_input_right[1..1 + RANDOMNESS_LEN_FE].copy_from_slice(randomness); - first_input_right[1 + RANDOMNESS_LEN_FE..].copy_from_slice(&make_tweak(TWEAK_TYPE_ENCODING, 0, slot)); - let pre_compressed = - poseidon16_compress_with_trace(message[..8].try_into().unwrap(), first_input_right, poseidon_16_trace); - - let mut pp_input = [F::default(); 8]; - pp_input[..PUBLIC_PARAM_LEN_FE].copy_from_slice(&xmss_pub_key.public_param); - let compressed = poseidon16_compress_with_trace(pre_compressed, pp_input, poseidon_16_trace); + let first_input_left = message; + let mut first_input_right = [F::default(); DIGEST_LEN_FE]; + first_input_right[..RANDOMNESS_LEN_FE].copy_from_slice(randomness); + first_input_right[RANDOMNESS_LEN_FE..][..TWEAK_LEN].copy_from_slice(&make_tweak(TWEAK_TYPE_ENCODING, 0, slot)); + let pre_compressed = poseidon16_compress_pair(&first_input_left, &first_input_right); + + let mut second_input_right = [F::default(); DIGEST_LEN_FE]; + second_input_right[..PUBLIC_PARAM_LEN_FE].copy_from_slice(&xmss_pub_key.public_param); + let compressed = poseidon16_compress_pair(&pre_compressed, &second_input_right); if compressed.iter().any(|&kb| kb == -F::ONE) { // ensures uniformity of encoding diff --git a/src/main.rs b/src/main.rs index 1b097a931..5145ba904 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ fn main() { raw_xmss: 0, children: vec![ AggregationTopology { - raw_xmss: 740, + raw_xmss: 750, children: vec![], log_inv_rate, }; @@ -90,7 +90,7 @@ fn main() { raw_xmss: 25, children: vec![ AggregationTopology { - raw_xmss: 1475, + raw_xmss: 1500, children: vec![], log_inv_rate: 1, }; @@ -104,7 +104,7 @@ fn main() { raw_xmss: 0, children: vec![ AggregationTopology { - raw_xmss: 1475, + raw_xmss: 1500, children: vec![], log_inv_rate: 2, }; From 4eb7fcc566bd59890bb591516e3a2a508b9759c3 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 11:27:40 +0200 Subject: [PATCH 29/66] w --- crates/xmss/src/lib.rs | 44 ++++++++++++++--------------------------- crates/xmss/src/xmss.rs | 32 +++++++++++++++++------------- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index de415ff27..f6064b748 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] pub mod signers_cache; mod wots; -use backend::KoalaBear; +use backend::{DIGEST_LEN_FE, KoalaBear, POSEIDON1_WIDTH}; pub use wots::*; mod xmss; pub use xmss::*; @@ -24,13 +24,8 @@ pub const LOG_LIFETIME: usize = 32; pub const RANDOMNESS_LEN_FE: usize = 5; pub const MESSAGE_LEN_FE: usize = 8; pub const PUBLIC_PARAM_LEN_FE: usize = 4; -/// Length of the non-data prefix in either Poseidon input. Both inputs reserve the -/// first `INPUT_PREFIX_LEN` field elements for metadata (tweak/zeros on the left, -/// public_param on the right) and place the 4-element data digest right after it. -pub const INPUT_PREFIX_LEN: usize = 8 - XMSS_DIGEST_LEN; // = 4 pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE; // = 9 -const _: () = assert!(INPUT_PREFIX_LEN + XMSS_DIGEST_LEN == 8); // Left layout (with tweak): [tweak(2) | zeros(2) | data(DIGEST_SIZE)] const _: () = assert!(TWEAK_LEN + 2 + XMSS_DIGEST_LEN == 8); @@ -59,29 +54,20 @@ pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> ] } -/// [tweak(2) | zeros(2) | public_param(4)] -/// -/// Merkle LEFT input. Matches the LEFT-input convention for -/// `poseidon16_compress_half_hardcoded_left_4`: the first 4 FE come from the merkle -/// tweak slot at a compile-time absolute address, and the next 4 FE come from the -/// runtime `public_param` pointer — so the verifier never copies pp into a per-level -/// buffer. -pub(crate) fn build_left(tweak: [F; TWEAK_LEN], public_param: &PublicParam) -> [F; 8] { - let mut left = [F::default(); 8]; - left[..TWEAK_LEN].copy_from_slice(&tweak); - // left[TWEAK_LEN..INPUT_PREFIX_LEN] = zeros (default) - left[INPUT_PREFIX_LEN..].copy_from_slice(public_param); - left -} - -/// [left_child(4) | right_child(4)] -/// -/// Merkle RIGHT input: the two children of the parent node packed contiguously. -pub(crate) fn build_right(left_child: &Digest, right_child: &Digest) -> [F; 8] { - let mut right = [F::default(); 8]; - right[..XMSS_DIGEST_LEN].copy_from_slice(left_child); - right[XMSS_DIGEST_LEN..].copy_from_slice(right_child); - right +/// [tweak(2) | zeros(2) | public_param(4) | left_child(4) | right_child(4)] +pub(crate) fn build_merkle_data( + tweak: [F; TWEAK_LEN], + public_param: &PublicParam, + left_child: &Digest, + right_child: &Digest, +) -> [F; POSEIDON1_WIDTH] { + let mut data = [F::default(); POSEIDON1_WIDTH]; + data[..TWEAK_LEN].copy_from_slice(&tweak); + // data[2..4] = zeros (default) + data[DIGEST_LEN_FE - PUBLIC_PARAM_LEN_FE..][..PUBLIC_PARAM_LEN_FE].copy_from_slice(public_param); + data[DIGEST_LEN_FE..][..XMSS_DIGEST_LEN].copy_from_slice(left_child); + data[DIGEST_LEN_FE + XMSS_DIGEST_LEN..].copy_from_slice(right_child); + data } /// Chain-hash-specific left layout: [tweak(2) | zeros(2) | data(4)]. diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs index f23c45e45..02c5f6b4d 100644 --- a/crates/xmss/src/xmss.rs +++ b/crates/xmss/src/xmss.rs @@ -2,7 +2,7 @@ use backend::*; use rand::{CryptoRng, RngExt, SeedableRng, rngs::StdRng}; use serde::{Deserialize, Serialize}; use sha3::{Digest as Sha3Digest, Keccak256}; -use utils::poseidon16_compress_pair; +use utils::poseidon16_compress; use crate::*; @@ -113,11 +113,13 @@ pub fn xmss_key_gen( } else { gen_random_node(&seed, level - 1, right_idx) }; - let poseidon_left = build_left(make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), &public_param); - let poseidon_right = build_right(&left, &right); - poseidon16_compress_pair(&poseidon_left, &poseidon_right)[..XMSS_DIGEST_LEN] - .try_into() - .unwrap() + let merkle_data = build_merkle_data( + make_tweak(TWEAK_TYPE_MERKLE, level, i as u32), + &public_param, + &left, + &right, + ); + poseidon16_compress(merkle_data)[..XMSS_DIGEST_LEN].try_into().unwrap() }) .collect() }; @@ -213,16 +215,18 @@ pub fn xmss_verify( for (level, neighbour) in signature.merkle_proof.iter().enumerate() { let is_left = (((slot as u64) >> level) & 1) == 0; let parent_index = ((slot as u64) >> (level + 1)) as u32; - let tweak = make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index); - let left = build_left(tweak, &pub_key.public_param); - let right = if is_left { - build_right(¤t_hash, neighbour) + let (left_child, right_child) = if is_left { + (current_hash, *neighbour) } else { - build_right(neighbour, ¤t_hash) + (*neighbour, current_hash) }; - current_hash = poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_LEN] - .try_into() - .unwrap(); + let merkle_data = build_merkle_data( + make_tweak(TWEAK_TYPE_MERKLE, level + 1, parent_index), + &pub_key.public_param, + &left_child, + &right_child, + ); + current_hash = poseidon16_compress(merkle_data)[..XMSS_DIGEST_LEN].try_into().unwrap(); } if current_hash == pub_key.merkle_root { Ok(()) From 697b88977b4f5a69dbd68b05177167437a9d759b Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 11:45:05 +0200 Subject: [PATCH 30/66] w --- crates/xmss/src/lib.rs | 34 +++++++++++++++------------------- crates/xmss/src/wots.rs | 25 ++++++------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index f6064b748..45db5b120 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -1,7 +1,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use backend::PrimeCharacteristicRing; +use backend::{DIGEST_LEN_FE, KoalaBear, POSEIDON1_WIDTH}; + pub mod signers_cache; mod wots; -use backend::{DIGEST_LEN_FE, KoalaBear, POSEIDON1_WIDTH}; pub use wots::*; mod xmss; pub use xmss::*; @@ -20,27 +22,22 @@ pub const W: usize = 3; pub const CHAIN_LENGTH: usize = 1 << W; pub const NUM_CHAIN_HASHES: usize = 110; pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; -pub const LOG_LIFETIME: usize = 32; pub const RANDOMNESS_LEN_FE: usize = 5; pub const MESSAGE_LEN_FE: usize = 8; pub const PUBLIC_PARAM_LEN_FE: usize = 4; -pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE; // = 9 - -// Left layout (with tweak): [tweak(2) | zeros(2) | data(DIGEST_SIZE)] -const _: () = assert!(TWEAK_LEN + 2 + XMSS_DIGEST_LEN == 8); - -/// In-memory layout consumed by `hint_wots`: just `randomness | chain_tips` (the WOTS -/// part of an XMSS signature). The XMSS merkle path lives in a separate prover-side -/// queue and is consumed by `hint_xmss_merkle_node`. +pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE; pub const WOTS_SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + V * XMSS_DIGEST_LEN; +// XMSS +pub const LOG_LIFETIME: usize = 32; + // Tweak: domain separation within each hash. pub(crate) const TWEAK_TYPE_CHAIN: usize = 0; pub(crate) const TWEAK_TYPE_WOTS_PK: usize = 1; pub(crate) const TWEAK_TYPE_MERKLE: usize = 2; pub(crate) const TWEAK_TYPE_ENCODING: usize = 3; -use backend::PrimeCharacteristicRing; +const _: () = assert!(V.is_multiple_of(2)); // For efficiency of the snark (we can batch chains in pairs) /// index = slot or node_index in Merkle tree pub(crate) fn make_tweak(tweak_type: usize, sub_position: usize, index: u32) -> [F; TWEAK_LEN] { @@ -70,18 +67,17 @@ pub(crate) fn build_merkle_data( data } -/// Chain-hash-specific left layout: [tweak(2) | zeros(2) | data(4)]. -pub(crate) fn build_left_chain(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; 8] { - let mut left = [F::default(); 8]; +/// [tweak(2) | zeros(2) | data(4)] +pub(crate) fn build_left_chain_input(tweak: [F; TWEAK_LEN], data: &Digest) -> [F; DIGEST_LEN_FE] { + let mut left = [F::default(); DIGEST_LEN_FE]; left[..TWEAK_LEN].copy_from_slice(&tweak); - // left[TWEAK_LEN..8-DIGEST_SIZE] = zeros (default) - left[8 - XMSS_DIGEST_LEN..].copy_from_slice(data); + left[DIGEST_LEN_FE - XMSS_DIGEST_LEN..].copy_from_slice(data); left } -/// Chain-hash-specific right layout: [public_param(4) | zeros(4)]. -pub(crate) fn build_right_chain_pp(public_param: &PublicParam) -> [F; 8] { - let mut right = [F::default(); 8]; +/// [public_param(4) | zeros(4)] +pub(crate) fn build_right_chain_input(public_param: &PublicParam) -> [F; DIGEST_LEN_FE] { + let mut right = [F::default(); DIGEST_LEN_FE]; right[..PUBLIC_PARAM_LEN_FE].copy_from_slice(public_param); right } diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs index 126596bcd..048bf4bbf 100644 --- a/crates/xmss/src/wots.rs +++ b/crates/xmss/src/wots.rs @@ -92,17 +92,8 @@ impl WotsSignature { } impl WotsPublicKey { - /// Sponge-like hash of V public key digests. - /// IV = [tweak(2) | 00(2) | pp(4)]; first absorb 8 zeros, then ingest 8 FE per step - /// (2 digests at a time). The leading zero-absorb gives the SNARK-side a uniform - /// per-pair loop starting at i=0 and lets each pair live in a 10-FE slot - /// `[leading_0 | tip_a | tip_b | trailing_0]`, with the leading/trailing zero cells - /// serving as copy_5 slack in `chain_hash_pair`. - /// The IV layout matches the LEFT-input convention for `poseidon16_compress_hardcoded_left_4` - /// — `[tweak(2) | 00]` is read straight from the wots_pk tweak slot, and `[pp(4)]` lives at - /// the runtime public_param pointer. Final output truncated to DIGEST_SIZE (4 FE). + // We use a T-Sponge with replacement, i.e. we use Poseidon in compression mode + replace (instead of modular addition) when ingesting 8 new field elements. pub fn hash(&self, public_param: PublicParam, slot: u32) -> Digest { - assert!(V % 2 == 0); // IV: [tweak(2) | 00 | pp(4)] let tweak = make_tweak(TWEAK_TYPE_WOTS_PK, 0, slot); let mut state = [F::default(); 8]; @@ -110,9 +101,7 @@ impl WotsPublicKey { // state[2..4] = 00 (default) state[4..4 + PUBLIC_PARAM_LEN_FE].copy_from_slice(&public_param); - // Initial absorb of 8 zeros (matches the SNARK's `poseidon16_compress_hardcoded_left_4` - // call against `ZERO_VEC_PTR` in `wots_pk_hash`). - let zeros = [F::ZERO; 8]; + let zeros = [F::ZERO; 8]; // for snark-friendliless (not necessary for security) state = poseidon16_compress_pair(&state, &zeros); for i in (0..V).step_by(2) { @@ -133,11 +122,11 @@ pub fn iterate_hash( chain_index: usize, start_step: usize, ) -> Digest { - // Chain hash layout: left = [tweak | zeros | data], right = [pp | zeros] (constant). - let right = build_right_chain_pp(&public_param); + // Chain hash layout: left = [tweak (2) | zeros (2) | data (4)], right = [public_param(4) | zeros(4)]. + let right = build_right_chain_input(&public_param); (0..n).fold(*a, |acc, j| { let tweak = make_tweak(TWEAK_TYPE_CHAIN, chain_index * CHAIN_LENGTH + start_step + j, slot); - let left = build_left_chain(tweak, &acc); + let left = build_left_chain_input(tweak, &acc); poseidon16_compress_pair(&left, &right)[..XMSS_DIGEST_LEN] .try_into() .unwrap() @@ -200,12 +189,10 @@ fn is_valid_encoding(encoding: &[u8]) -> bool { if encoding.len() != V { return false; } - // All indices must be < CHAIN_LENGTH if !encoding.iter().all(|&x| (x as usize) < CHAIN_LENGTH) { return false; } - // First V indices must sum to TARGET_SUM - if encoding[..V].iter().map(|&x| x as usize).sum::() != TARGET_SUM { + if encoding.iter().map(|&x| x as usize).sum::() != TARGET_SUM { return false; } true From 49dab0e93fa5033e89e23c4d8547620b342ddd85 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 11:50:14 +0200 Subject: [PATCH 31/66] w --- crates/rec_aggregation/main.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 90fae16bb..7f1b7f971 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -28,11 +28,6 @@ def main(): priv_start: Imu hint_private_input_start(priv_start) debug_assert(priv_start == TWEAK_TABLE_ADDR) - # Private input layout: [tweak_table (FIXED size, lives at the compile-time - # address TWEAK_TABLE_ADDR), header, pubkeys, source_blocks, ...]. - # Use the compile-time constant for the tweak table — this is what enables - # poseidon16_compress_hardcoded_left_4 in xmss_aggregate.py to embed every - # tweak's absolute address into the bytecode. tweak_table = TWEAK_TABLE_ADDR header = priv_start + TWEAK_TABLE_SIZE_FE_PADDED From 19c10c802ef5074721929f7ee03df940f29046d2 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 12:13:41 +0200 Subject: [PATCH 32/66] w --- crates/rec_aggregation/xmss_aggregate.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 96bb0c175..b2f055a24 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -77,10 +77,8 @@ def xmss_verify(pub_key, message, merkle_chunks): # poseidon16_compress_hardcoded_left_4 without ever copying tweak prefixes into # per-hash buffers. - # 1 extra element for safe copy_5 reads past the last chain_tip - wots = Array(WOTS_SIG_SIZE + 1) + wots = Array(WOTS_SIG_SIZE) hint_wots(wots) - wots[WOTS_SIG_SIZE] = 0 public_param = pub_key + XMSS_DIGEST_LEN randomness = wots From 88d5757f86c121b12d585eff7232dedf6afc20da Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 12:27:09 +0200 Subject: [PATCH 33/66] harcode PRIVATE_INPUT_START instead of hinting it --- crates/rec_aggregation/main.py | 5 +---- crates/rec_aggregation/src/compilation.rs | 23 ++++++++++------------- crates/rec_aggregation/xmss_aggregate.py | 3 ++- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 15d50d129..f0171b8b7 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -25,11 +25,8 @@ def main(): tweaks_hash_expected = merkle_chunks_for_slot + N_MERKLE_CHUNKS bytecode_claim_output = pub_mem + BYTECODE_CLAIM_OFFSET - priv_start: Imu - hint_private_input_start(priv_start) - debug_assert(priv_start == TWEAK_TABLE_ADDR) tweak_table = TWEAK_TABLE_ADDR - header = priv_start + TWEAK_TABLE_SIZE_FE_PADDED + header = PRIVATE_INPUT_START + TWEAK_TABLE_SIZE_FE_PADDED n_recursions = header[0] assert n_recursions <= MAX_RECURSIONS diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 34925df90..cbf215524 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -29,8 +29,8 @@ pub fn init_aggregation_bytecode() { BYTECODE.get_or_init(compile_main_program_self_referential); } -fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> Bytecode { - let bytecode_point_n_vars = inner_program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); +fn compile_main_program(program_log_size: usize, bytecode_zero_eval: F) -> Bytecode { + let bytecode_point_n_vars = program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); let claim_data_size = (bytecode_point_n_vars + 1) * DIMENSION; let claim_data_size_padded = claim_data_size.next_multiple_of(DIGEST_LEN); // pub_input layout: n_sigs(1) + slice_hash(8) + message + slot_low(1) + slot_high(1) @@ -43,17 +43,14 @@ fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> + DIGEST_LEN + claim_data_size_padded + DIGEST_LEN; - let inner_public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); - // The private input layout in lib.rs places the tweak table at the very start of - // private input. Private input starts at `public_memory_size = 1 << inner_public_memory_log_size`, - // so the tweak table sits at this exact compile-time address. - let tweak_table_address_in_memory = 1usize << inner_public_memory_log_size; + let public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); + let private_input_start = 1usize << public_memory_log_size; let replacements = build_replacements( - inner_program_log_size, - inner_public_memory_log_size, + program_log_size, + public_memory_log_size, bytecode_zero_eval, pub_input_size, - tweak_table_address_in_memory, + private_input_start, ); let filepath = Path::new(env!("CARGO_MANIFEST_DIR")) @@ -89,7 +86,7 @@ fn build_replacements( inner_public_memory_log_size: usize, bytecode_zero_eval: F, pub_input_size: usize, - tweak_table_address_in_memory: usize, + private_input_start: usize, ) -> BTreeMap { let mut replacements = BTreeMap::new(); @@ -162,8 +159,8 @@ fn build_replacements( tracing::info!("Warning: Too much grinding for WHIR folding"); // TODO } replacements.insert( - "TWEAK_TABLE_ADDR_PLACEHOLDER".to_string(), - tweak_table_address_in_memory.to_string(), + "PRIVATE_INPUT_START_PLACEHOLDER".to_string(), + private_input_start.to_string(), ); replacements.insert( "WHIR_FIRST_RS_REDUCTION_FACTOR_PLACEHOLDER".to_string(), diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index b2f055a24..e7488dc8d 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -21,7 +21,8 @@ NUM_ENCODING_FE = div_ceil(V, (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK -TWEAK_TABLE_ADDR = TWEAK_TABLE_ADDR_PLACEHOLDER +PRIVATE_INPUT_START = PRIVATE_INPUT_START_PLACEHOLDER +TWEAK_TABLE_ADDR = PRIVATE_INPUT_START # Tweak table layout: each tweak is stored as a 5-FE padded slot [0, tw[0], tw[1], 0, 0]. # Convention: tweak pointers always point to the tweak VALUE (offset +1 within the slot). From b3e8a0d0c11df1007010058057d856788b8a0ace Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 10 Apr 2026 12:31:38 +0200 Subject: [PATCH 34/66] tweaks table: 00 instead 000 --- crates/rec_aggregation/src/compilation.rs | 4 +-- crates/rec_aggregation/src/lib.rs | 27 ++++++++++++++------ crates/rec_aggregation/xmss_aggregate.py | 30 ++++++++++++----------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index cbf215524..689a23f78 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -11,9 +11,7 @@ use std::sync::OnceLock; use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; -use xmss::{ - LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, W, XMSS_DIGEST_LEN, -}; +use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, PUBLIC_PARAM_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, W, XMSS_DIGEST_LEN}; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 8b41e23eb..3469eaf96 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -29,9 +29,14 @@ const TWEAK_TYPE_ENCODING: usize = 3; /// Number of tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + (V-1) wots_pk + LOG_LIFETIME merkle const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME; -/// Each tweak is stored as a 5-FE slot [0, tw[0], tw[1], 0, 0] so that we can use copy_5 -const TWEAK_SLOT_SIZE: usize = 5; -const TWEAK_TABLE_SIZE_FE_PADDED: usize = (N_TWEAKS * TWEAK_SLOT_SIZE).next_multiple_of(DIGEST_LEN); +/// All, except one, tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. The first slot +/// (the encoding tweak) is the ONLY slot read via copy_5 (5 cells), so it gets +/// an extra trailing zero: [tw[0], tw[1], 0, 0, 0]. Every other slot is read +/// only via `poseidon16_compress_hardcoded_left_4`, which reads exactly 4 cells. +const TWEAK_SLOT_SIZE: usize = 4; +const ENCODING_TWEAK_SLOT_SIZE: usize = 5; +const TWEAK_TABLE_SIZE_FE_PADDED: usize = + (ENCODING_TWEAK_SLOT_SIZE + (N_TWEAKS - 1) * TWEAK_SLOT_SIZE).next_multiple_of(DIGEST_LEN); const TWEAKS_HASHING_USE_IV: bool = false; // fixed size → no IV needed @@ -65,21 +70,29 @@ fn make_tweak_values(tweak_type: usize, sub_position: usize, index: u32) -> [F; ] } -/// Each tweak is stored as a 5-FE slot: [0, tw[0], tw[1], 0, 0] +/// Tweak slots are 4-FE [tw[0], tw[1], 0, 0], except the first (encoding) slot +/// which is 5-FE [tw[0], tw[1], 0, 0, 0] — the extra trailing zero is needed +/// because the encoding tweak is the only slot read via copy_5. fn compute_tweak_table(slot: u32) -> Vec { let mut table = Vec::new(); let push_padded = |table: &mut Vec, tweak_type: usize, sub_position: usize, index: u32| { let tw = make_tweak_values(tweak_type, sub_position, index); - table.push(F::ZERO); table.push(tw[0]); table.push(tw[1]); table.push(F::ZERO); table.push(F::ZERO); }; - // Encoding tweak - push_padded(&mut table, TWEAK_TYPE_ENCODING, 0, slot); + // Encoding tweak (5-FE: extra trailing zero so that copy_5 reads all zeros after the value). + { + let tw = make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot); + table.push(tw[0]); + table.push(tw[1]); + table.push(F::ZERO); + table.push(F::ZERO); + table.push(F::ZERO); + } // Chain tweaks: for chain i, step s → make_tweak(CHAIN, i*CHAIN_LENGTH + s, slot) for i in 0..V { diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index e7488dc8d..c70bf00be 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -24,15 +24,17 @@ PRIVATE_INPUT_START = PRIVATE_INPUT_START_PLACEHOLDER TWEAK_TABLE_ADDR = PRIVATE_INPUT_START -# Tweak table layout: each tweak is stored as a 5-FE padded slot [0, tw[0], tw[1], 0, 0]. -# Convention: tweak pointers always point to the tweak VALUE (offset +1 within the slot). -# Individual access: ptr[0] = tw[0], ptr[1] = tw[1] (unchanged from the unpadded version). -# Copy_5 access: copy_5(ptr - 1, dst) reads the 5-element slot [0, tw[0], tw[1], 0, 0]. -TWEAK_LEN = 5 # stride / slot size in the padded table +# Tweak table layout: most tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. +# The first slot (encoding tweak) is 5-FE [tw[0], tw[1], 0, 0, 0]: it's the only +# tweak read via copy_5 (5 cells), so it gets an extra trailing zero. Every other +# tweak is read only via poseidon16_compress_(half_)hardcoded_left_4, which reads +# exactly 4 cells. Tweak pointers point directly at tw[0] (no offset trick). +TWEAK_LEN = 4 # stride / slot size for non-encoding tweaks +ENCODING_TWEAK_SLOT_SIZE = 5 # encoding tweak slot has one extra trailing zero N_TWEAKS = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME -TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(N_TWEAKS * TWEAK_LEN, DIGEST_LEN) -TWEAK_ENCODING_OFFSET = 1 # skip the leading zero of slot 0 -TWEAK_CHAIN_OFFSET = TWEAK_ENCODING_OFFSET + TWEAK_LEN +TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(ENCODING_TWEAK_SLOT_SIZE + (N_TWEAKS - 1) * TWEAK_LEN, DIGEST_LEN) +TWEAK_ENCODING_OFFSET = 0 +TWEAK_CHAIN_OFFSET = ENCODING_TWEAK_SLOT_SIZE # encoding occupies cells [0..5] TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + (V - 1) * TWEAK_LEN @@ -91,13 +93,13 @@ def xmss_verify(pub_key, message, merkle_chunks): # 1) Encode: poseidon16_compress(message[0:8], [randomness(5) | tweak_encoding(2) | 0]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET - # Allocate 10 elements so the 2nd copy_5 (which reads [tw(2), 0, 0, 0] from the padded - # table and writes 5 elements at offset 5) can safely write positions 5..10. The - # poseidon reads positions 0..8 = [randomness(5) | tw(2) | 0]; the trailing zero comes - # for free from the padded tweak slot's first zero pad cell. - a_input_right = Array(RANDOMNESS_LEN + TWEAK_LEN) + # Allocate 10 elements (RANDOMNESS_LEN + 5) so the 2nd copy_5 (which reads + # the 5-FE encoding slot [tw(2), 0, 0, 0] and writes 5 elements at offset 5) + # can safely write positions 5..10. The encoding slot is the unique 5-cell + # tweak slot precisely so this read returns all zeros after the value. + a_input_right = Array(RANDOMNESS_LEN + ENCODING_TWEAK_SLOT_SIZE) copy_5(randomness, a_input_right) - # encoding_tweak points to the tweak VALUE; reading 5 elements gives [tw(2), 0, 0, 0]. + # encoding_tweak points to tw[0] of slot 0; reading 5 elements gives [tw(2), 0, 0, 0]. copy_5(encoding_tweak, a_input_right + RANDOMNESS_LEN) pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) From 8c7a5c1232558d39ec563a57ff0edad16d6ca12f Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 11 Apr 2026 11:57:31 +0200 Subject: [PATCH 35/66] wip --- crates/rec_aggregation/src/lib.rs | 10 ++++------ crates/rec_aggregation/xmss_aggregate.py | 17 +++++------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 2acb8aafd..48c1a6633 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -27,8 +27,8 @@ const TWEAK_TYPE_WOTS_PK: usize = 1; const TWEAK_TYPE_MERKLE: usize = 2; const TWEAK_TYPE_ENCODING: usize = 3; -/// Number of tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + (V-1) wots_pk + LOG_LIFETIME merkle -const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME; +/// Number of tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + 1 wots_pk + LOG_LIFETIME merkle +const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + 1 + LOG_LIFETIME; /// All, except one, tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. The first slot /// (the encoding tweak) is the ONLY slot read via copy_5 (5 cells), so it gets /// an extra trailing zero: [tw[0], tw[1], 0, 0, 0]. Every other slot is read @@ -106,10 +106,8 @@ fn compute_tweak_table(slot: u32) -> Vec { } } - // WOTS_PK tweaks: for sub_pos p = 0..V-2 - for p in 0..V - 1 { - push_padded(&mut table, TWEAK_TYPE_WOTS_PK, p, slot); - } + // WOTS_PK tweak + push_padded(&mut table, TWEAK_TYPE_WOTS_PK, 0, slot); // Merkle tweaks: for level 0..LOG_LIFETIME-1 for level in 0..LOG_LIFETIME { diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 6423cb238..7ec9ebe6b 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -13,10 +13,7 @@ PUB_KEY_SIZE = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE PP_IN_LEFT = DIGEST_LEN - XMSS_DIGEST_LEN WOTS_SIG_SIZE = RANDOMNESS_LEN + V * XMSS_DIGEST_LEN -# wots_public_key pair stride: each pair occupies 10 cells -# `[leading_0 | tip_a(4) | tip_b(4) | trailing_0]`. The two zero cells are slack -# used by chain_hash_pair's copy_5 calls — they receive the harmless 5th-cell -# spillover so copy_5 never collides with adjacent chain writes. +# wots_public_key pair stride: each pair occupies 10 cells `[leading_0 | tip_a(4) | tip_b(4) | trailing_0]`. In order to be able to use copy_5 on both sides. WOTS_PK_PAIR_STRIDE = 2 + 2 * XMSS_DIGEST_LEN NUM_ENCODING_FE = div_ceil(V, (24 / W)) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER @@ -25,19 +22,15 @@ PRIVATE_INPUT_START = PREAMBLE_MEMORY_END TWEAK_TABLE_ADDR = PRIVATE_INPUT_START -# Tweak table layout: most tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. -# The first slot (encoding tweak) is 5-FE [tw[0], tw[1], 0, 0, 0]: it's the only -# tweak read via copy_5 (5 cells), so it gets an extra trailing zero. Every other -# tweak is read only via poseidon16_compress_(half_)hardcoded_left_4, which reads -# exactly 4 cells. Tweak pointers point directly at tw[0] (no offset trick). +# Tweak table layout: all tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0], except the first, encoding, tweak: which is 5-FE [tw[0], tw[1], 0, 0, 0] (in order to use copy_5) TWEAK_LEN = 4 # stride / slot size for non-encoding tweaks -ENCODING_TWEAK_SLOT_SIZE = 5 # encoding tweak slot has one extra trailing zero -N_TWEAKS = 1 + V * CHAIN_LENGTH + (V - 1) + LOG_LIFETIME +ENCODING_TWEAK_SLOT_SIZE = 5 # encoding tweak has one extra trailing zero +N_TWEAKS = 1 + V * CHAIN_LENGTH + 1 + LOG_LIFETIME TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(ENCODING_TWEAK_SLOT_SIZE + (N_TWEAKS - 1) * TWEAK_LEN, DIGEST_LEN) TWEAK_ENCODING_OFFSET = 0 TWEAK_CHAIN_OFFSET = ENCODING_TWEAK_SLOT_SIZE # encoding occupies cells [0..5] TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN -TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + (V - 1) * TWEAK_LEN +TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + TWEAK_LEN # Buffer size for the hash-chaining trick. # Each slot is [extra(1) | prefix(4) | hash_output(8)] = 13 elements. From 2e05b6d8ea2f16c4fa699fdb150501f23cd656bb Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 11 Apr 2026 12:29:23 +0200 Subject: [PATCH 36/66] wip --- crates/rec_aggregation/xmss_aggregate.py | 67 +++--------------------- 1 file changed, 8 insertions(+), 59 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 7ec9ebe6b..cd74bc949 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -32,79 +32,31 @@ TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + TWEAK_LEN -# Buffer size for the hash-chaining trick. -# Each slot is [extra(1) | prefix(4) | hash_output(8)] = 13 elements. -# The "extra" position at offset 0 is the landing spot for copy_5's leading zero when -# writing a padded tweak from the table. The effective buffer (hash-input pointer) is at -# offset 1; poseidon writes its output at offset 5 (= 1 + PP_IN_LEFT). -# Hash input for the next iteration = slot[1..9] = [prefix(4) | digest(4)]. -BUF_SIZE = 1 + PP_IN_LEFT + DIGEST_LEN - - -@inline -def build_right_fn(pp, data, out): - # [public_param(4) | data(XMSS_DIGEST)] - # out must be Array(DIGEST_LEN + 1) or more. - # data must have at least 5 readable elements in memory. - for k in unroll(0, PP_IN_LEFT): - out[k] = pp[k] - copy_5(data, out + PP_IN_LEFT) - return - - -@inline -def build_chain_right(public_param, out): - # Shared chain-hash right input: [public_param(4) | zeros(4)] - # Built once per xmss_verify and reused for every chain hash. - # out must be Array(DIGEST_LEN + 1) so set_to_5_zeros can write positions 4..8. - for k in unroll(0, PUBLIC_PARAM_LEN_FE): - out[k] = public_param[k] - set_to_5_zeros(out + PUBLIC_PARAM_LEN_FE) - return - - @inline def xmss_verify(pub_key, message, merkle_chunks): - # pub_key: PUB_KEY_SIZE FE = merkle_root(XMSS_DIGEST) | public_param(PUBLIC_PARAM_LEN_FE) - # signature: randomness(RANDOMNESS_LEN) | chain_tips(V * XMSS_DIGEST) | merkle_path(LOG_LIFETIME * XMSS_DIGEST) - # - # The tweak table lives at the compile-time constant address TWEAK_TABLE_ADDR - # (asserted at the top of main.py), so every tweak slot has a compile-time absolute - # address. This lets us pass tweak offsets straight to - # poseidon16_compress_hardcoded_left_4 without ever copying tweak prefixes into - # per-hash buffers. - wots = Array(WOTS_SIG_SIZE) hint_wots(wots) public_param = pub_key + XMSS_DIGEST_LEN randomness = wots chain_starts = wots + RANDOMNESS_LEN - # NOTE: the merkle path is no longer part of `signature`. The prover delivers - # each merkle node directly into a `do_4_merkle_levels` slot via - # `hint_xmss_merkle_node`, see `xmss_merkle_verify` / `do_4_merkle_levels`. - + # 1) Encode: poseidon16_compress(message[0:8], [randomness(5) | tweak_encoding(2) | 0]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET - # Allocate 10 elements (RANDOMNESS_LEN + 5) so the 2nd copy_5 (which reads - # the 5-FE encoding slot [tw(2), 0, 0, 0] and writes 5 elements at offset 5) - # can safely write positions 5..10. The encoding slot is the unique 5-cell - # tweak slot precisely so this read returns all zeros after the value. a_input_right = Array(RANDOMNESS_LEN + ENCODING_TWEAK_SLOT_SIZE) copy_5(randomness, a_input_right) # encoding_tweak points to tw[0] of slot 0; reading 5 elements gives [tw(2), 0, 0, 0]. copy_5(encoding_tweak, a_input_right + RANDOMNESS_LEN) pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) - - # pp_input layout: [public_param(4) | zeros(4)]. Allocate 9 so set_to_5_zeros can write positions 4..8. - pp_input = Array(DIGEST_LEN + 1) - for k in unroll(0, PUBLIC_PARAM_LEN_FE): - pp_input[k] = public_param[k] - set_to_5_zeros(pp_input + PUBLIC_PARAM_LEN_FE) + + public_params_paded_buff = Array(DIGEST_LEN + 2) # 0 [public_param(4) | zeros(4)] 0 + copy_5(public_param - 1, public_params_paded_buff) + set_to_5_zeros(public_params_paded_buff + 5) + public_params_paded = public_params_paded_buff + 1 encoding_fe = Array(DIGEST_LEN) - poseidon16_compress(pre_compressed, pp_input, encoding_fe) + poseidon16_compress(pre_compressed, public_params_paded, encoding_fe) # Decompose the encoding into chunks of 2*W bits. Each chunk packs the chain step # counts of two consecutive WOTS chains: chunk i = step_{2i} + CHAIN_LENGTH * step_{2i+1}. @@ -141,9 +93,6 @@ def xmss_verify(pub_key, message, merkle_chunks): # half-output lookup stays in bounds. wots_public_key = Array((V / 2) * WOTS_PK_PAIR_STRIDE + 4) - chain_right = Array(DIGEST_LEN + 1) - build_chain_right(public_param, chain_right) - # Each pair (chain 2*i, chain 2*i+1) is dispatched in a single match_range with # CHAIN_LENGTH^2 arms. The per-pair compile-time chain-step sum is written into # `pair_sum_ptr` by `chain_hash_pair` and accumulated at runtime into `target_sum`. @@ -169,7 +118,7 @@ def xmss_verify(pub_key, message, merkle_chunks): chain_end_b, tweaks_a, tweaks_b, - chain_right, + public_params_paded, pair_sum_ptr, ), ) From 0da362d41daa420a6ea7b8748cd217b421e73b4a Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 11 Apr 2026 14:12:31 +0200 Subject: [PATCH 37/66] w --- crates/rec_aggregation/xmss_aggregate.py | 82 +----------------------- 1 file changed, 3 insertions(+), 79 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index cd74bc949..3969808eb 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -60,9 +60,6 @@ def xmss_verify(pub_key, message, merkle_chunks): # Decompose the encoding into chunks of 2*W bits. Each chunk packs the chain step # counts of two consecutive WOTS chains: chunk i = step_{2i} + CHAIN_LENGTH * step_{2i+1}. - # Compared to the per-W-bit decomposition, this halves the number of `match_range` - # dispatch sites in the chain-hash loop (one match per pair, with CHAIN_LENGTH^2 - # arms, instead of two matches per pair with CHAIN_LENGTH arms each). encoding = Array(NUM_ENCODING_FE * 24 / (2 * W)) remaining = Array(NUM_ENCODING_FE) @@ -81,22 +78,8 @@ def xmss_verify(pub_key, message, merkle_chunks): assert partial_sum == encoding_fe[i] - - # 2) Chain hashes -> recover WOTS public key - # `wots_public_key` layout: V/2 pairs of stride WOTS_PK_PAIR_STRIDE = 10. Each pair - # is `[leading_0(1) | tip_a(4) | tip_b(4) | trailing_0(1)]`. The leading and trailing - # zero cells are slack used by `chain_hash_pair`'s copy_5 calls in the n==0 case - # (the 5th cell that copy_5 always writes lands in slack, never in the adjacent - # chain's slot). The wots_pk_hash absorbs 8 zeros first (matching the Rust side), - # then iterates over pairs reading 8 cells `[tip_a | tip_b]` per pair from offset - # `i * WOTS_PK_PAIR_STRIDE + 1`. Plus 4 trailing slots so the LAST chain b's - # half-output lookup stays in bounds. - wots_public_key = Array((V / 2) * WOTS_PK_PAIR_STRIDE + 4) - - # Each pair (chain 2*i, chain 2*i+1) is dispatched in a single match_range with - # CHAIN_LENGTH^2 arms. The per-pair compile-time chain-step sum is written into - # `pair_sum_ptr` by `chain_hash_pair` and accumulated at runtime into `target_sum`. debug_assert(V % 2 == 0) + wots_public_key = Array((V / 2) * WOTS_PK_PAIR_STRIDE) target_sum: Mut = 0 for i in unroll(0, V / 2): chain_start_a = chain_starts + (2 * i) * XMSS_DIGEST_LEN @@ -126,38 +109,16 @@ def xmss_verify(pub_key, message, merkle_chunks): assert target_sum == TARGET_SUM - # 3) Hash WOTS public key (the LEFT-input tweak slot is baked in via TWEAK_TABLE_ADDR) - expected_leaf = wots_pk_hash(wots_public_key, public_param) + merkle_leaf = wots_pk_hash(wots_public_key, public_param) - # 4) Merkle verification — merkle_tweaks is now a compile-time constant absolute - # address, which lets do_4_merkle_levels feed tweak offsets straight into - # poseidon16_compress_hardcoded_left_4. merkle_tweaks = TWEAK_TABLE_ADDR + TWEAK_MERKLE_OFFSET - xmss_merkle_verify(expected_leaf, merkle_chunks, pub_key, public_param, merkle_tweaks) + xmss_merkle_verify(merkle_leaf, merkle_chunks, pub_key, public_param, merkle_tweaks) return @inline def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): - # Chain of n half-output poseidon compressions: - # D_0 = poseidon([tweak_s | 00 | input(4)], chain_right)[..4] - # D_j = poseidon([tweak_{s+j} | 00 | D_{j-1}], chain_right)[..4] for j in 1..n-2 - # output = poseidon([tweak_{s+n-1} | 00 | D_{n-2}], chain_right)[..4] - # where s = starting_step = CHAIN_LENGTH - 1 - n. - # - # `chain_i_tweaks` is a compile-time constant absolute address into the tweak table, - # so each per-step `chain_i_tweaks + k * TWEAK_LEN` is also compile-time. The LEFT - # prefix [tweak | 00] is read straight from the tweak slot by the hardcoded_left_4 - # precompile — no per-hash buffer copies. Every output is consumed as a 4-element - # digest, so we use the `_half_` variant. - # - # Intermediate digests are packed at stride XMSS_DIGEST (= 4) inside `digests`. - # The buffer is sized n * XMSS_DIGEST: (n-1) digests at offsets 0, 4, ..., (n-2)*4 - # plus 4 trailing slots that the last digest's RES lookup reads (vacuously, since - # half_output leaves those columns unconstrained — the prover sets them to whatever - # is at memory[res+4..res+8], which is 0 in write-once memory). starting_step = CHAIN_LENGTH - 1 - n - if n == 1: first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN poseidon16_compress_half_hardcoded_left_4(input, chain_right, output, first_tweak) @@ -199,36 +160,17 @@ def chain_hash_pair( pair_sum_ptr, ): # Pair-encoded chain hash. `n` is a compile-time constant in [0, CHAIN_LENGTH^2) - # supplied by `match_range`. It packs two raw chain step counts: - # raw_a = n % CHAIN_LENGTH (chain 2*i) - # raw_b = (n - raw_a) / CHAIN_LENGTH (chain 2*i + 1) - # so the inverse is n = raw_a + CHAIN_LENGTH * raw_b, matching the bit layout - # produced by `hint_decompose_bits_xmss(..., 2 * W)`. - # - # We dispatch both chains from a single match arm, halving the number of - # match-arm sites compared to the per-chain version. Both chains share the - # same RIGHT poseidon input ([pp(4) | zeros(4)] in `chain_right`). - # - # `pair_sum_ptr[0]` receives the compile-time constant `raw_a + raw_b`, which - # the caller accumulates into the runtime `target_sum`. raw_a = n % CHAIN_LENGTH raw_b = (n - raw_a) / CHAIN_LENGTH num_hashes_a = (CHAIN_LENGTH - 1) - raw_a num_hashes_b = (CHAIN_LENGTH - 1) - raw_b if num_hashes_a == 0: - # Single-cycle copy_5: writes 5 cells [output_a[-1..4]]. The 5th cell is the - # leading-zero slack of this pair (output_a points to pair_start + 1, so - # output_a - 1 = pair_start = leading_0). The slack cell receives input_a[-1] - # (a signature byte) — harmless because nothing else reads it. copy_5(input_a - 1, output_a - 1) else: chain_hash_pa(input_a, num_hashes_a, output_a, tweaks_a, chain_right) if num_hashes_b == 0: - # Single-cycle copy_5: writes 5 cells [output_b[0..4]]. The 5th cell lands in - # the trailing-zero slack of this pair (output_b = pair_start + 5; output_b + 4 - # = pair_start + 9 = trailing_0). It receives input_b[4] — harmless. copy_5(input_b, output_b) else: chain_hash_pa(input_b, num_hashes_b, output_b, tweaks_b, chain_right) @@ -239,26 +181,8 @@ def chain_hash_pair( @inline def wots_pk_hash(wots_public_key, public_param): - # Sponge-like hash of V public key digests, packed at stride WOTS_PK_PAIR_STRIDE - # in `wots_public_key`. Each pair occupies 10 cells: - # `[leading_0 | tip_a(4) | tip_b(4) | trailing_0]`. The 8-FE sponge chunk for - # pair i is `[tip_a | tip_b]` at offset `i * WOTS_PK_PAIR_STRIDE + 1` (skipping - # the per-pair leading_0 slack). - # - # IV = [tweak(2) | 00 | pp(4)]. We absorb 8 zeros first via hardcoded_left_4 - # against `ZERO_VEC_PTR` (matching the Rust side's `WotsPublicKey::hash`), then - # iterate over pairs in a uniform `for i in 0..N_CHUNKS` loop. The leading - # zero-absorb gives us a uniform loop and lets each pair's slack cells live in - # the buffer (used by `chain_hash_pair` copy_5 calls). - # V must be even. N_CHUNKS = V / 2 - - # +1 chunk for the initial zero-absorb output. states = Array((N_CHUNKS + 1) * DIGEST_LEN) - # Initial absorb of 8 zeros: state[0..8] = poseidon([tweak(2) | 00 | pp(4)], 0(8)). - # The precompile reads [tweak(2) | 00] from the wots_pk tweak slot at the - # compile-time address `TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET` and `[pp(4)]` - # from the runtime `public_param` pointer. poseidon16_compress_hardcoded_left_4( public_param, ZERO_VEC_PTR, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET ) From ef144d22813533e5c8a5f90d260b07a98e706094 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 11 Apr 2026 14:25:12 +0200 Subject: [PATCH 38/66] wip --- crates/rec_aggregation/xmss_aggregate.py | 85 ++---------------------- 1 file changed, 7 insertions(+), 78 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 3969808eb..92b9245ff 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -206,27 +206,6 @@ def set_buf_prefix_right(buf, public_param): @inline def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk): - # New merkle hash layout: - # LEFT = [tweak(2) | 00 | pp(4)] ← `[tweak(2) | 00]` is read straight from - # the merkle tweak slot at the compile-time - # address `merkle_tweaks_chunk + level*TWEAK_LEN`, - # and `[pp(4)]` from the runtime `public_param`. - # RIGHT = [left_child(4) | right_child(4)] ← packed contiguously in `buf*` - # - # Each level's half-output digest is written DIRECTLY into the next level's `buf` - # at the correct slot (offset 0 if it's the LEFT child of the next level, offset 4 - # if it's the RIGHT child). The OTHER child slot is filled by `hint_xmss_merkle_node`, - # which the prover supplies in level order — so neither the merkle path nor the - # public param are ever copied/duplicated inside `do_4_merkle_levels`. The only - # remaining copy is `state_in` into the level-0 buffer (we don't know `state_in`'s - # address layout statically across chunk boundaries). - # - # `buf1`/`buf2`/`buf3` are over-allocated to 12 elements (instead of 8) so the - # half-output lookup at the digest still has 8 valid memory cells when the digest - # lands at offset 4. The trailing 4 cells are vacuous (memory[buf+8..buf+12] = 0 - # in write-once memory; the prover sets the unconstrained trace columns to 0). - BUF_SIZE_DEST = DIGEST_LEN + (DIGEST_LEN - XMSS_DIGEST_LEN) # 8 + 4 = 12 - b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -235,25 +214,7 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk r3 = (r2 - b2) / 2 b3 = r3 % 2 - # Level 0 input: copy state_in into buf0 (we don't know its address statically); - # hint the level-0 merkle path neighbour into the OTHER slot. - # - # We use copy_5 (1 dot_product_ee precompile call) instead of a 4-write loop in - # both branches. copy_5 writes 5 FE, and dot_product_ee's runtime helper also - # READS all 5 source elements via solve_unknowns, so state_in needs an extra - # readable element on the side opposite the hint slot: - # - b0 == 1: copy_5(state_in - 1, buf0 - 1). Source slack at state_in[-1], - # destination slack at buf0[-1]. state_in[-1] is initialized by the - # previous chunk's `state_out[7] = 0` write (or by `wots_pk_hash`'s - # full output for chunk 0, or by the leading-slot init in - # `xmss_merkle_verify` for chunk 1). - # - b0 == 0: copy_5(state_in, buf0 + 4). Source slack at state_in[4], - # destination slack at buf0[8]. state_in[4] is initialized by the - # previous chunk's `state_out[4] = 0` write (or by full output for - # chunk 0). - # buf0 is over-allocated by 2 (one slack at each end) so a single allocation - # serves both branches. - buf0_alloc = Array(DIGEST_LEN + 2) # 10 elements + buf0_alloc = Array(XMSS_DIGEST_LEN * 2 + 2) # 10 elements buf0 = buf0_alloc + 1 # logical positions [-1..8] if b0 == 1: # state_in is the LEFT child → state_in[0..4] lands at buf0[0..4]. @@ -264,9 +225,8 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk hint_xmss_merkle_node(buf0) copy_5(state_in, buf0 + XMSS_DIGEST_LEN) - # Level 0 hash → buf1 (digest at offset 0 if LEFT child of level 1, else offset 4). - # The path neighbour for level 1 is hinted into the OTHER slot of buf1. - buf1 = Array(BUF_SIZE_DEST) + # Level 0 hash + buf1 = Array(XMSS_DIGEST_LEN * 2) # 8 elements if b1 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) hint_xmss_merkle_node(buf1 + XMSS_DIGEST_LEN) @@ -275,7 +235,7 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk hint_xmss_merkle_node(buf1) # Level 1 hash → buf2 - buf2 = Array(BUF_SIZE_DEST) + buf2 = Array(XMSS_DIGEST_LEN * 2) # 8 elements if b2 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) hint_xmss_merkle_node(buf2 + XMSS_DIGEST_LEN) @@ -284,7 +244,7 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk hint_xmss_merkle_node(buf2) # Level 2 hash → buf3 - buf3 = Array(BUF_SIZE_DEST) + buf3 = Array(XMSS_DIGEST_LEN * 2) # 8 elements if b3 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) hint_xmss_merkle_node(buf3 + XMSS_DIGEST_LEN) @@ -292,32 +252,12 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 2 * TWEAK_LEN) hint_xmss_merkle_node(buf3) - # Level 3 hash → state_out (digest always written at offset 0 since the next chunk - # reads state_out as a 4-element pointer regardless). - # The next chunk's level-0 copy_5 reads `state_in[4]` (b0 == 0) or `state_in[-1]` - # (b0 == 1), which lands in this chunk's `state_out[4]` / `state_out[7]`. These - # cells are unwritten by this poseidon (half_output only writes [0..4]); the - # extension_op `solve_unknowns` "copy_5" fast path handles undefined cells - # cell-by-cell and sets both source/dest to zero — no explicit writes needed. poseidon16_compress_half_hardcoded_left_4(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return @inline def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, merkle_tweaks): - # Stride (XMSS_DIGEST_LEN + 1) = 5 per chunk: 4 digest cells written by the - # half-output Poseidon + 1 slack cell. The slack cell holds harmless writes - # from the next chunk's level-0 copy_5 (which reads `state_in[4]` for b0 == 0 - # or `state_in[-1]` for b0 == 1; with stride 5 both reads land in an adjacent - # chunk's slack cell, never colliding with a written digest cell). - # - # Allocation = 1 leading slack + (N-1) chunks × 5 cells + 4 trailing slack (for - # the LAST stored chunk's half-output Poseidon lookup which reads 8 cells past - # the storage start) = 5 * N_MERKLE_CHUNKS cells. - # - # The leading slack cell does NOT need an explicit `= 0` write — the execute - # fix in `solve_unknowns` handles undefined source/dest cells via the copy_5 - # fast path by setting both to zero. states_alloc = Array(DIM * N_MERKLE_CHUNKS) states = states_alloc + 1 @@ -340,27 +280,16 @@ def xmss_merkle_verify(leaf_digest, merkle_chunks, expected_root, public_param, ), ) - # Last chunk: write to a 5-cell buffer (4 digest cells + 1 slack for copy_5). - last_output = Array(DIM) + # last chunk → write directly to expected_root match_range( merkle_chunks[N_MERKLE_CHUNKS - 1], range(0, 16), lambda b: do_4_merkle_levels( b, state_indexes[N_MERKLE_CHUNKS - 2], - last_output, + expected_root, public_param, merkle_tweaks + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * TWEAK_LEN, ), ) - - # Assert computed root == expected (first XMSS_DIGEST elements). - # Single-cycle copy_5: cells 0..3 verify the digest against the merkle root in - # `expected_root` (the actual assertion). Cell 4 of `last_output` is undefined - # (do_4_merkle_levels no longer pre-writes the slack), and cell 4 of expected_root - # is `public_param[0]` (defined in pub_mem). The extension_op `solve_unknowns` - # copy_5 fast path handles this cell-by-cell: source unknown + dest known → - # propagate dest into source. Memory[last_output[4]] gets set to public_param[0], - # harmless because nothing else reads it. - copy_5(last_output, expected_root) return From c20668334e3da294ea2feeed6d38049a48e13f5d Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 12 Apr 2026 00:49:02 +0200 Subject: [PATCH 39/66] add a section "Efficiently verrifying hash-based signatures" --- minimal_zkVM.pdf | Bin 363585 -> 367977 bytes misc/minimal_zkVM.tex | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/minimal_zkVM.pdf b/minimal_zkVM.pdf index c3f56f730e7d3207cf297670d5eb74d4d2eadd1e..d14bebb42ec3d418d66cd5fabf843c97dde396fb 100644 GIT binary patch delta 86467 zcmZs=V{o8N*lru!&P+V9ZQHhO+fVF?GqH_{Z95a&w(b4yZ`Y|hr_TPjs;jH3`p@0h z)vGTJ>$DvUl}u4ooQ{c}1CDHFVQ2-8nK7xa1Ra8joryWgNQ4T|k#fnD#OOZPa1@1x z>8hJF8;?9pR&n2x9G_I>;%ZdVIfIK18;ZO;FU^Fa1&yU+c9))r%r4o!>$>h*)?CwV zXlwyLPq;ik-C4wSX5C3cL8+2+U<(M8ivPPx&m0Nacq-em#Nah3@&f_UROoYLxrcsn z%Py-d8(#cZ6UR7!MQh|$7R#?Qm_hcyo)j+FBI{!bve`K3#rVPO_k0!;(N`B)i+H#L zRVR1Bm9fiNBBIOolF9jlB+o+DPWd%2EwC7rE@3{NXTQamWDj)u!Pas@Ln{L`@HFs_ z%+S&^{sdqs`@q6J9w^PhF5JOS=Pd4_l?3*P1}>An@_|Z@8wA z1J1zVW+ORDWa>h>?_Vw84Rg(PC!I||Vr;5YjT7`)m5dg8czGn-s)S=qtFPBRy?Y6>8EDfR>1wKXg4*=s>)z! z0{y~A-v9?v+^nGO{^F}zTTCADPH84K8wRdO)IDby=m_P72RWAK-BV5_ZZHHa3c{SC zlLsr5AjzWvFnWG{EJqez{cj?B`QA_=B(1xD9-<||8J85~6^hI1zNDo-?&if@h@e*` zXy45yTCUBIWma=zhYK6j4^SRVJbDdFRcp<^%2a{A4uw>W@h>u1EIjvNGK|^0z3!iu z4M{k2_mv%_zX_8XXAwDyAl@rOUY%MGKy8Lhf;vT-PQS@ecKiba76Nz9225T3-*)}( zFJX9mgmrU}18Pj1-KX>1u~Gn@B{9o5-u_|4}RlBLk~9ibGCjNTd_FZ zW}5@)qb^MjLx1zhlh3`48D_|X<0=em6C8ziGH^JDDitxK`E}VW_EE2Q3giXyz?JjL z4i((RY$zzGt7wymXZF2>MWk#*2qFi(XmFi2z~jypmJn9qj;-g+mN`AxT@!L7#D-_1 zgquZTxnAeNkXC=(U;MbI-^(YSYxi7L4iEu0{QhMFg$Ax=g(Q6@Kc>=yXj1GOjPIK+ z2|6Yrb29OsXF=%M7+;53h+QNM%4m6~EQf`$q}WX9*jf*6>ig&;*W)BRXKfhcP1^S+ z#rp`$gTB?!kWm+|Jy|vMLS0kf5I-819KNOh5-v7O*+FY{S=6_% zVNySQne{k2es&&%z@?}C=J05D->%Q*Z`3>~E*WyzV-^~#sGTn`MN9tCZcosd~?9PdGfRf;CUWLsI!5tB=E9 z9iYmf`FALbYG&!oxEgKDBu^z{sqn{@T?{p64F>;1ipO@~xvv%>E2okjhHwQ`#qk+Z z(zt)GyT?r)QXE~txq(#Xa&2?aM}1J``)l>Vl(FuqfeL#?55iz3?ytql>uD;?gf6Os zSHYUbhtD#(`1yyQC<+fb&};SWnXI~MrhF+bY4;q-IMwSp4&5mFROJ^AjTqIU_Aj33A|QwZxWWg^`n8$=-#Uz;4x)qZaH%?>TdLc##C<( zQ5c&TGXug%#(T7CA%61ZX%#ocBcgLUrB+0k9cmHZo?HebiAR z^1?I@QgR`nJ55UF;+>hsp!y9l)CJ>tKnD^U**>>+E z)bvG?swn;47gFukxfZO~lbmY)8!R=#GV#j0;9=l$*sF^ScIVHrCBfeyVBFuCN!m~n zplnH~8C1ZK_NwzbCzju_#!lV45#_~%YDH<5-I|`10<}DYnmhqU`fZtUI(?-4-q#y{ zYM4-8k*Ir|>OB$2p0lIl^iUUw)D-8vtP@t+K_Ki{k#KREaun=D@}n$gN!zejiIY5B z37>^B%P(1CL>76*)AYkXW4&2JU5Q@uD3dziFjxR5^+?cg*U5Mxn(R%0HVbhPU84{_ zXQ8st5TsO(mpuO?-=L-K_$2VuNa*WMbBW=;G*X;r-X1~1VJG!Z5$*$sdGkl zoIU_{q4&NvVF%=4FbY0RYo?a%#~M~p42hw4Uw(k&0*tKH24Yg)>>7kQt0}}>KZG@> z=0XtrgJP@-_-Jgk5HJp@1yfeQi-_1>MpTAs=;cZ|+ME?{gh7W9Aw)9}!cy)0Myd`8 z3t3agp2_8!I{h(~MU3nM)t|&9<)wfo2w*#E<_Rd2VA3-(NO3Cb%x?rJcoqik?`{K2 z6MJLm>^vq7!1A>L1iwE>q?D z%5|&!b48VvmjkJ)3$O>)@47xO^Nf-`!2)G3vO_hhoh5q)+t+yU7szED*PsoTJLC!a zML7b+OC%h<%&0vCC+|-tXa1uF4CsJxN{kZ|7z<zF5&pu_psutLDW)7bu z`No+{RLjk9e!IWP;LVyXW7TnT?SH4VNaDS=q=K{#{>>dQvzM1X?OCS*>>o26A|vU6)*r!wN}6X6eL1B|M>KHY&OIqLId~{=)e`A3@l59yOMkPU+@0i6cw?XdqWu(t$w^`EH z*RS7JIXe5soDq6QGI_uV%{Y~FnqN}um|gAaKBZp92ca4>54g{O4flsag$OuX=42i5lu#>-XkmCY zEswc-N^GvHec3(Tp}g11mi%k^Zu7hPrv8j5ZO6BHHse3ThhHVnEy;a0&HPHLQe$3K z;~%MUPNR0Kzp{HgjLQ8)F(aly}hGNgmD`!$Rp;g zblv)&1hxbyVfQC?PV|DQx{@bX1RwRW?i^{syq_PUpxoYr5NU~GnW`U_5F1VevIQtH z&7|cS4DzDF1#k)mhd5JyX^I&?|2w6_Xf{iW5-*>#;gJ`^&xukbLzcDH(S{{g{^J{^ z^st)-Lj{ZnM3xDSSqImWPY^+t-8#3(g>50={fR{`zQOR8K0dvy3+^MIOOVLhcRfwE zPbt98TN7a-BuC@!+_!wOe;3|1qN+BVK;HG=aG6B4HR%<@@t%rZk=Q^H3`sZr>4LF6 zTc8Rx4~NbZ?}We*nmu}ab1N5n2WQ6`@E&^@IJ%WFMYjw zVMeyl5F-6Jd0vk`uP{+3^lvP@I7!^3-UyD{Wk(-5QQonrrt|=~GYOTVCT>*$X9a-| zuaj_S0rW{Km<>4Cf=ceyjbK?~7?(bs@ z4g*Klz|?CJ#{g9c0o!>mQ6;#X&_b<;(+h)tbv6wnVf!q#n zo8#OzaD(fIcL;zg_6E4C(`4_BdlCBj+;gn#jlmi0!dp&Cy6BlpO({dCNh^JJHU&2h zb#D%eA&!E;6*sic?6)& zt+IsFy#%7YAjIWSq#r?Cyih2$g2FQENd>0RFqm2dg&jahafG$jvSBp1v)qe4%*f9G zaR_nhCa`?as<|_Su!0OC>c}?1kknm7SP|v2h#K`(Pp+QnKCd^g(MMJtTo0K*^JHwB z^VFm1Ilh-e|Mf$LE2(bErOK%lSsY%BTA`6tK-a+k<>6^R+^>mATxft74G#q0pD>?e z7@{CXSOWl=(QOs;?YyJWl-(Fw98pMhVBq(ES=_Xbn(S$1akV?FhPSX@6xwCO9S=`WRnvQ&0$SbUpC7jpT?b&>OZ#) zbGSU%SDcXdaQ}+Cm?W)o>tOaV8cjYa$ff|1zao`~8okn~i;BV$P!#w8eq@~dkY%Of zshW{kaGE}HFts;NOgxF;)Ev*e<-R}+^2b2i8vSivf|LMbw+x&*4xH|DweqfXz0dc% zM_}Yx!vW|)@KmQx$b$>Q5(GVepoKzo-E6Q9u45HbhC7kYPkuGi%`(V^76E~SVa_js zmpczzBGJr4NhUC+d_$2z)q^V%{E#=Jl0d|$$AEE%IMBB`bWAN3BtQtDrB&~H~aafl5=6Rf&W}w&a{Ql@@n`IMZ2b!VpKN4z6e=CTiW1G_M^mRc;7mPaGYuP(( z$2E7tQ)$8#DfGY7wAKPFCr9l)*= zhqdagq8Z)DjKqwy zS%{gvMm8Q8O4(5V7YFp*!Hz%$%k3=bku0~^rPDn5B`2=MsLwgv_y9*Zz!5DF0TLq~ zXN(R~bWo#08fsx0#6Uz++e~cC7)W#S|ng{j;j!02SntfR&)&I zd4mcD>uPBt1ny;GjI$4kqt%ex>>+NmxMsOn+CZSEc9FxsY;?@y8 zuOOwAOt+2JMqQTwWCO5DAkzJkz#*!tN| zD@9YPrGkEqu^Q`=6bzX}fri*%4ktRrRCM(Y-XTEFhosFTy&vE{E61Hs=w(= zO<@~f>|7ZxJJUye(4YG7*d33Oxa0Hdf%OC(KvR;pcHOTD2;kaN1FD%2Tp^9HBr)^V zp`#d==b(XMYB~o&J-CwS3Gb|mZd$~8_37Zc{OCcd0rw9HLpT&t&KQM0*hq&z(BYoA z6%b(QeHm$Z(y&`?pkS}cl;W$xnwW}ry_8^)P&tiieGVdJLxD&|UBQf_D5h@7n_-5Y zKIQ*nh2m!L^8?~PfNyJB=o*JhwgnhtA_*&Iu|`z?HH`9{d=jdH`s&f|5F*fE>WA%$ zu{;u~)DVYYE?l)c(CSN%1_*MacFS$!4P#E)R%&v+-B02__dDewc4n3;u{V303m zNL~gh`yCHrz(2Df4=IhCiZZ~hVb zgD#?A>Y1t@Pz-N49y8ecPs-CyUn4}31r_Yu?}im*b!GpS!M)iz+~f?Fvt{eY*lBpl zcU5q#wvfSyw6(JbMtSz9`VQcGgU1bTEb|SzmDasD+#X(A_tz@-*8!pI(Scg9Q202$ zJDiI)z?b{NC_WvXNQO2gZh$}&Z_sESlZr<4b0xT=HoV0I`I;;p%189uei^+s9$FPy zVMth>^I^MB*c0d*;}H6ijDe_ZxKTqq-T|vAb$_zWJzDX>K<#J4c(5DZ{4&xGR09aILckM;LGC}>72{LN z18nfioQz{Tw1d^m^x9*5<}Tc=RcKg$a5U+UN-9sAiq6ocsh3m>PTV`2(Vx$FOTiD7_%H=OLrl*-m-23hdI1zhTAu*@vOo&X0t*R)`2YIOg zhX>g?qBYH zI{wXTi5xHRlsW(#K_Zt)MtLDmqBboWGyFt(0i`T6(cHCvu~_j`MgZAuibTfbIeNn6 zZzQjf-OGScd->1@y9WLt?7BQ`0~tSpF)~D)Lrk!Pt8j>;P}@^?jr)KzeS%^n3CA2? zRb5$Yvf`a96zqF*B?!T@scN-Xb=ACb*I<%-oKd^vs z!8qJU%hSEw(D;S&jWQJ~#O(5wKi{V}ZJi0XNcyIZ)IaDTJjRpcA+yJwOC#z@Th@$9 z(IW%O&6%0FR35^6-RlS8a1o(ie@`a6-Ds zOMp0xlG&ItN^XZ)8+p22gZ=OZ`f_Z10FSbfocTlR4)r@ZdYR+D4_n(i;0 z-j|AZz4hr8*27L6pdi+{qbk#D%myUakLGSWTB>9xNg0uWd2Bw0{T&qhb0Ot^&B02E z+`pDp3JD1)5j9qBj_4rlegy?gE1F`zo%UUI*%$B7Kk1uW%h;W+Th7f@s+M!pFlXv) zpjeevbgo+%UF+y{@+5{~&kKKlK&c$!pBhoHOX2eFh9W0+vW3{ zo3{!SS|DNZ{-Q0S>VoTybJnw$W`RM&%O^b3j-baKR4GQ=W#MFR| zur_!75j+#AZ!+C2 zR6blcQq(3PL0Y!N3WExW->$WgS_OI3gRHn#!;YbnVi~EJa2Jn-Y;ve8<2Ft<9|%-m zI%(HZT`!=qKvtXBH~NjTxq?}sQ^F3)M=*EEA8ngtN`kNKC$q51I@ae@P$!l7xQ*wAB;>4Qp_cWy^2IHvco18ol&Hf?5*$#S}TW36_V!2+d-y z?&a|}c3(#J0)vmN``XVxGSuV7vGY}B#rks%fBk7rr<^Q-DZe}kq2-4OA2B>oo~lfR z2YuF!h=~`+eI+oYnsO5@_fEr6-4FE%vl(sQV+|qNNO;>EBO~;?c|$Gvm3ZM;a!$3; z^>wRIMv#CU<^S9OOBPB}^kn^zKvp8Qpm( zCtmk=5R4<0VXI_yMd~>%EJC>eUdcMeB+D=_Q#8t8 z*+?}+j9?(?{Y88+1={_C>fpBURpt~;Z;|$pYONzwz3Gb@yQV~0*?9iG+HAEsVbq^- z94Mk`z8iPKB@OvAd(nS`T^+kxuqMR_$_y2^?0H=0GZDitMdF!qNE(kN-gwj-6IHSJN}*6TkqK3Kq{ zUxu=6pCEVOg2=1S8_V!MT&?X;)JpOsAK{P#TpwYF`6ps`9{bFCK|8~20PE6lnKmPi z2TCj_D$>W(4z_?v!Dkru0t5!+dHl`lQzy_^mrn(yhR;`V_Hq}4o1DSQgRCIDE*y1) zOESzXK=Li2JqCB~#g=>Rp;ohEV*m)xZ*5|%GfH_!kyflfidy<9XPixVW9s~?raIDN zJJyaW0L5f5us{F&v>q42v-ZLTk&aC{aaM6`6ZmT#V`h0rdsf>9{deGDOnbm=^0TvO zPg7{K%HY964@<`ys(yZtKc=|0zPaRI(i%Jjf53nyQqqy_&U_?6>LUQX~M2BMmGYr*Ce&FP3e6SXi%7E z^Hw=ioSu>q(MvJ0uT+ZL1issb`B@}iE*IEKuWZX!Isxiz1%OcI4URpPYgFrA#`d4y z=80_Y3BnKj!2_$zd4<)DiBQklwuvIoMy|1Nf)~CRupR`V0)ef32nF+a^EbSPUX%4d z8Atp({S&Cz{c+X}JpJ;>P^886dVU6AOI9$(f-YAw^eb{GlG^lbFOPp7SRYOlVxrk< za&($st-3 z%OC>__8i*eP&ct4LzPkzNEsNE2Y6R8ZNWj#B>5~=1v94B+j21WIVQxis;+T&ROQ{v z;9Yl$Iw-=1+ni4*sfZb6@NYKn`TLJt=eD&Y0uH@EQs6Q2S0N#RQG=v@gxGX7j7pnrMQT;%o-nwB~C0Beu=Cwcx$K{o3CNy4Q z_ec=)A0y@ze;!r3w(k~s3o%o@F*tjobYk_v7|IP_2GjwM-5D3ltvoI8uWI-6L~SfF zjhUa&eHs2vzdCSo?iE9o2lhzWSPyI7$5UT!f#pt1-yyC?4ifSKcc<1EO%2^`!VLvm z#@c#{GjNYSOj_z@7gYFYsCaH4U@6t2^0H^bOT(|q_jJi1u#?Z@dc}d^r$KJl#ocYK zr9Y)=){^N-e60ZLDUD6x+Pdhj35*0%?+~GeHDJmn&bZvI#Rux2f~gW9KG2HC5EqmM z7(n?K_o0^Oj`Kr*MCtGfh|*BCB(3Ml_8F>~O|ilB(&*O4MZ==FcEV17J`t@r@0Np# z^`8AD9Gr$cl^)x+wXqLZDDPMAqabT?V-wKWV0=Ofl#X4f^{PTdLUbpYA*9y-ggl6v za$O9LvXjFn3u0rO6rpk~TP|6)!LK<7-mP=8KDUv4Qo?ZneE| zhRa#Qvt#wC`2(AiWdBxDsq8Z%Hg;qwH}75daA|}Pxv2@DxL;=X1juIo`tnoZ=aM{Y zkbi1+T64Lve;h}`2c$t#vlyv_XG#>li%EmNNI%}7<&A^EgD+!ZQ9|$gPd~eM!)eFu z-5+y&GI8>OP=O*9dR+eu%?aQ)Uf^JOSWOOOM!g|{VH=wGB_OJmtp z5olD*;t36)*y;(-iX)t2#P=Zx+^EuteGVaiOc{w#?R5~QXOd`Xnwuvp(t>n7hyDqp zB-KBsZHW(o!V}d;G5H2TQ(-`2SWFrsY9r%kJBvT`hWz8~9Kk(YF;pMMmx$ph%8E0_ zJq+3q6bRuAywwF7Jwe4q8MyOO)ST7nxo~0UGib53yqFk|9tLp~EPo9{AEqM|%|LMw zx9ytVrcH-OVD_f*lehJn!d~CJNTH(dQixzvV}m|DXh8<2C^5DM=n^$@Dcmp`j@A{{gOLtjWbMFWuOJ4Au643erD3^%?vG(2 zqcRZn=_&$LeH}?;YR~t^OfgAU-kpE6nPzzYjhQYff;{B1uT zI2Ur8Uf5PY_n?W%LWZBp_^$SUvkJ-`p)nq_UXccPWhv_F&Fpq~Ofi8%L{%bYSIqBVO4ACh&9DGllr{XXi z(d%UmY()Lw-od`YlA>KS#r2TCxS-wt-AwB>ViSTD8QTi1F@J&m*)5_gAkr$7lS0w@ zY0U!s?upPr#kfoC&HfWHtdzM@_Kz%UoLi0~;N?74+L=7UZxZ3en+48A>()G@!%_u_W-aAcUIkCPXIkbZOoCRuC~r z9W)42vXc_fshUK0Vj@-6FAijH6B(isV?-ejfH1M!02xktBr>pmC|hx)DPV9^!MtCH z{f}x%aAeZ~e2hMY`sG(G-ya&0bxJXVF!E5wotnVM%Trm^_9!zWoS3$$lF$i=dk?@+ z*=`UPH*ojn&V7#Q5Z0L$Yto}zc8T!)!pq-7P-;?R+s1TS9X6!-$4kw+r#7MEZceHL zsW<4(bsn|fOzw22X>;O`vE#}}w)1>7ro>`4eKCZrGox8bxrzaW!Ul8iT6h7mDypP6m|#L-%@~Zum^sALC4MLTx3UV{OhKzB$0D#gJ00cu=_HT^uJsJ zbk6K9Y)&o#R!3)U8^a<^>8=3Ze=D};5aLBiL_0=rVTE+z)AtJZw-Zz0ri`QSB(rY&+gw5i`{=wtIJ zcQSq8`q3c{$Z&)B!mo{*wQe5E3PzP*CWI_?cqrL1nQoF>;k8+mUhkA3ua3xqTYd(j ze%*LWEr%w38oVi~c7%ZI^5*%WYh8PaQ3JzD?D>_}ch74T@c2=FreEgNcJ=JO`0kFO zUaw;+rQ||6h?fY7o5;W?S1)`R&zxbJ?&?nA8gSZ_ww)e89g|7^r|@{C1l&uwX}y`9 z>#t=~&7)GVYV%lu=FKq~=ZKZ;VP+H06_4Iw=E!H2^VK*9O%1T=Vt?Da{q=c;Yy@6# z7Oij>c?+=}h=F%^O2vS9o0AIlJ;O*S+!3r+-@%9s!YUPpzyXsl$9`wjN{|Q{4fA^q zD#>k+-Omqf^SN9O3wBZ|6KRX=Qf|L9WDw`)@z9!KG`UuI(~t=4bG$7jbh%x@TG6+Wtk&?r4J#wfCZ}D`4beaMIu1@ z31XIq=o}p9JHOFOa%n7F#6I!xqOnD;V6X~(l~Bce`KAmmvQX>fXYs4Qe?>$5N3NhJ zM$z4;G56th(1JcwHi4GxGW2}tu_zP?t@dk_Q4?o|!~qb8L2!pD(wneOed3~=`~wg3 zxlB5+r}>^9A{NX#E`k>a?Ac4~2{}}7i$K)Ey}aA<)TBdAu#!qP%YYg^Z%bkSQuG|7>OK`hD(saS-9B# zCyN~F>Ljf9JADYs=+=~IThS>u-(UM6fhdzg zN*5FjWuV6f@?`nMikBJ0Te?#zF0XD%voHwuP| zqUI=Rd6naQ(YB2a`y8{)@BU1|z<*-hp%ciFwy5twZrLy&I$B;YSEQ|MR+u2s{SwAR ztbtwfMboej{{ zKvsGgDm8BYYk0KRc;IkurIoz<%x|EI{Jeg6x7;3lSf3Vt?#m|OZWsd9n$JlQ!&%(V zcr;||#}lfR`~D>V3x25qWH}JB^##~La=g#68#FUraD zdXmX64`IYu=vtrk@p&|2h?z|v!WuHrnve{l?Ei5%;H^T&=HnRz%?VkFZt0flLQO>x z)j(f5vFA@)c>exgU-qPxGLC9f(_fAi!pt*hwFV{#PjGJ?#(k%lEW{PcWC$cwS7yzE z={D;RMcd&nteJUX`e(8?__iTv92QXL^c63yO4LRZM27p`x6+O?f2jFpU@BPZ?n@(Chntt9aJa z2IcAuM?@)&uuMDUxiTMy;rtu|ErIgSaZ1_rtB6v=-Dp!eAGd{sT=15{`kOi*U&o7fWOc;{?U|wjVD4V@P znbGvsa@^w>?_PbHf8tat%4XKX0j@8-KNq<>|C^M*o~a~5-2pPYMjG2Y#iDFj-ps#* zxy@?vUfMA$=52nuV&+Ed1a_um}M};hx1YR6_8a4pkq)DD zV;#B~C#pDfoeTDWygrst=WoLPb)nS>Z*Gjxlb!i2zeqVQ7g3v~>?N@_!VKI40D;-9 zIFQ(1$`pz^L}r&tis186hZSDlD(lnHkC3aG&a$X)j0U$>$rRg_i-{G9Sa4mcQZEXx zmV|o{AZVF5NA=MTZn}y@gSy_$3rfZoJSEn0?hiE8_;X3nwh$8JPAP*Ix2DvofB;gm zYCTKP8=vku#&DF2R;$X~Uw#^MBNhY&p+Ke9QNzk5`{)+FHo?j^@e;Y98NQ(q82~Q@ zwp=22C1PDP=$wjI7H08}8P%}etV#~q~Zv# zn39+nJe=(i#5j6<6YZB|eX54coGXTa>ea}B^N7t%LiyY(_E=khs8fRPrJOCj4|~8W zg&C~>O+L;ZC?>@FTYp1AB38ZU7w;>x#oJ5AfJp#uFT^lW8TQ~9rIjw&Ueqo_OI|$2 zZQMNo4<{XHMFYt}w#y%m_oH~UchgI6gl5(oP!iQca2 z79^{ZQRphk;3P>{=nAnc=Qxf*8&QJjBYReZirk8%9z2qV2z)X1&vXDXhGjSb`R=?3 zL&#K5wm8&OT_j&TKf@|2>z()!Fu{Fwal?betz4iG4zc@Zu8gyZ65S0?`+FlUui3FE&AKPx>yfO%kf^HgdvEKhIXYB%AeRL|APi>-kt^d@TPA^@={b zxE*CZK*4WmLuB^R)Xf8H-72Z$1Q{Ku`uHU_M2*n>X*^2r)XAKuEYuhD*7by|VAMg+f7Q?wq<7})LXSnzrEP5oi{wa*F zH}S={glDJr1X2{r=k4Ej;;nuh|nD+B}x?->Wd82+z5?LFLC=R^DX+ZgQ zs#4@bWlJVKI;2&{1tK1?^oC(X)#t*pf21)=LTh!s^C=CS7wsKVRlzN7K4L@FhS*IDsfx@%O~G~Ne6qO znk7O2y_4HGC1Q3h3+wMXVSBI3+i_Xwu7;@<;(@&;_(~)#_hH^P(d_f>QA@Y zCW`=s^sk9{FS*}$;a~moC>R0L7Yy@vsM3h(sje*W&1uA?%N#7oTwU8_*W=1k`Z2RE zk9pOp`PZ*045JPTgTdHo#yrXmSyOWs}eO8(Ve>o3P6jM_^d{?~k( z#Jb_Uj~b=_wt%;b0h>)NlttZL3LVv7zMUuNCp8uMDyGt8x%GcBh?ehy3`DG@v2cih zD}()j16F09iWXyjO|LuWRBeD1HFyMJ@-Druz|!L2?zEzCurrMHK`OT~lfp0YpMw!< zLTad2CSrtN#$6N@@lACcx`9$psv1)u(QDV-SCjbiO!N8fa18PEQdjXyvCBV;o^1}& z;j)O!ZyK~~O6<(SbR1ghA{nr}HEeSL_Grzx2vpEW2)~g)*xIsZohr(Hj#_8v-1`SC zMH8JiWgxDVX-pf9k$X=NPuP&SsjF-fUD>^Ycn&r~W0Gx%R0LAcAmonNYs+wMsCh21 zW$)t0fYMIqZ|BWj)J=G}wOVl45P5y9(MJf7jcNo0`5HwT1QXJbUuJ@Y2+@~C^lPtZM z_D{Ih^Pz)wUFIdR#J+Fbc`RU&)mJi>-5t&a7#e zc5K_WZB1<3*2MN5+sVYXZQGgHwr&6O{@>|y+-vRCC%e0=y6WnEu6r(hHTOo^skq+? zsGlI9Dw?BvnsHhhXOV7yYA&r!d>KHX$}fz8f)Cv2DXZPRI4bM>S=9z$ z^Faht94f|(iSYtUk#iE!sxn=lL|_SkkSA(xlKlM%cz%@t46-WZoW8DIQyx3k9u)7l zCnnv=&s~q23g|cg%I z>FcaUxD5vZ0Onw|W`t$5b6XWL2ni$Q9Bt%Jxf_||J0f>qg~EdgpC2UPv58;_nu3bT z)FJQ0IUEi_%tU-}`osowLlj`!#!eY>LDP)ba^sDWt4eyZb8EvgTJ2@MXB2eB%wRE1LR{-;c@y{ZFOG(Y5G2a$tbDTx1LOO}h>EQT80!#S;0PQ)IP*#=CNPd7seRwwggKD4d2FmZ}{Dn%txB$5E5{Yx@Tv zt5S?=;yJeKva+zho#DF`RftK6G#>&L)03UI8{>V4-^}9Oiu@u8E~$;3qZNC`XBtll zMtd*U3~XoQ-Z6h@o#-s)V}y>-!D$e)Y#VqDMnw>$AF)>CbN`&d+oDxo8L%@INAJ=I zP(6$mWMc;qT4m4SJkY7+>ZzS*d1f0Lfpqz<<6;>`_{;bCqW-?<_`DV(3jBs_r`k;U z55t8yX?Ft>R z%>I!U0AFr1aFRfH@rh%mNl`#ZK{+{oep{>sg%`;9UmoJ#D;Ag*JH4~!hv5slaSQ^n zc$?bnEV{_VN2mMNj||>3*!Y09SF4A$7e?8SOm|cM3r{xQB@bghtZo$hkkVX__9gL^n+Im9RSJzJ$L!d2mSPgVid8` zn4RmT0q<4TyppH(ydSahC~f0Xrl@KilpaD)Ea9N_lRQ7dFvf8czj(m{|mA)=$~o;s3LvZgXR7KGFU&aa>Fx<;_*XDwf6K^UDq)XRxx(0A9RU5WmSpnYV0XW2G3yby>BNdl zl%pB+G)ZR05_aoQ+9ULl0oeGY-kwrf^-3PTyz#V48N54Y-6)^P-tAyYLC*6gsF{@| zC*KdKYxrr)wBoedNJdUEwY-)WYVW`SG^LGm{eS8QziEQfgi$XineN0#v+32TMS3F+ z*{+Cv6%eO1x)ujh0#%e8f#IiBR1LZMW_QWxWXNp%V~jI+$pE>H#Gp`*6YIFVdTo7Q zSt&AEPpKEXG=%Mz_{1fR<+}AmxBMV-Vf3C1iz1C0&RD7$uNbTOw*$6A?{6=;c{7}g zXQEVf@ak%k@!eTG5;!0Y=|n7D+7YkLpUnY04B(sqy&D#O7tAcym>V_R@E#f=Z|eL? zuQR~^`GtSO9I#XKJj_55k%VKMNI;fBBA=DkWjrN;EDs%|#?$6yx5-bzFHifB5|c63N|p{juuoD}BB2u7R-o}M z4O8vI-*NTA&i+mxE#AHJmjR!#etZJ)>7KZ=Tkjj)9%{NcuAS@~fH3^PIzpZ?j9-UT z;0_6gNkcIe3Qm+DXMzDy97r;j-h$3~2f*S=SQec|qnSG350a0Dra=$o;nCDSPZn}X zLLrj{%9J;}mq76?za8=ULF1gE`ZeZIF1TVW1=GqpHisl{IV63>o1&ci)7jo{GS8Nr zu6}M?;*7#gn?M{JK}hRuq?-f!xVQN1QORaQT)lH3KI15qbq45;rKvbM#kJ@$0lSfZ z!H4&!ZPg6X1u{o1sEo(3&hb<9f{7JfeCSIA1Xp&OPGtiI^2ByG{#m7R^CXdKuY8E9 zV)_bm`S!y6aV{&Z@QnFwA-D$cT2-O9XP-NmC%vD~_fL$S`tVTUlF&6{;B9pqCrfx_ zmmT_BMrzFRMY}m@am=T3$@H8;0BIL9URn*;=I6O7Zzmwy23{z$3_H5Ey?g2TDymZd zWFat(`Ws*tYK@Bt8Psiqf(!G>cM3j;tA8{4q!BQ7I7I-$bKmm$qj5JX^E2mkB{AsS zZPSBea)`hhY92IMbI$`Am7s0fJ3VoB`>rgOW1LLIq#w~X*I`mnXY8L32)QltNMfHC z(|@MwC$n5ax$=UM!Gwh)=>hpkUiOD1_`{cXPaKM?APv>tnjv%lWk&*zzpo-Cyk_;P z3Xzz3eb22}44j)&p07)Wja!w74YiydX!Q4}#&AImD>c3^Mqs=O1ulL3x(d_`E3*G1 z%XAfH_%&fh`touQ*M+AG0I|Z*g2(|Y-<^f&*`C1WVgBiG)Xg;87O{xSIZD`%_=Rqs z73?tr#0*0v$eYr8Bu<4Z?9Y-AK%&LA-}aI`>4IjGd5uJ6mU%`;j^mU@lp%IHW)%no zW)i7axy%KIo|=Zy1CC*U9wkNq%w^r&5qs|Yagct3z^sg$JL&@sAVJeMKVpf$du3tU zsCY>(_vak!{8v2;Qlwru>wuY&%hH=krRF=JBs_H)Tp^|#jQ67A-_6T^%!}76ZuIG4 z0O2e%B5P%^$j#VcO7$}1MF4Tv(`&u2L|OeaS~{tPUDx=7jGN@qJzeRev?$i>g$4y^ zPj41vkWRBYe?Z{_EOC0`Uponff13|pRLHzeZ#)3HzTc)NHBLGaV5qbGC5c9bh^b@8 z&rx)@s7|E**;o(U_dhOh=o8@Io{@0C=M2&|{o>!PDHJ&D0qoWg%q5gELNXUroWvxu z_qK0o21id?G2S3M|!nET8v#+s2sy=HDT(F9{>5q2=pX@mXO5 z)yB7;j(X@yX&IN&{WQY;BjkOS=FbH%&>?&lxXdn+4rITGZ_By^gXa`qfD;6uDS&=i z`=P~{lZQzG9@Y2J*Og*+N>E9G)5mYaEq~bEh`kH+Yc48GQ8|rBm6h;a)`aYq;y?F zjNiZKv1d8m(95t_i-}b=%}6(u)E^sZIc{U43Bw7m41K;yEzAuxWV%OYBNxjUeH+Og zM!wxN{$G6!;rf9;SVaO_)v?rYDIl(mpc0hVBfhPetuzf?nvBZN)qOc&9od$JfKCWd zM5x$tjfd8E;?dIAS$^!H4g|0j-wDi~nz)cd9%}tFrY1={bt2rxNV#j13@P$>B^+2Wt<0)wS|9Y;D-(V=)Yp~tLx=cip2^rLao4ZCETMM zi9bmc7V0X#?SJlo~~y(2-jJJl9e-&%VCVRKFmi$ zjY42+@_hH&(nN@^h=}ZVr$9Rt@A0U_l8246zB;fmdZUz!G$&e%7BRIUy!S)I!{pdP zszq3l5k!St{Wzw1?~(7(ZmapR@|w3~Ll7Q^)NEdn_{5lD)cMEaM$ z?ZgpdjCdJmPs+-!vv`<#T9mKIAEjr`A<_&-q>TN{jDof-rPVbqG@1fJxXEY>mFyFt z0#Bg#T|(NeH!fyl6w(W}SnoHwyXL;oIGHdi6f6NG(+`#y%+&ip5{n#QY_o4fP^k*k z6|Oiq+c?`}1UxhC%ynBO0d@1SsLS0+lb}`so=LPm=~N{sj&{3}?uX``&o3L-mtw^e z#kb<%i5lPNlkJuTq@3hGDmbpen+fB zS9;(`%4H%y53(%@<0cy*+lp9QCq~o>{aF5EK-8yn;o0bl?Utbpb+HX3volOyz+-y@ zmn!d0!NUOzP6Lu;+RkaH4N3Ij8`WZ}R~2#YN&>MO<~24!@G*Ft%@8(A@yFzw4NmZT zhZN2|2hA2?0C7o#S_Sk-g`ng2Dj{g_jkh&Llw3{eOzzER9Q+snS}5PR+tNCg6Tg`; zvsqT+i(O8Fd|lr6Veap9$}Q4Ey=n}d=xt%l=r32q;56fxM*IugQJxEqmIdn(;r`bL zK?8U!gdCM~Fc$BIH0MlNBS2=_g6+r?JtXcmrOx2B6dM_0nVggBbKD^0DnMzmU$+AF1pbHBl>f$aFkDU76}IR@?NfpNv}s z`8&y+r)e~7WX1~w5R4zlF{p>l|Ik(}|D!yxGP862ALYR|Zi55myQg=6(5$KQHe1z; zJFlH%1p~5ag#iIL(NEFlN}Q`#EU|ICw--C{K2lrxw|8Thh_``_7GvSw6ziZxC|mm0 z)Y#<><`AH_tHa}2IzuY~GqQYb>8Z+JWY8jGJPVe&Q~U3>X|MW~-_kT=``(&yr&VW3 z)k!Mb24ISNvg71or`C^0(&sr6eps(TmzKY?$RSdSvSv&|!IIx&f4Z2A?009w*=>tO zZ5vb#%Sc0Ou%bJ&hZaBy#hp1BwxZS@Iss2iJhy=)vm&M&!9eUJRW|by-0v*fUn|XY zgr529dq2uXa@|iWaVPz> zwd!tH{sZnKh*XD@jV2|bSmips^1IlOQ@O*JVWimSrG_i zM-`oPr@&HRtu>nVdqv|ZYPt7B@J28LqJY({(L-`}utY>$a>D1)(Bo#o-{vr~h}!{; zX#4efcyI|(12aQMPS~ETKr>AIxSQ_ZvnCl1Nh-tpkzn_~Ou%s=$As&fX<}Lq74AU;)DHe*~ zReTTKy|dudLQLHK*!A^9k1+xTJ_1ht|2W>nX~XsnQ>#jl1oz2M(WRO>3q$7TZo8uK z#sT$V+94I|0QszE&1L9HUv0a7^xR+J<5k#Sw-sz=*Z7W>x(I%h$nEf)XNrB4+?gfu z)}Ig8+#UMKH)4G7exLK|xV=?GZPPZ5)GTw;6D@;JMzH)f_$gcdty<26Jq;+$it%Gx zn<T+q;rmtk8|J@xB6cyPb{imIv%BZC_%a0y|6_Qs$G$D4ge5)g?FMAwkXZbZIou;N$@Y5$5P z*gP@L@V*sx&FBZ=_n+fwG zmtDqVOh5D%p2YdfC>g*vki_f(EMIVW^27Zt7+ z^TXkJXXo;D&SND>SSK0#a24F;#BzchO4@xUk3Aa0{Iw-ueChn4J(r5I!k_x~ncyRK zO*k4Pn&bq3Ljm(Z7Eqn3zH|@$w)PCa0{ZL1Sbs98j)~di!KOS9XRYvNavwgKV5>kJsr#Xnk5Z(f4}&u0D}8x>5dT6jn^WN zRWnSw^8emK#CV$U1f8jxYdFnwjH`O(aQ!}CC4;ZhI|jgbWMq~gR=%g)`wTVv>L3w+ zS7!5Oq}2wXmR&MFL^!ev0k0?mi(waPD~9U8w@n^FVUn4> z@<g$JtdaTT__CL}d&BRFhx_R)xAxGH3f*BNF1Vh!gv3?v1OfTXul$k` zrGZ(cqsPTf57O?on$xEDSWom_w;#2B}H zHTG@X^^K=+#;;#q&+&rVa_zW>*b+9ouLD;Tx;^S(0!O1nMK5L~^|H?Qn zLQ=|2vz!xT`2@q1Em6s)a~Kv4g5{hmX5B@bNC)1WCAs`1h;Sqq zCw_IYDJSt@9x7WT(X8-aM=HK9_*`+v^fYo}81?z-HkhSKo$Rn5iL!~16Owr5*W0k+ zuWVk5HX!toDSKRs&>6m5c~svpWfd$wVDm*OHqcqlePpv}t$XLqw7#ZxghA-RhUGfr;`QBQ=#4TS}fZ$KXx zLFcrjVg9vT-X@BV%+>s?+Eio!yka2T5*X%*cvo$ z`k|(Aqy7X^Cc48edu1IU(QDJm&<$&T2@bGz-zMl)aqB;EPd7SCD1~ zE5Wd*l;W~W>1YCrwZXYVLwezxLMKi7rQTkE6&Eff*T}LVyO@f;d~eVOf82nWD89e( z_Wm@Lx#e7U+9Kv8b4fc}RTVFUh0nm|BJ zN)n|7OzUhp|C1ts7lZsWRQ6^~AC#gbz9OrPk`=c05$6?bO2j774I3&Yh zA~9wE{phb*%m}c4aJPEGUOj&sCw`+O4a%vn9}mC#!5N-xQ^%BWQzt)mDc31e4rbv| zO#E(hKl!fvY-)EZILqe>p44`E>%Pv&Ru8{C5ceYGdmw9{qwlT@|2$IdbU zlsPzlBz%t?{q2No5fvns@fJK+NK4A~b4lK@Tmqx{Q$xY$f)#A%PAsC_9i#kw=(6g!zT z`S5GTYv@ceM|0Ci1)<&g;7RN_wd1BW34tij6j`rq4t9oZ+o1J+A)zf8yx7kB=t`F= zl!vRq4t5bSpopDeyO((ygZh5R0R}p^e>TO&^LsnM&hU*5 zb#BsbSRB90$2b?I!wG}GSdef8{ zwxb_5fPe-5M!oZBqLO75m7vq2gi{3pHW`aKje3sn@SMcUNg&&d#7o&rJV6MG>hbij zG9XbWM=@m)>sUTcg>v+0g9h{C(;G4eqpe+>k9EO~I;Z zu{7sbs_|HHTFu~?S95HRhry8)3RVRf;ZyRrk9z#6rpy^@ATTu(deS4n3@;-Bdg_o7 zt*|5=tM?^{dDV9+fqE@>92-pP@NccK)I&KaXiORI1H> z;s;%F+mjaoflB7j%$6f|H8e^5E62Y=#)K_26v~++@V+hsG{7m0U{A1AP&?IHNByW` zjPr3IqrkznwIbE*w*ob;I8YIDt~J4Qw{{E-76k z6Hzm5c|yC5ZEzMq{2|V9pe?q)$%@crm6q`cj_0A31A3v|KJ_>dGk*cPtv#UMH%q3#{^6Zr%vv4olM+DKkAi%S-;?=N1H z0o;@5-3R(knq|74M1jZKf*||~E&=!Nu%Vth5z3o>V{}~X4ET*hA-M>0AFx{&aJB-k z-#o3h#0VGc?1oFp##O+mqC-a|$W#+AX9G%2kJAzCkVl-oe%QjN#CVp{MFXc^Rr8h3 z)&omctf2&Gn)CO$N;3TfV|7w*xhw)%$fu@A%-Ey6~Q=;XWj# za>E2huih5gQoL$&4j;0nWiDFV!Mu<3{(Y^tNoJ-`3G0VXln5rGQExrHn;oX}*LWIv zzdw-tvnSS#Ne}@6KfVOW{z!c@71q+@avJQtG7%ndG%kEQVz3W{Xe;ipdBPN+mJZif z*2p(j@N0G%QQ71J=XY_W^Y7x-e0t{Hr?R1Ve+U8lj4c!=UPj)D8V0(C z7>G#nxpl<#yj*Zw_LwC1yh;YlMb#T4x>NL(WYvODU<_rF#wjhRB>zMUgnqJaE<@iS zc?dHA(#CA%UjJ54k?~l&&_*it=b>7=&?dN+;@wM^ru|tuy~Y|XP*mz0xnZ~Mg>N1+ z7SE?PHhfFWX)MHRUdJiVk0wRdH z$amVGZlo3lOacraNgh+g_>54WbIhGt=wR+^eD9{Dcqm)^VkgmRf}Acs!8e})J?vbI z*DQr(Nh5@D(;mL|*D(rMkJk$@AB?Jr>Z^_{hwR}DskR!58-;JVOia?!%kVp!n^jN% z&nVZs_Axs!F_p?=p_G+z!WaM}BN!UeCn(-1i%4`HmWzNGjFzl0<2?vQ=yMOn(id+x zO%n)AhG@na%vT7QH!^{1<%lFIM%Cu+jax&P$qq0xDxQ?D2;z#ve6{kR!@x8usXfrKCz_X zF1!$wJ-gcaGkZkCBdV2v#5DRF>NZGg62m}#{ z9X`ujCL`^068jk7V60SPSqY<`xoT)bQR`v!><`?T-q4HN$Q*!wdGBWYYm^`=4`c_F z%w=AjtPB_Ja>|Mw_3ulGi+=%WheagsIw^S^z&*@9UV>&fJS#2GDVqy-?l)D zD1o`i%NvT4|GgSQsXKR~dBHcDd!m1HYiivZBEV$i<4UWmF8~!e)7=RWpt%U+m01i9 zC*hffU;{kP%QnAJ6nF_egjjecu0|I$JOC1@XLsgs2=Sa=?Y7)WHC@-(cS5c%*a;9F`&OKzEkXRtzg#0%2Sjs2Aj^UgDJAiZ9m=Ax=4eXW?tA31 zx^2%`CT3mNl^yJOsOAHfnDG=FAJ672eN}lBix!0D{6ioH0{!O%j&R`Flxs$@PD5H7 z$aZ@GV1)(>WcBmR7;R;dmGlv9y1y)o#Wkq;Y|+(5(-nITP~*|;CoVFV%Nv`D0~L5^ zmVeEe3p}1~SGnt~a9&v_Jp=PFOQpI9m$~hAS<*(-6gFefd=UeX-4Q^4(^o1inoj1; zlG;)`tEt+Nh^f&jQH4fUx!o_uOrUyY@PV zP=+Yd0r|&q|4xWV1Fpeo=Hm70s#rF(7mb z8kJ{(a*yka0hyTr0)A-+7J0?tJu7TTavEzO2+33p2$O-_q;psE&rEI`C&}@tESWY9 zEwyOK=YL;2po?&YNm9Orc^!nj9o2$JWJyt-2l;l@+wug6|Jlru@{Cq0jzrfOREnAj zFjXp4y;F!3N-T|C9Nz2R0sZ)bHbrqxm7ViTv|^3~{=6a3jNbI}Xe-+Q&EWV@U@VY7Y>TE{Uxqq}(>)xu^>|hRFjS#5%XRezSwfzc% zEgj+yZ(vpm|MCy3hIzBxSHgp;Z>f-f*~03+nyE>JZf4ysWV0dhOjTt%;v` z`D@p=<<^Rjwf4sik|KGC`hcdGxAo3qI3QC5O6eBhKiNT3iXhT;#E<8X@_ zXhaN<0q;kboerSYkwuzn7vA=gJ)Iqm$-*o9BkPTT*C1ACEWk6lNf3ycEqS{UpiY+9 zN)oVK=z)4$18h0S3L#R2IFPCRs}2n{M_8xx2ZahDdLq9?fVN3+AmEm4DR=053b<&< zMX3Wor!#Kc<2tbd4VeumPC^3_*&hZjG;upoR~W;}LFkWc8y( zj{FH9Q|*g#0dMdtmU)7sxHQ~OootxkbUPj)=dt?jGP_G3T};A(O=l2Tsz%u4gNN$7 zsR*nbZI&~Umd|!2)4q0nKk-Iv=|Wj^WfK8Nx+;|;jA=56b9rLD48(~Anes5uy$uEo zrFTfqmbMhn$Xo`_-7{g}l862E-OX*N`rr^@#@Jf*ax`a^?4s<|QkmC6*9;mt7Ljix zD1Cj;+GKYlNZM@wfViK2YuVma=NW~+6XTxAP9440IR|*FxNV9&g!bfPhgScRwTqi= zvp3TP#={4m8@7w zwG@_og*2=kz@K6~r#@KkfhoUqaL$%T=%44Z^s8mR6!4Yj#;X3xElw@NY;XUS?vt7H zXO+)$PE<%fC?4KzFe=DcSK(tDV{uoxoBRMQgIk~QBQ8Gsx0akfsUeItj?-!DuvH?w zQ9~@!F6NO*BVZ#=!JmonYn7DiAmJ&jfA}_gh!eP(&`j09g$O3<{Y7R;+)O>p@R%P+ z?Y{#u{~`PTk4Xv2%E^>u^e^DKr7h#U$??B{=hQh$#lBHECgHY|EqBBfmsV|ei}D)H z2BbAaqNU>)^LRql=lhj-5MCgNgxfy9DI+V<_UXwAyl7OESd)hvn%sA%Ev{o5(v3@{@W!-|if>=}d zARRJwiz7jUCVdKBa(edlqFd5|*&viUuIoOfOioch_mgWXY>$~6%(3dv7VF&xdDOwK z9-7#8dYZkY!`{`$ttoXeOoxnb1Dfz|z*7b7d8ELO7)@ZzFD;0Z=3=o7Zts4-TDPlW z_ixDUtA)kJji2bFiFp7o#`7eFt}B@lwdPnMoF=5Ju5ScYQ^#Wj2y7wSb3`C3cw`_} z)PM*|Nb4Nj0(=HsQOnrrfHWKxmhU@3Z$Oj5;kaBxmzUS@IX%>$(*kQY{e&CYlcK3FXF z$*t?8|B?$0ctA{xW1Ea+-a!wi#=Fe8&F2W65xLSZ+nqZCQc5_AA9E7UhGjmwH2*OD zC;_>W3%*9d0|#(g;(^Irj$85Dwb`ybZNVT(;{p|95%*m;`zyR!)Y`HX^80fO>MDNjYLLG>(7=XlE1+ULi%gq(2TvL1PWo{cFy(xk3r1zd-|2 zRV|ly^xAF1>DuQ^Qz}6oIS4G;fYhcF{rmxX=v_ zKfpU$$^w8qdRwO0uP&aw+of;Xewa1$-bodWdREtb%LM%j9E+S#7Y|ff1tj>^G{1pB zCsGoaSD%Ret8Zv1sw8rG$~K1a*F0o;=c#)$@5r?)ug?No#W%`LN+f z(M{8r*eI0RN=1p-*DadP9>8>Hv!Xmx(2VYxt^uI!ZfQO#s*w#%Pgd8dX32d~B%{0y zU?|v5ZpElR#AV99z^0^t&`M7OpwDnYpxM`&2;IZn702%7OXLMK!pNbFSx+E@v(XhJ z-3bXITi8V(RG_J_?3U`gLnzG$79zLeHh5rcvc~h61)6F*l3FPZoumSn$IhC9?JBw)QMk6-u*yKoi91@66^ydLkufrnx-;66bwL+-%Gbg9I; zG;L$Id*U#DTYh4n!Bc&WxNOlWGd0xv8w1)soFDBY_p^t?+QAx<+I_OhG>6O&WJIbN zof~S9M{FAf(fq%F6A>&yj6rHx(m2rPp&2fVtPZh`HG|8A2jSk)p>b~SwK;dSRDD5@ zX#*wCMS<6=*K7KP^;Ne-Zq!Xl0(az>+8qYJIopegTd$z;N(~_ew3e$djbf9`l{*7#BnrO@wdOJ>TD-f2BG%yyf`( zokeb7G)cbX!u>ipYy4{+@>YxHw*!E%uva>1JF0}gDy*VF`$AzPs9!>=LD%4_#kW$c zr=&J!dvW?oiU#BW8+%H5HC?CCf~x91Qk{$Sarv>nf^$P}g)s)&b2Jk&9b~S8eMTPa$@!y?q0-&; zaCytN+b!!HIH3oE5hZx(?M49X$Y)tcAWk4Fp${v`%2v-L69@-=2ILriQSr*%mB_$z zc3*u=`XhvV-PNDWN#l6W*UNIdj;6<-eoa)mBSUlsxo`wRaee|KX_<)M3wLiYuUpov zGP0WmXZ}7A$m?1&Wyb1z^qkEJEOp^O4zU8)Z>!3(?w(31q8>!r-Iai4bem!eGyUzB zkT310tgXrvDf?@h$(DkQC5(k{bG}Zzsqu&LlJe({I>o2lr{UU-@|qA+8nuQz5yt^9 z{y5@{ivv7l3Lzl~j7Pb8;sjt^n1U`IWV$vI&p@0;+yk^c9C$x1RUZ2?lxburCr_QF zOcF+&xJuy=Ns&3mk6J(nt4u^6qH7uxaD{B*uICGUj+U4qf?YfsxAkdt^+uvVYc)J~ z-b5MXC#P4p&a^3CUi*D1Z=RXj@o0di8>v0LWYClhJJGJm<9Q^M-8r}W3E zQ0vx84>}*4kZ1=B1<&7HeHyQm^;M zr@IO&q*I_;HUIEm?xB$rTox|90NO0?XgPH% zUK~dm(<6EbRn>#e*qfBq@-a*5&RH1e(G;C40`Rct@-!xtY2C%BVwwLKiB)HhKmRZ>wSDNHQtFrYPU{) zeYP_qt$t9W-r#KHVt=rTlKEHzfrIpZ{oih;DgYB(*`bXRxTl5VgsfnKaiCijHP_zV z*DU}{n*iVhCnjqA8n|CDN`mu*vNK+!dL?X;U)DoKnP1fWw(Z}F$$Z=+ABOvCw zQM0Pqy6;NnKNGLE3%fEVOPAq6EK`l`ctI6B6b@JmMpA67?CY*rt*dQxMTMJFruY0! z$M5^~3H;{~$;xXaCiPzmt!i`4Gbe`uF=TAV_=L?PKeJMq5wbBHgrZ;Ftt`ZJ=9`1P(YHSW|adjD22M2_i6j9TJ1;N*{YbUANrm4hJ#x@^@J&&k$ z{Tc9nJRC*1?Q<|l`10#P!%R<37Xmnq~hfp?)B>N5sJe!byVCgS{tZykNmcP9sQ==Yj%j0RT+c9pu0u3`~f zQnjq2VhoGuLxWMY!uFt)$p!@mB?5MuigN{KbAP1 zdYF%CK~GpBA$4}!)8I{?vyLjd?IZYzDCP50XF@}OF*#JaO@GXNoh>=RncCW>0`ob| zorLcWpB^|l>x)so#lQh3x$t#zhNBJrw>Sx0q|QE#zz}e>|LbMk-D(-=ySZ6;c*6ii*>=eag z9a97TSAj2_K3fy<40CxUMMiRGmVU2Mw$kCm+Rx&zsXXMQ@9t(=_R8$1>c`h%XK=Xn ztlgy~sK#$|6AE)fX!38{*e3*}2P?dN+?>}TCokXA9$2h=e#~`0_|+^tKPmvnk`V$b zSL=EZ$VlOaXda=BA9lJ0IwS(k{E#kW!{hBm+a7lRm#8_-;e;DaN;QM?C%__|Gpfnwz?_ZuZ9n7U}?UnE}~V znY+=G>XW9`AvyXC^!%d<3C7l=av1`g2e5&083%u=S2{}lk^E$JK_Vjj7tenu zM}ru6pZ@?-{$o)7>m-75vHXw!L=C{zv)>TE=kE=)FLuP3{Mwg5^tJSs%LJjHA88|q ze-22oj0h*|AVVJw`?%w5WTmAq|8qHHb)$$TDeL6U)%n2Rh*vzvB3)>oQ|$9~f4){a zV}|@kDt(-;qa#APjb1fbCW)%Luu|-i=lfl+xm|B1S8u8*{!Zhn#U8`eof(j%g-zRq zc6Tg%d}qhcc44lgfe*huP#nJT^wqM(w$#!4`gGo@kW0Hq|FTZvDaFbX*HAR27Qo7~ zB*oBRNr9qwFD?ocB=17t$pq+4i=aa+K(kVo`BRHA`+XwRS&wh)-;G}lB1 zeIj>~8k*2I)3wDM;+J}`X9Pq?RMwQ&thg$$>TUh&bH2@Xb@ydHeTn7ogOQP``7~EJ zBF@QX@F^7sUPTnKg{I`RZg{dU>bxS-QWuvv!9+su=|fO?^P^ToGa@I%hN-b5P4ITo z;$*jG%CYLs4^^C>jr0|GA_M+bp4BV&O_4NdciE*9n=#W+%P3-`TLR3)3-G(`{|pDc zk+YY>?b0TKtQIH2q0NGv12xc%_bHnbEbjPIP#++P=O>|Dj)SUGDxM=scm|rzniG;E z(F^OwUF7*FX_Au5BH7#s3;5<75q!!PTP3E{6@X>SY$YleCACt#o6WP?7-|*B65NA~ z20j@o))Di^?tm{-p#qZ7;~d$3wcavZVgx4>URn`EDEs;(zZXHoq|=Tp)5cc4B-c_^ zSq?OCH>$xuf3+Fjrbdg4`_0RX!HYTLE8?zh5MpLg~gLWhvU{ ze*`z5nr|;EEkNw%C4uD26kU+hQJOI4x8IJ}?2&=|P_<4isL`N8BCbjBMzE>SkYM%m z~n;@~U3)e(XAU==O zGJee!H*qt2tpZem?uSbF{zfC)7C3_z!V5`3+-tl=DB0_b#v|TYN1LN&~gRM7igDnBADr=i>d6v-;|{%}1Lb|d~|eWh@x*@6L-BU)(F zqEbS(zkE<*uGYH|=V%ln@do^Cd*ZymW1k9mzWM2MT)Ol0=$&CmLRdTU(Wd`+#i0C?+HE%`9By5s0-C%+Z&+ zC^*WUy0K-K>WnqIMF|dV%`H!;4eZd)|4dw8>Vt+;}`NwA%`GQvum6}EaV$} zsEMZ10IUVv4p3$VV2vod=+I+KyLpPPV7W+R*n70>IEGiH%LW6n2ftPeDCX69AxI5R zp^+dP^2XAIKqw!V*NqbZb7RS<^N?GDuu+6_bnIA!V=1Lb8BI_s1wo2>W(W$!2I0(+ zHjrj!`pbzPK6|H7{h60qQVE}@E(#JC6iN=l0U(nHa{DdW)e z@Bt*15v*lZ9Yq(xY^2d{Ug(r~)Z%@|DUr>Hz^Mp`A`HdB%1PY?Au{3*+`Lk53`XAC7e$>=*-hXx z@KveO3pn zTPQ(JPm@zDffY}=B;Ad~B@uREP43 zQ1W}FyO+q1&_R5I5JaqkveMSwT%DLT`N6H5Dd>yV^n(utaLQ|%Uv#U`2aZwA)YR*;2=WNsP{ z58I8q7lp3At)&MQx|0abIv~wl*k^n8&p>%GuCXIQi?FPWxt%)Eg`4b+&XhNv@Pdt} z7@v&Y;s&I%T7fc8%s=f`X%POBbfhJU={Hp8->ynv84+l=%!F46xo3b;J&u znZ39}m{oDnf8nXlqyMl95}^XfC?UDD%bu;7YB6iSN<6yHJz!+U!vzm1z6{$g&JKP4 zpifCv5nxWQ>1cY~A>^}==F5)Z`DcWT_HVje)>p7U_)h26yjsiY%R@rIlNK}ec~gcY z39f$5q67426@wk&^xji5rHL>;9pLmj!rdYGs5%-B{RT+>4f0K8K^gFyI zEIH`js8p~YA}l(MldVqem7n>rVE-wKmA?!L*6v1z3>UG{m!V|a%>8yV5i;1l06c&l_h^PxX*Fa3*Elyh`_TTx18tNY~a zei6qZ*8ApnsCV=2#N(~Z8pj#TkMXPjn#hS{Q_b6K^HLp5xx-e18j!?*u%x)%5bW{Y-oz$Yg`T@&Y(y-bcIMy-hEpf8|@=g4=u7Vg%oKC|AACTZi7|W3vrqTMlrH)t-zVBjy5PH?b&O9=4 zI6bdsg##kgYq&0)fG;F42Ay70aOaU^661YyCe8itS1@ggz^O%QmqKmfhr6n+Jmw&A zgc611QY*`Cp}>6F9sJ`=8O^V*%tbNiQyTgp;a?|kCruN#-ezDk5m0=~Zwh?MFhEn| zIturKsG*f?BBkgaQU!Wn-owN@=$+jpG%<0B0^Ax%@vt`M{Hm7@TLwR#rf3`$Jvp&! zLQz;*bax4~LV|&gV-zwewoBUcQwS2H=R)4t0Tqo%$B+Yl^F13mIevj(p>c$o?{l~A zZbd5BqZ5xtjh|rZ!N!ss?$t9!GEj3bIQ+KmuLmwn9~l_9CYa_2U{d-E((ixYD6!{I zA-DY&JY4lHEF>emC;K24|3n%Memjc)C0gn(*qCWaLJlKbJCB2=9bImW$2&f1#-%YB zK{w@ZFgw+Gb}r3riCcOxGH2T9EB$6~EsffKK%}UGs|b}IgRcB1MZv?G1<=J>J*Ve; z^Wb-d{TZpeZmB>z`|YO|>(C@ zEq2ok1gL)I9A@@$-&$Eq9bh|avcC%{W)15Hjy!)c&Tj0Up$NS!&*W+l0A4%%wR~Zm z&DN^N9ksfb&K-)T0#bTe^U_&|Aca4I&la)OX*wHDzp^Y)hXN)qyuEy}YERkB zq%Jq?4L_UaH!C>O=}TiauyGY5TxruFSam+$RiHrkXiq@G=Gmxl8?A_G_LLUB`0sgA zQOX}026|(!smis#43HfnMkE}5O!0QyT3XuA#sq=Nk! z3(TO64Tu*r+z2{|EwgMaW?-V@$Q(-A^|8#S&P3I8QaN5^;f0S~Gb4vwqj&Vyg%xe& zMf=B+bZrpI?##VL_fouAxZ(Zel!C^iZ$RTi$LV*Y^Y%z_Cjk04?C3+fU419=^aU{C zeOv}-K8P0bIjA(~P1k9@^^Uy+0{%Z0UJj=JNlgBa8RzANV~{biF>^8}V&Y`{KaN9{ z##BNEJ4VlOjlKtUcJ&X4--6tHWLZ{}S=;+*1ngY;l}&7YyF#&3#ot~#3+U`_qV=Z_ zf&4oo*WTe&tI&!!BM~QO@22wFV=0MC@&(#vBaCAlOy=Y0xzv({kvQmxTI6X#8=L9$X#jHqPIGG z@!3OJ3HbVKyNy%Lgccx*S4BTHt%$4M`-^wcR|Ck!FQKREg-`wgiKG!Hb^Ko#QzzpM z3^EGReg-FuS}1R7rgDyY83Z5{T)$!sZaAdrMxx>r0Ck2)7thUDyIrtp+*yV2r>bwR z54Nd;5qw!p@rbg4yv zl8iqE7B_gcpyn|5V-i3Ckj{3?7~U6*%)=VPVDSyi!=f{%X}xjWoYEqbqFo6miphGC zQ_m*BI4Wc9ROb{R#W3~CLyl5Q%)iJq>89sV8RA6x^aAZvCO;*J%(colY4d;}YD z-qJg0X(I-FZClvsxlL~)3C-%#l>Z7kqT9?8Ly)jIXgtG7O5O%LYn$wI3@?tT2QRPg`6M*ZWMgU^*Zin zGgL+NO}(xqvNajylmtj-1Igu(5Ndc#aoh`ENFFCyj|kWaf@n9Z*E~(G zMpWs4PhQ5BvmacxZRvw*<$J3WSU3#EGpA;oo^)k(tPqadH#G(w?^eqP+QV^=A%rnM zc@dJ(YXj8bN9j?QB@&#$*j}Wi;N-*k_R4=??Fr$*=rvgF{EU80_3L#8599abkxN%+ z+O~O*o4K%G&MGp1Z&uBPp#LXZu#w@hlF^+#_;Qxvvz{|k@9rzkeT8BxwQ@pEM;}5& zR8^r}P5+3z^5v6R(y>dv@!FPOr?)Ki^e!L4BmfGbIqGDSnNLb4empWPF$Bgu6=dyLnsnLbvGCyO>F{yysk$T`6?qmP z(gQ=h?d|CA1my=ABe$e+^s~+pe5vp?B9p=x@e3gmBHd>6Y?>E;()EA&X=Z*QOX)Kj z{i#^CR;ErFu4sv856DU?6pmYXN+?{y%ospxgR8+--bj+gX2xxBR@UgG1c z-409-52KX!W}u%NNKBY}Jg5c#t_pd|r#Ud;x|IPV-}a+f&l7~VUn^u29|Ad+E*$ia z*ahu`7(e`(-scbkfpfIiPGeVlA$nU&7%~dE+$4>M2qt!R=Kp~Q5}LOO04jb#5;A*Qv|o~AstzZ5*YucgtcR->>k*LpKc)3|1_t`G z+3I-D@3PE%X5OyX3Ei#gW)b^GTi%Rl_Nsq6RjwX0VrYFX;;ia$B^S|()m!pC)UY>f z5PlAxwPuaR*qNTVpzSz#9red#bLb;`qF4G2@Xd5P{6RX-^HvNW0g?$f?g|e@CL0(g zUmjJOJ_dD@^NYpUf47v`cW8Fd%~ujZ8=;DZQ`qESrwC|C>PJWtOCQ`OLGDp1%4X%2 z8cWG>LFqN7@>Q^uSaMBH=dw_XbWs_MaPMj)wW3GZb6gB252SAFp+wj_*w02h1KV`ETj+ZDoEE-md2%~$0JIP3Pn3Hi)2UQSoO>dJ z|JD-{qj?1Oz1CFpE+yw#Ap;{O2AmGp|Oh-|%iy!`BZiWPf%3 zO6cc~cT1K`JG^F6Q$RaYE0uM7(3Zl`6eiioxN?Sl{`zxYlg52fQYNfzK`D7mezuE* z#OWsz?junM_@M`XIDhz&cLoa_B_jG4XrY1jNlKd^*!Qy4*nL3i=50)^|MzM$pX&^d zOcn{z@+TOr;75{z(=;Y!D&Pt#Cl*Dzk6-;F5uKZ$Fg9OA{mPGix$TaAk!%%*vfd#E zf67S!HN%ew*aT|?vZ!Gp zRc2{bP+C}F3HmAOf9Q~Dzj^bn^H1kI%{r;XoQvT?cZbNxS6?Wk4|&nja!i@CB=F=f z+{+q)@Vr%n9xc_T$)@iAu7w2{!AyzaaZ0OzTp7xLGZNGf4}8lOA-qiVtJWBaT@k!Y6Nt!fcR-bHq?l&FC^knh8j-8T5JHK{T8lJhcB|PRIp#iQo zfx1lwNIh6qHV*@Y`(AvLLLe1*!JaUQLo-hc`(aS-c z?Z z^0v{Lpo<{O4|e8*Fl^BlC7wRRG0rq;4f^pZ;fy7o!6P~3T7XKcwBBN~ zHv(wB*YrZQHLz&cXb{J?uxJTrgHCB6V(n6*gpO^w79m93M-|}_Ic?e>9LZ(&q&z4m zL=PFh!Rqm1XQrUn(CcDpu)J-Y24Z1qF)zJ>m3DSICqn3;#67Sw6(wBSGK?TCVYDpV zY)z7^N&b6Zmobi@L@?coe=|Q33XB%rbO9pE)erUqzY>~3h36;NuWnt@f#r z9f!gMvDq7$pxB@){)MPQWPF^fC}Hq&;*yYo zr-NF7;+h+AVpI08-Wy{kYlv8dzKR*2P%`Pd&0v#@i0>Z9acSj|TOw&PiT-TQ*(kUg zb3W3GkyM9CyT`Nn2NSn;HU8paCqVE}b}LlTG3-=lUsk4{-3K0IN=l^;Hol#xJ-J}o z{7WT!j}#Usq>}XYt zP0Tc+-XBC}02%UKHx1rtc6J98+NE44`WQYKDF$mAP`hz(oO0Hv)M-XIAnN=6$PFfE zy$^A2o#2DwJqx5ar}6&oeHZ@T5{R^ec}viDzyRg?-|8L`Cnw|od$%#OBp5{g0;aUA z|I_!7zOMB0Vm6Q~P^lFeLiGJyJTK{Lp)q8CwssJ~;F+DhrLPBHI(2-15k!xKQATf+ zU4lV3&UNzUON19nM32?k2WztuW6>KruoKMi!i$BJAQ>^n2M{|@I1FRS$V4(_h@f7N z6`nw4OHdos)3~L@-~K~%mm!7P1mMl>21P+pV@ilYWc(u#n2ALFN3zlAm3krV;+Uxd z^i{?qHDNbBhP5O?)W*xG@H@u|tQ?sy3U|Rs;^y}ZR|EbcpItzbcrfGPA$OW-L!dkm z{Dg$yGYwO(-6f&s#EI4z@l=Rb>|3)V(ZO$sA1`W9Qe<2n2TdfVK1T5I2h`WhtCZE$ z{&T_7A3){F&meWlOV;~k;S_w!DSjyB-fF(0GW8{~-u$MhmHWxwCw`*Y39HnbN`{VR zOc{#73{yTYtOnZW7kdVM2Va5ai>{YjdJ+{1JrgTScFNC5s+bgYibkq7!W@sc9PYMc z&fkZJ=dwD0Ny25ac>pGZ3Y3Z~`55|*L99Yypbltpq8F@ay2WDzJQ^zc=re`Y))7d_ zE9C#`EkwEb#gmFvMxp-~mc`37K}xtSRfbWKYRp*8`k!$vcr5&ICU~l0WeTL}(5w-r zIhdNhgVU;kl})q|B1D6sB290_zdSSAOyy;|UNlWtWJ5DwqMy1yk^p?|Be1FjYNQVq zD7zzP5X#z;-~SCi!n$E+W+9fR9QhNsu-M8z)&Sqo2%69pe*IR#$1>M8whYicA9rk4 zp~g#6&2`0Z&MrKEGG{^xa1dCPw-MAUJeb%zyFcGkmd@{&>{J7EyctV%r;2GDT};c> z74RG*Jd3VCA)7fEtpOX@ZvQnVgdXpLtH!hdeB+ih(zAfJF2$})K5y4}o69d8rkn>0sTdiPp~xGUhJd^$@DOK5&22963{RsZE3 z50&b&_m#On%)FU0dojHm(mUBVk&8r0)3Rvm(`&O;+0T?LU;`35tb6kA?v2LxJ_=bo zIyLFCS9VA-H^%9Bwsz{bw&*S+0d2nw6(RZ`?$;Sdp?e$Hli`xL%elyNx!HR9|ElVsSB6X&k`CW4g2AZlhe-fX-r zl;jnSsL3wwl=G}ED}k$-o&_bHR5^a0KryIE3AEQ@Q~;Eo473ePMh!ognfgV~f{G^9 z4)hqTATW}oehO3eVj<7M?9a!G0E`L75Gu%n@J|}4ORVwHS#Z}(MKQBZ@=BS$zb zNbURlr|&Hb&FX6C_1a#Ah$fHZ5h5#vFBz8pWH-t179fDA55jskSbN-#h|PDNmp3cb ztNpo%EIrMybd4-c!EKO_j=)VVOo`>xFT%mJDo4@S|InBL&pVFB?LH3cu>bfV#|pbi zlm1eRE4vIg3+KW&Z$CG5dc^y0l=zPQZZ6D+*TlYDY->%lVy3{Kfu50IgaT7eYgjM7jY87V`>(YHggK5y2dxD?J@e-jeu<$M5tx*LJ0>-vAGYlfT$;?iEx?NN|7N96)r1 z`8J1-hvYh)h+8v4d>|XYyV~Y$5J&UM|ttUCMiwoK3iv9p+ymm!*4#{z7N=b{U z9-lC+m^yrAo@N7RWga?Kt72UzSVK}}SXMjuKO!1(J8lJUq& zr}$dn>HO7@S!Zp`uCX?Kq1LD@b9B!gHV{CDbxzkZ-W(r~q5l=$?{(_psW(_(pu(mNF^Ps51iv*P0jWha--NWydmGC2 z#9JY@Gec1PsxS131gc4|Jmuq^e^iTiGdkOUCO03Bk*XACjqz*ry%>9b zQd<$>@;&wk=fC^NDxN|mI4gWKNWkN)3SO}0=_{FQfyjM>?gs{ zQxV@j9eU?DS1R-ltAPYqypahyl=$*m0Yb5 zIE1UKD`g)v)@If&<|bcjM@I*gt1CPbtW8Q+jB(C)SGi}zuhg0@&qpnA^0N3}UA1*p z#1j3>!(rKJeqqo=zZmY)(qMZB2gmQF#Ad2l4Q08OByoPR)5H9;rwcqcP?Wn@4wDJZVKvDUe7Kn8v3kA^|qp?o8&5Jyq{ zlopq#xB500Cr|li0DiF2D07g>&CN_FpaVRxN(kvlOc2+XcXkZH_%nMB#vD?iiH#YW ztLng2UX<*D`Z}t#NjDaBg4);DKDPftJrZfQa(MPW40{ewheD zzdYN4Od?EvBR<<+_kg8sd|V>1GS@dXgRi${Y@~w8OjZekh{AF~QF2gngUk-UFbL-+ z6&{|7|~k( zwMMMBcMy50;Ae7#j-7ko+`|6$ZcS!yu4X6rCPsI}HA?7NXLM;cQfXmoafOnM_}nwL_Wnp5ncd!E$d;*Ehh{Hb6gI(MNE(HOav~RSY=iNgCv%k=c?zZ6nOl-hB zz5{mmV4OffGx0KobP2vyJ{X7@S->NwqB22(iIg320|n-GhKLe?+xJ(V)DFYN1prIbN&)i(-;*-=liaA*gbP9K;H4;30?Z_Eo2VzjVs;vVlyEfe!4EOANs8rTPF;PV@#r z^)X(BGeN}W1O_4XG2VqYLBwvq(J0Y<759ZJ^9-N9uU`|tP*{8wUy-}a)L(_K*Hph}Jzbgv9=1E)2t8^<50JZ7@?S%? z|Gs|w&vvJ`@6!VDmFFrw00=zTSUl(%0xQ^ST3v*X02z?QS#T?G(}h0!-}WOx;6=AH zEbNu=OZpvpP4oMA3efsZUi((?J+ZmGwnws?z$#$ z$I|6$^NQ?cJ99?(vfc4W=mo@MeWRXzq8c}TCj566eg3^8JbMNVFTYB6yf3$V%B(5{ zDdd2{$?xQ^^RU;NmruOQZ9>cq0mnl(+lNng*N^w~&#~L>Yk}gvkL}EZ9E&a*{GFWL zZ!z!dr8&40^C#qxXAxk&TfjO}&J-zEK(PZR@Kw545XI+Fw!pbza9IF9KUJE7zilo9&7um zZpW?VvuEDwel5IP!$>Q01|+;v_B(@%+yp*Y{)WzGcmlF=(iM(ZEVb# z>M3?4j12{d-Lk~$G2P6q>qPh_0UZW)Bm0ZtlHQemQvc3Atc>D26t4ehc%26-`pDIP zdXxVUq5uM2*o&Bql%-zEd$oQ?7}~sJ`TL~v{=$=K2R13k+Ihxw6%Yk)va*1Cn0(q~ z*035fLh9uiBGzYjH)2YqsCNXLkFtW0*7pbPu83t8dYqULt~PQ+>rdNW4TSM5F%^R?34r9BX-8 z00^+jQKeqBj5ueH8NDZzNPY%5c58GdH#?UT95B`zxLo#{`O#rl_nmQU*lqGocPHTG zaQp~6dd_uF=OSvM4o1_C=~`>9zkUw0%Cvz`8MjE!h1VIu@%&MrQdXWvba#w3>&~I5 zygvZnm|PhXzi3h!AcC`#)hc6ix!|hn4^&w|L0%`I=|!zsdUA55$-gbwVKzMSz{u6; zX&a|FN^%QwPurC_q>Mi)dg0j5_rnVjMiaLBUDm$g5|%}i52m-0kFl-85d><}4v1x@ zV-Lde^&~r69Hy7tn*LfOq8^WDbl}@h(CL>gNRoi9v2&q^fs0KtIWpPh&Z3BF1@7kM zu0u5HNL`4fB#0u+ulSgu%g_?Kkp%xW%(7>q^-f0NqKMetdPQ42b~Gz7yn}4~mpT0Y zM`5E0VSivc6_qz>IZp`n%Slr0Sd1;cpbkSKpYfGcj-~3L5A|*`F(;krITOZ?MgVV0 zz+9Ba5&=s(&RA<=^U2B|uEH@92AFt2&%TeuDrGJUk^B7AL|OsM`hYHLI){?RbSOwEJP}Xc&D;fktIQno19?)z`LbWvc|a{j^C5qd zvNvsWS?>x{>5?iEGQuv`?=?-^)5F^S76aygU#3+XKJKEf{jEycx%WZ-2LVr%0?xOL zcYQ}NuXsbsmjA}Ipz4mT+sd<)c(ZWVt@GY%DhVUt%8Jpr;J_I)<5%?i+o|#d6e+*H zY)=0wL{K)rj%)BB#Fi;0iVyuZK~e_4;k zw7#iZwNlEEj@zi%TGGz{fWS@>6~|DiNhG#;hg=CLql?+Hinhf{LXSqjJ-PEMyLna_ zfADts3Lz9O>sLA~v*bFT`!k!6*~p%g>x38T#k+=n7?HjO*d z?f%VToM-OGp9nyUfFW@QmAIBj_3+QOCoW8&gj}S zlPGH`Bk~7KE$*Kfum~c*7Kb3|WLAh2OZ^|9O;Si~*h7IDYuh}l2-jtwQV?8v9Fn@o z16kp%yhE8)j_wG1XUm1pjG_?4GcRz_tmS;`6}#7u zMf8UKkN!kT%r z3!nM8RKFr@Bv}p#8*|en)2qUcWE=sN5E`z4BGl`p$zVUrTMmu$W=c{YS3kwYB#HFl z=(G9fkDu)^4Q1wc*$uNhMo}hL&fSUSxrvhP@A&|G9kz0T^)HcYGarPPY?c<#`k3#j>X&#hyK4lS$%Vs^C2r2{BgrF!t^_3 z$)_`;@MU-8OZbndKd_`ld9aRPTonIM@39Ia{Un!I+x9%4?CB6gL#r2ATK#A&F!Xs1aIh0+$mJq4s>y$7a80Ti26!e| zDi#qE5Uw_}V~W$773jq+ga*qIzFr#^)a8;^R$I-QCZOgVT|KN++)a*6o+P^dN-~S? zw`;lsq<>j%lMHCJe1`a-mWP7-Qkx+YQYir2b(>1!1SPaGp!dv%1D=k`;!i>L1W4^B zUK6e9lJgS8Kg>DM=JOUM9*o<@lHdCj56%z;tXV!ch|o-T%_1ahvG}DZY8o}qYNkbV zD%;8WMPcHnoCtrJNgpTWM%E}WWDNYji`I=Ki7pTX*Jpxjg|u_nK)K+AHn%s0O5Yu_>^(gZKvCUA z%-Dx(@9g@V*gt;D`Bi)sHDI>H2IZC1L*g$d1a|%)D&VTEW2L$Ndnw1}8c0w58Jh}o zEsx|Fri?^TCphIH`VE5mtdZ8|C!1#R4_-1`;NKQXKq}so@GS@qHW~yIIu3yQf!bO* z{_*L7hJnC&k|gLP=Ca5_Iq>d|_fPj4M>u5`WfzgbLlfn=tAH8HY*tSE#!v|J#=^Z- zdzl$cGYy}E^}nF1+{OKohL;u^VYYO~#b1k7^`o}VV2ITMD>~U{&3x2$*sIY87D1-? zkm{(D*TNK+y=REfE>3v6q-}s=;KW;V;|qZ{e^PY6%2?P{6SA{!lpiTN)ahYce_j(lE;?|7A@!6X2%1yNVF=X~`w%(0YLiqqln<{AGak})4`y%^i@MYzx}o>C z2t9^uQxmm*uPlBLAG<&`sHlIS31R2Rd=t^yIOy>);!im912CITTq;r+(QV>WvB)(g z@dT5c(1Kwq(Mo`Q-cgQt&$!&jtj+CJ%#)2Mo6-2M{N%|X$bOoB;ooJV)LFY9rG1P=Klm7%k(P==0+1fK__g|%sKm{-V z6InICh*T)Yjc|xD=`iYo>c{1ZxBJo^wfNt(BSW%%4jB`Aw|@=X{A5xbG`|G=Ndkj* zewZI`lbG^+{6Twc%P^+prfYZd2h(Qx3yzW=akU29EMey zlr7Ltn91AMxOWBEA-od6a2~!A&YN-CnkFP7U#(luv<1cdypSGek%fKzZ*gzPpBxPl|8E_u$bs4vdJI zLEQT?q|!c585uItN^Up(7hWHQwhuoeAo%p(7uH#~KASjj|MXi8UZblsZmKVH6Y)e> zy`?!zk}u?s=@>MNzr<7i?bIsY2TspWz)Xm)qpN+xe^q@!upH|RPc9T+wfHtyGU|{s z{_^I2_bJM5kGlJHYr^tVI<1^{{ZGj6pWnphDSfGTsHeV?yH&BS=nXXOWnqTbQnp|u ze|)c`xxwRr!3C|~{4dY6Qa2D|{F`dD5pvwWY8~;M%VC%9lqePtRQyBhF_yyZ7v!?G zjujxbXLF#2#e9wnwXq_i>HghpXQN0zxvCw%9w8%{ z$HA|R>>Va9>P+VPT@3w!P#|ts`6spaspoBy5=g}hqztjrC=HkaEWfB1`%YxZ>*txr zwl7{kF91zktNh#apvuHoYFjCG;l8g?=b{}8EsC>IlJ(yIio|h2+&R=R9CPeUk_q0i zSpCg@525wLGDzEhH?)!$=K~|?Wbf9fgime8cpAg7W1rEH+%9`c)EWQ7r1<)USTh8T zgbFo)R+2k^K@t1Uf}$NCA=(pDyp1Yz4ync$Bvg5ZP)NuI!JU1B6U5w!0{wr zSUOvKuhly;UHKj^k2xQo(?9WajZ{*fCdMV_2U+7oD|C&x3km(}sb+KaGx4Yw@@X*B zQ)P_ylDA7dVl^$42)}!^s^#SjQr@Te9tx`g=n_32+w4{4W{OVIt0Mkmla4 zY(^jXE=yxHcCp?Ic52f_(bDRpgFvaPC)5 zdemaeMCjsTb6**Yg#=E{kjZ1DI`w(SShb$&0Jbr-R6{8**JXgtF}dDN$~*xJbu;Vl zk{=G-DY{jaW}FwAPK7+G%o3< zpT_4s^WpdV*8Gvt2YI-wO5A8h(WNj-iu=3m&%42iKFc!6D;m{LXmr>B5%zbS!Md}G z;cron%7#X}+rJU=A$8x2u1#N#^e#a};+dEK+;^G_zs`egpYHu1O?c<5WjD$I{~y$k z7Y<_cLE{UVt6rYP^xljwTiNpr=;<*f^fOil2_}5|%sBk$>zg{ukew*RB}hzENGOGI zxeK?-+a4TUY4^TtLB5G~i#dmNn3?H8BmXR%S?n1bYGES^oJobNNO0m8`Trbwwyt~5 zi>CK`{hku5WRhH>okVVL(Kjq{IXXU#6@5u zdL2d5XoMAY;l3!9PSS2_FUYWYJ+D-6(!J{cbQ1nv+W!&{A2%f38KU3ys5Bv%_Nyza zgyxvSXf~V5GItebk3r%AidQfrbX4KewAtZ*sAs@g)Ztz4w#i`vPo_5E{PUcaU}J8jZ- zK;;{c8g>~fo_q(H5D8_Co*z|RuoqL?Pe~`5(2EmJJw=w5*;$qV=e6W6iF8vdb3MbI zp^$S|1CU0x z5PS4_knB!Ts}(w2jQAhAi^(Fr^2B@MnlIP>#fn`DaCJ{R(Fu90y_RjW=KqaN!VT|1 zNA|WeEk`J;sQjS-OvFdrRhAx!DEbSmFMSG@ zdeUO+;9>u3#39heUN^!II7i!dv8121q!L4)Q0J_ooh{Q%KN~nbXE$bn3{oXnkdF4r z?)lboeUO}IL82cK5ttTI*JgHcMmO(CCosWLHbxYv*&wn75}uP0j(_XWsZD5ndx}oi zB5WO-z2x6c57kRSUY8rE=MpIt%L^kqzc1-rn>X!Ojj_(Gd!Ba)RbfgG&OQCtN5%P& zIFel(saRh>?#&iUL` zE>J(UrC>TwA7K@Hq<~Ycy@gl zLFd&BbU7m`qN4VowGB|V*qe%f#yfaf;wR7PRCUSG1+TF|NcMF+;)C@V&4xsVdK?dn zRp*SBR{n93oY6ipd~&)C$WnPeFI}&^ljv!|H+aj!#A2e9_Do-jbwi#x$i`6=b+1Er z8XC0BOj{<_zmGk-Ps(X}?Ro2t#-uu&9S<)DHbyi}1IQsR;^hrt3ACD3NJ$n(2lTS| zB+<%I)R8TuNE2eqy!JY2=e?}|WPoPy>0(v#!*)q*nY@v)Dst2`SLd>mz2I_?0FRr< zs}#ydM^{_@K>`<6E3xNco4-bV6r;Yy@?;NQ^7SWEga}mGnjV$AWPGC2|H7qlE-7>Z z(yt|M#<1ZmO1+pyuAP!6Ywi*bTYw28 zrSVe^*Ep&Om>=qblf#Js51hi$#+Q!-2taUxe5wygG0t|UY!=A3LOqHWz8sCAGGn$j zWp@PLF9knleRw?{+Lwa+VD9z=$%Y2W&Wcd5Eb118odRE`z=UO(N-yFDWf0Nv%T^#= z^BO|fm4lNpJ24t_Z{A926PE;Hm_S+;40iapQ_ER(3>>j};tfwnEv%GLHxdZ}Xw^+O zNc%w31P)=UfU&e|&7uV2;IvTNjQH6%cIQ~F`qVfc+w^>l4UCZQRuQGu3Hw;SgkXLe z#DviUUbQW_VI-flqE|mY7tbmgMsAfOnwUB(h3#dSu*DE9X3 zLU zPNMYcF3Rh_gsYCY2COSXs4w`KwCLGEFxF^n26S{PiiMwB={=S@;URAX~lMH+2ev zj^d@OC@EW)ZmxY{($G=>A5wheY_qrNp_FM;1L;q1H_=yVUI|Yv5_BCUDOLFcShe+4 zwo;G;b+v<;fHlyY(>kq=wE2v^FBIjQMSbBwDKLhc3{PE$a&7s(_VSc@bx(uz_VvV* zqwEk^p>$*6b|RjC-7K2nZl$xYQ<_f$g=6f|6G)`j%2&}D#qR+qmo2k-QAx2k{LGPP zb>zFs9t2mrq!1tMdrED8n!($1w|_79AuR`&YlsgOdj9f9POqxB;fxiPd{uOY>WSTs1z#48`!5?IVf|{N z_KX|e_bObAm0O{}e{v1>ix9?)@chG{{=@H=6Lwq(pa^ilmV|i)i0Q<<(^P&Iq#>fl2^qDXV;?!bXGD?{ek? zgi?T@4=fm+rivL(ye96kUSuZ~rGQo}cbl#rYey<3C9Bj#DcP}Pi}A}O=2#h|9y`N? z;cp^~F~~+hmX-*MO@B8pz4GCCPU>tr-8$)RI2GO}#PYB@KM*n+Qbp7-RGMju!PI(Q z6$alZ(oj2!l0WuHqk(dSw1VnU*+JpcH1oDOiBrZ&O_;WJ%v7=s5mvH{tzt`hu!2+c zG$N^_JJ^|D58-rUU|>vow=1h3Z+Cm*P-~c)jR8q@Gm*T3 zqotES(<@T#A?+tPw>`{+`9V%=2%ipHjB^>18^if*9k|!r@{wH~+r>!FD)i0@$S)b`fUNDC< zNSx*V_IQDg&l#gCmlG55cnI`<6w`_q#i44!gb#T&6K6v zwWi<%npwU$yY2Q@Y14lBm2&(%(gB`q!rc3p=Tre7lHwMi_iu0dAj@F zmC{m@c#TyEeX=Q!U3*}WQ;wX_A_Dx_Y_Fqkt$)Xf9Yc~1@JGW7djPq#b%?(nMUbfI zM)+Lc&;`bC9wK|#7!xr1{)8{g-yRXdFMEcb!gV_NX>@fpd-oT|rb`^VXnahT_QVQ%pk z41Y+7iD!sdum|2j`hZcDAE1CoIa_1?&tXJwr!y797K>=|+2+9NhG;S}oI+n6>lEg) z-8#_E)GB13{1r@`K5=c{{!ERIzT~f~~v z__2#RH*b07;VItcxCE*Z_+@MbY8-ts3$Aa5fr2!6#n9nMc9e?*R@hZRuMCwCewz}*j*$JYsNy0 zg74|W*U)83?>kpqqdmBOf@O2Y@~G{-PzHOb!hzLl5HMJINfm7jDOcI;Pd1`Ww|`_( zNsH1PKCo$j`s$xU7_opJ^1Y4+d30nbZMT&Z0z2o6YO^r3e^PxPLHego&}wnQExls$ zOFDWB8ywR?k?5dka$jNbl?*-*S=Bg{aaxgiFd}nmFp*5Motm}xv8ZtQru>&D^i5=Pr++PdVgfxjsgZF<~Lhe|`6$*foEpcflK^fSGuuPv=+k5yq+ZN!g=(h`~NVZ>Nzxf`2V4$(OWz z*~>GW@4y|eb$4Ob?q#>0KS~)~gM+e!y`!ZcpeDLj4ByORw?H*o+JVp30t~J<0F#g+K zilE*y_B@^vcXOv1dnRP%{<4)$(VvwEg=j(gTy`?=5^AeSL!nCLbAJiWm!56oBV^Vl z>Y`(JoFb|RimI6e*7LxL?lZE^CXS91RNi5?XZ1d&?(z5qGAxA#Rq*Q=fqan(Pxj}n z5{3R{f<14tEUpm|_^$J1PC|#Od})!*+U0%+s5MJBB@+3-1is!7zLb@4bXw=K7*EAC z>$f4Y+cJfp7o;|wJAc3meMYMALt!tAb5yQ&u5`a#P&wHBkx4i)96CO?B0Sc{t(abG zuUQsR{(f0h4&)VOYylYLsgy~r0H(ls5Pb2;mqPov_L4Kix6Ym1LO8P!TZ!=g@LP!l z*^^Gdyu0?6o%9s7i5nX@0e$Wu)Zi=|+?^_nR}rqu5#w8E0Dnowv1cZWyIwubh7<0b zw`&d;6REE}5wm7_CyIG?i|zMKYmLi({Bv-iXo+l%*F`R3yS60HYPeDdj2zufka*9R zA`?W)XQNt_gvx-NGO>Ou8gj}mg3@HD#KrX?G%E0Z&2F(0c8ll8NgWveaf8x^vD=js z@gko9uq6X>bALkOG9gv$=OD>2@k&>Ns3wDt=u>T0gP!#*lz5FE$^At%5&Gs=lLL#+ zwDGyQBfOj1W*$OZkB47VOs>nM9`RI-66;(1dhVea{aWG+Ed-CuDZ2H*WZU~(&YT#C zQZm+y)wj^CJiV>w3W&;7u^jyw@RLp<{ce_?M2iy~mI^V~N+WAEdGeNI7#fBn+I+1SbXYsbkq zXamWTF@IiCP|jcKrez?7M$Sxs&abF5WXW#~GA@oQf#E^(q<1!fdY)Sb$4cZa@7n9I z-v$Dj^9|m(ZdFO*$LTl6)c((UlPKW}AS)fW~um` z2jwvh$5D@W6KUyCL=eQX!!y!g-42aIrGF(E7PA+>s;Wl!6~SX~L9CNc=Mq8;x(-rk;~Ox0b!{;EnaV1E-t8~sV#$YlZdIHSn<^VjQo zIKGYOnU|s{so`AmY7E3lj0CP=-S9<`=0$plJrYaA`tC&xQqB$nn|&s7`a@JwvVW$) z#!epIc}8_ppv?6EQlvLAs@n!OHI?F*)d!NL9cF_v!Un`^-%*Wo1=#S@hI9s{gB;HR z<2Y$-M!B`ETI7x93(<|3Uc#R$D3!)EXCPjLlZVvCs3yOvJ?hIso`GfGY!^2}CvE4V zy`Gvv*yGeXT(K{QUFgx;=UrjWn}1oq2~NRlN5wz(scEnWZ%?`GvP|H%-EVD-onm4x z&V5Gj`B*t%U}?-0l_=m@|3Ns>ezPLKql>O6s3ul-$G3ASrO7E{pX^l01_o4e+vbka zQ9NfORztc!O_m%am$M zADpGh%f*K2G1bz`m@E}s4Jah#G=aBLtlyXG=tLSIIoSiD*INadlHQGZzzpN>4R@DmiB z^|8CzDf*wTj5z`!56FoJQbLPhA%!w1)jukjiUzCv%gzXZj~1hLjdOr%K`a{yC^&<$ zUJdLnl^1aqSkh>U(kKF!v$WCrTy^$JW_xXJwVwl{O+Me*jiQwThjNFFlO9%PZ7>hZ zH;X+e1Q1b`rz$ySvwxiQd4w= z5T1_}qFBqgHru>AZ9}<8R5?vQKy+s79F59C5V6_6YZI@Ws|bQoh2v8vW9T`@PK&Wt za5lficn=tfnD&_3T(5~4b9cH{;2y5 z7gVv2k-FCXg&UaKlWys(lzpaIK1LHnfLD;P`NyBe^u8ppi z1)^8NM80CM|9|j<&eL!+(3NwY(b^@Hady~kI^j|n>3w@zInVnw(F@=?pk%?pXsU;$ zEu6_wyKU_&TO3_&ej5{gRnSM4ok>C?HpPBHpz_ANPd!khEZI&rYJ22X<$*bzVIMpH zQJE&6y(Tpj%;Yji2~P9%R$ka4IGRs#H_?Yj z&ST}mq_?YiRjSNy6-G(eDtQH=Z;r_L#t22>Pb}rY#u3!b2J`5#tsBLGiQvw`sFr*6 z-#%a8Mt?0nG1`dLfdt5h2PQLguFUB%XibM!Gx&K0A9L)kqJT7QNLh^a`A1|hM&U)` z>X~#JTLs^8lJD>XjX#vRX7Q>b-fezimy%xi`^7{v_Z)8o z5H8cig&UnE)A@a<$~5Im5d;1e+Ne@YPZuBa@ADxQjW*qel$IwEtncC~^toy;(Uu3I zGl3S{(ocbm!;E5|fX3d!*LP{Bxt;;YO_(9~%D0evy|k-kbQY%9&vC_h_s|s5m|-dE z)PI#f3J$y6J-;V?y^JBInk!t6M)1urxCyF++ckqn(s0C>=_JX($nUB|rLTROm1T6lJmRX30K5IiP=nER0ecDku->GsVwW629;`ZkN%==*yUBjFsN`K|q zOOCK6{N5^ZJ315gXNATAQ(rb`)5Wq~*mhM{tZ9*t99-? z{U}_@NXw`qka5~f`4q(4u=_R zEFApQ6Au?JJ}W`IL=0pGO<)$ z<2kGb`mb11O31`UPVM4=B6MOrd5t(znya*|6-tl~*ZBv!TLT70G<2ij=z(|NcYLan zc27Reo}3YDV;#Isi0z5uFMmD5T=K#PAEMrL?19Uh9}-X5P&A*21NrW|VrwXbmSjKj z`?*+*eMqLEPpKoY(nZ2@dZE&=@Sl%1<^EMqj)vi`a|QF3v!>Coj0+C{EcSKLfqK!4 za~k^YEyy4#1pz6ixV28(uAMSwHk!{7&4-$+M-b(>5H_hhW;)2_l7AzoYv<@OABU@+rZGY5;kP@ZsXlq%4BP$C zfai@>*EZ@;h5T@DamnL@_^UU%<{^xB3SlFV>$Ng5M^eufygITYTh<>?mv+VvjrN+G zVMRid!_ez7*OJZ7cYj6h0vE$jw-TtYD^N{5e$;zg*KA3Q_r3Yt;zliSRIFHJA=@I+ z9gyklm^M!qo2}6kGH*s7Pi_h-EW-;+wXv>dC2)D!^0i{L`Cke~nB*&Nu&q9Q5rAM< z9p3l^Q9Zoe=OB>98$0|16-@pNeu9)oFYefYdMuFd;%I&N!G9h6%Ly}hxn8pb9xQS1AihUVq)j3_=Cg;}r#tyO1HO4Bf`$Lrf z<&0blq!D4jUHev--jb&6iOzi`M~J-3G88UX$*(>$8Ffc}onXAYW0b+DAT3_-OSB4K zs4s+-iJHQFr>W(*{pZ@Pm2rLm}>2OZmzGaU!eRe zW>6{xM|Pc|ufEq7VU!Gs%&4*H^oldU5pfWi$xvPv_+X>wy*9?VHS04+MndPq&g$Ts zOSR2bd4JoO6*&`>TN!+hp`HCD#Pz-?%p#r+q0_qf5`y;9r0b0N-Eq%SX8X7xBikw; zoGW)$4b>wz2EB&eurRU->nDfY-@OfbeC?TnfzPmn<1@aM4Q-#9Pmdf zIAUTrND7O;X&4vYelC6~t;2ER<<%ur!ENm@S$|8W{<7nWnx7fD`RIEr0tLf;V)A6# zOBwuJ@qr`-EVMlKFnqa*^@&6!y8z~xQB`xwN0q*j z#n}ffR}5$A>u-lCd7(C^6|{<IRaGZ78F7|=QIeW*q zvx)l>!sQauf3N@~2vG528?X-}#9IYoKWr zRmw$&o#)-KvIU?DA~r)-W!+AE)a$-`3-P?=>PUrqoUS1k&aHAy%$#SszJ0xpVt*M9 zVXryB%kXx#llDUm>njNjP47g1yn7OI zkaGx;P2iSdP?Q7}5bK2zeXR+E-+vQ|KMq>!qvz-^1aS0{Bfz(D*kBryarDg} z~yI983@Yb&&PP$9x@;uu2dILY+8W$VT$zI?VT6_VX+UmfjAFG6Rt$)RGXgMCe z1(@lmvI>85S1_5NP)e-DBK&k{$`^)&J;|KP?73B>*zth$B*Uh0^Z!F9LHgy z%89w0Qyw@;u?tY#t;!x#e71hw<47jna%`?H4pmMtAF%0)9C3|>-gRIEoS+nYY?|dw)frGJ?aDReB3Ml+~b9NyP zLX3?5VDUj%n7e1I>)8}cPfkr1xc@bMFmS;hy74$rl>=Ys>gFq5@G!|IPdJ_Mgan^jQbQN{yRtZAmnA=6l)f1*c9!aGk9>S626z>#% z9fXU>;fFS#M)^+bY=1F-yI2o?61z`24imU_2sYqZBZh&L9vnmnEyFog_aTtEo%|Pg zU$grbxesoI?muI=jj2NZA@)A=X!PYmWXDnQ8Js74CRQlV!Zb`2k=+pgAxjyw%gP~7jLWYPPhbjTiqp@0AOuJ-)iI4e7ljyfHJ zQ%5)M*o-1&6rBH|{{Sk7jCks>V#2>bIaZN6Uz;Hsv+Ef{nK3NS&+BMk(f-jB3S6iG z#vJLR8O{cOf$hPG#?X|hh0lyL z$rjaB2|_+A4f@+a#^w~L7cuPD^6H%orXsyg<>g7_c_kt zke6p0{p$1_Xzbb8z#)C53&8gyxf{uNpe+-Yicg~HaesaS#?f(R-u`HR@q@ySn>we| zg!0PKsj(|#5`GYs!Qq#du!DFTKH2N4z^63D$EJOz9r|UYk7=ugBC-6=38DgR-R$6x zgxoD-@tB?x|9?+wHG)JMDgzGb5Uy zAk1CiiGSRerKVrqOXP{9Hg&crQ0Y>gajgzjzqoZ?eErN>n(3f+bf)4i@B1l(yzWFw zK8N#BfATBAM$|U|Xh~#dx!~bxinc7!y~!rMdntG_8{T51oytVFT(*+&q|a_#184fc93rPrXnw4&2WO>^?&F7)2CvzXA+S~# z_0Ca5G20*jGQV<3l5!rl0~$So<+gPZ<-?Tc^;qV!3E59L5KcJCnSFDl_~GdrOdaCgGtW&A0zWS?YRl(!k%~VJu=~o;f`hD1Diunm9&o!uYFQ1us*Ttt>eC^fa+8q0vVXK$kK!>RXxsUjf~LcqoD_4_PL)jKNNHcd zhsT0r_@&er8xaR(?%c<8us$M{Moc(lfp08Lty*(G^yqM2iVn2Hlk8sv99nG9d{lIc zy~bH9D9r5Ie)O7?naJZ4Jk7r?)h{_hM6(0Vpj$-qn?G+jqGO-{j@lyY@e`5>U3^Kk7*TZwIcO( zqaf&|H~92KYLTFhn7%+!oy!G}6n{1I(hOYch6{E35sJA&V6+gM+~5RgFYJ?0WpsTk znCOxpsgnOz)(tMvJi(mz%wzw9_QPVSm*MHU2KWqD=_dlqkhnvl!~BLlYD*4@;he-g z4#Z9>DO&Bg6*QxWFO^NGOX~QqRtcbH!M@ zFj~ip6?zgsbXbxZ{|xf{wYyXBz;;5AgflVC2fo9t$@-Fgh8GB|?g5kpg`c$JZtkJz zrmA`L(8fGKjMk1L6aU6Bm4758L}`dwZy!OsN-SHe0)0^vGDHo$jgtZbHKI@?g+MFzw-T4i_o}GiJQJ+@T@%2TN=Zb(%X@wxNcINsNtKR*e|`TgM#3lealL zR6O~_2)xr@GF6M6#Qc|*7azCaZXT~*Mn(h~7?opkWgPLaGqT$B#DCnbL!Zh_V@?_e zkaE#YbYaWCyLdU7>b3;E<}PZ>m@Ak}cMhJsP>mWO5>?#(Xew&JV?vlAYdvA`HHbtY zRT;RzYd9|32V}mLI?gl!p_$rIDe-@)6L4&lT^)oSV4&<^3LNYOy1op+tAuwGQ`e9; z&P>WVi!jamVF#;;Fn`{YFS?l<)9i=*@{UWimYFhH%dNOZ{vN+Ufn7XAmwXF}ZAu1$ zyQ@Nw!6OC%cEf@3zx;q~4_x}50XjS=Jax(u0A_z%n}O57W@K7_*QzJ^r{2M6$6u@u z23XjFjbzc_E%XOASM85CYjpv09!lBmJ9q5!@Bg55$roH!yMGKFg0anx!0;|n-&P#Y zh%_*rWg}Q6h!U9?W=zhcL!S>kRYV0@EMM5U7Vs$Ye{>2!pg0V#%TNgQ*f&~mW63zQ z4Fm~?8;T9u8F|Y)f&AzV{Hcv{1LN~OJJ_6K`Ww9jc&=~Uz_ zypW^VGRe~0ZhsMxgfr)ieVa5{cbdtZ$4SIpS~~UQi55l4n3DG~9F3!QVZGeLkZQN_ zX}F9OG-0Nk=1{FGSQZOL{zj5&*^(D{@fG|LLI)*S;-Q3eVbK@pnVS>lZo}oxOfD zM1|P=KLJz_tMBZ{=;Y}BO1CBk4v7>Q@>OJqp5m0A41M|G3sqN|prk9PQ4aDAW>oFS zCV-5M+p>YwV44AIj1A_7P>LY&o+|nnVGIdF19jClILk95ynTgauz+$HioWcnv!44s zZ*!oQv415!<{Cr|ECTh^bhb!;Y%&a{Ki<r2wG;D1 z<>W85jA#xw+YGm{`U}n_(5g>@T4=({Q>b{QD#rMl3D~?x#uaSgNQeW4b;b z+0o7BwxNsjGY58b%YDD)A429!_W|sPRM-SrbAOI@aAku!E*oa^)98TifS~l(00Uru zd*LWew*j=0m@hQqZ=vK4#_~bCm5fkpLu$3%c&M>$t-sS8%mwi%7kH#IwMqXS1U?^^ zoXq2F7VK(MEileVny22shK~F~67K}l1wvz0i6#sP#Wes!5 z?61{`(}qSbX2UvKix$fXym+c5u5rGQe{z5&R0AYL2{U!0H#T+!VeGg9;Z`~SAvQ=o zI~_tLgOn+&x8nc4&xXg(O>SuKbvoAX9)IE6T_7%1k=22vQ}W+c?X5LeOd+2oQ@Q&_ zAf-HQ=2ekTExNh4U?+jJ-Z@Wb0ze}XAU}~Z7YaJ@hsP@sn?+Z^AX|I+f;}~&p&ybv zZz&b%!M%_{;S%lHmS;(2?EH05GYyqY;H9>hIj|Mr=yVa#`jiL{;~yv!nO`X_4S!td zEq1H5-4e^Ta}A^8(dYOLCcL5I67%+3L&t%WMcm4o7N_@t0S#!^0;b=Z+5=#_j*2mx zhAyL$wxgpue=R9kY|II@%*j5m+d(Sfw~0bRgG``mG_SMjLK1VK(8w>%g9y1Pp#Jmi z_=;C5&MTwEh3Leijw~kX9O>tYu79@t-hv*9#M#IoWCSEregOrtbX5!K^e-@&h!y~_s%14g2I8HDu1#r8F0}4 zw=ju+y{H*hTT+X_i#LuVEUYpIUnhG3l8qU*gRVr`v(+RF>XfKJaiK)X>Px4EYk5>i z=@h0(Y}vl>%H*D;rv@LBx8JAQi?}^@1=pb{^+6S435aZ#`+-Mb8h;2ZysEKf?uqVG zxa+dnWMRaQaGM702kL?w4>paC-}nb`$MBcbLEfdaI`jUP&{RDCVhdjyChiN8bOYQB zatF?p01}*`;#C$vc}cDZm(PhMm4M z{Aux6&k+W6C~{8HunrO?&iJmb^{`hYabrt@gxjMXF$Q;F(1DgHWyOvE$!gz3{WScDzLKau)qTY!p7$vR#1nt@E)j0AM<$b=)5_SFk*w@5Bz|ucDPS zR`o4%UZqkk&wtHua5`Hr^`|P^N}cn+7WV)Dt$~<1x$k$frm&=qwQtL|dHE3dzqENy zU50W-6tmk^FPJo(APu*#<_q?zSp}lKovvg^&v29ch+OpwrH9qu9KzJvmSnM8~*wt^n=)00uxwr;j|||IpjJG zMwGLbg=N3NthrmNsKg9X-;qV~(GHQ(XI_wD3$eJLx{@{Yzp`B_Yql50aEtoT1pi3u zw1sg$_{#B0eCA^?xjrRRygdzyw80W{%01~#6n{^LRrn4bzbCH+eRj>%7cTZO6W`}G zW1Uie&79B1@ibMS_nYa4%uw^A_88s9j4;l;w-_)^eN}%Tzy{UC7?&U%CHcs6R2p;K z*Jgvkv6<-53c0EF-#%l*HL9wTzJ>lLhVX9Qx%emG_ga>A%49I-9XU+6!}~gqiGG|L zMSnziYzcVx##682GmKi?R0V%dmF_C1Vm_1}M`~RjB>N1;{#)_}-q*VzJ6kAWlPa<^2VvOK-JP1dOVs|TLnH>QB0qPbt%i{T6f$ALXDzo0S; zBy9}|swyL;91fcP?o$~{;ffuxdDT;vKYza=FXP2GuH}wV3e?j7rS|sW19~deHBis= zJ53ConY7cZdrGi$Nt-P~y9E`tf*14aX7?fazF15L51ykUQp@Z1EhFyY>unG;p&?Q- zFVhEPA1jOAs8Ki>P8nPa$v!UvHj%IS0&-8hKLS9lawQtlDx_?Fc zg1=2y21@ZLF}|TtsRruPZBza1C)UAg?u^{6IEtu-zn^Pr-k7JzT>~A-;&0=v*V$)H zbQ$CZu^)&5k!f$xTKph}n`Rz<>MsR)2NLTZISC#9cQEK9g>tj#~M+=8S@~r+P|j zhsS=$AoOzgn^xY{JB_Kecngf{raOe!nyQA#Gil|2-k$$@5Vi;zg+@@N+ zCh+noMbu^0dIu!cdXDY16xLYofk6dvw*dp29e|y^S~OMRNlZve*S~kuIw5-Dr@C zn)b>?PWe>=AGh&+%pSzuf`90PG(nc-DeAO(mf~hBa|ShkuHGw+!M;y6J%f z(aE(81k@)st#r15ngxtDqHrINo7LBc`8hT+J1AN1j!`b8k@m=By{OmsueYWZdU9uE zb!kvz?+?&Q-`_X9MG8lrn9uc&XjBd~_~1=3Ee) zo;yV%jJj;J%@Kqd<*w9$gAkjiMN$j$#ky=D-$`g%#pVzOP=D$80pI{l?wU3od>5lk z>rqX4Uf-pV&pgn(7`VHWqdN|DFs+s>rZsat&{Y_L=mq^vu}1yzzy{7Ig5IsVZMoE? z#vKPj!1h@?L zw$`hE943sJLw}(70wO{sMvZZ2#KTsg^?3d5Frp;=2%{PKV_|P98vB(6Y%9yjP3?Gv z!&BoTzE@vU70V(shUzSLa+)_97CsCtTnS zD~JM|$MAv#>x3xH<<)v^c+K2RHkORfaCq>R!hc{c6!QiePwlTOPVF@6ER1Xml$&rk z`vL-~_W%NPg=1fV)A3<@4eODU!qadi!FG~v0ZV8%^(RIeE=4<%J1J_+go7xvABchc zg?VB#^U=QzPJQ7JE8h2RtqWF9Vb%{iXsu*B_Qx}&)4Z1WLd7?^$o8lg_9Wu#jZiX|7&GL)7_vJZ4pa=sKalkZHhEz-J5)?p zCDER>g68)K7`A+Kaoig>G=NWXU6>d2P0sI>Ji+}fO zSplcdl*0(*W1$??V&#pKls|9xzkm_{X0V611CU~@kK7P}l_QC=h_~AvF8U(Dy=w4a z(`aB3NWNnxjndmfH_1r<{B)UIO8 zsn|TIWGsxO#T*MC9269nam)e}m&^456}MMF0dhc>coqT{x8HyPGaQ#-AOaP)C!GQ0 z8oA(y?_}2OyWPH3Sy7T6+Ue9hd%41Qxfym;?PTm&a}d6}QpH0~#Th zKCuKAw*lz`*(aA-<^&eE@DK#QB$om81Q)l)H3T>tm)HFS6}J;n1hgHO-Vy~9x7uz5 zWI~rp83hv+F(5HGHZuw@Ol59obZ9alF)}tYHJ8zt0~G@^H#nCtFas-pP@HKNsExY> z2o|g%xVyVM1b1)T-QC^YgS)%CySoG@SRgoDW_EUGcK^TX-s-CEevY5_JLj#Bgiv0Q zM!?9%04Qo>?MTBwOV0@ql2KA(pa;;?Gttu1Gs2OOD49800{<p7?=PItegyNob>bnMtXXVe;eA^a{`3*oz08@GPD3m z8*88g9Ep&Pt((1>iK*j9o`3%ZkQ-6}7&tiCsQ+{a2v`B_%?$Oe0W$iIra-HYjE4G_ z07V-^GoYi}e}y3DHg$Bg<)ou?adDy5w{oDhu{YtRpa!^@Ihq0<6o3vuduN~#;E$F8 zvierQzgnY(BLOIxnmPPSp=g&+a0C~BM|Oasgfu|T7HIu1W9fexPy_xt8~_6?!+*m4 z<^4w>GwVN{^$iVetZenI-OQ{_0LEsPK!BX6G_9knBQ-$Z+UO5MeM<+M4}X1UeKSja zgAap0OVB@R7S=W{)*k;RV>4?b<3HLka>g0HCX(Dcv6ll-z8Ae<~UN5P$UG89>9x!~|erVgs;pumZgPJBqx% z*E8uA|1|;Rf2{`v;J;(Z+I-9{5J3L-m}}90v(OuUd@=k# z5Br}k|9><7N0k5TnE!7@qE42Uf9lEqW$^#e>sy&wy8UJGF}F^RA3Gpp^RWrm|7)rW z{MX{j0FBI?to~Ok;i&(y3j)?Amj5|OGY3&KSD=x+nWLfUpR@5VvGN~#W@%;(l(%s( z`{QN-&@j-`|BvotxeP6TK5h?(kCFUS1^n2Z|IR34ZD?ck$C5F!umbe$?e*Q@=szZj zk%a}|!SJz-MnKm;hZsOdYi;BB;R5*R&I@2{V-NSoBV}a)&qT|Ii<2N@w&BWB||s|A8L`82>v1%*76_tk=N>P{MbM`>wn-!UYmd5M_${1;74A&f8a-6`+wj^UWb3+$6-7E13%8f z=^yw}mGj@o_)&&`>p$?L47We=zoy90$=?3sD*bcOKSu1|{O7$51iAtZ;Z~Py47mc$ z>jFC-Y6NgxXbz@$=1C4zQz>XXR_!~To{*rED5^94x9xumB#ric+SnH%zvEvcc=b4I zZGaAGOZe9Q?D?V_r!afa4!1IcF*IFpCQv_!2a8Li#DD02`D*8>;%5Qg3fe76l56M0 zjwGLt_TVxg=2|~kwmuQObnxx4npFz^rEHESN;y)?uXuqZ&me0NiwMe*1`mc3^~x1_ z;qLc5YR>dKfn*#NoY#32Q=x~(4rBD=qWflr5~D*8HZisa79RK=>clLmhv0FtB<8Y5 zNm1lB&vG_@Jq0bu_QW88=Xf{aPfkr~2~AqAbF>?Pz-skUXpUm9i7Kzt+Fc*MG5^e)&`4#MdUkhl!EJQ2 zOq?!~U@<^#CNMXQ`>BCm0fv+oPMyG(Q?N8zCRNM%@ReV3-KdIxwq5ibbW$ed=)PCC zEbrNW2<58@Ro&AWPxCSFSvbUkMplTvPfK%(DsH&!-RE#|7InI{eQu4qp@QRrypX{J zPz)TA$wJNXDbn013HUzd<^_l991YZ7qzOLuEB9Gf6N6Uq;7nszv+wOcc_j zO6#@5CS$o3=jHuddft&^Dhhe-%JwuTaQhQ~R<&!sdZ$ik%=c=nxQ)4vR7ci@)DHWp zk_v0a&7Xi|kfJsov@LQGap1NG$$LF|b;WIW_s{CzEt%8mzD`Buu8oTmG;UfSM(LbB z47tRN&dW5xR8w>Ly1jbL*`jd9x0rbD|$0FQhbccELg2Jo6D;%{vrGGq4}V?+MUR< zQ#nD$cb4IH+x1vMiH@S(yl_PT#!G2`E}QMQ&M0PjLs0Rwg+=BoC(Uz27xm8mq;lu#>AcUZdFiyWuME<$g zr4-Snp31YAL#1bt#+Xo#gZKUjf{*)zD)X;~=+PsT#uX7P<$?)@WtP2A|)XT%fF_W)B zj)ZndW_Yh#OC4wQ#1BN{rM%LO(};wF)9a$=5@VBdl5X3)XKu4=0y%tgGLO=j#kz4M zm-08oLq%W)8N2ZN_4q8#UOnUVRL`Lo{=I0Wui#>d&3sf_+)p$eh=AUIlnafb94Pp+ z)pxR_*veoeP&toT(bTYh{oM@J5x?h$X)cz{%mR5UfrSyOT$SZ9_sy~I@fn5~1R+>@ zOgc(Pa5?hMaHL;qYW90iE8?{86%;bcgbIZqbgEfiIlkTs{M@DD8x}kxKH3Vzc24$7@ga91dZR^l?TDqE#&J6Wt( zS$VscF2hj^^+Wfwq{*Ct3*lZ0!bFF|fZv$XTp~!Na1XeF|0t)b9ig_5*-)NKdxGBk zaX~2rbR^k9pWcqC4pbk4l2VLBxwUv0(DFx9K1%0g{7v+If36J1Jt+K#YTIUEMx}b2Ag)&$>P6h0dfg7$Lxc< zu(Gn&_ZkCYH;~YFPLue}!?jB?iC|r~y(Z5m3r2L{V6@PGHb1>72Rf5cciXy<$GU-^ zZ6C%P;TSIJyljwtr9jTflP^64zRWnt?BnV20Z?~9rArExl7lo+Z ziaOB~4cL=^gq9=hDSvYx${X(gE<#Je{#_u`QI9HR%I0+SG!dq%645un{j=U7q34@V zr9NfVq|uQwd@KYs1+Ak5uBlzz@>?iAB|Uhq-Gy&kt#38&QJ^gPurll*(-6T(%nlp! zfG>xfW0NM6XVevoJw5oZR@|c(?&#TIf7^9U()kzcdY>Sjd^x*Cfah`i9csB%3A zrP)yTz+x5(N?TY+HF`jE7{i#DyikK$o_Lm%cHsEVVIj$k=u>`QApRYO*X{guqlx#N zP5IpL#AmX<3A!AFtQqWch;@rB@=V$kLuC3O!$NL&G zGaE{Tn;0_rY~S1VHe!h%VqZ@Mw=&58i9}%ji zt#%*$e;VYN?UZTcN^kB|PkhOBure6C@AP7Vd*0Djmvk^+z3&rP0_F=RzElKP)!kE` z>O0AVT4LZG1o27;C#JzDhwXlYsE$#(;1vFnqyjl8t0s*SYrmWXyx((Ux$q2e3gidaU+Q!_<;8*e{X8Vr11(}10us`@Y*Ge(!Qs`10bvr z2-ke36zrrE=yZbmpNs>3CnpeMw$X<+UO9ehmf;?Z09T_2b?;=@CT1l@Z}xijQ{~=f zFh4NcV`DvfCGKLb1%lQz)B}!6r4Oi`r}C7Xz8%}Na&F%6Tzla)iZqOuknbywjZf0k zeA|uz#BbdLfA{np;3P0abfORH91B!u2Y{+{7STe>}km zhL)TYu6OUUJQJ5Tat17gq`=m$yX=-1!>58*=fvHWdd)*8e$r6eK4Xv7rm8u(vGp!* zWUInaJV9wg=yiG-kR-=7W4Gt0&HhOI*{!8nFw{oc9?{o>$GE4df|v{-`Y`B4c^5$P zNEv~I`c(GhXylNOks$;s>u%W8f1Y0a?9U?RJ?XR3*OCncoH9n>Ef|&KqJoC+Ya2MY zlXZ`99!%@23N)0Z@iJpk!M%bNu{dq>Na8EqJXI!$hoXbBm#FVHV_3JK%}jf|)YAAN zcRW17WIMjkdC9+=Cpr$G=NC)5)+ezqkIXCJuJ6)fYI%(5Ic>gcqKD!Te>4`~lAN?M z^@hg01hMsLE!tcA2g^zlG@iaiOy8uv=89HYRMf%OL*8`BnRlmjeyQ4CJEUrWRt0`p zoN*|XSyV}hpeTDtP5#xp16?M0t*&xCp26O5-ow!Q3T5i+Z)xG>@O+!L|085gdPLfv+w!%c8i$ zj{o!B2Kvw`-OXNG#Ds+k?xxeB$q%2z*Sv_Oi%(u;%$7+ri3j|T)!n*s65Fk8*csDw z9pjP z4K3siV7!O?Kvv-B^J@G0ABra3arPzVD+vP;l>%BK6v~L_sSEM_OU~(Xj|D*r&iDLC zMN*;Id+XDNUu0lilKs)1863K8+K;fslf1NDvnRCcWXhEh9u|%0J zZX(lYC4@izkj6JyK<}llHj%R?V@Dr-87Zr*BEY!`l=yOt)UiiZR=4uQC}A7fLGrS)xLvyIbo3>`_ZVYETuQ`$hn&3 zb3NE;TP6z-eL&b)fvVY%N8SwV-JyJ4Esh=UEq!6e$|}M)eVt3BvRe9dKs!_1vr;jk zuckJne;M%gZloK!yZGv19Iv7H;HajjeF(YC@(Ag!4(Bm#A24!XaucY?iiGhPes~2e zxajRBWCHbt5mZ+)`IHI)wFXt|g`t$Eq@Ow!>K~d@J`HA!p0Qw(Q`EljcBNLNOXZlz zkjF?Nu!Q8XUGi|bZy2y#lN(oF;x145ZM-x^e{osiz=)FI$Wrzv=1PnKn)zr*>1QyP zPAA>LVsI>6`fV(xLfvkwejaeT<87aB*SGaf#b9>wi*FXb47>_}pCh^ld9K^(8shrGZ z15aPB#VQHbizu)}H83(Qqwr&?$6E(4#z<_BZvqr9UnEZ*q1ASx(ao5gA{9`Xf54P7 z>{#LqJn_BHp5nPr7@vFCunh#fg8XvQz7$HUEy%GRJ=1+61JmOqm|nQ<1Z(JSh%3Qw z14X}PH;I%0CB74*PI{k^(R1?>J1xq@9OI)fjNgFV`jwpi&bBKBPslDf)){RHI!e2N z0A_QCKaJ72=oIPB=7&`!-qFty*{9<;WK1avRULxy z^v$`AAv|j?JTK^Q6Venh5ZACsCvM50ph(d0zF{mTN?d?2gcyCzM3v;YAWf5q0T23G z3z!}>iQaB9q-+;N0i`Obe?;F9Rkbz>mhqZaPhn&Zpdp)O{0+QEf00cC=?Y_Jxngilt0P3~)mtIl*G>%qqi!xQONk5|cFgQ}sRzDjSdZcLmt-&<_C*3` zCddnGO@>x%o{P{s06fI!3Xje2IgM`{6;F`WIb`5_lAMO-e`QQX{>F@Ovhj_aOhQ7j zTymeroQb%7Fh=SH_&9jF7ee&o=dngSK0iIljJJn3{U-I2KD2hqD)PZ9WM0ov(VW-kqnw*N!}7866890 zWaj%O2*-pyf7BLMqaRVGfxlx@+E^;F+sRDXQl82=KwC8#t8Zy;f%p67K0SCg%AF5^ ze1TwmvnnN?hS2CxMz~z|siL1R>=}LiiSMF*3}&1E)KX6y)PZ7PEz4BWJ&hMzm_N+I z5XLG^Zw4i1i-b^Bj0eL=Yp_6g>b6F&8>=M0UcV$$f6c|T$}FBU1~`JA24bNFo_u9} z=lA~cjb(mXq6?8VOdD%pH=?Uu;RzwUz4+bMC)%@)xXA}J*kgO%Q{z)eRIdr zNzqA0#G~92r~>{TqezI!vth~?QBGk3Uzr9_xG9JOBZR{0lOtOpNaNup#$VOj3e%y1 z-tnu^e;G`>Ma$4#8q~>waSjM08a5Twk4v`KFNk)>zf!rtalvP{FXCa+xSYZJ$>$bL zLVPxVro4J)J8a2V$#K|y{%-9ChbVtj5QjenFcJkj#4v6xaomei( z$s+W#y|MK$L{po80YyGx{WVd&g+L6mpT+VUf5m;yazkS;2heAVo)iYhtPot;L z)DJ&$?y^7`&vFmMh`&iDZ@i7TR&Z{EeOHt$^x1oE__6Yfg$ z@XWfY%^lC$!Ml$+dur<7<{a*k^;Gfns=I%f9;~h zt+NuVGr>jLepTH7$GYku`+P^``{Wv)zy~$hHxbF_WCGKOkZE{|c`Q=o4hD%}BEIZ_ zbxow3OZ6`o7hgK|4;Z8P{801}>#2j^ZL->@qFtK?6~r>|zYWWA?b>kkG-iGB`PJhr zb;klV=cTmViIW(CGa+k!y-Ss3f8MFksIJMx2{m#qDL6ElnumM0Z`4k%W3=}JbEruM z;yWxosB61BW+kYH8xdHDk6V}?xn2_$J~C#B$_@4VurFK)pqwJR++_lZ1sBrrpgBGmSn9GjGcGdLW8O;*X@rqC>H+;>`7a9e}`)l=1|ix zt|?&jq_AE>g@08%lrCN2g68SvfNG*7QJuq9^>WIH&Q0zLp=CyRq>~b5;>)#!`2k7Ej^6GhWqQ ztz$+(X)QSTy6AoedzdeDm2}3q+F1TbuUL1hjy}t-xe(OW9D7DBe~cnk1>ejLuLhUg zr@!~nB+^SBlWtknlp-B*x?V(}DB^i#i}KOc1{?-yI&1X1BRZ^B=1$-s969g?wJW)V zQpIj@|JXW?=-wTB)-lSfT9L^yq<Er+#K?qjSjn1n(edX?<(q;4QOD zcGt`#sS1bN*aB1QzHgMiN{x?fPFCg4RQtB#<-WmK9woG=tJ+Dh5NzW)Ji0vS>95kO zU-f~HgLC=?qdPOD3UWt&oT_g2*=1B3Ra+5xb0#I5C*LD}f5N3qcO(EI*m%!vLs1S6j}ouTH!-7_E?fB^z159YYmM{@b-@(EyO9ktm=)-dnQffE5`OEnr2&QF{J99Zef2iadOa}agqVCp zEG1FfQv*uyPbg&0CJ_xF!sQdnooG}30g$0Ce~bJj40rJ=Flhu({tkFfKy4|_ zpkUYnT(GZWI;`;4309soHMK0D?VB#;zu1gHg&X0b6PQ*1pVIr9nzC zYj;w;f5#xMJVfMw+kt^9=81aFlr4wyPNyv2r>BpUs8xU3uV^)5K$SOW5<5a^+J<*S z?X@*WhJFRySV6>dqg9)pq2%RsSOLvt=XQPUS^Qugm186Q;6Q%Yi16#$EWs0`^q#{X zs1Fh8qY=zn^Q(B}0&LDJG|FD?ipfP=BlAqup+>gm6*QO;Q zanFWFqMmmOK5W3)*AuA%l1=D3Gzvxvf92>un;U6!;3?Mr$*eqz zjK{i%_nr%j;vJ0PsVR@?(XMqt!uRcbt&H_OyPDuoQ$0RwcR8%AopcFhlSqxOr67C^ zVQ%;;C4<@(lY}w&*!1!{`hgOn)Z?g48FpyJ5YYvaOH&s*UZ)Xj`;Y4h{4WE`f8<_p zGR#i)Nueuv`$ly|vTEtkBXF^-#H7cBtzG%D>(1_5(>YPwr72Hn=jK&B_1UVGQ5&#o zSrZN%X@WCYx8i!L-;@@eQKoGDNa@u$Dy}mEhr!cTUzFK~(jY=V-k3AU#hL`dhTP`u zc02mFcq5S?&q_-fHmwdpqzc6Oe=;Df>Efre4>(dzY{1@;r*AWsT&`I&CMXX4E_3nn zdOkI^7o!gDnDKn;8^HMmU+mc%cfGW{Q!F7DugY9URs_-oiDabdcH>Zed?`$Y1;sZW z`D$y~XmOp=5)n#JTSeCQN`llRruwUZ1~WdPDNB&O$2cCGQz|Bjix4e6e->PSVTWwc zjNo0`rE6H?O+eW0?7>`ihUG!!!T0R)aq{y4w3N!!RKOG5p3%Kt8H0zNxT^GYf>7mJ zQNAu?-L%L#LN$%QNy%w?VyFR+6_%A6EBI4l# zqVORrkSF|?47=Z*D9*|if0dzTbIuS^Jjkfq<+ockO9d+`Q6s99x)rs02-m5`{XJ&~ z*chyyI$Cso?9(*d!nVa6V%RrU)s8Nn6+x^m}o_C$iY#};Df4tcG)A|B0%#?CR zi{RVBZS~_1DV?n|Un$B1_ng8s`DAE^*5)rMd09yZ9EF))nc@?D@|1On0d8u~+L5B7j2yNMbV2Yhh<6YTddPtvKG!Bs!vVVo%N$@+x6&(Q^C;aQIfT7UC6G<@g!LLQx`b> zbawnDE6A-HU9d4a;0{)?K?{HHOFWRkTqA!91{XUa2oP#HB@}(#U#ft|H4;uZp;%)p z15kBza~;?9sXTI5__WjECAS1w4WBLMe~r}a@?lZRe>Hz_#oSs#c!dMH)Vx2KM_#R= z2Xkh04e>bP862+}P%MwSK|`SBC|zQZx<{CsgxXf=(bj#<#@BPDEP2;|+lrY!=uuer zHYk>Jzjk^HBj~Nzv^d6lnRvUTmFwVv?^GO%4okl#*q@}4CBGDzF+a@9tT`U|yo_;PzP zML0(=znB69MkuEFtgJWZCuMJTl-^eco|!(vf7!310G<9eKP|Rj(7cM5_i+p7UbC`c z*vzjop}hgGYE0Hic6x*4bhR=@0oJL_v*#f(9g4sC*7I`xO7Gf(m*f|e+cG_N2q!Qj z=#}CRIB3_iaB5To&qZ-MwdS<3qe5Re%DpO8d2PNAhLZleZf4QlFJ^2kk&{kkn)UCB ze=?9?MnGJ9VFju)Y9UD*lB}12P#Wcp4b{(h{ytjYsKh}zIoEZzX-I6aF9{2Ib(3ZN~%e5)XO9I z(i!-z!su2p=z$(j;I|(W2?b+R4D-e^e`%?9yIlTEwlXdphZlazFQKL&1?10VzSlH> zp+PEUaf^)l`f{1zMOmuoWsjDstfSPxWm8Ylx2#8$_zS~fsp?g5jR~2)@F_)37(8_q zHPvhg%I*!dOOYRVg0GyP)zfv_P~n(@L_C)y!>ewS*5aeAl?S`T?yqei)N68Ue{1*H zdZFVfJrW6)M=GC_A$NxuIC&&0bL$Fx@Re(1iIE&58G4?hZ&%Zg*uutyHVHtRVjks@ z{D&C8#&3QzRGCHM=kFsE~x`C;4)} zKzC?{=KqRaKVOeyv;^OJuyVS@jYcc- z?BnhLo&eKPfiSN@Mv_hhjo2VZNUXFA7fyI6z5Q#YKd$g~JEWV@FDke>W(P?l7m@;H zSL+seTvU3Sq~elGoxS1+E|KJGA?&Xup5uqWvip(>bK@`k*fGnFf2-^#`S)C5}*3Cc({e=y%Omc!y|)_0I=7G<*E))FG9P^S)FtEJqtKMypxH3WQy)1j{_ zp{yH!$0iV=*~d@s-zZ60DVzrpr0(kDsil^qrb*i8&t=R&5JgIWcZo1{2%>mK3x-vI zmRnz_ShO!vzrJ-LWQLv}cW9+9Zdo&4Np&%0pS>(;;}Lole>Fvv2h3^cV(uWLE?sLy z=^1cHvoep>cNk}$t`)*ZvhAD;Q=Dl}ZUz%OafY_%2orq*Pv$DdsO_(GrpgD&oym!@ zgv~(nv{d>s7wR}}zSCe4 zImj{*Sg@mYfBC2ppD1RS6@}l5HBoUgt^|D<@tE^-{{$p-&6_2h8ZMe*%1@!(9$R4_ z7Y;XPIR|T(kp_K7dW0F0d{zL1w+&t6J^YFc%pFQ7j~K_0V%*VggcxJNXLgK3$Tf3^ zQ5&&&tvj+y{QhMARQnZ>kMatKNDcSHTbO!M#vo=He`KYso#cpDWjH;G>{RP|1f2~i z>4a6AN>#<5gRn$l8sKCq64Kivn}63Y#?inUv`@oK-}l<5Ua54~&G1kLM^iNT&Ih-J z-yn!TZg(0&&(C`wc8-btauA8^+G=yrQ489o{62~U*WiKP(cqQ*+InY9iQBT^R+LDImfw z38?HpK`e)$KEN>;2kBi#-{3VfV{l$;+&XI6e;0nqdd#dbJx{B-I}(Z;JIxUwlg5e4 z6B3@~lY@_An_F27Mz~xtM~K`&FAF|3q%wODbRd{EKFk7&=GS5G>_9x+Eg(qAMRs23 z|9(wQRrYR7pTfJ{U!7N8t<<}jtCU_`@`ZS)11;vbyeZh>JL~*W)Y}nJQs|b_@;lGW zf4-XWw|x0vfm74%3``*c*!^`&@et_6k9cpr$K1}yUp+?jtfrhgn*C^_4)thao2)eP zt3s0M1AVJjOify&%?e>WsU-w`%n;ix3G`2n7*bHOlJ5JulPD132M-Zx^Bti6Vhc|Q zyK$lW!`2=HMa&kkFnV=MqOD>&T{1AEe=YDrsLSd~3$3!NYA4b9ReGWV!?oyZr$s9! z!H|d27DV6LR5>2&8a1q*oOu1QJGOJc1ZVA(`q*rzye5L#zb+PHdp@2iMK=>>rEtyh z^3)Pc(IQRmND+Lms1QY+bvRWg(-tTEZkGE@&2$7fM|3-mjt@n0BmX8La@@Wkm?b$9DIh=8^+b#RDGt^ET@`FPD)%!dJhxCd>KoPqR9XEqB~KTWAPp|G zc3^t5UhSku$xl6xk_ay^=+D2j&l+ehbcek<+omjqj?)UauH>gnpxyHCySyIN z2HM0LL!dWjxEQ?Aea<*)I=kji$XYiyrbb7;j)C|5p0JW?{I=J_)*yWXecv>_K-9>~ z^DCvI=u4LQ-kiDxM!nK~#Tuhgn(B||p7DThpNqw&rFK+1>_O!jdL~`8f5EbxevJrT zDQ=y0=);MP-O$m%*E8wE^h%n(ZB|u45PgTZD%LneS5Tol$jGNqj<(u)e?`|h+q3D+ zk!tZmH8^bDGFr7iwN0qOF7z6Yk`}t&sEg*|T65sXl$h@kk*KirafK0HLelLihPam= zkzN0(D(VY00TPb&Yvsg%qNB8d-6 zvFml~kObL9fZ+F6gEUVy4{?sYQnhFdU~aC{GplKEz5zhwhFzl8gl6Vxhj z?b^rb>o!gi?@7u%DC#gCmJD6ogi+L3a78Jdy{+9)mK{e3#uHwbfAKjN3W#pHwPL7s zPtM|=o#s6ko!b0_smOx@+}=0rduU+FjAGTCo_oD~`N~3#hPmV|?2IogSf1}#41Y&c z&ZKDSjE%1x1RdeyPiPeY5%f(r_Yo4@WbIQ7ps!3(0#7gTaoimzFv(x&&CIX&n3t#= zT0al$l}hIh6xn4Ie}S9x_6zN;^xi#PxyiH6c)?)HY2Uc7(s+l)Q{;+UnDx6*OXQXR zb_kt&9}bfj?V{km_;d;E9>Ze+rF9mWxP>TMpqTe+2q3RO}{G>%vnm2$E!A zcMh4%k=85<$vc=O&wzK%EQ-ucIhg=RSmhyHger^09_Zt4lzj2+!KO%t?3niL5pF*P za73vBPcVW!(}u5%h`K3!o$DmIvjt&iDgF>+gRU~@&y)_HdjX)1Viflzab!lG$5t~~ zE92Zt?2evZf5mgMAckg2k22<#lk)E5D%9ztq=za5Gw|=a6C|E;IA@Tg6qJkFTUobs zVvcbH&;Y|LsLlAr?A`*p0)p?z^t7DxJmS!>4YqtjvdJx5S!FA_pNOKvJpvc@?dw~9 z7diR^+*?M!!nH~|yRW+LEe_<-CdKSHH3rajj^LA-e-rma86+wW-6X|Y&ZRDLBo9RT zK4Q>VlC*AL>-B`e8B)|QY_Ka-9BA#D7D&-SdP|1v>$=z;y@V<;*$V7S6Nc1o_f8B(xecwZic+h~)bieVLg8O@?43j)M zMMId2`tkRmGzoaNeRFIP$%pSz2oBb?*=Gqvy=??=mJzBhEa2-Y9V8`)6YyRFb+Ec! zspUL%$aOA>^m0E&yMH%@KmE8;_i@ga3eBPAQ<(W|JVTr2EvN#02DTLa<+`5P5a!aZ6Cr%rpWU!UdZ{SIbK|^h4xxaTOA{3Gg2mP%Busn7rdAUn;E6Ua z7`?UOu83|@^OF?~ox>Du{)vvhqBLF505QJXGoJ3a08K4-LCKirrJEZ4*jJt7n4g51 zA5Cl$>f3tg$_lG!Xq}JM0X99sfpffy2;ss10sWfy0ha+51r(RJ9|aPZAgcldw{YeJ zkq(y-CIu6>6!iqd2$xGM1rxXG{RH$Bms~Lg6qjBZ1wOYh69wb~mt-^r6qjBZ1wNNB z69sp--4+FH0+(zz1r@hn83iE&m!LWY7MEZd1w5B969sm+NF4=k0+-c21r@i;9|bJ~ zm+L?U6}Jr|1p@<@A4CNdw-ms(;46qkTE1xA;yM+I%SvOxuH0+;M%1r?XIIt4|y zc|--<0s=WOmmx$26P7^*vbP&Z1#JQXIWdmLkFHB`_ zXLM*FH8?mplVL+Bf7LwMa@$6>@A?Wn-l>#@-j}MWsj?)?k!;zr7TZZG541%~oKQ0xdqYBqW~eYQRuXzHj6QRrw2=`?&=_`_!he*yk{VVt186tGCg zSV(1DVT358mKHj)klqXEs6q)Lwv5n)vdX}SfsV#004oB(e-Z%K3Eb|XBXLbAjMC7E z2uH#BMvB4;2aBaFtN~1nRE3QWAj1Y{9j>v^@ftd=@Ng=0eBpz{#i0nsz=#q>h#E$e zDxy}{F-3HU20X+7lpBR~Nujn3pecohvyE2Jv#`l%;ED(i5Cbq$3J+TWRVk5rJVQzc zgct)mWN@& z1BHMc3de$hG2pB8F`|aRSdbdVEGSM;LBY8IIiOIK!k%?;O9AI(@Dk4etU^G@fRBs< z6iylJNjOt!fD#qzEJBV(qw>LN(Gnf77m&BG4#xs;M=8=%X}6R_J^&8D1As0F7sOU* z=vk1le-g$U=;1o6E%Fn4z)9dX(qeH8XFcejq`iT9$m`(47DyDij=BQ-fe!*D4?862 z4(J8)26zNL*dY~a14vtea-eqM4!koGF_k1`fjewK9mO3^i>=>+R@=G&+P7m7l;1{h zK5nB{N=0KP&BKj~ep|I#P(KM1>4V3-H`{R6f3${u&P3>Y9k+eq;0icUZ!g5QqqAHA zs)KC^xX%WFVS<2tQo-#C&<($D{RT<`+u>pYr~sUG5)bpiK?~}DNKy;8%x>G(kaKvx zme^XWfVzI4TPG1;8^C}>1P_`{>yRTLKETo$8mDz|akkL}Fr_$aY1|u!lu|#969n!% zf8NC~V+{>Rj7S`OT2QwQ|8RFe$RQBBBH{*$4SlWgTuW>lYJ-;BY&5LKJzgTl+@=)) zc@~2$yu%gB;vAn~B?(93`5t&K>pq=NYrtHCM7U*$`K%_cRM1vo`j|Gs%vwGxU&ABb zklX}nOQIPrrt<+MNLu1O>L{<%f%u`2e+Nhh7-`^ah#4e;%KOs^iZsrl4AKd_lG+#% zPCim;Si{$xD>+R(f-)ny)lZ4v^;o_KM_F<#A;v4`3&B&emRQmrUF~)baWW1$W{@AK zd89r6V=dqX!3W)3vP%+Wq8(bYG!7c=(+C(xji!ngWgCz;# z-D>Ld^i$ddliGAl`@)^(EF)yeD7g$F(x2^wQfBt|mi`>u z<&|xqJMfq`txvG)GbHRoC@tQy)fY;5TG!;<{2q{Og3C4L2!YTBC8aOu&)CRSJ^*jm;AcDX5*g{gOvtzJ{u7)rz{dew#6w$kR8nu zi&UUE6{9u^A1vxT#5|(guqWUa)A4ow^1Z%>(Dd-i#N^Qeg(G+?F{6c1O*fbFWlT|Z zm01BNF&iYSWAJ)$v=TkpD#$-Dry!c(cKgVYOicLza+7jRf6Bp_vyl^Gg&5crK_ag; zUGBVXxWaLPRT%vmg&JTqK~#b`jdC^tF*V*NIcoMlpb@;z6)K|Q1rN9iR7w-bDhZ_7 zfC_?KL4H(P!kmt41D<3_D?MEcOZL!2K{n8mzC9)8SD-(#)dq#QSV7SRwLIcU(>!E% zm<4*uTq*cef5<-$8|2_sv>yV5xLt5C5gASt{iU(aidL}zJaq*m9b@GKL= z{j{DjNr{&Vme@zBFO_2QzBS~LK=nOcD`=?@6bwc8f0i7vMdb?ueQL0RREq)$G6%t< z*K!0ivfWh3ZnS-}+AHZ2d9$howATy1kXR+A)CezyCf}a7?Hr5?Kb9|s zK}^sr0t`(`PKLrna>`NAStJ=tNwuO8ZR^B5QrQaZJFF%XN!Pj%U6eT!0zB$iX7U%+8>L)2Yp=CIE%V!z#YTWWjk}?}dgQ1QOHc8HTL9U3r z7Oevs$vjEgjfRxVld6Kl`xr8}){$G074)Q4f}}=@kPkTmp0lCfZ!rk~8|x?+l2p4> zRM>826MJ#YsG8lOMypxL^N_Y;pau3-D|GBSe;A4-G+iik?@wtH%&f(eEXBOs;&b47 z6IDTiBz&Dn))0M|C-XpcR9b1Isx!3U4F!{?x(&Pp!SZ%GfI=guWd)ws7*2|;Xtr_9 zE2b1#_CErC63-y~hCWHIj@~H#^ ze~Z))#0);0S>2MOu0oN=Z+iq#YL<^y=XIi}?y@|?UAk038q6yP@@fo*Q-y(AUO}WP zs*rMJqSfc+jK=4fs%h*vGHq^RXg;Rt<*RbkZKe)1m!Y?59G>5-yk?a& z1!Hke+L$a#b5mWFtN0vw!nzcXW^{s(A=PqeK8=MccnDRsix{5Z*VQalA7584e;Y!( zy^I9}EJ-8lXVRn5mP}OxBjlyJdNr@ml=EqR0mK5yW|@Gzbl2!a%M`Hc<<+-T`=vI$ zJEJ_!dfWR8j>Em?y&xBw`Ms1SGePB9DQRVTUzMN=${RyE3s-Y5UuCFB&n=ypL!gV2 z-t{fPZs;$6BZyceZ29dcwLHSqe_;YHE4m=s$DELLgtCLW5N}w2XlE3Z*`^9SeQ>Rw z)REt5Qa?CF&WX#Ou!0nr4^Gwkgax%;G;cIr*6&hfZ6!^XP3kN3_>DAqG?%dxt{!1o zq@DF@USa5MnX%N8Z1P#=75BNdRi!~$zK2vL;8shD3Mg5y#EzjBGiu9Pf9t8nM?0}5 z%kyB?(0xd0bQ-TvZslT$yqd;?aUYxeLC~w5cC-q*vfvOF|Vhv@Q}4 z!JBfITNfHx;W;@sa*C`Wf2k{$6MgcV&P={I+LC(2mPc4Bw69*v^O!1OBBtvXM9my? zi2JD}yM}S0EVwl`Y;(mM%&Up?RvT|K_j&dyEyKxMKFe^rNG*?G?Lkvj3quSl{s~86 z2vwB?RV);x$Fx$@wE!Fn zo6G6A0Pv@a@%2ZvNL%ISvXE^o@)^7dw2-j#Qw+4yEU{{&T4=acW{d^(z6m5Xw5H5-r1#oxE(efd}Uclo{iQU2#2%(eXq(2Dj)6enB%`q!hR z@N)m?!|4G_e?bjML8BD#b(xOOBLxv3C4~dv|8e3x@|}5}xV?naPhs)+FOboCj8<|T zMt^*J{Qli*VzeLNvrXEr0`gftozKb-^7&hN*Cs}^(b>U}JyNlf5Ef;uf7z6%7ifm6yD&`Fb(Nz~xCWm_V$vO|q-s1XD5@hYQ3%bbp5y0cesH|OPjo2IpF)vJ&1 zKYjQaXnHm1F%;4i`MbDS3{?$LM&vY!Og>Hw5oP=!QD0`GujBD|{zMbAGr7EMp1v%a z{`KUde|cEkQU8zef|gA#t4A_U_yl) zr(3MmYgwnm*N1zDpMbTXHbd$nfwdX~B?x7r%XDALmbTzy&0D3^pzgak$lWno&d{7@y8%}_@ ze=43tu`d($pZyE5mhF6ZwD;=sA(0;+aBjle=eiH(0s1k{J5E%8Z&7E~GG8aZ9DVwP z@yN;0Z8xH#PCQ9j#UqSY9>cy)xx||XvWd|gF8XBt2bzM$J+yp7oo_I|F3O9^cs8C- z=3p-fk8*r{f%kuW!ykX%kEWEEJe-^Sf4dHyZzd2-Jtls>o8HesE$cA)&->fO_~Ofy zmRAqFgPyp{!S?lpDS2er-4E`}THfGzfA{VAYf_JayRS8?M>L@OCO%p_?0|Myook24 zwc{0O!pCxyGMRSXvWnv~;?x2-vy3Ba8TW&o_owgC-=Ek4V_4l6Egnj}#iQ;^e;B?m zUs*AymD(+1!5np>P4HUg|NPgFJIBXJup2OcqE1-!vlxh=2MMP8hosip-ll9V%YJlz zesqeV&dGpotIAJHrJjW{KP<{oO3;%%Y8fIjK5Tg=-8`LbNjnqo8iVhBrMGK&iFf-4 zryur^Q(?dgxY`AZ=i!taj&O4he>J>8eq=1D8PF$vy?FS|-rHS_eoh9wzplN%cF#ll zuAx)GO({cA0M0@61SJT6b12h#DAHJ z|IF4$`3$_a!`JI>Hn|?R$yv*)zW?y*;KMnQGn6h^pa;vMSR3_0J%{eZRl1WRM9BBA zYH_n%`^37n4CC41>EY>ne;{_a^rda>M9ec~2QI9PpfP?vXhqHEow|D!2U9(Gno7Xy z6o6Y%X+QS-(Prsd2KW5Ur~QL{Wa-&pO2YLN3wi7l>M4&)NOm)JzNex(rXQbMCG|G{ zQp!o;Ci>nEGuE=ir!NnW&Pcz`20Slnm&UKA({~l0%!?I%mlX!7e`Ff(yOgbG_4Z!> zx_AE5(do|7;7cr^4S(T4X-Hb4%5IKvotqOMF0;1Wi_A#L_v5J180#PXUC$sqikuC6b+1pY5 z08gJL7mKSo{n7=kJ-a=>fkgKLDHt#wt1#-mn(Jbu*1*X3F*568)Om;1Mac9J5-p5m zgAx8ZNe?5tK1Q7vQC*B&7b6oggwWmA_=r9}Zhd_EuaUYae}z7iV$LLyBW3$Y`Sp?N zy$kB#qD24S=>b}b9V${>^T#-Ve0uLcx(F@NZQ0_H z&3M$m|3R#eQ}-Q47p0zZOF4A^MgRT>xxxK!zY^#o)Kl$XS}YP7i~3>~<-%i%(dMPfLaRE+lMVt=pB#4nDT0(k9m+n|oNdYA%$IMP!n9UmZ~(PNRgsMX%U1ZBtU?O5IUiW$V=#*fCLCdFbJVa zjTC8b=qeo{^b!RG1d$>|5a~gXCLp3xgBQQ~=Ki`f_s`DmnccH9=lpo)JTvSbR@ElN1uC-=dFGor_D-(1!HTShNPFflHAF5*0riYB&)leUWKR}vo?mxWYm zsRr)SEudb`Mbv{}{-V&vQkx<1N9Eq3rma%F#q;9d3R}8^M3Q8C{dB*2Z`VAiWBC9( zRK+X{&Kt-=bXL&jpp$ZsLWD`A@rQit6);c5)OL1GA%Y-nW!_HwCJc1 z_H9^zJdfg6CekWwBKm&x=7WRJDfFnZ(V~(Uv%*8?DD4Myx});x6^gyPJ{!a%N`H0j z78HATJLhKc7nFI%Z}F3LvA|21Up-$w?}HCtcUx0lQGYl$TY=~4UdJKo?bK%Hrt-j& z%xo_7Zrxor;KhF&OjG#%e%O`{3EV4v>^3}7f@QX+mzJ2W^y%jnZokV_#b-JFxjT#NMFmtJI#i&u!8 zdJndGuU2gpcoe;v0}}i<_K88a+@WSy;I$a2!2w#U!t)x)vgcGM_nbcRd;2P>QHR~d z%h}K0*Tn(%*YkGtV3(K6TnlB1kdlKy;0P#08X_kNfk@t92OD}jYdZM50706{au7KM z2psy~l+1J@3yJA;#PTmCFC-`rVFzow`}+9<;Sj|Cvhz>{sQiD}`CJQJ!i>U&lhAgQ zh+p(lr2xrU{ajQO%AK51OgHPSZShl_cJx7frj_LA}b%*}B)Gg=Fj%l?_FBtVh4GlwmKa>KyeOm$9XO)kvlebX>saYYW$x9w_B7c zakdrFz9D2X|M!k8WJ^URxZ3Mv^0%EbEy?QV(fHu~F1l`D`^%)+5OD5$V$$qCYlX)T zf9bdQDm|C9{a%0Y;b_as+p#h#9BLp`;ZW`In+6<>V8s8CQM*!)}x3Sr3B zMD|t`0F812?va?On0*u_n;WS;K$8}4K~bKUyGaq_q;gt2%j){Y!)=2^4O`mquBIXI zmjKrm_8o-y)t|q^(gR4*LERjAgyEXn8vARP9pw#vSDpz&L55k#@aZwA+Ux3tw;?S0 z=miwI@~U6y05eY`uK9xY2c((n9N#)F{zyk39i0 z8rH-QX;7n{KXk&U+yT1}b6ei|#MMu|C*)IZXP_?s&}*-=Hq256Yr&n95o?%*2-kg; zQJ`y@F4B^*={5afl#iO^=ZT?-bugcc6Z433(+f>bOa*dRh;Pap)0jPZ;_@}5F$wps zWToi~vcS2)*f`#}p7X%eIQ~mf1vQbE;)pL7BQ|uNvsj4D6?}oJ#6IOz7b9P}@W5wL z{M*u|YqG8+o$+R?tag9}XsICYDvz&;B6GCx$FWG%c@Oh- zWNS4NKvb6{Kir){^~hjRE>jU->v$PHLJu|^&0=`oK71EQ-K6qwxBjBXY(9P~YNtnh zM4h|3cb!vz@>f39X^&!++n9Y6@Wj(j)3G+sR_ZwIw|}mYP!bt@G}sj$eGpDy z%?)>_!hR;Tj(?qh-W?~ke=;?>W8T>hNw)a3v*qMVNvH1f>O2 z1^)jj=T93MxOlnwV}Vc@RPH~xD4v1#yr*@!cZIr__XAsqM011;OUjJ#9WLMsi-t!> zO+m}|^vlddv5t&yRy#>mm-z~$`CA<#-{^GqX!0*=C*o;6c&9`%xfvfFCn+YeethB? zdeHVKH+X1ZPx(uZ-yhzbgMNDkq@9nf-`Dr5_`djGdi{!p0-yq%Rz)XKCW6~ibV_@M zGp(?p5Z7VDn(-W`VUtkk@(V-;g{UTRo*cw(`Nv0|k0`O7yinJsFB46FEd)0&w` zL}$W9l~|RLpCDZ7Z0}Y0T*g}1IF>q6PZFdE-~fUugN zPFN=FicfC`LJ$ma!zV?u!LJGqEf_^yvS-g>F6n?HxLm{dHf|7J%~f!@mncUJ6>CTf zvOirc>5y7{alL$3*Ve2(Wvf*7x!N;ALTdavC>~Ot1ez}0Dja@Jcsvoi&T}YQe!G02 zMgMi=NvseNvF=bwvWTCOg)keDgYTmf#zp+K%>EhXxbwQ;RAa5G)^KBef0_rlNCRDF z5rZdEUNE4`5Df272+c{i4gs~D;PTl^eWenGubk>y9tRWdNE3ya{GUrs*6{n2H}-AO zt^4Uwj~)EFY(@{?09Rp68`ADP0W%z+>1Gj=tQ{f{;k@-O;HBs+{@u(#wJ)!HJme}v zn(ar18_hwSCa!p46Kg!9iD^n|e45z{g~h~{gI)rt7ch{c_OI5uxTzF=Xr`gW5_O;% zFJVhtDo1sy*_WcQCG)fkK&o!l&3?)XHYr}gF*Jlp?L4wUzFjyd4pP0|#s)uC zT|3(qJj@2KR~`uYU6xmJTuu}y$YWapM<^AzPn?B!#g=VD_x3sHnSy)DE$2(|b)T#s zS+$#*s^3-fGt@wf6WB~Xs*b>=)cI{hU>fXjkKG=bbiwP!4*ajW2$i4JuY`c7m@?Fh&iLFP#SvBk;~4| zs#frw2e2Z%lGTwH&I1SN;(!f>Q!}@#-OtLqK5g%t@_eu&jzMb++>%@T! zulMCms2y@8t8~3t-;_sm712fBlC~UNORn_UD64dpesoe($tQjMQLJJ-o+;3!0kgC2 zgqf}ky02$W<=swtzfM_9s;h0Zf&yw^rLWH?kGmJq^e{H&M){5q~Aa%&}9G1KKz&J|fFvTT|B9 zwH3emI~g4MESoe8Hc zE~K6-T90A`)9{!n-O_AL1#UCS*y3rqlq67*0TvovHr+%n13@6*MGWaUdLBoasR&-- z=f_W1&T}l;A!hx!Y{*u|vm#8&aaMl|QK5R~J{BMd4&mB^&v6O_rc>CXMZPoWI4ic+ zc+eR_gI^vEZw;$bw^=Ot!c~j5jZ?htc>i??TlUwRAfXjw|x&_P`IENy@#BtdV-=q|o(8!p=*XVu%HCo-krhDlzvUyE3G6fs z7vyI-kGZpEL7+;+axu!?^txe;OuA!2e1B1G2j;03U?wM9BqGQBygvKQ``c!EI*1T1 zCORSrN-;s3s1Hy`k*DA$%Y|HMw+5>?`a`a{rUBvm>LdQOQgU_w_@(n2w{8JmEe-VL zN*)Wx%$;U~OOS6~igo}f&D9dZrY5h{UyI39K1R({cf){o$Ognr0}WwA|G;|nd8JNXRBo`Jbpob!8uV>uKruk={&eUgq!Az;2p*>A z04>f|*>Q)nX+s_g_kAfKeH2D!`!WQ-Uw4y_)!4?CcuD_LWN@3 zouMcjyW4Hui>+@X9X?qjM%u*+kU|Bc_%}^yZ&9XwpY(CQhdi$sX}UK`Vn3|ON0bYH zv~WT>rWqb(4+j{N2(b65vWtN)&=o}TPGH9E(Etx>2}T9k8||;s54qHQzAfa>@(1(A zbPWZ@j|UOQ2RQxJ=H>lkWR?;Wkio!;Bdd-3B!gbK-XzpCH6^7#~eP<4AW;jipamEvbqH;R77rIo*Ea{uD~$vqR@DnogkU zR=8xTWijf#t(!C4S4})1!5h{DY;fvBWq6;qOb$K;V$hjKsERHq2PwWX;$A1UfB6sV z^gzIRwldCEqkkvRyc?KaU!Go${T4-It})Yklsi1Cc2Gv6i+o9CzTMF1Ez0ACTL0TP8S|B@X(<@_2k;(ri z7!AP(P#)b8eY|3LDlaW`#goWARmO~g_cP)&-vxc{uaW!k!mtmFzA9#(>o6I;vJJ!`HLyhCJ)o6*m}5RrI|XC?Q-8~{X(nk|>H=6u%(h(4LzK!s;=k4iguDN-zyfZ1( zk(G99Dmbos_6D%xaicx07|&|wP#Aw`eEJ_7Zou<@aj$NB{=2J!zN#H}NtqCmz^rM+ z5unt75$*prgalI06YZ&`LH|Zt@)WhFY;q%h+6kRnA|e|&EnQTgOt1~DLEhTDWk0eI zNbAI804q`In*HaC3nIo0*OQzhM*498^hB{pS^A$y=&9rvIj+){jLNeiCBZdYPq5(Z&wLuvj!b`vlaiikqlBv&wkRs1ZqNe+cXdAyXQ61~L_NkOEg%4f5YxFAFEn=_z> z%Zh+7?lW1n|A+~BAPSXbJVm}@on}rwJRK_sgx@bB#aiftck(gWk7HaQJ|#&4;?|$T z&RiTf4el(lDOa!IY`umoB3QrZS^_8EVhGIYycYs^gc8oY67*H=f8^4)tXPhDF zP#&TPw$=y}KLp}>rJ&55MlXN>a&uhzem!@KuC}o#0d$WDDARn1sXS>73aOb%Q+~f2NS|bXr}<@P@S`q1^CaQ~ z%RRYAW#S6qU!RI4n_i6J)Z+^K$T{HaIIrXs+pRv!I;IqzDR)s#eGhQ}%t(->vgh)& zB7e$c7Q8d)a?4cIheSDjjoDNE6bGL8#1EQW_Fu5h2|{qH`zk!setQ*C5p=#s70YF< z=CXj}HsD#$s6zH~ywWBT)H3TeqzlZfF{C@24i+|JA})CbY_hAWnBVz#eKHIy+3J-w zvsl)v@tw-RW}YV56kSaL$s@VLt5i?qd8{=F)ve2(FP2Ge8Bl9km=w`PVElmz2}xH-y{ZTbWe%%t7p6*VI3M1t=8^^8xm915GvVAJQ)?Ozq9oYhMfo|?PJ z@9AIPQ=bipo(G?azo|#&24g!LKVy%$caTlOuQN$Q-?RDCQ?nPHH)%RSHli-7IbCIU z=8W!+eF+oG|1Gksv0se~NRky>%_4?JWqZx~l@GR`W;1rchOcpx90Tv%*}Er#3I(Y~ zj!q_r;BN6#m5=YjZjhZ8^_6WL=jq3je=C<Y#b%oiPENzVcD{afI#_ROlUZ>Yoc>}u3x8c); zqKW678M6sMslBF3?yxiR zocRA!JR#3X@)17x4F7Kd_&-%Q(u2q_EHb0m{}yc?ca#}yF@%&$MOTQfPybF150^|o zi#vJf>u{jozb@*QhAx&&7QTrWPT6^1JNLDjk30`9=tH2z=W{cNV9FdWVZ_3Ju(CEaV2l8mG_$nD#~4DB&q|TNt+nzff32zghyySyc8mlBo4y% zjWAX4Ng^1+FiJ-w=6TQjAG`GUgfIuxC1|rt-W-r&F~y{BLSq+$)H_t-_UFxE#sbk~ z&0qb1h47uzkd`47-KkK*#dn9}bpH2I|0t$s9PEy`cH#gi)=5rt#)cyC0;M2w3OAxN zEPW{6#JSt2m{bJMQ2n+beM9XJ5hqr$f#f}+?@YcsFZcyl0fOb z(#UrLp*`@}Wi_0H+(>M@j-jb;P9aWv*P{d=S@Ha>;M#xqV`IRi5w^V zCMVc;i+ynJ99#s*G5Ys)d3rs9TTlc>o;Z1aDzFG*lqwbpGt3odN3p8H0%LzMG%n|CEYqZz2n53Ie@#^)#Leg2v46hs+?yz>~dJ{H`z2| zK{TS-om+YuIQBNRVY5-h^V{kBRzfMpmub7`V`wQM1VW4a!W|0j^~Yok&~g4!vG^c6 zmz|sr8(O@ItX$7`(%T4IPpaifHeV)`XDG%^FM)C@5&0P?#G(u6RDSBu40~X@KVa$w zK*8Ut$Q3BEtUI@9_jo;W@OYNPR|q(L8_Ca%>CNu6D`CV?SuG0rZz#@=ZY2# z8~aOgD4kh11E#7Zm`PHzQt+AJ{dpi5>gRFKI*=XMUlUK%S)DWkIxaSrw7`>-PHd6J zX#vPVPUnr48m0z_r-w_)M6jhUc=&O2k1T-c_eS*tqu~3th@K&fLwjJvnlFqs0eHu1 zrvrl|O{VFoBYLE62{ZXaQHsfJ=cU-qum2u5JfaGQ;)rX;ELJtlfk{FukYL3nyFH0O zy?karz5k7?!S9W95{gXhXa*~(8lBR)6~rRqpyG|OHYH7bW7PwVa1Dx z`}D|+O?`+$%r3=<+Lc~Rw(Ec}6}4;X^sxa_ZwC|+brJNreTjqa7g;dy$$ ze@-8V#@aLpUsp!4z~P^y9H7qczrcE!DUi(2q=W(fr-Sr|mz#Bl%#N6A8ib5{`ntaF zBRh7F%YE&f4z?$?&KA}eT(~pasG25wAzU;}B!bQc2xjgP14M)JktlBZBqZCW7os&UUgXuSf=`{aP zBvHsCt)5tvrKI7rkres-1Q4+bg4dL-rWwZ`15yl=0%X|q!9OhT7g1Tg@J)-EDp(p- zdwMQX$M4J9c$ppq!pGdOEwlKv&-wI~blFTq# zFl4+SPK+^5j%k!Y1ad%H>zd$go*Z-LMd?$EzeY(O|Q;ap=V8IKia6@d{D7cQ*JcY_63_FZ6jO z&fQ+A?};&ORs+UiKF3HRKn^GYaA0UkSQGSMV#Ds0Fks6uKn9{R+9jR=#_0e@UtVky zZvj?2W%#HiEo&Cc2z;;cC}I!|TUdhKnQa>S>JL^%REvh)fY|ATM+2)jhr9|4tVD22 zP1<;HQD|bw<%t#!MHL|pe>O9!e2v~c*`FX`RN?6M_#AAJAsVrae8%Tz+Y*lURv>=G zrU|D7&HfxAIA{ndOqaDxtV%%qEf#e)WJ$gpIzggH`O9nHnYI9^n5-Dp;aaM}5aL+% zKQ$&+WAVVNx0ScrDALOwV7JH+QSfB4&#v>Fw)37qE#OKjppMzMf@F7tW?*xY{J~(j z+5YMdFBp9a5fZxe5;1hEu_=z|mKH>`Wo!t|Qc9nm*2oou)o2!B^om1wn@hz6J*A^B!wd?JV3#JHEW5<4U6eqf#gax zqi^N*hTI2F#V7Qq1g1u62{XQ;ry@s>t#j>BRXl?r%G0KP^kN$i6MPi+>_ra1WW~5E z45Rx%Ku*KcfJcdhV4MK7MARZR2xvxj#dGh`DG~_6dN8F?Q54xUeROb*i^HJ1LWOt9 zM$rEV(m{$L5WcCC1%?r)0_pTaHv#^J^%SKJe*)WXy@V3bCgobc)DWn&{G)^lQzbLC z_uLE0M}Wb|a^$d*!5gQ|+F-GERVqb{=Sky9(MZVtNydObUHSp^eV&B^AkShS12WFN(^HEgJcMkuu0ah-_Z4LlF5qI;GZ%Y*IBO)LA@1eAuFk|4q7biEz^LoeDh#xRn0#EcuKcp~WI;gY6F-%>eMQGWA4wbE(2U zGJU6Q^8O|#yAC?mSRKR80?kmYa=NAoaf__X$_>gA@pc^_nj~}*^2mNryh`4mM{4Kq zNS!~ypZ);XG&c+%if4qt&0}4#7OTpV!mE1|aS-(kRqrzi7vkG|#!>m-vbbkK2yryE zln((gS^5ZZ&t8H2GUuh~=b7yZvK*VLK@%8(kc876VIVQtDQ7HC3p@h2ULp@AY&in> z`zb_gp5xM30W|k*57+N^on;(PEcJYc1CuyDidTTHfWftqQU)rJEM0!7iS+2jw_hgQSbSBR?pNr`X!ND%Ejtbsr?m!45p7AlQd96H4`LXq^)e zB&G*{i;jMjpl@CEd38i6KdEE zR&(Q7oSCMOf5Ro)CEPuRcJv7BnBXOT;;?{I`HCf61Xm^s_)ZS6B~Tg$TX&GDi;e5` zR0<0wZq0X(?gyQdt&Vhl7^K3T52_sUtg@?$G!fctihRZ+0Mb9AN5Z7VSw@DyTQ*BN zS`m~(2k8yr2_l!+qS%jqV^QibsA7ckzv+I6p2mzCgnRtJd{WUq>Q=oOHMEchRSAGp zt+1w@z9sjgimTs?SB~1hENCs#-gkAIh4J?-cR!w_h?}6xaXU8-AcaziUa&A-N0KK& zNJS0&|2hK}7Yl9gxV(ER`O77N88ddIArP_oM;WIRDp21;K$>&-I)%K04Z>e{JxYKd zlTX{ZhFOP6`Hnj9j<8=jU-^RwjJV&oWan`sm-?{u08ncf|hx7=3o;ra^Wacn~(uBqmW85O(gQQqk%E z8BC(KMF(O?z${4%)Tm7_steE{oZSCQZ|M`j0(1SZU;uC31BJ%&|D{iCX$^7>}#B$|p z2E$udkCTi#$Nh%s6sc2SHCKK6iVwwP`zv$mBxR$GPhR ztA)$==?dqJNgjY+^D1KvRovGoj&yJajL$8A(M3Ko zy!Gm7oJ2i{QLCuFk&jPz8n^1(R?WCH;5h53O7}vgz2M0ZYIQk{kriAZOTSAdRwKO#@9U|6W3?-O-E1O=VzwnrmHX9Ip*?Jqo^Ks?4p z@H`;h?+=J`hEN-h07Qo)d5>cKa$tlIUb(P7K@|$wI?($f-Bd)gdhkA<_%1`baQIA% ztFSS62Xka|<4DNlcwp#{*ltav_!Ti2HUH(Iz?h3oIJhQF{@coCEq~yr`9egU-dH*| z`XD46I($p0Lak+2&5eQspelBk=s}qel+e>biEJr)k-B_gfZt#)`3Z!&0lm;$&ut(( za*3^mdys|jOV%8wnqhKyDyVyrOdaRbh_!Y~l6#cxH6q)etI^Ws&I}eXw2eC6#MTe9 zuJ1szWdjC4?xGQor7ssB$Yt1OBfQ>?oS?3R02iWRzeP=1hJ*@1M}v z)A`q#aFl~o)U5gX`uNvI#kF znh&b(FZ1XOl5->0c_uCM$AY?`(s7IU*(@V!ZBn+Qpa}LT{lJ|il+2VWE3A1Z6V;`D#$zy}LZlJSI;y4KV&||R} zP7MQgOGeJzPt>I5S-*}N29h5{|N8G7mO!TUU97uj+3OsWIK(hKiv&3Mu3O?t07U!IS-zFiQyoY0??ma^DQ7){|jE zoDd7uwCV${3e_^{(V7k9)anZ9MtzZ*lrVC5Duu;9H6c><^0JsH|Dt0=rqGA2h;HWT zT6`B#&wz|Fle(B4$Y%g0bi_1VMfecW)Udmbf_XV!{yS9@ox?S4vty@k2 zl@RY0epARLngR?|hJaQhy|0;yi2T_g6$!`D%##hU`{4&s_6CKIS`|u!2OQx+M%2anqNH;A4n)<6~6D3qMYEtI*Hh%&UVf z?!7R#4iR6jd_xGCE>^8P@|8;y$Do62Bu3q+0%q35v_0E|F*iP}zEA(Z)roIFZ(Dbo z{No5X?IeHIy+zQ*!x)dIY)6KA11P)%TKC^O)dk6@)P;`r(%9ZRC}$}yK|e?9f2>wLC4}N{tQ~M zDsc(gAJS|FPQN(l+g-a=!~+<>M)QXHfuse22WXR`k4MDq)Sa8ErTC%*?I6ROnHMVP z=DhrMW&aZ!u3B?8msb2JXyP6{@Vh7u-4{G#Ye= zQT)~WkzY@?J+Bvh#EV@=v|bNj9SiImr#cgtBZm_sesHxq1W?i(|D4!wYmsM=S~a~iqb^A5N>@dip%x6odH6@j z_}pF2&olJSlBn4vY_$YnUr87QfQm!E3KKYzL;Ykc8IJJ1uOiG9D@?*z8R~~J!V+o2 z9{>w?UZ8fweD`48NqyP1?(sh7Y!k^32T?|2iT()%FtQuQo>Rn&4KTDu7%CFO2Y!nS z@qn=kU|bt<=jzlL?h(Tby%Rf7(U`Ef1f>p8$G{-f+8-s`(k=iNBe7O|r6Dh~HE}Wh zbm~_a+41B`qG$-+rmo;$+aZGZxAQ?GNQ;3n6{DNSP$>~a-8?r+$H`4|R^y6IRpTDh zhCzg8)!p-LX8-V=VLrV@kZG_CR&YaS68p zP5$)A4~Wyg*&}{>75>z2WPnQJ?uwa8uvo;AahVn@?R<%fMlk$=+Fm{ImrM=_0io$z5qPu)=WK%jX^Pq! z6HriN#l0O$wBIO%NKZBOa~%^Flr-f5BQI;GgHd@A{qwyPN9MBJ7WsPsNZ6GCs_lF) zv`D~$Qr7^;YPln^6%fIJZN8pE;Gd6+U3|_+{_}{nwsa%(b{?$=n)Ml^&Ys3o5{_TB z@S~xzB95BIMUSMw7=qgS5S0npx*N2|4@paDw<^Z^JyAb+0KZS3{fDs}`u2>r zojm*|5idcCiQUdycjZBjW6R6MCVYde1?Q1=yqpCf12-o!Bs_QqLh0W=V7m;-lN@a4 zlwNs4n_T|Q8tI9>h0R*NAYuYy*u4A8LQ|WhilwP=V||IaP8bCB4@*oe5UTBWSkk*^ z5CT>i=TU{CGrp}A$!kzV%$B&i0qF+}7Q47t!N8cq+r+C!~<1s4JFffR}sMu_sY+ znKomM_5x_njb))K^g83t#^brymQ21Qim9Ig$#_YvMg2- zC3$VW&RM_zu@=^33;ASbRlia#TLDXF8DlTtlV)%}O);Q=1XT3K0jo0H@zofMhPsF3 zw+kz@vP$8|35$m1shtIZ{XOvw1Z(n%X*m~Ev@6RsP>Sa0YuDU_CIrzr7C40xNL9>p zsJ>wMr&MLmyxxco87tg0CGW;eYV|HwFVys^hfS>MJlV@K^>v;uDj$_ezAQ62_T6C&%Fn5A6&dZDGS2YeCqIt;wV;1{t>G`2n!bG8Nb{a6NA498e2kB=P%#TC(A__t4Bz(Phpx}*M~Ca4=%(FHpU4yW`r8>(mY>G%ZFr*4($jI#g`Z|M}}ErNZMltw37oX^Jteu zv=-_d%4xOM_sD&w>ic4UahU=VnMQZ4w+GzTLMur7emWg&r5vb|P>#5sjV>M1=ZjEF=G zKuD&Ua6)}W7(IRSNo+C74e6tM7ym*au^e#Zx_$L>zrrW(MGM!h-X#LS)m8z}AVn?w zjM{AmE(0jOyw*nVw2{+iV!HaEVFByS=HaB74|yEFV#4{<-};TtA>)0)#_-Sx(BB)-b6=&X!@1ug(Yz4GfMvqg_0lW; zC$}_iZF%m7(c)f^`MTa)KX9U=du*D1wlW1jYoPna)}0?~2Xz2O^?92ny7Z65pJEoE zPho8R(5tXdT#f?^s$yXQF!skT{D$H(Ba?4|aK4RvR;#rG|Br%Qt)Q!7GmToMd?eH3BN_oFG#%bq-jhtcP z#REJ^D%1TTg8xXx`)BE$u1#XRIegVWww!46k}?Iz5eT578HgxKUFDs^`GX>m&Vb=M zG@RMwwWbxH6uePY4LQtss5IlnG9sus7r6Q9Qu*$PA)}UNt@o2;QMjMR%&fsg76YAE z!rVz!_JB(At5I!2b`-(R;H^vmGOz9f8B~T&?gX zpvat~76oXN*RoW+9o#qPxpfGq%%DkvOC%2?wL)&X)?SIV?Wfq5sk&wUVV6z)QB5$g z-rQM1_ZOIZAkV-;y~|3RTd_Aep!)o@EiM0pM?E=wIMn&NG*|S3+}0`c?pgKjy!XYN zpFq*EnHI)611&@o!>*}Zp;rb8^|UuRs$7{#~go1*C{+7Yu(iQT|@o9B&N-!z~AXkOI~3aZ4-VOMwh=Xc zjuBM~RW2ORNFo#cJY`wvVAe1~*Nn#y7HkoDT*0JzTmg|83S5X^l3_p~FtEgNuvGzJ z(WtES1o&ZDA)HER7ZcKy=_0Qoj3!xn%s;0-vXGrtA{moxXdir}=NgM(>hfw1FG#)kEAr zE5CQ6&^~BjGoVLnUW=8J4zVmBKsnfP8K`O_y_OdGymfpd3VQCkuENzBEk*``wCPo| z>i~rqG?D?kD}js$7%jNP1fuXFPmKQput`cnV2NZuX}_qKY4{2eBe;C|@Dmo@sUK{j4-OktdjtLNx)xbl|(nb1_%~K!!Y@sl%E0 zLTLL5=k>+`hqsaX_-PisYz;nEnpTu5xM*ks4OiomLLk&#Ia=Lx+;iVdD(_FDZZQgy zqJ1@ydK1VUi~}*3Bv%aMSxic4P@uCAC}Cj!N-$bPvAACl8+t*WVm4wMSE(RQIKKLt z3#?@_bFPE-h%-zA^+aP3AP|hidU%LBh24{Q{hTizSvohsYn&*@(=2oE-w$C?=PS5$ zN?Wj)L(9+m!;SQY1L>78bXuipg7{`QoTI@io1iOU*UQO^&jJpB_4uUwZtG?8e&(o8 z;BAia%X1G0M}Imq%5DX5tNUIQtRtZd>T};6QOjiSCg_xt0S-|U(CXQ7i;+g}648>Y zmwxK{)xl{$cIwIfZ+;fYZl$ft4|ErPXay?Eu1mi;tKb|<|7Ww;U^dS!Z^p1S{E{3! zlv;mw4J#_;C}!L60bYHDM5@6Oo~`-VPN&n>zV-LDH`=kXvM&083#}zy^c7*Ue)Uhi z{o*!$*vZ3^cf+*~VCxmoeC76aVcqmypZT`s+T{rqho}T@N~N86twel7f~IVt{Ic`1 zLa1rov)=oca#*EbVa(tm+?fL|WCokBulZ@^u_lvOJpEaNTAyBbCaA znoR5%im5&m#Rp~)QI{06Q97(LTCcpm8KpWCg0aPQ6${3;LwvG>g-+YWjAYX0JwI%Z zNzfy3e=F>KwqxV9GT0^FV_xIfg9TjxD{X&r2qP_?LS@1s(LxKUg)vs)Ceo9(w9_Y^&{)zWiFc0sx)0;hds24 zsb)lo(9NNPO#UKQn@A*bn=GdM9*2~u#_yQ?Z|pKtyczfiICL}Lynrwh&X7qlvJoC5 z9o}&T&%1LE=OPDRL31SXqyC8-57i7UZj(?~q80-faMmdzn`W~W=8peJobnnjg|nKN zUprG=xH@p*a*h#u<_&rkkUa>4!MLOeoqUm8KDoB_5ZIOVJz8mAzXqK)diLbjajfy! zJb<6J+vK(Y7%*?D&J^XJ2A{8NyDV1rFSWQ5d(2W-*~UbsnYGA7sa0~SOP%hIXtdDM z#b*JA`~EDvV#lG#;^}pnn)Y5Q_z(!u#%*@?UYtr*1~3yO71c@N4M?T(4hm(=q zN4-liK2ObgG1=b7S)_G4&UP>TdjRh@gBb|4%P2*!5l@P}05rVE3vR|3pE22@F8lO2 z)jLDd4ZUfO3ZBv$#ux0HLsN{0wpD@e|^g-KqZ-pPn zO)eQ=h1^f?gp=Mg7aDgGAsJU>q9-FTsh2kwlMU90UE~d@}@Li{lcjT2lqLZ zmu1rznC$aD(vsNdBgvy4OU8wEfYK|Y&gNd1tr4%QwBE(=DOk6TLX?Djg^I=W|AS;$ z*;rWq*N31L9WCcAE|mX)GNfuIMh=A1GomawIuZ2q8`%OFz>rkq%cqAJUT?zuAx}GAezLC0v}(jkVy3?Y65iA z4K7`E(+n0NCQleM!^R0Ji4Un;ZDj7#c%$dS@GK5v8$D1TrBYEE!WwP1zqm)lq>8Dq zTf+xxX@+sLDSS;<}Oq zYgL(6>T1k8%TJ6Zc8wsOV6`P7@_=K9fp8U?dW;hgp7t!g@mdj%Q6LeP8Z#E@?Byb8 zO%Qgv+T{O$*JyKJM<$g_lV?lac{J-hBKP7Va?K9$50G9k{6)E>1yG zJ$!jC9l{eYdvhpa@*|m|REg#CHQ%%lskjeErU0FS_{QNnFjRhko*KJ$HWWdbCX3FR9)n zAXYJk%>$eEJ?UE0pAvmAX;EnM3F1o5ZqUU} zTeQ2m%rMCr%52(gqg#iumy#kxiTFi2cnJOJpq};b1Gm-4wm2=vKL9!HsiBwqu3HpK zov)!pZl$rZ+?A`=0sUL4uFq5%d-!ahL0zN+bKM{#ENIr95tW2-KF)1XjmDY0A;bdJ z8vJy}80&dWmjWryr!r$y+BLIz50!dY_cmHLPGF=L1_tuRr)q6l#moca9vP>UOv1|5 zJvXeN!P(1=4?4#7E5Nt#<_jhFIah@H_SB2ctDfa6wLj1>C9(+t%_l*V9AudTB^sGa zJ_-0Sp;q+CEU{GW)JV)W?EH&NncOAI5>$atM6))F2_o>nfQG&zeA1MSwP~4$pX{cn zrnG7BM>c*~tfAaUnb#N_w*{ggx7^lGt-uLMH+fQDvc=OA8Q{kY1=nJYtf8}q5uwB{ zp0H0zp_7*H?ujob_A~~ZRhiBomxWT+*$yS@WoZBRBoc2(E|Y-sOcIu@#3C9bvQ?7I zEBbF4VaW4jA&yw44#RP5)J+tP0fwz9m>-$Pu0+7XTc&^0Fowkme7^`#cgGj8gH@;g zF2AY#0?^cqH2}Pp{*gP!E_M7L1eV@A)C*tj;}eB}s+CPZl>pHk#KWC0#g;$sgAwZJ zpua+jsR%R~ED^)v0hpKJy4x6T^^kozY7c&8Dtguq<37 z@t232!ySnU#A}?O=|3MN#C$_SIco(vA5!9@;zz5AD!?ijix837z#Jq5u4y|Z>9o6_ z^esUzwnAF7;;H7NtjAfJHe4Z4W#eBoK@BI^n0p2G0&D}1>!RaX=fj!R_wR7UgEdI6 zpa6n99!=?y;xoJ)dU2}YrfuGujC{HglW6umA6(T*1CiW`C8X3RqlLpM(;Lv`F(}SN z5M74%djMrc98Bi;L5AVZp`?Mge@@=$uOtGU1fKr1(&Qj?TA3#N7!B=k{;V|wOaH6W zwRD;Zvr}VI^%yYpW#<+4INH{g(PuI&iVUjl2sH*7T0cYoKn{3`vy2h;^@%h^OJaF1 zT2``?W_X~WLikp2IqpIG(~3A9KxW201_&7!7y!UPX4greCox95f3xo%efQPaDeH{9 z2~+_Q@Prb6vNL{=HQpg^rb`+)pveh@f|r##mBD}g>R4>D7;{tgH4PA46@b6tA-#+%Ua#%lg z*}f96G7OB}k^pqJ6AXybV3KwBBC^9JV4VwuRQ#-%C};|6ma}zOjtE0$A96$)k39Gt zxu8Uqcxg=W>e~ys4@~O?x^Iha(QKz(7SQEqoK}UO-)(X82XQ3_FK7m>!dhxGW0ZM_ zeKDXB`B-vQV0$myFn8#Ldd0fp^Ri+&m8q(0Ma3XC8|us$8j|655=7`$zHNZpN%b^XSAsw*TL$X@T3YMXQnRE*BqU)cMm=_{o-zMgkpULzWnx}B0-9BqHgUAc1` zh7}*cmuS!v67=Cjk3EBA*Ah- zV|x@O=SqE5_d31>5-7IIsuyO*1>n(swH)GY5dUfrv)~$rEm|{R*3>=Q5L_<)y5_@< zuWvvzp%RS(z8)IXi^Gt27>bS>&j_O^A^VzTs6oG=@M=S`LkXHz*Du0N1~0Ki zc~Y9+6pE`TWA&EqM_`I5>O&(<#S5P|i3DN2ec;@lcb0(@DUk@z`I?S62B0)dckW?zvoPMYv^jwV!C`O9M{m1 z4l)}>q_qzT7_}|lP3IOE4&?hCj80nr3p!2}eQEt0i(6ph06ZxM0WquF-;S6()r@Jf zb%|s%$*`&gQ>z`M%N~X549KkPau#W@Ucm{gKg0nYdh0(V9C6B!1dHv`-W|U2{r8n| z#?UIz2E;ERca3J1QL`El4Vm7+7dxiVDv7ke!)Cx9&5=LGDOwes!!1XonCXG4hV?Yp8L3{YDi)jZr`=WrD1 z*Jz;4udmWPohoFQyyQdr_2RV$=vEt+R>aA|c#C`D*NUh>GVoZmYX|T3^u52_?jl&6dt&qOv+FynEn!Z&{?F zfRiJj07*N_;H^)gl93bTIa5&+nOR1cU3INiN;QIz8J&u8lpAxrAHIB*&NT2FpYX@b z!QXj`J+~c}(iJkj+yg5M^$$-5eUcC-o!PwmQ?X7_wOz-@u&au=7az%=? zCLB?EQmzRXV<5$t<2kJ^`$lUYa3|~6+kkpxG(e)=dS&SjNUu?j?l25Nx+pSv0yyf9@MuQmV7(^1EtvzKOMznzfK)^2Ab-e5GtZ=k-p;;pZT!EGdDCOt zb~O$|U<4y}J1JxCi9Nu(M{|jQ9E;RmVRqI#bwLX5OzPRipyO>vvFh7oxyJUa!i{~6 zgaW6`K`T1vrt)HZdA5ERAQ$yN=qAEm3v3K@$b`HH0LRL<7iVu8(y9Ll`YAWYh{oc} zgarEW8k*DkKgQd&mY=#tx=%{wSGgBF@}VUNSHW2KU;2Zf`uR_-=v83@fdT%-#g33m zMLQP(r`rp!(_9GIv2av zogu5z`gK|JZv?UW0lZ_`ezGjz)b5c>{mdJd0zMGBFN%5&(dvsPzU98~cIslbvHEyC z(f`5LId*3PZCg6FZQHh0v2EM-8{4*3v27a_TNT?*Zk^LT`b*z`u)nM^_MGdPbGCj|Ne`r~$z1GF<%489}F4tOc2*tcTsbQ5AiSfe0mLQ>C&4|VK`3P=xr zQC!=z!JS>rm@ML-QaID<-$ra*^gORO3GWxOhK?e~ zuMdg^G6Dd~ag#Fmf9PE@&IJq>6Zikm%&{_aasQvj23@UW+%{zYU4wx!BPrNJ0aHq~ zjq$i6W$D%yDK+aW$f%rcv(WUglDN-zwzz$Su>MY)9=nZH&D^7u8aL0 z14c(KeLVx;2he5y;Fyv|55G-T$4V^)r9iBco(rX$GEcSI_dbK4#~DOv_Pp*6(-`!j zH&-KogT61o@9sKCsH**!cL??{o7)VOr8!qm`y2j?gyTawfyDIb4&=*e^B9s5&MZX_ zCuW~#V39pkx#YZN!LlLW9g2A4uC8)vwBXaWk-ER`R~WdwRBvMEwh*ub5+L}l-AsC) zg5u16TP~2-U}Rl`S6r_aVwr}vDj5@QK<@|e!%*e5n|U;iLrn8f=c5wu^^I}LQ=S&nVVO);Lvui00()bk! zPe$`5obpWP_VNx~T~M&V`Qy2v#lymk7><(_BK1SXz$BNrz0v!}EVr!Qu4Mv|)+C|a z;+#Y%C2>09-KkH2)|=K+oWX6=OP9Maoj13lgrfN2`End84lZdjE|!ik75&Q_4(gySKM5rI*d z-B`TgENZEX5X9o$PZuPz;l1%(U6KDx^?iM*LGVOXLh-!Y&TEmK)c;_g(h0ec@gst| z*CUeM3wwT6S`(`vh0 zhka=>SR%UOz?gJ#Ip;Hn<-6hD3ka>6sZ(Dk>@@wu>JeTQK5q@fPYlX*`OlnYQ%{u? z%8rG;Z3MDu0x4=tjA?*ttB4CGar5ak#d{ft9Baq3DGS^~?kReWBiq;Co6a!6Q99&I zc-ahC7k!$JmrN(fP?;%BzpzHjmO*K|JFYbwBDpKM$(=KDx7R3o53IT0yv>2zaJ;r= zT2DsW?Zw7d?e{$T2)mQ;Yyivb*UeXa63kQeqm!j$Fiz6~`AMZ~c1@6ZIIv6W>B!&2&rd8JBp; zQ2~VQFJ)t~E1MFgUEWSSF%>MbHn2RCB;pbIjl_X^Ir$J9X{H5nl`QC=OpRW<8_k9$ zI7w+C=yeMrN?RmkpAOm<*mvpCYnq?_YwuK74f;om=T$(J*arlX`;VicXbG&!h09h;o8U8Xcx8ag@5@e ztJ7I^HF8A_U6}%4q|C|Ng1PFaOlN0Bx#f4ET$|vOSVLB2XB`?*V+jgAue;)NRi;9l zw6Lj@uMLn!(!{K*VbjGZWpq-{zwEPS{i7PVA)rs*GKt1`+}11HX9p)Q@sKL0rAl0= zi#U|JymwXNo3(>OW-3tyTr3T<@f;>a46!5)ZG?%W3aeal_lI5{+ zN`JDmkj)4olh~@+0FuEF{@GJf%alM1;5Pv>!f}_eT`7r7Md`W{2I&bv!s{#}n+Z=1 z!P@q$-I;1%e>g=tuiH*P?DEk_6aA8z4Xs)3#D%?mzH{WqR#j-~(p^-fNDETzflRq_ z-UR3?LZp6XsHRbf^Ws_JrGDCx3~1tg0czia&|F?l+%32UMwZKw#vnwiDgl1=0fef> zNGujBFP!8LZ*$yJ;%vc)V(+Kr8){S!|M-&hzM-)uRN#VVdN(si@oi7%L z%Z)i(AkSRIBaLv%z0~{}JsN8{MV+t{BXx09>)``+6vjFrO zyg|V-xzT~lfRJh7q37<$E^a!+`X7XoE(9G?YO zewqw`JU_-UN#UA{Kg{+WV^KGmP?(*Uoa}9tM;X>L)>bFgL5qF0zxa&xV2WUbYxL%- z8%KLrj^Z%sSM>1Ax}xPCoD*@?Gr(-c^YR!4%o47P`(YQ2x>z{|EVjZhAz!mZSy`92 z*1-(7m6?{fh#D|wjG=2{dFI_uuPb+<`M@{G#*1gv+ry^1r(Z;OyF_?1dSk>?GM*{= zSF@EY0;(sT{V(zg5w(r$FQ(M!OJ&z{3S8-Un>f=x3nN`3Xg>O^G6V0)2fz-)MOVo9 zL7(nxQ`{R4S0G*Qg-1+wBZ6?`9^~9G9?_3)hpxgDn>vxq`F-r}Oj~Z9Z>cEvt&O+dE+t)IMT_$PRPK#c^3`5_>k?-LUZvbF0iJ%y^;= zs^6E&J~jQbtLzJjIUS~wD(C36DzCbM4&0?E&%PIy;hFo&mg2Gy>TE1o8Pg~@*}-Hd zZ>$0^OX?5f zz4_h%Tc(amkl>c6bstfK6H|c{2pcjIOo%c#09WK@v-#Evw*~Oy-|~dQN+KG@1Pk?~ z`ZBXFNiuZ>-BMma{T*!T%D6EBHOBy!!LT$TD&xR$=d7+wP=`@i5QHZpj!+08ZxeD) z`Y)`{HnU`G_SoOGMA>-0sCPlNEvAE{+l@UJme~SI{M@*%Ue1DjYs+Q$i+FhaQ##a- zzC~F$1mr};{x?8mR0dsXY_Hhf2XNSN^ZxIfR;L(pPQr@Aym0*SVkA5S2x3Ldj{DUL zbvof+VAJGf*uszveG%MeL?YsTpuv2>-ZR0cGd(x4CYSHYpotO#RXlcEZmJ4#kPrt%L;ri41Y@}!lC=df# zg}CU4!-wkyiy#1D{f4wj_I3HsItiHdKiPjaCQhb*@`M_oqvNv4f#E+_H~$!!rA<`} zmO+7NmDPO4A=mnJ=}s{NO1i*-D((7@P<&av;ciz1gP)YYntdEO00(nKm_6dfTwH&P znf3P+`nvHbN9^^geT-pNrW&y*w_q)XLLZTJ5f4deT3o)AqmUFZU>a3>_UoWMfh|;f zhBT|pg-rwaxxd`(g_|wU@}uxoRmS=>(eARVoz(U@30_!l+t(dCC;-meKt7n7g3!3< zoN!%R4a1;R+VMYiYgX&)QN%|lPI(1BuV+NC5r#atPP0#f#BWUXO-p@-p6Dn;F-B2l zA{61$$Qp0i7Kg!-`oWq83LFCIJLKiH67VG8EldGt*lxyy;jhVEg!ulwZirvTK?oR! zS(24S%N+h0Sa3d|BxO}Pz#l_@v?ITL4W0_Hpu1Qb`5?U zMt`E3-$h}?1*_4){mD#)@j}n;3Y-tonbf#rKw%DrS1up3k(GJHIZ-Z#kcCeG{O6jb zgL(EXq3w7>CheV$SK*IHK%cU?!ls-?T_vFAaP7Ltxq3aVt8+Ugwu^T&_5Mk5X^}qR-HA-0_KvJS7V&#a_OoGg}d%O*C>`_>Ztj zaQrKO0|&V_$bJs;>}Nes$M7NA#2#piBCDfV&)@Rq*?tlVWuC6lV2g4@0Rh9k(o;ap z4A|`8G~Jww2rR!&En&Bpj0LUd8!C!Q#<~TsEB_(*Lg+sW=6N$1z)OjLRh2JuBxZx0 zEcgd)62cdMT^Mb=gcn$RC65H%#m@5?mVula>pmT|_){S7tD~C1L!L%(wF{QAHGT70 z@6CR;vGccwOKnrt-?-c2nZtM~VN?J&j;9U!EQR1VtTDW#1X*8FqTwdtc}vZ9XY?PX zHgMeWMzyZIwRAi>mR77VGrluCr=F9E#R=iu=u^))?qAEizqXjy;$t^m*V&RsJ)(N3 z?<``Hf_ACsiJmDo6b-4Jf=OT7642PPK|p};*~E7a6$gc9p1QPRJHJbR7wZv+8C=2gxumYg%Gm>^@Mh5`KX*r3Z0pM5|lj1NGA9#+T< zV(wW>MC5L*l^jO&?o@jsfKPAkFR96b-eij-KyR=}#8NM)*RS6ULe+C6fDIdBp}ly}_opW8imJz+6r^;>{pJ09yU>_dv@v~8?}r_KR0Z$lwU^H_uE7-ASe zotM@Wkz{4EQSKDtFQ5_|RjLt+68Xk!iTai99OsE2QdYvX5hx_3U<;}RG(7R5bC2?m z!}2?%xT%|w@;ZW=!8;Q8zhfHVu^frrtb`A7RDZ>R#}cBmIq3*zgGJvg$_pFB2TB}+ z>{cfm?PRn0MQH=7d=bk6QQZlbdfX-{19Upzdm!F#jj!rWOprsuS@KU9qyo;5natZ@ z_sSlyAhx|D3HDS3JvTI2jH!MgoycltAX|Jh5opQUE}k$fV>OxDrP^=Kf7G~o+T7vb z6pc(Lx0nM1(Vb@-94_*fafR-Hh@1pQ19LB0g7c??e;orzFgg0a<$8Wk)_f3|-qdnj zbt1f*T3!M>#vSQ4*k-0!rvUdWV_rMj#Kg~jAq=i`Ipfy`7`M5u$3oyco<&-OLzan- zEnlTej)^l}m8P?rx&ZC*cQJ0rq2DV^HCE*uBMkCns^#S_3ZFxQnU35An=|bh1$iO$ zA(74(_YVMAZffG|@8y(P1WWK)r*#+Oe?DZZR82I7K&GK6(~V2IY%#F7#IDTWu1%VL_IaxO|Wc=}q8 zWtfr$a@W-P#eC(IUh19Be$@cvY{dbSa%0`M)bj?Y3c>zJkf#o$=u;>QhRXrU*&MuP z)U;GiOl}58KFAx{8SFcE)^SUX_7d&C4E}}5cO&vIxYh-=V^Qky#525gWDUM3PcHGg z)C#1qY7};aLIH*R!>jv5(GCHTOu^>{Z06{Uv8vST06=b}&L7<|#LywbF5QDrf2L4yo|1J#db zf#a6kORn9lBW{+_0J%CFB-U{tvLZO=F`0%qq%*hK<}r)!*zi6gXn{oQavO1qp;iJ6 z0>Mb>YaY-AO$d)iWA!ijai|1@N`MK+4sfir&dg0i~N8kSvyO&h;g z9PyuU&~x_Cts$W%LrTgCb3K_@pW$uc$<*?j_9+2*rLw0eBf{J|xy7Ns=FI>tCLfM) zm%$WOa4F~VPK)nz_W8LnC^1MJ$p%XmLeGr|!paVYQObg1{-QDDBYe#U`v=NCkdi!G zdr1G(cexN{aBVUHL(zp(DTHWih|qE|H`fnj@Ki>xg#=3z2Lp-TPMAHPnt`ax&`F$V zHm>CC;%UpH@IW_#CKjK&7uvf0n13;+VEH$>8=23kZ1ccQHQ1l%SG^P|EUN5IUNPO;eCTEP|!~!UfTbqtF zQKR6gN!an(LzM>6+El3b;nyw<^7;Lq?w_Lb^E>$Y+_Q}-i_!25w~quINAr->0e%ry zQ8wHEJf&@M%kmF=* zv&s>1D5OUm}5XWAg~8wl05WoGBx0C z!Up%uO|8fJgIUfc$QX15#`Gm(NoZI$06aj$rR%SrtNckwr1P>b*22F~ zq=$d?SyS>ecoy?MgI>7eg`PLMbEJR4GG_Yckd!Yegt-m};TCB&R46jV^t9>nYpkkV zi$s}ax_L{s$B=H&f#QHPH|rAn0CL)3UUlz&u3o&!yD>n+j^~8%_U_2hF}Ox#9HYN~ zY?_^ss}QIr6AT>9Lxm4W7mbNJIbc0BN11P{_{RG1jKj|2m%mLX4Rsi8EP{DmOC`^M5|UQ|CL;Pe5D$7K z9|R$892lV29!cO*_lO12u+o!YLWaeSn%uO*iTW4omwaNwr%b3FF^p{?HaYR)BXbt2 znV|+qrros&QShl^G41e=$~=L)LFd;r3lJ|X}9&06N#njxY{SOFUeZ+ zug1gMZAMh_A3Tt#Qp8GKH7UrFB4W5(Mml8#bpimx#{6HauCFhh6F7w3Y&cprRQ^b= z>-34{OT*FM@#O^+%K@;($}?a>R0lr`QgtR2+Z<2WnO2a`4x}44U zM_s-9=gzxqxqx`tn=KOqmO-z$ks+FJOBZvn{mCtA9Egyie%XFm)l4Z@aNLK}NeVQs zr?3QYA8639@kH(it2Y$w0b=8&qV za>0RofQnRhe`qEa`maUM!=IR;6e#y0*dgwG9oKNoza37x zn4#1OoE2`SBJxrw(rBDO{PkyN0qJ#5guXJNZ7f6107llLZT z;94(yN^otRgrJq=lw+ww2V3^TFf{H06F<5u%eD^fya4i2R+`4=_gg+xRNK$}qx-<3 zx-;{v+Z-J3(HvToD zd)H*Bd{gxUO)q&U30D)GgQ;3Twg^WQ`}UXsE7UL)9}kX z5%;a)aEr)j#&(m)-D%Jg$vO#(4SM%>a7)JqqznKRWwKMi0V-*^Ck-v*X+6mVIws z;s&6r3e~sh4}FNaVyJql19t*OuhIwc4dzCi6WZ2V6>KEPPt*T5T=n?l)poV5stQC# z@SaaSe_w=v;hN!L2A>Y*AwqwnLMkShP~oAX?Rde;uHv;qbG7SAkqCAfoxUq^?aV74 zH{gVtd|pu-dMoTZOHlDW5eDYp{lik_@By^_;JoSz%-oMHyfU%~n3;Tw;?iBtqmUU$ zdQ_aM0~V?0{bmG@Ij2!C^(an%oyANDe1Ix{BecyRZV}FO!wKxh2 zI-8s+aC9FTIkUmd%xcqf`w~*|HO|f`A!Z!dk^Zu5cFdN#pWI8lukE;cTpbT4SmDrstoO}3x zm!AfO1MBNcbmyI*seHFlXMIFYQsK>{PQ7We>i3I75&B=4#RrUOZy|c8?%dIL+gUZ{ zs$xLW;JHoaORgRquPD0@nEVH6;{ad>I?_)guNaBWUqWAfzzp9w0nyyu&^ z(H?0DJL$aza4i&eN`Vfb%>wEjuR(FCKpXZ~s*aF8C%H(dI|wX^&TD2n3+s~Xg`MpG zS*8Y8=E9+{=D_=Q?68vh)O|~IcsfAvIcgP2KvfM9>BR6l)Q=v9IWc9xv#LcQFnB+M z)>A?h$*J))Ilm`%=&&q^++u*lWoZpIf#)95Uw~D8DjMH~4ai0=Gy%~=+SXS7A~B_$ zsE3HH{3|rqa%Y9NRnq3tR2j?JkP0G9u`Mmlp;}kO=PZE)K*GH`} zw+IXfR`WJ~!pn&$%ZP3YDGSXNxsX1XfNH~=JZYY>0&`LB86E|Wn^h3xSU>Kgzp~?e z4Oj-&)_$3pxs^tIt$2B`3lgO z*lh1*6u#$@St7b#p^6QYrOIO8Eb*ac?vOdAUcQ1wktd_OKH$a7j?;w6+yb7SIq`@# z0vKi|{d*q+{q!jYtxfO70Otdgr;iSSIT-f2^puCndBr!ye7o#rBrUn6^Mr!2)qzq`a?-4QJT7m6&*}?uFKVRR+FSfEu9@O^(kj}u zql_4Zd%{r4{I*oQxVbjzx^R?a;1&=wTaI$kP~ z5+*&<{ng5u<+s^4SdwF=z=?~55hL0m3|UhuHIe?7hAq_7)okZ-y#z-{sSnA%>L9v6 z(DQ-y0+Ex|ytlpKo6bRP4wUW``Mt)>qRlO+fu`zE;jMA&0-OT-O(>`(ysW5)Xm$1j z+7l;44FIokEEaXGfUEOo7L=YoF2}y@TE3xklH4jf3h>>a{Hw}xrrqhrp+8<8gOa6Q8+qeL$qeJw~>C(!+q zGm9Ws;{ncFCAhpQcyrunPT*LCt~W{PFn5b}0O-ir7CDm;5(36~>|R4L6$%GQX%eN* z(HTT-o^PVX0k@P0HNa5I<5-F~hYHXYP0P&gg+@HBy%D~h1I+MYF+t{ zxjOLd2$>N6WKR{@yg%8+XQzE&SG=VB0(?mti=_~58ZoKs)GtU-ype@%)q?wQY?~ut zaeog^xfbN4&jiPO@4 z?9^|l{%yDh!}jV9JRa!MN+kSfHCn{$?I1%Riqx3#Pp3j1%t@_nGajXu0oTdV08(*Z zo%4JT!$F8YR2O1c<{u2F%)xNr?=Eqpyh(OUBH*RV(oe}#jZef1aw#!<(O5T%EiqTU zOyVtEOZMJ4M7)v0M1e@Q6=hLfo2G?>3U4gYRFLBypsVj18+9cYgVX$bkun0^dK(XF zJwcGL-oP8^N!hi?f~Mr}!&XE?03v)}C?Ayko_YYyW-{$Bh@RP741wnWWU~9bhw92I zc5Wtrl&)U56XNJMHfoN13M+>_q-L=VwD+kn3oI0H;!yMoDekJ&Hp7NEh?_+*t~qT- z@XgOmCsDyVHuJi_it71tq|zEur%sLOdGC@Rff$#FPk+Uy{B{r)Eu__E0jV^+A-y5< zJTc}3#9b;I7a*duTEA)@s4F(zM$-P#>Ipdqm}GnHD>D$8k>dCaWXx5GR=DB`SWFjC zAllF985-!;&u&{B55(HmPrb7MYwojYq*u*Pgi=m>16 zH(3l`tdW5!_)kK%f}$YM0UDom_#AYZ@o{V1LlkmhT!jkyVZK0atxst(et2bf0rWik zh9rmkvRi9^xodQeFDn%xK{{r`*PI+Zm?`)x?fIBzoiP%}bc8fDU zP-cpL#=nr25qL}UT^7j)R=>|Pg2${VV5qdeLIp21aeJnqJE6q<`A=yC2{a)qi zGwju_xPzy3@UB_LU|L0^8ZypAfT>gpvc$&Kek&Qxwf#V(hirZm>3FRPmfqs}``DgA za{u>WuWv}}Oyj?bj{Sd&4RW%v{$DC}JAQK%8PL}kG%n*=G2$rrdPIb1kJAOc*B^-} z2h5AuJe(X+JWMM!();<8yN<1_YfZkLJ8PwlRxfAm!PUjeYtZ$%P}ue;aJ=FSQrhCta=Y_ZcTv`!^PA}rQ#?Ig?c>wA3}AN9M+4{z zk!k4od^!)W)2CrRc(A8vnm!@03GoP9d{-{}Zatr@+(Ppby>wu-nAg_u$oAGdR`W@? z!9C9QbV?wn86lt^Bubggjuf)sbJ^c30L}Fz&8lOxAd&w(@r*o6dQ7kTofxjN@7c~4 zp=ug?oWud|nh_iRq3KRr4$!R#cqx?CEg}z;ZJZ-sw-?fBCxbc+pL*UjL;LVjIVP+u zDkZmzN>gvi@UUcXRN2-mn*@Y?HT*1SBC))fOaCUCRFp?HRTAU7FjorjvLybpSF660 ze_fU9A=t1qPqZ^x(>R)GwB#j{P1#-cTs{U~oUeVT9JEi5JUDza2MC)?U(E2`M8SXJ zL(t=9MT!a(QA=VkbKFdNi`v}`QC;469qfgrwG)nmuOUa_RS466!*K83NzVAnn1VB* z&EcmJg&?4u&6L2f2*>Q=C9vN00qaX0^tou2E-tEKNm?=;BWoR7IuTAMS}A%Hi0YW= zc9k86&%XIyp@GtC1Gu~xw`-#xRs)eL^EdW9n42xA-2)1QAdCtoord0@EkZs%HulQeft&P7Q~E?bqpRzMHRl_O!ACb&T~zw#W{{3beLB} zJ;UF&IK(-zQHunY7{v10lI3~tT^Pgtiy@(Av6s}0^Wq>BwfRO@` zS%S~z=X%~@03Q@jsZs+A`%z$o*jhs+5sM%!;Zwn;N$;@b*cwlN)V50;5j$w+X#~*E zB*b_zlMbz_h~!8Z1Gh+PhAp)CQ=$@?hmqQy|E*Pn&G)uZa)_GD&6!yRKn_Pqc+P|q zkA!Q90~Jb?a2v+u!8!*!nA+dfiASewC$V+^c=hO~1MoAUJyAO-RAFf3ZL{gR`X9BQ zCsq0eaS&uGk$Q|8axq=wpJCMY{og?S9%vX>m$o^Ne7}meY@?20-veo7mbFBmV-*t4 z5L++UG)r70tf2Uem8-j4^D|D_NzOF~bB>vy5)eQXxuN|GmNpT{4c&r(M<>-Oqs*k0 z(_k(-0g=BtIU$5jYIi~=W$|ksktPy6H#=>NOM=@A!5DIqMEx?Y`{-$?3@^&u-fQe2 zSP;OFurhl;QtBM44!D<_cSYK1unbZRbC-lINv(kSga=Rh)Fo@wD`d0M|OggYQKrW!?1 z0n}lKrFHru((4O{fkH?J5C%aD&M}F4R}*D}f@4c1jtyIh9=2Txq^r zu>ApfC@b}eQ-C*coVP4l(7dBWe59bLwaOTuME(hLKEJj9bq+Csm(VlVH4?8H@)VKM z`&{vM1G>M58}S~+BiL<-UX(mP{4wo*;M@Cw0Rd9)J=~KxRCWm4y@2;x41c{AK#0N9 z3%oA?_EXPiQRfz<5RIw28+NNUR10j=$b3p-CI*B#STUtZp-?pp#J#9#zx$NOoMh zpw~?!-`@z(r9(-^k&s-%6nR3TkajQe&@0OuLz2Abt|?4ax(?q6FlmzNRk$5W*)HFc z{w_&BskSPft=SSOsdh;w@RW%+Cxxob}H)y(^QGBYBsLO%*`0 zh>#?oCX8%RXk1hR!nJh}a}6=Wpn@7PiMyZRu6*F$5U~lbeZfND?Qr5|*XoGOYyh8e zr}#@(VQRSpk!xZp4H(2wjz$ezQOUKjJP0V{wwxj1TVqNMGknZvk@add5?G5XgJq>x zSQnAURt_7Qe1L;ixtHQ4DH7a>$(z3{aS>;yQ!n}Eef?u>FJf&GUv1Z540dSMY+wki?>EJcyibYbLqP^e7h`5q$M zJmB^rm+Y*~uFtUg%VUk+f|r95^CUSGX~cY%dg5*)O%;s<6zLpv5JcJ{2beGyrj+^m zYYM)onFFT!`c?CR$zlTt`y<^`M(lr}$)^umF(}9|KoQsCu?#t}%yDW9kW_=C%vE6hmpCvHbxm`rqqbE)XonCRbS z1CEz0axr0~< zT){rW-E3{UN~BZPAbeq>CQ|cp&@)2Q*o)h#O{Ax{1d@4$E5x5+{9AmiSUi7&DOQlJ z{e<~H933)GzhOy(RxBtC_flVh57Qaz0PW#>Va0`1B4lX9M5)R)YV#HWUqEU`B%cm3 zhlCCOdTg9(N~!ep0aNzcwBSOrb211T#R03N55(7r?SmG$L4QCV%h?DfGl;<1b;}O@ z2eKtW$X>r^J_*lBMEnkf3O-^Sr6)ir*f!?k#njeAB z$={`peaGVK?i-vN^7l87g4R=IppclhS6ZxB*+;3^uw$YN`%F`9Pft26bsSMDib;Nd z-tZQM8g8ZfX)WqtyYFd=57itJ0h$Z|5?uRQuxKB9%MQXa%We+e~mT10;G>vnt16l=pk$Z}u5 zC7*651{5}LBM#?=YP7z9D-LTfRcUYKb~JNFRkTIIXlY zI85$SDn1fI;s{?0yMM|gK<}RdBO@c-|2br5ZY&!s&X(o!M|f5K)K?UzRnlwP6qe#c ztiX*krNb-K%0>&wpGbOtztNXVo=Mr5tfjBCAra#@(UBMY1q=<4Z#IP*E5x)a+d4VS z$}H!|OIWy1r)PITbGD}jXyLyecdjFNuZn~J^gzylW#S|s-@*gv5= zgz8bOITTd&juJwmzV=Wt8>@9~KQOFDUX~Tm{%cN=mRFU9!~s^wgur} z`p@kGP!2YZ|Ek#|nghK;BFuV377iybTvam1X4tv~^k)roBcXJi{MwWnvf0qZsxb$~bO_{-3Cc3YcND~xELWRj)ZOZz0MU_`VU z2DyLr(z$(C=SvDduPR-2<;p=_1 ztFOB!_Fiot(<i5fm~EH(tTQPR1Oq4eq3ue=Q#f7tpBw@jbq7Ax2F zI}(-El{q;dCNp@r>B0Gr(*5mcDnqq(8}F!uYc@a~&PU)5TbK63rq}|{eW|_WNlf1%m9IK@x!nUoV#v4h|qz zIfvRz4dwae@vztt$ixK2pdUZ9g43*-Pr^9(?lU)*kYa=wAzAV zAtf`nA`{ODedgA5?OHRa{`Dnau+VJlwg?YRaMtE8tI=7*)f%Onj&mN=4#O_5z!IPg zB`2%=FyO^J5QwJX>n@f%kSvbg;nX4cI;*iZP-@cUv|aJ%1VbTr{3Dug!e*ek+R-h3 z=S)P#DmGV(#d}yd=Xaro)fik)EqLc&@<_hov%Nplu7B;EW!v4T$W!)B6;Llsx6hnE zrV)W$I9F3WXFbrtBXAJ4OYWC$hYJA(?Q}&YE+A6(gTe*`reeBiA(3amj`~6dKyxba+4ye7ZAxwnopK zzElm{LCT4o)h01Tw~IV_;n`+n!CW+-T;q1clGa0U*r>@KP<&>pRZRCoaF&3mJ=u(h z%=tD$hFjSnpdyFjE_j{U;{rr_3KnKhNQ@lVr+J^T$o_4*PF$D}@3G3=*Ja$hc)n7APg|$rpyzr`KnXteURxyfW2dYVBgV zPqm_?eKCFHi3Ig3jy}eK4`VyKbqk$WTQ;+Rx6Ed|_D`bhwER}n3j3jwm*K7l(>Q4+ zCe{a4;uAEku#0t65PrU!84)Gw35dwgt7iG&;e>YZ&g|hj9UpMLq)PyyIm|m8wK-IR zU~%w1JIKyT!LWj~FgmHEn6x>zP@rI*@PzY2CoTeXE>O3zCWb6{>E8M&Ye@~ymYFf- zZt?cK(quCO^wCPLHmB>@2zpcb%yYwc9oCY>il9^4G*kq#LtH3h1*&v|q3I&|P(g!N>hesV znx}ipo8x=5m5LLG-=172ESWE%;}8$*ojZ*aap^{9t^9*h#?;-DQJ4V-;*-esp9{bH zkxAtd9Wp+Lrw4GIDkv+|A&QXj#d*C|_PeKS41cl#p*ZPvLSgn^$3WTTx3@U?krx<%Lk@Q|ZK}AgUK?eD1*$oCQ zP%45&!4!ic*`1bOs53Jnf1Y8g2_j5FuWnCp2XX=;>Pa@s9+X*ICt~lQ`@Y->!~-@v zG^=$MQ?;(C|G{%29>BOUayYc(^DTd|#7!cvo}KP`o=}_M`N`1HQjmO><7h7y1P;|Ag~)Jf5>E1`f8lAmvNeG7}&rS=yNZOq+kKdXm4{C0{4p?!c4mC+aWw; zu85OGov$a|bk}HpyD0W(@HyH=m!=W~tOerYhKGlQqe^_6_LWu4(^M3CXH14pd@p8Fd1hsl?Xlyu23{wjAD@5&W5I#QL2feZ8*CwOPvZg(QY8%gBhT=Lvr^#f zT+qX0YGvS=1ser`fTgGp{Wx3{lv9WHsUEZWmRgvc9Q0E$a9(e_L7lL@8d)ik?Dd*$&jMr00+j&JP*3V;D*j0ALgXWi2p2+g)ba9b~ap z*Y-W{h%9g9pM~~#?sp?7x3X}=c0r6Zl^JGD3}Swgg1cN)sCU$rleX<>NY+@5X|<75 ziz08^G!irc;55~@F_@*k%$^|BimpwS+tf~ewrfn)MZk)O?T{#(> zgEZxRa5s-p2PBW}@v&>ion8vc-XK#}IAWV{m{j4Xg`K849U^ek`4lK{@w&j5_)%iD z%dWWIf9Oh#g@R7S)=O8ML<|&JQUAMIfK6asGy`QIm;nr5rNU46hfEhY)3>h^*h<=qF)!uJp0^RIgD98z1mvv5_@ z;240;AlXb1+lwHT7PBDr;~XSYyn6J*6%Kg!XioZ#^QB*}GWhJ07H{Kb^wz;Jp0>{V$|9^uw<3J>z1RtGe0Vi0kWn-Tfe{V2s(1dLZoG z#o7O;f{Tu9ijFhI;0BP=Cr>Ppk9TkW3rWsdag!1269G4q!8H>DF*Y`n(Zwl~Wa}P( zI6lB4BylVfDoM#za_C?0E*AQrOgVO%_F@6p#S+-}@UXCFO)Sqkf9ILsli|DWM;==& zbb~PRt>M^8xa$QyEA}}Ih1M{&?(DuC%Tg92S#|D*7n|ALkA7nH++O5II5Tv6iElYR zbKytNyJPo_3D%v%Jq-AO`Qi($&{RO48G_vdsb&o)p3}e zL1$8kdQrlTb-=MIaVHkzZrF=0hcUOuBiiM>nJRXcw!M1kw67kS4C0%ps9dJ2hK zhnJAytVOPc9>KJR**AJIhY zy41Y8E@oLF?_F{{UUp#pBAbbanS5{H@GNGPhzU0O0E}n0#A!=Yy{39zz3+DAe7#0D z6Qj~q<)pj*dIQ}J%FZTBKn~x3guL@xxqz4lF>A*>(U=Db)0hXbw}&}eH0EeJF_9XW z2MJ+*yiPJW04AoP=G0K*J&WG|YMt@X;sIK~Q!;+1p9iwmwT1)+s_dI|>MWPfnPPHK zmm-zwtH*+hutHkt#&n01*sroR|Jtd;ewsSOm2Q&sUZM`}V2=|>r09o#M7ndrkiU*U zu!Ps}$DFde;GmX7KycC$+ISpr%_|&#fmi5uwRUUp3N4yfkKMn5kGF~h@XTFGHCk_8LQrX3;CG5<3m!Oy>cMM=U|CVW8>`;mQKimJ+x-0-P0GuWg-+>jUOp1SngH&Egq zbH2+72??~1O|ZU>wL<8P1>j|o7Q9a>^z=Q}>xMirF4%1ql*UCBypmt2FX1a_MN4=KtN1}y z#k^;qt692X1new{0AXV@Nyv)K7_@f85tSm#ZFb#cnz9u{S;3Nv_XCVdhQ ze|Ip7(z$yy=7E`WspfN0LaZ7O)C#dQ9#pY{%fLh$h*FG=5qWZ5&^Vr6i2PTUXC{aq zo``ux0baUUN-5Lt6=0rgwTJ_Iq%@K#)fXJ(K+^)2sBq&mY-oQa0N-7LU|$m?-X#dm z-x4GyC5Y)V0gQB&iazFYLDLRFbj(*37e8~k_-`yz<5qSc&_U?80S&Jtjnvfw-Vd~m zG81@mal(uetse1#fmq?-y3m6r0z!Thm{w17xR_u7ZWCE%bZTmoXetH6=IxJj0U!vz zQEQz245LHTcwT=XYS{cPX2`qcQK)qP_++ql$JFwfwDLbDN+3jt+sN-pMUGg}oZ&+w6igHYnYWtLOI zYW=SzYqQavTz&Ppr%EW4BUr^Js%sI;03Pu4Th)RNE_HKkqZ%W#MOi@rrs0Qzy;0@@ z9W?Yez=54=m*e?@q5UI<^Pd&G++1@#7HgL+!+0U^KR>8p3GMu0J??Uk0grm23))KG zXHmoZ!|>fdj9&=(li=|b0XUc8cLgR8I5;v2FHB`_XLM*FGB-CklM!kvf7@;wITC%> zSMYK2Fl-i!*M%`aGFfK>%mP820e06g4B75D-Z+vV%bCpI?;6%6cXuNKRoU$FeGOqU`Wnhz>u7?sP2_D49QT}nglSz0CHiQ6m(}i zWMw^U!sy0EhGY!DCiF#8lBF+(oE&6u#UAKTaK_~cvse}8V}~jZe~{y#frru9p(?PH zpuZ$0gt44~RG0)0G91TnhIHXcIToE=QqEJHFnr^JmnvXlh76D9iGvst4~e876$@L4kOvJ5gA@iJmH8L=3Ak%~iLYlMav3RM>2z>t+J zv?G-m0czz2YpLXnnJ~0TP8!0L$$9Y-5KiJY0TaZ52@;C$f0D;T4B|{9sa&RzF)vxpG`SNHnJ+INBQSVjtZn|34%Nf)tuYdoxdULdzu4~CGy2L86T zd$H1K?qCp*e>(wI3?JH;B-5g61Jhw@bTGVdCqO)#8+Cs9Wm3Ic&d-nPl{$tUULUIJ zL;Y=~P9~^=wW<$)E^0B*Wj%r6W~+L(x+!#p=9B7ueKWsZp4T^pjY`o!>x--DtNAx| zECm6~gDG-?Zl+7L#RP7fzMReG5OrL5PjtE0WvQQ3e@C}xtMdNa)$H@6dNp5O)XO5; zoK*j)-d4XJQ+W|(&M^vvsFv8Vjx>qkIBLvfa0%-uio7h2JW|!0`G>ixUaOz3>n~rX znqG)GS{+`iW8vjuuz&sgKL|c;V89qz6K2=!_WJtdQLJyU?6vUQ7_1|w;iEB7kfgTY zv@uwOf1n134zN4M{~&w^Q9g*+LC6p0%pl4Kb7nA`gsblQxr_g;BQ`&b*tliH%1XP( zi1j9DS?FOSjg`b((y}UpbhRhHF=B6S&u-B6YChAf?SsHg&j{S~FmR328SCE|Y)Gt` zM`D8n`ADq8J-cyO>vfLfus)NPm3kC58ZC>}e<-X6+nAA9A}yph2J52Lk;jeAf#FP` zaad;fcqA6&lE zUVdKgPZe?z^eDS4?GR(wWMJmIk{@LgA6I3*sApfl++SPRj$c~_*e8;rO;}1g(l zufryq+<>*gYQC31?V1$z)A+|Tq>&SQe>j&1S=C^RG05rrWMCyr44V?E9vy8<22Jw; z88{wA2JGT}GH}i0mmPU?xBDamZ1&6K$JSj(DCteqWls)nc#;dgI|(1_o9+kzsdt10 zY)sn2o7msu9Xqd-HFm!z>*18qR4)$>N)Oe`lGRm5)$i}$%I~L7tJUJ>Z&h`9f3^B_ zd#2ClU#ezi{ybk^RyV8Z^7GGU)AP^u?4mlmo}X1;rdPA-Vt#&89nEiN7qj~2=5+q? z^er5?oEGUfr(dt8r@zfFe_!au#m5(04-%vrLQn*|BEKduj2s71*2ejrM)>Y}KKuM9 zclQ@V4svch2Mw?t0bngi^uyNte*pnJenjy27U{9Q&-bWdzy?DC=u< z@5TI#@W|tnvkh3EvGGqd4(qWZjDtErIsdTSY!HCM4b-F2I0(Su@$X$UZtMOS9=Jcc zZOJq=TFNJa<{sL6Z0{}Ee};DJ72E79w%J#-?Y*n#?~wA-*#Q}c)W^!F2u)?Of(Vh& zhXfKz^!x;&UM#O>D}6ORUusEytg*f_-fy44khf3`D#@X0mJ@TY%juKTpq4#pm{Q(B z!`6r%Msr{x3ymWj6_DGyec2G1o83XhY@wo89##t^JdD&g%crcMe|_}((2;WGeAkMDO2t?PvA#TCf5&D^s4wcT)gPab$u6dgdb!)?u8T(wq*EyAYsT8iaw2W=Uqi)~ zzL}zSaZTTBvvzi0^qnc*+kZwsKFu0n+ce8@NybNEv4e5Jjlu@efyc=WFcEJ$r{93} zxXV98MqwT4z%&Z$vg8AP9M&@?$T<5tYd3FlHefj$O*xIjfBK|TwEvIf^r>|6?xIw+ zxcsws{r=6X6A^VRU%g`a_62G=4n!I$&;2WE64D@68S$Cr?-@EN zvN3Bp)4@V1Wv;C!NQdXEaYv#3p0f0Yo`hmp_|`lI2PSy7z;k5Tt)O z6Oh~loAC|L%{7qdD}Ra+o54QT6`WYaBfAM0OJg^|VIDzaXPc2qtuN&fi(_mtZF!N< zJcp)LBQvk1VTf44UVYoDl*XcYXQXD;k%nt<1Lz!=Bu4)S*~b`Dm%#%C69OoBd^fXl(1^ z09tSCZfs?1YyvX)d+WvkNf8x*F(|`-=jUSTY~|?c!sudU`&W)kf4KpbS;F2-%)!nM zXz%KR@Ynvtt(<|Tpt^f8{rhlj>>WJpef~q{R`zD*f2CpO=E$UBZ{_3$lo9{84M>FW zM`i(Z1#mDkGjnlr1AtBdpr@&SCDUI4)V&;me=Ax35`%K^^Ko==1ek-;0Qy;(13^Cs zJ}$=YK!B^W8_>_^pNjt_1Qr&6nU$$4zyxSvWsmU3J4g&P{}+SK-`UC&pvw$uKNbM< zU!VUz>4Q4V%)#E)>yP>0N6aKCA@WIGmG0ju|5qt0>fj0RVc_5dFtBoeFauavIN1SQ zpa;MI^`mTT_3wTBW37z6xdVXbU)u%M=|5X`|91~i|9e1a0RQVt(E-%CKmhfhM%QEJ zU^WGPvHX8-_rI6?|LgLRdNh&=e>* zfM&t||C(w6|24Xz4z^~0|5qpDY7CkM5qk^U|CZ6pMbgR>Xr^rCYHInfcKMfF?|3jb%3V#R`LGcfPA}IZr zxIqz={}2m+N#zf*0+>|)5GcCZ9|A>J|3jc`HU1E2FPeV{v=^;E1lo)C9|G;=(|?Hv zWNG||K$a$d2xMu0`d{Jz=}aALL5KSv$zNy8WcCjPWdi&Mf>JU6gMX3LA8%}bq5D6~ z{}R~#(f?)c;O6`fcOV0ce;}wBmj6X|P%$jM94&$W+#N_}^$!GPYV!{SrDOXK1m$Y? z2SJw((?48+t~n-$Kfyq@pcmmk)S%5e{!xS0I)dIs_O?KObJssomVZnCb&32}3_3SQ z(ADAar;ePUL_rt0%in|aCk80re^i47l$^_-YJrq4piB0@UO~CK*c!W7{$l|s(4VlN z%Ztg?(i!+C3D8h7xq3MKV*$vA+dmLgFZX{Ss0JSYI9$+b&wn7uz1KewR3GoZ@qgys z)Xf=G57)ncZ)DJA@E`vB6$=D<0!u7z^2gDWCd+H-;1P-^r5ZoN>JYkuaLa?oVGQ>hP5ZDbiDY!8YZaD>~|n6O@A1i zDmoQu7$AZtWKb7A@O^Xg)%1x_j4GzQ@=@M6X+29&SKb#eRQ^1 zW1r@|*DKXoU3&1y@pN#BAn!27XDEF{kG{&{Ec%pwi{2Di%;AcN8(sB{!!e0_ppsxY3Bl7~()2iqW9QB?Xc zJE4~EC!spx64O}ZqdV|BpYhuRgEgdenC~}#S3A88-hM<+ZXD&w7Zc^iqom98x8m-2 z%MsnCTC6+l;Au3&E}rn+X;;>FwxnjR>kxVwRvb~3^NMWlVcE1z=6NhT7eWmPt!b5K zGwrjFgfjy|D06SwUFYROt11^5XYA)d)hOv0ME$u)#&pL3!|4e^p{&n)+!r~#4#&KI zEgJZ4MdC?1x_0RXOvA=ab880y3+$sq+rLPj4B{TUS zBxDQfUo!Xzwq_U={ZGpRHRo!b;N)e0=?rJ!tmhhc5M19sxyvmVkbDQ~xxuB&U#U37 z@@po_h%AKSpFHsxcfuNOP*I*pins7ck!f}mKMA|rM2@(&GD;BodRKJo6j){Q<{54Yt&>_(K7L>AS5+IwaR zRU`iRJ^)d~O51cs0P}Ogh#;DqS3AD_^Np=W1wL$Rh)lt)kgi7s1pdyrDLkdlp-~Cm zOj&N%MlF(ynKV%HTPoFuR$7;XBjnlX&+2A8KShnVilWehLRWNS$Bh%nzgpC@biA6Q z!s;ERz%n1hbDa|#t47-7Ojn?+=W0Z6 zL;vckv-tw0=S&pf%aNi`(#k>F5JcNmgW64WwsOK>8$FpK9vj_%K;I$%%iNkSIpJ)y z>wEVY`2A__NH(b_bKip{8Dzz#o38MR-}ccm=|V8Vh?z`+1T8#0z`FZD|6stKvDekJ z4Gy~u{xZ=gzaH8P}LSlVF( zNNeKFzK@ih`Qbl*?ra2)tg<`%me0_@urNJqqfbrR-1>wFzI~agOB@E!r(bHexnz6y zq`dBnm?8&sAWIR?Ar%bro7$EQUQRU=d#i~_x0pfJ$;Xs(LS=s)cOB><(SumipVrPB zwzc>PIr`luQU4Ro0+jRe9W*!9WxL?!7MFBxUWXBQe=KZ&X8=`yGYz^kLi|D~!m$k~tb6Tv ze+xOF-xkujY7stoR>e{lwG#ZbH+@=sdA8~vt|>XMCt_(+=aAN14ZVey#SKLQ1Cb7O zc}WY?JxCOxD1)!!Uc$o;;ob2&f_?G9?lUUDo|}e$NId&w(?ox0IS2MP|K)@RwZ;D3 z($fZYk&zN>YE;`>5$>wD2^%!?F5=i71FpJetxsnS{G*}tWqg!Dm~Iyk(^hn7X9qbo zmgto|TJbs*u-Ee({-I4!uv-9_Tf(N2G|iAf#q2e+d0vK_8%uU43#SEOhTB{ZC*ab1 z3B_=Kz2c}|!()p{79ffJG1-Z?)DDGHirTl}tE-x^T_?!#StbTEq&(ZX*KDC=9t=WHCLDn;-k9du(5j59oD+T>B#4YkXY(!BrP)+4Vg!QOa14$bTkFuIp~Z8 zGucPIPvxp86;fV-s4eVf)M%TEal3@AKBOOiY@hTZ0s1z>)!dp0>5t%-S0a}J0#lF9 zcL2>jgMfS5eLK%PYT!d9;f;Xa;Ari~r}w-cqRlxb+=0_=Gup@t=?Z1F-y5T=FqTSo zFsu+9W4F?xXoj3G%aHQ?_tsf{Wgzw~#$~|r^97jE*BH!k7Vn#9(<}ElPL}#!Roirb zs&cn;HQf>mS|NtRgg4g#0S4V64O!WWvjv309krGiN7+U-x}uU{T^scB6hG}W2(vnm zFdIdIft|GEwIucqp4)740JOWlUz+4yx&^wu(Qlz?<{Er>?C()A^*MMdFxG9q5}!jF z5+Y$NI>JaSQa2qo){s9^5NY6J^>kW)MiiHItWp2W2C&{8wJqj0bk_wJl+XjV8@JNG zE8*%+hbK-N>IubaxsLGk~I>2(ROSbRQ?o{|-^14uYIEhmTEa zzw&S)?H0B6O#4)glw@L5=zMH6G-7aGv0aX%z3gxT56Z;N*&|$TUy96kMS_wS1Gkt( z;I%iAg%5w0Z|`@C!%rS^KD6dA=;whfS5(z(kVQSL;sRoC-Nf71Q=Dy2)na}cOmQ1_ zT?qI}yb2YL!#>y=waf2Bs^uzws>hcvQu+w3|5{iGOZB)lvnCCuTOO{Ivxz}g;lPQ=8kGqyw;p?y z_;PKcPIG};gPKPg^QA$s$>Vt4{iR{smmG(Y{UnK2c+N#=6~XE;e9955(PUsp&lx_3 z2Sudf$CeKNlRI7cC`^riyx3kx$|PMW(JD7Y9M)IDC$A_*PqEqM!CcCqZjI6uzhym{ zef)4|lI+pvQ5;zd0;KGoZQg@baUop%vQa=HEOq7$K6)?X*E1rH7;RM~Bc|2o@1;Jl zJVw>rTiB>^Q9J!Bk{IHY@g_Bh7bsVdjyvV z5mw;)k7wyXvR49sE-sob711UHJ^40JMi5H;!?R48@}!TZD(Hw~$0kgmqm|{9Wk8S5X(Je)iM_af+b7<`)Xi-PHS%WLfmyAfR z7Zm73yI$hHYK|n2&&f60kD?@FCYf<~Om=nCZJbk?7=Mv}&=~qih&wQ*N3s#`g6Sdg z``2S-&GH#%dxh3w4B{6|6>fOs^9Ft-3Qt?g&jck13%6Bh7yhD_I|5shjRl*eta@$w z^m_qwb;8a->QWS2$wS(K$WL6a%P}iUMn#Bqj%q>_tyaEcQBSVEV7hP*A58e42)Gmk zp0Z}Sa9l=zrtO>XgQn4%XUaWT**DM@Lwn4aOfJOzb!lpzU@h27^4X5w)}<)Hm>d;@ z`zTOTh?e=PI$e_vkik=V!J}~F`fuY0(>TMdzXB7x6y=(`#SMk~wNJCc$|^%_-Q;kx0MSiRhN?{SLI@)v6? zc(VJ1_e%2c3b;iBGXUTXQ1dTxf#Q-K8}<$ z3MKqz_k*Sz7$3RKPrEb|8(+xhyFVqC>hG#83@jQP&><6POvKGEYq*?613F2x38F%D ztIC(d2fYW%EeVtRE{khbrAN)(GAS!Iz!*$_r?Am7%iZmLKJI^88*HKRn}e*DJyx(F zoh9dZ=Ds%UvZxSqVHIoQqL<6gMV-$Pf}8A^oTAA)NC{`(Kzc@5V<7|Yb7-&l+SY=D zEkmMhnyvh>w+)RzQgF76mpeX3(=l9qt6|fOXo`hU|w-&Bc_9e8r{}8}lDEWT&#l??S`|Zb) z+&Alz^k1fq`T_pw-26SlyYangY|Vw%ZTqa*VL=or;5=N4lUL;4{_zHVy^#=tcJe>u z6?b_aydq8;dhx!T!GQsc7inOoNHku5X~Y?YCKkXJcsjFd%-L1%S<5eDXSCbe#*yJX z$cRx5RlNz|bjICioVK@o0MD5BD0gsdmb+?ao0*5o*jwo0qXUVaFf`n7b7%c_E%Jjf7Ds+nwJwN7@0NgeK?`s=P%HI#vO{s zWz0SznU+3oLuQly5IzJJS|%HJ*9WQ0^_ZAKHbxw$O4n+tv0Xu=uD%UUokK&?{3;=5 zV75xp*h6~rt`@~8H&C8S>C5F5<}okzhPRaYoI}2)88M@n-?j8^Yd6d3#HuIrC15Kx zx_u37qWi3fG+fx2jiw(V4-s5{k?Tm7tAPaS=Qayh+5UoDqc^PtAMWPU=majhkpqPb zG3JP>5GBP29IsxS@N#XfR0YlDT0gO}i6@eYv22U(qcU?!`eD7Lh09-C{CZ6NXMwAY z+6CBELY#>XQx`wBr3~LU0`p!M?5>iC?&~_v6m36J&n;(vy;ObqDL&tSnWN7T2L7<8 z{L7q%>dgI>J#wm|S3*`ZU{aT-+pd@oBK*ub9>p2rV+`)Q!gX2WbE%p7d;;%LLjP`jw66m`Is)gF%So=l0B2)}Ua(Qc+dE$6*Z4d$^0 z7vi}*|e`GJ-%Zgg2}5q>|^*m^rpE^0+SgD?o9{v7~o1oaz)a5sleD2P)9I**XwuJ zfkkDGw{DjQ0K4ogn>W+Sv z&9C!5wMO|%ud@xqck9QoG}Xms{>xp!&3M~9oHJ8PMK4aK1#It{7t!v9Tp>Mne4hgr zhnh4pnOrU^jxHsC(qbjcI=r5~@FKSd>5t@{~PHPXN%?oF~jb2J;p!VtZ`aqmZodp|+&} zj)g^@<=``fEMohh6ce1joTNgrgVl4R(e%fu7f-+sn1c0Ste2*#s<`|7uU%*V$6b@gPliT@-Opr&B}F&P!-;CPGH_}iS6Ls%pVzTn{6#0l#JT%Yj1 zOKX8vQz?#Ep~`h-fnM#(!Vu0-zbTJ7G_v}HL26Wc{?WVPh)&8k5iZ}{Fwt3GLMmZ( z)7WV>E7l^u#14L5ze&Nq@OLVlJ9VviJyYZDio8jG1$w}7)S}tW<+5rO->OumkdU$L z4^4H{?RPJOqt&Y4Ef$4#{#zMF^*XH4N88ikzERNAK6@gvrl~5bk3~pM_GZ)@sH<6C z={GI+4&oC=>gBRxaZqc&q{F;bjn{66=7^ME>)#3&lhGDWB1Sz2v&4iu-lLNg94Gm8 z<6@Y9r-Vx1fJ1x`I_f94ade?&s=5U>j}`+{B{9FTu!CoCl8vE5yGhHPPz>r!7OrZ$ zHkXItX?I@(@ZQgyAI_GqeXDtSThKJ272`#fdNsUJM%JWFu8gH>#HCSc&dE8+*Q=@7y!d;n3=mow}OKOO4)pEv}{m~1`Zg60u> za7N{~b{;QYkV}>ITiyCee)7z4P7d3$i|0dt^xNgjEj;$Ubxko*(a&Y_hcBKJEG~wB zWtDi;daR?nI(uC@jB{%UQbG^wI|QFkfcV+bjc(1&+{ehMY+H_BU%Ao6o#>MWyC@&m zQFYlAkZ!kKVESDbm@ucxdt23!_zo`;Uz$+Znq41NMdm5QI-9(~WHHh_~!=!VYz#eeb!uzmT!AG$4FHu z7V<>Wc1p`>73Fy*C_He=@p)bNu&pm74t+x@GYBaoD0-V?HJ&QB>b<+(a1;#br`|Zli1SJI#^!#l5|`jwp*Z9z~^h#}66Ja~0o%7Y{IG zKtz};eq(f@mzyj4cy!I{sAz|i=0WPE)wVlLW@4`&w*d3htV1@~I%8qG-pU*>X# znbbr5nV=6FrJG?GsvODO@oRuBKdlSe_@HcK5Z-xbK~|SJ{yeRz6N-|5AHhlPOl!LX zSsM1L92$Ok8u_-Y<)re66;URV?soc+!DA03ET5X{Rt!JHyr#_Hr`!~!*{yMla;CE_ zZb*QI2piNAteAYahjVo*U>f^+Fv^Hzxzavf4_K2zg%>M8n1=0t5f%y!4JuLB(%WH! zLWia5WbusAQIx~ZSx1fF8%+IaU;0r*FFu#(?dFZO$GMZ~tNd#D5N-M|l_hc)){}ss zu(?P`lvh4W*<7zOKZ(3^hC77-fp$TW7#>-$E0^Z#)RY!B(ZSvE9=YEFmQ@W5x~7ed z{L|~XPe@qOKMh=eGkkN_+w;t!gv4iRSbuB!h?B)3|Bh~6qzW#aVdcE%eUbVMFQr;J z-k+;S`_?5=j2Dw)sMZ*ITqIe#HwG@j-QFtuwQspoy#-~dDYF|M$;(iG4=hX9Bl0He zIixuLOB(^2nD50;ays40)jG4ePm2u@NWfJDxoRSLs^6@C#j|Op7B+e$;^L1RUO|8T zx1v%QZgiO|(wn?RS296@#WO@KB)u;?iQ9 zJ~pc)g5U<$?Dgq>OdkuW7IcII$P5dXLDKNJVi_A#S#x_cmFeV1LeY*5K_pu{EVM>LXv@)mWxG>#S2o{HefTlu#>OscLZ2nr zh6|DjvPUClQD&{=yNfT)K=!W`maBIQ&uCKi?O3yKxmaN7U0ayHP6W!@*Wlr>tVntD z@-3jxC=2x_^anP9HYsI@D<=fShL{5HU5)HiKU=?zb}OezkB0O$7>uKB5Xr*uA3%?g zPhcp2ZkgeSLA@e|n0yE?VyQrJg?A|{BZXf^_~yQ~?>=-UXY`W5CU%U1(ODOu zZA~>&fMg~SOjQ)eTBY1yp{`P=`X*pS>7n3%V)Fsq``J8-ZwMVcp#X+PMRgJZYUBHU zlu2l`XivT)dHr!{1y4naexfsZBnMaFxP#z*X>PXtK`_r%#t`l=*ds)maNVUgr(p|} zW=-F2)yLE|UKH z-*$n;?w)CX!K^V2*K05he;Nzc(AD-}gix;%7iNGS42;OU`-n?L#FPz8JsWw8##!TH zn1$6D795go`9$QmsN{*Tu*U0UF3=%=zQBQ62c}&P9=q-0yEc??UCwR(JSD}ZHVd(O zl%~zXO(t?}Q@c+s3Gof<$;*bsX~uZ&v7gFi0PlkikN(whTF!TQb1x*g!i=7FQZ#NH z3UgQF#B*ftwO5*P7d#gh;B7usJjL;&6LuyO|BAUE!*=9@uGgHNzT##JZ-;{+=Q_fnA6QxI1o6?vl&FQl&MyBi@K=6U>7n?L@QN)ppOT?is>-N z!OlK#HUxy3)Qrn$2-EM(S~R<7ku57htb`WAW+!4a$!y(5o@5?*F_v=W?7=L2C0)P^ zVt|{{kw;pA+Lz<^IB(`d6t|=|-k#r%lrPwR5FrzT23pQ{dU08I8}3}Y%zcqn%(Cvd zVqQfO{%OAF_9=H&nM#&_aC+03Omn#-iBjBh_th6qZXQyaq_s`mqY)0e8ijMvbuxfOYGv~)G2 z6M$IxWBXPQnjA)A{$tajhc$lPl3=DCR8Mix$_0e>ONubn+aw%+)fS!XT3>*_Cl2QP zM%R=TDidQ>HJjJCm1nPA3P}uA#sW7h_dP;)2pcR3@`)Aiw4uW&V4(((FBkeN(V$vJ z2O%kLXlybr_GC^%Qd(0Di{2B>d8&rtdqp0BG?ZS|(aQsA)!=yp;zQv$NguHzd{8@H zDmn`9d^-!IM9S)ahRKQ4&MTVd<6y!$jfhFD*VfrfM zxYax9F1Yc`)ODhsKxdcr5eH|olTtP5e3%t^oojh;NqkAS1#KU$VxGEqe=Mhuumi#J z2AC`QXsIzTZI!aPVt7miztNtaLwFj5`iLRe$oz3@!z~hjCjSRhfdT+tKbu>JhE9wD z#qh(_2H_zA%|1tgm>xeEhr>{x3o$fhkKtzO$D$sY_77e;miHgP3aXASP6?sX5*pnu z@IP4Hc=+Qq;>m7~#8W<`wl0VfyFi(p>OoP@sr+Y3=uRAWbiqVWREEp_F{*I>yI>9`ry=rnMi>Lt{|-JzTN-|m?5LO_3$d( zJWiz<(o-BS$qs|~rqgj`QwJ+_ZWQsaxzjCwd*a4nz=dzUSz##RIG;s+4`MbF9x^(> z)L*pfAyf^py{=m&bGcu|*FwFHXADy$A#>NYG_*L=FsRXfCR$v;_O8#!*r6}#r-wAo z1%Ke|h5MZyZ>#w}d>8r|e$nP~+jsl>40a$xaKeS1D@2~*wS*LNwVx*Dku?QlKTj8b zgtOX1F+4JTY=kS_DF;4DIky_!*V)usloO7sYcQc>p6*>LM5CWcK$#Q8Ii%);aFu&$ zA9Z=j;3_^rGn{37$;8Ann*QeQS3O%K^DF&<4bE)NVgk2kxXzT{7!vLpG=vN9-yV6t zxm2@0!+j3>S!woaS>3e#8?_~K5XFIiWPGeH+M?>+COIS@VEf6+N{momBim?v%+*vC zg@NaRd_aM6bk9df@Y%bymHJ+>##|$F4lSqGGY~3zVXblqUc~(s`Dl#;=1%@JMF{Bw z1!7_y8;#L-g7z^r@wQ3B8KYbISR)wBXRay361^0+9Eo)$T^y_|+<93a$JZ!-gutgF z6o5B)-&`k@SL2VVkK8B5-$c4ceIa+hIDXH9Cu2GSrcsT>mFqLtEzNu*8i)?vqPa*K z9vl^ZC<^0PNM<2e$5_ycuo7flPAG@2vCzp*H#F+b{nh=!t4_=4nrecK$yMqjFz~vE z=(FfzDFu+W9mfyq_yOj#3?Wf}M>m{ph)J}la~R2q!Ah<}nvrG7l^gDlB}dIMhA&Vi zPV%*`;PT~;JY@L=d=@NA-D({5Q)&rAf$?jk%Tl#B%Shx8u8be-Z#k7tyEW}ER>03q zSewMxbk~_0!0?&K(4wi&$j!(gm32woD{CBzhN#u606xZ9K8_S|>HJ}TH)!RxI{U2> zW8@Zl*w{!DeaEh{j*Mu>`<<1^E3tCLRQ+7OmEotpWRI{bULs|F+@w$4vYu3bP zv@<7)K0>fB(5~ir0PsX|XR||Gwv#Z*Gnip?O$=<7Tj?4kAhgiKn``Dm?BpBaIiZdc z&$#DIaGE3&tUKYy<)EC9vu}*s(MIdfI5=Q6x6_e@^MOy!zyOIrcE76Me@xh}zI{}g z@24KBBE@Rxrl8ddHC;IoJ&hz&)2QoM9Brbc#H*@LMIUSg&7*5PSo0gzS=R!&P03; zY3;a_idWE!ey@NEQkq>yf2FhFkx86|Gri}GGA4FgSSE+UO1B%&A&Oc$z9c17`m;yr z@RR$L?NbgtO+|mh^wQ6A*eW>l%_nQIe&--#IXPS%%Ue^`-wPLlBr_;G-btw3_>~Hs zA|k^&Iajxi#(mgm{MaxF-(FH##WCP|1yzGolBIdK6>dy^eA(<=e{$i9x_2ix$S5;g zA8((03%%NPKi%_y`P6zH#{M4my~rz00l{N8I-8X!E&F%0YvWz`mN|(^@n+T}uTZ8< zM0a?HoJYFt)<@;ROcYJU9pH)tgO8b!oHg&9$7_S<8$wAk?2Jv%)K(64eSU|!y1oYX zTaneuMrTBX-eVAse};^0ry#fHc8?AA{T=&KD+F*xT>SO;ZuGsJySq>HDe^e7S2f~* z4Z$uy1eb%2a zn8K%qJ;+V4jyBwOauz7@pynowgjvUH_$hc(ICzdJ7>4dLE0i7M%$jqJOprs-1EM|} zALdNN`p{eQCYuP55UfG5m+Lmj1D{7nC>l@J!uF=TaKr9RKf>~89=-*2c#AcDyQcse zHM8Lsg~<)`e;YslHtc&GiPR2+*^{Ayg~Dc(9aEF8c#?Q2K2_2}+N(J9^f`St%ta9@ z3qjde3^qr=-z*lvtE(Sxz-rWoJRhj-oTdL6y!?XMd@W%~@$>x44VaUoegRb8k16sm zN0CEvtv_?HU$*IbJ0JTk!t;qmxt%)78b0c*&*Z_%e;`=*%An&?Ad_twb9aUonnIp$ zCxE;8gLlqX0$~KTOkPQ2s{xh5%8Ld@yZS{Xn!R$-Ckgkr;G^gHabtVk<&}868Z#c| z2Z0m^jStnDA*>A(OcC+e+{n@JHOh;ujVt)ujkG z=vgVAf9;hsjmaSnyF*ahF*k$0)M*d@3>QyKK8> zezCCC7cGE!j@dTXFNA*K7Aab!_We5h6A6`ud+%!YkwDp5g7^?P(6K!wGQo|SS&^L1 ze;=T7k%|jvb4x8%&W8L!g=($@^LK13tPXK*I9JQ3Ys|Qm9{$s{3VP!&yqtY!k1m`F!C|@ATY)-?ShzWvfx0DQI``w6f|1r6 zku7V;yQn!DI9^h4Qg11RmJ8i{w~Dp`e^_m%N6wI?!;E?GR`E-sPN{v7YYvkP7ye94 zyU#JIgVh&%dG>7xvyl;eUz=j3x?@~;>uB-#SnX9rnB_7g!r!6u^35)P3C2pxs&m5Z zr1kCmw3Uopklu7NMa~&66r33vjrgv@5_Inz;GQds*`P@L%5!PMyd26;S#$wOf1@Xq zceeU&uRE=!+a?6tG~`}0SdYrTTt(z($>T8-z!5@t9Vlm2(*!tDCR{5p77#5o`#AYD zX>KP}%k7u?U@PGFg9wp706Y?XYQ((2_s6=mlR8xP3GX6T%X8}pA$ELj%L){PV}zDW z>)bATBL8B^L*m2b={H@KzR==H=B+B5LfBQm-Fz7mS zdX{F$di=;&-)uL}XrF8ReaH|J9?;LZ%wZd^W{^6dD(JUaOLvq=^WgQ}+*E^n`x-?w z1jr-~mN?M85%R+jTTZ=`xJyUrsR$mfc@p^`^g@vLd%5t^k99xcv$O2HxlOLi7zb%~ zEkTCXB&-&QrAw2U(ZaZ&eR8^ef%zut|bY;SMkw@Di+l?f=O zo@u>0td=9>`TxS?eTA5A6Aq?jH=(mqG}Pb-;W|Q-d5lA90O;>(v%bP8c#8( zzPtKCNUb-LEiTEne{t^Up7<3W5Ya-RuHz_WWlxj2wL!J3QfB9aD zk^ODT9EoiMjzejO_I)5nENIcUV0?TJ+3)5yGrBTT`QC{&iMox%Jf!t;^!S_)B_rKA znl(s|+p;)Fq7*%eWr4kiV*+ovHD+Ez2(~4fQMBOtJ?Q80e_)BUEdM^-nHEJ4+D?6`Z*5RDQj1mo$tv}gJ4uHzX7lowj@!nN8vd@wO%S(C;AhskK zY%7ijpQDC1keXYDH*#TOkI|P&I-<09@OW_d_4>RK-G(?+P+ww0R`(QX_vkYO4gw@9 zz?&mMJdOa~w7F~85;sd+d-ouBAG|NLNPLcL!`RUX3_6=6R>f>r?X!>BRKGL~kX*xm zWm-c7eJZNc>uLz!GSR_sC?`0*ic$Asm3e_hsUP%-hFp6G99IGz z1O&Nl7WN#o-EtDQwYN9kp1a5ctkZS8O3hO^e=+4&h^mQJ{l4echSZaeNsbYj#gZo$ z1c00vY#N}EKfX6{6f>1kAKn>Z(TtRYgdTdr{ASH=z^86ZAYr1qBT_)2KsCkW=;KdS zGjk?^KZJt^X@FI=Fw*w6{4E9&&HJ3=7p_MvZfrovgseTb@fGTkY4qsJchY+@L#K2h ze?}q^rcqm8jVV4+4vl+grq1TCBq_-{j%f|w;UuiAmZEQwof!R;jE)(~#kJjqR&F3qD{e@R_^ zc^d&mv-|Hg`1%8;0Y;IuFXciFhGNMN^u1b9{7V$Ye#b!(HIn9P+@%Aoa-MoyJ=DX@ z6TrdlYp{v0n(3RLEliK`{Y-q)4UwxYzbU9-^IDRE&kJwRw!LwRMikpGIv-D9Twy7) zB>V#Ja`=sl;n}+QMbqfauV9C=f3=M@KV^J!!s4^)sfKhAx2fjzs#-vR79^q2qjpa4OOB^6gp5;+Oh zaEfV4qmkPm?>||E`uJ6OpG)Ezrd45C5i&01$o3Z^SsMA~uf0`9&ms*Df0LQ#dKeNX zXrsSZd(-uAL7cm)$Q=?Uvo_&4(EHICn`$E&(G}PvE*$sNViqtzWmT7rTug~<0!G+w z4{o5cpn8wNt18Lu=-vod90*k9@;0H1YTkn3hGvs@G1}#@NnenLd?5wFdHWWeiR(B? zJk4nE`Gnj;#=qD4(Jg+Oe-Kcl{zT{<)9T!;dPi^X9NN}7!?beI>s;0;Vm0h?5{|8z zssHx;?N#i2$qXFc^C&CudR^e~JBuMHZgVQOkz+f0zb4&Vad^R~f>)n_G7ZlU4Ez!? z^c9qW=m2_nZmaPJ2}m3r3ka6Cxyk%V@b~U-?J)t9q(=i{D$W+ve_+jx9$k^VDW#B3 zhEXUu`^>(pGF5rLze~oNwGIL4>eT6 zLV~*wq}b}pdEGTsNFo{hnWCp7L>_3x?Y4-vVVOkRvRk(?Nd|RD;tw8~^KD&Em*yR8 z(rpiEg#?>g>p9|)f0vq6HtO>#iLv18Hhu*LXYRDO|AM#u?_;=5Z4Y~NXRmu<{X)C6 z2l;Pp+^-4cNVt5ct|zjP?Bb~wX>Jr*G}e3>#KkPo>G)EKN0{lWHZ>Yqy=HFw=wg+g zZMsS#(O3N+Q{K5A5_FSbNSrNrdzN8*`P3MHQ$B|{NeA4jf1wSiz2yuAoAF)eabS7!AzKis=B6~|$0I2ZF54}9|Yr1U+uq0{Vg za6YrJPz_M4e3BqQQA zdU`weMB;1Y;MEgbwW#I*)LBN__i<1#4L?HDwr3pdf9p4wM0Mk{3rj2CCRiW?o6coo zAW*XL-N`M)z$I;45!ZsqygB4)nL3R&6r9s{hV4Lu{nm;5(b@BP5Cyp? zXfXuZe*#d%avJs7Yi(vj^OI>BMWyFtP}^QE7bk-~X5uY!tK@3$)de;-bY1S6dp?pg z6NxUf{i8ORple8`{KIDXfHiL8R7Z(^NTC}(L%3REgv;!59n~XBkfjl$5!Nr!1Gc$D zmgkKXqH$@$YvSEH%@lGO=%YOwfFzg z33Is`dp?(oN%_RSg?`-{cFLO3LU+Aw0Gvt3apTG0!!;`KGAm#cx>_)EgW2}`8D`UT|F7wC)uwD4xOfE!e>5fiuEW5yx>aC+0+6rhq4P&nD$bbE98@U(+6~e~M=Cs>p!0l}JedZj?XrAtrXhUr#m;Zb3>4 z)t@q!N2jdYpVV1ynL966w3i~dRn}(>SzV!IqW&iZmALuDLpa$5+g#2)+4X{^#yb>`h!DPnMhf7R*n z0}mVG1J}8OjlQCk-Xs( z3Y#A;{p`Yv5nI|Is8LW@+nz*eQ&ps%H-lnhFYG@$FiwPUU5 zBxSy|d84LrfFk$I6d0l#Y+fR?f5JF6C#6mmzcn1Hgz2SMRB+JD)aCE?RV>6DogQ~A zyIH?!!F)sQvtKt#+%7vtO3$asSI=vp)Cdx&k4Sg zv$CN)B|O}eo;r$QE!+I?>!uifQ4H*-$3_X=Ngdi9|`LxW10<~oF(9z zv>)SPPt0E-Q(Wu`-s!^JfBzZkb0p|c{>uwi%@4T9+=kU6xJyfbH$-RZ#8at6O*)B2 z6!zEvO=2xHyJwb&*Al@_rzm2HnRwLe3H8kjqX1lZFIaHylKqviSDBbtJ%u(TB#COq@1| zEzpgQt0?ar9zlOFw%pDytyE0U#HJ-!6apt- zH=lwy!Ox}xwVG77f8Ax|h?DqV=9N{H1tbZ{hZ%fbAK&cm0xYWe&ONeTwI1}je~hIm zfB#-|OsKA3h#13a7EU-ZDjwVf_odPzJZFOU25C{((J=~gQTVR)wzU*v0W5&b%)XcbC^bE1NLA6d_l5b%CVL?}XS!8lR z2_5VLN9p}Ge_3)Mc>TW=DFqB3+y4~U~fJ_%gFu?hwM)h`g$9sV`6 zv()xM;Dub4`+SKqw%K$LJJ`nr!yS5(c0On}{zD%=8<@7=IMYSZ!Kl~*d&j3^zbLkE z0~!_gk$ul1aanBAmFKw9 z(#FD7T0WpXA@EV#fm^G$Df$&0>nor~bB2&+>J6MRz-^6G=1p^OywGziu6$mbk%(6l ziRM#@zE(q%rW}#c6rl|AbsJNo_wcB8yBMT{e`E_lD)ixNhr-o(uDsEns!E!%5jR54 z<=y&!0+kJF@?$CQVk5X3)PbmN3_ryem2@6YO8Dm24UICYuJMDh9Yw{(JnFy_=CDhu z%lcL2dz7pu0zA%y+Ok4D1fPJeJ#G)$Ju`UF7U3?@l4!OzU(_SX#DRos0Ne(!5iuD` zfA&DG0X<112?~7BT&&>pFF1T|AyIdKd>tw@BF$p-QhRImAv*f-{?NgT16EG98m*AP zE3*O2*7`DBL|1Xg{r|i@<|A|MH4P%`hjYKseTXi+zxtwmz9TAtLfH`+$Bl@3Ro{@=kjE|l8omoWt%=8M!tjL7B2{WrFN(d-RdOAk(vH=w?(vR-$#ncD|~ z^XH<9+QaETLAeTI>UW5x9 z5H{_p$Thh_HSACV{~yxNCgtwiG#7F8f`Du^m#QfGrjPQ5Qu(4k5^Sg(C5v#5^u z#iQpkSw%^DI0#p8Gqw&scf;m=Xb-%uc-Xdqxy`PXjgk9A7cCU1_#!|e3Tk7yUUo(k zpcRiCof^ec&2tq>zSkK^e-8+Vx`lm0_t2^=`Tp`f^lOqVp>k9!wiN{EE|`ZwruR*C z7Xb5jotJG* z>~Jb9e{YFo_>Fw4**U{x@!9*Trgi7Yov z-60pgV5%lX8_@_IDW<5W5{WzVgKT)zv1bJi3yHHNT;2lieqb75o&#<_vAbHvJM!l2 z<@k7CV39NbZP?O3f8YqIR^AMnea+0ZM=!G>bY=ll4qg9P>cRR26PnHPEJedV%406< zd@Po>oWckvN z4UkueNQ2C%t6F4?lDpRaJ1Qk-{7q8W!%x8P8;1UB=k(MSY;Zc*DKTTWu;$N8#JVAg zes}lVPuVN@YbaITtX=hvW3tJ0e*qx-t5a?@g+_Ul4y_k=X?#l#J$1}s<>f|7R^y`+ z&b@JV%fWC|f7!?sbnw;}cMw*0|4_fyN<8NJ`)AR>aA|Wb!~DJcBrBpVRQZ2Kn*zP@ zvQDUd^0os6^KJ!qCjK)9L;m#R!B;b5AvJT)e^RyXaj=NrOK8=`>YnMDv>!DkL|KJ} z05wHRaNE=3rj9ne*I~W;=05%^P@Z!rUDCP(o`}L$e^CoXSA}4jVwmiLi_9;1_QjSI zh^Y&q#SaL@@nS!3wl~p$kG@msg?IR;DpwJfdn?L~ak8NtKz-iq@I&40eZx9QgJ0Em zP{-o72Hj9`LQwd+iTFD_tpHh;29X#*;m9H2u804LKk}O>;G>env$WjdpF%{e!C75( zNL$BOe-unJdwH%tm_+2LteZyuVspbp!ZQ=)kTX( z0AkCOe4Hn+`UxbRtVA6IM3QfItOyHS4rVPuXHqn3kvO|4^w91OWuCWTBd zh?U_+MjLeW75E%SDe>ksYQ>=R;PK$(y_7Xkq5Dz`aT~TEQ z`#6;kYl~_$nl|i3-%M;Z2YBob3#v*KrQ#{soex-lOMs5}YCU9cL;V#TJBudR5{qOQ zbFN>uY>0gZvbFBtoqA_OR;S|I5VA?)+_X|}wX*kx*?Se~m ze?3?fuzR0)J*SOe^)YdJ%*ZfSUZBx%WZMp_@72v!v~hRF?{HpoS9d!$#JrzwRfInR zCgy`YUU&g!G@J+RE3JqD;9SiBT~@=Gb{4Cux6>|_{7!)^QnLxF|e>iDK-MOY9x{Qj-Co+RdgCu?u|nIe?aH* zICt1Pf7&ZGMF8kp%}FEAR0W0pbh?+z80LGwAmQh-Kx!+>xUOVei>jD}e>$4ErX4O7 zSC(2Sxrp!vF{aiiY20KG!G7htBx+%3AZX*0zo141>Qc7O*;Rem8OQXZS0O4cr48Ud z+wUJV>7)1@dfab@-az}wjHnYCf48Xow-~Rx$)Si|sQJz{EJuc4GUwyTnI-?#y~WjI z3V`gw<7|UxDT7MCFsMxL3J?^M6YSXG9;Y!D5V8;RZ|%(iN4TTwS>$!wkgjeXCi&U# z?j)@1uM|0Eh(dZ9+0pGqVEZ*kBL6!9OaMQ-lque>9c`%i+!VjMLuHyRf3y6*JMgI} z%e~GOd}tNKkV+50r;vHBVu7hOM}hHX+uWGjy4;29b7pib+0a^MY;+y^xqC5mX-;4k zcaw?h;MU6d4dc&cCyLZZsm9xX^#EFnS(bNl@Np}Ka%Rl^2=amK_~11JgBYDS0CVvs zsW4#dkV+>gYvH6jo};R3f2AZB|J1DV|M)ywqb((9&rQu9V6Om442fsC+x!;}<>-fP z(W*}|lkDvervZF!8VXV)Wl{WLYQdL$c`4ogE&f^vT5Vmz-E1tNa22fNee>vy_tbJ_;qFfn1aYNMEQ_~!i~o|TkqmZiPoe_Mx<_Y-ea@anNG z($`DJ$Gm+s>P>N`*4GosohDR;>Xj?5WRI%s^Z{toj#o|GArNX!`S-AdW|(jnGUpb2 zYXUDhQ`sV@Tx`TSM#u--kM&J8IkI@7Lu^$foYlnexs(z2d|ce|UyPAy^R0s%`&cMNIX-#i^}e`tiI(J(v2YVmlp$LY&C z^phc1U`Tm94X+P>1h)aeFw5Zit$0aJS6E6_2(ce#mwZZSG}-XJh=F}GI$wE!VI zm5*3ZgOnb^f9wy?v~GB}Nt?3AHXMtq7-4QNWw+m>mJK2*SeVPsp2jCeF7_T8{Xy7AY(uvRyZf8wdi6Fp zDuG{X#t6_NO?IG?cYsHKooq7)^ai6ja9@V_Bcdh8e{86vHQb`a6H#!6DX@+hMIXfB ze8h==!XfU5YF4e3$nCHmP)Y%6?DSq#1;S(}(!j)TQ1u0K9 zrIqu38%BJ>lPhcMFd3XP4lY{3*{clR`T#;*hsi@p(8jjR$)hK&`hvR{fEndCBR&5t zvL8r$f9+rpM+h2J&9Z4ujLLb2YY&Om68EOz(-JVmgR77vW$u}{wDbgbKsT>gOXTGu zAmLnokHV)XRbApdmZTw@r&+J7^Y+L5EL&Oafj>(jSZ&(GnU2Ln=9BE_0@tc<{a?n@ z0S8GFwXAS0iC&|Dwc4v_H0J9t|_MH+%RjBjKwB?-KxO z5p04%mqYr=vg@4ZE^%rkSYbY8OAGh75<$OqoK$ji1Rch719Qe&7rkP$@{9+1^mbF< z>B{sIP^=3;4(zGxa5!m-cTc%p%XD_J^@YDY3yq1jz`eDF-HL%`n0g?)8{5SQd7$?Q zRF+=i-3#b8G9dinitgN{vBSMV*oe zjDFp8{*dNirbOF_!@@0u;CRO#yL0 z0x>g}0WJa)w|$NQG#mjjmytaK6}QK#0plA2F*lb1E&>y`zs3Q!8`#EjsX_8EhGZc374^|0Ts6tE&`4`m#W4A7Pq)+0zn>^b>aaQw-1Q| zoiCSa0|FMe!nguqA(s~<0u{G7+5%W(m;WsS6}P2510gGyqG|#cx7}z11|XODhyoV3 z`G^Bg9ha=Q0v5MkrUU&hmm}H&6}NEJ0~#ThkUawyx1ja|*(aCAXag3vj~)cSB$wle z0~WVrLj*Y+mq?}q7Pqrr1hXBNTh#*`a(1==e`yzM022#0HvM{aN07_#j023D% z2koEk03jQoqlK}dEkMrD*&Jx|k_*ZN6uw(#La|@?`DOBuCo!tx_fq)Nz zwS_Uz*6G8=#nuGq2>8ejP?3@aDA)sS|79%uF9TY@U&8?~(KG!g++W^*1hTOG)7jA2 z*v`h@(ALAk)(l{3VGRT*h|AJDyF1eY3~f#RFf_DwvitBie{?mpur@UMF!-}{Lx8xD z62S1I!@v4-GIq4EcXpz8vatT6M}|Mbe2iJl);(HqeWDhQK;w_OdocX< zaII|Z+-$x6O{NyMCZ>P1Vd7%Xpk`~~-~yBq{mbS<1pBwl4CoACV`OCHWMKvX9RNUg zV{?W-5~zCEe*^ziGW{X`=)uR!-p(Fi`q2i^$HEl&@dNASWatV6I6JxkeZ2mu_-}$` zVgi_07&`-ufMyo9uzyGY5CcvB#UIb#(ZU^|!}zg&OaR6|KL7cr`>|mrcDB|Yf1CgL zh#90cWX0vBX#Q&XKT2U?J9mH=9V>v2nS}+w#=-$$f9GNc`22SiMMI0fs`!Vel&z^9 zfa_nyeoX1#1-t&W0hE882NmGIW69fn>@5&L`S+TCVPs=8{`g}0e_r-KUH<=e{EsOA z*ERp&io{*4t^d?h{>$M1qc^m%u=e=N;$v@JoIh?r&hFz9Z2#9(1Ng7Ql>?esxY+!! zR?6A%f8!Q}Y|X6yGe`?3aSL~#iK2zGvH737@h`F3A9rSLVGC5WbF%p3%>tlfVr2Xu z-N$hmTYbDeoIY0aPZjXvcK$n~n60s$$sb3?%*GBdbaXWIfMxvHBxW`?fEUxpHJSk3 z|6F1K1HG-C^M?!IqdOmfshuP2ACHus4ZtAue~0K_!~tLs`I|Ta45EJ%7l1+RKg7WT zV37Ko*Z~Z(f73@4xxeYdPvJkr$p~Oj{F|5o49b5KD}X`uKg9JB#qe+XsKw}S`iN=# zANpf%3?~0TCIAEQANY~P^dI<9x!FJPBdPg6@M9hp|Gd(DyRv9!%oA)`7yRR)MhMr9Zhw+*j%;5ejf&1| z#j(TX2@xupsyZuh)A3Fyd88M4ZC8x)e^&4(;j7naYXekRTcT3?v-gXByzUiklzS2Q8 zyA0e**(_a*TJ)EI@AG8&M%fG4#E{N(_|Vj-m+naOw|8@>xl`|i((yE~K4&p3f8V^c zx0t^^E_kk2s4_csxek9859 zbL+@Tp<*#wJFaj^jFbAb*E0lY_QAts?W-8rQ`gy_A}(Rmn7X-%OB5)y6!+^L42Qwo zhda`k$hpQs$$NMmY_EK})jGYEe>pv`o6_+d)iK?Hf!)DaYZhJ?eGPN)L6?KX`+dOK z?OkM#)n_?%Wc#R&`NmIpAY(}WfgqFtff9Kj56<~1Nw@@m%&79ADkhYcixV&aNTeA5}5B+rYHg6m~HTDZSM zh~9%tLBck-w=!)Gm)EJxe3&Cwt@y@w zTehP!j@OsCqFY1an>MaJ*Q34cG3q{C9bFezI~1TnE~*nhcMOtAj@r22w!lNoh1VXU z==12)8Nb=pH)D9aXi2aCIvJDqb4-%3aozSHM(^Zd&@FakPOf>5f7;!qWdZX*t116e)QeHH+nZgm{4n=8n@9JAp}N!Ddup2g;k&ZXr;U*snR#%VEbBYH zHFAwLf2^qWAuodFg3DJM@&3>27v=cb4XItYKNr$RSmMNGe5Ei$zi|j+s&WXe$vrD5 zB1Y2ld|i)=mK>$Ae+bpA&EfH>OL)jZKd>Co(!3R0aw#YLQOGvbZoe8QEcK&!J3mqd zfca9I$6>G35yQ%83@VvEzrcFwqH~7erq$7xl998Y&HO{hd3ttaH05VubVHVa`S0|| z7Ne}~H|iC9Fj9Y5lfiEWk)!~{5crVH2<`x6;=sJCQmU_|f8J^{7lWmz(WY3CkL75h zjYZB6$nz$tz2EcrDl)U!CT@QC(cm@}{j%ifAv?7Yd{4#PK^sP)!U3HLA5&!jnRW>O zthP2Seq4JW*UFUSEH)K5+q_;Nh%i&&kHv)3S6C;mr!0^{mz|t_-COK{84RVDMf4mE zM$KgAc7Mp+f1S_DP@I?B!qO#JzVoGq!*kWp^JInz@0cp0nSLw%@+Kfk%so(xqHVbB z{)8=D+}MxL*0Pg%r(*$f(HQ+G&W#B^=H+4hh{fM1S4uZ5E3(IM!l$Tu8Xnm3NBdxXMsOPzA3U@wA9O!|hCs z;eh9dDIT`h!Z2(D7Clu&*jz1074f?F z%F3B#egNK7CPUi5f~CaB|TglqB;>wsN?=lsX=i-3myjYCOjZf29HDM zm@uxIADX;?sb%5+2`N~Z)+!skX{GP!b& z0@5b^2_H^|0!e0P4uOI{7bQz{|+ko`;UmH^%5f*z)342A#d z(XF32CcT4sS|7x{7H;nKvvr_hS&7^DihPxt{ls^J%F5q68FE~;kbC;*(q^+pZbUn& z@Z&$6`U6H)XOloGMZ3X`0!O$t9Ef!NEC%!4+7k`lkBUk^K}A#S_Zn=O>p}LytE$FI zf0f%xMgXn%nhH=lCK9f{-uLAhpswF7?gpg!-z}{JC`t(I!#5Er`976vF|{7tl}F^M zbOxl0bvp}Fpez09+^>$4Gr@5kn? zw7o=T^wa*%`EIgf%|>A~X0+}=n<9Bfe-BVdtU2Nw*oKjpx4qZym$-(2a&Va-XdbFv zluH8Z{M}>rd^~T$01ieEWq0mN-QSUdy4}`^G};A3w|^LGgk}0w=VOQDF9UK$nR4MJ z^m*DzZWrG`0DuCY(>UgXgY1oZorMa5TB0}m^E>79Po^%{Tsct9mt`T#I3o2=e>Xu^ zY! zg<|wnoP|PJ&IUB8lXfR7CrQv%l?eVpp6CV#MBZ-?Z8f zF26l5b3$zfo{z!dY55gcT))?u@`zb4W8$abUWH^7_4u?vQ}0yjb2b(rqE_)FSMZ6 z$KK`SKXCnLu@U8l4XF#~e@X8m@Vi_|)|&XwIMmLJkNqb4nxM)-D4N00!)#mRk*3op znW8fei#0X|%io|%R^HXu;7ilwT_9<=-hU#oa-c+cNFY(p^uBFwB9sIm^ma!QS9*@_ zAlfPP>L+KWQGl+>dITA31*U58GUfBKx2C@W^hLkq-!LayEqaPHLP@XHdX_u8t&RL;iw#TTA7 zNqAV>!EH!mf9CMbYIj1-Nl5fOCL#guEsSlH)|7o@OlJ~;zAa1vveudVWV7H%l`f!; zXGSh?0wlx-FuG|@mHH%l1`lpY_Y?vh+<&GJ%>8}t2s-oV?+nIjsC2ojF z%wJu}D7tVD%BZ^2;!;(rR_>RXmS#pdl8GkJ^pxDNJv$fWS$Oo((_pD&MfMI|Wxx3` z{VMqNjy>IJe|qU9Oc?7rX6~@tRW*k;w%+CsZ&Wx-CaSK9yiP3vk`;KS9d-hAIUh;U zJzAQDf5Yu$9TEJ!_)NQ-Do7~+;tvBp)VD!okJM3!s83~2&L&O;n3*D=@}9;`?HS*n z1KA{eC;XOsTXKMa6Xq!Vd6RNHRM5y>T_Yz?imqYq{V78Yp@y<_epYN6*jKP(HkVC4 zX#&;jr^-aha12n+60PlKOxqT;=_#+5TDkzFe;*G|&^gZUvp$M1XGzZe7zN*@-RqM% zmxkw*@m9C#vA%eX8n~>#>tKZA5;lJSO?KSM(i0y062j5@Wx>%lFjQWeu<_(AYU(=u zHBY?Ks-h0A9^$%F!Llp0<8#&K&jXqUC=KA}g=wc!xdrvqD5|oDw3M5kEvPc-D=qb_ zf3Zx?hO=&_o>xc{5@}f`OaU~xhG79T;(<(~@8e26hSgn(%llBE4tGo0Sq&8bQe{eFV7`)>2K6!f3nK({hyCaz5=68$}KB00* zZ_X@UJp?b!b>BCrWS8zzQ?rCdfjNBfN+u;h#(??@P==xir|Tr3Tev!_0N=tU03P3=DGj5T%ne`h~! z)|N$an3ln2D>ZOgSBoab;kR6T5p{F6ZyRp6=UN-@hDU(ffylIAXsT9WC~mH(Zrj+V zjZ1duB>S_E!ZD5*y{b{8Hp)V)Zw87!OQd>1!!MjX$Oc?iBf`GZA3(b zQoB&;)UO&n?bWVF%lQdHJR?~c_?m6rXd>iiXD)6rzkIO{GNK)B)Z{(ft<+tafXw6aJYo} zFea%q*5#_iUIZ5GHTtfO>I$ov1)|uShq@ZMUe*zx|!f89_?uF7WloiJbp%kqwc<(h-LH84j;t;k@4Z@u# z6~Yq~2Q#e4WA9YGsJ}3(!c7p64GaI=&y&xCuM>b^OiH!Kc|U)Xy=+L*Y^X)}x$!8P zWGD~0&uiC0oDRDs%=h39IEVU=;(6yQky&mkOEsG6^(JyMMe@xSC%%^Ap_U=@^ zsu|CT|CTX7ZDSMVpRvj#R#`23(yyB(>0POk*jrN@)(jxI9qxka`hNK^hTrgg|FEXJ zeGsY4`VjH94)-yA7chKZaviL~j)?gfd2k6V`qk4#!~*IMEv%(#hMe{Z@+V}i52k9q zs$tq>cwl&L`4pHbe@5oKS#ELr{M)5wu|AD+5>q}imCz!h*JjDX#jbJCQcYfbd5NbY zwbEE=s>+h`z6mwqp|$)u)^e;7n&n7XDLR;2hl~EecW`Vx#!YONZ~9#}eSF{yN1J|1 zSHJ6BOn>hbz+O>E!N`9qXx|YSL36MyyGo~m9sPQmF;_>Ef76sNggP{XKkZns(_YF> zE;*ef!nJ7Q2;tqw;aG=yBk@Sa9d<44_}0X`L0N7lq1W5rQ14squ6PWo=P$FfF^_}Q zv!(T<6(Ty8Nc_-}M2aq8+RQ;Awc z-kGonIhqJae@1tK9luCRve<<-2Z801cSga%PYB@(YOqGf z6&co6}TO_SdKo^wp)`;JbQ z_1#FP3q?Vo?`nVtBUjliY_oMptdgxzfH+5E-`k&Of1yf>{Z|ZFk|r3%7n8^%naAHw zKFrZLUP?k#Zl9%3oS`(gzG7IgxI`SVdrhQJc36 z|HrkQUXGm@*!JQ@5(2MgxQQW920_nvXsg+u28IZ+bBotTqSZCV!;F!O|9b;50n>5u z`Ik!IWQ9}{yowXbh_IAubM(Tz>ow{M@Tf7Le-K8w-OAOP)VtE+{*2MV{;~sB$sJ_I z(Qa4rNh!QO-PQBzFmoX25*WU6#Tsi+GBge>HM&o06DU`ws5W%J#2|q0rQV*vgc-^Q zZTF3m+ZPe}T@X=~bjvtP?3jV;lsTy)Qgr~v*E{Pm3jeG#|2(h9OGH=9L|Vfpo3tT^ ze}W=K$Nz@8kRlb;tR4jN1NiAS%!0hX0lQDI>FbXJ5NhQXbxQ4As zsGQG~vX)D_ks^3=V5Muup|OCznVc1ywq3v7I{F@7Np5}6tZ7~-xG&b`xkNAj{L6CV z=|nhfE^=N)+OC<^h%T5ICt9-rcv|kVe}Q4RJcEHf_nW()9=C+9I3N(P;N z!Ch}2k>X7dT|-Cq-385CgkX&pTCg7?QIQ}Z3Yd2hN@o#T1|^3E()z;HTLk9c=N+z& z(Kz9Wct+^2r-pF5sQbHQRK$Snhi(A=;&{1H|n2RhT>Hf zz9q30RhLl6bGjkPg_A+ih;h3{!$yu02ZOiFm0gtjZ6_KQyJPZ9X~BOrsJDT?ubmtO z##~=qloA^`ZCN<*)AoPXwjIUqe=EskIp~cBPLET5t2G;3wtFtd_yOP}JyU*ce$Q=u zTdR12sLrJT-;w4vwk%^Q4m4$kl}~8oW)TsI<555!btUHY!yK*`65!(Nnh!Hfn8P0S zLVtRa8*7hjx+C|IJ+O7jF80Iz#=4rTt~0(xLtmgDVsuhM>U>uo$8gWq7)99fWJ#5pn~&F@QMBDA2hneKd{K z<9NDkF<<=*psSIB-MctD&tJH{%Ltx>a_dK^SR|a#tU-;hEiy8g8L5zSq7vW_bIMqM z?7v_bi`5o5x!BzXxvvsjf6F$Rd{5`Y5fKQ5y<>D{P4F)oJuxTF#I|kQwkEcnJQHJL zJ+W<0oJ_2VZJQH2C-1rU{y*Kb&WGKrYgbj*>a}|J-d($@egZ-fZK6v_GBFFJ6snkK zgY4^gv>55X%QyCV85yjmsU}o!ZDswTw=mK1H-*3NWMX!kn)^$cY%Q-Q5}R36AjVL3qYX_@L2Wg`v7r{2|-L%9&+d*cBcBsoDsKKYd(M&1(7U+OwqcvaLLhH${ zpQHqhThUsMf09J6EE+fRy(V>Qt*=6e4M{F(xXjcA*g~>0`Sa%l&kr^&f?0D9FXZBB zWlF2MkUF%S9D@?xnl#OT+Y2UsIWbt`*1pBQ+V1?ciHAe}jZE2q6CM60lzkO}0YTO^JUrTcVM#E>4{-p4O|x<{;Oi zJNL+5dJSHY;FlyE$ zCI6WS{D?(x_7`4I&z+7MedakZ?BqhWnEG8+ByG}tn9b? zG8PS1m_@v-6v@A3+#&Z3c)H1+N1q^*$6oZO(SCSgBQ56KbMAuHbsU=KtgwAPFcTw` zkI~V)Ss}@8C4arFxW5mH$B&YMpF1Hn&a=bzEAzmzp)s6enf$RFH4JUOYXiOV+x&Pi z4*lOLvc8|Qd68jYaFyck84vy=M4X7qiYaQBoUtQeFeY!)j7dwez&6Sm{;_WD3qJei zh^zu5?|S`i5`#+ki|tIotvr*dTVE!EM20#D%f8LIlc;Epb^ajBZ%^)`^ThR}b+q8B zxjSo}lcN$nl8Nlj*>jTjs;70E?whO$?iOZEzi8Wj2c_|?XexSJ54rAU*~A};azlEg z_+dVQYDY`FvJLR7fIIdPdqrg3!!mSDQzj}CW*kyb)ajj1{yQ5eIa#F09;j~e{R6g{ zlsJ6D^qxl4Tu98p`)qDjZ~c2wyxXOb^2{ zulD+wl$kLP&J7}q`wey*I6vsQOudTRjyM09?W5P!C^B<);;B34(O8xP4SYd6b(^TE zMR|=}e9*y50Jf;4^nd1a9utcXYjjMZ_gWXTI^tu0Oh*b887welQ91;BI_^WQlgZeR?vYb7SeE{pQ9&P-Q5!*TAP1q5qq>b_t5B{p82vp9H`S@kWhW1 zY987T=75AUoW+WFQ4v%2rqh^szt>mhx;$S_6!}L9|IPAYr)+Yo+pzSQkkTNf_QznI zeiOX#lTCWnd>|?MvNmq<6Q!6S z^j}f@1VFIwS)b9K1ewEq>T|I!V5foLxnr#EpRpSH@2~>N2OObS(Z3?~HbM!)^Khy! zrX^*s(}Sfd1AT2Q5Xv^OOxvHLfH~DviW%ywG%~x|OTSvP?ASf-IeRa|Z9!}u+K@5d zfuT)&Q**Bq4I=P6)3dh4%YPKrn~k1*d+n0@YJi%Vo?YNLwx)LA5Wg#0-y_jDzQD^v zD|OT%N0bj{x4+6+slcq78Ka7~t_2yx&DjU>y77{j<)L>YC~HYlkz^xoZkGhuvoe(Czz{r0qB& znE+o#6&pkJWOd9Vt(v+{S`Chs6$(vhw+zyau-;-ntectGI8RVjtJ(?Juj*mPQB(l3 zdeiY+&!#zz`{N}3Y-Qt~xVVMIHqAL6bh-*@%9t@3RBl{>%U}!#mM!o2D-d}^3%do7#z2uvn*pf%3#=VjHQ&kYhi2$Vq#0OCJHUef z4VZ@|=BhcGX4yi$>UFA<_eos?L z;ePHLSh5&OiLFr)7AAtX_zR;Kp-IyZ8Lja|zeAA>xY z2|JE`Ru7EjMA~1f3OPzW2Suq*5D$aY0`M3t{uB z;%+~ZAU?bEw=+$*6Nd;%98s&FG@h;tg7l+%)k4z_8{V7C)eS2wGgN_fsD^ZGx`4$W z#V>7v&1hc3wrD~D?rt5SC5`@7s*;x&FSF}*=?PlP1Xze}twNH{);iwY8o+n_4scwB z)dSL^7SZHH!H3C!U}FQ)bT@VSRT=ISIAWr9d*|aE`6XFnHdUQ8Hf3}aPIH~wd}cQ{m2DCaFNVGt>L0v z2-MfFd~IWFl@p$d*rI#nCSW$Sjz@iLzn6MpvMbHGYxnl7aHV2G3jk`$jYGvyqst|4 zlv&AfAnCnjX2(N8;`*M;KMYh8qrAmTa5HU1yB9_0L;g$l+6lQ|>TEKfapK~ss7mLD zI=6qJYgUTwkXXivBcT>2-faus+5-%fW&o@20eP&Pn6)O0BImDaa-a{6S?oS`(0xYt z&p%-~o~8kdPqXH!y2k;P(WGCCXS^#@sfpxUDq+3OK#JYeJYQD{0wrXOoDB}Lw1oXA zwBoFST9DbGy-`jgN3EQ(LHB!N5geb#ZM^WyO#aJg#)v6f5{jzEMSGjtbp1|PxPC*l zR-s`wl3<@nV>I*kbl}TaK}0LZF&;$vfm-+ltn@ev5Hm%@9qH|x^;Grh)nkHhIP`vh z|9qv;X6p*XWDXqNs7?HaJT#90*VTc_>D%xFlWbPeW_=|M0|-Kj?a$*ZWT(N9H>p01 z@2lw8R4R~NTuit`a__t9#`Y*xkY-j2`F*oKmYAmVRfd=b8}QYwfh<=dBev*QXBuKs zINTpnD%b3Xc@-VG+GwMOzGybqY132O_qUqU<$woXL+j)yw;vCu(mVO?V!9?2cCTu7 zha~Z6e$KV90^q7;N2i1?&7zt{3XW!p(ceu$8Nj0kbPP!B7wf#Rm#z<|0VA-kmOoQ? z)yWnZIXpa;b--Gzi^-v<`ssmBQWUwRbj%RQI|)M-(lEZc+?x!S6lo#YoJo}DwO7j~ z5sc9$^PcY)+q{RNs?Ko>!A)fBI1le69m0YYJcGX;dqsH6WKLHh0wi|HNL`?RsFpfX z-4^P?&wRO-UEdQjw^8-al%&UziBjRLp2*s6C%iT+tw zZ)VgYgYqJvNJ!tSRdkJSm3SyAv5+38{ z`l(@ZTUuapf2Z{k0dt9}u8ns!?e~#@q$`IT&iAAle0M|na}`mFJSO7oSN(Y^Kl*z( z!DeM+bb+xIUUO*Lh5v*)JX%)-u4GWELb*v?;DUY+PX4?B5QN!xpQtG%sAK5BNRrQi z8bXZ;5wqyC{8M?zON>u)vk@Aws|7`wRf&+9GD@ZBsv^G4X5MKzv;$44(YK7AHu>;O zBIkzhZNho*?7WP$2$&VUEv94!q6fjeH5g_6Wq|<JKllUZeL2YcRP?*81g-Bh* z+d%gvt?#O9-sqALTrf5lD{xL+wp^)a7iohpCGm#|)1*F?{OWvnze`uReFpZC)0e^i zG0ZD>Pm~FB+u!Mz`?_1mJ~uTif182wpyj8C*a488s9N{jMHNSuN(~xh1_a@E$9B&% zskfct{?!>jPm^ zYCC8Ztk*V4%A=5l6Zta;NcUFf^Hxqqi&j`GBl{k-9U6_UkPoon*I45mqw-(Q0C87K zV0`clQBsk*UJ^9vVlvO)+@~S$rby)_U-Swp%E@EGL6x~pa;KY=VYw)o)c-Q3w@rs9 zj|9{4p2%%=GnZbm-;nd+FfGONz5#{(8&2#m8_SNg>-JHr{)80KWB$D+4B(P<4jn|a zl^19&TtdPOuISfu})@pAn$+j;}gJQP-r-)N0^)Y;;!gA0meY)*k#Lut7*d|y()%}9~buYF(F`-#K z3XEHJgrkP70%o7yQF_21yBG$hPc`QXLut{nU%nkyl-JC>2)rTFPE!MoieOa(ZS+Vh zj`vFMz)A3`w}pTJQ>eZTJR~3&*=T=OQv2N8%#@)=_qcN_@xoU z8SHcqD9PfyE}6vCu{C%3@r@C%bsH^ny0I4n9aSqOwQb>`U zS>GLmxl`~x@uqsRmT(%#OKDDPc+Sw-`>7Zk`UOVsLVTf#C@3HAx8)@)Ml~DQ1PzyJ z$}N*I5a;ubYLS5mQ98KTxFj(gYp5aSXkkoqp&VJo=zNO>vzK$+()e9QBOUnx_Yqzv zl`s4UiauZhqWUC=l}L0gJ-39ndfQ-y6^!VKRy7<|7P}Z~`K{Td!4!eKkl(*v(~m*J z;}AVHp!jaE3lqd`C;PE#{6=kb8XYSV=c9mutQ+kjD`A)JibH}7Y2o>g>V!oX;pO~8 zdk;RA)A}kQw5V-}4;^2*hzZS-EqY22iWM#?62TTlM8pFa>ct8?#}j=~ zp1gKH%ANs~uu3>#A6Fbl7@^IkQEg4SJwlawgwi~Rsib?*1t8-YyPR~gOz^Duj9VI7 z;RkOtfvwBwQp;CK%IMnx8-Liogm|8~G?<@QDOTlfPv!n*UGS67t?dj@t#SzcV=YW~ z!|3JidqSP@@u!Ts%B~+#HG$PsXc@3FhB*iZNlOJxm5qw6hrr^^ zq&oBOs8aO#7q_TEt3_D1itjs4M~AX`*N*ujz@QjSKf%>`KV$68;$t`Pg;YMbkGQ6k z@QSxFs-tZPzhhnmCE^u#TCld4o*#4V>i*opd$@yeNvXyNYg$c5Nl4E6zsyE+)auU-9C{50BLv;578Hw24 z?7yCW8f&1+*he{fME4^t%CZ`Kj=un-YLS0bP-SRNXkpf4uOTg_mbPE*P@W+cND`-h z0+AG{xfF#C}+Y} z9+-zqj+U-Pv6%Y}aOwK8qJaG896uZ~aCM0tG=qLLIM#`e`7wdr#~)MmGXAgHOh=K> zov`%U)7@9)h-_2TZ4eU4#c&ei!O@2vD9s648tNfm-NtKp)kmEeZDL7&tEQ2?|UrpVk%nc8ffx}x- zp}~aoCX{-L|CcjAi{@5d&}qAhKb?f?sG&>oo^;W$QI5LhCUHd2jcAD3{FxV$A3 zGfqe9j43mapfpMp_15L3kbWPaucB<~yTHxm`W~9$Ff5v8YkC*GDblLw)QQzPm0$=@ zg_k*c-i!kxl#RNIiG<1K)(61?fBDOHSWPXCQ+F|c8gd1GthJ?EUOl~hM2aQ5kfUdp zd%M=(;G~$yn0Z1s{24z4e1%|@{`SHL;~Z7^coXgklo8FsMd2lDdiCPCjMf2R(+{mp zhsGWik6TZtQT6_^a|h75Y0>m=6s}%7m)HIy<`oX4{={{ z9cD}Ra4B<%`iGlwxSO3&wpvk0U!t9EUKOnpjZ?r>L%ibVDb_y?_=qDD!|T@nh;f6R zbCpYUF_}lV94I*$(O?JV=3~i7HRDtnvfio2ALnQK3lr#tKR@a$PcZEoL4xEggi*vM zhy=5d;Vf+g@b24$DP@d+MF6av$|D>EC&^jzh*HO3Qs5c`x-ifHR3eKObPRTmxm9!W z3d*FB^AqJm8kj{mcT{352yo0a3_?eK%^6t^^*$l0&*3MU9mY{t4{U!`f9<~x1q ztF_dNODHu~p*EyeCiO)2+S>rK#@j@^@vdw#W&ptf`yIlTrVPWcgp8- zWp|~E8N9NYEv#uSa;#Rr`<#gh;ZtEoO`8(?;>p?Aw;v9`v3OqXUI+;=y?SY zn26>qD40jYQf>iJM*EHSVaGO4&VR!Tp+pv<)K$Aa51Bdf2}qsPakliz8cv* zE!{d1$MckP%PpJ3k-kR)8A^UAX`d{W{#P_qkNKDp*-jXE(q$h2jYjM^s1hnTyl&{8 zQWl3sn0CPRlpp+FC`9tHU}E^dSQIA0%7;hzfV?leR$Y+X{e-Ph+H7`TNvv7_NPb6J zt8x#Sv=5zRz)qGu-^~TLFh8we*_4ttd*k4pwa7<;sFU~y8<|h$_>~atp&|lqWJ=w5 z<{xT-RF5IB_4P$z+vSs10M-HNg{%ib)wSf^I`fCu zXUu#n|M`0yXdu7`wo|VkkGe@|vi$;fN zt--Y+n?l~Nl3K(XuQh8||592$T468Xq;?Ms_{=#oX0GX;E@~bNyvfRXs1<{ptOtpX zm~)_jzAP5#PLOVh(M8mhkiUztUdaJXE#K(@4iF&v`!8M=BY+7Kf|DE6>juDR*9L_iI|1UcydBwErX0jm%oH97!ROQZ~d2oe;@2*7OVk_Ui8 zg2q|?AE}0xQ01>#fcDtG1`L-0NVdqU0oEWuq+DOLqt2IsOU?iIguGt{zGz}d5S73e zOU?|?-eOE>8mT^3)A-}Toc02)8C>&h>U-}v`2Rk=g(vAxa zP+!LteY7FqvG#b__XmXMh1@_SdLTHP7c-7sYI>wMs`k%GukSKBfwyOl0XhrSrw+|^ zA-N7vj2c=?iO&aytcxn@1+yv|vU_S;n|LqmDV|=xQ|4#YhBa8NqzIGbs04J%`jP6xYX#p9B76xP3iAe=%$8as1Y2z3bf@X)M9kWUm=66 z;x+1<(`m(+v_XEN$;yZ!(Z!gnK@M_s>?|6%eLCMf5`3D1a0&=X$YT=PRYUOavx*3Z z@Oz7cigH@oXH)loK*;^vHw~Tf#iOe-i;%1G&3l?J1g z;N#n|iiV=dCWWI8WU&_*P!OP-2j)OaP0lnXx`o{aPUhXuO2n{5kTKPwytp~f~k_ILxOe%DHf_f=2L9K15`RoA_ah$QJRmKRD?LG zTTLn0>}k$js+$uFyjORpl1dxNNo7tBrzf@a9q*!*KJVv+d*P zmRAo^2B%NeZtA>?(cvO(05EgabgJRrZ0;ZqW9U5dFxW2RSl+&E?4*upsi`Pae65Zm z8xrj~xRX!?q^An^#tkFR*+O!S>uGiS*6HaBotw7$M>{cgkEDO{&mlVH&I|@(!tJtqRp49%)!Gevu35oM zJL(BRr>m~56~>IDXnZ1|@gcaAMkPh}`*~N>LC${aaWcE%LpDbI&lsz0Xomr$XAFC7 zR&d@@u&;_`SsaGki~JH!NEEwLQ*>Q70=L6`%tGU)ZjlCD zg3>;u(D{5jPd3)fy7D-GemPg2zOM3`Zi%)zP?KCBZl7gT3VxYca-@Mq>IULhmP}5t zKaSwaNA-_aVpD!r-s9RoyoCb%Y6JO0;0X3QU7fathzEGDBP{Yc_B7VT*~jSgKcX%0 zz;Pv}50U|7=RmzI)y~Gf7Q_VjX&>?xWWx!aV}A3}f~_LycwM=vwYOwj|Bn6&n50Oa z0&qVjla#=9h!~sS`h{o|!Q-Th=WK9d@p9@v!#HkBM8Rzog?XW0YAB+hzEFPC$&LQnrHHYT*F`);*QR(S4pXY*VOPOV zb@i8TKL8l7J@{6vcGI>lEAni|v#m|@ZHGck?C=X6vNc%;!@pV^XE3XYHcqV*9*G~k zs)*;{jXnHSIzmm-!QEHZClg2ZonJ^L$X`01Ry&Nz<#-lxu0m;$e*UNWR-=weCAbx{ z6IybB@C8FM6YPL`g@JZ6cY?7eDt|%hp1_ZrOn9JE|Kq4dwHevXbv`3SIZMOO-XsOv zE@^0)DW@M^rOG8&uZna4$9SPKJ7}g+W<=^hy&?g2IvzfFbRKG{Udwtjdi8i4%~XqK6hvYJsKo89b_5WVn-Di0dV|e=CBL{@RO}@glX`tklEQ=TxQHsf{(nDD-(bvvf3xzkO zQl{SKv`yXQog!BHL$k|8Q>L9^K5z?j{~{wCl&Z0*W3C{=m=pDT01MPc)9-;5{haJQ z+nE)I<(C*=KJW7EPl%v!gPO6%2+e=BwP{iQ;I)=E95wR=mGYBoe3tqp^v(;|M5flf zN|vnUesRt&<~mVIb_Ie`?NmTa*7BswK5h*v@;SKBYtm14sWh*6T!!u zb$37%1dcYGQz9;vBwCjIv(V~w|1n}|;&GbN82#_Ou`OsI@&MV6y1Xa0y*{_Iy_i&O zd_mu4PI?y^&}#QoU`NDZGK&VRL4FdXEI5^Y>8C5)9^*_o2ohqZsOu{*PHrR0+TBjB z5wM1)bK=D@D!)!%p54W*6>p<^@vo_}FG@E{dWA;=QcSSxjxED2#ggmnr+iJK-|O0e zzJ)&X#=ejCUd=W$6WTn9;D$lOrg^{W=P$sxQ*?7#nfHnZfH0N@$>3^IJgdNV8@12H?stPk>OhzW zV`p1LFFo3t2uk2V1lIxGo>+`IM-8JxEHQ2ZpcBQ)BtxN`^KycsaC5M6#iB~eGj*{w z8~S)C@F=7f>*h5Q(Sbl0YKDVfGqLkzU(aAs-`Q&0iGVBxhG}aDXQj>?BT4d0n|V)I zr?O@_2bOo;(RUYfut==B!+9k~V3ERWvOVv3kxRlFdr$|=M2oI8%hQb#x1!jSrp$Fr z=lYpg}j2=Gzu0G2V@946AF2VaKo zdkxB%AVt`AYg*D$iL-|T?)O@5!P`|ArJT7DhQoD3k4?&0RiPH{_qZ4 zY5!T}!QRR4o}Wi!XU^5X+h3y;oBZBSXDes9gakyLpddqic|j)HBny0eePDF}(AeN! znB-C}d@Kp092n60#NDO)O_%+4X8-W$5RB35byQ1Be$jb`+40zo9cc+ z(Ttyb9k`3SUi=Nk1JfR!SkrSAK?|SrC}+VhD(|fUszi%Zr-d)x{`x&-MvGMUl`ro8 zy15IEr!Ds#FaGbg0_8&UL}zhJ*h^aX-U8J^3%M3|z$?d7gZbPV$D{kB7rFO?wF%IP zhCt^b<4Bwb7$vY<(wrP zkRzxE2>tO?AV2)tYc#;O3zToAnh*5u^Tj_Q3iNv68nxofD_(~_ZkbXt?DiTHenrc& zUv$O%+4$z1l|nT5tmZ#wF6r3+w{RFn3FtlWcL2>o^heXoX3|V78>GpnCvVTCi&@Lg z@kZq%lOl3jVWZ`b9O*$x(bm{{3#}5uKj0j{fy|SO<{+XTB%!O^>}&x+iU7iFK|B+w zjK4?$XeDH%biV7ym|Ujff`(t!lyq*K`#4`i?Oav1JiJ%Ov;@3gZ#F7(BTK0Y>qP@g zM|$eaoRKOt<#APR$l;&4S%#AT70UakDG4f;8qHDZ|Lr}$JzAIxKd1om@EHlvJGOov z0o_LDEOIlYJQTgU(~WYs0`zmHu(+_394Gc%Ufgw<9GIA>oOCC~J-}-DrYovD=Vs;# zho!DybyYK2+!LUxC8NY;p-q=+I4EGXd70Ek&Fk(T*GD`$Es zfl0E!Z{wOcEtp%&60g*or1XvM{|RAA|F8~Wu>5_ zy3Z4hm!~rimq2ygPm4_3q|c^u;O#;Gpa}&>)!_$O+EW7m)|LtLIJmjPZ;Hs zXJ{Lm3R$VL8wi#G`pHT9d6vL=XYq5t#G~~4v%7t{_7txz+^@Q$T`RP*o!ovNFQ+W= z@MMO6nn{$`QB)oH`x(%5fhLgr@UXgIVO5SGl+T-|XEHOPTB(8_heDxG$xnegLBdxQ z8tA@P|FMie0ghcOQyu({2Ybkd_#ZcGdb`heO9}u1tzS<}e4A^SOHjQIJaH?@eToic z8Lt`VCak$epZ#wxO#fa@Scg$0CN&1re0Tt9lZ+PJH)?B2Ojd^!z?sEl#1q(}_;OF4 z4pM}U&Mxgq2gdG$pAZ&Sr{4a)5-(gv@{H|Pjt9nH>%-f_W5B8wg+@JIFe2=;5jEgl zaT9XUW4+ViuyNcPlwI98h>wP=49psC}-;Nb}F~V8Ik5&fvmrrCPD@bSRBpRy1QJ@elc}Dwvu-^rvigA8WPw@&`{8r7v#k%^{xOj-TF@O2cd8{&i53 zZ89pC%|C5{o|p<$UNPx@JXZX$(`M2A?%!H`Q|}vhvgp-7rA1UbMRPZ!)2Nh79b(|0 z#aOXb?BN|Q1Ptq(J8215BLaxyWg?v)(%>RH%bx6Z9=)b zbMWYXYuNte`0F}Vv!$<+;ERkbi9bvk^b~{{<_k6q17bGg+Q?;B`5GrciKTmePUEtfo>+ds9=@&#Zg8R~6 z7aV-K)p2)%>vLQaI@-4z?k=_5EB08+@PdKlwz|R+wTqX%*(>pQiSh!4q|W=I5)l@u z3|#J&dK~{ClyJzK@17?$3VY^YT^p5qJbM(B>9s;{{^hj{*2<(n zT-D3hogi>n(k8yn~UxO-XHL1<6_T41&ITI?Dps{fVQ~S};Sr z7xBF^4kZYI~m%#a&YBz zdu0f6UAT@b>0U78cp2W1RrD$On@;yH_op9mB|!fw%wp>KtIF4wv6IWomQ(dBy~Vay z?~0!KyMM#OFXb9V5T9PRk|i`R?2DFjZVB9RC zo~_FBa%?gv{yu84vyZqPcj>8C@h{SuG0_Y<*^&6{96oIn*Q_xq?-aZSK#z4Ic^jm@ zJ)riJpnp}U$K*Gd9W+OAo8E8Z@);t=)Q#5dBZ%NQP|&_&lwYT0L>~v0l7VUv9944| zMc{Y2f`c?M3rb^Uz1Q#7`+vnO4)2M(Ca3(O=W(0w^B&AH2M4m{S}>4#+KC=MCP0A|p*jBSRaGNQtl2%&IEukeMn+^T&I&O5y80ugZh|^0=O7ryXG9KNqens^RbfOJ@#}brztR{r zVT6f06E}?hWTkd}&$?-f9|WgCZr~;!6v2Wi;vU)ki2+XTj112%dhKYtvCpv^b2UQ+LI#ONg>Io*?{JjmmV^ap=QF z&+${hnNa7!sp@gdBRUmAfN;|N)@k`xCrB4hF`TOA!@sLK_UQhka}%HN^fu!$JAt(+ zeDCG#@rVDae)s&kt*nvIMQklGq1i@Z(z&JV{i(p&>6L{X?GXPv7!@@k5ZnXm-=9@> zxVU=u&)Mh`)09q9&h4e2>sRW zt}#Yxf<5d=cE1_u?gI0^&aYe~1B9expahzn)-W3Qw})98Iri~;KPH>b$zcN{PHW<8 z(rPUJsnFj7XDM1vy!%U%qI$I4m*Q*6y|^e_kUs~_rBj+dw(frOHs@^S+u^CLbulrV z__E!;zQ}`fb`3r~Hsgc4CyfH?VT%8DjsA zAP}|r>0RS%313*c){+28u-X3@@d-B1YoGl8^A=zQ$$*sku=21aq1jLa2~y?kQYA3Lj^42I znx^TBxmIVtUnI*RFdS%FS<~2CBLLtT0Pqx)oOlY-$$-ejcy1Qwd_Du57=$0Drn{o( z{e3YiKM)&HIT+23D4rOW^PEN8ay)PMvs!K^gtoFCvjQf09*ri`dtkA8RVUjD*jg^V zK?TccO?GSg&)MGsu(->%{ip)mDvGl|^eJ-&gU0tM0QN(}5m%7kJiG$D(}U zzeU?2-3qg%I%HQN`Bx!UF=q|y&@UiX&1Vgax$q>A$BTT0iB{d4t(?&NKa6=W&O}DAWlS_VC@N3Ax=!3pzH-!y=P6FFpwNWT#JVPz~TwQ zTFOZEILt&xVVB|sl+17|r%?z1EPkq1&q1&1 z2$eS;$Mbd>%3W3dnnpS~bt`SR(-1jO*-%&3(8Y@9VpdS^F=;Gg>`zna3QZ~Gg};@$ zN4xm8AaEzIJ~d6Fk}V1xuyL)?&#X|R^&S@!*AQ<9GPjc+%Ti8k_#6E?i7Wfaqbq0+ zGK@&$T})f^GdjcbIw#LlHo?J%d?Lm-mqAac5Eg(KL=anlLd@gf@7iYRIf^1v_-1~m z>B^w?lFv!_9l%~%3XcXkOz0}WWE*)@`=!U}`DSVT#A8~sZ0X>nkZ~RpxEJu#zOV-B z#}FCtaOiupq9u>b<=dHT{SSQbG-g5=ZKhaAL~JTB&RbYS!!Pp*$ znwt3K9=@^ky-WVtexNwqeAbea6~SGG;tX~HDuZ9g4-0nV&HrS`!?FXo=MfsCPJN=p zq4?1)BU6`MK{Ff*zNz+%f5J}*1?A239e5q)T5fxvp5fl-tX#HnZ~164YEnHy zcIA1@xgSw-mP|-yLV$vX&?0v$F&Rbiv`;>RT%V z6#M=}^Jo2jln2FVHh_15Chx=*jy`yN8@?4@-3a~^cO(byoWms>b4hlE7ALPl#C-mG zp=rTufxmb?A4+*um;n+Si`ptLFE9ui9*_2~K!6;dRz^%TZ3Sz`ZFPZ7qbD8aLYjZQ z$_!Q`j1X-Fhwn{xL3L_nS8>^-OEp~-@RR|x8C}-v(gMQZ(7oJal~721{irFhPu;rJ zR%tu{bzn4We>|iWXpZ;1Clp+yejer&aE^aIORIEX(gvu^n}qf%VYut={8f`M(Dd4| zmEfzoKB+5kpnK~sD;tJa$|7|PEfu0UZ8lw~J!!D5L_aP4mSL!1^-Q0CksDG2ya~B% z3)wsg**N<40z!v&AV(AcIURR}?7+a0#eEyfL|1HZ$Y7n^luHA$nFLa$B9rTPr_|lp z^_{Bru>nu$pD6xn|5N;KE!oVv%2fFNe`w~8zF&Y&3Kfj38GbtaJsyg7_hNY r8GUjlm#ep->30z4*0cn@{)sQ3FAoyV1`r~!GJk!CQ&31KNy7gxS*L(C diff --git a/misc/minimal_zkVM.tex b/misc/minimal_zkVM.tex index d2c2c22dc..880928e9b 100644 --- a/misc/minimal_zkVM.tex +++ b/misc/minimal_zkVM.tex @@ -272,7 +272,7 @@ \subsubsection{POSEIDON} Compression of 16 field elements (two blocks of 8) into 8 field elements. $$ -\textbf{m}[\nu_C..\nu_C + 8] = \text{Poseidon}(\textbf{m}[\nu_A..\nu_A + 8] | \textbf{m}[\nu_B..\nu_B + 8]) + \textbf{m}[\nu_A..\nu_A + 8] +\textbf{m}[\nu_C..\nu_C + 8] = \text{Poseidon}(\textbf{m}[\nu_A..\nu_A + 8] \;\|\; \textbf{m}[\nu_B..\nu_B + 8]) + \textbf{m}[\nu_A..\nu_A + 8] $$ \vspace{2mm} @@ -558,7 +558,42 @@ \subsection{Poseidon table} We use poseidon \cite{poseidon1} over 16 field elements, in compression mode, i.e. for $\texttt{input} \in \Fp^{16}$ and interpreting the addition as coordinate-wise in $\Fp^8$: $$\text{poseidon\_compress}(\texttt{input}) = \text{poseidon}(\texttt{input})[..8] + \texttt{input}[..8]$$. -The Poseidon precompile receives 3 arguments: $\nu_A$, $\nu_B$, $\nu_C$ interpreted as memory pointers. 3 lookups (each of size 8) into the memory are used to fetch $\texttt{left}$ = $\textbf{m}[\nu_A..\nu_A + 8] \in \Fp^8$, $\texttt{right}$ = $\textbf{m}[\nu_B..\nu_B + 8] \in \Fp^8$, and $\texttt{res}$ = $\textbf{m}[\nu_C..\nu_C + 8] \in \Fp^8$. AIR constraints, of degree 9, assert that $\texttt{res} = \text{poseidon\_compress}(\texttt{left} | \texttt{right})$. Degree 3 is also an alternative, but with $\approx$ 2x more columns committed. +The Poseidon precompile receives 3 runtime arguments: $\nu_A$, $\nu_B$, $\nu_C$ interpreted as memory pointers. 3 lookups (each of size 8) into the memory are used to fetch $\texttt{left}$ = $\textbf{m}[\nu_A..\nu_A + 8] \in \Fp^8$, $\texttt{right}$ = $\textbf{m}[\nu_B..\nu_B + 8] \in \Fp^8$, and $\texttt{res}$ = $\textbf{m}[\nu_C..\nu_C + 8] \in \Fp^8$. AIR constraints, of degree 9, assert that $\texttt{res} = \text{poseidon\_compress}(\texttt{left} \;\|\; \texttt{right})$. Degree 3 is also an alternative, at the cost of more committed columns ($\approx 160$ vs. $\approx 100$). + +\subsubsection{Efficiently verrifying hash-based signatures} + +Hash-based signatures often rely on tweaks and public parameters (see \cite{ethereum_signatures}). + +We present two independent (and composable) optimizations of the poseidon precompile, when both the hash digest and the public parameters are composed of $n = 4$ field elements. We also assume each tweak occupies less than $n$ field elements (in practice 2 is enough). + +\vspace{5mm} + +\textbf{1. Short digest} + +We introduce a boolean flag $\texttt{flag\_short}$ that signals that only the first $\texttt{n}$ field elements of the output should be written to memory, at $\textbf{m}[\nu_C..\nu_C + \texttt{n}]$ (the remaining $8 - \texttt{n}$ ones are ignored), + +\vspace{5mm} + +\textbf{2. Hardcoded left first-half} + +We introduce a boolean flag $\texttt{flag\_left}$, alongside a compile-time parameter $\texttt{offset\_left}$: +$$\texttt{left} = \begin{cases} \textbf{m}[\nu_A..\nu_A + 8] & \text{if } \texttt{flag\_left} = 0 \text{ (default)} \\ \textbf{m}[\texttt{offset\_left}..\texttt{offset\_left} + 4] \;\|\; \textbf{m}[\nu_A..\nu_A + 4] & \text{if } \texttt{flag\_left} = 1 \end{cases}$$ + +\vspace{5mm} + +Both optimizations can be implemented (together), at the cost of 4 additional columns, and incrementing the degree of the constraints by 1. + +Both flags ($\texttt{flag\_short}$ and $\texttt{flag\_left}$) and the offset ($\texttt{offset\_left}$) can be encoded in a single (compile-time) parameter, as follows: +$$\texttt{AUX} = 1 + 2 \cdot \texttt{flag\_short} + 4 \cdot \texttt{flag\_left} + 8 \cdot \texttt{offset\_left} \cdot \texttt{flag\_left}$$ + +Soundness of this encoding (encoding multiple data into a single field element requires care, to avoid overflows that would break injectivity): +\begin{itemize} + \item both flags are asserted to be boolean by the AIR constraints + \item if $\texttt{flag\_left} = 0$: then $\texttt{offset\_left}$ is unconstrained: but it does not contribute to the encoding $\texttt{AUX}$, and it has no effect in the AIR logic. + \item if $\texttt{flag\_left} = 1$: then for the lookup into memory $\textbf{m}[\texttt{offset\_left}..\texttt{offset\_left} + 4]$ to be valid, $\texttt{offset\_left}$ must be smaller than the memory size $M \leq 2^{26} < p / 2$. As a result no overflow can occur in the encoding of $\texttt{AUX}$, which is thus injective. +\end{itemize} + +\textbf{Conclusion.} Every tuple $(\nu_A, \nu_B, \nu_C, \texttt{AUX})$ pushed by the execution table is faithfully pulled and decoded by the Poseidon table: the recovered triple $(\texttt{flag\_short}, \texttt{flag\_left}, \texttt{offset\_left})$ matches the one used at the call site, with one harmless exception: when $\texttt{flag\_left} = 0$, $\texttt{offset\_left}$ is left unconstrained, but this is inconsequential since it then has no effect at the AIR level. \subsection{Extension op table} From 29eabc588594925848c4391d1371f81b80807a51 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 12 Apr 2026 01:09:35 +0200 Subject: [PATCH 40/66] naming --- minimal_zkVM.pdf | Bin 367977 -> 367673 bytes misc/minimal_zkVM.tex | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/minimal_zkVM.pdf b/minimal_zkVM.pdf index d14bebb42ec3d418d66cd5fabf843c97dde396fb..ca28e7d87f01e395e3e353931ee846064ccf42e7 100644 GIT binary patch delta 31750 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 delta 32106 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 Date: Tue, 14 Apr 2026 13:33:05 +0200 Subject: [PATCH 41/66] n_sigs != 0 --- crates/rec_aggregation/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index 66e989031..38fb09eae 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -28,7 +28,7 @@ def main(): data_buf = Array(INPUT_DATA_SIZE_PADDED) hint_witness("input_data", data_buf) n_sigs = data_buf[0] - assert 1 < n_sigs + assert n_sigs != 0 assert n_sigs - 1 < MAX_N_SIGS pubkeys_hash_expected = data_buf + 1 message = pubkeys_hash_expected + DIGEST_LEN From 7228a574bcb3920f4b3038907893f1e1ef854790 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Wed, 15 Apr 2026 13:46:28 +0200 Subject: [PATCH 42/66] skip the last 5 sumchecks of logup-GKR (send data in clear instead) --- crates/rec_aggregation/recursion.py | 35 ++++++++++++++-------- crates/rec_aggregation/src/compilation.rs | 6 +++- crates/rec_aggregation/utils.py | 7 ++++- crates/sub_protocols/src/lib.rs | 1 + crates/sub_protocols/src/quotient_gkr.rs | 36 ++++++++++++++--------- sig. | 23 +++++++++++++++ 6 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 sig. diff --git a/crates/rec_aggregation/recursion.py b/crates/rec_aggregation/recursion.py index e63cb473a..d87e09c55 100644 --- a/crates/rec_aggregation/recursion.py +++ b/crates/rec_aggregation/recursion.py @@ -4,6 +4,9 @@ N_TABLES = N_TABLES_PLACEHOLDER +LOGUP_GKR_N_VARS_TO_SEND_COEFFS = LOGUP_GKR_N_VARS_TO_SEND_COEFFS_PLACEHOLDER +LOGUP_GKR_N_COEFFS_SENT = 2**LOGUP_GKR_N_VARS_TO_SEND_COEFFS + MIN_LOG_N_ROWS_PER_TABLE = MIN_LOG_N_ROWS_PER_TABLE_PLACEHOLDER MAX_LOG_N_ROWS_PER_TABLE = MAX_LOG_N_ROWS_PER_TABLE_PLACEHOLDER MIN_LOG_MEMORY_SIZE = MIN_LOG_MEMORY_SIZE_PLACEHOLDER @@ -673,27 +676,33 @@ def fingerprint_bytecode(instr_evals, eval_on_pc, logup_alphas_eq_poly): def verify_gkr_quotient(fs: Mut, n_vars): - fs, nums = fs_receive_ef_inlined(fs, 2) - fs, denoms = fs_receive_ef_inlined(fs, 2) - - q1 = div_extension_ret(nums, denoms) - q2 = div_extension_ret(nums + DIM, denoms + DIM) - quotient = add_extension_ret(q1, q2) + fs, nums = fs_receive_ef_inlined(fs, LOGUP_GKR_N_COEFFS_SENT) + fs, denoms = fs_receive_ef_inlined(fs, LOGUP_GKR_N_COEFFS_SENT) + + initial_quotients = Array(LOGUP_GKR_N_COEFFS_SENT * DIM) + for k in unroll(0, LOGUP_GKR_N_COEFFS_SENT): + div_extension(nums + k * DIM, denoms + k * DIM, initial_quotients + k * DIM) + debug_assert(NUM_REPEATED_ONES <= LOGUP_GKR_N_COEFFS_SENT) + debug_assert(LOGUP_GKR_N_COEFFS_SENT % NUM_REPEATED_ONES == 0) + quotient: Mut = ZERO_VEC_PTR + for k in unroll(0, LOGUP_GKR_N_COEFFS_SENT / NUM_REPEATED_ONES): + quotient = add_extension_ret(quotient, sum_continuous_ef(initial_quotients + k * NUM_REPEATED_ONES * DIM, NUM_REPEATED_ONES)) points = Array(n_vars) claims_num = Array(n_vars) claims_den = Array(n_vars) - fs, points[0] = fs_sample_ef(fs) + fs, initial_point = fs_sample_many_ef(fs, LOGUP_GKR_N_VARS_TO_SEND_COEFFS) + points[LOGUP_GKR_N_VARS_TO_SEND_COEFFS - 1] = initial_point - point_poly_eq = poly_eq_extension(points[0], 1) + point_poly_eq = poly_eq_extension(initial_point, LOGUP_GKR_N_VARS_TO_SEND_COEFFS) - first_claim_num = dot_product_ee_ret(nums, point_poly_eq, 2) - first_claim_den = dot_product_ee_ret(denoms, point_poly_eq, 2) - claims_num[0] = first_claim_num - claims_den[0] = first_claim_den + first_claim_num = dot_product_ee_ret(nums, point_poly_eq, LOGUP_GKR_N_COEFFS_SENT) + first_claim_den = dot_product_ee_ret(denoms, point_poly_eq, LOGUP_GKR_N_COEFFS_SENT) + claims_num[LOGUP_GKR_N_VARS_TO_SEND_COEFFS - 1] = first_claim_num + claims_den[LOGUP_GKR_N_VARS_TO_SEND_COEFFS - 1] = first_claim_den - for i in range(1, n_vars): + for i in range(LOGUP_GKR_N_VARS_TO_SEND_COEFFS, n_vars): fs, points[i], claims_num[i], claims_den[i] = verify_gkr_quotient_step(fs, i, points[i - 1], claims_num[i - 1], claims_den[i - 1]) return ( diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 000ad83a4..ff43dc183 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -8,7 +8,7 @@ use lean_vm::*; use std::collections::{BTreeMap, HashMap}; use std::path::Path; use std::sync::OnceLock; -use sub_protocols::{min_stacked_n_vars, total_whir_statements}; +use sub_protocols::{N_VARS_TO_SEND_GKR_COEFFS, min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W}; @@ -183,6 +183,10 @@ fn build_replacements( "MAX_NUM_VARIABLES_TO_SEND_COEFFS_PLACEHOLDER".to_string(), MAX_NUM_VARIABLES_TO_SEND_COEFFS.to_string(), ); + replacements.insert( + "LOGUP_GKR_N_VARS_TO_SEND_COEFFS_PLACEHOLDER".to_string(), + N_VARS_TO_SEND_GKR_COEFFS.to_string(), + ); replacements.insert( "WHIR_INITIAL_FOLDING_FACTOR_PLACEHOLDER".to_string(), WHIR_INITIAL_FOLDING_FACTOR.to_string(), diff --git a/crates/rec_aggregation/utils.py b/crates/rec_aggregation/utils.py index b20cc608a..2d1a70341 100644 --- a/crates/rec_aggregation/utils.py +++ b/crates/rec_aggregation/utils.py @@ -304,9 +304,14 @@ def mul_base_extension_ret(a, b): @inline def div_extension_ret(n, d): quotient = Array(DIM) - dot_product_ee(d, quotient, n) + div_extension(n, d, quotient) return quotient +@inline +def div_extension(n, d, res): + dot_product_ee(d, res, n) + return + @inline def sub_extension(a, b, c): diff --git a/crates/sub_protocols/src/lib.rs b/crates/sub_protocols/src/lib.rs index 008470555..4921c7734 100644 --- a/crates/sub_protocols/src/lib.rs +++ b/crates/sub_protocols/src/lib.rs @@ -11,3 +11,4 @@ mod quotient_gkr; pub use quotient_gkr::*; pub(crate) const MIN_VARS_FOR_PACKING: usize = 8; +pub const N_VARS_TO_SEND_GKR_COEFFS: usize = 5; diff --git a/crates/sub_protocols/src/quotient_gkr.rs b/crates/sub_protocols/src/quotient_gkr.rs index f6d64e839..e0f8ccb9e 100644 --- a/crates/sub_protocols/src/quotient_gkr.rs +++ b/crates/sub_protocols/src/quotient_gkr.rs @@ -2,7 +2,7 @@ use std::ops::Mul; use backend::*; -use crate::MIN_VARS_FOR_PACKING; +use crate::{MIN_VARS_FOR_PACKING, N_VARS_TO_SEND_GKR_COEFFS}; /* GKR to compute sum of fractions. @@ -13,6 +13,8 @@ pub fn prove_gkr_quotient>>( numerators: &MleRef<'_, EF>, denominators: &MleRef<'_, EF>, ) -> (EF, MultilinearPoint, EF, EF) { + assert!(numerators.n_vars() == denominators.n_vars()); + assert!(numerators.n_vars() > N_VARS_TO_SEND_GKR_COEFFS); assert!(numerators.is_packed() == denominators.is_packed()); let mut layers: Vec<(Mle<'_, EF>, Mle<'_, EF>)> = vec![(numerators.soft_clone().into(), denominators.soft_clone().into())]; @@ -26,23 +28,23 @@ pub fn prove_gkr_quotient>>( prev_denominators.unpack().as_owned_or_clone().into(), ) } - if prev_numerators.n_vars() == 1 { + if prev_numerators.n_vars() <= N_VARS_TO_SEND_GKR_COEFFS { break; } let (new_numerators, new_denominators) = sum_quotients(prev_numerators.by_ref(), prev_denominators.by_ref()); layers.push((new_numerators.into(), new_denominators.into())); } - let (last_numerators, last_denominators) = layers.pop().unwrap(); - let last_numerators = last_numerators.as_owned().unwrap(); - let last_numerators = last_numerators.as_extension().unwrap(); - let last_denominators = last_denominators.as_owned().unwrap(); - let last_denominators = last_denominators.as_extension().unwrap(); + let (last_numerators_mle, last_denominators_mle) = layers.pop().unwrap(); + let last_numerators_owned = last_numerators_mle.unpack().as_owned_or_clone(); + let last_denominators_owned = last_denominators_mle.unpack().as_owned_or_clone(); + let last_numerators = last_numerators_owned.as_extension().unwrap(); + let last_denominators = last_denominators_owned.as_extension().unwrap(); prover_state.add_extension_scalars(last_numerators); prover_state.add_extension_scalars(last_denominators); - let quotient = last_numerators[0] / last_denominators[0] + last_numerators[1] / last_denominators[1]; + let quotient = compute_quotient(last_numerators, last_denominators); - let mut point = MultilinearPoint(vec![prover_state.sample()]); + let mut point = MultilinearPoint(prover_state.sample_vec(N_VARS_TO_SEND_GKR_COEFFS)); let mut claims = vec![last_numerators.evaluate(&point), last_denominators.evaluate(&point)]; for (nums, denoms) in layers.iter().rev() { @@ -208,13 +210,15 @@ pub fn verify_gkr_quotient>>( verifier_state: &mut impl FSVerifier, n_vars: usize, ) -> Result<(EF, MultilinearPoint, EF, EF), ProofError> { - let last_nums = verifier_state.next_extension_scalars_vec(2)?; - let last_dens = verifier_state.next_extension_scalars_vec(2)?; - let quotient = last_nums[0] / last_dens[0] + last_nums[1] / last_dens[1]; - let mut point = MultilinearPoint(vec![verifier_state.sample()]); + assert!(n_vars > N_VARS_TO_SEND_GKR_COEFFS); + let send_len = 1 << N_VARS_TO_SEND_GKR_COEFFS; + let last_nums = verifier_state.next_extension_scalars_vec(send_len)?; + let last_dens = verifier_state.next_extension_scalars_vec(send_len)?; + let quotient: EF = compute_quotient(&last_nums, &last_dens); + let mut point = MultilinearPoint(verifier_state.sample_vec(N_VARS_TO_SEND_GKR_COEFFS)); let mut claims_num = last_nums.evaluate(&point); let mut claims_den = last_dens.evaluate(&point); - for i in 1..n_vars { + for i in N_VARS_TO_SEND_GKR_COEFFS..n_vars { (point, claims_num, claims_den) = verify_gkr_quotient_step(verifier_state, i, &point, claims_num, claims_den)?; } Ok((quotient, point, claims_num, claims_den)) @@ -291,6 +295,10 @@ where (new_numerators, new_denominators) } +fn compute_quotient>>(numerators: &[EF], denominators: &[EF]) -> EF { + numerators.iter().zip(denominators).map(|(&n, &d)| n / d).sum() +} + #[cfg(test)] mod tests { use super::*; diff --git a/sig. b/sig. new file mode 100644 index 000000000..723d51e23 --- /dev/null +++ b/sig. @@ -0,0 +1,23 @@ + +pub use leansig::signature::SignatureScheme; + + +#[test] +fn test_xmss_signature_slow_keygen() { + let activation_epoch = 111; + let num_active_epochs = 39; + let slot = 124; + let mut rng: StdRng = StdRng::seed_from_u64(0); + + let (pub_key, secret_key) = LeanSigScheme::key_gen(&mut rng, activation_epoch as usize, num_active_epochs as usize); + let n = 1_000; + let mut signing_duration = std::time::Duration::default(); + for _ in 0..n { + let msg: [u8; MESSAGE_LENGTH] = rand::RngExt::random(&mut rng); + let time = std::time::Instant::now(); + let signature = LeanSigScheme::sign(&secret_key, slot, &msg).unwrap(); + signing_duration += time.elapsed(); + xmss_verify(&pub_key, slot, &msg, &signature).unwrap(); + } + println!("Average signing time: {} ms", signing_duration.as_millis() as f64 / n as f64); +} From 4424e9701924f0e4a7a0aaa0180864b6bd016051 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Tue, 28 Apr 2026 23:28:23 +0200 Subject: [PATCH 43/66] bench numbers --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3c24ad60d..604ecf915 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,7 +98,7 @@ fn main() { raw_xmss: 25, children: vec![ AggregationTopology { - raw_xmss: 1500, + raw_xmss: 1550, children: vec![], log_inv_rate: 1, }; @@ -110,7 +110,7 @@ fn main() { raw_xmss: 0, children: vec![ AggregationTopology { - raw_xmss: 750, + raw_xmss: 775, children: vec![], log_inv_rate: 2, }; From 4e58a2384b449becfc19692bb9136fcd0f94ab33 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 30 Apr 2026 11:58:46 +0200 Subject: [PATCH 44/66] latex --- misc/minimal_zkVM.tex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/misc/minimal_zkVM.tex b/misc/minimal_zkVM.tex index f6fabdefc..c78e76acb 100644 --- a/misc/minimal_zkVM.tex +++ b/misc/minimal_zkVM.tex @@ -38,6 +38,7 @@ \newtheorem{lemma}{Lemma} \title{Minimal zkVM for Lean Ethereum (draft 0.6.0)} +\author{} \date{} \begin{document} @@ -346,7 +347,7 @@ \subsubsection{Loops}\label{loops} \subsubsection{Range checks} -\fbox{It's possible to check that a given memory cell is smaller than some value $t$ (for $t \leq 2^{16})$ in 3 cycles.} +\fbox{It's possible to check that a memory cell is smaller than $t$ (for $t \leq 2^{16}$) in 3 cycles.} We denote by \textbf{m}[\textbf{fp} + $x$] the memory cell for which we want to ensure \textbf{m}[\textbf{fp} + $x$] $< t$. We also denote by \textbf{m}[\textbf{fp} + $i$], \textbf{m}[\textbf{fp} + $j$] and \textbf{m}[\textbf{fp} + $k$] 3 auxiliary memory cells (that have not been used yet). @@ -393,11 +394,10 @@ \subsubsection{Switch statements} Suppose we want a different logic depending on the value $x$ of a given memory cell, where $x$ is known to be $< k$ (if the value $x$ comes from a "hint", don't forget to range-check it). -Each of the $k$ different value leads to a different branch at runtime, represented by a block of code. We want to jump to the correct block of code depending on $x$. -One efficient implementation consists in placing our blocks of code at regular intervals, and to jump to a $a+ b.x$, where $a$ is the offset of the first block of code (in case $x = 0$), and $b$ is the distance between two consecutive blocks. -\newline -\newline -Example: During XMSS verification, for each of the $v$ chains, we need to hash a pre-image, a number of times depending on the encoding, but known to be $< w$. Here $k = w$, and the $i-th$ block of code we could jump to will execute $i$ times the hash function (unrolled loop). +Each of the $k$ different values leads to a different branch at runtime, represented by a block of code. We want to jump to the correct block of code depending on $x$. +One efficient implementation consists in placing our blocks of code at regular intervals, and to jump to $a + b \cdot x$, where $a$ is the offset of the first block of code (in case $x = 0$), and $b$ is the distance between two consecutive blocks. + +Example: During XMSS verification, for each of the $v$ chains, we need to hash a pre-image, a number of times depending on the encoding, but known to be $< w$. Here $k = w$, and the $i$-th block of code we could jump to will execute $i$ times the hash function (unrolled loop). \section{Proving system}\label{sec:proving} @@ -673,7 +673,7 @@ \subsubsection{Memory lookups} \end{itemize} \subsubsection{Bus interaction} -On rows where $\texttt{activation\_flag} = 1$ (i.e.\ $\texttt{start} = 1$ and $\texttt{active} = 1$), the table \textsc{Pull}s $(\texttt{aux}, \text{idx}_A, \text{idx}_B, \text{idx}_R)$ from the precompile bus. The execution table \textsc{Push}es a matching tuple for each EXTENSION\_OP instruction. +On rows where $\texttt{activation\_flag} = 1$ (i.e.\ $\texttt{start} = 1$ and $\texttt{active} = 1$), the table \textsc{Pulls} the tuple $(\texttt{aux}, \text{idx}_A, \text{idx}_B, \text{idx}_R)$ from the precompile bus. The execution table \textsc{Pushes} a matching tuple for each EXTENSION\_OP instruction. The $\texttt{aux}$ encoding ensures both the mode and the length are bound to the bus data. Since $\texttt{is\_be}$, $\texttt{flag\_add}$, $\texttt{flag\_mul}$, and $\texttt{flag\_poly\_eq}$ are constrained to be boolean, and since the length is constrained to be $\leq 2^{20}$ (by Lemma~\ref{lem:len-bound}), no overflow can occur modulo $p$ and the $\texttt{aux}$ value is unique for each combination of parameters, which enforces that all values of PRECOMPILE\_DATA sent by the execution table are correctly received by the extension\_op table. From a29ca490600713d873b5de4f8633559100308246 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 30 Apr 2026 14:02:14 +0200 Subject: [PATCH 45/66] small improvements --- .../lean_compiler/src/a_simplify_lang/mod.rs | 27 ++++++++--------- .../lean_compiler/src/instruction_encoder.rs | 8 ++--- .../src/parser/parsers/function.rs | 12 ++------ crates/lean_prover/src/test_zkvm.rs | 8 ----- crates/lean_prover/src/trace_gen.rs | 30 +++++++++++-------- crates/lean_vm/src/tables/poseidon_16/mod.rs | 6 ++++ 6 files changed, 41 insertions(+), 50 deletions(-) diff --git a/crates/lean_compiler/src/a_simplify_lang/mod.rs b/crates/lean_compiler/src/a_simplify_lang/mod.rs index 8e850c878..66fbd7554 100644 --- a/crates/lean_compiler/src/a_simplify_lang/mod.rs +++ b/crates/lean_compiler/src/a_simplify_lang/mod.rs @@ -6,9 +6,9 @@ use crate::{ }; use backend::PrimeCharacteristicRing; use lean_vm::{ - Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, - POSEIDON16_HALF_NAME, POSEIDON16_HARDCODED_LEFT_4_NAME, PrecompileArgs, PrecompileCompTimeArgs, SourceLocation, - Table, TableT, + ALL_POSEIDON16_NAMES, Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, + POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_NAME, POSEIDON16_HARDCODED_LEFT_4_NAME, PrecompileArgs, + PrecompileCompTimeArgs, SourceLocation, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -2256,23 +2256,20 @@ fn simplify_lines( } // Special handling for poseidon16 precompile (4 variants) - if function_name == Table::poseidon16().name() - || function_name == POSEIDON16_HALF_NAME - || function_name == POSEIDON16_HARDCODED_LEFT_4_NAME - || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME - { - let half_output = function_name == POSEIDON16_HALF_NAME - || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME; - let is_hardcoded = function_name == POSEIDON16_HARDCODED_LEFT_4_NAME - || function_name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME; + if ALL_POSEIDON16_NAMES.contains(&function_name.as_str()) { if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" )); } - let expected_args = if is_hardcoded { 4 } else { 3 }; + let half_output = [POSEIDON16_HALF_NAME, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME] + .contains(&function_name.as_str()); + let is_hardcoded_left = + [POSEIDON16_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME] + .contains(&function_name.as_str()); + let expected_args = if is_hardcoded_left { 4 } else { 3 }; if args.len() != expected_args { - let signature = if is_hardcoded { + let signature = if is_hardcoded_left { "(ptr_a, ptr_b, ptr_res, offset)" } else { "(ptr_a, ptr_b, ptr_res)" @@ -2286,7 +2283,7 @@ fn simplify_lines( .iter() .map(|arg| simplify_expr(ctx, state, const_malloc, arg, &mut res)) .collect::, _>>()?; - let hardcoded_left_4 = if is_hardcoded { + let hardcoded_left_4 = if is_hardcoded_left { Some(simplified_args[3].as_constant().ok_or_else(|| { format!( "{function_name}: offset argument must be a compile-time constant, at {location}" diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index 7009a4193..6d3f19c63 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -52,12 +52,12 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { half_output, hardcoded_left_4, } => { - let flag = hardcoded_left_4.is_some() as usize; - let offset = hardcoded_left_4.unwrap_or(0); + let flag_left = hardcoded_left_4.is_some() as usize; + let offset_left = hardcoded_left_4.unwrap_or(0); POSEIDON_PRECOMPILE_DATA + POSEIDON_HALF_OUTPUT_SHIFT * (*half_output as usize) - + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag - + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset + + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag_left + + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_left } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index 8868a2a91..576e5af60 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -8,10 +8,7 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{ - CUSTOM_HINTS, ExtensionOpMode, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_NAME, - POSEIDON16_HARDCODED_LEFT_4_NAME, POSEIDON16_NAME, -}; +use lean_vm::{ALL_POSEIDON16_NAMES, CUSTOM_HINTS, ExtensionOpMode}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -36,12 +33,7 @@ fn is_reserved_function_name(name: &str) -> bool { if RESERVED_FUNCTION_NAMES.contains(&name) || CUSTOM_HINTS.iter().any(|hint| hint.name() == name) { return true; } - // Check precompile names (poseidon16, extension_op functions) - if name == POSEIDON16_NAME - || name == POSEIDON16_HALF_NAME - || name == POSEIDON16_HARDCODED_LEFT_4_NAME - || name == POSEIDON16_HALF_HARDCODED_LEFT_4_NAME - { + if ALL_POSEIDON16_NAMES.contains(&name) { return true; } if ExtensionOpMode::from_name(name).is_some() { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index 030fe20a5..7d79d1513 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -103,14 +103,6 @@ def main(): F::from_usize(444), ]); - // Hardcoded-left-4 test data, placed at public_input[1496..1520]. - // - The 4-element data digest lives at offset 1496..1500 (the "left" pointer). - // - The 4-element hardcoded prefix lives at offset 1500..1504. - // The hardcoded variant computes - // Poseidon(prefix(4) || data(4), original_input[8..16]) - // i.e. only 4 elements are read at the left pointer (matching the new convention - // where flag_hardcoded_left_4 = 1 reads m[index_a..index_a+4] for the second half - // of the left input and m[offset..offset+4] for the first half). let hardcoded_data: [F; 4] = rng.random(); let hardcoded_prefix: [F; 4] = rng.random(); public_input[1496..1500].copy_from_slice(&hardcoded_data); diff --git a/crates/lean_prover/src/trace_gen.rs b/crates/lean_prover/src/trace_gen.rs index e7acc955b..1801a5b62 100644 --- a/crates/lean_prover/src/trace_gen.rs +++ b/crates/lean_prover/src/trace_gen.rs @@ -109,21 +109,25 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul fill_trace_poseidon_16(&mut poseidon_trace.columns); // For half_output rows, override last 4 output columns with actual memory values - // (the AIR doesn't constrain them, but the lookup checks against memory) + // (the AIR doesn't constrain them, but the lookup checks against memory). { - let half_output_col = poseidon_trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].clone(); - let res_col = poseidon_trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].clone(); - for j in HALF_DIGEST_LEN..DIGEST_LEN { - poseidon_trace.columns[POSEIDON_16_COL_OUTPUT_START + j] - .par_iter_mut() - .zip(&half_output_col) - .zip(&res_col) - .for_each(|((out, &half), &res)| { - if half == F::ONE { - *out = memory_padded[res.to_usize() + j]; + let split = POSEIDON_16_COL_OUTPUT_START + HALF_DIGEST_LEN; + let (left, right) = poseidon_trace.columns.split_at_mut(split); + let half_output_col = &left[POSEIDON_16_COL_FLAG_HALF_OUTPUT]; + let res_col = &left[POSEIDON_16_COL_INDEX_INPUT_RES]; + let output_cols: &mut [Vec; HALF_DIGEST_LEN] = (&mut right[..HALF_DIGEST_LEN]).try_into().unwrap(); + + transposed_par_iter_mut(output_cols) + .zip(half_output_col) + .zip(res_col) + .for_each(|((row, &half), &res)| { + if half == F::ONE { + let base = res.to_usize() + HALF_DIGEST_LEN; + for j in 0..HALF_DIGEST_LEN { + *row[j] = memory_padded[base + j]; } - }); - } + } + }); } let extension_op_trace = traces.get_mut(&Table::extension_op()).unwrap(); diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 76aa750cd..8249848d9 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -125,6 +125,12 @@ pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; pub const POSEIDON16_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_hardcoded_left_4"; pub const POSEIDON16_HALF_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_half_hardcoded_left_4"; +pub const ALL_POSEIDON16_NAMES: [&str; 4] = [ + POSEIDON16_NAME, + POSEIDON16_HALF_NAME, + POSEIDON16_HARDCODED_LEFT_4_NAME, + POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, +]; pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] From 9da912b4b6c09fbef28890d2da5d84812425769f Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 30 Apr 2026 14:58:31 +0200 Subject: [PATCH 46/66] naming --- .../lean_compiler/src/a_simplify_lang/mod.rs | 4 ++-- .../lean_compiler/src/instruction_encoder.rs | 8 ++++---- crates/lean_vm/src/isa/instruction.rs | 12 +++++------ crates/lean_vm/src/tables/poseidon_16/mod.rs | 20 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/lean_compiler/src/a_simplify_lang/mod.rs b/crates/lean_compiler/src/a_simplify_lang/mod.rs index 66fbd7554..9967e326c 100644 --- a/crates/lean_compiler/src/a_simplify_lang/mod.rs +++ b/crates/lean_compiler/src/a_simplify_lang/mod.rs @@ -2283,7 +2283,7 @@ fn simplify_lines( .iter() .map(|arg| simplify_expr(ctx, state, const_malloc, arg, &mut res)) .collect::, _>>()?; - let hardcoded_left_4 = if is_hardcoded_left { + let hardcoded_offset_left = if is_hardcoded_left { Some(simplified_args[3].as_constant().ok_or_else(|| { format!( "{function_name}: offset argument must be a compile-time constant, at {location}" @@ -2298,7 +2298,7 @@ fn simplify_lines( res: simplified_args[2].clone(), data: PrecompileCompTimeArgs::Poseidon16 { half_output, - hardcoded_left_4, + hardcoded_offset_left, }, })); continue; diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index 6d3f19c63..1060e3be4 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -50,14 +50,14 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { let precompile_data = match &precompile.data { PrecompileCompTimeArgs::Poseidon16 { half_output, - hardcoded_left_4, + hardcoded_offset_left, } => { - let flag_left = hardcoded_left_4.is_some() as usize; - let offset_left = hardcoded_left_4.unwrap_or(0); + let flag_left = hardcoded_offset_left.is_some() as usize; + let hardcoded_offset_left_val = hardcoded_offset_left.unwrap_or(0); POSEIDON_PRECOMPILE_DATA + POSEIDON_HALF_OUTPUT_SHIFT * (*half_output as usize) + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag_left - + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_left + + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * hardcoded_offset_left_val } PrecompileCompTimeArgs::ExtensionOp { size, mode } => { assert!(*size >= 1, "invalid extension_op size={size}"); diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index 0a62fae35..f0b7ef212 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -65,9 +65,9 @@ pub struct PrecompileArgs { pub enum PrecompileCompTimeArgs { Poseidon16 { half_output: bool, - /// `None` = standard variant. `Some(offset)` = the first 4 elements of the left input - /// are read from `memory[offset..offset+4]` instead of `memory[index_left..index_left+4]`. - hardcoded_left_4: Option, + // hardcoded_offset_left = None: left_input = m[arg_a..arg_a+8] + // hardcoded_offset_left = Some(offset_left): left_input = m[offset_left..offset_left+4] | m[arg_a..arg_a+4] (arg_a is the first runtime parameter) + hardcoded_offset_left: Option, }, ExtensionOp { size: S, @@ -87,10 +87,10 @@ impl PrecompileCompTimeArgs { match self { Self::Poseidon16 { half_output, - hardcoded_left_4, + hardcoded_offset_left: hardcoded_left_4, } => PrecompileCompTimeArgs::Poseidon16 { half_output, - hardcoded_left_4: hardcoded_left_4.map(&mut f), + hardcoded_offset_left: hardcoded_left_4.map(&mut f), }, Self::ExtensionOp { size, mode } => PrecompileCompTimeArgs::ExtensionOp { size: f(size), mode }, } @@ -252,7 +252,7 @@ impl Display for PrecompileArgs { match data { PrecompileCompTimeArgs::Poseidon16 { half_output, - hardcoded_left_4, + hardcoded_offset_left: hardcoded_left_4, } => match (*half_output, hardcoded_left_4) { (false, None) => write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res})"), (true, None) => write!(f, "{POSEIDON16_NAME}({arg_0}, {arg_1}, {res}, half)"), diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 8249848d9..7d8037947 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -221,7 +221,7 @@ impl TableT for Poseidon16Precompile { ) -> Result<(), RunnerError> { let PrecompileCompTimeArgs::Poseidon16 { half_output, - hardcoded_left_4, + hardcoded_offset_left, } = args else { unreachable!("Poseidon16 table called with non-Poseidon16 args"); @@ -229,13 +229,13 @@ impl TableT for Poseidon16Precompile { let trace = ctx.traces.get_mut(&self.table()).unwrap(); let arg_a_usize = arg_a.to_usize(); - let flag_hardcoded = hardcoded_left_4.is_some(); + let flag_hardcoded = hardcoded_offset_left.is_some(); // Convention: - // flag = 0: left input = m[arg_a..arg_a+8] (split as [arg_a..+4], [arg_a+4..+8]) - // flag = 1: left input = m[offset..offset+4] | m[arg_a..arg_a+4] - // (i.e. arg_a now points to a 4-element data digest, and the first 4 - // elements come from the hardcoded prefix at `offset`) - let left_first_addr = hardcoded_left_4.unwrap_or(arg_a_usize); + // flag_hardcoded = 0: left input = m[arg_a..arg_a+8] (split as [arg_a..+4], [arg_a+4..+8]) + // flag_hardcoded = 1: left input = m[offset..offset+4] | m[arg_a..arg_a+4] + // (i.e. arg_a now points to a 4-element data digest, and the first 4 + // elements come from the hardcoded prefix at `offset`) + let left_first_addr = hardcoded_offset_left.unwrap_or(arg_a_usize); let left_second_addr = if flag_hardcoded { arg_a_usize } else { @@ -259,14 +259,14 @@ impl TableT for Poseidon16Precompile { ctx.memory.set_slice(index_res_a.to_usize(), &output)?; } - let offset_hardcoded = hardcoded_left_4.unwrap_or(0); + let hardcoded_offset_left_val = hardcoded_offset_left.unwrap_or(0); trace.columns[POSEIDON_16_COL_FLAG].push(F::ONE); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); trace.columns[POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4].push(if flag_hardcoded { F::ONE } else { F::ZERO }); - trace.columns[POSEIDON_16_COL_OFFSET_HARDCODED].push(F::from_usize(offset_hardcoded)); + trace.columns[POSEIDON_16_COL_OFFSET_HARDCODED].push(F::from_usize(hardcoded_offset_left_val)); trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST].push(F::from_usize(left_first_addr)); trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND].push(F::from_usize(left_second_addr)); for (i, value) in input.iter().enumerate() { @@ -277,7 +277,7 @@ impl TableT for Poseidon16Precompile { let precompile_data = POSEIDON_PRECOMPILE_DATA + POSEIDON_HALF_OUTPUT_SHIFT * (half_output as usize) + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * (flag_hardcoded as usize) - + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_hardcoded; + + POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * hardcoded_offset_left_val; trace.columns[POSEIDON_16_COL_PRECOMPILE_DATA].push(F::from_usize(precompile_data)); // the rest of the trace is filled at the end of the execution (to get parallelism + SIMD) From dc98f613b52c3e5f6be444dd29d4c774fe34bdb6 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 30 Apr 2026 15:08:25 +0200 Subject: [PATCH 47/66] naming --- crates/lean_vm/src/tables/poseidon_16/mod.rs | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 7d8037947..64eecd53d 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -111,8 +111,8 @@ pub const POSEIDON_16_COL_FLAG: ColIndex = 0; pub const POSEIDON_16_COL_INDEX_INPUT_RIGHT: ColIndex = 1; pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 2; pub const POSEIDON_16_COL_FLAG_HALF_OUTPUT: ColIndex = 3; -pub const POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4: ColIndex = 4; -pub const POSEIDON_16_COL_OFFSET_HARDCODED: ColIndex = 5; +pub const POSEIDON_16_COL_FLAG_HARDCODED_LEFT: ColIndex = 4; +pub const POSEIDON_16_COL_OFFSET_LEFT_HARDCODED: ColIndex = 5; pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST: ColIndex = 6; pub const POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND: ColIndex = 7; pub const POSEIDON_16_COL_INPUT_START: ColIndex = 8; @@ -198,8 +198,8 @@ impl TableT for Poseidon16Precompile { *perm.index_b = F::from_usize(zero_vec_ptr); *perm.index_res = F::from_usize(null_hash_ptr); *perm.flag_half_output = F::ZERO; - *perm.flag_hardcoded_left_4 = F::ZERO; - *perm.offset_hardcoded = F::ZERO; + *perm.flag_hardcoded_left = F::ZERO; + *perm.offset_hardcoded_left = F::ZERO; *perm.effective_index_left_first = F::from_usize(zero_vec_ptr); *perm.effective_index_left_second = F::from_usize(zero_vec_ptr + HALF_DIGEST_LEN); // Non-committed columns @@ -265,8 +265,8 @@ impl TableT for Poseidon16Precompile { trace.columns[POSEIDON_16_COL_INDEX_INPUT_RIGHT].push(arg_b); trace.columns[POSEIDON_16_COL_INDEX_INPUT_RES].push(index_res_a); trace.columns[POSEIDON_16_COL_FLAG_HALF_OUTPUT].push(if half_output { F::ONE } else { F::ZERO }); - trace.columns[POSEIDON_16_COL_FLAG_HARDCODED_LEFT_4].push(if flag_hardcoded { F::ONE } else { F::ZERO }); - trace.columns[POSEIDON_16_COL_OFFSET_HARDCODED].push(F::from_usize(hardcoded_offset_left_val)); + trace.columns[POSEIDON_16_COL_FLAG_HARDCODED_LEFT].push(if flag_hardcoded { F::ONE } else { F::ZERO }); + trace.columns[POSEIDON_16_COL_OFFSET_LEFT_HARDCODED].push(F::from_usize(hardcoded_offset_left_val)); trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_FIRST].push(F::from_usize(left_first_addr)); trace.columns[POSEIDON_16_COL_EFFECTIVE_INDEX_LEFT_SECOND].push(F::from_usize(left_second_addr)); for (i, value) in input.iter().enumerate() { @@ -316,14 +316,14 @@ impl Air for Poseidon16Precompile { let precompile_data_reconstructed = AB::IF::ONE + cols.flag_half_output * AB::F::from_usize(POSEIDON_HALF_OUTPUT_SHIFT) - + cols.flag_hardcoded_left_4 * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT) - + cols.flag_hardcoded_left_4 - * cols.offset_hardcoded + + cols.flag_hardcoded_left * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT) + + cols.flag_hardcoded_left + * cols.offset_hardcoded_left * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT); // effective_index_left_first = index_a * (1 - flag_hardcoded_left_4) + offset * flag_hardcoded_left_4 let index_a = cols.effective_index_left_second - - (AB::IF::ONE - cols.flag_hardcoded_left_4) * AB::F::from_usize(HALF_DIGEST_LEN); + - (AB::IF::ONE - cols.flag_hardcoded_left) * AB::F::from_usize(HALF_DIGEST_LEN); // Bus data: [precompile_data, a, b, res] if BUS { @@ -339,9 +339,9 @@ impl Air for Poseidon16Precompile { builder.assert_bool(cols.flag); builder.assert_bool(cols.flag_half_output); - builder.assert_bool(cols.flag_hardcoded_left_4); + builder.assert_bool(cols.flag_hardcoded_left); - builder.assert_zero(cols.flag_hardcoded_left_4 * (cols.offset_hardcoded - cols.effective_index_left_first)); + builder.assert_zero(cols.flag_hardcoded_left * (cols.offset_hardcoded_left - cols.effective_index_left_first)); eval_poseidon1_16(builder, &cols) } @@ -354,8 +354,8 @@ pub(super) struct Poseidon1Cols16 { pub index_b: T, pub index_res: T, pub flag_half_output: T, - pub flag_hardcoded_left_4: T, - pub offset_hardcoded: T, + pub flag_hardcoded_left: T, + pub offset_hardcoded_left: T, pub effective_index_left_first: T, pub effective_index_left_second: T, @@ -470,7 +470,7 @@ fn eval_last_2_full_rounds_16( outputs: &[AB::IF; WIDTH / 2], round_constants_1: &[F; WIDTH], round_constants_2: &[F; WIDTH], - half_output: AB::IF, + flag_half_output: AB::IF, builder: &mut AB, ) { for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { @@ -487,14 +487,14 @@ fn eval_last_2_full_rounds_16( for (state_i, init_state_i) in state.iter_mut().zip(initial_state) { *state_i += *init_state_i; } + let one_minus_flag_half_output = AB::IF::ONE - flag_half_output; for (idx, (state_i, output_i)) in state.iter_mut().zip(outputs).enumerate() { if idx < HALF_DIGEST_LEN { // First 4 outputs: always constrained builder.assert_eq(*state_i, *output_i); } else { // Last 4 outputs: constrained only when half_output = 0 - let one_minus_half = AB::IF::ONE - half_output; - builder.assert_zero(one_minus_half * (*state_i - *output_i)); + builder.assert_zero(one_minus_flag_half_output * (*state_i - *output_i)); } *state_i = *output_i; } From 7f93044c8fe79da4d9806fe816b15ca6a43ee36b Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Thu, 30 Apr 2026 15:25:38 +0200 Subject: [PATCH 48/66] missing constraint --- crates/lean_vm/src/tables/poseidon_16/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 64eecd53d..53bd56bfb 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -302,7 +302,7 @@ impl Air for Poseidon16Precompile { vec![] } fn n_constraints(&self) -> usize { - BUS as usize + 79 + BUS as usize + 80 } fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { let cols: Poseidon1Cols16 = { @@ -322,8 +322,9 @@ impl Air for Poseidon16Precompile { * AB::F::from_usize(POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT); // effective_index_left_first = index_a * (1 - flag_hardcoded_left_4) + offset * flag_hardcoded_left_4 - let index_a = cols.effective_index_left_second - - (AB::IF::ONE - cols.flag_hardcoded_left) * AB::F::from_usize(HALF_DIGEST_LEN); + let one_minus_flag_hardcoded_left = AB::IF::ONE - cols.flag_hardcoded_left; + let index_a = + cols.effective_index_left_second - one_minus_flag_hardcoded_left * AB::F::from_usize(HALF_DIGEST_LEN); // Bus data: [precompile_data, a, b, res] if BUS { @@ -342,6 +343,7 @@ impl Air for Poseidon16Precompile { builder.assert_bool(cols.flag_hardcoded_left); builder.assert_zero(cols.flag_hardcoded_left * (cols.offset_hardcoded_left - cols.effective_index_left_first)); + builder.assert_zero(one_minus_flag_hardcoded_left * (index_a - cols.effective_index_left_first)); eval_poseidon1_16(builder, &cols) } From 09fb5104327771878b06bea34ac7658dbd9cd9dd Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 10:49:00 +0200 Subject: [PATCH 49/66] w --- crates/rec_aggregation/src/benchmark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rec_aggregation/src/benchmark.rs b/crates/rec_aggregation/src/benchmark.rs index a6b5771f5..187a61ffa 100644 --- a/crates/rec_aggregation/src/benchmark.rs +++ b/crates/rec_aggregation/src/benchmark.rs @@ -284,7 +284,7 @@ fn build_aggregation( let children: Vec<(&[XmssPublicKey], AggregatedXMSS)> = child_pub_keys_list .iter() .zip(child_aggs) - .map(|(pks, agg): (&Vec, AggregatedXMSS)| (pks.as_slice(), agg)) + .map(|(pks, agg)| (pks.as_slice(), agg)) .collect(); let time = Instant::now(); From 3c828f6089fca009d123f8f796800b9150a9f785 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 10:55:30 +0200 Subject: [PATCH 50/66] pdf --- minimal_zkVM.pdf | Bin 367673 -> 367587 bytes misc/minimal_zkVM.tex | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/minimal_zkVM.pdf b/minimal_zkVM.pdf index ca28e7d87f01e395e3e353931ee846064ccf42e7..68a052ba2ffe7ab390e0a356d7ec9368370cfe2d 100644 GIT binary patch delta 56562 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 XoE^?exD!I9ixGiMlt(7X8M%p~(X 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 IQjx~_FVT5wEC2ui diff --git a/misc/minimal_zkVM.tex b/misc/minimal_zkVM.tex index c78e76acb..1567d3a23 100644 --- a/misc/minimal_zkVM.tex +++ b/misc/minimal_zkVM.tex @@ -37,7 +37,7 @@ \newtheorem{lemma}{Lemma} -\title{Minimal zkVM for Lean Ethereum (draft 0.6.0)} +\title{Minimal zkVM for Lean Ethereum (draft 0.7.0)} \author{} \date{} \begin{document} @@ -270,7 +270,7 @@ \subsection{Precompiles} \subsubsection{POSEIDON} -Compression of 16 field elements (two blocks of 8) into 8 field elements. +Compression (feed-forward) of 16 field elements (two blocks of 8) into 8 field elements. $$ \textbf{m}[\nu_C..\nu_C + 8] = \text{Poseidon}(\textbf{m}[\nu_A..\nu_A + 8] \;\|\; \textbf{m}[\nu_B..\nu_B + 8]) + \textbf{m}[\nu_A..\nu_A + 8] @@ -280,6 +280,8 @@ \subsubsection{POSEIDON} \texttt{PRECOMPILE\_DATA} $= 1$ +Recently some additonal paramters were introduced (see \ref{efficiently_verrifying_hash-based_signatures} for details), allowing more granular input / output. + \subsubsection{EXTENSION\_OP}\label{extension_op_instruction} EXTENSION\_OP enables computations of one these 3 forms in the extension field $\Fq$: @@ -560,7 +562,7 @@ \subsection{Poseidon table} The Poseidon precompile receives 3 runtime arguments: $\nu_A$, $\nu_B$, $\nu_C$ interpreted as memory pointers. 3 lookups (each of size 8) into the memory are used to fetch $\texttt{left}$ = $\textbf{m}[\nu_A..\nu_A + 8] \in \Fp^8$, $\texttt{right}$ = $\textbf{m}[\nu_B..\nu_B + 8] \in \Fp^8$, and $\texttt{res}$ = $\textbf{m}[\nu_C..\nu_C + 8] \in \Fp^8$. AIR constraints, of degree 9, assert that $\texttt{res} = \text{poseidon\_compress}(\texttt{left} \;\|\; \texttt{right})$. Degree 3 is also an alternative, at the cost of more committed columns ($\approx 160$ vs. $\approx 100$). -\subsubsection{Efficiently verrifying hash-based signatures} +\subsubsection{Efficiently verrifying hash-based signatures}\label{efficiently_verrifying_hash-based_signatures} Hash-based signatures often rely on tweaks and public parameters (see \cite{ethereum_signatures}). From 8f280c9f8c7b06bdfcedf89e62560d1188f23a00 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 10:56:30 +0200 Subject: [PATCH 51/66] w --- crates/lean_compiler/snark_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index eb870301a..ab139bdfd 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -79,7 +79,7 @@ def poseidon16_compress_half(left, right, output): def poseidon16_compress_hardcoded_left_4(left, right, output, offset): """Poseidon16 compression where the first 4 FE of the left input are read from memory[offset..offset+4] instead of memory[left..left+4]. The last 4 FE of the - left input still come from memory[left+4..left+8]. `offset` must be a compile-time + left input come from memory[left..left+4]. `offset` must be a compile-time constant expression.""" _ = left, right, output, offset From ad48a8be8cc00101543e2e74a9de292988038e44 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 11:03:28 +0200 Subject: [PATCH 52/66] w --- crates/lean_vm/src/tables/poseidon_16/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index 53bd56bfb..ca3e3a800 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -194,7 +194,7 @@ impl TableT for Poseidon16Precompile { let perm: &mut Poseidon1Cols16<&mut F> = unsafe { &mut *(ptrs.as_ptr() as *mut Poseidon1Cols16<&mut F>) }; perm.inputs.iter_mut().for_each(|x| **x = F::ZERO); - *perm.flag = F::ZERO; + *perm.flag_active = F::ZERO; *perm.index_b = F::from_usize(zero_vec_ptr); *perm.index_res = F::from_usize(null_hash_ptr); *perm.flag_half_output = F::ZERO; @@ -330,15 +330,15 @@ impl Air for Poseidon16Precompile { if BUS { builder.eval_virtual_column(eval_virtual_bus_column::( extra_data, - cols.flag, + cols.flag_active, &[precompile_data_reconstructed, index_a, cols.index_b, cols.index_res], )); } else { - builder.declare_values(std::slice::from_ref(&cols.flag)); + builder.declare_values(std::slice::from_ref(&cols.flag_active)); builder.declare_values(&[precompile_data_reconstructed, index_a, cols.index_b, cols.index_res]); } - builder.assert_bool(cols.flag); + builder.assert_bool(cols.flag_active); builder.assert_bool(cols.flag_half_output); builder.assert_bool(cols.flag_hardcoded_left); @@ -352,7 +352,7 @@ impl Air for Poseidon16Precompile { #[repr(C)] #[derive(Debug)] pub(super) struct Poseidon1Cols16 { - pub flag: T, + pub flag_active: T, // 0 = padding, 1 = active pub index_b: T, pub index_res: T, pub flag_half_output: T, From 69e47e8b906f5fbd86102b8fe4261ed2122fca7b Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 13:14:10 +0200 Subject: [PATCH 53/66] w --- crates/backend/fiat-shamir/src/challenger.rs | 2 +- crates/rec_aggregation/src/compilation.rs | 4 ++-- crates/rec_aggregation/src/lib.rs | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/backend/fiat-shamir/src/challenger.rs b/crates/backend/fiat-shamir/src/challenger.rs index d650d68aa..34fcd94ab 100644 --- a/crates/backend/fiat-shamir/src/challenger.rs +++ b/crates/backend/fiat-shamir/src/challenger.rs @@ -43,7 +43,7 @@ impl> Challenger { } pub fn sample_many(&mut self, n: usize) -> Vec<[F; RATE]> { - let mut sampled = Vec::with_capacity(n); + let mut sampled = Vec::with_capacity(n + 1); for i in 0..n + 1 { let mut domain_sep = [F::ZERO; RATE]; domain_sep[0] = F::from_usize(i); diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 5eb05f35c..7d8b11eef 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -31,8 +31,8 @@ fn compile_main_program(program_log_size: usize, bytecode_zero_eval: F) -> Bytec let bytecode_point_n_vars = program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); let claim_data_size = (bytecode_point_n_vars + 1) * DIMENSION; let claim_data_size_padded = claim_data_size.next_multiple_of(DIGEST_LEN); - // input_data_buf layout (lives in private memory, hashed to a single digest in public input): - // n_sigs(1) + slice_hash(8) + message + merkle_chunks_for_slot + // input_data_buf layout (part of the witness, "hinted" then hashed to a single digest that should match public input): + // n_sigs(1) + pubkeys_hash(8) + message + merkle_chunks_for_slot // + tweaks_hash(8) + bytecode_claim_padded + bytecode_hash_domsep(8) let input_data_size = 1 + DIGEST_LEN + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + DIGEST_LEN + claim_data_size_padded + DIGEST_LEN; diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index d572776bb..c40f2af5f 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -44,9 +44,8 @@ const TWEAKS_HASHING_USE_IV: bool = false; // fixed size → no IV needed #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct Digest(pub [F; DIGEST_LEN]); -// preamble memory layout: see `build_preamble_memory` in utils.py, plus the XMSS tweak -// table which sits at the end of the preamble region and is populated via -// `hint_witness("tweak_table", TWEAK_TABLE_ADDR)` at program startup. +// preamble memory layout: see `build_preamble_memory` in utils.py: +// [000.. (ZERO_VEC_LEN)][10000000 (fiat-shamir domain sep)][10000 (one in extension field)][111... (NUM_REPEATED_ONES)][tweak table] pub const ZERO_VEC_LEN: usize = 16; pub const NUM_REPEATED_ONES: usize = 32; pub const PREAMBLE_MEMORY_LEN: usize = From 063c50453c3b021977367900a69711e772b50b13 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 13:28:33 +0200 Subject: [PATCH 54/66] w --- crates/rec_aggregation/src/lib.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index c40f2af5f..19136a3cb 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -105,24 +105,17 @@ fn compute_tweak_table(slot: u32) -> Vec { let mut table = Vec::new(); let push_padded = |table: &mut Vec, tweak_type: usize, sub_position: usize, index: u32| { - let tw = make_tweak_values(tweak_type, sub_position, index); - table.push(tw[0]); - table.push(tw[1]); - table.push(F::ZERO); - table.push(F::ZERO); + table.extend(make_tweak_values(tweak_type, sub_position, index)); + table.extend(std::iter::repeat(F::ZERO).take(2)); }; - // Encoding tweak (5-FE: extra trailing zero so that copy_5 reads all zeros after the value). + // Encoding tweak { - let tw = make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot); - table.push(tw[0]); - table.push(tw[1]); - table.push(F::ZERO); - table.push(F::ZERO); - table.push(F::ZERO); + table.extend(make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot)); + table.extend(std::iter::repeat(F::ZERO).take(3)); } - // Chain tweaks: for chain i, step s → make_tweak(CHAIN, i*CHAIN_LENGTH + s, slot) + // Chain tweaks for i in 0..V { for s in 0..CHAIN_LENGTH { push_padded(&mut table, TWEAK_TYPE_CHAIN, i * CHAIN_LENGTH + s, slot); @@ -132,7 +125,7 @@ fn compute_tweak_table(slot: u32) -> Vec { // WOTS_PK tweak push_padded(&mut table, TWEAK_TYPE_WOTS_PK, 0, slot); - // Merkle tweaks: for level 0..LOG_LIFETIME-1 + // Merkle tweaks for level in 0..LOG_LIFETIME { let parent_index = ((slot as u64) >> (level + 1)) as u32; push_padded(&mut table, TWEAK_TYPE_MERKLE, level + 1, parent_index); @@ -189,9 +182,6 @@ pub(crate) fn hash_input_data(data: &[F]) -> [F; DIGEST_LEN] { } fn encode_wots_signature(sig: &XmssSignature) -> Vec { - // The in-memory signature buffer consumed by the `wots` named hint is just the - // WOTS part: `randomness | chain_tips`. The XMSS merkle path is delivered - // separately via the `xmss_merkle_node` named hint (one 4-FE node per call). let mut data = vec![]; data.extend(sig.wots_signature.randomness.to_vec()); data.extend(sig.wots_signature.chain_tips.iter().flat_map(|digest| digest.to_vec())); From 43cf6774481e8d632de3f6ce80d58c77106b6112 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 13:44:55 +0200 Subject: [PATCH 55/66] use 6 field elements for XMSS randomnes --- crates/lean_vm/src/core/constants.rs | 2 +- crates/rec_aggregation/src/lib.rs | 14 +++----------- crates/rec_aggregation/utils.py | 5 +++++ crates/rec_aggregation/xmss_aggregate.py | 15 +++++++-------- crates/xmss/src/lib.rs | 2 +- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/crates/lean_vm/src/core/constants.rs b/crates/lean_vm/src/core/constants.rs index 59011adc2..afe6bc2d8 100644 --- a/crates/lean_vm/src/core/constants.rs +++ b/crates/lean_vm/src/core/constants.rs @@ -22,7 +22,7 @@ pub const MIN_BYTECODE_LOG_SIZE: usize = 8; /// Minimum and maximum number of rows per table (as powers of two), both inclusive pub const MIN_LOG_N_ROWS_PER_TABLE: usize = 8; // Zero padding will be added to each at least, if this minimum is not reached, (ensuring AIR / GKR work fine, with SIMD, without too much edge cases). Long term, we should find a more elegant solution. pub const MAX_LOG_N_ROWS_PER_TABLE: [(Table, usize); 3] = [ - (Table::execution(), 25), + (Table::execution(), 24), (Table::extension_op(), 21), (Table::poseidon16(), 21), ]; diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 19136a3cb..0e7dbc089 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -30,14 +30,9 @@ const TWEAK_TYPE_ENCODING: usize = 3; /// Number of tweaks in the table: 1 encoding + V*CHAIN_LENGTH chains + 1 wots_pk + LOG_LIFETIME merkle const N_TWEAKS: usize = 1 + V * CHAIN_LENGTH + 1 + LOG_LIFETIME; -/// All, except one, tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. The first slot -/// (the encoding tweak) is the ONLY slot read via copy_5 (5 cells), so it gets -/// an extra trailing zero: [tw[0], tw[1], 0, 0, 0]. Every other slot is read -/// only via `poseidon16_compress_hardcoded_left_4`, which reads exactly 4 cells. +/// All, tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0]. const TWEAK_SLOT_SIZE: usize = 4; -const ENCODING_TWEAK_SLOT_SIZE: usize = 5; -const TWEAK_TABLE_SIZE_FE_PADDED: usize = - (ENCODING_TWEAK_SLOT_SIZE + (N_TWEAKS - 1) * TWEAK_SLOT_SIZE).next_multiple_of(DIGEST_LEN); +const TWEAK_TABLE_SIZE_FE_PADDED: usize = (N_TWEAKS * TWEAK_SLOT_SIZE).next_multiple_of(DIGEST_LEN); const TWEAKS_HASHING_USE_IV: bool = false; // fixed size → no IV needed @@ -110,10 +105,7 @@ fn compute_tweak_table(slot: u32) -> Vec { }; // Encoding tweak - { - table.extend(make_tweak_values(TWEAK_TYPE_ENCODING, 0, slot)); - table.extend(std::iter::repeat(F::ZERO).take(3)); - } + push_padded(&mut table, TWEAK_TYPE_ENCODING, 0, slot); // Chain tweaks for i in 0..V { diff --git a/crates/rec_aggregation/utils.py b/crates/rec_aggregation/utils.py index b2bcf9dbe..3999f4548 100644 --- a/crates/rec_aggregation/utils.py +++ b/crates/rec_aggregation/utils.py @@ -360,6 +360,11 @@ def set_to_5_zeros(a): dot_product_ee(a, ONE_EF_PTR, zero_ptr) return +@inline +def copy_6(a, b): + dot_product_ee(a, ONE_EF_PTR, b) + a[5] = b[5] + return @inline def set_to_7_zeros(a): diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index dd07d270f..a9176c39f 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -21,13 +21,12 @@ INNER_PUB_MEM_SIZE = 2**INNER_PUBLIC_MEMORY_LOG_SIZE # = DIGEST_LEN TWEAK_TABLE_ADDR = PREAMBLE_MEMORY_END -# Tweak table layout: all tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0], except the first, encoding, tweak: which is 5-FE [tw[0], tw[1], 0, 0, 0] (in order to use copy_5) +# Tweak table layout: all tweaks are stored as a 4-FE slot [tw[0], tw[1], 0, 0] TWEAK_LEN = 4 # stride / slot size for non-encoding tweaks -ENCODING_TWEAK_SLOT_SIZE = 5 # encoding tweak has one extra trailing zero N_TWEAKS = 1 + V * CHAIN_LENGTH + 1 + LOG_LIFETIME -TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(ENCODING_TWEAK_SLOT_SIZE + (N_TWEAKS - 1) * TWEAK_LEN, DIGEST_LEN) +TWEAK_TABLE_SIZE_FE_PADDED = next_multiple_of(N_TWEAKS * TWEAK_LEN, DIGEST_LEN) TWEAK_ENCODING_OFFSET = 0 -TWEAK_CHAIN_OFFSET = ENCODING_TWEAK_SLOT_SIZE # encoding occupies cells [0..5] +TWEAK_CHAIN_OFFSET = TWEAK_ENCODING_OFFSET + TWEAK_LEN # just after the encoding tweak TWEAK_WOTS_PK_OFFSET = TWEAK_CHAIN_OFFSET + V * CHAIN_LENGTH * TWEAK_LEN TWEAK_MERKLE_OFFSET = TWEAK_WOTS_PK_OFFSET + TWEAK_LEN @@ -43,10 +42,10 @@ def xmss_verify(pub_key, message, merkle_chunks): # 1) Encode: poseidon16_compress(message[0:8], [randomness(5) | tweak_encoding(2) | 0]) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET - a_input_right = Array(RANDOMNESS_LEN + ENCODING_TWEAK_SLOT_SIZE) - copy_5(randomness, a_input_right) - # encoding_tweak points to tw[0] of slot 0; reading 5 elements gives [tw(2), 0, 0, 0]. - copy_5(encoding_tweak, a_input_right + RANDOMNESS_LEN) + a_input_right = Array(DIGEST_LEN) + copy_6(randomness, a_input_right) + a_input_right[6] = encoding_tweak[0] + a_input_right[7] = encoding_tweak[1] pre_compressed = Array(DIGEST_LEN) poseidon16_compress(message, a_input_right, pre_compressed) diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs index 45db5b120..3a41f2164 100644 --- a/crates/xmss/src/lib.rs +++ b/crates/xmss/src/lib.rs @@ -22,7 +22,7 @@ pub const W: usize = 3; pub const CHAIN_LENGTH: usize = 1 << W; pub const NUM_CHAIN_HASHES: usize = 110; pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; -pub const RANDOMNESS_LEN_FE: usize = 5; +pub const RANDOMNESS_LEN_FE: usize = 6; pub const MESSAGE_LEN_FE: usize = 8; pub const PUBLIC_PARAM_LEN_FE: usize = 4; pub const PUB_KEY_FLAT_SIZE: usize = XMSS_DIGEST_LEN + PUBLIC_PARAM_LEN_FE; From 0efa54c345da251360e3bbf48ba59602eb354edc Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 13:46:15 +0200 Subject: [PATCH 56/66] clippy --- crates/rec_aggregation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 0e7dbc089..68d7d60df 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -101,7 +101,7 @@ fn compute_tweak_table(slot: u32) -> Vec { let push_padded = |table: &mut Vec, tweak_type: usize, sub_position: usize, index: u32| { table.extend(make_tweak_values(tweak_type, sub_position, index)); - table.extend(std::iter::repeat(F::ZERO).take(2)); + table.extend(std::iter::repeat_n(F::ZERO, 2)); }; // Encoding tweak From 8b4ed7965fcc84ed2dfab63fb054173df5d8574b Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 14:56:40 +0200 Subject: [PATCH 57/66] w --- crates/rec_aggregation/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 68d7d60df..a2062162e 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -93,9 +93,7 @@ fn make_tweak_values(tweak_type: usize, sub_position: usize, index: u32) -> [F; ] } -/// Tweak slots are 4-FE [tw[0], tw[1], 0, 0], except the first (encoding) slot -/// which is 5-FE [tw[0], tw[1], 0, 0, 0] — the extra trailing zero is needed -/// because the encoding tweak is the only slot read via copy_5. +/// Tweak slots are 4-FE [tw[0], tw[1], 0, 0] fn compute_tweak_table(slot: u32) -> Vec { let mut table = Vec::new(); From dd84b5e2e1407711dc9d9728ba2b8c63894ff55a Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 16:06:41 +0200 Subject: [PATCH 58/66] w --- crates/rec_aggregation/xmss_aggregate.py | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index a9176c39f..f31804651 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -39,7 +39,7 @@ def xmss_verify(pub_key, message, merkle_chunks): randomness = wots chain_starts = wots + RANDOMNESS_LEN - # 1) Encode: poseidon16_compress(message[0:8], [randomness(5) | tweak_encoding(2) | 0]) + # 1) Encode: poseidon16_compress(message[0:8], [randomness(6) | tweak_encoding(2)) # poseidon16_compress(pre_compressed, [pp(4) | zeros(4)]) encoding_tweak = TWEAK_TABLE_ADDR + TWEAK_ENCODING_OFFSET a_input_right = Array(DIGEST_LEN) @@ -71,11 +71,11 @@ def xmss_verify(pub_key, message, merkle_chunks): for j in unroll(1, 24 / (2 * W)): partial_sum += encoding[i * (24 / (2 * W)) + j] * (CHAIN_LENGTH**2) ** j - # p = 2^31 - 2^24 + 1, so inv(2^24) = -127 (mod p). + # p = 2^31 - 2^24 + 1 = 127.2^24 + 1, so inv(2^24) = -127 (mod p). # Deduce remaining_i from partial_sum + remaining_i * 2^24 == encoding_fe[i]: # remaining_i = (encoding_fe[i] - partial_sum) * inv(2^24) = (partial_sum - encoding_fe[i]) * 127 remaining_i = (partial_sum - encoding_fe[i]) * 127 - assert remaining_i < 2**7 - 1 # ensures uniformity + prevent overflow + assert remaining_i < 127 # ensures uniformity + prevent overflow debug_assert(V % 2 == 0) @@ -85,7 +85,7 @@ def xmss_verify(pub_key, message, merkle_chunks): chain_start_a = chain_starts + (2 * i) * XMSS_DIGEST_LEN chain_start_b = chain_starts + (2 * i + 1) * XMSS_DIGEST_LEN chain_end_a = wots_public_key + i * WOTS_PK_PAIR_STRIDE + 1 - chain_end_b = wots_public_key + i * WOTS_PK_PAIR_STRIDE + 1 + XMSS_DIGEST_LEN + chain_end_b = chain_end_a + XMSS_DIGEST_LEN tweaks_a = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i) * CHAIN_LENGTH * TWEAK_LEN tweaks_b = TWEAK_TABLE_ADDR + TWEAK_CHAIN_OFFSET + (2 * i + 1) * CHAIN_LENGTH * TWEAK_LEN pair_sum_ptr = Array(1) @@ -214,43 +214,43 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk r3 = (r2 - b2) / 2 b3 = r3 % 2 - buf0_alloc = Array(XMSS_DIGEST_LEN * 2 + 2) # 10 elements - buf0 = buf0_alloc + 1 # logical positions [-1..8] + buf0_alloc = Array(XMSS_DIGEST_LEN * 2 + 2) + buf0 = buf0_alloc + 1 if b0 == 1: # state_in is the LEFT child → state_in[0..4] lands at buf0[0..4]. copy_5(state_in - 1, buf0 - 1) - hint_witness("xmss_merkle_node",buf0 + XMSS_DIGEST_LEN) + hint_witness("xmss_merkle_node", buf0 + XMSS_DIGEST_LEN) else: # state_in is the RIGHT child → state_in[0..4] lands at buf0[4..8]. - hint_witness("xmss_merkle_node",buf0) + hint_witness("xmss_merkle_node", buf0) copy_5(state_in, buf0 + XMSS_DIGEST_LEN) # Level 0 hash - buf1 = Array(XMSS_DIGEST_LEN * 2) # 8 elements + buf1 = Array(XMSS_DIGEST_LEN * 2) if b1 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) - hint_witness("xmss_merkle_node",buf1 + XMSS_DIGEST_LEN) + hint_witness("xmss_merkle_node", buf1 + XMSS_DIGEST_LEN) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST_LEN, merkle_tweaks_chunk) - hint_witness("xmss_merkle_node",buf1) + hint_witness("xmss_merkle_node", buf1) # Level 1 hash → buf2 - buf2 = Array(XMSS_DIGEST_LEN * 2) # 8 elements + buf2 = Array(XMSS_DIGEST_LEN * 2) if b2 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) - hint_witness("xmss_merkle_node",buf2 + XMSS_DIGEST_LEN) + hint_witness("xmss_merkle_node", buf2 + XMSS_DIGEST_LEN) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 1 * TWEAK_LEN) - hint_witness("xmss_merkle_node",buf2) + hint_witness("xmss_merkle_node", buf2) # Level 2 hash → buf3 - buf3 = Array(XMSS_DIGEST_LEN * 2) # 8 elements + buf3 = Array(XMSS_DIGEST_LEN * 2) if b3 == 1: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) - hint_witness("xmss_merkle_node",buf3 + XMSS_DIGEST_LEN) + hint_witness("xmss_merkle_node", buf3 + XMSS_DIGEST_LEN) else: poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 2 * TWEAK_LEN) - hint_witness("xmss_merkle_node",buf3) + hint_witness("xmss_merkle_node", buf3) poseidon16_compress_half_hardcoded_left_4(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return From 8267435a07a0151193e5b6fcf35ff0af4f6fab79 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 16:07:31 +0200 Subject: [PATCH 59/66] naming --- crates/lean_compiler/snark_lib.py | 6 ++--- .../lean_compiler/src/a_simplify_lang/mod.rs | 6 ++--- crates/lean_prover/src/test_zkvm.rs | 6 ++--- crates/lean_vm/src/tables/poseidon_16/mod.rs | 8 +++---- crates/rec_aggregation/xmss_aggregate.py | 24 +++++++++---------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index ab139bdfd..5d13b761f 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -76,7 +76,7 @@ def poseidon16_compress_half(left, right, output): _ = left, right, output -def poseidon16_compress_hardcoded_left_4(left, right, output, offset): +def poseidon16_compress_hardcoded_left(left, right, output, offset): """Poseidon16 compression where the first 4 FE of the left input are read from memory[offset..offset+4] instead of memory[left..left+4]. The last 4 FE of the left input come from memory[left..left+4]. `offset` must be a compile-time @@ -84,8 +84,8 @@ def poseidon16_compress_hardcoded_left_4(left, right, output, offset): _ = left, right, output, offset -def poseidon16_compress_half_hardcoded_left_4(left, right, output, offset): - """Composition of `poseidon16_compress_half` and `poseidon16_compress_hardcoded_left_4`.""" +def poseidon16_compress_half_hardcoded_left(left, right, output, offset): + """Composition of `poseidon16_compress_half` and `poseidon16_compress_hardcoded_left`.""" _ = left, right, output, offset diff --git a/crates/lean_compiler/src/a_simplify_lang/mod.rs b/crates/lean_compiler/src/a_simplify_lang/mod.rs index 6f48c4419..bc268fb80 100644 --- a/crates/lean_compiler/src/a_simplify_lang/mod.rs +++ b/crates/lean_compiler/src/a_simplify_lang/mod.rs @@ -7,7 +7,7 @@ use crate::{ use backend::PrimeCharacteristicRing; use lean_vm::{ ALL_POSEIDON16_NAMES, Boolean, BooleanExpr, CustomHint, ExtensionOpMode, FunctionName, - POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_NAME, POSEIDON16_HARDCODED_LEFT_4_NAME, PrecompileArgs, + POSEIDON16_HALF_HARDCODED_LEFT_NAME, POSEIDON16_HALF_NAME, POSEIDON16_HARDCODED_LEFT_NAME, PrecompileArgs, PrecompileCompTimeArgs, SourceLocation, }; use std::{ @@ -2266,10 +2266,10 @@ fn simplify_lines( "Precompile {function_name} should not return values, at {location}" )); } - let half_output = [POSEIDON16_HALF_NAME, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME] + let half_output = [POSEIDON16_HALF_NAME, POSEIDON16_HALF_HARDCODED_LEFT_NAME] .contains(&function_name.as_str()); let is_hardcoded_left = - [POSEIDON16_HARDCODED_LEFT_4_NAME, POSEIDON16_HALF_HARDCODED_LEFT_4_NAME] + [POSEIDON16_HARDCODED_LEFT_NAME, POSEIDON16_HALF_HARDCODED_LEFT_NAME] .contains(&function_name.as_str()); let expected_args = if is_hardcoded_left { 4 } else { 3 }; if args.len() != expected_args { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index 8925c448d..d4742bad8 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -25,13 +25,13 @@ def main(): for i in unroll(0, HALF_DIGEST_LEN): assert full_out[i] == half_out[i] - # poseidon16_compress_hardcoded_left_4: with the new convention, only 4 FE are read + # poseidon16_compress_hardcoded_left: with the new convention, only 4 FE are read # at the left pointer (the 4-element data digest at pub_start + 1496) and the first # 4 FE of the left input come from memory[pub_start + 1500 .. pub_start + 1504] # (the hardcoded prefix). hardcoded_left = pub_start + 1496 hardcoded_full_out = pub_start + 1504 - poseidon16_compress_hardcoded_left_4( + poseidon16_compress_hardcoded_left( hardcoded_left, pub_start + 5 * DIGEST_LEN, hardcoded_full_out, @@ -40,7 +40,7 @@ def main(): # Same, but only first 4 FE of the output are constrained. hardcoded_half_out = pub_start + 1512 - poseidon16_compress_half_hardcoded_left_4( + poseidon16_compress_half_hardcoded_left( hardcoded_left, pub_start + 5 * DIGEST_LEN, hardcoded_half_out, diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index ca3e3a800..fcd50d08b 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -123,13 +123,13 @@ pub const POSEIDON_16_COL_PRECOMPILE_DATA: ColIndex = num_cols_poseidon_16() + 1 pub const POSEIDON16_NAME: &str = "poseidon16_compress"; pub const POSEIDON16_HALF_NAME: &str = "poseidon16_compress_half"; -pub const POSEIDON16_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_hardcoded_left_4"; -pub const POSEIDON16_HALF_HARDCODED_LEFT_4_NAME: &str = "poseidon16_compress_half_hardcoded_left_4"; +pub const POSEIDON16_HARDCODED_LEFT_NAME: &str = "poseidon16_compress_hardcoded_left"; +pub const POSEIDON16_HALF_HARDCODED_LEFT_NAME: &str = "poseidon16_compress_half_hardcoded_left"; pub const ALL_POSEIDON16_NAMES: [&str; 4] = [ POSEIDON16_NAME, POSEIDON16_HALF_NAME, - POSEIDON16_HARDCODED_LEFT_4_NAME, - POSEIDON16_HALF_HARDCODED_LEFT_4_NAME, + POSEIDON16_HARDCODED_LEFT_NAME, + POSEIDON16_HALF_HARDCODED_LEFT_NAME, ]; pub const HALF_DIGEST_LEN: usize = DIGEST_LEN / 2; diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index f31804651..39090e6c6 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -121,18 +121,18 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): starting_step = CHAIN_LENGTH - 1 - n if n == 1: first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4(input, chain_right, output, first_tweak) + poseidon16_compress_half_hardcoded_left(input, chain_right, output, first_tweak) else: digests = Array(n * XMSS_DIGEST_LEN) # Hash 0: input → digests[0..4] first_tweak = chain_i_tweaks + starting_step * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4(input, chain_right, digests, first_tweak) + poseidon16_compress_half_hardcoded_left(input, chain_right, digests, first_tweak) # Hashes 1..n-2: digests[(j-1)*4..j*4] → digests[j*4..(j+1)*4] for j in unroll(1, n - 1): cur_tweak = chain_i_tweaks + (starting_step + j) * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4( + poseidon16_compress_half_hardcoded_left( digests + (j - 1) * XMSS_DIGEST_LEN, chain_right, digests + j * XMSS_DIGEST_LEN, @@ -141,7 +141,7 @@ def chain_hash_pa(input, n, output, chain_i_tweaks, chain_right): # Final hash: digests[(n-2)*4..(n-1)*4] → output last_tweak = chain_i_tweaks + (starting_step + n - 1) * TWEAK_LEN - poseidon16_compress_half_hardcoded_left_4( + poseidon16_compress_half_hardcoded_left( digests + (n - 2) * XMSS_DIGEST_LEN, chain_right, output, last_tweak ) return @@ -183,7 +183,7 @@ def chain_hash_pair( def wots_pk_hash(wots_public_key, public_param): N_CHUNKS = V / 2 states = Array((N_CHUNKS + 1) * DIGEST_LEN) - poseidon16_compress_hardcoded_left_4( + poseidon16_compress_hardcoded_left( public_param, ZERO_VEC_PTR, states, TWEAK_TABLE_ADDR + TWEAK_WOTS_PK_OFFSET ) for i in unroll(0, N_CHUNKS): @@ -228,31 +228,31 @@ def do_4_merkle_levels(b, state_in, state_out, public_param, merkle_tweaks_chunk # Level 0 hash buf1 = Array(XMSS_DIGEST_LEN * 2) if b1 == 1: - poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1, merkle_tweaks_chunk) + poseidon16_compress_half_hardcoded_left(public_param, buf0, buf1, merkle_tweaks_chunk) hint_witness("xmss_merkle_node", buf1 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf0, buf1 + XMSS_DIGEST_LEN, merkle_tweaks_chunk) + poseidon16_compress_half_hardcoded_left(public_param, buf0, buf1 + XMSS_DIGEST_LEN, merkle_tweaks_chunk) hint_witness("xmss_merkle_node", buf1) # Level 1 hash → buf2 buf2 = Array(XMSS_DIGEST_LEN * 2) if b2 == 1: - poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left(public_param, buf1, buf2, merkle_tweaks_chunk + 1 * TWEAK_LEN) hint_witness("xmss_merkle_node", buf2 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf1, buf2 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 1 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left(public_param, buf1, buf2 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 1 * TWEAK_LEN) hint_witness("xmss_merkle_node", buf2) # Level 2 hash → buf3 buf3 = Array(XMSS_DIGEST_LEN * 2) if b3 == 1: - poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left(public_param, buf2, buf3, merkle_tweaks_chunk + 2 * TWEAK_LEN) hint_witness("xmss_merkle_node", buf3 + XMSS_DIGEST_LEN) else: - poseidon16_compress_half_hardcoded_left_4(public_param, buf2, buf3 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 2 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left(public_param, buf2, buf3 + XMSS_DIGEST_LEN, merkle_tweaks_chunk + 2 * TWEAK_LEN) hint_witness("xmss_merkle_node", buf3) - poseidon16_compress_half_hardcoded_left_4(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) + poseidon16_compress_half_hardcoded_left(public_param, buf3, state_out, merkle_tweaks_chunk + 3 * TWEAK_LEN) return From 20a61301a90595feeaffac44145b82211c80afbe Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Fri, 1 May 2026 19:17:56 +0200 Subject: [PATCH 60/66] README --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74ab8fa77..1d69513ff 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,6 @@ Documentation: [PDF](minimal_zkVM.pdf) The VM design is inspired by the famous [Cairo paper](https://eprint.iacr.org/2021/1063.pdf). -## Security - -123 bits of provable security, given by Johnson bound + degree 5 extension of koala-bear. (128 bits would require hash digests of more than 8 field elements, todo?). In the benchmarks, we also display performance with conjectured security, even though leanVM targets the proven regime by default. - ## Benchmarks Machine: M4 Max 48GB (CPU only) @@ -75,6 +71,19 @@ cargo run --release -- fancy-aggregation (Proven regime) +## Security + +### snark + +≈ 124 bits of provable security, given by Johnson bound + degree 5 extension of koala-bear. (128 bits requires bigger hash digests (8 koalabears ≈ 248 bits) -> TODO). In the benchmarks, we also display performance with conjectured security, even though leanVM targets the proven regime by default. + +### XMSS + +Currently, we use an [XMSS](https://eprint.iacr.org/2025/055.pdf) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameter ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: +- hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay bellow one ip-v6 MTU = 1280 bytes) +- a new prime, close to 32 bits (typically p = 125.2^25 + 1) or 64 bits ([goldilocks](https://2π.com/22/goldilocks/)) +It's important to mention that a security analysis in the ROM / QROM is not the most conservative. In particular, [eprint 2025/055](https://eprint.iacr.org/2025/055.pdf) security proof holds in the standard model (at the cost of bigger hash digests). + ## Credits - [Plonky3](https://github.com/Plonky3/Plonky3) for its various performant crates From 911ba17ea0ac2b92ba6b43cd801e5000ea2d6897 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 2 May 2026 13:57:59 +0200 Subject: [PATCH 61/66] readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1d69513ff..32b10b693 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,11 @@ cargo run --release -- fancy-aggregation ### XMSS -Currently, we use an [XMSS](https://eprint.iacr.org/2025/055.pdf) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameter ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: -- hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay bellow one ip-v6 MTU = 1280 bytes) +Currently, we use an [XMSS](https://datatracker.ietf.org/doc/html/rfc8391) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameter ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: +- hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay bellow one IPv6 MTU = 1280 bytes) - a new prime, close to 32 bits (typically p = 125.2^25 + 1) or 64 bits ([goldilocks](https://2π.com/22/goldilocks/)) -It's important to mention that a security analysis in the ROM / QROM is not the most conservative. In particular, [eprint 2025/055](https://eprint.iacr.org/2025/055.pdf) security proof holds in the standard model (at the cost of bigger hash digests). + +It's important to mention that a security analysis in the ROM / QROM is not the most conservative. In particular, [eprint 2025/055](https://eprint.iacr.org/2025/055.pdf)'s security proof holds in the standard model (at the cost of bigger hash digests): the implementation is available in the [leanSig](https://github.com/leanEthereum/leanSig) repository. A compatible version of leanMultisig ca be found in the [devnet4](https://github.com/leanEthereum/leanMultisig/tree/devnet4) branch. ## Credits From 685ecf5456b37ec48292db73ef5a0cd0fec66ce8 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 2 May 2026 13:59:46 +0200 Subject: [PATCH 62/66] typos --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32b10b693..ae1ee1587 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,11 @@ cargo run --release -- fancy-aggregation ### XMSS -Currently, we use an [XMSS](https://datatracker.ietf.org/doc/html/rfc8391) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameter ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: -- hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay bellow one IPv6 MTU = 1280 bytes) +Currently, we use an [XMSS](https://datatracker.ietf.org/doc/html/rfc8391) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameters ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: +- hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay below one IPv6 MTU = 1280 bytes) - a new prime, close to 32 bits (typically p = 125.2^25 + 1) or 64 bits ([goldilocks](https://2π.com/22/goldilocks/)) -It's important to mention that a security analysis in the ROM / QROM is not the most conservative. In particular, [eprint 2025/055](https://eprint.iacr.org/2025/055.pdf)'s security proof holds in the standard model (at the cost of bigger hash digests): the implementation is available in the [leanSig](https://github.com/leanEthereum/leanSig) repository. A compatible version of leanMultisig ca be found in the [devnet4](https://github.com/leanEthereum/leanMultisig/tree/devnet4) branch. +It's important to mention that a security analysis in the ROM / QROM is not the most conservative. In particular, [eprint 2025/055](https://eprint.iacr.org/2025/055.pdf)'s security proof holds in the standard model (at the cost of bigger hash digests): the implementation is available in the [leanSig](https://github.com/leanEthereum/leanSig) repository. A compatible version of leanMultisig can be found in the [devnet4](https://github.com/leanEthereum/leanMultisig/tree/devnet4) branch. ## Credits From bf628513a453865b48d1956ede7949b562caaad7 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 2 May 2026 15:47:44 +0200 Subject: [PATCH 63/66] XMSS high-level specification --- README.md | 2 +- crates/xmss/xmss.md | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 crates/xmss/xmss.md diff --git a/README.md b/README.md index ae1ee1587..2e14555f4 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ cargo run --release -- fancy-aggregation ### XMSS -Currently, we use an [XMSS](https://datatracker.ietf.org/doc/html/rfc8391) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameters ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: +Currently, we use an [XMSS](crates/xmss/xmss.md) with hash digests of 4 field elements ≈ 124 bits. Tweaks and public parameters ensure domain separation. An analysis in the ROM (resp. QROM), inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103) would lead to ≈ 124 (resp. 62) bits of classical (resp. quantum) security. Going to 128 / 64 bits of classical / quantum security, i.e. NIST level 1 (in the ROM/QROM), is an ongoing effort. It requires either: - hash digests of 5 field elements (drawback: we need to double the hash chain length from 8 to 16 if we want to stay below one IPv6 MTU = 1280 bytes) - a new prime, close to 32 bits (typically p = 125.2^25 + 1) or 64 bits ([goldilocks](https://2π.com/22/goldilocks/)) diff --git a/crates/xmss/xmss.md b/crates/xmss/xmss.md new file mode 100644 index 000000000..5dafc4b24 --- /dev/null +++ b/crates/xmss/xmss.md @@ -0,0 +1,48 @@ +# XMSS high-level specification + +## Field + +KoalaBear (p = 2^31 - 2^24 + 1). + +## Hash function + +[Poseidon](https://eprint.iacr.org/2019/458), in compression mode (feedforward addition). Input: 16 field elements. Output: 8 field elements. We denote it `H`. Chain hashes, Merkle hashes, and the final WOTS-pubkey hash truncate the output to 4 field elements (`n`); the encoding step and the intermediate WOTS-pubkey sponge states keep the full 8 elements. + +## Sizes (in field elements) + +- `n = 4`: digest size +- `|pp| = 4`: public parameter +- `|randomness| = 6`: signature randomness +- `|msg| = 8`: message size +- `|tweak| = 2`: tweak (domain separation: `encoding`, `chain`, `wots_pk`, `merkle`) + +## WOTS (Winternitz One Time Signature) + +- `v = 42`: number of hash chains +- `w = 3`, `chain_length = 2^w = 8` +- `target_sum = 184`: a WOTS encoding `(e_0, ..., e_{v-1})` is valid iff each `e_i < chain_length` and `sum(e_i) = target_sum`. The signer grinds `randomness` until the encoding is valid (avoids checksum chains). + +## XMSS + +`log_lifetime = 32`: a key is valid for up to `2^32` slots. `log_lifetime` corresponds to the Merkle tree height. + +## Verification + +Inputs: public key `(merkle_root, pp)`, message `msg`, slot `s`, signature `(chain_tips, randomness, merkle_proof)`. + +1. **Encode**: compute the 8-limb digest `D = H(H(msg | randomness | tweak_encoding(s)) | pp | 0)`. Reject if any limb of `D` equals `-1` (for a uniform sampling). For each limb, take its canonical representative's low 24 bits in little-endian order, concatenate to get 192 bits, then take the first `v · w = 126` bits split into `v = 42` little-endian chunks of `w = 3` bits → encoding `(e_0, ..., e_{v-1})` with each `e_i ∈ [0, chain_length)`. Reject if `sum(e_i) ≠ target_sum`. +2. **Recover WOTS public key**: for each `i`, walk chain `i` from `chain_tips[i]` for `chain_length - 1 - e_i` steps, where each step is `H(tweak_chain(i, step, s) | 00 | acc | pp | 0000)` truncated to `n`. +3. **Hash WOTS public key**: T-sponge with replacement over the `v` recovered chain ends, with IV `[tweak_wots_pk(s) | 00 | pp]`, ingesting two chain end digests at a time. Output is the Merkle leaf. +4. **Walk Merkle path**: for `level = 0..log_lifetime`, combine the current node with `merkle_proof[level]` (left/right determined by bit `level` of `s`) via `H(tweak_merkle(level+1, parent_index) | 00 | pp | left | right)` truncated to `n`. +5. **Check root**: accept iff the final hash equals `merkle_root`. + + +## Security + +target = 123,9 ≈ 124 bits of classical security in the ROM, and ≈ 62 bits of quantum security in the QROM, with an analysis inspired by the section 3.1 of [Tight adaptive reprogramming in the QROM](https://arxiv.org/pdf/2010.15103). TODO write it on paper. + +## Signature size + + **1171 bytes** `log2(p).(|randomness| + n.(v + log_lifetime))` + +below IPv6 [MTU](https://fr.wikipedia.org/wiki/Maximum_transmission_unit) (1280 bytes) From c5bd2eaa21d5dec945c51b3c899c47f3b3957b1f Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sat, 2 May 2026 18:05:17 +0200 Subject: [PATCH 64/66] comment --- crates/lean_vm/src/tables/extension_op/mod.rs | 4 +--- crates/lean_vm/src/tables/mod.rs | 8 ++++++++ crates/lean_vm/src/tables/poseidon_16/mod.rs | 14 +------------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/crates/lean_vm/src/tables/extension_op/mod.rs b/crates/lean_vm/src/tables/extension_op/mod.rs index c50ac663d..03cc0045c 100644 --- a/crates/lean_vm/src/tables/extension_op/mod.rs +++ b/crates/lean_vm/src/tables/extension_op/mod.rs @@ -6,9 +6,7 @@ use air::*; mod exec; pub use exec::fill_trace_extension_op; -// domain separation: Poseidon16=1, Poseidon24= 2 or 3 or 4, ExtensionOp>=8 -/// Extension op PRECOMPILE_DATA bit-field encoding: -/// aux = 4*is_be + 8*flag_add + 16*flag_mul + 32*flag_poly_eq + 64*len +// `PRECOMPILE_DATA` encoding: see `tables/mod.rs`. pub(crate) const EXT_OP_FLAG_IS_BE: usize = 4; pub(crate) const EXT_OP_FLAG_ADD: usize = 8; pub(crate) const EXT_OP_FLAG_MUL: usize = 16; diff --git a/crates/lean_vm/src/tables/mod.rs b/crates/lean_vm/src/tables/mod.rs index 3010d39fd..bf9523291 100644 --- a/crates/lean_vm/src/tables/mod.rs +++ b/crates/lean_vm/src/tables/mod.rs @@ -15,3 +15,11 @@ pub use execution::*; mod utils; pub(crate) use utils::*; + +// `PRECOMPILE_DATA` is the bus discriminator separating the two precompile +// tables. Disjointness is by parity of bit 0: +// +// Poseidon16 (odd): 1 + 2·flag_half + 4·flag_left + 8·flag_left·offset_left +// ExtensionOp (even): 4·is_be + 8·flag_add + 16·flag_mul + 32·flag_poly_eq + 64·len +// +// Multiplying `offset_left` by `flag_left` is needed for soundness: see 3.4.1 in minimal_zkVM.pdf diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index fcd50d08b..5cffe5194 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -89,19 +89,7 @@ const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; -/// Encoding of `PRECOMPILE_DATA` for the Poseidon16 precompile: -/// -/// ```text -/// precompile_data = -/// POSEIDON_PRECOMPILE_DATA -/// + POSEIDON_HALF_OUTPUT_SHIFT * flag_half_output -/// + POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT * flag_hardcoded_left_4 -/// + flag_hardcoded_left_4 * POSEIDON_HARDCODED_LEFT_4_OFFSET_SHIFT * offset_hardcoded -/// ``` -/// -/// the last multiplication by flag_hardcoded_left_4 is crucial for soundness. -/// - when flag_hardcoded_left_4 = 1, offset_hardcoded is constrained to be < 2^MAX_LOG_MEMORY_SIZE -> no overflow modulo p -/// - but when flag_hardcoded_left_4 = 0, there isnt this constraint, so we need to need to prevent an attacker to use an overflow to "spoof" the precompile_data encoding +// `PRECOMPILE_DATA` encoding: see `tables/mod.rs`. pub const POSEIDON_PRECOMPILE_DATA: usize = 1; pub const POSEIDON_HALF_OUTPUT_SHIFT: usize = 1 << 1; pub const POSEIDON_HARDCODED_LEFT_4_FLAG_SHIFT: usize = 1 << 2; From 684d0b56ec2e48631075424c5d47ab9f2adc6022 Mon Sep 17 00:00:00 2001 From: Tom Wambsgans Date: Sun, 3 May 2026 13:35:13 +0200 Subject: [PATCH 65/66] update benchmarks --- README.md | 22 ++++++++++++---------- misc/images/fancy-aggregation.png | Bin 346001 -> 345118 bytes src/bench.sh | 10 ++++++---- src/main.rs | 10 +++++----- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2e14555f4..e46ecedd5 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ cargo run --release -- xmss --n-signatures 1500 --log-inv-rate 1 | WHIR rate | Proven Regime | Proximity Gaps Conjecture | | --------- | --------------------- | ------------------------- | -| 1/2 | 1193 XMSS/s - 377 KiB | 1207 XMSS/s - 191 KiB | -| 1/4 | 863 XMSS/s - 243 KiB | 872 XMSS/s - 129 KiB | +| 1/2 | 1319 XMSS/s - 338 KiB | 1345 XMSS/s - 176 KiB | +| 1/4 | 961 XMSS/s - 228 KiB | 969 XMSS/s - 126 KiB | + (Proving throughput - proof size) @@ -49,14 +50,15 @@ cargo run --release -- recursion --n 2 --log-inv-rate 2 | n | WHIR rate | Proven Regime | Proximity Gaps Conjecture | | --- | --------- | --------------------------- | --------------------------- | -| 1 | 1/2 | 0.35s = 1 x 0.35s - 256 KiB | 0.24s = 1 x 0.24s - 146 KiB | -| 1 | 1/4 | 0.33s = 1 x 0.33s - 183 KiB | 0.26s = 1 x 0.26s - 98 KiB | -| 2 | 1/2 | 0.65s = 2 x 0.33s - 272 KiB | 0.43s = 2 x 0.21s - 157 KiB | -| 2 | 1/4 | 0.56s = 2 x 0.28s - 190 KiB | 0.41s = 2 x 0.21s - 101 KiB | -| 3 | 1/2 | 0.83s = 3 x 0.28s - 303 KiB | 0.62s = 3 x 0.21s - 150 KiB | -| 3 | 1/4 | 0.86s = 3 x 0.29s - 192 KiB | 0.71s = 3 x 0.24s - 107 KiB | -| 4 | 1/2 | 1.23s = 4 x 0.31s - 327 KiB | 0.76s = 4 x 0.19s - 166 KiB | -| 4 | 1/4 | 1.01s = 4 x 0.25s - 200 KiB | 0.76s = 4 x 0.19s - 106 KiB | +| 1 | 1/2 | 0.39s = 1 x 0.39s - 278 KiB | 0.24s = 1 x 0.24s - 147 KiB | +| 1 | 1/4 | 0.32s = 1 x 0.32s - 188 KiB | 0.27s = 1 x 0.27s - 100 KiB | +| 2 | 1/2 | 0.7s = 2 x 0.35s - 293 KiB | 0.43s = 2 x 0.21s - 157 KiB | +| 2 | 1/4 | 0.56s = 2 x 0.28s - 194 KiB | 0.43s = 2 x 0.22s - 102 KiB | +| 3 | 1/2 | 0.85s = 3 x 0.28s - 312 KiB | 0.63s = 3 x 0.21s - 150 KiB | +| 3 | 1/4 | 0.94s = 3 x 0.31s - 203 KiB | 0.73s = 3 x 0.24s - 108 KiB | +| 4 | 1/2 | 1.27s = 4 x 0.32s - 308 KiB | 0.78s = 4 x 0.2s - 166 KiB | +| 4 | 1/4 | 1.02s = 4 x 0.26s - 206 KiB | 0.79s = 4 x 0.2s - 108 KiB | + (time for n->1 recursive aggregation - proof size) diff --git a/misc/images/fancy-aggregation.png b/misc/images/fancy-aggregation.png index 711939507dedf2adc600aa8cb17cde785114c1a3..2b729a0ff1cf63256a098b97bf59ad1d2808de15 100644 GIT binary patch literal 345118 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