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,7 +23,7 @@ use plonky2::{
};
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::primitives::merkletree::MerkleProof;
@ -37,20 +37,23 @@ pub struct MerkleProofGadget {
pub struct MerkleProofTarget {
max_depth: usize,
pub root: HashOutTarget,
pub key: ValueTarget,
pub value: ValueTarget,
pub existence: BoolTarget,
pub siblings: Vec<HashOutTarget>,
pub case_ii_selector: BoolTarget, // for case ii)
pub other_key: ValueTarget,
pub other_value: ValueTarget,
// `enabled` determines if the merkleproof verification is enabled
pub(crate) enabled: BoolTarget,
pub(crate) root: HashOutTarget,
pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget,
pub(crate) existence: BoolTarget,
pub(crate) siblings: Vec<HashOutTarget>,
pub(crate) case_ii_selector: BoolTarget, // for case ii)
pub(crate) other_key: ValueTarget,
pub(crate) other_value: ValueTarget,
}
impl MerkleProofGadget {
/// creates the targets and defines the logic of the circuit
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 value = builder.add_virtual_value();
// from proof struct:
@ -104,7 +107,7 @@ impl MerkleProofGadget {
// previously computed hash h.
let empty_hash = builder.constant_hash(HashOut::from(EMPTY_HASH.0));
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]))
.collect(),
);
@ -115,12 +118,24 @@ impl MerkleProofGadget {
// compute the root for the given siblings and the computed leaf_hash
// (this is for the three cases (existence, non-existence case i, and
// non-existence case ii).
// This root will be assigned in the `set_targets` method, and it is a
// public input.
let root = compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
let obtained_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 {
max_depth: self.max_depth,
enabled,
existence,
root,
siblings,
@ -138,12 +153,15 @@ impl MerkleProofTarget {
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,
// `enabled` determines if the merkleproof verification is enabled
enabled: bool,
existence: bool,
root: Hash,
proof: MerkleProof,
key: Value,
value: Value,
) -> Result<()> {
pw.set_bool_target(self.enabled, enabled)?;
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.value.elements, &value.0)?;
@ -185,16 +203,19 @@ pub struct MerkleProofExistenceGadget {
pub struct MerkleProofExistenceTarget {
max_depth: usize,
pub root: HashOutTarget,
pub key: ValueTarget,
pub value: ValueTarget,
pub siblings: Vec<HashOutTarget>,
// `enabled` determines if the merkleproof verification is enabled
pub(crate) enabled: BoolTarget,
pub(crate) root: HashOutTarget,
pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget,
pub(crate) siblings: Vec<HashOutTarget>,
}
impl MerkleProofExistenceGadget {
/// creates the targets and defines the logic of the circuit
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 value = builder.add_virtual_value();
// siblings are padded till max_depth length
@ -207,12 +228,24 @@ impl MerkleProofExistenceGadget {
let path = keypath_target(self.max_depth, builder, &key);
// 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
// public input.
let root = compute_root_from_leaf(self.max_depth, builder, &path, &leaf_hash, &siblings)?;
let obtained_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 {
max_depth: self.max_depth,
enabled,
root,
siblings,
key,
@ -226,11 +259,16 @@ impl MerkleProofExistenceTarget {
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,
// `enabled` determines if the merkleproof verification is enabled
enabled: bool,
root: Hash,
proof: MerkleProof,
key: Value,
value: Value,
) -> 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_target_arr(&self.key.elements, &key.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)
// new_h = hash([input_1, input_2])
// 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]))
.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]))
.collect();
let new_h =
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]))
.collect();
h = HashOutTarget::from_vec(h_targ);
@ -487,7 +525,15 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new();
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
let data = builder.build::<C>();
@ -526,7 +572,7 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new();
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
let data = builder.build::<C>();
@ -593,7 +639,15 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new();
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
let data = builder.build::<C>();
@ -634,13 +688,44 @@ pub mod tests {
let mut pw = PartialWitness::<F>::new();
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
// root)
let data = builder.build::<C>();
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(())
}
}

View file

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