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:
parent
abce0af675
commit
b1689c5b37
9 changed files with 683 additions and 26 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue