Skip to content

Commit

Permalink
Circuit optimizations (#41)
Browse files Browse the repository at this point in the history
* use max lookup bits for circuit degree

* remove y coord check

* tweak poseidon

* half number of poseidon hash inputs

* add step aggregation

* cargo fix

* cargo fix + fmt
  • Loading branch information
nulltea authored Dec 12, 2023
1 parent b740691 commit 5c2ac0d
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 105 deletions.
2 changes: 1 addition & 1 deletion eth-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ use halo2curves::ff::PrimeField;
pub use spec::{Mainnet, Minimal, Spec, Testnet};

pub const NUM_LIMBS: usize = 4;
pub const LIMB_BITS: usize = 112;
pub const LIMB_BITS: usize = 104;

pub trait Field = BigPrimeField + PrimeField<Repr = [u8; 32]>;
25 changes: 9 additions & 16 deletions lightclient-circuits/src/committee_update_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ use crate::{
};
use eth_types::{Field, Spec, LIMB_BITS, NUM_LIMBS};
use halo2_base::{
gates::{
circuit::CircuitBuilderStage, flex_gate::threads::CommonCircuitBuilder, RangeInstructions,
},
gates::{circuit::CircuitBuilderStage, flex_gate::threads::CommonCircuitBuilder},
halo2_proofs::{halo2curves::bn256, plonk::Error},
AssignedValue, Context, QuantumCell,
};
Expand Down Expand Up @@ -60,7 +58,7 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {

let poseidon_commit = {
let pubkeys_x = Self::decode_pubkeys_x(builder.main(), fp_chip, compressed_encodings);
fq_array_poseidon(builder.main(), range.gate(), &pubkeys_x)?
fq_array_poseidon(builder.main(), fp_chip, &pubkeys_x)?
};

// Finalized header
Expand Down Expand Up @@ -165,19 +163,14 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
where
[(); S::SYNC_COMMITTEE_SIZE]:,
{
let pubkeys_x = args
.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")
})
.collect_vec();
let pubkeys_x = args.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")
});

let poseidon_commitment =
fq_array_poseidon_native::<bn256::Fr>(pubkeys_x.into_iter(), limb_bits).unwrap();
fq_array_poseidon_native::<bn256::Fr>(pubkeys_x, limb_bits).unwrap();

let mut pk_vector: Vector<Vector<u8, 48>, { S::SYNC_COMMITTEE_SIZE }> = args
.pubkeys_compressed
Expand Down Expand Up @@ -219,7 +212,7 @@ impl<S: Spec> AppCircuit for CommitteeUpdateCircuit<S, bn256::Fr> {
let mut builder = Eth2CircuitBuilder::<ShaBitGateManager<bn256::Fr>>::from_stage(stage)
.use_k(k as usize)
.use_instance_columns(1);
let range = builder.range_chip(8);
let range = builder.range_chip(k as usize - 1);
let fp_chip = FpChip::new(&range, LIMB_BITS, NUM_LIMBS);

let assigned_instances = Self::synthesize(&mut builder, &fp_chip, witness)?;
Expand Down
4 changes: 2 additions & 2 deletions lightclient-circuits/src/gadget/crypto/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ pub struct SHAConfig<F: Field, CustomConfig: GateBuilderConfig<F>> {

impl<F: Field, GateConfig: GateBuilderConfig<F>> SHAConfig<F, GateConfig> {
pub fn configure(meta: &mut ConstraintSystem<F>, params: BaseCircuitParams) -> Self {
let base = BaseConfig::configure(meta, params);
let compression = GateConfig::configure(meta);
let base = BaseConfig::configure(meta, params.clone());
let compression = GateConfig::configure(meta, params);

Self { base, compression }
}
Expand Down
4 changes: 3 additions & 1 deletion lightclient-circuits/src/gadget/crypto/sha256_flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ impl<'a, F: Field> HashInstructions<F> for Sha256Chip<'a, F> {

impl<'a, F: Field> Sha256Chip<'a, F> {
pub fn new(range: &'a RangeChip<F>) -> Self {
let lookup_bits = if range.lookup_bits() > 8 { 16 } else { 8 };

Self {
spread: SpreadChip::new(range),
spread: SpreadChip::new(range, lookup_bits),
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions lightclient-circuits/src/gadget/crypto/sha256_flex/spread.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use eth_types::Field;
use halo2_base::gates::circuit::BaseCircuitParams;
use halo2_base::utils::{decompose, ScalarField};
use halo2_base::QuantumCell;
use halo2_base::{
Expand Down Expand Up @@ -78,8 +79,12 @@ impl<F: Field> SpreadConfig<F> {
}

impl<F: Field> GateBuilderConfig<F> for SpreadConfig<F> {
fn configure(meta: &mut ConstraintSystem<F>) -> Self {
Self::configure(meta, 8, 1) // todo: configure
fn configure(meta: &mut ConstraintSystem<F>, params: BaseCircuitParams) -> Self {
let lookup_bits = params
.lookup_bits
.map_or(8, |lookup_bits| if lookup_bits > 8 { 16 } else { 8 });

Self::configure(meta, lookup_bits, 1) // todo: configure num_advice_columns
}

fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
Expand Down Expand Up @@ -126,22 +131,23 @@ impl<F: Field> GateBuilderConfig<F> for SpreadConfig<F> {

#[derive(Debug, Clone)]
pub struct SpreadChip<'a, F: ScalarField> {
lookup_bits: usize,
range: &'a RangeChip<F>,
}

impl<'a, F: Field> SpreadChip<'a, F> {
pub fn new(range: &'a RangeChip<F>) -> Self {
debug_assert_eq!(16 % range.lookup_bits(), 0);
pub fn new(range: &'a RangeChip<F>, lookup_bits: usize) -> Self {
debug_assert_eq!(16 % lookup_bits, 0);

Self { range }
Self { range, lookup_bits }
}
pub fn spread(
&self,
thread_pool: &mut ShaCircuitBuilder<F, ShaFlexGateManager<F>>,
dense: &AssignedValue<F>,
) -> Result<AssignedValue<F>, Error> {
let gate = self.range.gate();
let limb_bits = self.range.lookup_bits();
let limb_bits = self.lookup_bits;
let num_limbs = 16 / limb_bits;
let limbs = decompose(dense.value(), num_limbs, limb_bits);
let assigned_limbs = thread_pool.main().assign_witnesses(limbs);
Expand Down
9 changes: 6 additions & 3 deletions lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use crate::util::{CommonGateManager, GateBuilderConfig};
use eth_types::Field;
use getset::CopyGetters;
use halo2_base::{
gates::circuit::CircuitBuilderStage,
halo2_proofs::{circuit::Region, plonk::Error},
gates::circuit::{BaseCircuitParams, CircuitBuilderStage},
halo2_proofs::{
circuit::Region,
plonk::{ConstraintSystem, Error},
},
virtual_region::{
copy_constraints::SharedCopyConstraintManager, manager::VirtualRegionManager,
},
Expand Down Expand Up @@ -182,7 +185,7 @@ impl<F: Field> ShaBitGateManager<F> {
}

impl<F: Field> GateBuilderConfig<F> for Sha256CircuitConfig<F> {
fn configure(meta: &mut halo2_base::halo2_proofs::plonk::ConstraintSystem<F>) -> Self {
fn configure(meta: &mut ConstraintSystem<F>, _: BaseCircuitParams) -> Self {
Sha256CircuitConfig::new(meta)
}

Expand Down
65 changes: 31 additions & 34 deletions lightclient-circuits/src/poseidon.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
use eth_types::{Field, LIMB_BITS};
use halo2_base::{
gates::GateInstructions, halo2_proofs::halo2curves::bn256, halo2_proofs::plonk::Error,
poseidon::hasher::PoseidonSponge, AssignedValue, Context,
poseidon::hasher::PoseidonSponge, AssignedValue, Context, QuantumCell,
};
use halo2_ecc::bigint::ProperCrtUint;
use halo2_ecc::{bigint::ProperCrtUint, bls12_381::FpChip, fields::FieldChip};
use halo2curves::{
bls12_381::{self, Fq},
group::UncompressedEncoding,
};
use itertools::Itertools;
use pse_poseidon::Poseidon as PoseidonNative;

const POSEIDON_SIZE: usize = 16;
// Using recommended parameters from whitepaper https://eprint.iacr.org/2019/458.pdf (table 2, table 8)
// Generated by https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/calc_round_numbers.py
// And rounded up to nearest integer that divides by t
const N_ROUNDS_PC: [usize; 16] = [
56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68,
];

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;
const R_P: usize = 68;

pub fn fq_array_poseidon<'a, F: Field>(
ctx: &mut Context<F>,
gate: &impl GateInstructions<F>,
fp_chip: &FpChip<F>,
fields: impl IntoIterator<Item = &'a ProperCrtUint<F>>,
) -> Result<AssignedValue<F>, Error> {
let gate = fp_chip.gate();
let limbs_bases = fp_chip.limb_bases[..2]
.iter()
.map(|c| QuantumCell::Constant(*c))
.collect_vec();

let limbs = fields
.into_iter()
.flat_map(|f| f.limbs())
.copied()
.flat_map(|f| {
// Fold 4 limbs into 2 to reduce number of posedidon inputs in half.
f.limbs()
.chunks(2)
.map(|limbs| gate.inner_product(ctx, limbs.to_vec(), limbs_bases.clone()))
.collect_vec()
})
.collect_vec();

let mut poseidon =
PoseidonSponge::<F, POSEIDON_SIZE, { POSEIDON_SIZE - 1 }>::new::<R_F, R_P, 0>(ctx);
let mut poseidon = PoseidonSponge::<F, T, POSEIDON_SIZE>::new::<R_F, R_P, 0>(ctx);

let mut current_poseidon_hash = None;

for (i, chunk) in limbs.chunks(POSEIDON_SIZE - 3).enumerate() {
for (i, chunk) in limbs.chunks(POSEIDON_SIZE - 2).enumerate() {
poseidon.update(chunk);
if i != 0 {
poseidon.update(&[current_poseidon_hash.unwrap()]);
Expand All @@ -50,16 +68,15 @@ pub fn fq_array_poseidon_native<F: Field>(
// Converts Fq elements to Fr limbs.
.flat_map(|x| {
x.to_bytes_le()
.chunks(limb_bits / 8)
.chunks((limb_bits / 8) * 2)
.map(F::from_bytes_le)
.collect_vec()
})
.collect_vec();

let mut poseidon = PoseidonNative::<F, POSEIDON_SIZE, { POSEIDON_SIZE - 1 }>::new(R_F, R_P);
let mut poseidon = PoseidonNative::<F, T, POSEIDON_SIZE>::new(R_F, R_P);
let mut current_poseidon_hash = None;

for (i, chunk) in limbs.chunks(POSEIDON_SIZE - 3).enumerate() {
for (i, chunk) in limbs.chunks(POSEIDON_SIZE - 2).enumerate() {
poseidon.update(chunk);
if i != 0 {
poseidon.update(&[current_poseidon_hash.unwrap()]);
Expand All @@ -69,26 +86,6 @@ pub fn fq_array_poseidon_native<F: Field>(
Ok(current_poseidon_hash.unwrap())
}

pub fn poseidon_sponge<F: Field>(
ctx: &mut Context<F>,
gate: &impl GateInstructions<F>,
elems: Vec<AssignedValue<F>>,
) -> Result<AssignedValue<F>, Error> {
let mut poseidon =
PoseidonSponge::<F, POSEIDON_SIZE, { POSEIDON_SIZE - 1 }>::new::<R_F, R_P, 0>(ctx);
let mut current_poseidon_hash = None;

for (i, chunk) in elems.chunks(POSEIDON_SIZE - 3).enumerate() {
poseidon.update(chunk);
if i != 0 {
poseidon.update(&[current_poseidon_hash.unwrap()]);
}
let _ = current_poseidon_hash.insert(poseidon.squeeze(ctx, gate));
}

Ok(current_poseidon_hash.unwrap())
}

pub fn poseidon_committee_commitment_from_uncompressed(
pubkeys_uncompressed: &[Vec<u8>],
) -> Result<[u8; 32], Error> {
Expand Down
Loading

0 comments on commit 5c2ac0d

Please sign in to comment.