Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSZ Multiproof #57

Open
wants to merge 37 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
700583e
multiproof helpers
ec2 Jan 18, 2024
5ca9cdd
leaves for block header
ec2 Jan 18, 2024
282dd5e
in circuit multiproofs and step circuit integration
ec2 Jan 18, 2024
f195e04
some comments
ec2 Jan 18, 2024
67a9446
gen test data using rust
ec2 Jan 23, 2024
9ff59e1
add new test data
ec2 Jan 23, 2024
b91c039
clippy
ec2 Jan 23, 2024
1a00a19
more lints
ec2 Jan 23, 2024
7a136e6
remove typescript
ec2 Jan 23, 2024
35ec5c7
Feature gate test gen to pull in less deps
ec2 Jan 23, 2024
a44fa8d
Merge branch 'ec2/unit-test-gen' into ec2/ssz-multiproof
ec2 Jan 23, 2024
8784d0d
Merge branch 'main' into ec2/ssz-multiproof
ec2 Jan 23, 2024
6582bf9
fix unit tests for step circuit
ec2 Jan 23, 2024
ad80976
fix unit tests for committee update circuits
ec2 Jan 23, 2024
62c96be
Fix spec tests
ec2 Jan 24, 2024
27bdb04
some cleanup
ec2 Jan 24, 2024
4ef20b3
clean up
ec2 Jan 24, 2024
dcc5f2c
lint
ec2 Jan 24, 2024
17d03ff
remove preprocessor mock circuit tests in favor of testing both at th…
ec2 Jan 24, 2024
1183658
fix default trait impl
ec2 Jan 24, 2024
b227de1
Fix cargo.toml for feature flags
ec2 Jan 25, 2024
e36da2e
fix pr comments
ec2 Feb 2, 2024
476a9b7
change config in test
ec2 Feb 2, 2024
f67efe8
fix the rest of pr review
ec2 Feb 2, 2024
335a38c
Merge branch 'main' into ec2/ssz-multiproof
ec2 Feb 20, 2024
44104c0
Merge branch 'develop' into ec2/ssz-multiproof
ec2 Mar 20, 2024
e19071e
finish merge conflicts
ec2 Mar 20, 2024
b4eb920
uipdate submodule
ec2 Mar 20, 2024
2a655ac
Merge branch 'develop' into ec2/ssz-multiproof
ec2 Mar 20, 2024
faae48e
Merge branch 'develop' into ec2/ssz-multiproof
ec2 Apr 5, 2024
b9a5fd6
compiles
ec2 Apr 5, 2024
2765192
fix test to compile
ec2 Apr 5, 2024
e21399d
fmt
ec2 Apr 5, 2024
26e925d
dont pin
ec2 Apr 5, 2024
8d40442
revert some tests
ec2 Apr 5, 2024
9e95e4e
update test params
ec2 Apr 5, 2024
2515ad9
need to finish fixing unit_test_gen
ec2 Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions eth-types/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub trait Spec: 'static + Sized + Copy + Default + Debug {
const FINALIZED_HEADER_DEPTH: usize;
const BYTES_PER_LOGS_BLOOM: usize = 256;
const MAX_EXTRA_DATA_BYTES: usize = 32;

const HEADER_SLOT_INDEX: usize = 8;
const HEADER_STATE_ROOT_INDEX: usize = 11;
const HEADER_BODY_ROOT_INDEX: usize = 12;
}

#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
Expand Down
29 changes: 19 additions & 10 deletions lightclient-circuits/src/committee_update_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{
gadget::crypto::{HashInstructions, Sha256ChipWide, ShaBitGateManager, ShaCircuitBuilder},
poseidon::{fq_array_poseidon, poseidon_hash_fq_array},
ssz_merkle::{ssz_merkleize_chunks, verify_merkle_proof},
ssz_merkle::{ssz_merkleize_chunks, verify_merkle_multiproof, verify_merkle_proof},
sync_step_circuit::clear_3_bits,
util::{AppCircuit, CommonGateManager, Eth2ConfigPinning, IntoWitness},
witness::{self, HashInput, HashInputChunk},
Expand All @@ -30,7 +30,6 @@ use halo2curves::bls12_381;
use itertools::Itertools;
use ssz_rs::{Merkleized, Vector};
use std::{env::var, iter, marker::PhantomData, vec};

/// `CommitteeUpdateCircuit` maps next sync committee SSZ root in the finalized state root to the corresponding Poseidon commitment to the public keys.
///
/// Assumes that public keys are BLS12-381 points on G1; `sync_committee_branch` is exactly `S::SYNC_COMMITTEE_PUBKEYS_DEPTH` hashes in lenght.
Expand Down Expand Up @@ -84,16 +83,26 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();
let finalized_header_root = ssz_merkleize_chunks(
let finalized_header_root = args
.finalized_header
.clone()
.hash_tree_root()
.map_err(|_| Error::Synthesis)?
ec2 marked this conversation as resolved.
Show resolved Hide resolved
.as_ref()
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have this in 5 or more places now (incl step circuit), might be worth putting this in a util method for this?


verify_merkle_multiproof(
builder,
&sha256_chip,
[
args.finalized_header.slot.into_witness(),
args.finalized_header.proposer_index.into_witness(),
args.finalized_header.parent_root.as_ref().into_witness(),
finalized_state_root.clone().into(),
args.finalized_header.body_root.as_ref().into_witness(),
],
args.finalized_header_multiproof
.iter()
.map(|w| w.clone().into_witness()),
vec![finalized_state_root.clone().into()],
ec2 marked this conversation as resolved.
Show resolved Hide resolved
&finalized_header_root,
[S::HEADER_STATE_ROOT_INDEX],
args.finalized_header_helper_indices.clone(),
)?;

// Verify that the sync committee root is in the finalized state root
Expand Down
57 changes: 57 additions & 0 deletions lightclient-circuits/src/ssz_merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Code: https://github.com/ChainSafe/Spectre
// SPDX-License-Identifier: LGPL-3.0-only

use std::collections::HashMap;

use crate::{
gadget::crypto::HashInstructions,
util::IntoConstant,
Expand Down Expand Up @@ -94,6 +96,61 @@ pub fn verify_merkle_proof<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(
Ok(())
}

// Implemented following https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md#merkle-multiproofs
ec2 marked this conversation as resolved.
Show resolved Hide resolved
pub fn verify_merkle_multiproof<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(
builder: &mut CircuitBuilder,
hasher: &impl HashInstructions<F, CircuitBuilder = CircuitBuilder>,
branch: impl IntoIterator<Item = HashInputChunk<QuantumCell<F>>>,
leaves: impl IntoIterator<Item = HashInputChunk<QuantumCell<F>>>,
root: &[AssignedValue<F>],
gindices: impl IntoIterator<Item = usize>,
helper_indices: impl IntoIterator<Item = usize>,
) -> Result<(), Error> {
let mut objects: HashMap<usize, _> = gindices
.into_iter()
.zip(leaves)
.chain(helper_indices.into_iter().zip(branch))
.collect();
let mut keys = objects.keys().copied().collect_vec();
keys.sort_by(|a, b| b.cmp(a));

let mut pos = 0;
while pos < keys.len() {
let k = keys[pos];
// if the sibling exists AND the parent does NOT, we hash
if objects.contains_key(&k)
&& objects.contains_key(&(k ^ 1))
&& !objects.contains_key(&(k / 2))
{
let left = objects[&((k | 1) ^ 1)].clone();
let right = objects[&(k | 1)].clone();
let computed_hash = hasher
.digest(builder, HashInput::TwoToOne(left, right))?
.into();
objects.insert(k / 2, computed_hash);
keys.push(k / 2);
}
pos += 1;
}

let computed_root = objects
.get(&1)
.unwrap()
.clone()
.into_iter()
.map(|b| match b {
QuantumCell::Existing(av) => av,
_ => unreachable!(),
});

computed_root.zip(root.iter()).for_each(|(a, b)| {
builder.main().constrain_equal(&a, b);
});

// Ok(())
ec2 marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}

pub const ZERO_HASHES: [[u8; 32]; 2] = [
[0; 32],
[
Expand Down
51 changes: 38 additions & 13 deletions lightclient-circuits/src/sync_step_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
to_bytes_le,
},
poseidon::{fq_array_poseidon, poseidon_hash_fq_array},
ssz_merkle::{ssz_merkleize_chunks, verify_merkle_proof},
ssz_merkle::{verify_merkle_multiproof, verify_merkle_proof},
util::{AppCircuit, Eth2ConfigPinning, IntoWitness},
witness::{self, HashInput, HashInputChunk, SyncStepArgs},
Eth2CircuitBuilder,
Expand Down Expand Up @@ -106,7 +106,6 @@ impl<S: Spec, F: Field> StepCircuit<S, F> {
assigned_affines.iter().map(|p| &p.x),
)?;

// Compute attested header root
let attested_slot_bytes: HashInputChunk<_> = args.attested_header.slot.into_witness();
let attested_header_state_root = args
.attested_header
Expand All @@ -115,19 +114,31 @@ impl<S: Spec, F: Field> StepCircuit<S, F> {
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();
let attested_header_root = ssz_merkleize_chunks(
let attested_header_root = args
.attested_header
.clone()
.hash_tree_root()
.map_err(|_| Error::Synthesis)?
.as_ref()
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();

verify_merkle_multiproof(
builder,
&sha256_chip,
[
args.attested_header_multiproof
.iter()
.map(|w| w.clone().into_witness()),
vec![
ec2 marked this conversation as resolved.
Show resolved Hide resolved
attested_slot_bytes.clone(),
args.attested_header.proposer_index.into_witness(),
args.attested_header.parent_root.as_ref().into_witness(),
attested_header_state_root.clone().into(),
args.attested_header.body_root.as_ref().into_witness(),
],
&attested_header_root,
[S::HEADER_SLOT_INDEX, S::HEADER_STATE_ROOT_INDEX],
args.attested_header_helper_indices.clone(),
)?;

// Compute finalized header root
let finalized_block_body_root = args
.finalized_header
.body_root
Expand All @@ -136,16 +147,30 @@ impl<S: Spec, F: Field> StepCircuit<S, F> {
.map(|&b| builder.main().load_witness(F::from(b as u64)))
.collect_vec();
let finalized_slot_bytes: HashInputChunk<_> = args.finalized_header.slot.into_witness();
let finalized_header_root = ssz_merkleize_chunks(

let finalized_header_root = args
.finalized_header
.clone()
.hash_tree_root()
.map_err(|_| Error::Synthesis)?
.as_ref()
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();

verify_merkle_multiproof(
builder,
&sha256_chip,
[
args.finalized_header_multiproof
.iter()
.map(|w| w.clone().into_witness()),
vec![
ec2 marked this conversation as resolved.
Show resolved Hide resolved
finalized_slot_bytes.clone(),
args.finalized_header.proposer_index.into_witness(),
args.finalized_header.parent_root.as_ref().into_witness(),
args.finalized_header.state_root.as_ref().into_witness(),
finalized_block_body_root.clone().into(),
],
&finalized_header_root,
[S::HEADER_SLOT_INDEX, S::HEADER_BODY_ROOT_INDEX],
args.finalized_header_helper_indices.clone(),
)?;

let signing_root = sha256_chip.digest(
Expand Down
36 changes: 32 additions & 4 deletions lightclient-circuits/src/witness/multiproof.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// TODO: A lot if not all/most of this code is copy pasta from: https://github.com/ralexstokes/ssz-rs/pull/118 which is mostly implemented w.r.t. the spec
// TODO: Remove this once the above PR lands in ssz-rs

use ethereum_consensus_types::BeaconBlockHeader;
use sha2::{Digest, Sha256};
use ssz_rs::Node;
use ssz_rs::{MerkleizationError, Merkleized, Node};
use std::collections::{HashMap, HashSet};

pub type GeneralizedIndex = usize;
Expand Down Expand Up @@ -149,9 +150,7 @@ pub fn calculate_multi_merkle_root(
hasher.update(left_input.as_ref());
hasher.update(right_input.as_ref());

let parent = objects
.entry(parent_index)
.or_default();
let parent = objects.entry(parent_index).or_default();
parent.as_mut().copy_from_slice(&hasher.finalize_reset());
keys.push(parent_index);
}
Expand Down Expand Up @@ -186,3 +185,32 @@ pub fn create_multiproof(merkle_tree: &[Node], indices_to_prove: &[GeneralizedIn
.map(|i| merkle_tree[i])
.collect()
}

/// Returns nodes representing the leaves a BeaconBlockHeader in merkleized representation.
pub fn block_header_to_leaves(
header: &mut BeaconBlockHeader,
) -> Result<[Node; 5], MerkleizationError> {
Ok([
header.slot.hash_tree_root()?,
header.proposer_index.hash_tree_root()?,
header.parent_root.hash_tree_root()?,
header.state_root.hash_tree_root()?,
header.body_root.hash_tree_root()?,
])
}

pub fn beacon_header_multiproof_and_helper_indices(
header: &mut BeaconBlockHeader,
gindices: &[usize],
) -> (Vec<Node>, Vec<usize>) {
let header_leaves = block_header_to_leaves(header).unwrap();
let merkle_tree = merkle_tree(&header_leaves);
let helper_indices = get_helper_indices(gindices);
let proof = helper_indices
.iter()
.copied()
.map(|i| merkle_tree[i])
.collect::<Vec<_>>();
assert_eq!(proof.len(), helper_indices.len());
(proof, helper_indices)
}
24 changes: 20 additions & 4 deletions lightclient-circuits/src/witness/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{iter, marker::PhantomData};

use crate::witness::beacon_header_multiproof_and_helper_indices;

/// Input datum for the `CommitteeUpdateCircuit` to map next sync committee SSZ root in the finalized state root to the corresponding Poseidon commitment to the public keys.
///
/// Assumes that public keys are BLS12-381 points on G1; `sync_committee_branch` is exactly `S::SYNC_COMMITTEE_PUBKEYS_DEPTH` hashes in lenght.
Expand All @@ -20,6 +22,8 @@ pub struct CommitteeUpdateArgs<S: Spec> {

pub sync_committee_branch: Vec<Vec<u8>>,

pub finalized_header_multiproof: Vec<Vec<u8>>,
pub finalized_header_helper_indices: Vec<usize>,
#[serde(skip)]
pub _spec: PhantomData<S>,
}
Expand Down Expand Up @@ -59,17 +63,29 @@ impl<S: Spec> Default for CommitteeUpdateArgs<S> {
&sync_committee_branch,
S::SYNC_COMMITTEE_PUBKEYS_ROOT_INDEX,
);
let finalized_header = BeaconBlockHeader {
state_root: state_root.as_slice().try_into().unwrap(),
..Default::default()
};

let (finalized_header_multiproof, finalized_header_helper_indices) =
beacon_header_multiproof_and_helper_indices(
&mut finalized_header.clone(),
&[S::HEADER_STATE_ROOT_INDEX],
);

Self {
pubkeys_compressed: iter::repeat(dummy_x_bytes)
.take(S::SYNC_COMMITTEE_SIZE)
.collect_vec(),
sync_committee_branch,
finalized_header: BeaconBlockHeader {
state_root: state_root.as_slice().try_into().unwrap(),
..Default::default()
},
finalized_header,
_spec: PhantomData,
finalized_header_multiproof: finalized_header_multiproof
.into_iter()
.map(|n| n.as_ref().to_vec())
ec2 marked this conversation as resolved.
Show resolved Hide resolved
.collect_vec(),
finalized_header_helper_indices,
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions lightclient-circuits/src/witness/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use std::iter;
use std::marker::PhantomData;
use std::ops::Deref;

use crate::witness::beacon_header_multiproof_and_helper_indices;

use super::mock_root;

/// Input datum for the `StepCircuit` to verify `attested_header` singed by the lightclient sync committee,
Expand Down Expand Up @@ -43,6 +45,11 @@ pub struct SyncStepArgs<S: Spec> {

pub domain: [u8; 32],

pub attested_header_multiproof: Vec<Vec<u8>>,
pub attested_header_helper_indices: Vec<usize>,
pub finalized_header_multiproof: Vec<Vec<u8>>,
pub finalized_header_helper_indices: Vec<usize>,

#[serde(skip)]
pub _spec: PhantomData<S>,
}
Expand Down Expand Up @@ -105,6 +112,19 @@ impl<S: Spec> Default for SyncStepArgs<S> {
.to_uncompressed_be()
.to_vec();

// Proof length is 3
let (attested_header_multiproof, attested_header_helper_indices) =
beacon_header_multiproof_and_helper_indices(
&mut attested_header.clone(),
&[S::HEADER_SLOT_INDEX, S::HEADER_STATE_ROOT_INDEX],
);
// Proof length is 4
let (finalized_header_multiproof, finalized_header_helper_indices) =
beacon_header_multiproof_and_helper_indices(
&mut finalized_header.clone(),
&[S::HEADER_SLOT_INDEX, S::HEADER_BODY_ROOT_INDEX],
);

Self {
signature_compressed,
pubkeys_uncompressed: iter::repeat(pubkey_uncompressed)
Expand All @@ -118,6 +138,17 @@ impl<S: Spec> Default for SyncStepArgs<S> {
execution_payload_branch: execution_branch,
execution_payload_root: execution_root,
_spec: PhantomData,

attested_header_multiproof: attested_header_multiproof
.into_iter()
.map(|n| n.as_ref().to_vec())
.collect_vec(),
attested_header_helper_indices,
finalized_header_multiproof: finalized_header_multiproof
.into_iter()
.map(|n| n.as_ref().to_vec())
.collect_vec(),
finalized_header_helper_indices,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions preprocessor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ itertools.workspace = true
serde_json.workspace = true
serde.workspace = true
ethereum-consensus-types.workspace = true
sha2.workspace = true
# local
eth-types.workspace = true
lightclient-circuits.workspace = true
Expand Down
Loading
Loading