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

@ -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()