From ce26a316a14237f315cb546d751e78c28601d289 Mon Sep 17 00:00:00 2001 From: "Eduard S." Date: Tue, 1 Apr 2025 11:23:45 -0700 Subject: [PATCH] MainPod implementation (#168) * Initial circuit op work * Fix copy op * Add more ops * add mainpod boilerplate * pass basic test of mainpod * fix duplicate imports * WIP * fixes * wip * fix test * wip * clean up * address feedback from @ax0 * oops --------- Co-authored-by: Ahmad --- Cargo.toml | 4 + src/backends/plonky2/circuits/common.rs | 82 ++++-- src/backends/plonky2/circuits/mainpod.rs | 165 +++++++----- src/backends/plonky2/circuits/signedpod.rs | 44 +++- src/backends/plonky2/mainpod.rs | 278 +++++++++++++++++++++ src/backends/plonky2/mock/mainpod/mod.rs | 19 +- src/backends/plonky2/mock/signedpod.rs | 12 +- src/backends/plonky2/mod.rs | 1 + src/backends/plonky2/signedpod.rs | 13 +- src/middleware/statement.rs | 16 ++ 10 files changed, 530 insertions(+), 104 deletions(-) create mode 100644 src/backends/plonky2/mainpod.rs diff --git a/Cargo.toml b/Cargo.toml index 7db3e83..ed4c5d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,10 @@ serde_json = "1.0.140" base64 = "0.22.1" schemars = "1.0.0-alpha.17" +# Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory. +# [patch."https://github.com/0xPolygonZero/plonky2"] +# plonky2 = { path = "../plonky2/plonky2" } + [features] default = ["backend_plonky2"] backend_plonky2 = ["plonky2"] diff --git a/src/backends/plonky2/circuits/common.rs b/src/backends/plonky2/circuits/common.rs index 4040b63..0510258 100644 --- a/src/backends/plonky2/circuits/common.rs +++ b/src/backends/plonky2/circuits/common.rs @@ -4,10 +4,9 @@ use crate::backends::plonky2::basetypes::D; use crate::backends::plonky2::mock::mainpod::Statement; use crate::backends::plonky2::mock::mainpod::{Operation, OperationArg}; use crate::middleware::{ - NativeOperation, NativePredicate, Params, Predicate, StatementArg, ToFields, Value, F, - HASH_SIZE, VALUE_SIZE, + NativeOperation, NativePredicate, Params, Predicate, StatementArg, ToFields, Value, + EMPTY_VALUE, F, HASH_SIZE, OPERATION_ARG_F_LEN, STATEMENT_ARG_F_LEN, VALUE_SIZE, }; -use crate::middleware::{OPERATION_ARG_F_LEN, STATEMENT_ARG_F_LEN}; use anyhow::Result; use plonky2::field::extension::Extendable; use plonky2::field::types::{Field, PrimeField64}; @@ -51,10 +50,55 @@ impl ValueTarget { } } +#[derive(Clone)] +pub struct StatementArgTarget { + pub elements: [Target; STATEMENT_ARG_F_LEN], +} + +impl StatementArgTarget { + pub fn set_targets( + &self, + pw: &mut PartialWitness, + params: &Params, + arg: &StatementArg, + ) -> Result<()> { + pw.set_target_arr(&self.elements, &arg.to_fields(params)) + } + + fn new(first: ValueTarget, second: ValueTarget) -> Self { + let elements: Vec<_> = first + .elements + .into_iter() + .chain(second.elements.into_iter()) + .collect(); + StatementArgTarget { + elements: elements.try_into().expect("size STATEMENT_ARG_F_LEN"), + } + } + + pub fn none(builder: &mut CircuitBuilder) -> Self { + let empty = builder.constant_value(EMPTY_VALUE); + Self::new(empty.clone(), empty) + } + + pub fn literal(builder: &mut CircuitBuilder, value: &ValueTarget) -> Self { + let empty = builder.constant_value(EMPTY_VALUE); + Self::new(value.clone(), empty) + } + + pub fn anchored_key( + _builder: &mut CircuitBuilder, + pod_id: &ValueTarget, + key: &ValueTarget, + ) -> Self { + Self::new(pod_id.clone(), key.clone()) + } +} + #[derive(Clone)] pub struct StatementTarget { pub predicate: [Target; Params::predicate_size()], - pub args: Vec<[Target; STATEMENT_ARG_F_LEN]>, + pub args: Vec, } impl StatementTarget { @@ -62,18 +106,16 @@ impl StatementTarget { builder: &mut CircuitBuilder, params: &Params, predicate: NativePredicate, - args: &[[Target; STATEMENT_ARG_F_LEN]], + args: &[StatementArgTarget], ) -> Self { let predicate_vec = builder.constants(&Predicate::Native(predicate).to_fields(params)); Self { predicate: array::from_fn(|i| predicate_vec[i]), args: args .iter() - .map(|arg| *arg) - .chain( - iter::repeat([builder.zero(); STATEMENT_ARG_F_LEN]) - .take(params.max_statement_args - args.len()), - ) + .cloned() + .chain(iter::repeat_with(|| StatementArgTarget::none(builder))) + .take(params.max_statement_args) .collect(), } } @@ -92,7 +134,7 @@ impl StatementTarget { .take(params.max_statement_args) .enumerate() { - pw.set_target_arr(&self.args[i], &arg.to_fields(params))?; + self.args[i].set_targets(pw, params, arg)?; } Ok(()) } @@ -159,7 +201,7 @@ impl Flattenable for StatementTarget { fn flatten(&self) -> Vec { self.predicate .iter() - .chain(self.args.iter().flatten()) + .chain(self.args.iter().flat_map(|a| &a.elements)) .cloned() .collect() } @@ -172,7 +214,11 @@ impl Flattenable for StatementTarget { ); let predicate: [Target; Params::predicate_size()] = array::from_fn(|i| v[i]); let args = (0..num_args) - .map(|i| array::from_fn(|j| v[Params::predicate_size() + i * STATEMENT_ARG_F_LEN + j])) + .map(|i| StatementArgTarget { + elements: array::from_fn(|j| { + v[Params::predicate_size() + i * STATEMENT_ARG_F_LEN + j] + }), + }) .collect(); Self { predicate, args } @@ -192,7 +238,7 @@ pub trait CircuitBuilderPod, const D: usize> { // Convenience methods for checking values. /// Checks whether `xs` is right-padded with 0s so as to represent a `Value`. - fn statement_arg_is_value(&mut self, xs: &[Target]) -> BoolTarget; + fn statement_arg_is_value(&mut self, arg: &StatementArgTarget) -> BoolTarget; /// Checks whether `x < y` if `b` is true. This involves checking /// that `x` and `y` each consist of two `u32` limbs. fn assert_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget); @@ -231,7 +277,9 @@ impl CircuitBuilderPod for CircuitBuilder { StatementTarget { predicate: self.add_virtual_target_arr(), args: (0..params.max_statement_args) - .map(|_| self.add_virtual_target_arr()) + .map(|_| StatementArgTarget { + elements: self.add_virtual_target_arr(), + }) .collect(), } } @@ -272,11 +320,11 @@ impl CircuitBuilderPod for CircuitBuilder { }) } - fn statement_arg_is_value(&mut self, xs: &[Target]) -> BoolTarget { + fn statement_arg_is_value(&mut self, arg: &StatementArgTarget) -> BoolTarget { let zeros = iter::repeat(self.zero()) .take(STATEMENT_ARG_F_LEN - VALUE_SIZE) .collect::>(); - self.is_equal_slice(&xs[VALUE_SIZE..], &zeros) + self.is_equal_slice(&arg.elements[VALUE_SIZE..], &zeros) } fn assert_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget) { diff --git a/src/backends/plonky2/circuits/mainpod.rs b/src/backends/plonky2/circuits/mainpod.rs index 2c06a83..2a4ec14 100644 --- a/src/backends/plonky2/circuits/mainpod.rs +++ b/src/backends/plonky2/circuits/mainpod.rs @@ -1,28 +1,16 @@ use anyhow::Result; -use itertools::Itertools; +use itertools::zip_eq; use plonky2::{ - field::types::Field, - hash::{ - hash_types::{HashOut, HashOutTarget}, - poseidon::PoseidonHash, - }, - iop::{ - target::{BoolTarget, Target}, - witness::{PartialWitness, WitnessWrite}, - }, + hash::{hash_types::HashOutTarget, poseidon::PoseidonHash}, + iop::{target::BoolTarget, witness::PartialWitness}, plonk::circuit_builder::CircuitBuilder, }; -use std::collections::HashMap; -use std::iter; -use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE}; +use crate::backends::plonky2::basetypes::{Value, D, EMPTY_HASH, F, VALUE_SIZE}; use crate::backends::plonky2::circuits::common::{ CircuitBuilderPod, OperationTarget, StatementTarget, ValueTarget, }; -use crate::backends::plonky2::primitives::merkletree::MerkleTree; -use crate::backends::plonky2::primitives::merkletree::{ - MerkleProofExistenceGadget, MerkleProofExistenceTarget, -}; +use crate::backends::plonky2::mock::mainpod; use crate::backends::plonky2::signedpod::SignedPod; use crate::middleware::{ hash_str, AnchoredKey, NativeOperation, NativePredicate, Params, PodType, Statement, @@ -118,18 +106,20 @@ impl OperationVerifyGadget { // The values embedded in the op args must match, the last // `STATEMENT_ARG_F_LEN - VALUE_SIZE` slots of each being 0. - let arg1_value = resolved_op_args[0].args[1]; - let arg2_value = resolved_op_args[1].args[1]; + let arg1_value = &resolved_op_args[0].args[1]; + let arg2_value = &resolved_op_args[1].args[1]; let op_arg_range_checks = [ - builder.statement_arg_is_value(&arg1_value), - builder.statement_arg_is_value(&arg2_value), + builder.statement_arg_is_value(arg1_value), + builder.statement_arg_is_value(arg2_value), ]; let op_arg_range_ok = builder.all(op_arg_range_checks); - let op_args_eq = - builder.is_equal_slice(&arg1_value[..VALUE_SIZE], &arg2_value[..VALUE_SIZE]); + let op_args_eq = builder.is_equal_slice( + &arg1_value.elements[..VALUE_SIZE], + &arg2_value.elements[..VALUE_SIZE], + ); - let arg1_key = resolved_op_args[0].args[0]; - let arg2_key = resolved_op_args[1].args[0]; + let arg1_key = resolved_op_args[0].args[0].clone(); + let arg2_key = resolved_op_args[1].args[0].clone(); let expected_statement = StatementTarget::new_native( builder, &self.params, @@ -167,21 +157,21 @@ impl OperationVerifyGadget { // The values embedded in the op args must satisfy `<`, the // last `STATEMENT_ARG_F_LEN - VALUE_SIZE` slots of each being // 0. - let arg1_value = resolved_op_args[0].args[1]; - let arg2_value = resolved_op_args[1].args[1]; - let op_arg_range_checks = [&arg1_value, &arg2_value] + let arg1_value = &resolved_op_args[0].args[1]; + let arg2_value = &resolved_op_args[1].args[1]; + let op_arg_range_checks = [arg1_value, arg2_value] .into_iter() .map(|x| builder.statement_arg_is_value(x)) .collect::>(); let op_arg_range_ok = builder.all(op_arg_range_checks); builder.assert_less_if( op_code_ok, - ValueTarget::from_slice(&arg1_value[..VALUE_SIZE]), - ValueTarget::from_slice(&arg2_value[..VALUE_SIZE]), + ValueTarget::from_slice(&arg1_value.elements[..VALUE_SIZE]), + ValueTarget::from_slice(&arg2_value.elements[..VALUE_SIZE]), ); - let arg1_key = resolved_op_args[0].args[0]; - let arg2_key = resolved_op_args[1].args[0]; + let arg1_key = resolved_op_args[0].args[0].clone(); + let arg2_key = resolved_op_args[1].args[0].clone(); let expected_statement = StatementTarget::new_native( builder, &self.params, @@ -222,14 +212,17 @@ impl OperationVerifyGadget { let expected_arg_prefix = builder.constants( &StatementArg::Key(AnchoredKey(SELF, EMPTY_HASH)).to_fields(&self.params)[..VALUE_SIZE], ); - let arg_prefix_ok = builder.is_equal_slice(&st.args[0][..VALUE_SIZE], &expected_arg_prefix); + let arg_prefix_ok = + builder.is_equal_slice(&st.args[0].elements[..VALUE_SIZE], &expected_arg_prefix); let dupe_check = { let individual_checks = prev_statements .into_iter() - .map(|ps| { + .enumerate() + .map(|(i, ps)| { let same_predicate = builder.is_equal_slice(&st.predicate, &ps.predicate); - let same_anchored_key = builder.is_equal_slice(&st.args[0], &ps.args[0]); + let same_anchored_key = + builder.is_equal_slice(&st.args[0].elements, &ps.args[0].elements); builder.and(same_predicate, same_anchored_key) }) .collect::>(); @@ -292,7 +285,22 @@ impl MainPodVerifyGadget { // Build the statement array let mut statements = Vec::new(); for signed_pod in &signed_pods { - statements.extend_from_slice(signed_pod.pub_statements().as_slice()); + statements.extend_from_slice(signed_pod.pub_statements(builder, false).as_slice()); + } + debug_assert_eq!( + statements.len(), + 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, + &[], + )) + } } // Add the input (private and public) statements and corresponding operations @@ -304,19 +312,22 @@ impl MainPodVerifyGadget { } let input_statements = &statements[input_statements_offset..]; - let pub_statements = &input_statements[statements.len() - params.max_public_statements..]; + let pub_statements = + &input_statements[input_statements.len() - params.max_public_statements..]; // 2. Calculate the Pod Id from the public statements let pub_statements_flattened = pub_statements .iter() - .map(|s| s.predicate.iter().chain(s.args.iter().flatten())) + .map(|s| { + s.predicate + .iter() + .chain(s.args.iter().flat_map(|a| &a.elements)) + }) .flatten() .cloned() .collect(); let id = builder.hash_n_to_hash_no_pad::(pub_statements_flattened); - // 3. TODO check that all `input_statements` of type `ValueOf` with origin=SELF have unique keys (no duplicates). Maybe we can do this via the NewEntry operation (check that the key doesn't exist in a previous statement with ID=SELF) - // 4. Verify type let type_statement = &pub_statements[0]; // TODO: Store this hash in a global static with lazy init so that we don't have to @@ -330,10 +341,12 @@ impl MainPodVerifyGadget { ); builder.connect_flattenable(type_statement, &expected_type_statement); + // 3. check that all `input_statements` of type `ValueOf` with origin=SELF have unique keys + // (no duplicates). We do this in the verification of NewEntry operation. // 5. Verify input statements let mut op_verifications = Vec::new(); for (i, (st, op)) in input_statements.iter().zip(operations.iter()).enumerate() { - let prev_statements = &statements[..input_statements_offset + i - 1]; + let prev_statements = &statements[..input_statements_offset + i]; let op_verification = OperationVerifyGadget { params: params.clone(), } @@ -352,7 +365,7 @@ impl MainPodVerifyGadget { } } -struct MainPodVerifyTarget { +pub struct MainPodVerifyTarget { params: Params, id: HashOutTarget, signed_pods: Vec, @@ -362,25 +375,33 @@ struct MainPodVerifyTarget { op_verifications: Vec, } -struct MainPodVerifyInput { - signed_pods: Vec, +pub struct MainPodVerifyInput { + pub signed_pods: Vec, + pub statements: Vec, + pub operations: Vec, } impl MainPodVerifyTarget { - fn set_targets(&self, pw: &mut PartialWitness, input: &MainPodVerifyInput) -> Result<()> { + pub fn set_targets( + &self, + pw: &mut PartialWitness, + input: &MainPodVerifyInput, + ) -> Result<()> { 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 + // TODO: Instead of using an input for padding, use a canonical minimal SignedPod + let pad_pod = &input.signed_pods[0]; for i in input.signed_pods.len()..self.params.max_input_signed_pods { - // TODO: We need to disable the verification for the unused slots. - // self.signed_pods[i].set_targets(pw, signed_pod)?; + self.signed_pods[i].set_targets(pw, pad_pod)?; + } + 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)?; + self.operations[i].set_targets(pw, &self.params, op)?; } - // TODO: set_targets for: - // - statements - // - operations - // - op_verifications Ok(()) } } @@ -470,30 +491,38 @@ mod tests { // NewEntry let st1: mainpod::Statement = Statement::ValueOf(AnchoredKey(SELF, "hello".into()), 55.into()).into(); - let op = mainpod::Operation( - OperationType::Native(NativeOperation::NewEntry), - vec![], - OperationAux::None, - ); - operation_verify(st1.clone(), op, vec![])?; - - // Copy - let op = mainpod::Operation( - OperationType::Native(NativeOperation::CopyStatement), - vec![OperationArg::Index(0)], - OperationAux::None, - ); - operation_verify(st, op, prev_statements)?; - - // Eq let st2: mainpod::Statement = Statement::ValueOf( AnchoredKey(PodId(Value::from(75).into()), "hello".into()), 55.into(), ) .into(); + let prev_statements = vec![st2]; + let op = mainpod::Operation( + OperationType::Native(NativeOperation::NewEntry), + vec![], + OperationAux::None, + ); + operation_verify(st1.clone(), op, prev_statements.clone())?; + + // Copy + let st: mainpod::Statement = Statement::None.into(); + let op = mainpod::Operation( + OperationType::Native(NativeOperation::CopyStatement), + vec![OperationArg::Index(0)], + OperationAux::None, + ); + let prev_statements = vec![Statement::None.into()]; + operation_verify(st, op, prev_statements)?; + + // Eq + let st2: mainpod::Statement = Statement::ValueOf( + AnchoredKey(PodId(Value::from(75).into()), "world".into()), + 55.into(), + ) + .into(); let st: mainpod::Statement = Statement::Equal( AnchoredKey(SELF, "hello".into()), - AnchoredKey(PodId(Value::from(75).into()), "hello".into()), + AnchoredKey(PodId(Value::from(75).into()), "world".into()), ) .into(); let op = mainpod::Operation( diff --git a/src/backends/plonky2/circuits/signedpod.rs b/src/backends/plonky2/circuits/signedpod.rs index 49dc851..dacb414 100644 --- a/src/backends/plonky2/circuits/signedpod.rs +++ b/src/backends/plonky2/circuits/signedpod.rs @@ -2,20 +2,24 @@ use anyhow::Result; use itertools::Itertools; use plonky2::{ hash::hash_types::{HashOut, HashOutTarget}, + iop::target::Target, iop::witness::{PartialWitness, WitnessWrite}, plonk::circuit_builder::CircuitBuilder, }; +use std::iter; use crate::backends::plonky2::{ basetypes::{Value, D, EMPTY_VALUE, F}, - circuits::common::{CircuitBuilderPod, StatementTarget, ValueTarget}, + circuits::common::{CircuitBuilderPod, StatementArgTarget, StatementTarget, ValueTarget}, primitives::{ merkletree::{MerkleProof, MerkleProofExistenceGadget, MerkleProofExistenceTarget}, signature::{PublicKey, SignatureVerifyGadget, SignatureVerifyTarget}, }, signedpod::SignedPod, }; -use crate::middleware::{hash_str, Params, PodType, KEY_SIGNER, KEY_TYPE}; +use crate::middleware::{ + hash_str, NativePredicate, Params, PodType, Predicate, ToFields, KEY_SIGNER, KEY_TYPE, SELF, +}; pub struct SignedPodVerifyGadget { pub params: Params, @@ -73,9 +77,39 @@ pub struct SignedPodVerifyTarget { } impl SignedPodVerifyTarget { - pub fn pub_statements(&self) -> Vec { - // TODO: Here we need to use the self.id in the ValueOf statements - todo!() + pub fn pub_statements( + &self, + builder: &mut CircuitBuilder, + self_id: bool, + ) -> Vec { + let mut statements = Vec::new(); + let predicate: [Target; Params::predicate_size()] = builder + .constants(&Predicate::Native(NativePredicate::ValueOf).to_fields(&self.params)) + .try_into() + .expect("size predicate_size"); + let pod_id = if self_id { + builder.constant_value(SELF.0.into()) + } else { + ValueTarget { + elements: self.id.elements, + } + }; + for mt_proof in &self.mt_proofs { + let args = [ + StatementArgTarget::anchored_key(builder, &pod_id, &mt_proof.key), + StatementArgTarget::literal(builder, &mt_proof.value), + ] + .into_iter() + .chain(iter::repeat_with(|| StatementArgTarget::none(builder))) + .take(self.params.max_statement_args) + .collect(); + let statement = StatementTarget { + predicate: predicate.clone(), + args, + }; + statements.push(statement); + } + statements } pub fn set_targets(&self, pw: &mut PartialWitness, pod: &SignedPod) -> Result<()> { diff --git a/src/backends/plonky2/mainpod.rs b/src/backends/plonky2/mainpod.rs new file mode 100644 index 0000000..dfaf89b --- /dev/null +++ b/src/backends/plonky2/mainpod.rs @@ -0,0 +1,278 @@ +use crate::backends::plonky2::basetypes::C; +use anyhow::{anyhow, Result}; +use base64::prelude::*; +use itertools::Itertools; +use log::error; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::iop::witness::PartialWitness; +use plonky2::plonk::config::Hasher; +use plonky2::plonk::proof::ProofWithPublicInputs; +use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig}; +use serde::{Deserialize, Serialize}; +use std::any::Any; +use std::fmt; + +use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE}; +use crate::backends::plonky2::circuits::mainpod::{MainPodVerifyCircuit, MainPodVerifyInput}; +use crate::backends::plonky2::signedpod::SignedPod; +// TODO: Move the shared components between MockMainPod and MainPod to a common place. +use crate::backends::plonky2::mock::mainpod::{hash_statements, MockMainPod, Operation, Statement}; +use crate::middleware::{ + self, hash_str, AnchoredKey, MainPodInputs, NativeOperation, NativePredicate, NonePod, + OperationType, Params, Pod, PodId, PodProver, Predicate, StatementArg, ToFields, KEY_TYPE, + SELF, +}; + +pub struct Prover {} + +impl PodProver for Prover { + // TODO: Be consistent on where we apply the padding, here, or in the set_targets? + fn prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result> { + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let main_pod = MainPodVerifyCircuit { + params: params.clone(), + } + .eval(&mut builder)?; + + let mut pw = PartialWitness::::new(); + let signed_pods_input: Vec = inputs + .signed_pods + .iter() + .map(|p| { + let p: Box = (*p).clone(); + *p.into_any() + .downcast::() + .expect("type SignedPod") + }) + .collect_vec(); + + // TODO: Move these methods from the mock main pod to a common place + let statements = MockMainPod::layout_statements(params, &inputs); + let operations = MockMainPod::process_private_statements_operations( + params, + &statements, + &[], // TODO: fill in the merkle proofs for Contains/NotContains ops + inputs.operations, + )?; + let operations = + MockMainPod::process_public_statements_operations(params, &statements, operations)?; + + let public_statements = + statements[statements.len() - params.max_public_statements..].to_vec(); + // get the id out of the public statements + let id: PodId = PodId(hash_statements(&public_statements, params)); + + let input = MainPodVerifyInput { + signed_pods: signed_pods_input, + statements: statements[statements.len() - params.max_statements..].to_vec(), + operations, + }; + main_pod.set_targets(&mut pw, &input)?; + + // generate & verify proof + let data = builder.build::(); + let proof = data.prove(pw)?; + + Ok(Box::new(MainPod { + params: params.clone(), + id, + public_statements, + proof, + })) + } +} + +#[derive(Clone, Debug)] +pub struct MainPod { + params: Params, + id: PodId, + public_statements: Vec, + proof: ProofWithPublicInputs, +} + +impl Pod for MainPod { + fn verify(&self) -> Result<()> { + // 2. get the id out of the public statements + let id: PodId = PodId(hash_statements(&self.public_statements, &self.params)); + if id != self.id { + return Err(anyhow!( + "id does not match, expected {}, computed {}", + self.id, + id + )); + } + + // 1, 3, 4, 5 verification via the zkSNARK proof + // TODO: cache these artefacts + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let _main_pod = MainPodVerifyCircuit { + params: self.params.clone(), + } + .eval(&mut builder)?; + + let data = builder.build::(); + data.verify(self.proof.clone()) + .map_err(|e| anyhow!("MainPod proof verification failure: {:?}", e)) + } + + fn id(&self) -> PodId { + self.id + } + + fn pub_statements(&self) -> Vec { + // return the public statements, where when origin=SELF is replaced by origin=self.id() + self.public_statements + .iter() + .cloned() + .map(|statement| { + Statement( + statement.0.clone(), + statement + .1 + .iter() + .map(|sa| match &sa { + StatementArg::Key(AnchoredKey(pod_id, h)) if *pod_id == SELF => { + StatementArg::Key(AnchoredKey(self.id(), *h)) + } + _ => *sa, + }) + .collect(), + ) + .try_into() + .unwrap() + }) + .collect() + } + + fn into_any(self: Box) -> Box { + self + } + + fn serialized_proof(&self) -> String { + todo!() + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::backends::plonky2::mock::mainpod::{MockProver, OperationAux}; + use crate::backends::plonky2::primitives::signature::SecretKey; + use crate::backends::plonky2::signedpod::Signer; + use crate::examples::zu_kyc_sign_pod_builders; + use crate::frontend; + use crate::middleware; + use crate::op; + + // TODO: Use the method from examples once everything works + pub fn zu_kyc_pod_builder( + params: &Params, + gov_id: &frontend::SignedPod, + pay_stub: &frontend::SignedPod, + sanction_list: &frontend::SignedPod, + ) -> Result { + let now_minus_18y: i64 = 1169909388; + let now_minus_1y: i64 = 1706367566; + + let mut kyc = frontend::MainPodBuilder::new(params); + kyc.add_signed_pod(gov_id); + kyc.add_signed_pod(pay_stub); + kyc.add_signed_pod(sanction_list); + // NOTE: Unimplemented in the circuit + // kyc.pub_op(op!( + // not_contains, + // (sanction_list, "sanctionList"), + // (gov_id, "idNumber") + // ))?; + kyc.pub_op(op!(lt, (gov_id, "dateOfBirth"), now_minus_18y))?; + kyc.pub_op(op!( + eq, + (gov_id, "socialSecurityNumber"), + (pay_stub, "socialSecurityNumber") + ))?; + let start_date_st = kyc.pub_op(frontend::Operation( + frontend::OperationType::Native(frontend::NativeOperation::NewEntry), + vec![frontend::OperationArg::Entry( + "startDate".to_string(), + now_minus_1y.into(), + )], + middleware::OperationAux::None, + ))?; + kyc.pub_op(op!(eq, (pay_stub, "startDate"), start_date_st))?; + kyc.pub_op(op!(eq, (pay_stub, "startDate"), now_minus_1y))?; + + Ok(kyc) + } + + #[test] + fn test_main_zu_kyc() -> Result<()> { + 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: 1, + ..Default::default() + }; + + let sanctions_values = vec!["A343434340".into()]; + let sanction_set = frontend::Value::Set(frontend::containers::Set::new(sanctions_values)?); + let (gov_id_builder, pay_stub_builder, sanction_list_builder) = + zu_kyc_sign_pod_builders(¶ms, &sanction_set); + let mut signer = Signer(SecretKey(Value::from(1))); + let gov_id_pod = gov_id_builder.sign(&mut signer)?; + let mut signer = Signer(SecretKey(Value::from(2))); + let pay_stub_pod = pay_stub_builder.sign(&mut signer)?; + let mut signer = Signer(SecretKey(Value::from(3))); + let sanction_list_pod = sanction_list_builder.sign(&mut signer)?; + let kyc_builder = + zu_kyc_pod_builder(¶ms, &gov_id_pod, &pay_stub_pod, &sanction_list_pod)?; + + let mut prover = Prover {}; + let kyc_pod = kyc_builder.prove(&mut prover, ¶ms)?; + let pod = kyc_pod.pod.into_any().downcast::().unwrap(); + + pod.verify() + } + + #[test] + fn test_mini_0() { + let params = middleware::Params { + max_input_signed_pods: 1, + max_input_main_pods: 1, + max_signed_pod_values: 6, + max_statements: 8, + max_public_statements: 4, + ..Default::default() + }; + + let mut gov_id_builder = frontend::SignedPodBuilder::new(¶ms); + gov_id_builder.insert("idNumber", "4242424242"); + gov_id_builder.insert("dateOfBirth", 1169909384); + gov_id_builder.insert("socialSecurityNumber", "G2121210"); + let mut signer = Signer(SecretKey(Value::from(42))); + let gov_id = gov_id_builder.sign(&mut signer).unwrap(); + let now_minus_18y: i64 = 1169909388; + let mut kyc_builder = frontend::MainPodBuilder::new(¶ms); + kyc_builder.add_signed_pod(&gov_id); + kyc_builder + .pub_op(op!(lt, (&gov_id, "dateOfBirth"), now_minus_18y)) + .unwrap(); + + println!("{}", kyc_builder); + println!(); + + // Mock + let mut prover = MockProver {}; + let kyc_pod = kyc_builder.prove(&mut prover, ¶ms).unwrap(); + let pod = kyc_pod.pod.into_any().downcast::().unwrap(); + pod.verify().unwrap(); + println!("{:#}", pod); + + // Real + let mut prover = Prover {}; + let kyc_pod = kyc_builder.prove(&mut prover, ¶ms).unwrap(); + let pod = kyc_pod.pod.into_any().downcast::().unwrap(); + pod.verify().unwrap() + } +} diff --git a/src/backends/plonky2/mock/mainpod/mod.rs b/src/backends/plonky2/mock/mainpod/mod.rs index d3cee82..81dc15f 100644 --- a/src/backends/plonky2/mock/mainpod/mod.rs +++ b/src/backends/plonky2/mock/mainpod/mod.rs @@ -12,8 +12,8 @@ use crate::{ backends::plonky2::primitives::merkletree::MerkleProof, middleware::{ self, hash_str, AnchoredKey, Hash, MainPodInputs, NativeOperation, NativePredicate, - NonePod, OperationType, Params, Pod, PodId, PodProver, Predicate, StatementArg, ToFields, - KEY_TYPE, SELF, + NonePod, OperationType, Params, Pod, PodId, PodProver, PodType, Predicate, StatementArg, + ToFields, KEY_TYPE, SELF, }, }; @@ -22,8 +22,6 @@ mod statement; pub use operation::*; pub use statement::*; -pub const VALUE_TYPE: &str = "MockMainPOD"; - pub struct MockProver {} impl PodProver for MockProver { @@ -67,7 +65,7 @@ impl fmt::Display for MockMainPod { } if (i >= offset_input_main_pods) && (i < offset_input_statements) - && (i % self.params.max_public_statements == 0) + && ((i - offset_input_main_pods) % self.params.max_public_statements == 0) { writeln!( f, @@ -116,7 +114,7 @@ fn fmt_statement_index( Ok(()) } -fn fill_pad(v: &mut Vec, pad_value: T, len: usize) { +pub fn fill_pad(v: &mut Vec, pad_value: T, len: usize) { if v.len() > len { panic!("length exceeded"); } @@ -153,7 +151,7 @@ impl MockMainPod { /// Returns the statements from the given MainPodInputs, padding to the /// respective max lengths defined at the given Params. - fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec { + pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec { let mut statements = Vec::new(); // Input signed pods region @@ -209,7 +207,7 @@ impl MockMainPod { assert!(inputs.public_statements.len() < params.max_public_statements); let mut type_st = middleware::Statement::ValueOf( AnchoredKey(SELF, hash_str(KEY_TYPE)), - middleware::Value(hash_str(VALUE_TYPE).0), + middleware::Value::from(PodType::MockMain), ) .into(); Self::pad_statement(params, &mut type_st); @@ -267,7 +265,7 @@ impl MockMainPod { } } - fn process_private_statements_operations( + pub(crate) fn process_private_statements_operations( params: &Params, statements: &[Statement], merkle_proofs: &[MerkleProof], @@ -298,7 +296,7 @@ impl MockMainPod { // previous statements, so we fill in the operations accordingly. /// This method assumes that the given `statements` array has been padded to /// `params.max_statements`. - fn process_public_statements_operations( + pub(crate) fn process_public_statements_operations( params: &Params, statements: &[Statement], mut operations: Vec, @@ -526,6 +524,7 @@ impl Pod for MockMainPod { } fn pub_statements(&self) -> Vec { // 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 .iter() .skip(self.offset_public_statements()) diff --git a/src/backends/plonky2/mock/signedpod.rs b/src/backends/plonky2/mock/signedpod.rs index e2fc89e..16cf8ed 100644 --- a/src/backends/plonky2/mock/signedpod.rs +++ b/src/backends/plonky2/mock/signedpod.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use itertools::Itertools; use std::any::Any; use std::collections::HashMap; @@ -104,8 +105,15 @@ impl Pod for MockSignedPod { fn pub_statements(&self) -> Vec { let id = self.id(); - self.dict - .iter() + // By convention we put the KEY_TYPE first and KEY_SIGNER second + let mut kvs: HashMap<_, _> = self.dict.iter().collect(); + let key_type = Value::from(hash_str(KEY_TYPE)); + let value_type = kvs.remove(&key_type).expect("KEY_TYPE"); + let key_signer = Value::from(hash_str(KEY_SIGNER)); + let value_signer = kvs.remove(&key_signer).expect("KEY_SIGNER"); + [(&key_type, value_type), (&key_signer, value_signer)] + .into_iter() + .chain(kvs.into_iter().sorted_by_key(|kv| kv.0)) .map(|(k, v)| Statement::ValueOf(AnchoredKey(id, Hash(k.0)), *v)) .collect() } diff --git a/src/backends/plonky2/mod.rs b/src/backends/plonky2/mod.rs index 11a6e16..abc940e 100644 --- a/src/backends/plonky2/mod.rs +++ b/src/backends/plonky2/mod.rs @@ -1,5 +1,6 @@ pub mod basetypes; pub mod circuits; +pub mod mainpod; pub mod mock; pub mod primitives; pub mod signedpod; diff --git a/src/backends/plonky2/signedpod.rs b/src/backends/plonky2/signedpod.rs index a2f2bb5..dc92a6e 100644 --- a/src/backends/plonky2/signedpod.rs +++ b/src/backends/plonky2/signedpod.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use itertools::Itertools; use std::any::Any; use std::collections::HashMap; @@ -83,8 +84,15 @@ impl Pod for SignedPod { fn pub_statements(&self) -> Vec { let id = self.id(); - self.dict - .iter() + // By convention we put the KEY_TYPE first and KEY_SIGNER second + let mut kvs: HashMap<_, _> = self.dict.iter().collect(); + let key_type = Value::from(hash_str(KEY_TYPE)); + let value_type = kvs.remove(&key_type).expect("KEY_TYPE"); + let key_signer = Value::from(hash_str(KEY_SIGNER)); + let value_signer = kvs.remove(&key_signer).expect("KEY_SIGNER"); + [(&key_type, value_type), (&key_signer, value_signer)] + .into_iter() + .chain(kvs.into_iter().sorted_by_key(|kv| kv.0)) .map(|(k, v)| Statement::ValueOf(AnchoredKey(id, Hash(k.0)), *v)) .collect() } @@ -119,6 +127,7 @@ pub mod tests { pod.insert("dateOfBirth", 1169909384); pod.insert("socialSecurityNumber", "G2121210"); + // TODO: Use a deterministic secret key to get deterministic tests let sk = SecretKey::new(); let mut signer = Signer(sk); let pod = pod.sign(&mut signer).unwrap(); diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index f900113..e8eb2d8 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -9,7 +9,9 @@ use super::{ AnchoredKey, CustomPredicateRef, Params, Predicate, ToFields, Value, F, HASH_SIZE, VALUE_SIZE, }; +// hash(KEY_SIGNER) = [2145458785152392366, 15113074911296146791, 15323228995597834291, 11804480340100333725] pub const KEY_SIGNER: &str = "_signer"; +// hash(KEY_TYPE) = [17948789436443445142, 12513915140657440811, 15878361618879468769, 938231894693848619] pub const KEY_TYPE: &str = "_type"; pub const STATEMENT_ARG_F_LEN: usize = 8; pub const OPERATION_ARG_F_LEN: usize = 1; @@ -276,3 +278,17 @@ impl ToFields for StatementArg { f } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::middleware::hash_str; + + #[test] + fn test_print_special_keys() { + let key = hash_str(KEY_SIGNER); + println!("hash(KEY_SIGNER) = {:?}", key); + let key = hash_str(KEY_TYPE); + println!("hash(KEY_TYPE) = {:?}", key); + } +}