Skip to content

Commit

Permalink
docs (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltea committed Dec 13, 2023
1 parent 4672ff3 commit 2bd19b6
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .licensesnip
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
The Licensed Work is (c) 2023 ChainSafe
Source: https://github.com/ChainSafe/Spectre
Code: https://github.com/ChainSafe/Spectre
SPDX-License-Identifier: LGPL-3.0-only
6 changes: 3 additions & 3 deletions contract-tests/tests/rotation_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use itertools::Itertools;
use lightclient_circuits::committee_update_circuit::CommitteeUpdateCircuit;
use lightclient_circuits::halo2_proofs::halo2curves::bn256;
use lightclient_circuits::poseidon::poseidon_committee_commitment_from_compressed;
use lightclient_circuits::witness::CommitteeRotationArgs;
use lightclient_circuits::witness::CommitteeUpdateArgs;
use rstest::rstest;
use ssz_rs::prelude::*;
use ssz_rs::Merkleized;
Expand All @@ -24,11 +24,11 @@ abigen!(
);

// CommitteeRotationArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeRotationArgs<Spec>> for RotateInput
impl<Spec: eth_types::Spec> From<CommitteeUpdateArgs<Spec>> for RotateInput
where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
fn from(args: CommitteeUpdateArgs<Spec>) -> Self {
let poseidon_commitment = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
);
Expand Down
6 changes: 3 additions & 3 deletions contracts/rust-abi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ethers::{contract::abigen, types::U256};
use itertools::Itertools;
use lightclient_circuits::{
poseidon::poseidon_committee_commitment_from_compressed,
witness::{CommitteeRotationArgs, SyncStepArgs},
witness::{CommitteeUpdateArgs, SyncStepArgs},
};
use ssz_rs::{Merkleized, Vector};
use std::ops::Deref;
Expand Down Expand Up @@ -63,11 +63,11 @@ impl SyncStepInput {
}

// CommitteeRotationArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeRotationArgs<Spec>> for RotateInput
impl<Spec: eth_types::Spec> From<CommitteeUpdateArgs<Spec>> for RotateInput
where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
fn from(args: CommitteeUpdateArgs<Spec>) -> Self {
let sync_committee_poseidon = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
);
Expand Down
30 changes: 17 additions & 13 deletions lightclient-circuits/src/committee_update_circuit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
gadget::crypto::{HashInstructions, Sha256ChipWide, ShaBitGateManager, ShaCircuitBuilder},
poseidon::{fq_array_poseidon, fq_array_poseidon_native},
poseidon::{fq_array_poseidon, poseidon_hash_fq_array},
ssz_merkle::{ssz_merkleize_chunks, verify_merkle_proof},
sync_step_circuit::clear_3_bits,
util::{AppCircuit, CommonGateManager, Eth2ConfigPinning, IntoWitness},
Expand All @@ -23,7 +23,9 @@ use itertools::Itertools;
use ssz_rs::{Merkleized, Vector};
use std::{env::var, iter, marker::PhantomData, vec};

#[allow(type_alias_bounds)]
/// `CommitteeUpdateCircuit` maps next sync committee SSZ root in the finalized state root to the corresponding Poseidon commitment to the public keys.
///
///
#[derive(Clone, Debug, Default)]
pub struct CommitteeUpdateCircuit<S: Spec, F: Field> {
_f: PhantomData<F>,
Expand All @@ -34,7 +36,7 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
fn synthesize(
builder: &mut ShaCircuitBuilder<F, ShaBitGateManager<F>>,
fp_chip: &FpChip<F>,
args: &witness::CommitteeRotationArgs<S>,
args: &witness::CommitteeUpdateArgs<S>,
) -> Result<Vec<AssignedValue<F>>, Error> {
let range = fp_chip.range();

Expand Down Expand Up @@ -156,8 +158,10 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
ssz_merkleize_chunks(builder, hasher, pubkeys_hashes)
}

// Computes public inputs to `CommitteeUpdateCircuit` matching the in-circuit logic from `synthesise` method.
// Note, this function outputes only instances of the `CommitteeUpdateCircuit` proof, not the aggregated proof which will also include 12 accumulator limbs.
pub fn get_instances(
args: &witness::CommitteeRotationArgs<S>,
args: &witness::CommitteeUpdateArgs<S>,
limb_bits: usize,
) -> Vec<Vec<bn256::Fr>>
where
Expand All @@ -169,7 +173,7 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
.expect("bad bls12_381::Fq encoding")
});

let poseidon_commitment = fq_array_poseidon_native::<bn256::Fr>(pubkeys_x, limb_bits);
let poseidon_commitment = poseidon_hash_fq_array::<bn256::Fr>(pubkeys_x, limb_bits);

let mut pk_vector: Vector<Vector<u8, 48>, { S::SYNC_COMMITTEE_SIZE }> = args
.pubkeys_compressed
Expand Down Expand Up @@ -200,12 +204,12 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {

impl<S: Spec> AppCircuit for CommitteeUpdateCircuit<S, bn256::Fr> {
type Pinning = Eth2ConfigPinning;
type Witness = witness::CommitteeRotationArgs<S>;
type Witness = witness::CommitteeUpdateArgs<S>;

fn create_circuit(
stage: CircuitBuilderStage,
pinning: Option<Self::Pinning>,
witness: &witness::CommitteeRotationArgs<S>,
witness: &witness::CommitteeUpdateArgs<S>,
k: u32,
) -> Result<impl crate::util::PinnableCircuit<bn256::Fr>, Error> {
let mut builder = Eth2CircuitBuilder::<ShaBitGateManager<bn256::Fr>>::from_stage(stage)
Expand Down Expand Up @@ -244,7 +248,7 @@ mod tests {

use crate::{
aggregation_circuit::AggregationConfigPinning, util::Halo2ConfigPinning,
witness::CommitteeRotationArgs,
witness::CommitteeUpdateArgs,
};

use super::*;
Expand All @@ -263,7 +267,7 @@ mod tests {
use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk};
use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, CircuitExt, Snark};

fn load_circuit_args() -> CommitteeRotationArgs<Testnet> {
fn load_circuit_args() -> CommitteeUpdateArgs<Testnet> {
#[derive(serde::Deserialize)]
struct ArgsJson {
finalized_header: BeaconBlockHeader,
Expand All @@ -277,7 +281,7 @@ mod tests {
finalized_header,
} = serde_json::from_slice(&fs::read("../test_data/rotation_512.json").unwrap()).unwrap();

CommitteeRotationArgs {
CommitteeUpdateArgs {
pubkeys_compressed,
_spec: PhantomData,
finalized_header,
Expand All @@ -288,7 +292,7 @@ mod tests {
fn gen_application_snark(
params: &ParamsKZG<bn256::Bn256>,
pk: &ProvingKey<bn256::G1Affine>,
witness: &CommitteeRotationArgs<Testnet>,
witness: &CommitteeUpdateArgs<Testnet>,
pinning_path: &str,
) -> Snark {
CommitteeUpdateCircuit::<Testnet, Fr>::gen_snark_shplonk(
Expand Down Expand Up @@ -335,7 +339,7 @@ mod tests {
PKEY_PATH,
PINNING_PATH,
false,
&CommitteeRotationArgs::<Testnet>::default(),
&CommitteeUpdateArgs::<Testnet>::default(),
);

let witness = load_circuit_args();
Expand Down Expand Up @@ -363,7 +367,7 @@ mod tests {
APP_PK_PATH,
APP_PINNING_PATH,
false,
&CommitteeRotationArgs::<Testnet>::default(),
&CommitteeUpdateArgs::<Testnet>::default(),
);

let witness = load_circuit_args();
Expand Down
File renamed without changes.
20 changes: 17 additions & 3 deletions lightclient-circuits/src/poseidon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@ const N_ROUNDS_PC: [usize; 16] = [
56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68,
];

// Empirically chosen to take the least space in circuit.
const POSEIDON_SIZE: usize = 11;
const T: usize = POSEIDON_SIZE + 1;
const R_P: usize = N_ROUNDS_PC[T - 2];
const R_F: usize = 8;

/// Generates Poseidon hash commitment to a list of BLS12-381 Fq elements.
///
/// Fields elements are initially represented as `NUM_LIMBS` limbs of `LIMB_BITS` bits each.
/// By composing element two limbs in one, we reduce the number of inputs to Poseidon in half.
///
/// Each Poseidon sponge absorbs `POSEIDON_SIZE`-2 elements and previos sponge output if it's not the first batch, ie. onion commitment.
///
/// Assumes that LIMB_BITS * 2 < 254 (BN254).
pub fn fq_array_poseidon<'a, F: Field>(
ctx: &mut Context<F>,
fp_chip: &FpChip<F>,
Expand Down Expand Up @@ -60,7 +69,10 @@ pub fn fq_array_poseidon<'a, F: Field>(
Ok(current_poseidon_hash.unwrap())
}

pub fn fq_array_poseidon_native<F: Field>(elems: impl Iterator<Item = Fq>, limb_bits: usize) -> F {
/// Generates Poseidon hash commitment to a list of BLS12-381 Fq elements.
///
/// This is the off-circuit analog of `fq_array_poseidon`.
pub fn poseidon_hash_fq_array<F: Field>(elems: impl Iterator<Item = Fq>, limb_bits: usize) -> F {
let limbs = elems
// Converts Fq elements to Fr limbs.
.flat_map(|x| {
Expand All @@ -83,6 +95,7 @@ pub fn fq_array_poseidon_native<F: Field>(elems: impl Iterator<Item = Fq>, limb_
current_poseidon_hash.unwrap()
}

/// Wrapper on `poseidon_hash_fq_array` taking pubkeys encoded as uncompressed bytes.
pub fn poseidon_committee_commitment_from_uncompressed(
pubkeys_uncompressed: &[Vec<u8>],
) -> bn256::Fr {
Expand All @@ -97,14 +110,15 @@ pub fn poseidon_committee_commitment_from_uncompressed(
})
.collect_vec();

fq_array_poseidon_native::<bn256::Fr>(pubkey_affines.iter().map(|p| p.x), LIMB_BITS)
poseidon_hash_fq_array::<bn256::Fr>(pubkey_affines.iter().map(|p| p.x), LIMB_BITS)
}

/// Wrapper on `poseidon_hash_fq_array` taking pubkeys encoded as compressed bytes.
pub fn poseidon_committee_commitment_from_compressed(pubkeys_compressed: &[Vec<u8>]) -> bn256::Fr {
let pubkeys_x = pubkeys_compressed.iter().cloned().map(|mut bytes| {
bytes[0] &= 0b00011111;
bls12_381::Fq::from_bytes_be(&bytes.try_into().unwrap())
.expect("bad bls12_381::Fq encoding")
});
fq_array_poseidon_native::<bn256::Fr>(pubkeys_x, LIMB_BITS)
poseidon_hash_fq_array::<bn256::Fr>(pubkeys_x, LIMB_BITS)
}
34 changes: 13 additions & 21 deletions lightclient-circuits/src/ssz_merkle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{
gadget::crypto::HashInstructions,
util::IntoConstant,
witness::{HashInput, HashInputChunk},
};
use eth_types::Field;
Expand All @@ -10,23 +9,21 @@ use halo2_base::{
};
use itertools::Itertools;

/// Computes Merkle root of a list of SSZ chunks.
///
/// Assumes that number if chunks is a power of two.
pub fn ssz_merkleize_chunks<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(
builder: &mut CircuitBuilder,
hasher: &impl HashInstructions<F, CircuitBuilder = CircuitBuilder>,
chunks: impl IntoIterator<Item = HashInputChunk<QuantumCell<F>>>,
) -> Result<Vec<AssignedValue<F>>, Error> {
let mut chunks = chunks.into_iter().collect_vec();
let len_even = chunks.len() + chunks.len() % 2;
let height = (len_even as f64).log2().ceil() as usize;
for depth in 0..height {
// Pad to even length using 32 zero bytes assigned as constants.
let len_even = chunks.len() + chunks.len() % 2;
let padded_chunks = chunks
.into_iter()
.pad_using(len_even, |_| ZERO_HASHES[depth].as_slice().into_constant())
.collect_vec();
assert!(chunks.len().is_power_of_two());

let height = (chunks.len() as f64).log2() as usize;

chunks = padded_chunks
for _ in 0..height {
chunks = chunks
.into_iter()
.tuples()
.map(|(left, right)| {
Expand All @@ -47,17 +44,20 @@ pub fn ssz_merkleize_chunks<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(
Ok(root.bytes)
}

/// Verifies `leaf` against the `root` using Merkle `branch`. Requires `gindex` for deterministic traversal of the tree.
///
/// Assumes that `root` and `leaf` are 32 bytes each.
pub fn verify_merkle_proof<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(
builder: &mut CircuitBuilder,
hasher: &impl HashInstructions<F, CircuitBuilder = CircuitBuilder>,
proof: impl IntoIterator<Item = HashInputChunk<QuantumCell<F>>>,
branch: impl IntoIterator<Item = HashInputChunk<QuantumCell<F>>>,
leaf: HashInputChunk<QuantumCell<F>>,
root: &[AssignedValue<F>],
mut gindex: usize,
) -> Result<(), Error> {
let mut computed_hash = leaf;

for witness in proof.into_iter() {
for witness in branch.into_iter() {
computed_hash = hasher
.digest(
builder,
Expand All @@ -82,11 +82,3 @@ pub fn verify_merkle_proof<F: Field, CircuitBuilder: CommonCircuitBuilder<F>>(

Ok(())
}

pub const ZERO_HASHES: [[u8; 32]; 2] = [
[0; 32],
[
245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35,
32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75,
],
];
Loading

0 comments on commit 2bd19b6

Please sign in to comment.