Merkleproof verify circuit (#143)

* merkletree: add keypath circuit

* merkletree-circuit: implement proof of existence verification in-circuit

* parametrize max_depth at the tree circuit

* Constrain selectors in-circuit

* implement merketree nonexistence proof circuit, and add edgecase tests

* add non-existence proofs documentation in the mdbook, mv EMPTY->EMPTY_VALUE & NULL->EMPTY_HASH, dependency clean and public exposure methods

* review comments, some extra polishing and add a test that expects wrong proofs to fail

* Add circuit to check only merkleproofs-of-existence

With this, the merkletree_circuit module offers two different circuits:
- `MerkleProofCircuit`: allows to verify both proofs of existence and proofs
non-existence with the same circuit.
- `MerkleProofExistenceCircuit`: allows to verify proofs of existence only.

In this way, if only proofs of existence are needed,
`MerkleProofExistenceCircuit` should be used, which requires less amount
of constraints than `MerkleProofCircuit`.

* Code review

---------

Co-authored-by: Ahmad <root@ahmadafuni.com>
This commit is contained in:
arnaucube 2025-03-18 19:34:01 +01:00 committed by GitHub
parent abce0af675
commit b1689c5b37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 683 additions and 26 deletions

View file

@ -137,6 +137,10 @@ Since leaf positions are deterministic based on the key, the same approach is us
For the current use cases, we don't need to prove that the key exists but the value is different on that leaf, so we only use the option 1.
There are 2 cases to have into account when dealing with non-inclusion proofs:
- case i) the expected leaf does not exist.
- case ii) the expected leaf does exist in the tree, but it has a different `key`.
## Encoding
> TODO: how key-values, nodes, merkle-proofs, ... are encoded.

View file

@ -30,9 +30,9 @@ pub type Proof = Plonky2Proof<F, PoseidonGoldilocksConfig, D>;
pub const HASH_SIZE: usize = 4;
pub const VALUE_SIZE: usize = 4;
pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
pub const EMPTY_VALUE: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]);
pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
pub const EMPTY_HASH: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
pub struct Value(pub [F; VALUE_SIZE]);

View file

@ -110,7 +110,7 @@ pub mod tests {
use super::*;
use crate::constants::MAX_DEPTH;
use crate::frontend;
use crate::middleware::{self, F, NULL};
use crate::middleware::{self, EMPTY_HASH, F};
#[test]
fn test_mock_signed_0() -> Result<()> {
@ -137,7 +137,7 @@ pub mod tests {
assert!(!bad_pod.verify());
let mut bad_pod = pod.clone();
let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(NULL).0 .0));
let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(EMPTY_HASH).0 .0));
let bad_kvs_mt = &bad_pod
.kvs()
.into_iter()

View file

@ -1,13 +1,16 @@
//! Module that implements the MerkleTree specified at
//! https://0xparc.github.io/pod2/merkletree.html .
use anyhow::{anyhow, Result};
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::field::types::Field;
use std::collections::HashMap;
use std::fmt;
use std::iter::IntoIterator;
use crate::backends::counter;
use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, F, NULL};
use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, EMPTY_HASH, F};
// mod merkletree_circuit;
pub use super::merkletree_circuit::*;
/// Implements the MerkleTree specified at
/// https://0xparc.github.io/pod2/merkletree.html
@ -178,8 +181,8 @@ impl MerkleTree {
/// mitigate fake proofs.
pub fn kv_hash(key: &Value, value: Option<Value>) -> Hash {
value
.map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat()))
.unwrap_or(Hash([GoldilocksField(0); 4]))
.map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![F::ONE]].concat()))
.unwrap_or(EMPTY_HASH)
}
impl<'a> IntoIterator for &'a MerkleTree {
@ -209,10 +212,10 @@ pub struct MerkleProof {
// note: currently we don't use the `_existence` field, we would use if we merge the methods
// `verify` and `verify_nonexistence` into a single one
#[allow(unused)]
existence: bool,
siblings: Vec<Hash>,
pub(crate) existence: bool,
pub(crate) siblings: Vec<Hash>,
// other_leaf is used for non-existence proofs
other_leaf: Option<(Value, Value)>,
pub(crate) other_leaf: Option<(Value, Value)>,
}
impl fmt::Display for MerkleProof {
@ -244,11 +247,12 @@ impl MerkleProof {
let path = keypath(max_depth, *key)?;
let mut h = kv_hash(key, value);
for (i, sibling) in self.siblings.iter().enumerate().rev() {
let input: Vec<F> = if path[i] {
let mut input: Vec<F> = if path[i] {
[sibling.0, h.0].concat()
} else {
[h.0, sibling.0].concat()
};
input.push(F::TWO);
h = hash_fields(&input);
}
Ok(h)
@ -302,14 +306,14 @@ impl Node {
}
fn compute_hash(&mut self) -> Hash {
match self {
Self::None => NULL,
Self::None => EMPTY_HASH,
Self::Leaf(l) => l.compute_hash(),
Self::Intermediate(n) => n.compute_hash(),
}
}
fn hash(&self) -> Hash {
match self {
Self::None => NULL,
Self::None => EMPTY_HASH,
Self::Leaf(l) => l.hash(),
Self::Intermediate(n) => n.hash(),
}
@ -360,7 +364,7 @@ impl Node {
}
// adds the leaf at the tree from the current node (self), without computing any hash
fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
pub(crate) fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
counter::count_tree_insert();
if lvl >= max_depth {
@ -472,12 +476,12 @@ impl Intermediate {
}
fn compute_hash(&mut self) -> Hash {
if self.left.clone().is_empty() && self.right.clone().is_empty() {
self.hash = Some(NULL);
return NULL;
self.hash = Some(EMPTY_HASH);
return EMPTY_HASH;
}
let l_hash = self.left.compute_hash();
let r_hash = self.right.compute_hash();
let input: Vec<F> = [l_hash.0, r_hash.0].concat();
let input: Vec<F> = [l_hash.0.to_vec(), r_hash.0.to_vec(), vec![F::TWO]].concat();
let h = hash_fields(&input);
self.hash = Some(h);
h
@ -520,7 +524,7 @@ impl Leaf {
// max-depth? ie, what happens when two keys share the same path for more bits
// than the max_depth?
/// returns the path of the given key
fn keypath(max_depth: usize, k: Value) -> Result<Vec<bool>> {
pub(crate) fn keypath(max_depth: usize, k: Value) -> Result<Vec<bool>> {
let bytes = k.to_bytes();
if max_depth > 8 * bytes.len() {
// note that our current keys are of Value type, which are 4 Goldilocks

View file

@ -0,0 +1,648 @@
//! Circuits compatible with the merkletree.rs implementation. This module
//! offers two different circuits:
//!
//! - `MerkleProofCircuit`: allows to verify both proofs of existence and proofs
//! non-existence with the same circuit.
//! - `MerkleProofExistenceCircuit`: allows to verify proofs of existence only.
//!
//! If only proofs of existence are needed, use `MerkleProofExistenceCircuit`,
//! which requires less amount of constraints than `MerkleProofCircuit`.
//!
use anyhow::Result;
use plonky2::{
field::types::Field,
hash::{
hash_types::{HashOut, HashOutTarget},
poseidon::PoseidonHash,
},
iop::{
target::{BoolTarget, Target},
witness::{PartialWitness, WitnessWrite},
},
plonk::circuit_builder::CircuitBuilder,
};
use crate::backends::plonky2::basetypes::{Hash, Value, D, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE};
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
/// `MerkleProofCircuit` allows to verify both proofs of existence and proofs
/// non-existence with the same circuit.
/// 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>,
}
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);
// 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);
// We have 3 cases for when computing the Leaf's hash:
// - existence: leaf contains the given key & value
// - non-existence:
// - case i) expected leaf does not exist
// - case ii) expected leaf does exist but it has a different key
//
// The following table expresses the options with their in-circuit
// selectors:
// | existence | case_ii | leaf_hash |
// | ----------- | --------- | ---------------------------- |
// | 1 | 0 | H(key, value, 1) |
// | 0 | 0 | EMPTY_HASH |
// | 0 | 1 | H(other_key, other_value, 1) |
// | 1 | 1 | invalid combination |
// First, ensure that both existence & case_ii are not true at the same
// time:
// 1. sum = existence + case_ii_selector
let sum = builder.add(existence.target, case_ii_selector.target);
// 2. sum * (sum-1) == 0
builder.assert_bool(BoolTarget::new_unsafe(sum));
// define the case_i_selector as true when both existence and
// case_ii_selector are false:
let not_existence = builder.not(existence);
let not_case_ii_selector = builder.not(case_ii_selector);
let case_i_selector = builder.and(not_existence, not_case_ii_selector);
// use (key,value) or (other_key, other_value) depending if it's a proof
// 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();
// get leaf's hash for the selected k & v
let h = kv_hash_target(builder, &k, &v);
// if we're in the case i), use leaf_hash=EMPTY_HASH, else use the
// previously computed hash h.
let empty_hash = builder.constant_hash(HashOut::from(EMPTY_HASH.0));
let leaf_hash = HashOutTarget::from_vec(
(0..4)
.map(|j| builder.select(case_i_selector, empty_hash.elements[j], h.elements[j]))
.collect(),
);
// get key's path
let path = keypath_target::<MAX_DEPTH>(builder, &key);
// 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::<MAX_DEPTH>(builder, &path, &leaf_hash, &siblings)?;
Ok(Self {
existence,
root,
siblings,
key,
value,
case_ii_selector,
other_key,
other_value,
})
}
/// assigns the given values to the targets
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,
existence: bool,
root: Hash,
proof: MerkleProof,
key: Value,
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_bool_target(self.existence, existence)?;
// pad siblings with zeros to length MAX_DEPTH
let mut siblings = proof.siblings.clone();
siblings.resize(MAX_DEPTH, EMPTY_HASH);
assert_eq!(self.siblings.len(), siblings.len());
for (i, sibling) in siblings.iter().enumerate() {
pw.set_hash_target(self.siblings[i], HashOut::from_vec(sibling.0.to_vec()))?;
}
match proof.other_leaf {
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)?;
}
_ => {
// 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)?;
}
}
Ok(())
}
}
/// `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>,
}
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);
// siblings are padded till MAX_DEPTH length
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
// get leaf's hash for the selected k & v
let leaf_hash = kv_hash_target(builder, &key, &value);
// get key's path
let path = keypath_target::<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::<MAX_DEPTH>(builder, &path, &leaf_hash, &siblings)?;
Ok(Self {
root,
siblings,
key,
value,
})
}
/// assigns the given values to the targets
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,
root: Hash,
proof: MerkleProof,
key: Value,
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)?;
// pad siblings with zeros to length MAX_DEPTH
let mut siblings = proof.siblings.clone();
siblings.resize(MAX_DEPTH, EMPTY_HASH);
assert_eq!(self.siblings.len(), siblings.len());
for (i, sibling) in siblings.iter().enumerate() {
pw.set_hash_target(self.siblings[i], HashOut::from_vec(sibling.0.to_vec()))?;
}
Ok(())
}
}
fn compute_root_from_leaf<const MAX_DEPTH: usize>(
builder: &mut CircuitBuilder<F, D>,
path: &Vec<BoolTarget>,
leaf_hash: &HashOutTarget,
siblings: &Vec<HashOutTarget>,
) -> Result<HashOutTarget> {
assert_eq!(siblings.len(), MAX_DEPTH);
// Convenience constants
let zero = builder.zero();
let one = builder.one();
let two = builder.two();
// Generate/constrain sibling selectors
let sibling_selectors = siblings
.iter()
.rev()
.scan(zero, |cur_selector, sibling| {
let sibling_is_empty = sibling.elements.iter().fold(builder._true(), |acc, x| {
let x_is_zero = builder.is_equal(*x, zero);
builder.and(acc, x_is_zero)
});
// If there is a sibling, the selector is true, else retain the
// current selector
*cur_selector = builder.select(sibling_is_empty, *cur_selector, one);
Some(BoolTarget::new_unsafe(*cur_selector))
})
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect::<Vec<_>>();
let mut h = leaf_hash.clone();
for (i, (sibling, selector)) in std::iter::zip(siblings, &sibling_selectors)
.enumerate()
.rev()
{
// to compute the hash, we want to do the following 3 steps:
// Let s := path[i], then
// input_1 = sibling * s + h * (1-s) = select(s, sibling, h)
// 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)
.map(|j| builder.select(path[i], sibling.elements[j], h.elements[j]))
.collect();
let input_2: Vec<Target> = (0..4)
.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)
.map(|j| builder.select(*selector, new_h.elements[j], h.elements[j]))
.collect();
h = HashOutTarget::from_vec(h_targ);
}
Ok(h)
}
// Note: this logic is in its own method for easy of reusability but
// specially to be able to test it isolated.
fn keypath_target<const MAX_DEPTH: usize>(
builder: &mut CircuitBuilder<F, D>,
key: &Vec<Target>,
) -> 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
.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);
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);
// and directly get the extra_bits, but the `split_le` method
// returns the wrong bits, so currently we get the entire array of
// bits and crop it at the desired n_extra_bits amount.
} else {
vec![]
};
[path, extra_bits].concat()
}
fn kv_hash_target(
builder: &mut CircuitBuilder<F, D>,
key: &Vec<Target>,
value: &Vec<Target>,
) -> HashOutTarget {
let inputs: Vec<Target> = [key.clone(), value.clone(), vec![builder.one()]].concat();
builder.hash_n_to_hash_no_pad::<PoseidonHash>(inputs)
}
#[cfg(test)]
pub mod tests {
use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig};
use std::collections::HashMap;
use super::*;
use crate::backends::plonky2::basetypes::hash_value;
use crate::backends::plonky2::basetypes::C;
use crate::backends::plonky2::primitives::merkletree::*;
#[test]
fn test_keypath() -> Result<()> {
test_keypath_opt::<10>()?;
test_keypath_opt::<16>()?;
test_keypath_opt::<32>()?;
test_keypath_opt::<40>()?;
test_keypath_opt::<64>()?;
test_keypath_opt::<128>()?;
test_keypath_opt::<130>()?;
test_keypath_opt::<250>()?;
test_keypath_opt::<256>()?;
Ok(())
}
fn test_keypath_opt<const MD: usize>() -> Result<()> {
for i in 0..5 {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let key = Value::from(hash_value(&Value::from(i)));
let expected_path = keypath(MD, key)?;
// small circuit logic to check
// expected_path_targ==keypath_target(key_targ)
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 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)?;
for i in 0..MD {
pw.set_bool_target(expected_path_targ[i], expected_path[i])?;
}
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
}
Ok(())
}
#[test]
fn test_kv_hash() -> Result<()> {
for i in 0..10 {
let key = Value::from(hash_value(&Value::from(i)));
let value = Value::from(1000 + i);
let h = kv_hash(&key, Some(value));
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
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 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_hash_target(h_targ, HashOut::from_vec(h.0.to_vec()))?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
}
Ok(())
}
#[test]
fn test_merkleproof_verify_existence() -> Result<()> {
test_merkleproof_verify_opt::<10>(true)?;
test_merkleproof_verify_opt::<16>(true)?;
test_merkleproof_verify_opt::<32>(true)?;
test_merkleproof_verify_opt::<40>(true)?;
test_merkleproof_verify_opt::<64>(true)?;
test_merkleproof_verify_opt::<128>(true)?;
test_merkleproof_verify_opt::<130>(true)?;
test_merkleproof_verify_opt::<250>(true)?;
test_merkleproof_verify_opt::<256>(true)?;
Ok(())
}
#[test]
fn test_merkleproof_verify_nonexistence() -> Result<()> {
test_merkleproof_verify_opt::<10>(false)?;
test_merkleproof_verify_opt::<16>(false)?;
test_merkleproof_verify_opt::<32>(false)?;
test_merkleproof_verify_opt::<40>(false)?;
test_merkleproof_verify_opt::<64>(false)?;
test_merkleproof_verify_opt::<128>(false)?;
test_merkleproof_verify_opt::<130>(false)?;
test_merkleproof_verify_opt::<250>(false)?;
test_merkleproof_verify_opt::<256>(false)?;
Ok(())
}
// test logic to be reused both by the existence & nonexistence tests
fn test_merkleproof_verify_opt<const MD: usize>(existence: bool) -> Result<()> {
let mut kvs: HashMap<Value, Value> = HashMap::new();
for i in 0..10 {
kvs.insert(Value::from(hash_value(&Value::from(i))), Value::from(i));
}
let tree = MerkleTree::new(MD, &kvs)?;
let (key, value, proof) = if existence {
let key = Value::from(hash_value(&Value::from(5)));
let (value, proof) = tree.prove(&key)?;
assert_eq!(value, Value::from(5));
(key, value, proof)
} else {
let key = Value::from(hash_value(&Value::from(200)));
(key, EMPTY_VALUE, tree.prove_nonexistence(&key)?)
};
assert_eq!(proof.existence, existence);
if existence {
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
} else {
MerkleTree::verify_nonexistence(MD, tree.root(), &proof, &key)?;
}
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofCircuit::<MD>::add_targets(&mut builder)?;
targets.set_targets(&mut pw, existence, tree.root(), proof, key, value)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
#[test]
fn test_merkleproof_only_existence_verify() -> Result<()> {
test_merkleproof_only_existence_verify_opt::<10>()?;
test_merkleproof_only_existence_verify_opt::<16>()?;
test_merkleproof_only_existence_verify_opt::<32>()?;
test_merkleproof_only_existence_verify_opt::<40>()?;
test_merkleproof_only_existence_verify_opt::<64>()?;
test_merkleproof_only_existence_verify_opt::<128>()?;
test_merkleproof_only_existence_verify_opt::<130>()?;
test_merkleproof_only_existence_verify_opt::<250>()?;
test_merkleproof_only_existence_verify_opt::<256>()?;
Ok(())
}
fn test_merkleproof_only_existence_verify_opt<const MD: usize>() -> Result<()> {
let mut kvs: HashMap<Value, Value> = HashMap::new();
for i in 0..10 {
kvs.insert(Value::from(hash_value(&Value::from(i))), Value::from(i));
}
let tree = MerkleTree::new(MD, &kvs)?;
let key = Value::from(hash_value(&Value::from(5)));
let (value, proof) = tree.prove(&key)?;
assert_eq!(value, Value::from(5));
assert_eq!(proof.existence, true);
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofExistenceCircuit::<MD>::add_targets(&mut builder)?;
targets.set_targets(&mut pw, tree.root(), proof, key, value)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
#[test]
fn test_merkletree_edgecases() -> Result<()> {
// fill the tree as in https://0xparc.github.io/pod2/merkletree.html#example-3
//
// root
// / \
// () ()
// / \ /
// 0 2 ()
// \
// ()
// /\
// 5 13
let mut kvs = HashMap::new();
kvs.insert(Value::from(0), Value::from(1000));
kvs.insert(Value::from(2), Value::from(1002));
kvs.insert(Value::from(5), Value::from(1005));
kvs.insert(Value::from(13), Value::from(1013));
const MD: usize = 5;
let tree = MerkleTree::new(MD, &kvs)?;
// existence
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(5))?;
// non-existence case i) expected leaf does not exist
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(1))?;
// non-existence case ii) expected leaf does exist but it has a different 'key'
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(21))?;
Ok(())
}
fn test_merkletree_edgecase_opt<const MD: usize>(tree: &MerkleTree, key: Value) -> Result<()> {
let contains = tree.contains(&key)?;
// generate merkleproof
let (value, proof) = if contains {
tree.prove(&key)?
} else {
let proof = tree.prove_nonexistence(&key)?;
(EMPTY_VALUE, proof)
};
assert_eq!(proof.existence, contains);
// verify the proof (non circuit)
if proof.existence {
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
} else {
MerkleTree::verify_nonexistence(MD, tree.root(), &proof, &key)?;
}
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofCircuit::<MD>::add_targets(&mut builder)?;
targets.set_targets(&mut pw, proof.existence, tree.root(), proof, key, value)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
#[test]
fn test_wrong_witness() -> Result<()> {
let mut kvs: HashMap<Value, Value> = HashMap::new();
for i in 0..10 {
kvs.insert(Value::from(i), Value::from(i));
}
const MD: usize = 16;
let tree = MerkleTree::new(MD, &kvs)?;
let key = Value::from(3);
let (value, proof) = tree.prove(&key)?;
// build another tree with an extra key-value, so that it has a
// different root
kvs.insert(Value::from(100), Value::from(100));
let tree2 = MerkleTree::new(MD, &kvs)?;
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
assert_eq!(
MerkleTree::verify(MD, tree2.root(), &proof, &key, &value)
.unwrap_err()
.to_string(),
"proof of inclusion does not verify"
);
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleProofCircuit::<MD>::add_targets(&mut builder)?;
targets.set_targets(&mut pw, true, tree2.root(), proof, 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());
Ok(())
}
}

View file

@ -1,2 +1,3 @@
pub mod merkletree;
mod merkletree_circuit;
pub mod signature;

View file

@ -35,6 +35,6 @@
/// then the Value, Hash and F types would come from the plonky3 backend.
#[cfg(feature = "backend_plonky2")]
pub use crate::backends::plonky2::basetypes::{
hash_fields, hash_str, hash_value, Hash, Value, EMPTY, F, HASH_SIZE, NULL, SELF_ID_HASH,
VALUE_SIZE,
hash_fields, hash_str, hash_value, Hash, Value, EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE,
SELF_ID_HASH, VALUE_SIZE,
};

View file

@ -8,7 +8,7 @@ use crate::constants::MAX_DEPTH;
#[cfg(feature = "backend_plonky2")]
use crate::backends::plonky2::primitives::merkletree::{Iter as TreeIter, MerkleProof, MerkleTree};
use super::basetypes::{hash_value, Hash, Value, EMPTY};
use super::basetypes::{hash_value, Hash, Value, EMPTY_VALUE};
/// Dictionary: the user original keys and values are hashed to be used in the leaf.
/// leaf.key=hash(original_key)
@ -78,7 +78,7 @@ impl Set {
.iter()
.map(|e| {
let h = hash_value(e);
(Value::from(h), EMPTY)
(Value::from(h), EMPTY_VALUE)
})
.collect();
Ok(Self {
@ -99,7 +99,7 @@ impl Set {
self.mt.prove_nonexistence(value)
}
pub fn verify(root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> {
MerkleTree::verify(MAX_DEPTH, root, proof, value, &EMPTY)
MerkleTree::verify(MAX_DEPTH, root, proof, value, &EMPTY_VALUE)
}
pub fn verify_nonexistence(root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> {
MerkleTree::verify_nonexistence(MAX_DEPTH, root, proof, value)

View file

@ -23,7 +23,7 @@ impl fmt::Display for PodId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == SELF {
write!(f, "self")
} else if self.0 == NULL {
} else if self.0 == EMPTY_HASH {
write!(f, "null")
} else {
write!(f, "{}", self.0)
@ -185,7 +185,7 @@ impl Pod for NonePod {
true
}
fn id(&self) -> PodId {
PodId(NULL)
PodId(EMPTY_HASH)
}
fn pub_statements(&self) -> Vec<Statement> {
Vec::new()