Skip to content

Commit 5ecf161

Browse files
committed
implement merketree nonexistence proof circuit, and add edgecase tests
1 parent f30728d commit 5ecf161

File tree

3 files changed

+216
-65
lines changed

3 files changed

+216
-65
lines changed

src/backends/plonky2/basetypes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub type Proof = Plonky2Proof<F, PoseidonGoldilocksConfig, D>;
3030
pub const HASH_SIZE: usize = 4;
3131
pub const VALUE_SIZE: usize = 4;
3232

33+
// TODO mv `EMPTY`-> `EMTPY_VALUE`, and `NULL` -> `EMPTY_HASH`.
3334
pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
3435
pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]);
3536
pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);

src/backends/plonky2/primitives/merkletree.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl MerkleTree {
182182
pub fn kv_hash(key: &Value, value: Option<Value>) -> Hash {
183183
value
184184
.map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat()))
185-
.unwrap_or(Hash([GoldilocksField(0); 4]))
185+
.unwrap_or(NULL)
186186
}
187187

188188
impl<'a> IntoIterator for &'a MerkleTree {

src/backends/plonky2/primitives/merkletree_circuit.rs

Lines changed: 214 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use std::iter::IntoIterator;
2424

2525
use crate::backends::counter;
2626
use crate::backends::plonky2::basetypes::{
27-
hash_fields, Hash, Value, D, F, HASH_SIZE, NULL, VALUE_SIZE,
27+
hash_fields, Hash, Value, D, EMPTY, F, HASH_SIZE, NULL, VALUE_SIZE,
2828
};
2929
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
3030

@@ -34,6 +34,10 @@ pub struct MerkleProofCircuit<const MAX_DEPTH: usize> {
3434
value: Vec<Target>,
3535
existence: BoolTarget,
3636
siblings: Vec<HashOutTarget>,
37+
// siblings_selectors: Vec<BoolTarget>,
38+
case_ii_selector: BoolTarget, // for case ii)
39+
other_key: Vec<Target>,
40+
other_value: Vec<Target>,
3741
}
3842

3943
impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
@@ -48,22 +52,94 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
4852
// siblings are padded
4953
let siblings = builder.add_virtual_hashes(MAX_DEPTH);
5054

51-
let h = Self::compute_root_from_leaf(builder, &key, &value, &siblings)?;
52-
builder.connect_hashes(h, root);
55+
let case_ii_selector = builder.add_virtual_bool_target_safe();
56+
let other_key = builder.add_virtual_targets(VALUE_SIZE);
57+
let other_value = builder.add_virtual_targets(VALUE_SIZE);
58+
59+
// We have 3 cases for when computing the Leaf's hash:
60+
// - existence: leaf contains the given key & value
61+
// - non-existence:
62+
// - case i) expected leaf does not exist
63+
// - case ii) expected leaf does exist but it has a different key
64+
//
65+
// The following table expresses the options with their in-circuit
66+
// selectors:
67+
// | existence | case_ii | leaf_hash |
68+
// | ----------- | --------- | ---------------------------- |
69+
// | 1 | 0 | H(key, value, 1) |
70+
// | 0 | 0 | EMPTY_HASH |
71+
// | 0 | 1 | H(other_key, other_value, 1) |
72+
// | 1 | 1 | invalid combination |
73+
74+
// First, ensure that both existence & case_ii are not true at the same
75+
// time:
76+
// 1. sum = existence + case_ii_selector
77+
let sum = builder.add(existence.target, case_ii_selector.target);
78+
// 2. sum * (sum-1) == 0
79+
builder.assert_bool(BoolTarget::new_unsafe(sum));
80+
81+
// define the case_i_selector as true when both existence and
82+
// case_ii_selector are false:
83+
// case_i_selector = (1 - existence) * (1- case_ii_selector)
84+
let one = builder.one();
85+
let existence_inv = builder.sub(one, existence.target);
86+
let case_ii_inv = builder.sub(one, case_ii_selector.target);
87+
let case_i_selector = BoolTarget::new_unsafe(builder.mul(existence_inv, case_ii_inv));
88+
89+
// use (key,value) or (other_key, other_value) depending if it's a proof
90+
// of existence or of non-existence, ie:
91+
// k = key * existence + other_key * (1-existence)
92+
// v = value * existence + other_value * (1-existence)
93+
let k: Vec<Target> = (0..4)
94+
.map(|j| builder.select(existence, key[j], other_key[j]))
95+
.collect();
96+
let v: Vec<Target> = (0..4)
97+
.map(|j| builder.select(existence, value[j], other_value[j]))
98+
.collect();
99+
100+
// get leaf's hash for the selected k & v
101+
let h = Self::kv_hash_target(builder, &k, &v);
102+
103+
// if we're in the case i), use leaf_hash=EMPTY_HASH, else use the
104+
// previously computed hash h.
105+
let empty_hash = builder.constant_hash(HashOut::from(NULL.0));
106+
let leaf_hash = HashOutTarget::from_vec(
107+
(0..4)
108+
.map(|j| builder.select(case_i_selector, empty_hash.elements[j], h.elements[j]))
109+
.collect(),
110+
);
111+
112+
// get key's path
113+
let path = Self::keypath_target(builder, &key);
114+
115+
// compute the root for the given siblings and the computed leaf_hash
116+
// (this is for the three cases (existence, non-existence case i, and
117+
// non-existence case ii)
118+
let computed_root = Self::compute_root_from_leaf(
119+
builder, &path, &leaf_hash, &siblings,
120+
// &siblings_selectors,
121+
)?;
122+
// ensure that the computed root matches the given root (which is a
123+
// public input)
124+
builder.connect_hashes(computed_root, root);
53125

54126
Ok(Self {
55127
existence,
56128
root,
57129
siblings,
58130
key,
59131
value,
132+
case_ii_selector,
133+
other_key,
134+
other_value,
60135
})
61136
}
62137

63138
/// assigns the given values to the targets
64139
fn set_targets(
65140
&self,
66141
pw: &mut PartialWitness<F>,
142+
existence: bool,
67143
root: Hash,
68144
proof: MerkleProof,
69145
key: Value,
@@ -72,7 +148,7 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
72148
pw.set_hash_target(self.root, HashOut::from_vec(root.0.to_vec()))?;
73149
pw.set_target_arr(&self.key, &key.0.to_vec())?;
74150
pw.set_target_arr(&self.value, &value.0.to_vec())?;
75-
pw.set_bool_target(self.existence, proof.existence)?;
151+
pw.set_bool_target(self.existence, existence)?;
76152

77153
// pad siblings with zeros to length MAX_DEPTH
78154
let mut siblings = proof.siblings.clone();
@@ -83,13 +159,25 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
83159
pw.set_hash_target(self.siblings[i], HashOut::from_vec(sibling.0.to_vec()));
84160
}
85161

162+
if !existence && proof.other_leaf.is_some() {
163+
// non-existence case ii) expected leaf does exist but it has a different key
164+
pw.set_bool_target(self.case_ii_selector, true)?;
165+
pw.set_target_arr(&self.other_key, &proof.other_leaf.unwrap().0 .0.to_vec())?;
166+
pw.set_target_arr(&self.other_value, &proof.other_leaf.unwrap().1 .0.to_vec())?;
167+
} else {
168+
// existence & non-existence case i) expected leaf does not exist
169+
pw.set_bool_target(self.case_ii_selector, false)?;
170+
pw.set_target_arr(&self.other_key, &EMPTY.0.to_vec())?;
171+
pw.set_target_arr(&self.other_value, &EMPTY.0.to_vec())?;
172+
}
173+
86174
Ok(())
87175
}
88176

89177
fn compute_root_from_leaf(
90178
builder: &mut CircuitBuilder<F, D>,
91-
key: &Vec<Target>,
92-
value: &Vec<Target>,
179+
path: &Vec<BoolTarget>,
180+
leaf_hash: &HashOutTarget,
93181
siblings: &Vec<HashOutTarget>,
94182
) -> Result<HashOutTarget> {
95183
assert!(siblings.len() <= MAX_DEPTH);
@@ -116,58 +204,31 @@ impl<const MAX_DEPTH: usize> MerkleProofCircuit<MAX_DEPTH> {
116204
.rev()
117205
.collect::<Vec<_>>();
118206

119-
// get key's path
120-
let path = Self::keypath_target(builder, key);
121-
// get leaf's hash
122-
let mut h = Self::kv_hash_target(builder, key, value);
123-
207+
let mut h = leaf_hash.clone();
124208
let one: Target = builder.one(); // constant in-circuit
125209
for (i, (sibling, selector)) in std::iter::zip(siblings, &sibling_selectors)
126210
.enumerate()
127211
.rev()
128212
{
129213
// to compute the hash, we want to do the following 3 steps:
130214
// Let s := path[i], then
131-
// input_1 = sibling * s + h * (1-s)
132-
// input_2 = sibling * (1-s) + h * s
215+
// input_1 = sibling * s + h * (1-s) = select(s, sibling, h)
216+
// input_2 = sibling * (1-s) + h * s = select(s, h, sibling)
133217
// new_h = hash([input_1, input_2])
134-
135-
// TODO group multiple muls in a single gate
136-
let bit: Target = path[i].target;
137-
let bit_inv: Target = builder.sub(one, bit);
138-
139-
let input_1_sibling: Vec<Target> = sibling
140-
.elements
141-
.iter()
142-
.map(|e| builder.mul(*e, bit))
143-
.collect();
144-
let input_1_h: Vec<Target> = h
145-
.elements
146-
.iter()
147-
.map(|e| builder.mul(*e, bit_inv))
148-
.collect();
218+
// TODO explore if to group multiple muls in a single gate
219+
let bit: BoolTarget = path[i];
149220
let input_1: Vec<Target> = (0..4)
150-
.map(|j| builder.add(input_1_sibling[j], input_1_h[j]))
221+
.map(|j| builder.select(bit, sibling.elements[j], h.elements[j]))
151222
.collect();
152-
153-
let input_2_sibling: Vec<Target> = sibling
154-
.elements
155-
.iter()
156-
.map(|e| builder.mul(*e, bit_inv))
157-
.collect();
158-
let input_2_h: Vec<Target> = h.elements.iter().map(|e| builder.mul(*e, bit)).collect();
159223
let input_2: Vec<Target> = (0..4)
160-
.map(|j| builder.add(input_2_sibling[j], input_2_h[j]))
224+
.map(|j| builder.select(bit, h.elements[j], sibling.elements[j]))
161225
.collect();
162226

163227
let new_h = builder.hash_n_to_hash_no_pad::<PoseidonHash>([input_1, input_2].concat());
164228

165-
// Let s := sibling_selectors[i], then h = new_h * s + h * (1-s)
166-
let s: Target = selector.target;
167-
let s_inv: Target = builder.sub(one, s);
168-
let new_h_s: Vec<Target> = new_h.elements.iter().map(|e| builder.mul(*e, s)).collect();
169-
let h_s: Vec<Target> = h.elements.iter().map(|e| builder.mul(*e, s_inv)).collect();
170-
let h_targ = (0..4).map(|j| builder.add(new_h_s[j], h_s[j])).collect();
229+
let h_targ: Vec<Target> = (0..4)
230+
.map(|j| builder.select(*selector, new_h.elements[j], h.elements[j]))
231+
.collect();
171232
h = HashOutTarget::from_vec(h_targ);
172233
}
173234
Ok(h)
@@ -305,43 +366,132 @@ pub mod tests {
305366
}
306367

307368
#[test]
308-
fn test_merkleproof_verify() -> Result<()> {
309-
test_merkleproof_verify_opt::<10>()?;
310-
test_merkleproof_verify_opt::<16>()?;
311-
test_merkleproof_verify_opt::<32>()?;
312-
test_merkleproof_verify_opt::<40>()?;
313-
test_merkleproof_verify_opt::<64>()?;
314-
test_merkleproof_verify_opt::<128>()?;
315-
test_merkleproof_verify_opt::<130>()?;
316-
test_merkleproof_verify_opt::<250>()?;
317-
test_merkleproof_verify_opt::<256>()?;
369+
fn test_merkleproof_verify_existence() -> Result<()> {
370+
test_merkleproof_verify_opt::<10>(true)?;
371+
test_merkleproof_verify_opt::<16>(true)?;
372+
test_merkleproof_verify_opt::<32>(true)?;
373+
test_merkleproof_verify_opt::<40>(true)?;
374+
test_merkleproof_verify_opt::<64>(true)?;
375+
test_merkleproof_verify_opt::<128>(true)?;
376+
test_merkleproof_verify_opt::<130>(true)?;
377+
test_merkleproof_verify_opt::<250>(true)?;
378+
test_merkleproof_verify_opt::<256>(true)?;
379+
Ok(())
380+
}
381+
#[test]
382+
fn test_merkleproof_verify_nonexistence() -> Result<()> {
383+
test_merkleproof_verify_opt::<10>(false)?;
384+
test_merkleproof_verify_opt::<16>(false)?;
385+
test_merkleproof_verify_opt::<32>(false)?;
386+
test_merkleproof_verify_opt::<40>(false)?;
387+
test_merkleproof_verify_opt::<64>(false)?;
388+
test_merkleproof_verify_opt::<128>(false)?;
389+
test_merkleproof_verify_opt::<130>(false)?;
390+
test_merkleproof_verify_opt::<250>(false)?;
391+
test_merkleproof_verify_opt::<256>(false)?;
318392
Ok(())
319393
}
320394

321-
fn test_merkleproof_verify_opt<const MD: usize>() -> Result<()> {
395+
// test logic to be reused both by the existence & nonexistence tests
396+
fn test_merkleproof_verify_opt<const MD: usize>(existence: bool) -> Result<()> {
322397
let mut kvs: HashMap<Value, Value> = HashMap::new();
323-
for i in 0..8 {
324-
kvs.insert(
325-
Value::from(hash_value(&Value::from(i))),
326-
Value::from(1000 + i),
327-
);
398+
for i in 0..10 {
399+
kvs.insert(Value::from(hash_value(&Value::from(i))), Value::from(i));
328400
}
329401

330402
let tree = MerkleTree::new(MD, &kvs)?;
331403

332-
let key = Value::from(hash_value(&Value::from(5)));
333-
let (value, proof) = tree.prove(&key)?;
334-
assert_eq!(value, Value::from(1005));
404+
let (key, value, proof) = if existence {
405+
let key = Value::from(hash_value(&Value::from(5)));
406+
let (value, proof) = tree.prove(&key)?;
407+
assert_eq!(value, Value::from(5));
408+
(key, value, proof)
409+
} else {
410+
let key = Value::from(hash_value(&Value::from(200)));
411+
(key, EMPTY, tree.prove_nonexistence(&key)?)
412+
};
413+
assert_eq!(proof.existence, existence);
335414

336-
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
415+
if existence {
416+
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
417+
} else {
418+
MerkleTree::verify_nonexistence(MD, tree.root(), &proof, &key)?;
419+
}
420+
421+
// circuit
422+
let config = CircuitConfig::standard_recursion_config();
423+
let mut builder = CircuitBuilder::<F, D>::new(config);
424+
let mut pw = PartialWitness::<F>::new();
425+
426+
let targets = MerkleProofCircuit::<MD>::add_targets(&mut builder)?;
427+
targets.set_targets(&mut pw, existence, tree.root(), proof, key, value)?;
428+
429+
// generate & verify proof
430+
let data = builder.build::<C>();
431+
let proof = data.prove(pw)?;
432+
data.verify(proof)?;
433+
434+
Ok(())
435+
}
436+
437+
#[test]
438+
fn test_merkletree_edgecases() -> Result<()> {
439+
// fill the tree as in https://0xparc.github.io/pod2/merkletree.html#example-3
440+
//
441+
// root
442+
// / \
443+
// () ()
444+
// / \ /
445+
// 0 2 ()
446+
// \
447+
// ()
448+
// /\
449+
// 5 13
450+
451+
let mut kvs = HashMap::new();
452+
kvs.insert(Value::from(0), Value::from(1000));
453+
kvs.insert(Value::from(2), Value::from(1002));
454+
kvs.insert(Value::from(5), Value::from(1005));
455+
kvs.insert(Value::from(13), Value::from(1013));
456+
457+
const MD: usize = 5;
458+
let tree = MerkleTree::new(MD, &kvs)?;
459+
// existence
460+
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(5))?;
461+
// non-existence case i) expected leaf does not exist
462+
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(1))?;
463+
// non-existence case ii) expected leaf does exist but it has a different 'key'
464+
test_merkletree_edgecase_opt::<MD>(&tree, Value::from(21))?;
465+
466+
Ok(())
467+
}
468+
469+
fn test_merkletree_edgecase_opt<const MD: usize>(tree: &MerkleTree, key: Value) -> Result<()> {
470+
let contains = tree.contains(&key)?;
471+
// generate merkleproof
472+
let (value, proof) = if contains {
473+
tree.prove(&key)?
474+
} else {
475+
let proof = tree.prove_nonexistence(&key)?;
476+
(EMPTY, proof)
477+
};
478+
479+
assert_eq!(proof.existence, contains);
480+
481+
// verify the proof (non circuit)
482+
if proof.existence {
483+
MerkleTree::verify(MD, tree.root(), &proof, &key, &value)?;
484+
} else {
485+
MerkleTree::verify_nonexistence(MD, tree.root(), &proof, &key)?;
486+
}
337487

338488
// circuit
339489
let config = CircuitConfig::standard_recursion_config();
340490
let mut builder = CircuitBuilder::<F, D>::new(config);
341491
let mut pw = PartialWitness::<F>::new();
342492

343493
let targets = MerkleProofCircuit::<MD>::add_targets(&mut builder)?;
344-
targets.set_targets(&mut pw, tree.root(), proof, key, value)?;
494+
targets.set_targets(&mut pw, proof.existence, tree.root(), proof, key, value)?;
345495

346496
// generate & verify proof
347497
let data = builder.build::<C>();

0 commit comments

Comments
 (0)