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 <root@ahmadafuni.com>
This commit is contained in:
parent
4a94b34792
commit
ce26a316a1
10 changed files with 530 additions and 104 deletions
|
|
@ -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<F>,
|
||||
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<F, D>) -> Self {
|
||||
let empty = builder.constant_value(EMPTY_VALUE);
|
||||
Self::new(empty.clone(), empty)
|
||||
}
|
||||
|
||||
pub fn literal(builder: &mut CircuitBuilder<F, D>, value: &ValueTarget) -> Self {
|
||||
let empty = builder.constant_value(EMPTY_VALUE);
|
||||
Self::new(value.clone(), empty)
|
||||
}
|
||||
|
||||
pub fn anchored_key(
|
||||
_builder: &mut CircuitBuilder<F, D>,
|
||||
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<StatementArgTarget>,
|
||||
}
|
||||
|
||||
impl StatementTarget {
|
||||
|
|
@ -62,18 +106,16 @@ impl StatementTarget {
|
|||
builder: &mut CircuitBuilder<F, D>,
|
||||
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<Target> {
|
||||
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<F: RichField + Extendable<D>, 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<F, D> for CircuitBuilder<F, D> {
|
|||
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<F, D> for CircuitBuilder<F, D> {
|
|||
})
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
|
@ -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::<PoseidonHash>(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<SignedPodVerifyTarget>,
|
||||
|
|
@ -362,25 +375,33 @@ struct MainPodVerifyTarget {
|
|||
op_verifications: Vec<OperationVerifyTarget>,
|
||||
}
|
||||
|
||||
struct MainPodVerifyInput {
|
||||
signed_pods: Vec<SignedPod>,
|
||||
pub struct MainPodVerifyInput {
|
||||
pub signed_pods: Vec<SignedPod>,
|
||||
pub statements: Vec<mainpod::Statement>,
|
||||
pub operations: Vec<mainpod::Operation>,
|
||||
}
|
||||
|
||||
impl MainPodVerifyTarget {
|
||||
fn set_targets(&self, pw: &mut PartialWitness<F>, input: &MainPodVerifyInput) -> Result<()> {
|
||||
pub fn set_targets(
|
||||
&self,
|
||||
pw: &mut PartialWitness<F>,
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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<StatementTarget> {
|
||||
// TODO: Here we need to use the self.id in the ValueOf statements
|
||||
todo!()
|
||||
pub fn pub_statements(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
self_id: bool,
|
||||
) -> Vec<StatementTarget> {
|
||||
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<F>, pod: &SignedPod) -> Result<()> {
|
||||
|
|
|
|||
278
src/backends/plonky2/mainpod.rs
Normal file
278
src/backends/plonky2/mainpod.rs
Normal file
|
|
@ -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<Box<dyn Pod>> {
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
let main_pod = MainPodVerifyCircuit {
|
||||
params: params.clone(),
|
||||
}
|
||||
.eval(&mut builder)?;
|
||||
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
let signed_pods_input: Vec<SignedPod> = inputs
|
||||
.signed_pods
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let p: Box<dyn middleware::Pod> = (*p).clone();
|
||||
*p.into_any()
|
||||
.downcast::<SignedPod>()
|
||||
.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::<C>();
|
||||
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<Statement>,
|
||||
proof: ProofWithPublicInputs<F, C, 2>,
|
||||
}
|
||||
|
||||
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::<F, D>::new(config);
|
||||
let _main_pod = MainPodVerifyCircuit {
|
||||
params: self.params.clone(),
|
||||
}
|
||||
.eval(&mut builder)?;
|
||||
|
||||
let data = builder.build::<C>();
|
||||
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<middleware::Statement> {
|
||||
// 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<Self>) -> Box<dyn Any> {
|
||||
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<frontend::MainPodBuilder> {
|
||||
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::<MainPod>().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::<MockMainPod>().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::<MainPod>().unwrap();
|
||||
pod.verify().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T: Clone>(v: &mut Vec<T>, pad_value: T, len: usize) {
|
||||
pub fn fill_pad<T: Clone>(v: &mut Vec<T>, 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<Statement> {
|
||||
pub(crate) fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<Statement> {
|
||||
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<Operation>,
|
||||
|
|
@ -526,6 +524,7 @@ impl Pod for MockMainPod {
|
|||
}
|
||||
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
|
||||
.iter()
|
||||
.skip(self.offset_public_statements())
|
||||
|
|
|
|||
|
|
@ -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<Statement> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod basetypes;
|
||||
pub mod circuits;
|
||||
pub mod mainpod;
|
||||
pub mod mock;
|
||||
pub mod primitives;
|
||||
pub mod signedpod;
|
||||
|
|
|
|||
|
|
@ -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<Statement> {
|
||||
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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue