Skip to content

Commit

Permalink
Add missing instances (#18)
Browse files Browse the repository at this point in the history
* add missing instances in step & rotation circuits

* return all 32 bytes as expected by sha chip

* replace onion hashing with input concat

* off-circuit instance generation for concat input hashing  0c6f3f

* refactor poseidon commit native

* replace instance comm with instance vector in rotation circuit

* spel fix
  • Loading branch information
nulltea authored Oct 4, 2023
1 parent 84999c0 commit 9c80785
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 219 deletions.
134 changes: 64 additions & 70 deletions lightclient-circuits/src/committee_update_circuit.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::{env::var, marker::PhantomData, vec};
use std::{env::var, iter, marker::PhantomData, vec};

use crate::{
builder::Eth2CircuitBuilder,
gadget::crypto::{
calculate_ysquared, Fp2Point, FpPoint, G1Chip, G1Point, G2Chip, G2Point, HashInstructions,
HashToCurveCache, HashToCurveChip, Sha256ChipWide, ShaBitThreadBuilder, ShaCircuitBuilder,
},
poseidon::{fq_array_poseidon, g1_array_poseidon_native, poseidon_sponge},
poseidon::{fq_array_poseidon, fq_array_poseidon_native, poseidon_sponge},
ssz_merkle::ssz_merkleize_chunks,
sync_step_circuit::{clear_3_bits, to_bytes_le, truncate_sha256_into_single_elem},
util::{
decode_into_field, gen_pkey, AppCircuit, AssignedValueCell, Challenges, Eth2ConfigPinning,
IntoWitness, ThreadBuilderBase,
Expand All @@ -26,7 +27,7 @@ use halo2_base::{
range::{RangeConfig, RangeStrategy},
},
safe_types::{GateInstructions, RangeChip, RangeInstructions},
utils::{fs::gen_srs, CurveAffineExt},
utils::{fs::gen_srs, CurveAffineExt, ScalarField},
AssignedValue, Context, QuantumCell,
};
use halo2_ecc::{
Expand All @@ -42,7 +43,7 @@ use halo2_proofs::{
poly::{commitment::Params, kzg::commitment::ParamsKZG},
};
use halo2curves::{
bls12_381::{Fq, Fq12, G1Affine, G2Affine, G2Prepared, G1, G2},
bls12_381::{self, Fq, Fq12, G1Affine, G2Affine, G2Prepared, G1, G2},
bn256,
};
use itertools::Itertools;
Expand All @@ -51,7 +52,7 @@ use num_bigint::BigUint;
use pasta_curves::group::{ff, GroupEncoding};
use poseidon::PoseidonChip;
use snark_verifier_sdk::CircuitExt;
use ssz_rs::Merkleized;
use ssz_rs::{Merkleized, Vector};
use sync_committee_primitives::consensus_types::BeaconBlockHeader;

#[allow(type_alias_bounds)]
Expand Down Expand Up @@ -83,89 +84,82 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
})
.collect_vec();

let root =
let committee_root_ssz =
Self::sync_committee_root_ssz(thread_pool, &sha256_chip, compressed_encodings.clone())?;

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

Ok(vec![poseidon_commit])
let public_inputs = iter::once(poseidon_commit)
.chain(committee_root_ssz)
.collect();

Ok(public_inputs)
}

pub fn instance(pubkeys_uncompressed: Vec<Vec<u8>>) -> Vec<Vec<bn256::Fr>> {
let pubkey_affines = pubkeys_uncompressed
pub fn instance(args: &witness::CommitteeRotationArgs<S, F>) -> Vec<Vec<bn256::Fr>> {
let pubkeys_x = args.pubkeys_compressed.iter().cloned().map(|mut bytes| {
bytes[47] &= 0b11111000;
bls12_381::Fq::from_bytes_le(&bytes)
});

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

let mut pk_vector: Vector<Vector<u8, 48>, 512> = args
.pubkeys_compressed
.iter()
.map(|bytes| {
G1Affine::from_compressed_unchecked(&bytes.as_slice().try_into().unwrap()).unwrap()
})
.collect_vec();
let poseidon_commitment = g1_array_poseidon_native::<bn256::Fr>(&pubkey_affines).unwrap();
vec![vec![poseidon_commitment]]
.cloned()
.map(|v| v.try_into().unwrap())
.collect_vec()
.try_into()
.unwrap();

let ssz_root = pk_vector.hash_tree_root().unwrap();

let instance_vec = iter::once(poseidon_commitment)
.chain(ssz_root.0.map(|b| bn256::Fr::from(b as u64)))
.collect();

vec![instance_vec]
}

fn decode_pubkeys_x<I: IntoIterator<Item = Vec<AssignedValue<F>>>>(
fn decode_pubkeys_x(
ctx: &mut Context<F>,
fp_chip: &FpChip<'_, F>,
compressed_encodings: I,
compressed_encodings: impl IntoIterator<Item = Vec<AssignedValue<F>>>,
) -> Vec<ProperCrtUint<F>> {
let range = fp_chip.range();
let gate = fp_chip.gate();

let g1_chip = G1Chip::<F>::new(fp_chip);

let mut pubkeys_x = vec![];

for assigned_bytes in compressed_encodings {
// assertion check for assigned_uncompressed vector to be equal to S::PubKeyCurve::BYTES_COMPRESSED from specification
assert_eq!(assigned_bytes.len(), G1::BYTES_COMPRESSED);

// masked byte from compressed representation
let masked_byte = &assigned_bytes[G1::BYTES_COMPRESSED - 1];
// clear the sign bit from masked byte
let cleared_byte = Self::clear_flag_bits(range, masked_byte, ctx);
// Use the cleared byte to construct the x coordinate
let assigned_x_bytes_cleared = [
&assigned_bytes.as_slice()[..G1::BYTES_COMPRESSED - 1],
&[cleared_byte],
]
.concat();
let x_crt = decode_into_field::<F, G1>(
assigned_x_bytes_cleared,
&fp_chip.limb_bases,
gate,
ctx,
);

pubkeys_x.push(x_crt);
}

pubkeys_x
}

/// Clears the 3 first least significat bits used for flags from a last byte of compressed pubkey.
/// This function emulates bitwise and on 00011111 (0x1F): `b & 0b00011111` = c
fn clear_flag_bits(
range: &RangeChip<F>,
b: &AssignedValue<F>,
ctx: &mut Context<F>,
) -> AssignedValue<F> {
let gate = range.gate();
// Shift `a` three bits to the left (equivalent to a << 3 mod 256)
let b_shifted = gate.mul(ctx, *b, QuantumCell::Constant(F::from(8)));
// since b_shifted can at max be 255*8=2^4 we use 16 bits for modulo division.
let b_shifted = range.div_mod(ctx, b_shifted, BigUint::from(256u64), 16).1;

// Shift `s` three bits to the right (equivalent to s >> 3) to zeroing the first three bits (MSB) of `a`.
range.div_mod(ctx, b_shifted, BigUint::from(8u64), 8).0
compressed_encodings
.into_iter()
.map(|assigned_bytes| {
// assertion check for assigned_uncompressed vector to be equal to S::PubKeyCurve::BYTES_COMPRESSED from specification
assert_eq!(assigned_bytes.len(), G1::BYTES_COMPRESSED);
// masked byte from compressed representation
let masked_byte = &assigned_bytes[G1::BYTES_COMPRESSED - 1];
// clear the flag bits from a last byte of compressed pubkey.
// we are using [`clear_3_bits`] function which appears to be just as useful here as for public input commitment.
let cleared_byte = clear_3_bits(ctx, range, masked_byte);
// Use the cleared byte to construct the x coordinate
let assigned_x_bytes_cleared = [
&assigned_bytes.as_slice()[..G1::BYTES_COMPRESSED - 1],
&[cleared_byte],
]
.concat();

decode_into_field::<F, G1>(assigned_x_bytes_cleared, &fp_chip.limb_bases, gate, ctx)
})
.collect()
}

fn sync_committee_root_ssz<
ThreadBuilder: ThreadBuilderBase<F>,
I: IntoIterator<Item = Vec<AssignedValue<F>>>,
>(
fn sync_committee_root_ssz<ThreadBuilder: ThreadBuilderBase<F>>(
thread_pool: &mut ThreadBuilder,
hasher: &impl HashInstructions<F, ThreadBuilder>,
compressed_encodings: I,
compressed_encodings: impl IntoIterator<Item = Vec<AssignedValue<F>>>,
) -> Result<Vec<AssignedValue<F>>, Error> {
let mut pubkeys_hashes: Vec<HashInputChunk<QuantumCell<F>>> = compressed_encodings
.into_iter()
Expand Down
24 changes: 10 additions & 14 deletions lightclient-circuits/src/poseidon.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::gadget::crypto::G1Point;
use eth_types::{AppCurveExt, Field};
use eth_types::{AppCurveExt, Field, Spec};
use halo2_base::safe_types::ScalarField;
use halo2_base::{safe_types::GateInstructions, AssignedValue, Context};
use halo2_ecc::bigint::{ProperCrtUint, ProperUint};
use halo2_proofs::plonk::Error;
use halo2curves::bls12_381::G1Affine;
use halo2curves::bls12_381::G1;
use halo2curves::bls12_381::{self, G1Affine};
use itertools::Itertools;
use poseidon::PoseidonChip;
use poseidon_native::Poseidon as PoseidonNative;
Expand Down Expand Up @@ -42,21 +42,17 @@ pub fn fq_array_poseidon<'a, F: Field>(
Ok(current_poseidon_hash.unwrap())
}

pub fn g1_array_poseidon_native<F: Field>(points: &[G1Affine]) -> Result<F, Error> {
let limbs = points
.iter()
// Converts the point (usually in Fq) to limbs.
.flat_map(|point| {
point
.x
.to_bytes_le()
.chunks(14)
pub fn fq_array_poseidon_native<F: Field>(
elems: impl Iterator<Item = bls12_381::Fq>,
) -> Result<F, Error> {
let limbs = elems
// Converts Fq elements to Fr limbs.
.flat_map(|x| {
x.to_bytes_le()
.chunks(bls12_381::G1::LIMB_BITS / 8)
.map(F::from_bytes_le)
.collect_vec()
})
// Converts the Fq point to a circuit field. It is safe because the limbs should be smaller
// even if the bits in the Field of the point are larger than the bits of the circuit field.
.map(|fq_limbs| F::from_bytes_le_unsecure(&fq_limbs.to_bytes_le()))
.collect_vec();

let mut poseidon = PoseidonNative::<F, POSEIDON_SIZE, { POSEIDON_SIZE - 1 }>::new(R_F, R_P);
Expand Down
Loading

0 comments on commit 9c80785

Please sign in to comment.