From c1e7b2024ca3f480778cff0dd1551d9a1bdced68 Mon Sep 17 00:00:00 2001 From: Devin Turner <5410284+l1h3r@users.noreply.github.com> Date: Fri, 22 Jan 2021 17:16:58 +0000 Subject: [PATCH] Add Verification Method Merkle Tree (#111) * Add generic Merkle tree * Disable import merging --- identity-core/Cargo.toml | 1 + identity-core/examples/merkle_tree.rs | 73 +++++ .../src/crypto/merkle_tree/consts.rs | 9 + .../src/crypto/merkle_tree/digest.rs | 30 +++ identity-core/src/crypto/merkle_tree/hash.rs | 104 +++++++ identity-core/src/crypto/merkle_tree/math.rs | 47 ++++ .../src/crypto/merkle_tree/merkle.rs | 253 ++++++++++++++++++ identity-core/src/crypto/merkle_tree/mod.rs | 22 ++ identity-core/src/crypto/merkle_tree/node.rs | 74 +++++ identity-core/src/crypto/merkle_tree/proof.rs | 50 ++++ identity-core/src/crypto/merkle_tree/tree.rs | 83 ++++++ identity-core/src/crypto/mod.rs | 1 + rustfmt.toml | 1 - 13 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 identity-core/examples/merkle_tree.rs create mode 100644 identity-core/src/crypto/merkle_tree/consts.rs create mode 100644 identity-core/src/crypto/merkle_tree/digest.rs create mode 100644 identity-core/src/crypto/merkle_tree/hash.rs create mode 100644 identity-core/src/crypto/merkle_tree/math.rs create mode 100644 identity-core/src/crypto/merkle_tree/merkle.rs create mode 100644 identity-core/src/crypto/merkle_tree/mod.rs create mode 100644 identity-core/src/crypto/merkle_tree/node.rs create mode 100644 identity-core/src/crypto/merkle_tree/proof.rs create mode 100644 identity-core/src/crypto/merkle_tree/tree.rs diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 9fda71d41c..48a9a9bd38 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -17,6 +17,7 @@ bs58 = { version = "0.4", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } +digest = { version = "0.9", default-features = false } ed25519-zebra = { version = "2.2", default-features = false } identity-diff = { version = "=0.1.0", path = "../identity-diff", default-features = false } lazy_static = { version = "1.4", default-features = false } diff --git a/identity-core/examples/merkle_tree.rs b/identity-core/examples/merkle_tree.rs new file mode 100644 index 0000000000..7393d17ca9 --- /dev/null +++ b/identity-core/examples/merkle_tree.rs @@ -0,0 +1,73 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! cargo run --example merkle_tree + +use digest::Digest; +use identity_core::crypto::merkle_tree::DigestExt; +use identity_core::crypto::merkle_tree::Hash; +use identity_core::crypto::merkle_tree::MTree; +use identity_core::crypto::merkle_tree::Proof; +use identity_core::crypto::KeyPair; +use identity_core::error::Result; +use identity_core::proof::JcsEd25519Signature2020; +use rand::rngs::OsRng; +use rand::Rng; +use sha2::Sha256; + +const LEAVES: usize = 1 << 8; + +fn generate_leaves(count: usize) -> Vec { + (0..count).map(|_| JcsEd25519Signature2020::new_keypair()).collect() +} + +fn generate_hashes<'a, D, T, I>(digest: &mut D, leaves: I) -> Vec> +where + D: Digest, + T: AsRef<[u8]> + 'a, + I: IntoIterator, +{ + leaves + .into_iter() + .map(AsRef::as_ref) + .map(|leaf| digest.hash_leaf(leaf)) + .collect() +} + +fn main() -> Result<()> { + let mut digest: Sha256 = Sha256::new(); + + // Choose a random index from 0..LEAVES. + // + // We will generate a proof-of-inclusion for this public key. + let index: usize = OsRng.gen_range(0, LEAVES); + + println!("Target Leaves: {}", LEAVES); + println!("Target Index: {}", index); + + // Generate a list of keypairs to use for the Merkle tree. + let kpairs: Vec = generate_leaves(LEAVES); + + // Hash all keypairs with SHA-256. + let leaves: _ = kpairs.iter().map(KeyPair::public); + let hashes: Vec> = generate_hashes(&mut digest, leaves); + + // Construct the Merkle tree from the list of hashes. + let tree: MTree = MTree::from_leaves(&hashes).unwrap(); + println!("Merkle Tree: {:#?}", tree); + + // Generate a proof-of-inclusion for the leaf node at the specified index. + let proof: Proof = tree.proof(index).unwrap(); + println!("Inclusion Proof: {:#?}", proof); + + // Hash the target public key with SHA-256. + let target: Hash = digest.hash_leaf(kpairs[index].public().as_ref()); + println!("Target Hash: {:?}", target); + + // Use the generated proof to verify inclusion of the target hash in the + // Merkle tree. + let verified: bool = tree.verify(&proof, target); + println!("Proof Verified: {:#?}", verified); + + Ok(()) +} diff --git a/identity-core/src/crypto/merkle_tree/consts.rs b/identity-core/src/crypto/merkle_tree/consts.rs new file mode 100644 index 0000000000..8edf395390 --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/consts.rs @@ -0,0 +1,9 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::mem; + +pub const PREFIX_L: &[u8] = &[0x00]; +pub const PREFIX_B: &[u8] = &[0x01]; + +pub const SIZE_USIZE: usize = mem::size_of::(); diff --git a/identity-core/src/crypto/merkle_tree/digest.rs b/identity-core/src/crypto/merkle_tree/digest.rs new file mode 100644 index 0000000000..9c2d2675fb --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/digest.rs @@ -0,0 +1,30 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[doc(import)] +pub use digest::Digest; + +use crate::crypto::merkle_tree::consts; +use crate::crypto::merkle_tree::Hash; + +/// An extension of the [`Digest`] trait for Merkle tree construction. +pub trait DigestExt: Sized + Digest { + /// Computes the [`struct@Hash`] of a Merkle tree leaf node. + fn hash_leaf(&mut self, data: &[u8]) -> Hash { + self.reset(); + self.update(consts::PREFIX_L); + self.update(data); + self.finalize_reset().into() + } + + /// Computes the parent [`struct@Hash`] of two Merkle tree nodes. + fn hash_branch(&mut self, lhs: &Hash, rhs: &Hash) -> Hash { + self.reset(); + self.update(consts::PREFIX_B); + self.update(lhs.as_ref()); + self.update(rhs.as_ref()); + self.finalize_reset().into() + } +} + +impl DigestExt for D where D: Digest {} diff --git a/identity-core/src/crypto/merkle_tree/hash.rs b/identity-core/src/crypto/merkle_tree/hash.rs new file mode 100644 index 0000000000..45be798b4f --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/hash.rs @@ -0,0 +1,104 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::cmp::Ordering; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; +use digest::generic_array::typenum::Unsigned; +use digest::Digest; +use digest::Output; +use subtle::Choice; +use subtle::ConstantTimeEq; + +use crate::utils::encode_b58; + +/// The output of a hash function. +pub struct Hash(Output) +where + D: Digest; + +impl Hash { + /// Creates a new [`struct@Hash`] from a slice. + pub fn from_slice(slice: &[u8]) -> Option { + if slice.len() != D::OutputSize::USIZE { + return None; + } + + let mut this: Self = Self::default(); + + this.0.copy_from_slice(slice); + + Some(this) + } +} + +impl Clone for Hash +where + Output: Clone, +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Copy for Hash where Output: Copy {} + +impl PartialEq for Hash +where + Output: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for Hash where Output: Eq {} + +impl PartialOrd for Hash +where + Output: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for Hash +where + Output: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl Debug for Hash { + fn fmt(&self, f: &mut Formatter) -> Result { + f.write_str(&encode_b58(self)) + } +} + +impl Default for Hash { + fn default() -> Self { + Self(Output::::default()) + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From> for Hash { + fn from(other: Output) -> Self { + Self(other) + } +} + +impl ConstantTimeEq for Hash { + fn ct_eq(&self, other: &Self) -> Choice { + self.as_ref().ct_eq(other.as_ref()) + } +} diff --git a/identity-core/src/crypto/merkle_tree/math.rs b/identity-core/src/crypto/merkle_tree/math.rs new file mode 100644 index 0000000000..89c544ea7e --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/math.rs @@ -0,0 +1,47 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::crypto::merkle_tree::consts; + +/// Return true if `value` is a power of two greater than `1`. +#[inline(always)] +pub const fn is_pow2(value: usize) -> bool { + value > 1 && value.is_power_of_two() +} + +/// Returns the base-2 logarithm of `value`, rounded up. +#[inline(always)] +pub fn log2c(value: usize) -> usize { + (consts::SIZE_USIZE * 8) - value.leading_zeros() as usize - value.is_power_of_two() as usize +} + +#[cfg(test)] +mod tests { + use super::is_pow2; + use super::log2c; + + #[test] + fn test_is_pow2() { + assert_eq!(is_pow2(0), false); + assert_eq!(is_pow2(1), false); + assert_eq!(is_pow2(2), true); + assert_eq!(is_pow2(3), false); + } + + #[test] + fn test_log2c() { + assert_eq!(log2c(0), 0); + assert_eq!(log2c(1), 0); + assert_eq!(log2c(1 << 1), 1); + assert_eq!(log2c(1 << 2), 2); + assert_eq!(log2c(1 << 3), 3); + assert_eq!(log2c(1 << 4), 4); + assert_eq!(log2c(1 << 5), 5); + assert_eq!(log2c(1 << 6), 6); + assert_eq!(log2c(1 << 7), 7); + assert_eq!(log2c(1 << 8), 8); + assert_eq!(log2c(1 << 9), 9); + assert_eq!(log2c(1 << 10), 10); + assert_eq!(log2c(1 << 11), 11); + } +} diff --git a/identity-core/src/crypto/merkle_tree/merkle.rs b/identity-core/src/crypto/merkle_tree/merkle.rs new file mode 100644 index 0000000000..bd55bf4cf0 --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/merkle.rs @@ -0,0 +1,253 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; +use core::marker::PhantomData; +use sha2::digest::Output; +use sha2::Digest; +use sha2::Sha256; + +use crate::crypto::merkle_tree::math; +use crate::crypto::merkle_tree::tree; +use crate::crypto::merkle_tree::Hash; +use crate::crypto::merkle_tree::Node; +use crate::crypto::merkle_tree::Proof; + +/// A [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) designed for +/// static data. +// # Overview +// +// The Merkle tree is implemented as a **perfect binary tree** where all +// interior nodes have two children and all leaves have the same depth. +// +// # Layout +// +// An example tree with 8 leaves [A..H]: +// +// 0-| 0 +// -| | +// 1-| 1 ------------- 2 +// -| | | +// 2-| 3 ----- 4 5 ------ 6 +// -| | | | | +// 3-| A - B C - D E - F G - H +// +// The tree will have the following layout: +// +// [0, 1, 2, 3, 4, 5, 6, A, B, C, D, E, F, G, H] +// +// Building the tree is straight-forward: +// +// 1. Allocate Vec: [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _] +// 2. Insert Hashes: [_, _, _, _, _, _, _, A, B, C, D, E, F, G, H] +// 3. Update (H=2): [_, _, _, 3, 4, 5, 6, A, B, C, D, E, F, G, H] +// 4. Update (H=1): [_, 1, 2, 3, 4, 5, 6, A, B, C, D, E, F, G, H] +// 5. Update (H=0): [0, 1, 2, 3, 4, 5, 6, A, B, C, D, E, F, G, H] +// +// Computing the root hash: +// +// H(H(H(A | B) | H(C | D)) | H(H(E | F) | H(G | H))) +pub struct MTree +where + D: Digest, +{ + nodes: Box<[Hash]>, + marker: PhantomData, +} + +impl MTree +where + D: Digest, +{ + /// Returns the number of leaf nodes in the tree. + pub fn leaves(&self) -> usize { + tree::leaves(self.nodes.len()) + } + + /// Returns the height of the tree. + pub fn height(&self) -> usize { + tree::height(tree::leaves(self.nodes.len())) + } + + /// Returns the root hash of the tree. + pub fn root(&self) -> &Hash { + &self.nodes[0] + } + + /// Returns a slice of the leaf nodes in the tree. + pub fn data(&self) -> &[Hash] { + &self.nodes[self.nodes.len() - self.leaves()..] + } + + /// Returns a slice of nodes at the specified `height`. + pub fn layer(&self, height: usize) -> &[Hash] { + let leaves: usize = 2_usize.pow(height as u32); + let total: usize = tree::total(leaves); + + if total <= self.nodes.len() { + &self.nodes[total - leaves..total] + } else { + &[] + } + } +} + +impl MTree +where + D: Digest, + Output: Copy, +{ + /// Creates a new [`MTree`] from a slice of pre-hashed data. + pub fn from_leaves(leaves: &[Hash]) -> Option { + // This Merkle tree only supports pow2 sequences + if !math::is_pow2(leaves.len()) { + return None; + } + + Some(Self { + nodes: tree::compute_nodes(&mut D::new(), leaves), + marker: PhantomData, + }) + } + + /// Generates a proof-of-inclusion for the leaf node at the specified index. + pub fn proof(&self, local: usize) -> Option> { + let leaves: usize = self.leaves(); + + assert!(leaves >= 2); + + if local >= leaves { + return None; + } + + let mut nodes: Vec> = Vec::new(); + let mut index: usize = tree::total(leaves) - leaves + local; + + while index > 0 { + if index & 1 == 0 { + nodes.push(Node::L(self.nodes[index - 1])); + } else { + nodes.push(Node::R(self.nodes[index + 1])); + } + + index = (index - 1) >> 1; + } + + Some(Proof::new(nodes.into_boxed_slice())) + } + + /// Verifies the computed root of `proof` with the root hash of `self`. + pub fn verify(&self, proof: &Proof, hash: Hash) -> bool { + proof.verify(self.root(), hash) + } +} + +impl Debug for MTree +where + D: Digest, +{ + fn fmt(&self, f: &mut Formatter) -> Result { + let mut f = f.debug_struct("MTree"); + + let total: usize = self.height(); + let count: usize = total.min(4); + + f.field("layer (root)", &self.layer(0)); + + for index in 1..count { + f.field(&format!("layer (#{})", index), &self.layer(index)); + } + + f.field("height", &total); + f.field("leaves", &self.leaves()); + + f.finish() + } +} + +#[cfg(test)] +mod tests { + use digest::Digest; + use sha2::Sha256; + + use crate::crypto::merkle_tree::DigestExt; + use crate::crypto::merkle_tree::Hash; + use crate::crypto::merkle_tree::MTree; + use crate::crypto::merkle_tree::Proof; + + macro_rules! h { + ($leaf:expr) => { + Sha256::new().hash_leaf($leaf) + }; + ($lhs:expr, $rhs:expr) => { + Sha256::new().hash_branch(&$lhs, &$rhs) + }; + } + + type Sha256Hash = Hash; + type Sha256Proof = Proof; + + #[test] + fn test_works() { + let nodes: Vec> = (0..(1 << 7)) + .map(|byte: u8| byte as char) + .map(String::from) + .map(String::into_bytes) + .collect(); + + let mut digest: Sha256 = Sha256::new(); + + let hashes: Vec = nodes.iter().map(|node| digest.hash_leaf(node.as_ref())).collect(); + + let tree: MTree = MTree::from_leaves(&hashes).unwrap(); + + assert_eq!(tree.data(), &hashes[..]); + assert_eq!(tree.leaves(), hashes.len()); + + for (index, hash) in hashes.iter().enumerate() { + let proof: Sha256Proof = tree.proof(index).unwrap(); + let root: Sha256Hash = proof.root(*hash); + + assert_eq!(tree.root(), &root); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_root() { + let A: Sha256Hash = h!(b"A"); + let B: Sha256Hash = h!(b"B"); + let C: Sha256Hash = h!(b"C"); + let D: Sha256Hash = h!(b"D"); + let E: Sha256Hash = h!(b"E"); + let F: Sha256Hash = h!(b"F"); + let G: Sha256Hash = h!(b"G"); + let H: Sha256Hash = h!(b"H"); + + let AB: Sha256Hash = h!(A, B); + let CD: Sha256Hash = h!(C, D); + let EF: Sha256Hash = h!(E, F); + let GH: Sha256Hash = h!(G, H); + + let ABCD: Sha256Hash = h!(AB, CD); + let EFGH: Sha256Hash = h!(EF, GH); + + let ABCDEFGH: Sha256Hash = h!(ABCD, EFGH); + + let data: [Sha256Hash; 8] = [A, B, C, D, E, F, G, H]; + let tree: MTree = MTree::from_leaves(&data).unwrap(); + + assert_eq!(tree.root(), &ABCDEFGH); + assert_eq!(tree.data(), &[A, B, C, D, E, F, G, H]); + assert_eq!(tree.height(), 3); + assert_eq!(tree.leaves(), 8); + + assert_eq!(tree.layer(0), &[ABCDEFGH]); + assert_eq!(tree.layer(1), &[ABCD, EFGH]); + assert_eq!(tree.layer(2), &[AB, CD, EF, GH]); + assert_eq!(tree.layer(3), &[A, B, C, D, E, F, G, H]); + assert_eq!(tree.layer(4), &[]); + } +} diff --git a/identity-core/src/crypto/merkle_tree/mod.rs b/identity-core/src/crypto/merkle_tree/mod.rs new file mode 100644 index 0000000000..e45c7ecdaf --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Types and traits for [Merkle tree][WIKI] operations. +//! +//! [WIKI]: https://en.wikipedia.org/wiki/Merkle_tree + +mod consts; +mod digest; +mod hash; +mod math; +mod merkle; +mod node; +mod proof; +mod tree; + +pub use self::digest::Digest; +pub use self::digest::DigestExt; +pub use self::hash::Hash; +pub use self::merkle::MTree; +pub use self::node::Node; +pub use self::proof::Proof; diff --git a/identity-core/src/crypto/merkle_tree/node.rs b/identity-core/src/crypto/merkle_tree/node.rs new file mode 100644 index 0000000000..764b1e2ff8 --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/node.rs @@ -0,0 +1,74 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; +use digest::Digest; + +use crate::crypto::merkle_tree::DigestExt; +use crate::crypto::merkle_tree::Hash; + +/// A tagged [`struct@Hash`]. +pub enum Node { + /// A node tagged with `L`. + L(Hash), + /// A node tagged with `R`. + R(Hash), +} + +impl Node { + /// Returns the [`struct@Hash`] of the node. + pub fn get(&self) -> &Hash { + match self { + Self::L(hash) => hash, + Self::R(hash) => hash, + } + } + + /// Computes the parent hash of `self` and `other` using a default digest. + pub fn hash(&self, other: &Hash) -> Hash { + self.hash_with(&mut D::new(), other) + } + + /// Computes the parent hash of `self` and `other` using the given `digest`. + pub fn hash_with(&self, digest: &mut D, other: &Hash) -> Hash { + match self { + Self::L(hash) => digest.hash_branch(hash, other), + Self::R(hash) => digest.hash_branch(other, hash), + } + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Self::L(hash) => f.write_fmt(format_args!("L({:?})", hash)), + Self::R(hash) => f.write_fmt(format_args!("R({:?})", hash)), + } + } +} + +#[cfg(test)] +mod tests { + use digest::Digest; + use sha2::Sha256; + + use crate::crypto::merkle_tree::DigestExt; + use crate::crypto::merkle_tree::Hash; + use crate::crypto::merkle_tree::Node; + + #[test] + fn test_hash() { + let mut digest: Sha256 = Sha256::new(); + + let h1: Hash = digest.hash_leaf(b"A"); + let h2: Hash = digest.hash_leaf(b"B"); + + assert_eq!(Node::L(h1).hash(&h2), digest.hash_branch(&h1, &h2)); + assert_eq!(Node::R(h1).hash(&h2), digest.hash_branch(&h2, &h1)); + + assert_eq!(Node::L(h2).hash(&h1), digest.hash_branch(&h2, &h1)); + assert_eq!(Node::R(h2).hash(&h1), digest.hash_branch(&h1, &h2)); + } +} diff --git a/identity-core/src/crypto/merkle_tree/proof.rs b/identity-core/src/crypto/merkle_tree/proof.rs new file mode 100644 index 0000000000..f63435a294 --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/proof.rs @@ -0,0 +1,50 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; +use digest::Digest; +use subtle::ConstantTimeEq; + +use crate::crypto::merkle_tree::Hash; +use crate::crypto::merkle_tree::Node; + +/// An inclusion proof that allows proving the existence of data in a +/// [`Merkle tree`](`struct@super::MTree`). +pub struct Proof { + nodes: Box<[Node]>, +} + +impl Proof { + /// Creates a new [`Proof`] from a boxed slice of nodes. + pub fn new(nodes: Box<[Node]>) -> Self { + Self { nodes } + } + + /// Returns the nodes as a slice. + pub fn nodes(&self) -> &[Node] { + &self.nodes + } + + /// Verifies the computed root of `self` with the given `root` hash. + pub fn verify(&self, root: &Hash, hash: Hash) -> bool { + self.root(hash).ct_eq(root).into() + } + + /// Computes the root hash from `target` using a default digest. + pub fn root(&self, target: Hash) -> Hash { + self.root_with(&mut D::new(), target) + } + + /// Computes the root hash from `target` using the given `digest`. + pub fn root_with(&self, digest: &mut D, target: Hash) -> Hash { + self.nodes.iter().fold(target, |acc, item| item.hash_with(digest, &acc)) + } +} + +impl Debug for Proof { + fn fmt(&self, f: &mut Formatter) -> Result { + f.debug_struct("Proof").field("nodes", &self.nodes).finish() + } +} diff --git a/identity-core/src/crypto/merkle_tree/tree.rs b/identity-core/src/crypto/merkle_tree/tree.rs new file mode 100644 index 0000000000..156d888406 --- /dev/null +++ b/identity-core/src/crypto/merkle_tree/tree.rs @@ -0,0 +1,83 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use digest::Digest; +use digest::Output; + +use crate::crypto::merkle_tree::math; +use crate::crypto::merkle_tree::DigestExt; +use crate::crypto::merkle_tree::Hash; + +#[inline(always)] +pub fn height(leaves: usize) -> usize { + math::log2c(leaves) +} + +#[inline(always)] +pub const fn total(leaves: usize) -> usize { + // 2l - 1 + (leaves << 1) - 1 +} + +#[inline(always)] +pub const fn leaves(nodes: usize) -> usize { + // l = (n + 1) / 2 + (nodes + 1) >> 1 +} + +#[inline(always)] +pub const fn index_lhs(index: usize) -> usize { + // 2i + 1 + (index << 1) + 1 +} + +#[inline(always)] +pub const fn index_rhs(index: usize) -> usize { + // 2i + 2 + (index << 1) + 2 +} + +pub fn compute_nodes(digest: &mut D, leaves: &[Hash]) -> Box<[Hash]> +where + D: Digest, + Output: Copy, +{ + let count: usize = leaves.len(); + let total: usize = self::total(count); + let offset: usize = total - count; + let height: usize = self::height(count); + + assert_eq!(count, 1 << height); + + // Create a vec for the entire set of nodes + let mut nodes: Vec> = vec![Hash::default(); total]; + + // Copy the initial hashes to the end of the vec + nodes[offset..total].copy_from_slice(leaves); + + // Compute parent hashes in bottom-up order + for index in 0..height { + compute(digest, &mut nodes, height - index); + } + + nodes.into_boxed_slice() +} + +fn compute(digest: &mut D, nodes: &mut Vec>, index: usize) +where + D: Digest, +{ + let update: usize = 1 << (index - 1); + let offset: usize = update - 1; + + for index in 0..update { + let local: usize = offset + index; + + assert!(nodes.len() > local); + + let lhs: &Hash = &nodes[index_lhs(local)]; + let rhs: &Hash = &nodes[index_rhs(local)]; + + nodes[local] = digest.hash_branch(lhs, rhs); + } +} diff --git a/identity-core/src/crypto/mod.rs b/identity-core/src/crypto/mod.rs index d4dac6edea..9560060659 100644 --- a/identity-core/src/crypto/mod.rs +++ b/identity-core/src/crypto/mod.rs @@ -5,5 +5,6 @@ mod key_impl; mod key_pair; +pub mod merkle_tree; pub use self::{key_impl::*, key_pair::*}; diff --git a/rustfmt.toml b/rustfmt.toml index 2b98e07a9b..657511c75d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,7 +2,6 @@ comment_width = 120 format_code_in_doc_comments = true license_template_path = ".license_template" max_width = 120 -merge_imports = true normalize_comments = false normalize_doc_attributes = false wrap_comments = true