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:
Eduard S. 2025-03-20 13:32:12 +01:00 committed by GitHub
parent 2a2628ccbf
commit 22db6ce4c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 513 additions and 51 deletions

View file

@ -21,8 +21,12 @@ use plonky2::{
},
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::common::{
CircuitBuilderPod, OperationTarget, StatementTarget, ValueTarget,
};
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
/// `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`,
/// which requires less amount of constraints.
pub struct MerkleProofCircuit<const MAX_DEPTH: usize> {
root: HashOutTarget,
key: Vec<Target>,
value: Vec<Target>,
existence: BoolTarget,
siblings: Vec<HashOutTarget>,
case_ii_selector: BoolTarget, // for case ii)
other_key: Vec<Target>,
other_value: Vec<Target>,
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,
}
impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
/// creates the targets and defines the logic of the circuit
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
// create the targets
let key = builder.add_virtual_targets(VALUE_SIZE);
let value = builder.add_virtual_targets(VALUE_SIZE);
let key = builder.add_virtual_value();
let value = builder.add_virtual_value();
// from proof struct:
let existence = builder.add_virtual_bool_target_safe();
// siblings are padded till MAX_DEPTH length
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
let case_ii_selector = builder.add_virtual_bool_target_safe();
let other_key = builder.add_virtual_targets(VALUE_SIZE);
let other_value = builder.add_virtual_targets(VALUE_SIZE);
let other_key = builder.add_virtual_value();
let other_value = builder.add_virtual_value();
// We have 3 cases for when computing the Leaf's hash:
// - 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:
// k = key * existence + other_key * (1-existence)
// v = value * existence + other_value * (1-existence)
let k: Vec<Target> = (0..4)
.map(|j| builder.select(existence, key[j], other_key[j]))
.collect();
let v: Vec<Target> = (0..4)
.map(|j| builder.select(existence, value[j], other_value[j]))
.collect();
let k = builder.select_value(existence, key, other_key);
let v = builder.select_value(existence, value, other_value);
// get leaf's hash for the selected k & v
let h = kv_hash_target(builder, &k, &v);
@ -139,8 +139,8 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
value: Value,
) -> Result<()> {
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.value, &value.0)?;
pw.set_target_arr(&self.key.elements, &key.0)?;
pw.set_target_arr(&self.value.elements, &value.0)?;
pw.set_bool_target(self.existence, existence)?;
// 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 => {
// 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_target_arr(&self.other_key, &k.0)?;
pw.set_target_arr(&self.other_value, &v.0)?;
pw.set_target_arr(&self.other_key.elements, &k.0)?;
pw.set_target_arr(&self.other_value.elements, &v.0)?;
}
_ => {
// existence & non-existence case i) expected leaf does not exist
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_value, &EMPTY_VALUE.0)?;
pw.set_target_arr(&self.other_key.elements, &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
/// proofs of non-existence are needed, use `MerkleProofCircuit`.
pub struct MerkleProofExistenceCircuit<const MAX_DEPTH: usize> {
root: HashOutTarget,
key: Vec<Target>,
value: Vec<Target>,
siblings: Vec<HashOutTarget>,
pub root: HashOutTarget,
pub key: ValueTarget,
pub value: ValueTarget,
pub siblings: Vec<HashOutTarget>,
}
impl<const MAX_DEPTH: usize> MerkleProofExistenceCircuit<MAX_DEPTH> {
/// creates the targets and defines the logic of the circuit
pub fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
// create the targets
let key = builder.add_virtual_targets(VALUE_SIZE);
let value = builder.add_virtual_targets(VALUE_SIZE);
let key = builder.add_virtual_value();
let value = builder.add_virtual_value();
// siblings are padded till MAX_DEPTH length
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
@ -218,8 +218,8 @@ impl<const MAX_DEPTH: usize> MerkleProofExistenceCircuit<MAX_DEPTH> {
value: Value,
) -> Result<()> {
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.value, &value.0)?;
pw.set_target_arr(&self.key.elements, &key.0)?;
pw.set_target_arr(&self.value.elements, &value.0)?;
// pad siblings with zeros to length MAX_DEPTH
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.
fn keypath_target<const MAX_DEPTH: usize>(
builder: &mut CircuitBuilder<F, D>,
key: &Vec<Target>,
key: &ValueTarget,
) -> Vec<BoolTarget> {
assert_eq!(key.len(), VALUE_SIZE);
let n_complete_field_elems: usize = MAX_DEPTH / F::BITS;
let n_extra_bits: usize = MAX_DEPTH - n_complete_field_elems * F::BITS;
let path: Vec<BoolTarget> = key
.elements
.iter()
.take(n_complete_field_elems)
.flat_map(|e| builder.split_le(*e, F::BITS))
.collect();
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()
// Note: ideally we would do:
// 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(
builder: &mut CircuitBuilder<F, D>,
key: &Vec<Target>,
value: &Vec<Target>,
key: &ValueTarget,
value: &ValueTarget,
) -> 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)
}
@ -371,14 +377,14 @@ pub mod tests {
let expected_path_targ: Vec<BoolTarget> = (0..MD)
.map(|_| builder.add_virtual_bool_target_safe())
.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);
for i in 0..MD {
builder.connect(computed_path_targ[i].target, expected_path_targ[i].target);
}
// 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 {
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 h_targ = builder.add_virtual_hash();
let key_targ = builder.add_virtual_targets(VALUE_SIZE);
let value_targ = builder.add_virtual_targets(VALUE_SIZE);
let key_targ = builder.add_virtual_value();
let value_targ = builder.add_virtual_value();
let computed_h = kv_hash_target(&mut builder, &key_targ, &value_targ);
builder.connect_hashes(computed_h, h_targ);
// assign the input values to the targets
pw.set_target_arr(&key_targ, &key.0)?;
pw.set_target_arr(&value_targ, &value.0)?;
pw.set_target_arr(&key_targ.elements, &key.0)?;
pw.set_target_arr(&value_targ.elements, &value.0)?;
pw.set_hash_target(h_targ, HashOut::from_vec(h.0.to_vec()))?;
// generate & verify proof