Skip to content

Commit b1689c5

Browse files
arnaucubeax0
andauthored
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 <[email protected]>
1 parent abce0af commit b1689c5

File tree

9 files changed

+683
-26
lines changed

9 files changed

+683
-26
lines changed

book/src/merkletree.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ Since leaf positions are deterministic based on the key, the same approach is us
137137

138138
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.
139139

140+
There are 2 cases to have into account when dealing with non-inclusion proofs:
141+
- case i) the expected leaf does not exist.
142+
- case ii) the expected leaf does exist in the tree, but it has a different `key`.
143+
140144

141145
## Encoding
142146
> TODO: how key-values, nodes, merkle-proofs, ... are encoded.

src/backends/plonky2/basetypes.rs

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

33-
pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
33+
pub const EMPTY_VALUE: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
3434
pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]);
35-
pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
35+
pub const EMPTY_HASH: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
3636

3737
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
3838
pub struct Value(pub [F; VALUE_SIZE]);

src/backends/plonky2/mock_signed.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub mod tests {
110110
use super::*;
111111
use crate::constants::MAX_DEPTH;
112112
use crate::frontend;
113-
use crate::middleware::{self, F, NULL};
113+
use crate::middleware::{self, EMPTY_HASH, F};
114114

115115
#[test]
116116
fn test_mock_signed_0() -> Result<()> {
@@ -137,7 +137,7 @@ pub mod tests {
137137
assert!(!bad_pod.verify());
138138

139139
let mut bad_pod = pod.clone();
140-
let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(NULL).0 .0));
140+
let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(EMPTY_HASH).0 .0));
141141
let bad_kvs_mt = &bad_pod
142142
.kvs()
143143
.into_iter()

src/backends/plonky2/primitives/merkletree.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
//! Module that implements the MerkleTree specified at
22
//! https://0xparc.github.io/pod2/merkletree.html .
33
use anyhow::{anyhow, Result};
4-
use plonky2::field::goldilocks_field::GoldilocksField;
4+
use plonky2::field::types::Field;
55
use std::collections::HashMap;
66
use std::fmt;
77
use std::iter::IntoIterator;
88

99
use crate::backends::counter;
10-
use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, F, NULL};
10+
use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, EMPTY_HASH, F};
11+
12+
// mod merkletree_circuit;
13+
pub use super::merkletree_circuit::*;
1114

1215
/// Implements the MerkleTree specified at
1316
/// https://0xparc.github.io/pod2/merkletree.html
@@ -178,8 +181,8 @@ impl MerkleTree {
178181
/// mitigate fake proofs.
179182
pub fn kv_hash(key: &Value, value: Option<Value>) -> Hash {
180183
value
181-
.map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat()))
182-
.unwrap_or(Hash([GoldilocksField(0); 4]))
184+
.map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![F::ONE]].concat()))
185+
.unwrap_or(EMPTY_HASH)
183186
}
184187

185188
impl<'a> IntoIterator for &'a MerkleTree {
@@ -209,10 +212,10 @@ pub struct MerkleProof {
209212
// note: currently we don't use the `_existence` field, we would use if we merge the methods
210213
// `verify` and `verify_nonexistence` into a single one
211214
#[allow(unused)]
212-
existence: bool,
213-
siblings: Vec<Hash>,
215+
pub(crate) existence: bool,
216+
pub(crate) siblings: Vec<Hash>,
214217
// other_leaf is used for non-existence proofs
215-
other_leaf: Option<(Value, Value)>,
218+
pub(crate) other_leaf: Option<(Value, Value)>,
216219
}
217220

218221
impl fmt::Display for MerkleProof {
@@ -244,11 +247,12 @@ impl MerkleProof {
244247
let path = keypath(max_depth, *key)?;
245248
let mut h = kv_hash(key, value);
246249
for (i, sibling) in self.siblings.iter().enumerate().rev() {
247-
let input: Vec<F> = if path[i] {
250+
let mut input: Vec<F> = if path[i] {
248251
[sibling.0, h.0].concat()
249252
} else {
250253
[h.0, sibling.0].concat()
251254
};
255+
input.push(F::TWO);
252256
h = hash_fields(&input);
253257
}
254258
Ok(h)
@@ -302,14 +306,14 @@ impl Node {
302306
}
303307
fn compute_hash(&mut self) -> Hash {
304308
match self {
305-
Self::None => NULL,
309+
Self::None => EMPTY_HASH,
306310
Self::Leaf(l) => l.compute_hash(),
307311
Self::Intermediate(n) => n.compute_hash(),
308312
}
309313
}
310314
fn hash(&self) -> Hash {
311315
match self {
312-
Self::None => NULL,
316+
Self::None => EMPTY_HASH,
313317
Self::Leaf(l) => l.hash(),
314318
Self::Intermediate(n) => n.hash(),
315319
}
@@ -360,7 +364,7 @@ impl Node {
360364
}
361365

362366
// adds the leaf at the tree from the current node (self), without computing any hash
363-
fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
367+
pub(crate) fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
364368
counter::count_tree_insert();
365369

366370
if lvl >= max_depth {
@@ -472,12 +476,12 @@ impl Intermediate {
472476
}
473477
fn compute_hash(&mut self) -> Hash {
474478
if self.left.clone().is_empty() && self.right.clone().is_empty() {
475-
self.hash = Some(NULL);
476-
return NULL;
479+
self.hash = Some(EMPTY_HASH);
480+
return EMPTY_HASH;
477481
}
478482
let l_hash = self.left.compute_hash();
479483
let r_hash = self.right.compute_hash();
480-
let input: Vec<F> = [l_hash.0, r_hash.0].concat();
484+
let input: Vec<F> = [l_hash.0.to_vec(), r_hash.0.to_vec(), vec![F::TWO]].concat();
481485
let h = hash_fields(&input);
482486
self.hash = Some(h);
483487
h
@@ -520,7 +524,7 @@ impl Leaf {
520524
// max-depth? ie, what happens when two keys share the same path for more bits
521525
// than the max_depth?
522526
/// returns the path of the given key
523-
fn keypath(max_depth: usize, k: Value) -> Result<Vec<bool>> {
527+
pub(crate) fn keypath(max_depth: usize, k: Value) -> Result<Vec<bool>> {
524528
let bytes = k.to_bytes();
525529
if max_depth > 8 * bytes.len() {
526530
// note that our current keys are of Value type, which are 4 Goldilocks

0 commit comments

Comments
 (0)