Add SignedPod verification circuit (SignedPodVerifyGadget) (#170)

* add boolean selector to the MerkleProofGadget, to allow skipping proof verifications when all the slots are not used (eg. in the SignedPod circuit)

* move existing signedpod's circuits draft to its own file

* implement SignedPodVerify circuit
This commit is contained in:
arnaucube 2025-04-01 18:20:28 +02:00 committed by GitHub
parent 0637f52573
commit 4a94b34792
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 357 additions and 182 deletions

View file

@ -23,103 +23,16 @@ use crate::backends::plonky2::primitives::merkletree::MerkleTree;
use crate::backends::plonky2::primitives::merkletree::{ use crate::backends::plonky2::primitives::merkletree::{
MerkleProofExistenceGadget, MerkleProofExistenceTarget, MerkleProofExistenceGadget, MerkleProofExistenceTarget,
}; };
use crate::backends::plonky2::signedpod::SignedPod;
use crate::middleware::{ use crate::middleware::{
hash_str, AnchoredKey, NativeOperation, NativePredicate, Params, PodType, Statement, hash_str, AnchoredKey, NativeOperation, NativePredicate, Params, PodType, Statement,
StatementArg, ToFields, KEY_TYPE, SELF, StatementArg, ToFields, KEY_TYPE, SELF,
}; };
use super::common::Flattenable; use super::{
common::Flattenable,
// signedpod::{SignedPodVerifyGadget, SignedPodVerifyTarget},
// SignedPod verification };
//
struct SignedPodVerifyGadget {
params: Params,
}
impl SignedPodVerifyGadget {
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 = MerkleProofExistenceGadget {
max_depth: self.params.max_depth_mt_gadget,
}
.eval(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<MerkleProofExistenceTarget>,
}
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(self.params.max_depth_mt_gadget, &input.kvs)?;
// First handle the type entry, then the rest of the entries, and finally pad with
// repetitions of the type entry (which always exists)
let mut kvs = input.kvs.clone();
let key_type = Value::from(hash_str(KEY_TYPE));
let value_type = kvs.remove(&key_type).expect("KEY_TYPE");
for (i, (k, v)) in iter::once((key_type, value_type))
.chain(kvs.into_iter().sorted_by_key(|kv| kv.0))
.chain(iter::repeat((key_type, value_type)))
.take(self.params.max_signed_pod_values)
.enumerate()
{
let (_, proof) = tree.prove(&k)?;
self.mt_proofs[i].set_targets(pw, tree.root(), proof, k, v)?;
}
Ok(())
}
}
// //
// MainPod verification // MainPod verification
@ -450,7 +363,7 @@ struct MainPodVerifyTarget {
} }
struct MainPodVerifyInput { struct MainPodVerifyInput {
signed_pods: Vec<SignedPodVerifyInput>, signed_pods: Vec<SignedPod>,
} }
impl MainPodVerifyTarget { impl MainPodVerifyTarget {
@ -489,6 +402,8 @@ impl MainPodVerifyCircuit {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig};
use super::*; use super::*;
use crate::backends::plonky2::mock::mainpod; use crate::backends::plonky2::mock::mainpod;
use crate::backends::plonky2::{ use crate::backends::plonky2::{
@ -496,36 +411,6 @@ mod tests {
mock::mainpod::{OperationArg, OperationAux}, mock::mainpod::{OperationArg, OperationAux},
}; };
use crate::middleware::{OperationType, PodId}; use crate::middleware::{OperationType, PodId};
use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig};
#[test]
fn test_signed_pod_verify() -> Result<()> {
let params = Params::default();
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let signed_pod_verify = SignedPodVerifyGadget { params }.eval(&mut builder)?;
let mut pw = PartialWitness::<F>::new();
let kvs = [
(
Value::from(hash_str(KEY_TYPE)),
Value::from(PodType::MockSigned),
),
(Value::from(hash_str("foo")), Value::from(42)),
]
.into();
let input = SignedPodVerifyInput { kvs };
signed_pod_verify.set_targets(&mut pw, &input)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
fn operation_verify( fn operation_verify(
st: mainpod::Statement, st: mainpod::Statement,

View file

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

View file

@ -0,0 +1,202 @@
use anyhow::Result;
use itertools::Itertools;
use plonky2::{
hash::hash_types::{HashOut, HashOutTarget},
iop::witness::{PartialWitness, WitnessWrite},
plonk::circuit_builder::CircuitBuilder,
};
use crate::backends::plonky2::{
basetypes::{Value, D, EMPTY_VALUE, F},
circuits::common::{CircuitBuilderPod, StatementTarget, ValueTarget},
primitives::{
merkletree::{MerkleProof, MerkleProofExistenceGadget, MerkleProofExistenceTarget},
signature::{PublicKey, SignatureVerifyGadget, SignatureVerifyTarget},
},
signedpod::SignedPod,
};
use crate::middleware::{hash_str, Params, PodType, KEY_SIGNER, KEY_TYPE};
pub struct SignedPodVerifyGadget {
pub params: Params,
}
impl SignedPodVerifyGadget {
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<SignedPodVerifyTarget> {
// 1. 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 = MerkleProofExistenceGadget {
max_depth: self.params.max_depth_mt_gadget,
}
.eval(builder)?;
builder.connect_hashes(id, mt_proof.root);
mt_proofs.push(mt_proof);
}
// 2. 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::Signed));
builder.connect_values(type_mt_proof.value, value_type);
// 3.a. Verify signature
let signature = SignatureVerifyGadget {}.eval(builder)?;
// 3.b. Verify signer (ie. signature.pk == merkletree.signer_leaf)
let signer_mt_proof = &mt_proofs[1];
let key_signer = builder.constant_value(hash_str(KEY_SIGNER).into());
builder.connect_values(signer_mt_proof.key, key_signer);
builder.connect_values(signer_mt_proof.value, signature.pk);
// 3.c. connect signed message to pod.id
builder.connect_values(ValueTarget::from_slice(&id.elements), signature.msg);
Ok(SignedPodVerifyTarget {
params: self.params.clone(),
id,
mt_proofs,
signature,
})
}
}
pub 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<MerkleProofExistenceTarget>,
signature: SignatureVerifyTarget,
}
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 set_targets(&self, pw: &mut PartialWitness<F>, pod: &SignedPod) -> Result<()> {
// set the self.mt_proofs witness with the following order:
// - KEY_TYPE leaf proof
// - KEY_SIGNER leaf proof
// - rest of leaves
// - empty leaves (if needed)
// add proof verification of KEY_TYPE & KEY_SIGNER leaves
let key_type_key = Value::from(hash_str(KEY_TYPE));
let key_signer_key = Value::from(hash_str(KEY_SIGNER));
let key_signer_value = [key_type_key, key_signer_key]
.iter()
.enumerate()
.map(|(i, k)| {
let (v, proof) = pod.dict.prove(&k)?;
self.mt_proofs[i].set_targets(pw, true, pod.dict.commitment(), proof, *k, v)?;
Ok(v)
})
.collect::<Result<Vec<Value>>>()?[1];
// add the verification of the rest of leaves
let mut curr = 2; // since we already added key_type and key_signer
for (k, v) in pod.dict.iter().sorted_by_key(|kv| kv.0) {
if *k == key_type_key || *k == key_signer_key {
// skip the key_type & key_signer leaves, since they have
// already been checked
continue;
}
let (obtained_v, proof) = pod.dict.prove(&k)?;
assert_eq!(obtained_v, *v); // sanity check
self.mt_proofs[curr].set_targets(pw, true, pod.dict.commitment(), proof, *k, *v)?;
curr += 1;
}
// sanity check
assert!(curr <= self.params.max_signed_pod_values);
// add the proofs of empty leaves (if needed), till the max_signed_pod_values
for i in curr..self.params.max_signed_pod_values {
self.mt_proofs[i].set_targets(
pw,
false, // disable verification
pod.dict.commitment(),
// use an empty proof:
MerkleProof {
existence: true,
siblings: vec![],
other_leaf: None,
},
EMPTY_VALUE,
EMPTY_VALUE,
)?;
}
// get the signer pk
let pk = PublicKey(key_signer_value);
// the msg signed is the pod.id
let msg = Value::from(pod.id.0);
// set signature targets values
self.signature
.set_targets(pw, true, pk, msg, pod.signature.clone())?;
// set the id target value
pw.set_hash_target(self.id, HashOut::from_vec(pod.id.0 .0.to_vec()))?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig};
use super::*;
use crate::backends::plonky2::{
basetypes::C,
primitives::signature::SecretKey,
signedpod::{SignedPod, Signer},
};
use crate::middleware::F;
#[test]
fn test_signed_pod_verify() -> Result<()> {
let mut params = Params {
max_signed_pod_values: 6,
..Default::default()
};
// set max_signed_pod_values to 6, and we insert 3 leaves, so that the
// circuit has enough space for the 3 leaves plus the KEY_TYPE and
// KEY_SIGNER and one empty leaf.
// prepare a signedpod
let mut pod = crate::frontend::SignedPodBuilder::new(&params);
pod.insert("idNumber", "4242424242");
pod.insert("dateOfBirth", 1169909384);
pod.insert("socialSecurityNumber", "G2121210");
let sk = SecretKey::new();
let mut signer = Signer(sk);
let pod = pod.sign(&mut signer).unwrap();
let signed_pod = pod.pod.into_any().downcast::<SignedPod>().unwrap();
// use the pod in the circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
// build the circuit logic
let signed_pod_verify = SignedPodVerifyGadget { params }.eval(&mut builder)?;
// set the signed_pod as target values for the circuit
signed_pod_verify.set_targets(&mut pw, &signed_pod)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
}

View file

@ -56,17 +56,7 @@ impl MockSignedPod {
impl Pod for MockSignedPod { impl Pod for MockSignedPod {
fn verify(&self) -> Result<()> { fn verify(&self) -> Result<()> {
// 1. Verify type // 1. Verify id
let value_at_type = self.dict.get(&hash_str(KEY_TYPE).into())?;
if Value::from(PodType::MockSigned) != value_at_type {
return Err(anyhow!(
"type does not match, expected MockSigned ({}), found {}",
PodType::MockSigned,
value_at_type
));
}
// 2. Verify id
let mt = MerkleTree::new( let mt = MerkleTree::new(
MAX_DEPTH, MAX_DEPTH,
&self &self
@ -84,6 +74,16 @@ impl Pod for MockSignedPod {
)); ));
} }
// 2. Verify type
let value_at_type = self.dict.get(&hash_str(KEY_TYPE).into())?;
if Value::from(PodType::MockSigned) != value_at_type {
return Err(anyhow!(
"type does not match, expected MockSigned ({}), found {}",
PodType::MockSigned,
value_at_type
));
}
// 3. Verify signature // 3. Verify signature
let pk_hash = self.dict.get(&hash_str(KEY_SIGNER).into())?; let pk_hash = self.dict.get(&hash_str(KEY_SIGNER).into())?;
let signature = format!("{}_signed_by_{}", id, pk_hash); let signature = format!("{}_signed_by_{}", id, pk_hash);

View file

@ -23,7 +23,7 @@ use plonky2::{
}; };
use std::iter; use std::iter;
use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F}; use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE};
use crate::backends::plonky2::circuits::common::{CircuitBuilderPod, ValueTarget}; use crate::backends::plonky2::circuits::common::{CircuitBuilderPod, ValueTarget};
use crate::backends::plonky2::primitives::merkletree::MerkleProof; use crate::backends::plonky2::primitives::merkletree::MerkleProof;
@ -37,20 +37,23 @@ pub struct MerkleProofGadget {
pub struct MerkleProofTarget { pub struct MerkleProofTarget {
max_depth: usize, max_depth: usize,
pub root: HashOutTarget, // `enabled` determines if the merkleproof verification is enabled
pub key: ValueTarget, pub(crate) enabled: BoolTarget,
pub value: ValueTarget, pub(crate) root: HashOutTarget,
pub existence: BoolTarget, pub(crate) key: ValueTarget,
pub siblings: Vec<HashOutTarget>, pub(crate) value: ValueTarget,
pub case_ii_selector: BoolTarget, // for case ii) pub(crate) existence: BoolTarget,
pub other_key: ValueTarget, pub(crate) siblings: Vec<HashOutTarget>,
pub other_value: ValueTarget, pub(crate) case_ii_selector: BoolTarget, // for case ii)
pub(crate) other_key: ValueTarget,
pub(crate) other_value: ValueTarget,
} }
impl MerkleProofGadget { impl MerkleProofGadget {
/// creates the targets and defines the logic of the circuit /// creates the targets and defines the logic of the circuit
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MerkleProofTarget> { pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MerkleProofTarget> {
// create the targets let enabled = builder.add_virtual_bool_target_safe();
let root = builder.add_virtual_hash();
let key = builder.add_virtual_value(); let key = builder.add_virtual_value();
let value = builder.add_virtual_value(); let value = builder.add_virtual_value();
// from proof struct: // from proof struct:
@ -104,7 +107,7 @@ impl MerkleProofGadget {
// previously computed hash h. // previously computed hash h.
let empty_hash = builder.constant_hash(HashOut::from(EMPTY_HASH.0)); let empty_hash = builder.constant_hash(HashOut::from(EMPTY_HASH.0));
let leaf_hash = HashOutTarget::from_vec( let leaf_hash = HashOutTarget::from_vec(
(0..4) (0..HASH_SIZE)
.map(|j| builder.select(case_i_selector, empty_hash.elements[j], h.elements[j])) .map(|j| builder.select(case_i_selector, empty_hash.elements[j], h.elements[j]))
.collect(), .collect(),
); );
@ -115,12 +118,24 @@ impl MerkleProofGadget {
// compute the root for the given siblings and the computed leaf_hash // compute the root for the given siblings and the computed leaf_hash
// (this is for the three cases (existence, non-existence case i, and // (this is for the three cases (existence, non-existence case i, and
// non-existence case ii). // non-existence case ii).
// This root will be assigned in the `set_targets` method, and it is a let obtained_root =
// public input. compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
let root = compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
// check that obtained_root==root (from inputs), when enabled==true
let zero = builder.zero();
let expected_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(enabled, root.elements[j], zero))
.collect();
let computed_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(enabled, obtained_root.elements[j], zero))
.collect();
for j in 0..HASH_SIZE {
builder.connect(computed_root[j], expected_root[j]);
}
Ok(MerkleProofTarget { Ok(MerkleProofTarget {
max_depth: self.max_depth, max_depth: self.max_depth,
enabled,
existence, existence,
root, root,
siblings, siblings,
@ -138,12 +153,15 @@ impl MerkleProofTarget {
pub fn set_targets( pub fn set_targets(
&self, &self,
pw: &mut PartialWitness<F>, pw: &mut PartialWitness<F>,
// `enabled` determines if the merkleproof verification is enabled
enabled: bool,
existence: bool, existence: bool,
root: Hash, root: Hash,
proof: MerkleProof, proof: MerkleProof,
key: Value, key: Value,
value: Value, value: Value,
) -> Result<()> { ) -> Result<()> {
pw.set_bool_target(self.enabled, enabled)?;
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.elements, &key.0)?; pw.set_target_arr(&self.key.elements, &key.0)?;
pw.set_target_arr(&self.value.elements, &value.0)?; pw.set_target_arr(&self.value.elements, &value.0)?;
@ -185,16 +203,19 @@ pub struct MerkleProofExistenceGadget {
pub struct MerkleProofExistenceTarget { pub struct MerkleProofExistenceTarget {
max_depth: usize, max_depth: usize,
pub root: HashOutTarget, // `enabled` determines if the merkleproof verification is enabled
pub key: ValueTarget, pub(crate) enabled: BoolTarget,
pub value: ValueTarget, pub(crate) root: HashOutTarget,
pub siblings: Vec<HashOutTarget>, pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget,
pub(crate) siblings: Vec<HashOutTarget>,
} }
impl MerkleProofExistenceGadget { impl MerkleProofExistenceGadget {
/// creates the targets and defines the logic of the circuit /// creates the targets and defines the logic of the circuit
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MerkleProofExistenceTarget> { pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<MerkleProofExistenceTarget> {
// create the targets let enabled = builder.add_virtual_bool_target_safe();
let root = builder.add_virtual_hash();
let key = builder.add_virtual_value(); let key = builder.add_virtual_value();
let value = builder.add_virtual_value(); let value = builder.add_virtual_value();
// siblings are padded till max_depth length // siblings are padded till max_depth length
@ -207,12 +228,24 @@ impl MerkleProofExistenceGadget {
let path = keypath_target(self.max_depth, builder, &key); let path = keypath_target(self.max_depth, builder, &key);
// compute the root for the given siblings and the computed leaf_hash. // compute the root for the given siblings and the computed leaf_hash.
// This root will be assigned in the `set_targets` method, and it is a let obtained_root =
// public input. compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
let root = compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
// check that obtained_root==root (from inputs), when enabled==true
let zero = builder.zero();
let expected_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(enabled, root.elements[j], zero))
.collect();
let computed_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(enabled, obtained_root.elements[j], zero))
.collect();
for j in 0..HASH_SIZE {
builder.connect(computed_root[j], expected_root[j]);
}
Ok(MerkleProofExistenceTarget { Ok(MerkleProofExistenceTarget {
max_depth: self.max_depth, max_depth: self.max_depth,
enabled,
root, root,
siblings, siblings,
key, key,
@ -226,11 +259,16 @@ impl MerkleProofExistenceTarget {
pub fn set_targets( pub fn set_targets(
&self, &self,
pw: &mut PartialWitness<F>, pw: &mut PartialWitness<F>,
// `enabled` determines if the merkleproof verification is enabled
enabled: bool,
root: Hash, root: Hash,
proof: MerkleProof, proof: MerkleProof,
key: Value, key: Value,
value: Value, value: Value,
) -> Result<()> { ) -> Result<()> {
assert!(proof.existence); // sanity check
pw.set_bool_target(self.enabled, enabled)?;
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.elements, &key.0)?; pw.set_target_arr(&self.key.elements, &key.0)?;
pw.set_target_arr(&self.value.elements, &value.0)?; pw.set_target_arr(&self.value.elements, &value.0)?;
@ -291,16 +329,16 @@ fn compute_root_from_leaf(
// input_2 = sibling * (1-s) + h * s = select(s, h, sibling) // input_2 = sibling * (1-s) + h * s = select(s, h, sibling)
// new_h = hash([input_1, input_2]) // new_h = hash([input_1, input_2])
// TODO explore if to group multiple muls in a single gate // TODO explore if to group multiple muls in a single gate
let input_1: Vec<Target> = (0..4) let input_1: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(path[i], sibling.elements[j], h.elements[j])) .map(|j| builder.select(path[i], sibling.elements[j], h.elements[j]))
.collect(); .collect();
let input_2: Vec<Target> = (0..4) let input_2: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(path[i], h.elements[j], sibling.elements[j])) .map(|j| builder.select(path[i], h.elements[j], sibling.elements[j]))
.collect(); .collect();
let new_h = let new_h =
builder.hash_n_to_hash_no_pad::<PoseidonHash>([input_1, input_2, vec![two]].concat()); builder.hash_n_to_hash_no_pad::<PoseidonHash>([input_1, input_2, vec![two]].concat());
let h_targ: Vec<Target> = (0..4) let h_targ: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(*selector, new_h.elements[j], h.elements[j])) .map(|j| builder.select(*selector, new_h.elements[j], h.elements[j]))
.collect(); .collect();
h = HashOutTarget::from_vec(h_targ); h = HashOutTarget::from_vec(h_targ);
@ -487,7 +525,15 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?; let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?;
targets.set_targets(&mut pw, existence, tree.root(), proof, key, value)?; targets.set_targets(
&mut pw,
true, // verification enabled
existence,
tree.root(),
proof,
key,
value,
)?;
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();
@ -526,7 +572,7 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofExistenceGadget { max_depth }.eval(&mut builder)?; let targets = MerkleProofExistenceGadget { max_depth }.eval(&mut builder)?;
targets.set_targets(&mut pw, tree.root(), proof, key, value)?; targets.set_targets(&mut pw, true, tree.root(), proof, key, value)?;
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();
@ -593,7 +639,15 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?; let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?;
targets.set_targets(&mut pw, proof.existence, tree.root(), proof, key, value)?; targets.set_targets(
&mut pw,
true, // verification enabled
proof.existence,
tree.root(),
proof,
key,
value,
)?;
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();
@ -634,13 +688,44 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?; let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?;
targets.set_targets(&mut pw, true, tree2.root(), proof, key, value)?; targets.set_targets(
&mut pw,
true, // verification enabled
true, // proof of existence
tree2.root(),
proof.clone(),
key,
value,
)?;
// generate proof, expecting it to fail (since we're using the wrong // generate proof, expecting it to fail (since we're using the wrong
// root) // root)
let data = builder.build::<C>(); let data = builder.build::<C>();
assert!(data.prove(pw).is_err()); assert!(data.prove(pw).is_err());
// Now generate a new proof, using `enabled=false`, which should pass the verification
// despite containing 'wrong' witness.
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofGadget { max_depth }.eval(&mut builder)?;
targets.set_targets(
&mut pw,
false, // verification disabled
true, // proof of existence
tree2.root(),
proof,
key,
value,
)?;
// generate proof, should pass despite using wrong witness, since the
// `enabled=false`
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(()) Ok(())
} }
} }

View file

@ -34,9 +34,10 @@ pub struct SignatureVerifyGadget {}
pub struct SignatureVerifyTarget { pub struct SignatureVerifyTarget {
// verifier_data of the SignatureInternalCircuit // verifier_data of the SignatureInternalCircuit
verifier_data_targ: VerifierCircuitTarget, verifier_data_targ: VerifierCircuitTarget,
selector: BoolTarget, // `enabled` determines if the signature verification is enabled
pk: ValueTarget, pub(crate) enabled: BoolTarget,
msg: ValueTarget, pub(crate) pk: ValueTarget,
pub(crate) msg: ValueTarget,
// proof of the SignatureInternalCircuit (=signature::Signature.0) // proof of the SignatureInternalCircuit (=signature::Signature.0)
proof: ProofWithPublicInputsTarget<D>, proof: ProofWithPublicInputsTarget<D>,
} }
@ -56,7 +57,7 @@ impl SignatureVerifyGadget {
impl SignatureVerifyGadget { impl SignatureVerifyGadget {
/// creates the targets and defines the logic of the circuit /// creates the targets and defines the logic of the circuit
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<SignatureVerifyTarget> { pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<SignatureVerifyTarget> {
let selector = builder.add_virtual_bool_target_safe(); let enabled = builder.add_virtual_bool_target_safe();
let common_data = super::signature::VP.0.common.clone(); let common_data = super::signature::VP.0.common.clone();
@ -83,10 +84,10 @@ impl SignatureVerifyGadget {
builder.constant_value(Value(dummy_pi[VALUE_SIZE * 2..].try_into().unwrap())); builder.constant_value(Value(dummy_pi[VALUE_SIZE * 2..].try_into().unwrap()));
// connect the {pk, msg, s} with the proof_targ.public_inputs conditionally // connect the {pk, msg, s} with the proof_targ.public_inputs conditionally
let pk_targ_connect = builder.select_value(selector, pk_targ, pk_targ_dummy); let pk_targ_connect = builder.select_value(enabled, pk_targ, pk_targ_dummy);
let msg_targ_connect = builder.select_value(selector, msg_targ, msg_targ_dummy); let msg_targ_connect = builder.select_value(enabled, msg_targ, msg_targ_dummy);
let s_targ_connect = builder.select_value( let s_targ_connect = builder.select_value(
selector, enabled,
ValueTarget { ValueTarget {
elements: s_targ.elements, elements: s_targ.elements,
}, },
@ -108,7 +109,7 @@ impl SignatureVerifyGadget {
Ok(SignatureVerifyTarget { Ok(SignatureVerifyTarget {
verifier_data_targ, verifier_data_targ,
selector, enabled,
pk: pk_targ, pk: pk_targ,
msg: msg_targ, msg: msg_targ,
proof: proof_targ, proof: proof_targ,
@ -121,12 +122,12 @@ impl SignatureVerifyTarget {
pub fn set_targets( pub fn set_targets(
&self, &self,
pw: &mut PartialWitness<F>, pw: &mut PartialWitness<F>,
selector: bool, enabled: bool,
pk: PublicKey, pk: PublicKey,
msg: Value, msg: Value,
signature: Signature, signature: Signature,
) -> Result<()> { ) -> Result<()> {
pw.set_bool_target(self.selector, selector)?; pw.set_bool_target(self.enabled, enabled)?;
pw.set_target_arr(&self.pk.elements, &pk.0 .0)?; pw.set_target_arr(&self.pk.elements, &pk.0 .0)?;
pw.set_target_arr(&self.msg.elements, &msg.0)?; pw.set_target_arr(&self.msg.elements, &msg.0)?;
@ -134,7 +135,7 @@ impl SignatureVerifyTarget {
let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements); let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements);
let public_inputs: Vec<F> = [pk.0 .0, msg.0, s.0].concat(); let public_inputs: Vec<F> = [pk.0 .0, msg.0, s.0].concat();
if selector { if enabled {
pw.set_proof_with_pis_target( pw.set_proof_with_pis_target(
&self.proof, &self.proof,
&ProofWithPublicInputs { &ProofWithPublicInputs {
@ -220,20 +221,21 @@ pub mod tests {
let mut builder = CircuitBuilder::<F, D>::new(config); let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = SignatureVerifyGadget {}.eval(&mut builder)?; let targets = SignatureVerifyGadget {}.eval(&mut builder)?;
targets.set_targets(&mut pw, true, pk.clone(), msg, sig.clone())?; // selector=true targets.set_targets(&mut pw, true, pk.clone(), msg, sig.clone())?; // enabled=true
// generate proof, and expect it to fail // generate proof, and expect it to fail
let data = builder.build::<C>(); let data = builder.build::<C>();
assert!(data.prove(pw).is_err()); // expect prove to fail assert!(data.prove(pw).is_err()); // expect prove to fail
// build the circuit again, but now disable the selector that disables // build the circuit again, but now disable the selector ('enabled')
// the in-circuit signature verification // that disables the in-circuit signature verification (ie.
// `enabled=false`)
let config = CircuitConfig::standard_recursion_zk_config(); let config = CircuitConfig::standard_recursion_zk_config();
let mut builder = CircuitBuilder::<F, D>::new(config); let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
let targets = SignatureVerifyGadget {}.eval(&mut builder)?; let targets = SignatureVerifyGadget {}.eval(&mut builder)?;
targets.set_targets(&mut pw, false, pk, msg, sig)?; // selector=false targets.set_targets(&mut pw, false, pk, msg, sig)?; // enabled=false
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();

View file

@ -11,7 +11,7 @@ use crate::middleware::{
use super::primitives::signature::{PublicKey, SecretKey, Signature}; use super::primitives::signature::{PublicKey, SecretKey, Signature};
pub struct Signer(SecretKey); pub struct Signer(pub SecretKey);
impl PodSigner for Signer { impl PodSigner for Signer {
fn sign(&mut self, _params: &Params, kvs: &HashMap<Hash, Value>) -> Result<Box<dyn Pod>> { fn sign(&mut self, _params: &Params, kvs: &HashMap<Hash, Value>) -> Result<Box<dyn Pod>> {
@ -34,9 +34,9 @@ impl PodSigner for Signer {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SignedPod { pub struct SignedPod {
id: PodId, pub id: PodId,
signature: Signature, pub signature: Signature,
dict: Dictionary, pub dict: Dictionary,
} }
impl Pod for SignedPod { impl Pod for SignedPod {