Integrate recursion into MainPod (#243)

* calculate MainPod id in a dynamic-friendly way

The MainPod id is now calculated with front padding and a fixed size
independent of max_public_statements so that introduction gadgets can be
verified by a MainPod while paying only for the number of statements
they use.  This is because with front padding of none-statements we can
precompute the poseidon state corresponding to absorbing all the padding
statements and only pay constraints for the non-padding statements.

The id is calculated as follows:
`id = hash(serialize(reverse(statements || none-statements)))`

* add time feature and disable timing by default

* apply suggestions from @arnaucube

* link issues in todos
This commit is contained in:
Eduard S. 2025-05-29 17:10:19 +02:00 committed by GitHub
parent d3fef8392e
commit 88a75986b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1405 additions and 729 deletions

View file

@ -1,15 +1,16 @@
//! This file exposes the middleware::basetypes to be used in the middleware when the
//! `backend_plonky2` feature is enabled.
//! This file exposes the basetypes to be used in the middleware when the `backend_plonky2` feature
//! is enabled.
//! See src/middleware/basetypes.rs for more details.
use plonky2::{
field::extension::quadratic::QuadraticExtension,
field::{extension::quadratic::QuadraticExtension, goldilocks_field::GoldilocksField},
hash::poseidon::PoseidonHash,
plonk::{config::GenericConfig, proof::Proof as Plonky2Proof},
plonk::{circuit_builder, circuit_data, config::GenericConfig, proof},
};
use serde::Serialize;
use crate::middleware::F;
/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2
pub type F = GoldilocksField;
/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension).
pub const D: usize = 2;
@ -27,5 +28,11 @@ impl GenericConfig<D> for C {
type InnerHasher = PoseidonHash;
}
/// proof system proof
pub type Proof = Plonky2Proof<F, C, D>;
pub type CircuitData = circuit_data::CircuitData<F, C, D>;
pub type CommonCircuitData = circuit_data::CommonCircuitData<F, D>;
pub type ProverOnlyCircuitData = circuit_data::ProverOnlyCircuitData<F, C, D>;
pub type VerifierOnlyCircuitData = circuit_data::VerifierOnlyCircuitData<C, D>;
pub type VerifierCircuitData = circuit_data::VerifierCircuitData<F, C, D>;
pub type CircuitBuilder = circuit_builder::CircuitBuilder<F, D>;
pub type Proof = proof::Proof<F, C, D>;
pub type ProofWithPublicInputs = proof::ProofWithPublicInputs<F, C, D>;

View file

@ -17,13 +17,12 @@ use plonky2::{
target::{BoolTarget, Target},
witness::{PartialWitness, PartitionWitness, Witness, WitnessWrite},
},
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
util::serialization::{Buffer, IoResult, Read, Write},
};
use crate::{
backends::plonky2::{
basetypes::D,
basetypes::{CircuitBuilder, CommonCircuitData, D},
circuits::mainpod::CustomPredicateVerification,
error::Result,
mainpod::{Operation, OperationArg, Statement},
@ -47,13 +46,13 @@ pub struct ValueTarget {
}
impl ValueTarget {
pub fn zero(builder: &mut CircuitBuilder<F, D>) -> Self {
pub fn zero(builder: &mut CircuitBuilder) -> Self {
Self {
elements: [builder.zero(); VALUE_SIZE],
}
}
pub fn one(builder: &mut CircuitBuilder<F, D>) -> Self {
pub fn one(builder: &mut CircuitBuilder) -> Self {
Self {
elements: array::from_fn(|i| {
if i == 0 {
@ -99,25 +98,25 @@ impl StatementArgTarget {
}
}
pub fn none(builder: &mut CircuitBuilder<F, D>) -> Self {
pub fn none(builder: &mut CircuitBuilder) -> Self {
let empty = builder.constant_value(EMPTY_VALUE);
Self::new(empty, empty)
}
pub fn literal(builder: &mut CircuitBuilder<F, D>, value: &ValueTarget) -> Self {
pub fn literal(builder: &mut CircuitBuilder, value: &ValueTarget) -> Self {
let empty = builder.constant_value(EMPTY_VALUE);
Self::new(*value, empty)
}
pub fn anchored_key(
_builder: &mut CircuitBuilder<F, D>,
_builder: &mut CircuitBuilder,
pod_id: &ValueTarget,
key: &ValueTarget,
) -> Self {
Self::new(*pod_id, *key)
}
pub fn wildcard_literal(builder: &mut CircuitBuilder<F, D>, value: &ValueTarget) -> Self {
pub fn wildcard_literal(builder: &mut CircuitBuilder, value: &ValueTarget) -> Self {
let empty = builder.constant_value(EMPTY_VALUE);
Self::new(*value, empty)
}
@ -137,17 +136,17 @@ pub struct StatementTarget {
}
pub trait Build<T> {
fn build(self, builder: &mut CircuitBuilder<F, D>, params: &Params) -> T;
fn build(self, builder: &mut CircuitBuilder, params: &Params) -> T;
}
impl Build<NativePredicateTarget> for NativePredicate {
fn build(self, builder: &mut CircuitBuilder<F, D>, params: &Params) -> NativePredicateTarget {
fn build(self, builder: &mut CircuitBuilder, params: &Params) -> NativePredicateTarget {
NativePredicateTarget::constant(builder, params, self)
}
}
impl<T> Build<T> for T {
fn build(self, _builder: &mut CircuitBuilder<F, D>, _params: &Params) -> T {
fn build(self, _builder: &mut CircuitBuilder, _params: &Params) -> T {
self
}
}
@ -155,7 +154,7 @@ impl<T> Build<T> for T {
impl StatementTarget {
/// Build a new native StatementTarget. Pads the arguments.
pub fn new_native(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
params: &Params,
native_predicate: impl Build<NativePredicateTarget>,
args: &[StatementArgTarget],
@ -194,7 +193,7 @@ impl StatementTarget {
pub fn has_native_type(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
params: &Params,
t: NativePredicate,
) -> BoolTarget {
@ -210,7 +209,7 @@ pub struct OperationTypeTarget {
impl OperationTypeTarget {
pub fn new_custom(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
batch_id: HashOutTarget,
index: Target,
) -> Self {
@ -222,10 +221,7 @@ impl OperationTypeTarget {
}
}
pub fn as_custom(
&self,
builder: &mut CircuitBuilder<F, D>,
) -> (BoolTarget, HashOutTarget, Target) {
pub fn as_custom(&self, builder: &mut CircuitBuilder) -> (BoolTarget, HashOutTarget, Target) {
// TODO: Use an enum for these prefixes
let three = builder.constant(F::from_canonical_usize(3));
let op_is_custom = builder.is_equal(self.elements[0], three);
@ -234,7 +230,7 @@ impl OperationTypeTarget {
(op_is_custom, batch_id, index)
}
pub fn has_native(&self, builder: &mut CircuitBuilder<F, D>, t: NativeOperation) -> BoolTarget {
pub fn has_native(&self, builder: &mut CircuitBuilder, t: NativeOperation) -> BoolTarget {
// TODO: Use an enum for these prefixes
let one = builder.one();
let op_is_native = builder.is_equal(self.elements[0], one);
@ -288,7 +284,7 @@ pub struct NativePredicateTarget(Target);
impl NativePredicateTarget {
pub fn constant(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
params: &Params,
native_predicate: NativePredicate,
) -> Self {
@ -316,7 +312,7 @@ pub struct PredicateTarget {
impl PredicateTarget {
pub fn new_native(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
params: &Params,
native_predicate: impl Build<NativePredicateTarget>,
) -> Self {
@ -328,7 +324,7 @@ impl PredicateTarget {
}
}
pub fn new_batch_self(builder: &mut CircuitBuilder<F, D>, index: Target) -> Self {
pub fn new_batch_self(builder: &mut CircuitBuilder, index: Target) -> Self {
let prefix = builder.constant(F::from(PredicatePrefix::BatchSelf));
let zero = builder.zero();
Self {
@ -337,7 +333,7 @@ impl PredicateTarget {
}
pub fn new_custom(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
batch_id: HashOutTarget,
index: Target,
) -> Self {
@ -373,7 +369,7 @@ impl LiteralOrWildcardTarget {
/// cases: ((is_key, key), (is_wildcard, wildcard_index))
pub fn cases(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
) -> ((BoolTarget, ValueTarget), (BoolTarget, Target)) {
let zero = builder.zero();
let is_zero_tail: Vec<_> = (1..4)
@ -397,12 +393,12 @@ pub struct StatementTmplArgTarget {
}
impl StatementTmplArgTarget {
pub fn as_none(&self, builder: &mut CircuitBuilder<F, D>) -> BoolTarget {
pub fn as_none(&self, builder: &mut CircuitBuilder) -> BoolTarget {
let prefix = builder.constant(F::from(StatementTmplArgPrefix::None));
builder.is_equal(self.elements[0], prefix)
}
pub fn as_literal(&self, builder: &mut CircuitBuilder<F, D>) -> (BoolTarget, ValueTarget) {
pub fn as_literal(&self, builder: &mut CircuitBuilder) -> (BoolTarget, ValueTarget) {
let prefix = builder.constant(F::from(StatementTmplArgPrefix::Literal));
let case_ok = builder.is_equal(self.elements[0], prefix);
let value = ValueTarget::from_slice(&self.elements[1..5]);
@ -411,7 +407,7 @@ impl StatementTmplArgTarget {
pub fn as_anchored_key(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
) -> (BoolTarget, Target, LiteralOrWildcardTarget) {
let prefix = builder.constant(F::from(StatementTmplArgPrefix::AnchoredKey));
let case_ok = builder.is_equal(self.elements[0], prefix);
@ -420,7 +416,7 @@ impl StatementTmplArgTarget {
(case_ok, id_wildcard_index, value_key_or_wildcard)
}
pub fn as_wildcard_literal(&self, builder: &mut CircuitBuilder<F, D>) -> (BoolTarget, Target) {
pub fn as_wildcard_literal(&self, builder: &mut CircuitBuilder) -> (BoolTarget, Target) {
let prefix = builder.constant(F::from(StatementTmplArgPrefix::WildcardLiteral));
let case_ok = builder.is_equal(self.elements[0], prefix);
let wildcard_index = self.elements[1];
@ -479,7 +475,7 @@ pub struct CustomPredicateBatchTarget {
}
impl CustomPredicateBatchTarget {
pub fn id(&self, builder: &mut CircuitBuilder<F, D>) -> HashOutTarget {
pub fn id(&self, builder: &mut CircuitBuilder) -> HashOutTarget {
let flattened = self.predicates.iter().flat_map(|cp| cp.flatten()).collect();
builder.hash_n_to_hash_no_pad::<PoseidonHash>(flattened)
}
@ -573,7 +569,7 @@ impl Flattenable for CustomPredicateEntryTarget {
}
impl CustomPredicateEntryTarget {
pub fn hash(&self, builder: &mut CircuitBuilder<F, D>) -> HashOutTarget {
pub fn hash(&self, builder: &mut CircuitBuilder) -> HashOutTarget {
builder.hash_n_to_hash_no_pad::<PoseidonHash>(self.flatten())
}
}
@ -630,7 +626,7 @@ pub struct CustomPredicateVerifyQueryTarget {
}
impl CustomPredicateVerifyQueryTarget {
pub fn hash(&self, builder: &mut CircuitBuilder<F, D>) -> HashOutTarget {
pub fn hash(&self, builder: &mut CircuitBuilder) -> HashOutTarget {
builder.hash_n_to_hash_no_pad::<PoseidonHash>(self.flatten())
}
}
@ -930,7 +926,7 @@ pub trait CircuitBuilderPod<F: RichField + Extendable<D>, const D: usize> {
fn lt_mask(&mut self, len: usize, n: Target) -> Vec<BoolTarget>;
}
impl CircuitBuilderPod<F, D> for CircuitBuilder<F, D> {
impl CircuitBuilderPod<F, D> for CircuitBuilder {
fn connect_slice(&mut self, xs: &[Target], ys: &[Target]) {
assert_eq!(xs.len(), ys.len());
for (x, y) in xs.iter().zip(ys.iter()) {
@ -1267,11 +1263,11 @@ impl CircuitBuilderPod<F, D> for CircuitBuilder<F, D> {
// then do `ts: &[HashCache<T>]`.
fn vec_ref<T: Flattenable>(&mut self, params: &Params, ts: &[T], i: Target) -> T {
// TODO: Revisit this when we need more than 64 statements.
let vector_ref = |builder: &mut CircuitBuilder<F, D>, v: &[Target], i| {
let vector_ref = |builder: &mut CircuitBuilder, v: &[Target], i| {
assert!(v.len() <= 64);
builder.random_access(i, v.to_vec())
};
let matrix_row_ref = |builder: &mut CircuitBuilder<F, D>, m: &[Vec<Target>], i| {
let matrix_row_ref = |builder: &mut CircuitBuilder, m: &[Vec<Target>], i| {
let num_rows = m.len();
let num_columns = m
.first()
@ -1367,7 +1363,7 @@ pub struct LtMaskGenerator {
pub(crate) mask: Vec<Target>,
}
impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D> for LtMaskGenerator {
impl SimpleGenerator<F, D> for LtMaskGenerator {
fn id(&self) -> String {
"LtMaskGenerator".to_string()
}
@ -1390,12 +1386,12 @@ impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D> for LtM
Ok(())
}
fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> {
fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData) -> IoResult<()> {
dst.write_target(self.n)?;
dst.write_target_vec(&self.mask)
}
fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData<F, D>) -> IoResult<Self> {
fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult<Self> {
let n = src.read_target()?;
let mask = src.read_target_vec()?;
Ok(Self { n, mask })

View file

@ -10,14 +10,14 @@ use plonky2::{
},
iop::{
target::{BoolTarget, Target},
witness::PartialWitness,
witness::{PartialWitness, WitnessWrite},
},
plonk::{circuit_builder::CircuitBuilder, config::AlgebraicHasher},
plonk::config::AlgebraicHasher,
};
use crate::{
backends::plonky2::{
basetypes::D,
basetypes::CircuitBuilder,
circuits::{
common::{
CircuitBuilderPod, CustomPredicateBatchTarget, CustomPredicateEntryTarget,
@ -28,18 +28,21 @@ use crate::{
},
signedpod::{SignedPodVerifyGadget, SignedPodVerifyTarget},
},
emptypod::EmptyPod,
error::Result,
mainpod::{self, pad_statement},
primitives::merkletree::{
MerkleClaimAndProof, MerkleClaimAndProofTarget, MerkleProofGadget,
},
recursion::{InnerCircuit, VerifiedProofTarget},
signedpod::SignedPod,
},
measure_gates_begin, measure_gates_end,
middleware::{
AnchoredKey, CustomPredicate, CustomPredicateBatch, CustomPredicateRef, NativeOperation,
NativePredicate, Params, PodType, PredicatePrefix, Statement, StatementArg, ToFields,
Value, WildcardValue, F, KEY_TYPE, SELF, VALUE_SIZE,
AnchoredKey, CustomPredicate, CustomPredicateBatch, CustomPredicateRef, Hash,
NativeOperation, NativePredicate, Params, PodType, PredicatePrefix, Statement,
StatementArg, ToFields, Value, WildcardValue, EMPTY_VALUE, F, HASH_SIZE, KEY_TYPE, SELF,
VALUE_SIZE,
},
};
@ -47,6 +50,13 @@ use crate::{
// MainPod verification
//
/// Offset in public inputs where we store the pod id
pub const PI_OFFSET_ID: usize = 0;
/// Offset in public inputs where we store the verified data array root
pub const PI_OFFSET_VDSROOT: usize = 4;
pub const NUM_PUBLIC_INPUTS: usize = 8;
struct OperationVerifyGadget {
params: Params,
}
@ -58,7 +68,7 @@ impl OperationVerifyGadget {
/// argument.
fn first_n_args_as_values<const N: usize>(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
resolved_op_args: &[StatementTarget],
) -> (BoolTarget, [ValueTarget; N]) {
let arg_is_valueof = resolved_op_args[..N]
@ -80,7 +90,7 @@ impl OperationVerifyGadget {
fn eval(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op: &OperationTarget,
prev_statements: &[StatementTarget],
@ -212,7 +222,7 @@ impl OperationVerifyGadget {
fn eval_contains_from_entries(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_merkle_claim: MerkleClaimTarget,
@ -260,7 +270,7 @@ impl OperationVerifyGadget {
fn eval_not_contains_from_entries(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_merkle_claim: MerkleClaimTarget,
@ -306,7 +316,7 @@ impl OperationVerifyGadget {
fn eval_custom(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_custom_pred_verification: HashOutTarget,
@ -331,7 +341,7 @@ impl OperationVerifyGadget {
/// NotEqualFromEntries.
fn eval_eq_neq_from_entries(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -382,7 +392,7 @@ impl OperationVerifyGadget {
/// LtEqFromEntries.
fn eval_lt_lteq_from_entries(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -451,7 +461,7 @@ impl OperationVerifyGadget {
fn eval_hash_of(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -485,7 +495,7 @@ impl OperationVerifyGadget {
fn eval_sum_of(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -524,7 +534,7 @@ impl OperationVerifyGadget {
fn eval_product_of(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -563,7 +573,7 @@ impl OperationVerifyGadget {
fn eval_max_of(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -609,7 +619,7 @@ impl OperationVerifyGadget {
fn eval_transitive_eq(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -645,7 +655,7 @@ impl OperationVerifyGadget {
}
fn eval_none(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
) -> BoolTarget {
@ -663,7 +673,7 @@ impl OperationVerifyGadget {
fn eval_new_entry(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
prev_statements: &[StatementTarget],
@ -701,7 +711,7 @@ impl OperationVerifyGadget {
fn eval_lt_to_neq(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -730,7 +740,7 @@ impl OperationVerifyGadget {
fn eval_copy(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
resolved_op_args: &[StatementTarget],
@ -759,7 +769,7 @@ struct CustomOperationVerifyGadget {
impl CustomOperationVerifyGadget {
fn statement_arg_from_template(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st_tmpl_arg: &StatementTmplArgTarget,
args: &[ValueTarget],
) -> StatementArgTarget {
@ -812,7 +822,7 @@ impl CustomOperationVerifyGadget {
fn statement_from_template(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st_tmpl: &StatementTmplTarget,
args: &[ValueTarget],
) -> StatementTarget {
@ -836,7 +846,7 @@ impl CustomOperationVerifyGadget {
/// - Build the expected operation type
fn eval(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
custom_predicate: &CustomPredicateEntryTarget,
op_args: &[StatementTarget],
args: &[ValueTarget], // arguments to the custom predicate, public and private
@ -901,10 +911,49 @@ impl CustomOperationVerifyGadget {
}
}
struct CalculateIdGadget {
/// Replace references to SELF by `self_id` in a statement.
struct NormalizeStatementGadget {
params: Params,
}
impl NormalizeStatementGadget {
fn eval(
&self,
builder: &mut CircuitBuilder,
statement: &StatementTarget,
self_id: &ValueTarget,
) -> StatementTarget {
let zero_value = builder.constant_value(EMPTY_VALUE);
let self_value = builder.constant_value(SELF.0.into());
let args = statement
.args
.iter()
.map(|arg| {
let first = ValueTarget::from_slice(&arg.elements[..VALUE_SIZE]);
let second = ValueTarget::from_slice(&arg.elements[VALUE_SIZE..]);
let is_not_ak = builder.is_equal_flattenable(&zero_value, &second);
let is_ak = builder.not(is_not_ak);
let is_self = builder.is_equal_flattenable(&self_value, &first);
let normalize = builder.and(is_ak, is_self);
let first_normalized =
builder.select_flattenable(&self.params, normalize, self_id, &first);
StatementArgTarget::new(first_normalized, second)
})
.collect_vec();
StatementTarget {
predicate: statement.predicate.clone(),
args,
}
}
}
pub struct CalculateIdGadget {
/// `params.num_public_statements_id` is the total number of statements that will be hashed.
/// The id is calculated with front-padded none-statements and then the input statements
/// reversed. The part of the hash from the front-padded none-statements is precomputed.
pub params: Params,
}
impl CalculateIdGadget {
/// Precompute the hash state by absorbing all full chunks from `inputs` and return the reminder
/// elements that didn't fit into a chunk.
@ -923,7 +972,7 @@ impl CalculateIdGadget {
/// Hash `inputs` starting from a circuit-constant `perm` state.
fn hash_from_state<H: AlgebraicHasher<F>, P: PlonkyPermutation<F>>(
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
perm: P,
inputs: &[Target],
) -> HashOutTarget {
@ -953,17 +1002,19 @@ impl CalculateIdGadget {
}
}
fn eval(
pub fn eval(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
// These statements will be padded to reach `self.num_statements`
statements: &[StatementTarget],
) -> HashOutTarget {
assert!(statements.len() <= self.params.num_public_statements_id);
let measure = measure_gates_begin!(builder, "CalculateId");
let statements_rev_flattened = statements.iter().rev().flat_map(|s| s.flatten());
let mut none_st = mainpod::Statement::from(Statement::None);
pad_statement(&self.params, &mut none_st);
let front_pad_elts = iter::repeat(&none_st)
.take(self.params.num_public_statements_id - self.params.max_public_statements)
.take(self.params.num_public_statements_id - statements.len())
.flat_map(|s| s.to_fields(&self.params))
.collect_vec();
let (perm, front_pad_elts_rem) =
@ -992,7 +1043,7 @@ impl MainPodVerifyGadget {
// index
fn normalize_st_tmpl(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
st_tmpl: &StatementTmplTarget,
id: HashOutTarget,
) -> StatementTmplTarget {
@ -1012,7 +1063,7 @@ impl MainPodVerifyGadget {
/// calculate the id of each batch.
fn build_custom_predicate_table(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
) -> Result<(Vec<HashOutTarget>, Vec<CustomPredicateBatchTarget>)> {
let measure = measure_gates_begin!(builder, "BuildCustomPredicateTable");
let params = &self.params;
@ -1053,7 +1104,7 @@ impl MainPodVerifyGadget {
/// custom predicate against the operation and statement.
fn build_custom_predicate_verification_table(
&self,
builder: &mut CircuitBuilder<F, D>,
builder: &mut CircuitBuilder,
custom_predicate_table: &[HashOutTarget],
) -> Result<(Vec<HashOutTarget>, Vec<CustomPredicateVerifyEntryTarget>)> {
let measure = measure_gates_begin!(builder, "BuildCustomPredicateVerificationTable");
@ -1105,7 +1156,13 @@ impl MainPodVerifyGadget {
))
}
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MainPodVerifyTarget> {
fn eval(
&self,
builder: &mut CircuitBuilder,
verified_proofs: &[VerifiedProofTarget],
) -> Result<MainPodVerifyTarget> {
assert_eq!(self.params.max_input_recursive_pods, verified_proofs.len());
let measure = measure_gates_begin!(builder, "MainPodVerify");
let params = &self.params;
// 1. Verify all input signed pods
@ -1115,6 +1172,7 @@ impl MainPodVerifyGadget {
params: params.clone(),
}
.eval(builder)?;
builder.assert_one(signed_pod.signature.enabled.target);
signed_pods.push(signed_pod);
}
@ -1132,16 +1190,47 @@ impl MainPodVerifyGadget {
statements.len(),
1 + self.params.max_input_signed_pods * self.params.max_signed_pod_values
);
// TODO: Fill with input main pods
for _main_pod in 0..self.params.max_input_main_pods {
for _statement in 0..self.params.max_public_statements {
statements.push(StatementTarget::new_native(
builder,
&self.params,
NativePredicate::None,
&[],
))
let id_gadget = CalculateIdGadget {
params: params.clone(),
};
let mut input_pods_self_statements: Vec<Vec<StatementTarget>> = Vec::new();
let normalize_statement_gadget = NormalizeStatementGadget {
params: self.params.clone(),
};
for verified_proof in verified_proofs {
let expected_id = HashOutTarget::try_from(
&verified_proof.public_inputs[PI_OFFSET_ID..PI_OFFSET_ID + HASH_SIZE],
)
.expect("4 elements");
let id_value = ValueTarget {
elements: expected_id.elements,
};
let mut input_pod_self_statements = Vec::new();
for _ in 0..self.params.max_input_pods_public_statements {
let self_st = builder.add_virtual_statement(params);
let normalized_st = normalize_statement_gadget.eval(builder, &self_st, &id_value);
input_pod_self_statements.push(self_st);
statements.push(normalized_st);
}
let id = id_gadget.eval(builder, &input_pod_self_statements);
builder.connect_hashes(expected_id, id);
input_pods_self_statements.push(input_pod_self_statements);
}
let vds_root = builder.add_virtual_hash();
// TODO: verify that all input pod proofs use verifier data from the public input VD array
// This requires merkle proofs
// https://github.com/0xPARC/pod2/issues/250
// Verify that VD array that input pod uses is the same we use now.
for verified_proof in verified_proofs {
let verified_proof_vds_root = HashOutTarget::try_from(
&verified_proof.public_inputs[PI_OFFSET_VDSROOT..PI_OFFSET_VDSROOT + HASH_SIZE],
)
.expect("4 elements");
builder.connect_hashes(vds_root, verified_proof_vds_root);
}
// Add the input (private and public) statements and corresponding operations
@ -1220,8 +1309,10 @@ impl MainPodVerifyGadget {
measure_gates_end!(builder, measure);
Ok(MainPodVerifyTarget {
params: params.clone(),
vds_root,
id,
signed_pods,
input_pods_self_statements,
statements: input_statements.to_vec(),
operations,
merkle_proofs,
@ -1233,8 +1324,10 @@ impl MainPodVerifyGadget {
pub struct MainPodVerifyTarget {
params: Params,
vds_root: HashOutTarget,
id: HashOutTarget,
signed_pods: Vec<SignedPodVerifyTarget>,
input_pods_self_statements: Vec<Vec<StatementTarget>>,
// The KEY_TYPE statement must be the first public one
statements: Vec<StatementTarget>,
operations: Vec<OperationTarget>,
@ -1251,7 +1344,9 @@ pub struct CustomPredicateVerification {
}
pub struct MainPodVerifyInput {
pub vds_root: Hash,
pub signed_pods: Vec<SignedPod>,
pub recursive_pods_pub_self_statements: Vec<Vec<Statement>>,
pub statements: Vec<mainpod::Statement>,
pub operations: Vec<mainpod::Operation>,
pub merkle_proofs: Vec<MerkleClaimAndProof>,
@ -1259,18 +1354,44 @@ pub struct MainPodVerifyInput {
pub custom_predicate_verifications: Vec<CustomPredicateVerification>,
}
fn set_targets_input_pods_self_statements(
pw: &mut PartialWitness<F>,
params: &Params,
statements_target: &[StatementTarget],
statements: &[Statement],
) -> Result<()> {
assert_eq!(
statements_target.len(),
params.max_input_pods_public_statements
);
assert!(statements.len() <= params.num_public_statements_id);
for (i, statement) in statements.iter().enumerate() {
statements_target[i].set_targets(pw, params, &statement.clone().into())?;
}
// Padding
let mut none_st = mainpod::Statement::from(Statement::None);
pad_statement(params, &mut none_st);
for statement_target in statements_target.iter().skip(statements.len()) {
statement_target.set_targets(pw, params, &none_st)?;
}
Ok(())
}
impl MainPodVerifyTarget {
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,
input: &MainPodVerifyInput,
) -> Result<()> {
pw.set_target_arr(&self.vds_root.elements, &input.vds_root.0)?;
assert!(input.signed_pods.len() <= self.params.max_input_signed_pods);
for (i, signed_pod) in input.signed_pods.iter().enumerate() {
self.signed_pods[i].set_targets(pw, signed_pod)?;
}
// Padding
if self.params.max_input_signed_pods > 0 {
if input.signed_pods.len() != self.params.max_input_signed_pods {
// TODO: Instead of using an input for padding, use a canonical minimal SignedPod,
// without it a MainPod configured to support input signed pods must have at least one
// input signed pod :(
@ -1279,6 +1400,34 @@ impl MainPodVerifyTarget {
self.signed_pods[i].set_targets(pw, pad_pod)?;
}
}
assert!(
input.recursive_pods_pub_self_statements.len() <= self.params.max_input_recursive_pods
);
for (i, pod_pub_statements) in input.recursive_pods_pub_self_statements.iter().enumerate() {
set_targets_input_pods_self_statements(
pw,
&self.params,
&self.input_pods_self_statements[i],
pod_pub_statements,
)?;
}
// Padding
if input.recursive_pods_pub_self_statements.len() != self.params.max_input_recursive_pods {
let empty_pod = EmptyPod::new_boxed(&self.params, input.vds_root);
let empty_pod_statements = empty_pod.pub_statements();
for i in
input.recursive_pods_pub_self_statements.len()..self.params.max_input_recursive_pods
{
set_targets_input_pods_self_statements(
pw,
&self.params,
&self.input_pods_self_statements[i],
&empty_pod_statements,
)?;
}
}
assert_eq!(input.statements.len(), self.params.max_statements);
for (i, (st, op)) in zip_eq(&input.statements, &input.operations).enumerate() {
self.statements[i].set_targets(pw, &self.params, st)?;
@ -1342,17 +1491,44 @@ pub struct MainPodVerifyCircuit {
pub params: Params,
}
// TODO: Remove this type and implement it's logic directly in `impl InnerCircuit for MainPodVerifyTarget`
impl MainPodVerifyCircuit {
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MainPodVerifyTarget> {
pub fn eval(
&self,
builder: &mut CircuitBuilder,
verified_proofs: &[VerifiedProofTarget],
) -> Result<MainPodVerifyTarget> {
let main_pod = MainPodVerifyGadget {
params: self.params.clone(),
}
.eval(builder)?;
.eval(builder, verified_proofs)?;
builder.register_public_inputs(&main_pod.id.elements);
builder.register_public_inputs(&main_pod.vds_root.elements);
Ok(main_pod)
}
}
impl InnerCircuit for MainPodVerifyTarget {
type Input = MainPodVerifyInput;
type Params = Params;
fn build(
builder: &mut CircuitBuilder,
params: &Self::Params,
verified_proofs: &[VerifiedProofTarget],
) -> Result<Self> {
MainPodVerifyCircuit {
params: params.clone(),
}
.eval(builder, verified_proofs)
}
/// assigns the values to the targets
fn set_targets(&self, pw: &mut PartialWitness<F>, input: &Self::Input) -> Result<()> {
self.set_targets(pw, input)
}
}
#[cfg(test)]
mod tests {
use std::{iter, ops::Not};
@ -1395,7 +1571,7 @@ mod tests {
};
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut builder = CircuitBuilder::new(config);
let st_target = builder.add_virtual_statement(&params);
let op_target = builder.add_virtual_operation(&params);
@ -2268,7 +2444,7 @@ mod tests {
expected_st_arg: StatementArg,
) -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut builder = CircuitBuilder::new(config);
let gadget = CustomOperationVerifyGadget {
params: params.clone(),
};
@ -2369,7 +2545,7 @@ mod tests {
expected_st: Statement,
) -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut builder = CircuitBuilder::new(config);
let gadget = CustomOperationVerifyGadget {
params: params.clone(),
};
@ -2433,7 +2609,7 @@ mod tests {
expected_st: Option<Statement>,
) -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut builder = CircuitBuilder::new(config);
let gadget = CustomOperationVerifyGadget {
params: params.clone(),
};
@ -2775,7 +2951,7 @@ mod tests {
fn helper_calculate_id(params: &Params, statements: &[Statement]) -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut builder = CircuitBuilder::new(config);
let gadget = CalculateIdGadget {
params: params.clone(),
};

View file

@ -83,7 +83,7 @@ pub struct SignedPodVerifyTarget {
// the KEY_TYPE entry must be the first one
// the KEY_SIGNER entry must be the second one
mt_proofs: Vec<MerkleProofExistenceTarget>,
signature: SignatureVerifyTarget,
pub(crate) signature: SignatureVerifyTarget,
}
impl SignedPodVerifyTarget {

View file

@ -0,0 +1,207 @@
use std::{collections::HashMap, sync::Mutex};
use base64::{prelude::BASE64_STANDARD, Engine};
use itertools::Itertools;
use plonky2::{
hash::hash_types::HashOutTarget,
iop::witness::{PartialWitness, WitnessWrite},
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::{self, CircuitConfig},
proof::ProofWithPublicInputs,
},
};
use crate::{
backends::plonky2::{
basetypes::{Proof, C, D},
circuits::{
common::{Flattenable, StatementTarget},
mainpod::{CalculateIdGadget, PI_OFFSET_ID},
},
error::{Error, Result},
mainpod::{self, calculate_id},
recursion::pad_circuit,
LazyLock, DEFAULT_PARAMS, STANDARD_REC_MAIN_POD_CIRCUIT_DATA,
},
middleware::{
self, AnchoredKey, DynError, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement,
ToFields, Value, VerifierOnlyCircuitData, EMPTY_HASH, F, HASH_SIZE, KEY_TYPE, SELF,
},
timed,
};
struct EmptyPodVerifyCircuit {
params: Params,
}
fn type_statement() -> Statement {
Statement::ValueOf(
AnchoredKey::from((SELF, KEY_TYPE)),
Value::from(PodType::Empty),
)
}
impl EmptyPodVerifyCircuit {
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<EmptyPodVerifyTarget> {
let type_statement = StatementTarget::from_flattened(
&self.params,
&builder.constants(&type_statement().to_fields(&self.params)),
);
let id = CalculateIdGadget {
params: self.params.clone(),
}
.eval(builder, &[type_statement]);
let vds_root = builder.add_virtual_hash();
builder.register_public_inputs(&id.elements);
builder.register_public_inputs(&vds_root.elements);
Ok(EmptyPodVerifyTarget { vds_root })
}
}
pub struct EmptyPodVerifyTarget {
vds_root: HashOutTarget,
}
impl EmptyPodVerifyTarget {
pub fn set_targets(&self, pw: &mut PartialWitness<F>, vds_root: Hash) -> Result<()> {
Ok(pw.set_target_arr(&self.vds_root.elements, &vds_root.0)?)
}
}
#[derive(Clone, Debug)]
pub struct EmptyPod {
params: Params,
id: PodId,
vds_root: Hash,
proof: Proof,
}
type CircuitData = circuit_data::CircuitData<F, C, D>;
static STANDARD_EMPTY_POD_DATA: LazyLock<(EmptyPodVerifyTarget, CircuitData)> =
LazyLock::new(|| build().expect("successful build"));
fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
let params = &*DEFAULT_PARAMS;
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let empty_pod_verify_target = EmptyPodVerifyCircuit {
params: params.clone(),
}
.eval(&mut builder)?;
let circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
pad_circuit(&mut builder, &circuit_data.common);
let data = timed!("EmptyPod build", builder.build::<C>());
assert_eq!(circuit_data.common, data.common);
Ok((empty_pod_verify_target, data))
}
static EMPTY_POD_CACHE: LazyLock<Mutex<HashMap<Hash, EmptyPod>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
impl EmptyPod {
pub fn _prove(params: &Params, vds_root: Hash) -> Result<EmptyPod> {
let (empty_pod_verify_target, data) = &*STANDARD_EMPTY_POD_DATA;
let mut pw = PartialWitness::<F>::new();
empty_pod_verify_target.set_targets(&mut pw, vds_root)?;
let proof = timed!("EmptyPod prove", data.prove(pw)?);
let id = &proof.public_inputs[PI_OFFSET_ID..PI_OFFSET_ID + HASH_SIZE];
let id = PodId(Hash([id[0], id[1], id[2], id[3]]));
Ok(EmptyPod {
params: params.clone(),
id,
vds_root,
proof: proof.proof,
})
}
pub fn new_boxed(params: &Params, vds_root: Hash) -> Box<dyn RecursivePod> {
let default_params = &*DEFAULT_PARAMS;
assert_eq!(default_params.id_params(), params.id_params());
let empty_pod = EMPTY_POD_CACHE
.lock()
.unwrap()
.entry(vds_root)
.or_insert_with(|| Self::_prove(params, vds_root).expect("prove EmptyPod"))
.clone();
Box::new(empty_pod)
}
fn _verify(&self) -> Result<()> {
let statements = self
.pub_self_statements()
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let id = PodId(calculate_id(&statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
}
let public_inputs = id
.to_fields(&self.params)
.iter()
.chain(EMPTY_HASH.0.iter()) // slot for the unused vds root
.cloned()
.collect_vec();
let (_, data) = &*STANDARD_EMPTY_POD_DATA;
data.verify(ProofWithPublicInputs {
proof: self.proof.clone(),
public_inputs,
})
.map_err(|e| Error::custom(format!("EmptyPod proof verification failure: {:?}", e)))
}
}
impl Pod for EmptyPod {
fn params(&self) -> &Params {
&self.params
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
self.id
}
fn pub_self_statements(&self) -> Vec<middleware::Statement> {
vec![type_statement()]
}
fn serialized_proof(&self) -> String {
let mut buffer = Vec::new();
use plonky2::util::serialization::Write;
buffer.write_proof(&self.proof).unwrap();
BASE64_STANDARD.encode(buffer)
}
}
impl RecursivePod for EmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
let (_, data) = &*STANDARD_EMPTY_POD_DATA;
data.verifier_only.clone()
}
fn proof(&self) -> Proof {
self.proof.clone()
}
fn vds_root(&self) -> Hash {
self.vds_root
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_empty_pod() {
let params = Params::default();
let empty_pod = EmptyPod::new_boxed(&params, EMPTY_HASH);
empty_pod.verify().unwrap();
}
}

View file

@ -7,31 +7,29 @@ use itertools::Itertools;
pub use operation::*;
use plonky2::{
hash::poseidon::PoseidonHash,
iop::witness::PartialWitness,
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::{CircuitConfig, CommonCircuitData},
config::Hasher,
proof::{Proof, ProofWithPublicInputs},
},
plonk::{circuit_data::CommonCircuitData, config::Hasher},
util::serialization::{Buffer, Read},
};
pub use statement::*;
use crate::{
backends::plonky2::{
basetypes::{C, D},
basetypes::{Proof, ProofWithPublicInputs, VerifierOnlyCircuitData, D},
circuits::mainpod::{
CustomPredicateVerification, MainPodVerifyCircuit, MainPodVerifyInput,
CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget, NUM_PUBLIC_INPUTS,
},
emptypod::EmptyPod,
error::{Error, Result},
mock::emptypod::MockEmptyPod,
primitives::merkletree::MerkleClaimAndProof,
recursion::{self, RecursiveCircuit, RecursiveParams},
signedpod::SignedPod,
STANDARD_REC_MAIN_POD_CIRCUIT_DATA,
},
middleware::{
self, resolve_wildcard_values, AnchoredKey, CustomPredicateBatch, DynError, Hash,
MainPodInputs, NativeOperation, NonePod, OperationType, Params, Pod, PodId, PodProver,
PodType, StatementArg, ToFields, F, KEY_TYPE, SELF,
PodType, RecursivePod, StatementArg, ToFields, F, KEY_TYPE, SELF,
},
};
@ -42,11 +40,8 @@ use crate::{
/// with a precomputed constant corresponding to the front-padding part:
/// `id = hash(serialize(reverse(statements || none-statements)))`
pub(crate) fn calculate_id(statements: &[Statement], params: &Params) -> middleware::Hash {
assert_eq!(params.max_public_statements, statements.len());
assert!(statements.len() <= params.num_public_statements_id);
assert!(params.max_public_statements <= params.num_public_statements_id);
statements
.iter()
.for_each(|st| assert_eq!(params.max_statement_args, st.1.len()));
let mut none_st: Statement = middleware::Statement::None.into();
pad_statement(params, &mut none_st);
@ -250,7 +245,11 @@ fn pad_operation_args(params: &Params, args: &mut Vec<OperationArg>) {
/// Returns the statements from the given MainPodInputs, padding to the
/// respective max lengths defined at the given Params.
pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<Statement> {
pub(crate) fn layout_statements(
params: &Params,
mock: bool,
inputs: &MainPodInputs,
) -> Result<Vec<Statement>> {
let mut statements = Vec::new();
// Statement at index 0 is always None to be used for padding operation arguments in custom
@ -258,6 +257,8 @@ pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<
statements.push(middleware::Statement::None.into());
// Input signed pods region
// TODO: Replace this with a dumb signed pod
// https://github.com/0xPARC/pod2/issues/246
let none_sig_pod_box: Box<dyn Pod> = Box::new(NonePod {});
let none_sig_pod = none_sig_pod_box.as_ref();
assert!(inputs.signed_pods.len() <= params.max_input_signed_pods);
@ -277,14 +278,20 @@ pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<
}
// Input main pods region
let none_main_pod_box: Box<dyn Pod> = Box::new(NonePod {});
let none_main_pod = none_main_pod_box.as_ref();
assert!(inputs.main_pods.len() <= params.max_input_main_pods);
for i in 0..params.max_input_main_pods {
let pod = inputs.main_pods.get(i).copied().unwrap_or(none_main_pod);
let empty_pod_box: Box<dyn RecursivePod> =
if mock || inputs.recursive_pods.len() == params.max_input_recursive_pods {
// We mocking or we don't need padding so we skip creating an EmptyPod
MockEmptyPod::new_boxed(params)
} else {
EmptyPod::new_boxed(params, inputs.vds_root)
};
let empty_pod = empty_pod_box.as_ref();
assert!(inputs.recursive_pods.len() <= params.max_input_recursive_pods);
for i in 0..params.max_input_recursive_pods {
let pod = inputs.recursive_pods.get(i).copied().unwrap_or(empty_pod);
let sts = pod.pub_statements();
assert!(sts.len() <= params.max_public_statements);
for j in 0..params.max_public_statements {
for j in 0..params.max_input_pods_public_statements {
let mut st = sts
.get(j)
.unwrap_or(&middleware::Statement::None)
@ -329,7 +336,7 @@ pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<
statements.push(st);
}
statements
Ok(statements)
}
pub(crate) fn process_private_statements_operations(
@ -399,15 +406,25 @@ pub(crate) fn process_public_statements_operations(
pub struct Prover {}
impl Prover {
fn _prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result<MainPod> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let main_pod = MainPodVerifyCircuit {
params: params.clone(),
}
.eval(&mut builder)?;
fn _prove(&self, params: &Params, inputs: MainPodInputs) -> Result<MainPod> {
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
let (main_pod_target, circuit_data) =
RecursiveCircuit::<MainPodVerifyTarget>::circuit_data_padded(
params.max_input_recursive_pods,
&rec_circuit_data.common,
params,
)?;
let rec_params = RecursiveParams {
arity: params.max_input_recursive_pods,
common_data: circuit_data.common.clone(),
verifier_data: circuit_data.verifier_data(),
};
let main_pod = RecursiveCircuit {
params: rec_params,
prover: circuit_data.prover_data(),
target: main_pod_target,
};
let mut pw = PartialWitness::<F>::new();
let signed_pods_input: Vec<SignedPod> = inputs
.signed_pods
.iter()
@ -419,6 +436,33 @@ impl Prover {
})
.collect_vec();
// Pad input recursive pods with empty pods if necessary
let empty_pod = if inputs.recursive_pods.len() == params.max_input_recursive_pods {
// We don't need padding so we skip creating an EmptyPod
MockEmptyPod::new_boxed(params)
} else {
EmptyPod::new_boxed(params, inputs.vds_root)
};
let inputs = MainPodInputs {
recursive_pods: &inputs
.recursive_pods
.iter()
.copied()
.chain(iter::repeat(&*empty_pod))
.take(params.max_input_recursive_pods)
.collect_vec(),
..inputs
};
let recursive_pods_pub_self_statements = inputs
.recursive_pods
.iter()
.map(|pod| {
assert_eq!(params.id_params(), pod.params().id_params());
pod.pub_self_statements()
})
.collect_vec();
let merkle_proofs = extract_merkle_proofs(params, inputs.operations)?;
let custom_predicate_batches = extract_custom_predicate_batches(params, inputs.operations)?;
let custom_predicate_verifications = extract_custom_predicate_verifications(
@ -427,7 +471,7 @@ impl Prover {
&custom_predicate_batches,
)?;
let statements = layout_statements(params, &inputs);
let statements = layout_statements(params, false, &inputs)?;
let operations = process_private_statements_operations(
params,
&statements,
@ -442,23 +486,38 @@ impl Prover {
// get the id out of the public statements
let id: PodId = PodId(calculate_id(&public_statements, params));
let proofs = inputs
.recursive_pods
.iter()
.map(|pod| {
assert_eq!(inputs.vds_root, pod.vds_root());
ProofWithPublicInputs {
proof: pod.proof(),
public_inputs: [pod.id().0 .0, inputs.vds_root.0].concat(),
}
})
.collect_vec();
let verifier_datas = inputs
.recursive_pods
.iter()
.map(|pod| pod.verifier_data())
.collect_vec();
let input = MainPodVerifyInput {
vds_root: inputs.vds_root,
signed_pods: signed_pods_input,
recursive_pods_pub_self_statements,
statements: statements[statements.len() - params.max_statements..].to_vec(),
operations,
merkle_proofs,
custom_predicate_batches,
custom_predicate_verifications,
};
main_pod.set_targets(&mut pw, &input)?;
// generate & verify proof
let data = builder.build::<C>();
let proof_with_pis = data.prove(pw)?;
let proof_with_pis = main_pod.prove(&input, proofs, verifier_datas)?;
Ok(MainPod {
params: params.clone(),
id,
vds_root: inputs.vds_root,
public_statements,
proof: proof_with_pis.proof,
})
@ -467,41 +526,21 @@ impl Prover {
impl PodProver for Prover {
fn prove(
&mut self,
&self,
params: &Params,
inputs: MainPodInputs,
) -> Result<Box<dyn Pod>, Box<DynError>> {
) -> Result<Box<dyn RecursivePod>, Box<DynError>> {
Ok(self._prove(params, inputs).map(Box::new)?)
}
}
pub type MainPodProof = Proof<F, C, D>;
#[derive(Clone, Debug)]
pub struct MainPod {
params: Params,
id: PodId,
vds_root: Hash,
public_statements: Vec<Statement>,
proof: MainPodProof,
}
/// Convert a Statement into middleware::Statement and replace references to SELF by `self_id`.
pub(crate) fn normalize_statement(statement: &Statement, self_id: PodId) -> middleware::Statement {
Statement(
statement.0.clone(),
statement
.1
.iter()
.map(|sa| match &sa {
StatementArg::Key(AnchoredKey { pod_id, key }) if *pod_id == SELF => {
StatementArg::Key(AnchoredKey::new(self_id, key.clone()))
}
_ => sa.clone(),
})
.collect(),
)
.try_into()
.unwrap()
proof: Proof,
}
// This is a helper function to get the CommonCircuitData necessary to decode
@ -509,66 +548,76 @@ pub(crate) fn normalize_statement(statement: &Statement, self_id: PodId) -> midd
// as a constant or with static initialization, but in the meantime we can
// generate it on-demand.
fn get_common_data(params: &Params) -> Result<CommonCircuitData<F, D>, Error> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let _main_pod = MainPodVerifyCircuit {
params: params.clone(),
}
.eval(&mut builder)
.map_err(|e| Error::custom(format!("Failed to evaluate MainPodVerifyCircuit: {}", e)))?;
let data = builder.build::<C>();
Ok(data.common)
// TODO: Cache this somehow
// https://github.com/0xPARC/pod2/issues/247
let rec_params = recursion::new_params::<MainPodVerifyTarget>(
params.max_input_recursive_pods,
NUM_PUBLIC_INPUTS,
params,
)?;
Ok(rec_params.common_data().clone())
}
impl MainPod {
fn _verify(&self) -> Result<()> {
// 2. get the id out of the public statements
let id: PodId = PodId(calculate_id(&self.public_statements, &self.params));
let id = PodId(calculate_id(&self.public_statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
}
// 1, 3, 4, 5 verification via the zkSNARK proof
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
// TODO: cache these artefacts
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let _main_pod = MainPodVerifyCircuit {
params: self.params.clone(),
}
.eval(&mut builder)?;
let data = builder.build::<C>();
data.verify(ProofWithPublicInputs {
proof: self.proof.clone(),
public_inputs: id.to_fields(&self.params),
})
.map_err(|e| Error::custom(format!("MainPod proof verification failure: {:?}", e)))
// https://github.com/0xPARC/pod2/issues/247
let (_, circuit_data) = RecursiveCircuit::<MainPodVerifyTarget>::circuit_data_padded(
self.params.max_input_recursive_pods,
&rec_circuit_data.common,
&self.params,
)?;
let public_inputs = id
.to_fields(&self.params)
.iter()
.chain(self.vds_root.0.iter())
.cloned()
.collect_vec();
circuit_data
.verify(ProofWithPublicInputs {
proof: self.proof.clone(),
public_inputs,
})
.map_err(|e| Error::custom(format!("MainPod proof verification failure: {:?}", e)))
}
pub fn proof(&self) -> MainPodProof {
pub fn proof(&self) -> Proof {
self.proof.clone()
}
pub fn vds_root(&self) -> Hash {
self.vds_root
}
pub fn params(&self) -> &Params {
&self.params
}
pub(crate) fn new(
proof: MainPodProof,
proof: Proof,
public_statements: Vec<Statement>,
id: PodId,
vds_root: Hash,
params: Params,
) -> Self {
Self {
params,
id,
vds_root,
public_statements,
proof,
}
}
pub fn decode_proof(proof: &str, params: &Params) -> Result<MainPodProof, Error> {
pub fn decode_proof(proof: &str, params: &Params) -> Result<Proof, Error> {
let decoded = BASE64_STANDARD.decode(proof).map_err(|e| {
Error::custom(format!(
"Failed to decode proof from base64: {}. Value: {}",
@ -590,6 +639,9 @@ impl MainPod {
}
impl Pod for MainPod {
fn params(&self) -> &Params {
&self.params
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
@ -598,12 +650,11 @@ impl Pod for MainPod {
self.id
}
fn pub_statements(&self) -> Vec<middleware::Statement> {
// return the public statements, where when origin=SELF is replaced by origin=self.id()
fn pub_self_statements(&self) -> Vec<middleware::Statement> {
self.public_statements
.iter()
.cloned()
.map(|statement| normalize_statement(&statement, self.id()))
.map(|st| st.try_into().expect("valid statement"))
.collect()
}
@ -615,6 +666,19 @@ impl Pod for MainPod {
}
}
impl RecursivePod for MainPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
let data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
data.verifier_only.clone()
}
fn proof(&self) -> Proof {
self.proof.clone()
}
fn vds_root(&self) -> Hash {
self.vds_root
}
}
#[cfg(test)]
pub mod tests {
use super::*;
@ -642,7 +706,7 @@ pub mod tests {
let params = middleware::Params {
// Currently the circuit uses random access that only supports vectors of length 64.
// With max_input_main_pods=3 we need random access to a vector of length 73.
max_input_main_pods: 0,
max_input_recursive_pods: 0,
max_custom_predicate_batches: 0,
max_custom_predicate_verifications: 0,
..Default::default()
@ -672,10 +736,11 @@ pub mod tests {
fn test_mini_0() {
let params = middleware::Params {
max_input_signed_pods: 1,
max_input_main_pods: 1,
max_input_recursive_pods: 1,
max_signed_pod_values: 6,
max_statements: 8,
max_public_statements: 4,
max_input_pods_public_statements: 10,
..Default::default()
};
@ -715,7 +780,8 @@ pub mod tests {
fn test_mainpod_small_empty() {
let params = middleware::Params {
max_input_signed_pods: 0,
max_input_main_pods: 0,
max_input_recursive_pods: 0,
max_input_pods_public_statements: 2,
max_statements: 5,
max_signed_pod_values: 2,
max_public_statements: 2,
@ -753,14 +819,11 @@ pub mod tests {
fn test_main_ethdos() -> frontend::Result<()> {
let params = Params {
max_input_signed_pods: 2,
max_input_main_pods: 1,
max_input_recursive_pods: 1,
max_statements: 26,
max_public_statements: 5,
max_signed_pod_values: 8,
max_statement_args: 3,
max_operation_args: 4,
max_custom_predicate_arity: 4,
max_custom_batch_size: 3,
max_operation_args: 5,
max_custom_predicate_wildcards: 6,
max_custom_predicate_verifications: 8,
..Default::default()
@ -805,7 +868,7 @@ pub mod tests {
fn test_main_mini_custom_1() -> frontend::Result<()> {
let params = Params {
max_input_signed_pods: 0,
max_input_main_pods: 0,
max_input_recursive_pods: 0,
max_statements: 9,
max_public_statements: 4,
max_statement_args: 3,

View file

@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, iter};
use serde::{Deserialize, Serialize};
@ -28,9 +28,15 @@ impl Statement {
}
impl ToFields for Statement {
fn to_fields(&self, _params: &Params) -> Vec<middleware::F> {
let mut fields = self.0.to_fields(_params);
fields.extend(self.1.iter().flat_map(|arg| arg.to_fields(_params)));
fn to_fields(&self, params: &Params) -> Vec<middleware::F> {
let mut fields = self.0.to_fields(params);
fields.extend(
self.1
.iter()
.chain(iter::repeat(&StatementArg::None))
.take(params.max_statement_args)
.flat_map(|arg| arg.to_fields(params)),
);
fields
}
}

View file

@ -0,0 +1,93 @@
use itertools::Itertools;
use crate::{
backends::plonky2::{
basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result},
mainpod::{self, calculate_id},
},
middleware::{
AnchoredKey, DynError, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement, Value,
KEY_TYPE, SELF,
},
};
#[derive(Clone, Debug)]
pub struct MockEmptyPod {
params: Params,
id: PodId,
}
fn type_statement() -> Statement {
Statement::ValueOf(
AnchoredKey::from((SELF, KEY_TYPE)),
Value::from(PodType::Empty),
)
}
impl MockEmptyPod {
pub fn new_boxed(params: &Params) -> Box<dyn RecursivePod> {
let statements = [mainpod::Statement::from(type_statement())];
let id = PodId(calculate_id(&statements, params));
Box::new(Self {
params: params.clone(),
id,
})
}
fn _verify(&self) -> Result<()> {
let statements = self
.pub_self_statements()
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let id = PodId(calculate_id(&statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
}
Ok(())
}
}
impl Pod for MockEmptyPod {
fn params(&self) -> &Params {
&self.params
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
self.id
}
fn pub_self_statements(&self) -> Vec<Statement> {
vec![type_statement()]
}
fn serialized_proof(&self) -> String {
todo!()
}
}
impl RecursivePod for MockEmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
panic!("MockEmptyPod can't be verified in a recursive MainPod circuit");
}
fn proof(&self) -> Proof {
panic!("MockEmptyPod can't be verified in a recursive MainPod circuit");
}
fn vds_root(&self) -> Hash {
panic!("MockEmptyPod can't be verified in a recursive MainPod circuit");
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_mock_empty_pod() {
let params = Params::default();
let empty_pod = MockEmptyPod::new_boxed(&params);
empty_pod.verify().unwrap();
}
}

View file

@ -9,17 +9,18 @@ use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result},
mainpod::{
calculate_id, extract_merkle_proofs, layout_statements, normalize_statement,
calculate_id, extract_merkle_proofs, layout_statements,
process_private_statements_operations, process_public_statements_operations, Operation,
Statement,
},
primitives::merkletree::MerkleClaimAndProof,
},
middleware::{
self, hash_str, AnchoredKey, DynError, MainPodInputs, NativePredicate, Params, Pod, PodId,
PodProver, Predicate, StatementArg, KEY_TYPE, SELF,
self, hash_str, AnchoredKey, DynError, Hash, MainPodInputs, NativePredicate, Params, Pod,
PodId, PodProver, Predicate, RecursivePod, StatementArg, KEY_TYPE, SELF,
},
};
@ -27,10 +28,10 @@ pub struct MockProver {}
impl PodProver for MockProver {
fn prove(
&mut self,
&self,
params: &Params,
inputs: MainPodInputs,
) -> Result<Box<dyn Pod>, Box<DynError>> {
) -> Result<Box<dyn RecursivePod>, Box<DynError>> {
Ok(Box::new(MockMainPod::new(params, inputs)?))
}
}
@ -73,7 +74,8 @@ impl fmt::Display for MockMainPod {
}
if (i >= offset_input_main_pods)
&& (i < offset_input_statements)
&& ((i - offset_input_main_pods) % self.params.max_public_statements == 0)
&& ((i - offset_input_main_pods) % self.params.max_input_pods_public_statements
== 0)
{
writeln!(
f,
@ -137,7 +139,7 @@ impl MockMainPod {
}
fn offset_input_statements(&self) -> usize {
self.offset_input_main_pods()
+ self.params.max_input_main_pods * self.params.max_public_statements
+ self.params.max_input_recursive_pods * self.params.max_input_pods_public_statements
}
fn offset_public_statements(&self) -> usize {
self.offset_input_statements() + self.params.max_priv_statements()
@ -146,7 +148,7 @@ impl MockMainPod {
pub fn new(params: &Params, inputs: MainPodInputs) -> Result<Self> {
// TODO: Insert a new public statement of ValueOf with `key=KEY_TYPE,
// value=PodType::MockMainPod`
let statements = layout_statements(params, &inputs);
let statements = layout_statements(params, true, &inputs)?;
// Extract Merkle proofs and pad.
let merkle_proofs = extract_merkle_proofs(params, inputs.operations)?;
@ -278,20 +280,20 @@ impl MockMainPod {
}
impl Pod for MockMainPod {
fn params(&self) -> &Params {
&self.params
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
self.id
}
fn pub_statements(&self) -> Vec<middleware::Statement> {
// return the public statements, where when origin=SELF is replaced by origin=self.id()
// By convention we expect the KEY_TYPE to be the first statement
self.statements
fn pub_self_statements(&self) -> Vec<middleware::Statement> {
self.public_statements
.iter()
.skip(self.offset_public_statements())
.cloned()
.map(|statement| normalize_statement(&statement, self.id()))
.map(|st| st.try_into().expect("valid statement"))
.collect()
}
@ -300,6 +302,18 @@ impl Pod for MockMainPod {
}
}
impl RecursivePod for MockMainPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn proof(&self) -> Proof {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn vds_root(&self) -> Hash {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
}
#[cfg(test)]
pub mod tests {
use std::any::Any;

View file

@ -1,2 +1,3 @@
pub mod emptypod;
pub mod mainpod;
pub mod signedpod;

View file

@ -10,7 +10,7 @@ use crate::{
constants::MAX_DEPTH,
middleware::{
containers::Dictionary, hash_str, AnchoredKey, DynError, Hash, Key, Params, Pod, PodId,
PodSigner, PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
PodSigner, PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
},
};
@ -111,6 +111,9 @@ impl MockSignedPod {
}
impl Pod for MockSignedPod {
fn params(&self) -> &Params {
panic!("MockSignedPod doesn't have params");
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
@ -119,8 +122,7 @@ impl Pod for MockSignedPod {
self.id
}
fn pub_statements(&self) -> Vec<Statement> {
let id = self.id();
fn pub_self_statements(&self) -> Vec<Statement> {
// By convention we put the KEY_TYPE first and KEY_SIGNER second
let mut kvs = self.kvs.clone();
let key_type = Key::from(KEY_TYPE);
@ -130,7 +132,7 @@ impl Pod for MockSignedPod {
[(key_type, value_type), (key_signer, value_signer)]
.into_iter()
.chain(kvs.into_iter().sorted_by_key(|kv| kv.0.hash()))
.map(|(k, v)| Statement::ValueOf(AnchoredKey::from((id, k)), v))
.map(|(k, v)| Statement::ValueOf(AnchoredKey::from((SELF, k)), v))
.collect()
}

View file

@ -1,5 +1,6 @@
pub mod basetypes;
pub mod circuits;
pub mod emptypod;
mod error;
pub mod mainpod;
pub mod mock;
@ -7,4 +8,32 @@ pub mod primitives;
pub mod recursion;
pub mod signedpod;
use std::sync::LazyLock;
pub use error::*;
use crate::{
backends::plonky2::{
basetypes::CircuitData,
circuits::mainpod::{MainPodVerifyTarget, NUM_PUBLIC_INPUTS},
recursion::RecursiveCircuit,
},
middleware::Params,
timed,
};
pub static DEFAULT_PARAMS: LazyLock<Params> = LazyLock::new(Params::default);
pub static STANDARD_REC_MAIN_POD_CIRCUIT_DATA: LazyLock<CircuitData> = LazyLock::new(|| {
let params = &*DEFAULT_PARAMS;
timed!(
"recursive MainPod circuit_data",
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data(
params.max_input_recursive_pods,
NUM_PUBLIC_INPUTS,
params
)
.expect("calculate circuit_data")
.1
)
});

View file

@ -23,7 +23,7 @@ use plonky2::{
use crate::{
backends::plonky2::{
basetypes::{Proof, C, D},
basetypes::{C, D},
circuits::common::{CircuitBuilderPod, ValueTarget},
error::Result,
primitives::signature::{
@ -31,7 +31,7 @@ use crate::{
},
},
measure_gates_begin, measure_gates_end,
middleware::{Hash, RawValue, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE},
middleware::{Hash, Proof, RawValue, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE},
};
lazy_static! {

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,5 @@
pub mod circuit;
pub use circuit::{InnerCircuit, RecursiveCircuit, RecursiveParams};
pub use circuit::{
common_data_for_recursion, new_params, new_params_padded, pad_circuit, InnerCircuit,
RecursiveCircuit, RecursiveParams, VerifiedProofTarget,
};

View file

@ -15,7 +15,7 @@ use crate::{
constants::MAX_DEPTH,
middleware::{
containers::Dictionary, AnchoredKey, DynError, Hash, Key, Params, Pod, PodId, PodSigner,
PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
},
};
@ -120,6 +120,9 @@ impl SignedPod {
}
impl Pod for SignedPod {
fn params(&self) -> &Params {
panic!("SignedPod doesn't have params");
}
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify().map_err(Box::new)?)
}
@ -128,8 +131,7 @@ impl Pod for SignedPod {
self.id
}
fn pub_statements(&self) -> Vec<Statement> {
let id = self.id();
fn pub_self_statements(&self) -> Vec<Statement> {
// By convention we put the KEY_TYPE first and KEY_SIGNER second
let mut kvs: HashMap<Key, Value> = self.dict.kvs().clone();
let key_type = Key::from(KEY_TYPE);
@ -139,7 +141,7 @@ impl Pod for SignedPod {
[(key_type, value_type), (key_signer, value_signer)]
.into_iter()
.chain(kvs.into_iter().sorted_by_key(|kv| kv.0.hash()))
.map(|(k, v)| Statement::ValueOf(AnchoredKey::from((id, k)), v))
.map(|(k, v)| Statement::ValueOf(AnchoredKey::from((SELF, k)), v))
.collect()
}