use alloy::{
primitives::{keccak256, Bytes, B256},
sol_types::SolValue,
};
use newton_core::bn254_certificate_verifier::{
IBN254CertificateVerifierTypes::BN254OperatorInfoWitness, IOperatorTableCalculatorTypes::BN254OperatorInfo,
};
pub const OPERATOR_INFO_LEAF_SALT: u8 = 0x75;
pub fn compute_operator_info_leaf(info: &BN254OperatorInfo) -> B256 {
let encoded_struct = info.abi_encode();
let mut buf = Vec::with_capacity(1 + encoded_struct.len());
buf.push(OPERATOR_INFO_LEAF_SALT);
buf.extend_from_slice(&encoded_struct);
keccak256(&buf)
}
pub fn merkle_root(leaves: &[B256]) -> B256 {
if leaves.is_empty() {
return B256::ZERO;
}
if leaves.len() == 1 {
return leaves[0];
}
let mut layer: Vec<B256> = leaves.to_vec();
let target = next_power_of_two(layer.len());
layer.resize(target, B256::ZERO);
while layer.len() > 1 {
let mut next = Vec::with_capacity(layer.len() / 2);
for pair in layer.chunks_exact(2) {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(pair[0].as_slice());
buf[32..].copy_from_slice(pair[1].as_slice());
next.push(keccak256(buf));
}
layer = next;
}
layer[0]
}
pub fn merkle_proof(leaves: &[B256], index: u32) -> Result<Bytes, WitnessError> {
let n = leaves.len();
if (index as usize) >= n {
return Err(WitnessError::IndexOutOfRange { index, total: n });
}
if n <= 1 {
return Ok(Bytes::new());
}
let mut layer: Vec<B256> = leaves.to_vec();
let target = next_power_of_two(n);
layer.resize(target, B256::ZERO);
let mut proof_bytes = Vec::new();
let mut cur_index = index as usize;
while layer.len() > 1 {
let sibling_index = cur_index ^ 1;
proof_bytes.extend_from_slice(layer[sibling_index].as_slice());
let mut next = Vec::with_capacity(layer.len() / 2);
for pair in layer.chunks_exact(2) {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(pair[0].as_slice());
buf[32..].copy_from_slice(pair[1].as_slice());
next.push(keccak256(buf));
}
layer = next;
cur_index /= 2;
}
Ok(Bytes::from(proof_bytes))
}
pub fn build_witness(operators: &[BN254OperatorInfo], index: u32) -> Result<BN254OperatorInfoWitness, WitnessError> {
let n = operators.len();
if (index as usize) >= n {
return Err(WitnessError::IndexOutOfRange { index, total: n });
}
let leaves: Vec<B256> = operators.iter().map(compute_operator_info_leaf).collect();
let proof = merkle_proof(&leaves, index)?;
Ok(BN254OperatorInfoWitness {
operatorIndex: index,
operatorInfoProof: proof,
operatorInfo: operators[index as usize].clone(),
})
}
pub fn build_non_signer_witnesses(
operators: &[BN254OperatorInfo],
signer_indices: &std::collections::HashSet<usize>,
) -> Result<Vec<BN254OperatorInfoWitness>, WitnessError> {
let leaves: Vec<B256> = operators.iter().map(compute_operator_info_leaf).collect();
let mut out = Vec::new();
for (idx, info) in operators.iter().enumerate() {
if signer_indices.contains(&idx) {
continue;
}
let proof = merkle_proof(&leaves, idx as u32)?;
out.push(BN254OperatorInfoWitness {
operatorIndex: idx as u32,
operatorInfoProof: proof,
operatorInfo: info.clone(),
});
}
out.sort_by_key(|w| w.operatorIndex);
Ok(out)
}
#[derive(Debug, thiserror::Error)]
pub enum WitnessError {
#[error("operator index {index} out of range (operator set size: {total})")]
IndexOutOfRange {
index: u32,
total: usize,
},
}
fn next_power_of_two(n: usize) -> usize {
if n <= 1 {
return 1;
}
let mut p = 1usize;
while p < n {
p <<= 1;
}
p
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::U256;
use newton_core::bn254_certificate_verifier::BN254::G1Point;
fn op_info(x: u8, y: u8, weights: Vec<u64>) -> BN254OperatorInfo {
BN254OperatorInfo {
pubkey: G1Point {
X: U256::from(x),
Y: U256::from(y),
},
weights: weights.into_iter().map(U256::from).collect(),
}
}
#[test]
fn next_pow2_handles_edge_cases() {
assert_eq!(next_power_of_two(0), 1);
assert_eq!(next_power_of_two(1), 1);
assert_eq!(next_power_of_two(2), 2);
assert_eq!(next_power_of_two(3), 4);
assert_eq!(next_power_of_two(4), 4);
assert_eq!(next_power_of_two(5), 8);
assert_eq!(next_power_of_two(8), 8);
assert_eq!(next_power_of_two(9), 16);
}
#[test]
fn single_leaf_root_and_proof() {
let info = op_info(1, 2, vec![100]);
let leaf = compute_operator_info_leaf(&info);
let leaves = vec![leaf];
assert_eq!(merkle_root(&leaves), leaf);
let proof = merkle_proof(&leaves, 0).expect("proof");
assert!(proof.is_empty(), "single-leaf proof must be empty");
}
#[test]
fn two_leaf_tree() {
let info0 = op_info(1, 2, vec![100]);
let info1 = op_info(3, 4, vec![200]);
let leaf0 = compute_operator_info_leaf(&info0);
let leaf1 = compute_operator_info_leaf(&info1);
let leaves = vec![leaf0, leaf1];
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(leaf0.as_slice());
buf[32..].copy_from_slice(leaf1.as_slice());
let expected_root = keccak256(buf);
assert_eq!(merkle_root(&leaves), expected_root);
let proof_0 = merkle_proof(&leaves, 0).expect("proof");
assert_eq!(&proof_0[..], leaf1.as_slice(), "proof for leaf 0 is leaf 1");
let proof_1 = merkle_proof(&leaves, 1).expect("proof");
assert_eq!(&proof_1[..], leaf0.as_slice(), "proof for leaf 1 is leaf 0");
}
#[test]
fn three_leaf_tree_pads_with_zero() {
let info0 = op_info(1, 2, vec![100]);
let info1 = op_info(3, 4, vec![200]);
let info2 = op_info(5, 6, vec![300]);
let leaf0 = compute_operator_info_leaf(&info0);
let leaf1 = compute_operator_info_leaf(&info1);
let leaf2 = compute_operator_info_leaf(&info2);
let leaves = vec![leaf0, leaf1, leaf2];
let h_01 = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(leaf0.as_slice());
buf[32..].copy_from_slice(leaf1.as_slice());
keccak256(buf)
};
let h_2_zero = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(leaf2.as_slice());
keccak256(buf)
};
let expected_root = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(h_01.as_slice());
buf[32..].copy_from_slice(h_2_zero.as_slice());
keccak256(buf)
};
assert_eq!(merkle_root(&leaves), expected_root);
let proof_2 = merkle_proof(&leaves, 2).expect("proof");
assert_eq!(proof_2.len(), 64, "depth-2 tree → 2 sibling hashes");
assert_eq!(&proof_2[0..32], B256::ZERO.as_slice(), "leaf 2 sibling is zero pad");
assert_eq!(
&proof_2[32..64],
h_01.as_slice(),
"leaf 2's level-1 sibling is H(L0, L1)"
);
let proof_0 = merkle_proof(&leaves, 0).expect("proof");
assert_eq!(proof_0.len(), 64);
assert_eq!(&proof_0[0..32], leaf1.as_slice());
assert_eq!(&proof_0[32..64], h_2_zero.as_slice());
}
#[test]
fn four_leaf_tree_no_padding() {
let infos: Vec<BN254OperatorInfo> = (0u8..4).map(|i| op_info(i, i + 1, vec![100 + i as u64])).collect();
let leaves: Vec<B256> = infos.iter().map(compute_operator_info_leaf).collect();
let h_01 = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(leaves[0].as_slice());
buf[32..].copy_from_slice(leaves[1].as_slice());
keccak256(buf)
};
let h_23 = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(leaves[2].as_slice());
buf[32..].copy_from_slice(leaves[3].as_slice());
keccak256(buf)
};
let expected_root = {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(h_01.as_slice());
buf[32..].copy_from_slice(h_23.as_slice());
keccak256(buf)
};
assert_eq!(merkle_root(&leaves), expected_root);
let proof_0 = merkle_proof(&leaves, 0).expect("proof");
assert_eq!(proof_0.len(), 64);
assert_eq!(&proof_0[0..32], leaves[1].as_slice());
assert_eq!(&proof_0[32..64], h_23.as_slice());
let proof_3 = merkle_proof(&leaves, 3).expect("proof");
assert_eq!(&proof_3[0..32], leaves[2].as_slice());
assert_eq!(&proof_3[32..64], h_01.as_slice());
}
#[test]
fn proof_round_trips_for_every_leaf_at_every_size() {
for n in 1..=10usize {
let infos: Vec<BN254OperatorInfo> = (0u8..(n as u8)).map(|i| op_info(i, i + 100, vec![i as u64])).collect();
let leaves: Vec<B256> = infos.iter().map(compute_operator_info_leaf).collect();
let root = merkle_root(&leaves);
for idx in 0..n {
let proof = merkle_proof(&leaves, idx as u32).expect("proof");
let mut computed = leaves[idx];
let mut cur_index = idx;
let mut offset = 0;
while offset < proof.len() {
let sibling: [u8; 32] = proof[offset..offset + 32].try_into().expect("32 bytes");
let sibling = B256::from(sibling);
let mut buf = [0u8; 64];
if cur_index % 2 == 0 {
buf[..32].copy_from_slice(computed.as_slice());
buf[32..].copy_from_slice(sibling.as_slice());
} else {
buf[..32].copy_from_slice(sibling.as_slice());
buf[32..].copy_from_slice(computed.as_slice());
}
computed = keccak256(buf);
cur_index /= 2;
offset += 32;
}
assert_eq!(computed, root, "round-trip failed at n={n}, idx={idx}");
}
}
}
#[test]
fn build_non_signer_witnesses_sorts_and_skips_signers() {
let operators: Vec<BN254OperatorInfo> = (0u8..5).map(|i| op_info(i, i + 100, vec![i as u64])).collect();
let signers: std::collections::HashSet<usize> = [1usize, 3].into_iter().collect();
let witnesses = build_non_signer_witnesses(&operators, &signers).expect("build");
let indices: Vec<u32> = witnesses.iter().map(|w| w.operatorIndex).collect();
assert_eq!(indices, vec![0, 2, 4], "non-signers in ascending order");
for (w, expected_idx) in witnesses.iter().zip([0usize, 2, 4]) {
assert_eq!(w.operatorInfo.pubkey, operators[expected_idx].pubkey);
}
}
#[test]
fn build_witness_rejects_out_of_range_index() {
let operators: Vec<BN254OperatorInfo> = vec![op_info(1, 2, vec![100])];
let err = build_witness(&operators, 5).expect_err("out of range");
assert!(matches!(err, WitnessError::IndexOutOfRange { index: 5, total: 1 }));
}
#[test]
fn empty_leaves_returns_zero() {
assert_eq!(merkle_root(&[]), B256::ZERO);
}
#[test]
fn leaf_hash_uses_correct_salt_and_encoding() {
let info = op_info(7, 8, vec![42]);
let leaf = compute_operator_info_leaf(&info);
let encoded = info.abi_encode();
let mut buf = Vec::with_capacity(1 + encoded.len());
buf.push(0x75);
buf.extend_from_slice(&encoded);
let expected = keccak256(&buf);
assert_eq!(leaf, expected);
let mut alt_buf = Vec::with_capacity(1 + encoded.len());
alt_buf.push(0x76);
alt_buf.extend_from_slice(&encoded);
let alt = keccak256(&alt_buf);
assert_ne!(leaf, alt);
}
}