Skip to content

Commit

Permalink
feat: add TranscriptObject to track assigned proof transcript
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanpwang committed Sep 23, 2023
1 parent ebb0bbd commit c09e0eb
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 15 deletions.
61 changes: 49 additions & 12 deletions snark-verifier-sdk/src/halo2/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ use snark_verifier::{
kzg::{KzgAccumulator, KzgAsProvingKey, KzgAsVerifyingKey, KzgSuccinctVerifyingKey},
AccumulationScheme, AccumulationSchemeProver, PolynomialCommitmentScheme,
},
system::halo2::transcript::halo2::TranscriptObject,
verifier::SnarkVerifier,
};
use std::{fs::File, mem, path::Path, rc::Rc};

use super::{CircuitExt, PoseidonTranscript, Snark, POSEIDON_SPEC};

pub type Svk = KzgSuccinctVerifyingKey<G1Affine>;
type FieldPoint = ProperCrtUint<Fr>;
pub type BaseFieldEccChip<'chip> = halo2_ecc::ecc::BaseFieldEccChip<'chip, G1Affine>;
pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader<G1Affine, BaseFieldEccChip<'chip>>;

Expand All @@ -58,8 +58,8 @@ pub struct SnarkAggregationWitness<'a> {
/// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark.
/// These can then be exposed as public instances.
pub preprocessed: Vec<PreprocessedAndDomainAsWitness>,
/// The proof transcript, as assigned [G1Affine] points, for each SNARK that was aggregated.
pub proofs: Vec<Vec<halo2_ecc::ecc::EcPoint<Fr, FieldPoint>>>,
/// The proof transcript, as loaded scalars and elliptic curve points, for each SNARK that was aggregated.
pub proof_transcripts: Vec<Vec<TranscriptObject<G1Affine, Rc<Halo2Loader<'a>>>>>,
}

/// Different possible stages of universality the aggregation circuit can support
Expand Down Expand Up @@ -136,7 +136,7 @@ where
);

let preprocessed_as_witness = universality.preprocessed_as_witness();
let (proofs, accumulators): (Vec<_>, Vec<_>) = snarks
let (proof_transcripts, accumulators): (Vec<_>, Vec<_>) = snarks
.iter()
.map(|snark: &Snark| {
let protocol = if preprocessed_as_witness {
Expand Down Expand Up @@ -188,8 +188,18 @@ where
previous_instances.push(
instances.into_iter().flatten().map(|scalar| scalar.into_assigned()).collect(),
);
let proof = proof.witnesses.into_iter().map(|point| point.into_assigned()).collect();
(proof, accumulator)
let proof_transcript = transcript.loaded_stream.clone();
debug_assert_eq!(
snark.proof().len(),
proof_transcript
.iter()
.map(|t| match t {
TranscriptObject::Scalar(_) => 32,
TranscriptObject::EcPoint(_) => 32,
})
.sum::<usize>()
);
(proof_transcript, accumulator)
})
.unzip();
let mut accumulators = accumulators.into_iter().flatten().collect_vec();
Expand All @@ -212,7 +222,7 @@ where
previous_instances,
accumulator,
preprocessed: preprocessed_witnesses,
proofs,
proof_transcripts,
}
}

Expand Down Expand Up @@ -329,8 +339,15 @@ pub struct SnarkAggregationOutput {
/// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark.
/// These can then be exposed as public instances.
pub preprocessed: Vec<PreprocessedAndDomainAsWitness>,
/// The proof transcript, as assigned [G1Affine] points, for each SNARK that was aggregated.
pub proofs: Vec<Vec<halo2_ecc::ecc::EcPoint<Fr, FieldPoint>>>,
/// The proof transcript, as loaded scalars and elliptic curve points, for each SNARK that was aggregated.
pub proof_transcripts: Vec<Vec<AssignedTranscriptObject>>,
}

#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug)]
pub enum AssignedTranscriptObject {
Scalar(AssignedValue<Fr>),
EcPoint(halo2_ecc::ecc::EcPoint<Fr, ProperCrtUint<Fr>>),
}

/// Given snarks, this populates the circuit builder with the virtual cells and constraints necessary to verify all the snarks.
Expand Down Expand Up @@ -404,8 +421,12 @@ where
let loader = Halo2Loader::new(ecc_chip, tmp_pool);

// run witness and copy constraint generation
let SnarkAggregationWitness { previous_instances, accumulator, preprocessed, proofs } =
aggregate::<AS>(&svk, &loader, &snarks, as_proof.as_slice(), universality);
let SnarkAggregationWitness {
previous_instances,
accumulator,
preprocessed,
proof_transcripts,
} = aggregate::<AS>(&svk, &loader, &snarks, as_proof.as_slice(), universality);
let lhs = accumulator.lhs.assigned();
let rhs = accumulator.rhs.assigned();
let accumulator = lhs
Expand All @@ -417,6 +438,22 @@ where
.chain(rhs.y().limbs().iter())
.copied()
.collect_vec();
let proof_transcripts = proof_transcripts
.into_iter()
.map(|transcript| {
transcript
.into_iter()
.map(|obj| match obj {
TranscriptObject::Scalar(scalar) => {
AssignedTranscriptObject::Scalar(scalar.into_assigned())
}
TranscriptObject::EcPoint(point) => {
AssignedTranscriptObject::EcPoint(point.into_assigned())
}
})
.collect()
})
.collect();

#[cfg(debug_assertions)]
{
Expand All @@ -429,7 +466,7 @@ where
}
// put back `pool` into `builder`
*pool = loader.take_ctx();
SnarkAggregationOutput { previous_instances, accumulator, preprocessed, proofs }
SnarkAggregationOutput { previous_instances, accumulator, preprocessed, proof_transcripts }
}

impl AggregationCircuit {
Expand Down
36 changes: 33 additions & 3 deletions snark-verifier/src/system/halo2/transcript/halo2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ where
) -> Result<Vec<Self::AssignedScalar>, Error>;
}

/// A way to keep track of what gets read in the transcript.
#[derive(Clone, Debug)]
pub enum TranscriptObject<C, L>
where
C: CurveAffine,
L: Loader<C>,
{
/// Scalar
Scalar(L::LoadedScalar),
/// Elliptic curve point
EcPoint(L::LoadedEcPoint),
}

#[derive(Debug)]
/// Transcript for verifier in [`halo2_proofs`] circuit using poseidon hasher.
/// Currently It assumes the elliptic curve scalar field is same as native
Expand All @@ -53,6 +66,10 @@ pub struct PoseidonTranscript<
{
loader: L,
stream: S,
/// Only relevant for Halo2 loader: as elements from `stream` are read, they are assigned as witnesses.
/// The loaded witnesses are pushed to `loaded_stream`. This way at the end we have the entire proof transcript
/// as loaded witnesses.
pub loaded_stream: Vec<TranscriptObject<C, L>>,
buf: Poseidon<C::Scalar, <L as ScalarLoader<C::Scalar>>::LoadedScalar, T, RATE>,
}

Expand All @@ -70,7 +87,7 @@ where
C::Scalar: FieldExt,
{
let buf = Poseidon::new::<R_F, R_P, SECURE_MDS>(loader);
Self { loader: loader.clone(), stream, buf }
Self { loader: loader.clone(), stream, buf, loaded_stream: vec![] }
}

/// Initialize [`PoseidonTranscript`] from a precomputed spec of round constants and MDS matrix because computing the constants is expensive.
Expand All @@ -80,12 +97,13 @@ where
spec: OptimizedPoseidonSpec<C::Scalar, T, RATE>,
) -> Self {
let buf = Poseidon::from_spec(loader, spec);
Self { loader: loader.clone(), stream, buf }
Self { loader: loader.clone(), stream, buf, loaded_stream: vec![] }
}

/// Clear the buffer and set the stream to a new one. Effectively the same as starting from a new transcript.
pub fn new_stream(&mut self, stream: R) {
self.buf.clear();
self.loaded_stream.clear();
self.stream = stream;
}
}
Expand Down Expand Up @@ -148,6 +166,7 @@ where
C::Scalar::from_repr(data).unwrap()
};
let scalar = self.loader.assign_scalar(scalar);
self.loaded_stream.push(TranscriptObject::Scalar(scalar.clone()));
self.common_scalar(&scalar)?;
Ok(scalar)
}
Expand All @@ -159,6 +178,7 @@ where
C::from_bytes(&compressed).unwrap()
};
let ec_point = self.loader.assign_ec_point(ec_point);
self.loaded_stream.push(TranscriptObject::EcPoint(ec_point.clone()));
self.common_ec_point(&ec_point)?;
Ok(ec_point)
}
Expand All @@ -177,17 +197,24 @@ impl<C: CurveAffine, S, const T: usize, const RATE: usize, const R_F: usize, con
loader: NativeLoader,
stream,
buf: Poseidon::new::<R_F, R_P, SECURE_MDS>(&NativeLoader),
loaded_stream: vec![],
}
}

/// Initialize [`PoseidonTranscript`] from a precomputed spec of round constants and MDS matrix because computing the constants is expensive.
pub fn from_spec(stream: S, spec: OptimizedPoseidonSpec<C::Scalar, T, RATE>) -> Self {
Self { loader: NativeLoader, stream, buf: Poseidon::from_spec(&NativeLoader, spec) }
Self {
loader: NativeLoader,
stream,
buf: Poseidon::from_spec(&NativeLoader, spec),
loaded_stream: vec![],
}
}

/// Clear the buffer and set the stream to a new one. Effectively the same as starting from a new transcript.
pub fn new_stream(&mut self, stream: S) {
self.buf.clear();
self.loaded_stream.clear();
self.stream = stream;
}
}
Expand All @@ -198,6 +225,7 @@ impl<C: CurveAffine, const T: usize, const RATE: usize, const R_F: usize, const
/// Clear the buffer and stream.
pub fn clear(&mut self) {
self.buf.clear();
self.loaded_stream.clear();
self.stream.clear();
}
}
Expand Down Expand Up @@ -247,6 +275,7 @@ where
let scalar = C::Scalar::from_repr_vartime(data).ok_or_else(|| {
Error::Transcript(io::ErrorKind::Other, "Invalid scalar encoding in proof".to_string())
})?;
self.loaded_stream.push(TranscriptObject::Scalar(scalar));
self.common_scalar(&scalar)?;
Ok(scalar)
}
Expand All @@ -262,6 +291,7 @@ where
"Invalid elliptic curve point encoding in proof".to_string(),
)
})?;
self.loaded_stream.push(TranscriptObject::EcPoint(ec_point));
self.common_ec_point(&ec_point)?;
Ok(ec_point)
}
Expand Down

0 comments on commit c09e0eb

Please sign in to comment.