MainPod circuit skeleton (#151)
* feat: add MainPod circuit skeleton * feat: use ValueTarget in mt, verify SignedPod type * wip * feat: match structure with mock * apply feedback from @arnaucube * add 2 operations * fix test compilation * Add missing todo
This commit is contained in:
parent
2a2628ccbf
commit
22db6ce4c6
6 changed files with 513 additions and 51 deletions
110
src/backends/plonky2/common.rs
Normal file
110
src/backends/plonky2/common.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
//! Common functionality to build Pod circuits with plonky2
|
||||||
|
|
||||||
|
use crate::middleware::STATEMENT_ARG_F_LEN;
|
||||||
|
use crate::middleware::{Params, Value, HASH_SIZE, VALUE_SIZE};
|
||||||
|
use plonky2::field::extension::Extendable;
|
||||||
|
use plonky2::field::types::PrimeField64;
|
||||||
|
use plonky2::hash::hash_types::RichField;
|
||||||
|
use plonky2::iop::target::{BoolTarget, Target};
|
||||||
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ValueTarget {
|
||||||
|
pub elements: [Target; VALUE_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct StatementTarget {
|
||||||
|
pub code: [Target; HASH_SIZE + 2],
|
||||||
|
pub args: Vec<[Target; STATEMENT_ARG_F_LEN]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatementTarget {
|
||||||
|
pub fn to_flattened(&self) -> Vec<Target> {
|
||||||
|
self.code
|
||||||
|
.iter()
|
||||||
|
.chain(self.args.iter().flatten())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement Operation::to_field to determine the size of each element
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OperationTarget {
|
||||||
|
pub code: [Target; 6], // TODO: Figure out the length
|
||||||
|
pub args: Vec<[Target; STATEMENT_ARG_F_LEN]>, // TODO: Figure out the length
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CircuitBuilderPod<F: RichField + Extendable<D>, const D: usize> {
|
||||||
|
fn connect_values(&mut self, x: ValueTarget, y: ValueTarget);
|
||||||
|
fn connect_slice(&mut self, xs: &[Target], ys: &[Target]);
|
||||||
|
fn add_virtual_value(&mut self) -> ValueTarget;
|
||||||
|
fn add_virtual_statement(&mut self, params: &Params) -> StatementTarget;
|
||||||
|
fn add_virtual_operation(&mut self, params: &Params) -> OperationTarget;
|
||||||
|
fn select_value(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget) -> ValueTarget;
|
||||||
|
fn select_bool(&mut self, b: BoolTarget, x: BoolTarget, y: BoolTarget) -> BoolTarget;
|
||||||
|
fn constant_value(&mut self, v: Value) -> ValueTarget;
|
||||||
|
fn is_equal_slice(&mut self, xs: &[Target], ys: &[Target]) -> BoolTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilderPod<F, D>
|
||||||
|
for CircuitBuilder<F, D>
|
||||||
|
{
|
||||||
|
fn connect_slice(&mut self, xs: &[Target], ys: &[Target]) {
|
||||||
|
assert_eq!(xs.len(), ys.len());
|
||||||
|
for (x, y) in xs.iter().zip(ys.iter()) {
|
||||||
|
self.connect(*x, *y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_values(&mut self, x: ValueTarget, y: ValueTarget) {
|
||||||
|
self.connect_slice(&x.elements, &y.elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_virtual_value(&mut self) -> ValueTarget {
|
||||||
|
ValueTarget {
|
||||||
|
elements: self.add_virtual_target_arr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_virtual_statement(&mut self, params: &Params) -> StatementTarget {
|
||||||
|
StatementTarget {
|
||||||
|
code: self.add_virtual_target_arr::<6>(),
|
||||||
|
args: (0..params.max_statement_args)
|
||||||
|
.map(|_| self.add_virtual_target_arr::<STATEMENT_ARG_F_LEN>())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_virtual_operation(&mut self, params: &Params) -> OperationTarget {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_value(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget) -> ValueTarget {
|
||||||
|
ValueTarget {
|
||||||
|
elements: std::array::from_fn(|i| self.select(b, x.elements[i], y.elements[i])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_bool(&mut self, b: BoolTarget, x: BoolTarget, y: BoolTarget) -> BoolTarget {
|
||||||
|
BoolTarget::new_unsafe(self.select(b, x.target, y.target))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constant_value(&mut self, v: Value) -> ValueTarget {
|
||||||
|
ValueTarget {
|
||||||
|
elements: std::array::from_fn(|i| {
|
||||||
|
self.constant(F::from_noncanonical_u64(v.0[i].to_noncanonical_u64()))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equal_slice(&mut self, xs: &[Target], ys: &[Target]) -> BoolTarget {
|
||||||
|
assert_eq!(xs.len(), ys.len());
|
||||||
|
let init = self._true();
|
||||||
|
xs.iter().zip(ys.iter()).fold(init, |ok, (x, y)| {
|
||||||
|
let is_eq = self.is_equal(*x, *y);
|
||||||
|
self.and(ok, is_eq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
340
src/backends/plonky2/main.rs
Normal file
340
src/backends/plonky2/main.rs
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE};
|
||||||
|
use crate::backends::plonky2::common::{
|
||||||
|
CircuitBuilderPod, OperationTarget, StatementTarget, ValueTarget,
|
||||||
|
};
|
||||||
|
use crate::backends::plonky2::primitives::merkletree::MerkleProofExistenceCircuit;
|
||||||
|
use crate::backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree};
|
||||||
|
use crate::middleware::{
|
||||||
|
hash_str, AnchoredKey, NativeOperation, NativePredicate, Operation, Params, PodType, Predicate,
|
||||||
|
Statement, StatementArg, ToFields, KEY_TYPE, SELF, STATEMENT_ARG_F_LEN,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use plonky2::{
|
||||||
|
field::types::Field,
|
||||||
|
hash::{
|
||||||
|
hash_types::{HashOut, HashOutTarget},
|
||||||
|
poseidon::PoseidonHash,
|
||||||
|
},
|
||||||
|
iop::{
|
||||||
|
target::{BoolTarget, Target},
|
||||||
|
witness::{PartialWitness, WitnessWrite},
|
||||||
|
},
|
||||||
|
plonk::circuit_builder::CircuitBuilder,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// MerkleTree Max Depth
|
||||||
|
const MD: usize = 32;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SignedPod verification
|
||||||
|
//
|
||||||
|
|
||||||
|
struct SignedPodVerifyGate {
|
||||||
|
params: Params,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedPodVerifyGate {
|
||||||
|
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<SignedPodVerifyTarget> {
|
||||||
|
// 2. Verify id
|
||||||
|
let id = builder.add_virtual_hash();
|
||||||
|
let mut mt_proofs = Vec::new();
|
||||||
|
for _ in 0..self.params.max_signed_pod_values {
|
||||||
|
let mt_proof = MerkleProofExistenceCircuit::<MD>::add_targets(builder)?;
|
||||||
|
builder.connect_hashes(id, mt_proof.root);
|
||||||
|
mt_proofs.push(mt_proof);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Verify type
|
||||||
|
let type_mt_proof = &mt_proofs[0];
|
||||||
|
let key_type = builder.constant_value(hash_str(KEY_TYPE).into());
|
||||||
|
builder.connect_values(type_mt_proof.key, key_type);
|
||||||
|
let value_type = builder.constant_value(Value::from(PodType::MockSigned));
|
||||||
|
builder.connect_values(type_mt_proof.value, value_type);
|
||||||
|
|
||||||
|
// 3. TODO: Verify signature
|
||||||
|
|
||||||
|
Ok(SignedPodVerifyTarget {
|
||||||
|
params: self.params.clone(),
|
||||||
|
id,
|
||||||
|
mt_proofs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SignedPodVerifyTarget {
|
||||||
|
params: Params,
|
||||||
|
id: HashOutTarget,
|
||||||
|
// The KEY_TYPE entry must be the first one
|
||||||
|
// The KEY_SIGNER entry must be the second one
|
||||||
|
mt_proofs: Vec<MerkleProofExistenceCircuit<MD>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SignedPodVerifyInput {
|
||||||
|
kvs: HashMap<Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedPodVerifyTarget {
|
||||||
|
fn kvs(&self) -> Vec<(ValueTarget, ValueTarget)> {
|
||||||
|
let mut kvs = Vec::new();
|
||||||
|
for mt_proof in &self.mt_proofs {
|
||||||
|
kvs.push((mt_proof.key, mt_proof.value));
|
||||||
|
}
|
||||||
|
// TODO: when the slot is unused, do we force the kv to be (EMPTY, EMPTY), and then from
|
||||||
|
// it get a ValueOf((id, EMPTY), EMPTY)? Or should we keep some boolean flags for unused
|
||||||
|
// slots and translate them to Statement::None instead?
|
||||||
|
kvs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pub_statements(&self) -> Vec<StatementTarget> {
|
||||||
|
// TODO: Here we need to use the self.id in the ValueOf statements
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_targets(&self, pw: &mut PartialWitness<F>, input: &SignedPodVerifyInput) -> Result<()> {
|
||||||
|
assert!(input.kvs.len() <= self.params.max_signed_pod_values);
|
||||||
|
let tree = MerkleTree::new(MD, &input.kvs)?;
|
||||||
|
for (i, (k, v)) in input.kvs.iter().sorted_by_key(|kv| kv.0).enumerate() {
|
||||||
|
let (_, proof) = tree.prove(&k)?;
|
||||||
|
self.mt_proofs[i].set_targets(pw, tree.root(), proof, *k, *v)?;
|
||||||
|
}
|
||||||
|
// Padding
|
||||||
|
for i in input.kvs.len()..self.params.max_signed_pod_values {
|
||||||
|
// TODO: We need to disable the proofs for the unused slots. We could add a flag
|
||||||
|
// "enable" to the MerkleTree proof circuit that skips the verification when false.
|
||||||
|
// self.mt_proofs[i].set_targets(pw, false, EMPTY_HASH, proof, *k, *v)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// MainPod verification
|
||||||
|
//
|
||||||
|
|
||||||
|
struct OperationVerifyGate {
|
||||||
|
params: Params,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperationVerifyGate {
|
||||||
|
fn eval(
|
||||||
|
&self,
|
||||||
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
|
st: &StatementTarget,
|
||||||
|
op: &OperationTarget,
|
||||||
|
prev_statements: &[StatementTarget],
|
||||||
|
) -> Result<OperationVerifyTarget> {
|
||||||
|
// Verify that the operation `op` correctly generates the statement `st`. The operation
|
||||||
|
// can reference any of the `prev_statements`.
|
||||||
|
// The verification may require aux data which needs to be stored in the
|
||||||
|
// `OperationVerifyTarget` so that we can set during witness generation.
|
||||||
|
|
||||||
|
// TODO: Figure out the right encoding of op.code
|
||||||
|
let op_none = builder.constant(F::from_canonical_u64(NativeOperation::None as u64));
|
||||||
|
let is_none = builder.is_equal(op.code[0], op_none);
|
||||||
|
let op_new_entry =
|
||||||
|
builder.constant(F::from_canonical_u64(NativeOperation::NewEntry as u64));
|
||||||
|
let is_new_entry = builder.is_equal(op.code[0], op_new_entry);
|
||||||
|
let op_copy_statement =
|
||||||
|
builder.constant(F::from_canonical_u64(NativeOperation::CopyStatement as u64));
|
||||||
|
let is_copy_statement = builder.is_equal(op.code[0], op_copy_statement);
|
||||||
|
let op_eq_from_entries = builder.constant(F::from_canonical_u64(
|
||||||
|
NativeOperation::EqualFromEntries as u64,
|
||||||
|
));
|
||||||
|
let is_eq_from_entries = builder.is_equal(op.code[0], op_eq_from_entries);
|
||||||
|
let op_gt_from_entries =
|
||||||
|
builder.constant(F::from_canonical_u64(NativeOperation::GtFromEntries as u64));
|
||||||
|
let is_gt_from_entries = builder.is_equal(op.code[0], op_gt_from_entries);
|
||||||
|
let op_lt_from_entries =
|
||||||
|
builder.constant(F::from_canonical_u64(NativeOperation::LtFromEntries as u64));
|
||||||
|
let is_lt_from_entries = builder.is_equal(op.code[0], op_lt_from_entries);
|
||||||
|
let op_contains_from_entries = builder.constant(F::from_canonical_u64(
|
||||||
|
NativeOperation::ContainsFromEntries as u64,
|
||||||
|
));
|
||||||
|
let is_contains_from_entries = builder.is_equal(op.code[0], op_contains_from_entries);
|
||||||
|
|
||||||
|
let ok = builder._true();
|
||||||
|
let none_ok = self.eval_none(builder, st, op);
|
||||||
|
let ok = builder.select_bool(is_none, none_ok, ok);
|
||||||
|
let new_entry_ok = self.eval_new_entry(builder, st, op);
|
||||||
|
let ok = builder.select_bool(is_new_entry, new_entry_ok, ok);
|
||||||
|
|
||||||
|
let _true = builder._true();
|
||||||
|
builder.connect(ok.target, _true.target);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_none(
|
||||||
|
&self,
|
||||||
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
|
st: &StatementTarget,
|
||||||
|
_op: &OperationTarget,
|
||||||
|
) -> BoolTarget {
|
||||||
|
let expected_statement_flattened =
|
||||||
|
builder.constants(&Statement::None.to_fields(&self.params));
|
||||||
|
builder.is_equal_slice(&st.to_flattened(), &expected_statement_flattened)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_new_entry(
|
||||||
|
&self,
|
||||||
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
|
st: &StatementTarget,
|
||||||
|
_op: &OperationTarget,
|
||||||
|
) -> BoolTarget {
|
||||||
|
let value_of_st = &Statement::ValueOf(AnchoredKey(SELF, EMPTY_HASH), EMPTY_VALUE);
|
||||||
|
let expected_code =
|
||||||
|
builder.constants(&Predicate::Native(NativePredicate::ValueOf).to_fields(&self.params));
|
||||||
|
let code_ok = builder.is_equal_slice(&st.code, &expected_code);
|
||||||
|
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);
|
||||||
|
builder.and(code_ok, arg_prefix_ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OperationVerifyTarget {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OperationVerifyInputs {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperationVerifyTarget {
|
||||||
|
fn set_targets(&self, pw: &mut PartialWitness<F>, input: &OperationVerifyInputs) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainPodVerifyGate {
|
||||||
|
params: Params,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MainPodVerifyGate {
|
||||||
|
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MainPodVerifyTarget> {
|
||||||
|
let params = &self.params;
|
||||||
|
// 1. Verify all input signed pods
|
||||||
|
let mut signed_pods = Vec::new();
|
||||||
|
for _ in 0..params.max_input_signed_pods {
|
||||||
|
let signed_pod = SignedPodVerifyGate {
|
||||||
|
params: params.clone(),
|
||||||
|
}
|
||||||
|
.eval(builder)?;
|
||||||
|
signed_pods.push(signed_pod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the input (private and public) statements and corresponding operations
|
||||||
|
let mut operations = Vec::new();
|
||||||
|
let input_statements_offset = statements.len();
|
||||||
|
for _ in 0..params.max_statements {
|
||||||
|
statements.push(builder.add_virtual_statement(params));
|
||||||
|
operations.push(builder.add_virtual_operation(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_statements = &statements[input_statements_offset..];
|
||||||
|
let pub_statements = &input_statements[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.code.iter().chain(s.args.iter().flatten()))
|
||||||
|
.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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// compute it every time.
|
||||||
|
let key_type = hash_str(KEY_TYPE);
|
||||||
|
let expected_type_statement_flattened = builder.constants(
|
||||||
|
&Statement::ValueOf(AnchoredKey(SELF, key_type), Value::from(PodType::MockMain))
|
||||||
|
.to_fields(params),
|
||||||
|
);
|
||||||
|
builder.connect_slice(
|
||||||
|
&type_statement.to_flattened(),
|
||||||
|
&expected_type_statement_flattened,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 op_verification = OperationVerifyGate {
|
||||||
|
params: params.clone(),
|
||||||
|
}
|
||||||
|
.eval(builder, st, op, prev_statements)?;
|
||||||
|
op_verifications.push(op_verification);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MainPodVerifyTarget {
|
||||||
|
params: params.clone(),
|
||||||
|
id,
|
||||||
|
signed_pods,
|
||||||
|
statements: input_statements.to_vec(),
|
||||||
|
operations,
|
||||||
|
op_verifications,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainPodVerifyTarget {
|
||||||
|
params: Params,
|
||||||
|
id: HashOutTarget,
|
||||||
|
signed_pods: Vec<SignedPodVerifyTarget>,
|
||||||
|
// The KEY_TYPE statement must be the first public one
|
||||||
|
statements: Vec<StatementTarget>,
|
||||||
|
operations: Vec<OperationTarget>,
|
||||||
|
op_verifications: Vec<OperationVerifyTarget>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainPodVerifyInput {
|
||||||
|
signed_pods: Vec<SignedPodVerifyInput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MainPodVerifyTarget {
|
||||||
|
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
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
// TODO: set_targets for:
|
||||||
|
// - statements
|
||||||
|
// - operations
|
||||||
|
// - op_verifications
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainPodVerifyCircuit {
|
||||||
|
params: Params,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MainPodVerifyCircuit {
|
||||||
|
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MainPodVerifyTarget> {
|
||||||
|
let main_pod = MainPodVerifyGate {
|
||||||
|
params: self.params.clone(),
|
||||||
|
}
|
||||||
|
.eval(builder)?;
|
||||||
|
builder.register_public_inputs(&main_pod.id.elements);
|
||||||
|
Ok(main_pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -371,10 +371,12 @@ pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware
|
||||||
|
|
||||||
impl Pod for MockMainPod {
|
impl Pod for MockMainPod {
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
|
// 1. TODO: Verify input pods
|
||||||
|
|
||||||
let input_statement_offset = self.offset_input_statements();
|
let input_statement_offset = self.offset_input_statements();
|
||||||
// get the input_statements from the self.statements
|
// get the input_statements from the self.statements
|
||||||
let input_statements = &self.statements[input_statement_offset..];
|
let input_statements = &self.statements[input_statement_offset..];
|
||||||
// get the id out of the public statements, and ensure it is equal to self.id
|
// 2. get the id out of the public statements, and ensure it is equal to self.id
|
||||||
let ids_match = self.id == PodId(hash_statements(&self.public_statements, &self.params));
|
let ids_match = self.id == PodId(hash_statements(&self.public_statements, &self.params));
|
||||||
// find a ValueOf statement from the public statements with key=KEY_TYPE and check that the
|
// find a ValueOf statement from the public statements with key=KEY_TYPE and check that the
|
||||||
// value is PodType::MockMainPod
|
// value is PodType::MockMainPod
|
||||||
|
|
@ -391,7 +393,7 @@ impl Pod for MockMainPod {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.is_some();
|
.is_some();
|
||||||
// check that all `input_statements` of type `ValueOf` with origin=SELF have unique keys
|
// 3. check that all `input_statements` of type `ValueOf` with origin=SELF have unique keys
|
||||||
// (no duplicates)
|
// (no duplicates)
|
||||||
// TODO: Instead of doing this, do a uniqueness check when verifying the output of a
|
// TODO: Instead of doing this, do a uniqueness check when verifying the output of a
|
||||||
// `NewValue` operation.
|
// `NewValue` operation.
|
||||||
|
|
@ -421,7 +423,9 @@ impl Pod for MockMainPod {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
!(0..key_id_pairs.len() - 1).any(|i| key_id_pairs[i + 1..].contains(&key_id_pairs[i]))
|
!(0..key_id_pairs.len() - 1).any(|i| key_id_pairs[i + 1..].contains(&key_id_pairs[i]))
|
||||||
};
|
};
|
||||||
// verify that all `input_statements` are correctly generated
|
// 4. TODO: Verify type
|
||||||
|
|
||||||
|
// 5. verify that all `input_statements` are correctly generated
|
||||||
// by `self.operations` (where each operation can only access previous statements)
|
// by `self.operations` (where each operation can only access previous statements)
|
||||||
let statement_check = input_statements
|
let statement_check = input_statements
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ pub struct MockSignedPod {
|
||||||
|
|
||||||
impl Pod for MockSignedPod {
|
impl Pod for MockSignedPod {
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
// Verify type
|
// 1. Verify type
|
||||||
let value_at_type = match self.dict.get(&hash_str(KEY_TYPE).into()) {
|
let value_at_type = match self.dict.get(&hash_str(KEY_TYPE).into()) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
|
|
@ -55,7 +55,7 @@ impl Pod for MockSignedPod {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify id
|
// 2. Verify id
|
||||||
let mt = match MerkleTree::new(
|
let mt = match MerkleTree::new(
|
||||||
MAX_DEPTH,
|
MAX_DEPTH,
|
||||||
&self
|
&self
|
||||||
|
|
@ -72,7 +72,7 @@ impl Pod for MockSignedPod {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify signature
|
// 3. Verify signature
|
||||||
let pk_hash = match self.dict.get(&hash_str(KEY_SIGNER).into()) {
|
let pk_hash = match self.dict.get(&hash_str(KEY_SIGNER).into()) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod basetypes;
|
pub mod basetypes;
|
||||||
|
pub mod common;
|
||||||
|
pub mod main;
|
||||||
pub mod mock_main;
|
pub mod mock_main;
|
||||||
pub mod mock_signed;
|
pub mod mock_signed;
|
||||||
pub mod primitives;
|
pub mod primitives;
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,12 @@ use plonky2::{
|
||||||
},
|
},
|
||||||
plonk::circuit_builder::CircuitBuilder,
|
plonk::circuit_builder::CircuitBuilder,
|
||||||
};
|
};
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE};
|
use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE};
|
||||||
|
use crate::backends::plonky2::common::{
|
||||||
|
CircuitBuilderPod, OperationTarget, StatementTarget, ValueTarget,
|
||||||
|
};
|
||||||
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
|
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
|
||||||
|
|
||||||
/// `MerkleProofCircuit` allows to verify both proofs of existence and proofs
|
/// `MerkleProofCircuit` allows to verify both proofs of existence and proofs
|
||||||
|
|
@ -30,30 +34,30 @@ use crate::backends::plonky2::primitives::merkletree::MerkleProof;
|
||||||
/// If only proofs of existence are needed, use `MerkleProofExistenceCircuit`,
|
/// If only proofs of existence are needed, use `MerkleProofExistenceCircuit`,
|
||||||
/// which requires less amount of constraints.
|
/// which requires less amount of constraints.
|
||||||
pub struct MerkleProofCircuit<const MAX_DEPTH: usize> {
|
pub struct MerkleProofCircuit<const MAX_DEPTH: usize> {
|
||||||
root: HashOutTarget,
|
pub root: HashOutTarget,
|
||||||
key: Vec<Target>,
|
pub key: ValueTarget,
|
||||||
value: Vec<Target>,
|
pub value: ValueTarget,
|
||||||
existence: BoolTarget,
|
pub existence: BoolTarget,
|
||||||
siblings: Vec<HashOutTarget>,
|
pub siblings: Vec<HashOutTarget>,
|
||||||
case_ii_selector: BoolTarget, // for case ii)
|
pub case_ii_selector: BoolTarget, // for case ii)
|
||||||
other_key: Vec<Target>,
|
pub other_key: ValueTarget,
|
||||||
other_value: Vec<Target>,
|
pub other_value: ValueTarget,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
||||||
/// creates the targets and defines the logic of the circuit
|
/// creates the targets and defines the logic of the circuit
|
||||||
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
|
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
|
||||||
// create the targets
|
// create the targets
|
||||||
let key = builder.add_virtual_targets(VALUE_SIZE);
|
let key = builder.add_virtual_value();
|
||||||
let value = builder.add_virtual_targets(VALUE_SIZE);
|
let value = builder.add_virtual_value();
|
||||||
// from proof struct:
|
// from proof struct:
|
||||||
let existence = builder.add_virtual_bool_target_safe();
|
let existence = builder.add_virtual_bool_target_safe();
|
||||||
// siblings are padded till MAX_DEPTH length
|
// siblings are padded till MAX_DEPTH length
|
||||||
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
|
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
|
||||||
|
|
||||||
let case_ii_selector = builder.add_virtual_bool_target_safe();
|
let case_ii_selector = builder.add_virtual_bool_target_safe();
|
||||||
let other_key = builder.add_virtual_targets(VALUE_SIZE);
|
let other_key = builder.add_virtual_value();
|
||||||
let other_value = builder.add_virtual_targets(VALUE_SIZE);
|
let other_value = builder.add_virtual_value();
|
||||||
|
|
||||||
// We have 3 cases for when computing the Leaf's hash:
|
// We have 3 cases for when computing the Leaf's hash:
|
||||||
// - existence: leaf contains the given key & value
|
// - existence: leaf contains the given key & value
|
||||||
|
|
@ -87,12 +91,8 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
||||||
// of existence or of non-existence, ie:
|
// of existence or of non-existence, ie:
|
||||||
// k = key * existence + other_key * (1-existence)
|
// k = key * existence + other_key * (1-existence)
|
||||||
// v = value * existence + other_value * (1-existence)
|
// v = value * existence + other_value * (1-existence)
|
||||||
let k: Vec<Target> = (0..4)
|
let k = builder.select_value(existence, key, other_key);
|
||||||
.map(|j| builder.select(existence, key[j], other_key[j]))
|
let v = builder.select_value(existence, value, other_value);
|
||||||
.collect();
|
|
||||||
let v: Vec<Target> = (0..4)
|
|
||||||
.map(|j| builder.select(existence, value[j], other_value[j]))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// get leaf's hash for the selected k & v
|
// get leaf's hash for the selected k & v
|
||||||
let h = kv_hash_target(builder, &k, &v);
|
let h = kv_hash_target(builder, &k, &v);
|
||||||
|
|
@ -139,8 +139,8 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
||||||
value: Value,
|
value: Value,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
pw.set_hash_target(self.root, HashOut::from_vec(root.0.to_vec()))?;
|
pw.set_hash_target(self.root, HashOut::from_vec(root.0.to_vec()))?;
|
||||||
pw.set_target_arr(&self.key, &key.0)?;
|
pw.set_target_arr(&self.key.elements, &key.0)?;
|
||||||
pw.set_target_arr(&self.value, &value.0)?;
|
pw.set_target_arr(&self.value.elements, &value.0)?;
|
||||||
pw.set_bool_target(self.existence, existence)?;
|
pw.set_bool_target(self.existence, existence)?;
|
||||||
|
|
||||||
// pad siblings with zeros to length MAX_DEPTH
|
// pad siblings with zeros to length MAX_DEPTH
|
||||||
|
|
@ -156,14 +156,14 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
||||||
Some((k, v)) if !existence => {
|
Some((k, v)) if !existence => {
|
||||||
// non-existence case ii) expected leaf does exist but it has a different key
|
// non-existence case ii) expected leaf does exist but it has a different key
|
||||||
pw.set_bool_target(self.case_ii_selector, true)?;
|
pw.set_bool_target(self.case_ii_selector, true)?;
|
||||||
pw.set_target_arr(&self.other_key, &k.0)?;
|
pw.set_target_arr(&self.other_key.elements, &k.0)?;
|
||||||
pw.set_target_arr(&self.other_value, &v.0)?;
|
pw.set_target_arr(&self.other_value.elements, &v.0)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// existence & non-existence case i) expected leaf does not exist
|
// existence & non-existence case i) expected leaf does not exist
|
||||||
pw.set_bool_target(self.case_ii_selector, false)?;
|
pw.set_bool_target(self.case_ii_selector, false)?;
|
||||||
pw.set_target_arr(&self.other_key, &EMPTY_VALUE.0)?;
|
pw.set_target_arr(&self.other_key.elements, &EMPTY_VALUE.0)?;
|
||||||
pw.set_target_arr(&self.other_value, &EMPTY_VALUE.0)?;
|
pw.set_target_arr(&self.other_value.elements, &EMPTY_VALUE.0)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,18 +174,18 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
|
||||||
/// `MerkleProofExistenceCircuit` allows to verify proofs of existence only. If
|
/// `MerkleProofExistenceCircuit` allows to verify proofs of existence only. If
|
||||||
/// proofs of non-existence are needed, use `MerkleProofCircuit`.
|
/// proofs of non-existence are needed, use `MerkleProofCircuit`.
|
||||||
pub struct MerkleProofExistenceCircuit<const MAX_DEPTH: usize> {
|
pub struct MerkleProofExistenceCircuit<const MAX_DEPTH: usize> {
|
||||||
root: HashOutTarget,
|
pub root: HashOutTarget,
|
||||||
key: Vec<Target>,
|
pub key: ValueTarget,
|
||||||
value: Vec<Target>,
|
pub value: ValueTarget,
|
||||||
siblings: Vec<HashOutTarget>,
|
pub siblings: Vec<HashOutTarget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const MAX_DEPTH: usize> MerkleProofExistenceCircuit<MAX_DEPTH> {
|
impl<const MAX_DEPTH: usize> MerkleProofExistenceCircuit<MAX_DEPTH> {
|
||||||
/// creates the targets and defines the logic of the circuit
|
/// creates the targets and defines the logic of the circuit
|
||||||
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
|
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
|
||||||
// create the targets
|
// create the targets
|
||||||
let key = builder.add_virtual_targets(VALUE_SIZE);
|
let key = builder.add_virtual_value();
|
||||||
let value = builder.add_virtual_targets(VALUE_SIZE);
|
let value = builder.add_virtual_value();
|
||||||
// siblings are padded till MAX_DEPTH length
|
// siblings are padded till MAX_DEPTH length
|
||||||
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
|
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
|
||||||
|
|
||||||
|
|
@ -218,8 +218,8 @@ impl<const MAX_DEPTH: usize> MerkleProofExistenceCircuit<MAX_DEPTH> {
|
||||||
value: Value,
|
value: Value,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
pw.set_hash_target(self.root, HashOut::from_vec(root.0.to_vec()))?;
|
pw.set_hash_target(self.root, HashOut::from_vec(root.0.to_vec()))?;
|
||||||
pw.set_target_arr(&self.key, &key.0)?;
|
pw.set_target_arr(&self.key.elements, &key.0)?;
|
||||||
pw.set_target_arr(&self.value, &value.0)?;
|
pw.set_target_arr(&self.value.elements, &value.0)?;
|
||||||
|
|
||||||
// pad siblings with zeros to length MAX_DEPTH
|
// pad siblings with zeros to length MAX_DEPTH
|
||||||
let mut siblings = proof.siblings.clone();
|
let mut siblings = proof.siblings.clone();
|
||||||
|
|
@ -297,21 +297,21 @@ fn compute_root_from_leaf<const MAX_DEPTH: usize>(
|
||||||
// specially to be able to test it isolated.
|
// specially to be able to test it isolated.
|
||||||
fn keypath_target<const MAX_DEPTH: usize>(
|
fn keypath_target<const MAX_DEPTH: usize>(
|
||||||
builder: &mut CircuitBuilder<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
key: &Vec<Target>,
|
key: &ValueTarget,
|
||||||
) -> Vec<BoolTarget> {
|
) -> Vec<BoolTarget> {
|
||||||
assert_eq!(key.len(), VALUE_SIZE);
|
|
||||||
|
|
||||||
let n_complete_field_elems: usize = MAX_DEPTH / F::BITS;
|
let n_complete_field_elems: usize = MAX_DEPTH / F::BITS;
|
||||||
let n_extra_bits: usize = MAX_DEPTH - n_complete_field_elems * F::BITS;
|
let n_extra_bits: usize = MAX_DEPTH - n_complete_field_elems * F::BITS;
|
||||||
|
|
||||||
let path: Vec<BoolTarget> = key
|
let path: Vec<BoolTarget> = key
|
||||||
|
.elements
|
||||||
.iter()
|
.iter()
|
||||||
.take(n_complete_field_elems)
|
.take(n_complete_field_elems)
|
||||||
.flat_map(|e| builder.split_le(*e, F::BITS))
|
.flat_map(|e| builder.split_le(*e, F::BITS))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let extra_bits = if n_extra_bits > 0 {
|
let extra_bits = if n_extra_bits > 0 {
|
||||||
let extra_bits: Vec<BoolTarget> = builder.split_le(key[n_complete_field_elems], F::BITS);
|
let extra_bits: Vec<BoolTarget> =
|
||||||
|
builder.split_le(key.elements[n_complete_field_elems], F::BITS);
|
||||||
extra_bits[..n_extra_bits].to_vec()
|
extra_bits[..n_extra_bits].to_vec()
|
||||||
// Note: ideally we would do:
|
// Note: ideally we would do:
|
||||||
// let extra_bits = builder.split_le(key[n_complete_field_elems], n_extra_bits);
|
// let extra_bits = builder.split_le(key[n_complete_field_elems], n_extra_bits);
|
||||||
|
|
@ -326,10 +326,16 @@ fn keypath_target<const MAX_DEPTH: usize>(
|
||||||
|
|
||||||
fn kv_hash_target(
|
fn kv_hash_target(
|
||||||
builder: &mut CircuitBuilder<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
key: &Vec<Target>,
|
key: &ValueTarget,
|
||||||
value: &Vec<Target>,
|
value: &ValueTarget,
|
||||||
) -> HashOutTarget {
|
) -> HashOutTarget {
|
||||||
let inputs: Vec<Target> = [key.clone(), value.clone(), vec![builder.one()]].concat();
|
let inputs = key
|
||||||
|
.elements
|
||||||
|
.iter()
|
||||||
|
.chain(value.elements.iter())
|
||||||
|
.cloned()
|
||||||
|
.chain(iter::once(builder.one()))
|
||||||
|
.collect();
|
||||||
builder.hash_n_to_hash_no_pad::<PoseidonHash>(inputs)
|
builder.hash_n_to_hash_no_pad::<PoseidonHash>(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,14 +377,14 @@ pub mod tests {
|
||||||
let expected_path_targ: Vec<BoolTarget> = (0..MD)
|
let expected_path_targ: Vec<BoolTarget> = (0..MD)
|
||||||
.map(|_| builder.add_virtual_bool_target_safe())
|
.map(|_| builder.add_virtual_bool_target_safe())
|
||||||
.collect();
|
.collect();
|
||||||
let key_targ = builder.add_virtual_targets(VALUE_SIZE);
|
let key_targ = builder.add_virtual_value();
|
||||||
let computed_path_targ = keypath_target::<MD>(&mut builder, &key_targ);
|
let computed_path_targ = keypath_target::<MD>(&mut builder, &key_targ);
|
||||||
for i in 0..MD {
|
for i in 0..MD {
|
||||||
builder.connect(computed_path_targ[i].target, expected_path_targ[i].target);
|
builder.connect(computed_path_targ[i].target, expected_path_targ[i].target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign the input values to the targets
|
// assign the input values to the targets
|
||||||
pw.set_target_arr(&key_targ, &key.0)?;
|
pw.set_target_arr(&key_targ.elements, &key.0)?;
|
||||||
for i in 0..MD {
|
for i in 0..MD {
|
||||||
pw.set_bool_target(expected_path_targ[i], expected_path[i])?;
|
pw.set_bool_target(expected_path_targ[i], expected_path[i])?;
|
||||||
}
|
}
|
||||||
|
|
@ -404,15 +410,15 @@ pub mod tests {
|
||||||
let mut pw = PartialWitness::<F>::new();
|
let mut pw = PartialWitness::<F>::new();
|
||||||
|
|
||||||
let h_targ = builder.add_virtual_hash();
|
let h_targ = builder.add_virtual_hash();
|
||||||
let key_targ = builder.add_virtual_targets(VALUE_SIZE);
|
let key_targ = builder.add_virtual_value();
|
||||||
let value_targ = builder.add_virtual_targets(VALUE_SIZE);
|
let value_targ = builder.add_virtual_value();
|
||||||
|
|
||||||
let computed_h = kv_hash_target(&mut builder, &key_targ, &value_targ);
|
let computed_h = kv_hash_target(&mut builder, &key_targ, &value_targ);
|
||||||
builder.connect_hashes(computed_h, h_targ);
|
builder.connect_hashes(computed_h, h_targ);
|
||||||
|
|
||||||
// assign the input values to the targets
|
// assign the input values to the targets
|
||||||
pw.set_target_arr(&key_targ, &key.0)?;
|
pw.set_target_arr(&key_targ.elements, &key.0)?;
|
||||||
pw.set_target_arr(&value_targ, &value.0)?;
|
pw.set_target_arr(&value_targ.elements, &value.0)?;
|
||||||
pw.set_hash_target(h_targ, HashOut::from_vec(h.0.to_vec()))?;
|
pw.set_hash_target(h_targ, HashOut::from_vec(h.0.to_vec()))?;
|
||||||
|
|
||||||
// generate & verify proof
|
// generate & verify proof
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue