From bb865a4fea67ab49bef4ce5f4e74e421a19862dd Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 12 Feb 2025 12:06:40 +0100 Subject: [PATCH] Implement Containers (Dictionary,Set,Array) on top of MerkleTree. And restructure the code. (#55) * Implement Containers (Dictionary,Set,Array) on top of MerkleTree. And restructure the code. - Reorganize the code grouping backends, middleware, frontend, (crypto) primitives. - Add types Dictionary,Set,Array at the middleware layer, so that it can be used both by the backend and frontend. The Dictionary, Set, Array use the merkletree differently as specified at https://github.com/0xPARC/pod2/blob/f2575d1524c9e15a5b688eb444ee3c39df242c81/book/src/values.md#dictionary-array-set - The containers introduce the trait Container, which has the method 'cm()'. At the current version this uses a merkletree under the hood, and the method 'cm' returns the merkle root. - Ideally neither frontend nor backend use the MerkleTree type, and they use the wrappers {Dictionary,Set,Array}. Note that the current commit the MerkleTree is used at the mock-backend to check internal values, but not at the struct types. - updated the spec's merkletree section updating the defined interface - add github ci to run the tests --------- Co-authored-by: Ahmad Afuni Co-authored-by: Eduard S. --- .github/workflows/tests.yml | 21 +++ book/src/merkletree.md | 24 ++-- book/src/values.md | 3 +- src/backends/mock_main.rs | 1 - src/backends/mock_signed.rs | 63 ++++++--- src/{backends.rs => backends/mod.rs} | 0 src/examples.rs | 12 +- src/frontend.rs | 30 ++--- src/lib.rs | 2 +- src/main.rs | 3 - src/middleware/containers.rs | 159 +++++++++++++++++++++++ src/{middleware.rs => middleware/mod.rs} | 11 +- src/{ => primitives}/merkletree.rs | 94 +++++++++----- src/primitives/mod.rs | 1 + 14 files changed, 330 insertions(+), 94 deletions(-) create mode 100644 .github/workflows/tests.yml rename src/{backends.rs => backends/mod.rs} (100%) delete mode 100644 src/main.rs create mode 100644 src/middleware/containers.rs rename src/{middleware.rs => middleware/mod.rs} (99%) rename src/{ => primitives}/merkletree.rs (59%) create mode 100644 src/primitives/mod.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c2ea78b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,21 @@ +name: Rust Tests + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test: + if: github.event.pull_request.draft == false + name: Rust tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Run tests + run: cargo test diff --git a/book/src/merkletree.md b/book/src/merkletree.md index cf5f10e..b58d766 100644 --- a/book/src/merkletree.md +++ b/book/src/merkletree.md @@ -145,28 +145,30 @@ For the current use cases, we don't need to prove that the key exists but the va ```rust impl MerkleTree { - /// builds a new `MerkleTree` where the leaves contain the given key-values - fn new(kvs: HashMap) -> Self; - /// returns the root of the tree - fn root(&self) -> Result; + fn root(&self) -> Hash; + + /// returns the value at the given key + fn get(&self, key: &Value) -> Result; + /// returns a boolean indicating whether the key exists in the tree + fn contains(&self, key: &Value) -> bool; + /// returns a proof of existence, which proves that the given key exists in - /// the tree. It returns the `value` of the leaf at the given `key`, and - /// the `MerkleProof`. - fn prove(&self, key: &Value) -> Result<(Value, MerkleProof)>; - + /// the tree. It returns the `MerkleProof`. + fn prove(&self, key: &Value) -> Result; + /// returns a proof of non-existence, which proves that the given `key` /// does not exist in the tree fn prove_nonexistence(&self, key: &Value) -> Result; - + /// verifies an inclusion proof for the given `key` and `value` fn verify(root: Hash, proof: &MerkleProof, key: &Value, value: &Value) -> Result<()>; - + /// verifies a non-inclusion proof for the given `key`, that is, the given /// `key` does not exist in the tree fn verify_nonexistence(root: Hash, proof: &MerkleProof, key: &Value) -> Result<()>; - + /// returns an iterator over the leaves of the tree fn iter(&self) -> std::collections::hash_map::Iter; } diff --git a/book/src/values.md b/book/src/values.md index 875f1cf..f4e2f5d 100644 --- a/book/src/values.md +++ b/book/src/values.md @@ -51,14 +51,15 @@ The array, set and dictionary types are similar types. While all of them use [a - `leaf.key=hash(original_key)` - `leaf.value=hash(original_value)` - **array**: the elements are placed at the value field of each leaf, and the key field is just the array index (integer) - - `leaf.value=original_value` - `leaf.key=i` + - `leaf.value=original_value` - **set**: the value field of the leaf is unused, and the key contains the hash of the element - `leaf.key=hash(original_value)` - `leaf.value=0` In the three types, the merkletree under the hood allows to prove inclusion & non-inclusion of the particular entry of the {dictionary/array/set} element. +A concrete implementation of dictionary, array, set can be found at [pod2/src/middleware/containers.rs](https://github.com/0xPARC/pod2/blob/main/src/middleware/containers.rs).

diff --git a/src/backends/mock_main.rs b/src/backends/mock_main.rs index cdbab22..371871e 100644 --- a/src/backends/mock_main.rs +++ b/src/backends/mock_main.rs @@ -9,7 +9,6 @@ use plonky2::hash::poseidon::PoseidonHash; use plonky2::plonk::config::Hasher; use std::any::Any; use std::fmt; -use std::io::{self, Write}; pub struct MockProver {} diff --git a/src/backends/mock_signed.rs b/src/backends/mock_signed.rs index 4080025..3c5041f 100644 --- a/src/backends/mock_signed.rs +++ b/src/backends/mock_signed.rs @@ -1,7 +1,8 @@ -use crate::merkletree::MerkleTree; use crate::middleware::{ - hash_str, Hash, Params, PodId, PodSigner, PodType, SignedPod, Value, KEY_SIGNER, KEY_TYPE, + containers::Dictionary, hash_str, Hash, Params, PodId, PodSigner, PodType, SignedPod, Value, + KEY_SIGNER, KEY_TYPE, }; +use crate::primitives::merkletree::MerkleTree; use anyhow::Result; use std::any::Any; use std::collections::HashMap; @@ -17,10 +18,14 @@ impl PodSigner for MockSigner { kvs.insert(hash_str(&KEY_SIGNER), Value(pk_hash.0)); kvs.insert(hash_str(&KEY_TYPE), Value::from(PodType::MockSigned)); - let mt = MerkleTree::new(&kvs); - let id = PodId(mt.root()?); + let dict = Dictionary::new(&kvs); + let id = PodId(dict.commitment()); let signature = format!("{}_signed_by_{}", id, pk_hash); - Ok(Box::new(MockSignedPod { mt, id, signature })) + Ok(Box::new(MockSignedPod { + dict, + id, + signature, + })) } } @@ -28,30 +33,37 @@ impl PodSigner for MockSigner { pub struct MockSignedPod { id: PodId, signature: String, - mt: MerkleTree, + dict: Dictionary, } impl SignedPod for MockSignedPod { fn verify(&self) -> bool { // Verify type - if Some(&Value::from(PodType::MockSigned)) != self.mt.kvs().get(&hash_str(&KEY_TYPE)) { + let value_at_type = match self.dict.get(&hash_str(&KEY_TYPE).into()) { + Ok(v) => v, + Err(_) => return false, + }; + if Value::from(PodType::MockSigned) != value_at_type { return false; } // Verify id - let mt = MerkleTree::new(&self.mt.kvs()); - let id = match mt.root() { - Ok(id) => PodId(id), - Err(_) => return false, - }; + let mt = MerkleTree::new( + &self + .dict + .iter() + .map(|(&k, &v)| (k, v)) + .collect::>(), + ); + let id = PodId(mt.root()); if id != self.id { return false; } // Verify signature - let pk_hash = match self.mt.kvs().get(&hash_str(&KEY_SIGNER)) { - Some(v) => v, - None => return false, + let pk_hash = match self.dict.get(&hash_str(&KEY_SIGNER).into()) { + Ok(v) => v, + Err(_) => return false, }; let signature = format!("{}_signed_by_{}", id, pk_hash); if signature != self.signature { @@ -66,7 +78,10 @@ impl SignedPod for MockSignedPod { } fn kvs(&self) -> HashMap { - self.mt.kvs().clone() + self.dict + .into_iter() + .map(|(&k, &v)| (Hash(k.0), v)) + .collect() } fn into_any(self: Box) -> Box { @@ -108,15 +123,23 @@ pub mod tests { let mut bad_pod = pod.clone(); let mut bad_kvs = bad_pod.kvs(); bad_kvs.insert(hash_str(KEY_SIGNER), Value(PodId(NULL).0 .0)); - let bad_mt = MerkleTree::new(&bad_kvs); - bad_pod.mt = bad_mt; + let bad_kvs_mt = &bad_kvs + .into_iter() + .map(|(k, v)| (Value(k.0), v)) + .collect::>(); + let bad_mt = MerkleTree::new(&bad_kvs_mt); + bad_pod.dict.mt = bad_mt; assert_eq!(bad_pod.verify(), false); let mut bad_pod = pod.clone(); let mut bad_kvs = bad_pod.kvs(); bad_kvs.insert(hash_str(KEY_TYPE), Value::from(0)); - let bad_mt = MerkleTree::new(&bad_kvs); - bad_pod.mt = bad_mt; + let bad_kvs_mt = &bad_kvs + .into_iter() + .map(|(k, v)| (Value(k.0), v)) + .collect::>(); + let bad_mt = MerkleTree::new(&bad_kvs_mt); + bad_pod.dict.mt = bad_mt; assert_eq!(bad_pod.verify(), false); } } diff --git a/src/backends.rs b/src/backends/mod.rs similarity index 100% rename from src/backends.rs rename to src/backends/mod.rs diff --git a/src/examples.rs b/src/examples.rs index 0105ae3..52e0a68 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -1,5 +1,7 @@ -use crate::frontend::{MainPodBuilder, MerkleTree, SignedPod, SignedPodBuilder, Value}; -use crate::middleware::{Params, PodType, KEY_SIGNER, KEY_TYPE}; +use std::collections::HashMap; + +use crate::frontend::{MainPodBuilder, SignedPod, SignedPodBuilder, Value}; +use crate::middleware::{containers::Dictionary, Params, PodType, KEY_SIGNER, KEY_TYPE}; use crate::op; // ZuKYC @@ -22,7 +24,7 @@ pub fn zu_kyc_pod_builder( gov_id: &SignedPod, pay_stub: &SignedPod, ) -> MainPodBuilder { - let sanction_list = Value::MerkleTree(MerkleTree { root: 1 }); + let sanction_list = Value::Dictionary(Dictionary::new(&HashMap::new())); // empty dictionary let now_minus_18y: i64 = 1169909388; let now_minus_1y: i64 = 1706367566; @@ -178,7 +180,7 @@ pub fn great_boy_pod_full_flow() -> MainPodBuilder { alice_friend_pods.push(friend.sign(&mut bob_signer).unwrap()); alice_friend_pods.push(friend.sign(&mut charlie_signer).unwrap()); - let good_boy_issuers_mt = Value::MerkleTree(MerkleTree { root: 33 }); + let good_boy_issuers_dict = Value::Dictionary(Dictionary::new(&HashMap::new())); // empty great_boy_pod_builder( ¶ms, [ @@ -188,7 +190,7 @@ pub fn great_boy_pod_full_flow() -> MainPodBuilder { &charlie_good_boys[1], ], [&alice_friend_pods[0], &alice_friend_pods[1]], - &good_boy_issuers_mt, + &good_boy_issuers_dict, alice, ) } diff --git a/src/frontend.rs b/src/frontend.rs index cae15df..bd32684 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -3,14 +3,15 @@ use anyhow::Result; use itertools::Itertools; -use plonky2::field::types::Field; use std::collections::HashMap; use std::convert::From; use std::fmt; use crate::middleware::{ - self, hash_str, Hash, MainPodInputs, NativeOperation, NativeStatement, Params, PodId, - PodProver, PodSigner, F, SELF, + self, + containers::{Array, Dictionary, Set}, + hash_str, Hash, MainPodInputs, NativeOperation, NativeStatement, Params, PodId, PodProver, + PodSigner, SELF, }; /// This type is just for presentation purposes. @@ -25,16 +26,13 @@ pub enum PodClass { #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Origin(pub PodClass, pub PodId); -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MerkleTree { - pub root: u8, // TODO -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { String(String), Int(i64), - MerkleTree(MerkleTree), + Dictionary(Dictionary), + Set(Set), + Array(Array), } impl From<&str> for Value { @@ -54,13 +52,9 @@ impl From<&Value> for middleware::Value { match v { Value::String(s) => middleware::Value(hash_str(s).0), Value::Int(v) => middleware::Value::from(*v), - // TODO - Value::MerkleTree(mt) => middleware::Value([ - F::from_canonical_u64(mt.root as u64), - F::ZERO, - F::ZERO, - F::ZERO, - ]), + Value::Dictionary(d) => middleware::Value(d.commitment().0), + Value::Set(s) => middleware::Value(s.commitment().0), + Value::Array(a) => middleware::Value(a.commitment().0), } } } @@ -70,7 +64,9 @@ impl fmt::Display for Value { match self { Value::String(s) => write!(f, "\"{}\"", s), Value::Int(v) => write!(f, "{}", v), - Value::MerkleTree(mt) => write!(f, "mt:{}", mt.root), + Value::Dictionary(d) => write!(f, "dict:{}", d.commitment()), + Value::Set(s) => write!(f, "set:{}", s.commitment()), + Value::Array(a) => write!(f, "arr:{}", a.commitment()), } } } diff --git a/src/lib.rs b/src/lib.rs index 4bad04e..95245d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod backends; pub mod frontend; -pub mod merkletree; pub mod middleware; +pub mod primitives; #[cfg(test)] pub mod examples; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/src/middleware/containers.rs b/src/middleware/containers.rs new file mode 100644 index 0000000..43204b4 --- /dev/null +++ b/src/middleware/containers.rs @@ -0,0 +1,159 @@ +/// This file implements the types defined at +/// https://0xparc.github.io/pod2/values.html#dictionary-array-set . +use anyhow::Result; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::plonk::config::Hasher; +use std::collections::HashMap; + +use super::{Hash, Value, EMPTY}; +use crate::primitives::merkletree::{MerkleProof, MerkleTree}; + +/// Dictionary: the user original keys and values are hashed to be used in the leaf. +/// leaf.key=hash(original_key) +/// leaf.value=hash(original_value) +#[derive(Clone, Debug)] +pub struct Dictionary { + // exposed with pub(crate) so that it can be modified at tests + pub(crate) mt: MerkleTree, +} + +impl Dictionary { + pub fn new(kvs: &HashMap) -> Self { + let kvs: HashMap = kvs.into_iter().map(|(&k, &v)| (Value(k.0), v)).collect(); + Self { + mt: MerkleTree::new(&kvs), + } + } + pub fn commitment(&self) -> Hash { + self.mt.root() + } + pub fn get(&self, key: &Value) -> Result { + self.mt.get(key) + } + pub fn prove(&self, key: &Value) -> Result { + self.mt.prove(key) + } + pub fn prove_nonexistence(&self, key: &Value) -> Result { + self.mt.prove_nonexistence(key) + } + pub fn verify(root: Hash, proof: &MerkleProof, key: &Value, value: &Value) -> Result<()> { + MerkleTree::verify(root, proof, key, value) + } + pub fn verify_nonexistence(root: Hash, proof: &MerkleProof, key: &Value) -> Result<()> { + MerkleTree::verify_nonexistence(root, proof, key) + } + pub fn iter(&self) -> std::collections::hash_map::Iter { + self.mt.iter() + } +} +impl<'a> IntoIterator for &'a Dictionary { + type Item = (&'a Value, &'a Value); + type IntoIter = std::collections::hash_map::Iter<'a, Value, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.mt.iter() + } +} + +impl PartialEq for Dictionary { + fn eq(&self, other: &Self) -> bool { + self.mt.root() == other.mt.root() + } +} +impl Eq for Dictionary {} + +/// Set: the value field of the leaf is unused, and the key contains the hash of the element. +/// leaf.key=hash(original_value) +/// leaf.value=0 +#[derive(Clone, Debug)] +pub struct Set { + mt: MerkleTree, +} + +impl Set { + pub fn new(set: &Vec) -> Self { + let kvs: HashMap = set + .into_iter() + .map(|e| { + let h = PoseidonHash::hash_no_pad(&e.0).elements; + (Value(h), EMPTY) + }) + .collect(); + Self { + mt: MerkleTree::new(&kvs), + } + } + pub fn commitment(&self) -> Hash { + self.mt.root() + } + pub fn contains(&self, value: &Value) -> bool { + self.mt.contains(value) + } + pub fn prove(&self, value: &Value) -> Result { + self.mt.prove(value) + } + pub fn prove_nonexistence(&self, value: &Value) -> Result { + self.mt.prove_nonexistence(value) + } + pub fn verify(root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> { + MerkleTree::verify(root, proof, value, &EMPTY) + } + pub fn verify_nonexistence(root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> { + MerkleTree::verify_nonexistence(root, proof, value) + } + pub fn iter(&self) -> std::collections::hash_map::Iter { + self.mt.iter() + } +} + +impl PartialEq for Set { + fn eq(&self, other: &Self) -> bool { + self.mt.root() == other.mt.root() + } +} +impl Eq for Set {} + +/// Array: the elements are placed at the value field of each leaf, and the key field is just the +/// array index (integer). +/// leaf.key=i +/// leaf.value=original_value +#[derive(Clone, Debug)] +pub struct Array { + mt: MerkleTree, +} + +impl Array { + pub fn new(array: &Vec) -> Self { + let kvs: HashMap = array + .into_iter() + .enumerate() + .map(|(i, &e)| (Value::from(i as i64), e)) + .collect(); + + Self { + mt: MerkleTree::new(&kvs), + } + } + pub fn commitment(&self) -> Hash { + self.mt.root() + } + pub fn get(&self, i: usize) -> Result { + self.mt.get(&Value::from(i as i64)) + } + pub fn prove(&self, i: usize) -> Result { + self.mt.prove(&Value::from(i as i64)) + } + pub fn verify(root: Hash, proof: &MerkleProof, i: usize, value: &Value) -> Result<()> { + MerkleTree::verify(root, proof, &Value::from(i as i64), value) + } + pub fn iter(&self) -> std::collections::hash_map::Iter { + self.mt.iter() + } +} + +impl PartialEq for Array { + fn eq(&self, other: &Self) -> bool { + self.mt.root() == other.mt.root() + } +} +impl Eq for Array {} diff --git a/src/middleware.rs b/src/middleware/mod.rs similarity index 99% rename from src/middleware.rs rename to src/middleware/mod.rs index 809d5df..eb2cb46 100644 --- a/src/middleware.rs +++ b/src/middleware/mod.rs @@ -12,9 +12,11 @@ use plonky2::plonk::config::{Hasher, PoseidonGoldilocksConfig}; use std::any::Any; use std::cmp::{Ord, Ordering}; use std::collections::HashMap; -use std::{array, fmt}; +use std::fmt; use strum_macros::FromRepr; +pub mod containers; + pub const KEY_SIGNER: &str = "_signer"; pub const KEY_TYPE: &str = "_type"; pub const STATEMENT_ARG_F_LEN: usize = 8; @@ -57,6 +59,12 @@ impl From for Value { } } +impl From for Value { + fn from(h: Hash) -> Self { + Value(h.0) + } +} + impl TryInto for Value { type Error = Error; fn try_into(self) -> std::result::Result { @@ -109,6 +117,7 @@ impl PartialOrd for Hash { } } +pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); impl fmt::Display for Hash { diff --git a/src/merkletree.rs b/src/primitives/merkletree.rs similarity index 59% rename from src/merkletree.rs rename to src/primitives/merkletree.rs index 8d6f87e..8a393af 100644 --- a/src/merkletree.rs +++ b/src/primitives/merkletree.rs @@ -20,19 +20,22 @@ use crate::middleware::{Hash, Value, C, D, F}; const CAP_HEIGHT: usize = 0; -/// MerkleTree currently implements the MerkleTree interface with a wrapper on top of Plonky2's -/// MerkleTree. A future iteration will replace it by the MerkleTree specified at -/// https://0xparc.github.io/pod2/merkletree.html . +/// MerkleTree currently is a wrapper on top of Plonky2's MerkleTree. A future iteration will +/// replace it by the MerkleTree specified at https://0xparc.github.io/pod2/merkletree.html . #[derive(Clone, Debug)] pub struct MerkleTree { tree: PlonkyMerkleTree>::Hasher>, // keyindex: key -> index mapping. This is just for the current plonky-tree wrapper - keyindex: HashMap, + keyindex: HashMap, // kvs are a field in the MerkleTree in order to be able to iterate over the keyvalues. This is // specific of the current implementation (Plonky2's tree wrapper), in the next iteration this // will not be needed since the tree implementation itself will offer the hashmap // functionality. - kvs: HashMap, + pub kvs: HashMap, + // leaves_map is a map between the leaf (leaf=Hash(key,value)) and the actual (key, value). It + // is used to get the actual value from a leaf for a given key (through the method + // `MerkleTree.get`. + leaves_map: HashMap, } pub struct MerkleProof { @@ -42,9 +45,11 @@ pub struct MerkleProof { } impl MerkleTree { - pub fn new(kvs: &HashMap) -> Self { - let mut keyindex: HashMap = HashMap::new(); + /// builds a new `MerkleTree` where the leaves contain the given key-values + pub fn new(kvs: &HashMap) -> Self { + let mut keyindex: HashMap = HashMap::new(); let mut leaves: Vec> = Vec::new(); + let mut leaves_map: HashMap = HashMap::new(); // Note: current version iterates sorting by keys of the kvs, but the merkletree defined at // https://0xparc.github.io/pod2/merkletree.html will not need it since it will be // deterministic based on the keys values not on the order of the keys when added into the @@ -54,6 +59,7 @@ impl MerkleTree { let leaf = PoseidonHash::hash_no_pad(&input).elements; leaves.push(leaf.into()); keyindex.insert(*k, i); + leaves_map.insert(Hash(leaf), (*k, *v)); } // pad to a power of two if needed @@ -67,17 +73,40 @@ impl MerkleTree { tree, keyindex, kvs: kvs.clone(), + leaves_map, } } +} - pub fn root(&self) -> Result { +impl MerkleTree { + /// returns the root of the tree + pub fn root(&self) -> Hash { if self.tree.cap.is_empty() { - return Err(anyhow!("empty tree")); + return crate::middleware::NULL; } - Ok(Hash(self.tree.cap.0[0].elements)) + Hash(self.tree.cap.0[0].elements) } - pub fn prove(&self, key: &Hash) -> Result { + /// returns the value at the given key + pub fn get(&self, key: &Value) -> Result { + let i = self.keyindex.get(&key).ok_or(anyhow!("key not in tree"))?; + let leaf_hash_raw = self.tree.get(*i); + let leaf_hash_f: [F; 4] = leaf_hash_raw + .try_into() + .map_err(|_| anyhow!("unexpected length (len!=4)"))?; + let leaf_hash: Hash = Hash(leaf_hash_f); + let (_, value) = self.leaves_map.get(&leaf_hash).unwrap(); + Ok(*value) + } + + /// returns a boolean indicating whether the key exists in the tree + pub fn contains(&self, key: &Value) -> bool { + self.keyindex.get(&key).is_some() + } + + /// returns a proof of existence, which proves that the given key exists in + /// the tree. It returns the `MerkleProof`. + pub fn prove(&self, key: &Value) -> Result { let i = self.keyindex.get(&key).ok_or(anyhow!("key not in tree"))?; let proof = self.tree.prove(*i); Ok(MerkleProof { @@ -87,7 +116,9 @@ impl MerkleTree { }) } - pub fn prove_nonexistence(&self, _key: &Hash) -> Result { + /// returns a proof of non-existence, which proves that the given `key` + /// does not exist in the tree + pub fn prove_nonexistence(&self, _key: &Value) -> Result { // mock method println!("WARNING: MerkleTree::verify_nonexistence is currently a mock"); Ok(MerkleProof { @@ -97,7 +128,8 @@ impl MerkleTree { }) } - pub fn verify(root: Hash, proof: &MerkleProof, key: &Hash, value: &Value) -> Result<()> { + /// verifies an inclusion proof for the given `key` and `value` + pub fn verify(root: Hash, proof: &MerkleProof, key: &Value, value: &Value) -> Result<()> { if !proof.existence { return Err(anyhow!( "expected proof of existence, found proof of non-existence" @@ -108,12 +140,9 @@ impl MerkleTree { verify_merkle_proof(leaf.into(), proof.index, root, &proof.proof) } - pub fn verify_nonexistence( - _root: Hash, - proof: &MerkleProof, - _key: &Hash, - _value: &Value, - ) -> Result<()> { + /// verifies a non-inclusion proof for the given `key`, that is, the given + /// `key` does not exist in the tree + pub fn verify_nonexistence(_root: Hash, proof: &MerkleProof, _key: &Value) -> Result<()> { // mock method if proof.existence { return Err(anyhow!( @@ -124,18 +153,15 @@ impl MerkleTree { Ok(()) } - pub fn iter(&self) -> std::collections::hash_map::Iter { + /// returns an iterator over the leaves of the tree + pub fn iter(&self) -> std::collections::hash_map::Iter { self.kvs.iter() } - - pub fn kvs(&self) -> &HashMap { - &self.kvs - } } impl<'a> IntoIterator for &'a MerkleTree { - type Item = (&'a Hash, &'a Value); - type IntoIter = std::collections::hash_map::Iter<'a, Hash, Value>; + type Item = (&'a Value, &'a Value); + type IntoIter = std::collections::hash_map::Iter<'a, Value, Value>; fn into_iter(self) -> Self::IntoIter { self.kvs.iter() @@ -151,15 +177,15 @@ pub mod tests { #[test] fn test_merkletree() -> Result<()> { let (k0, v0) = ( - hash_str("key_0".into()), + Value(hash_str("key_0".into()).0), Value(hash_str("value_0".into()).0), ); let (k1, v1) = ( - hash_str("key_1".into()), + Value(hash_str("key_1".into()).0), Value(hash_str("value_1".into()).0), ); let (k2, v2) = ( - hash_str("key_2".into()), + Value(hash_str("key_2".into()).0), Value(hash_str("value_2".into()).0), ); @@ -171,18 +197,18 @@ pub mod tests { let tree = MerkleTree::new(&kvs); let proof = tree.prove(&k2)?; - MerkleTree::verify(tree.root()?, &proof, &k2, &v2)?; + MerkleTree::verify(tree.root(), &proof, &k2, &v2)?; // expect verification to fail with different key / value - assert!(MerkleTree::verify(tree.root()?, &proof, &k2, &v0).is_err()); - assert!(MerkleTree::verify(tree.root()?, &proof, &k0, &v2).is_err()); + assert!(MerkleTree::verify(tree.root(), &proof, &k2, &v0).is_err()); + assert!(MerkleTree::verify(tree.root(), &proof, &k0, &v2).is_err()); // non-existence proofs let proof_ne = tree.prove_nonexistence(&k2)?; - let _ = MerkleTree::verify_nonexistence(tree.root()?, &proof_ne, &k2, &v2)?; + let _ = MerkleTree::verify_nonexistence(tree.root(), &proof_ne, &k2)?; // expect verification of existence fail for nonexistence proof - let _ = MerkleTree::verify(tree.root()?, &proof_ne, &k2, &v2).is_err(); + let _ = MerkleTree::verify(tree.root(), &proof_ne, &k2, &v2).is_err(); Ok(()) } diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 0000000..ad3ec6a --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1 @@ +pub mod merkletree;