Skip to content

Commit

Permalink
Add Verification Method Merkle Tree (iotaledger#111)
Browse files Browse the repository at this point in the history
* Add generic Merkle tree

* Disable import merging
  • Loading branch information
l1h3r authored Jan 22, 2021
1 parent f681b02 commit c1e7b20
Show file tree
Hide file tree
Showing 13 changed files with 747 additions and 1 deletion.
1 change: 1 addition & 0 deletions identity-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
73 changes: 73 additions & 0 deletions identity-core/examples/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -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<KeyPair> {
(0..count).map(|_| JcsEd25519Signature2020::new_keypair()).collect()
}

fn generate_hashes<'a, D, T, I>(digest: &mut D, leaves: I) -> Vec<Hash<D>>
where
D: Digest,
T: AsRef<[u8]> + 'a,
I: IntoIterator<Item = &'a T>,
{
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<KeyPair> = generate_leaves(LEAVES);

// Hash all keypairs with SHA-256.
let leaves: _ = kpairs.iter().map(KeyPair::public);
let hashes: Vec<Hash<Sha256>> = generate_hashes(&mut digest, leaves);

// Construct the Merkle tree from the list of hashes.
let tree: MTree<Sha256> = 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<Sha256> = tree.proof(index).unwrap();
println!("Inclusion Proof: {:#?}", proof);

// Hash the target public key with SHA-256.
let target: Hash<Sha256> = 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(())
}
9 changes: 9 additions & 0 deletions identity-core/src/crypto/merkle_tree/consts.rs
Original file line number Diff line number Diff line change
@@ -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::<usize>();
30 changes: 30 additions & 0 deletions identity-core/src/crypto/merkle_tree/digest.rs
Original file line number Diff line number Diff line change
@@ -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> {
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<Self>, rhs: &Hash<Self>) -> Hash<Self> {
self.reset();
self.update(consts::PREFIX_B);
self.update(lhs.as_ref());
self.update(rhs.as_ref());
self.finalize_reset().into()
}
}

impl<D> DigestExt for D where D: Digest {}
104 changes: 104 additions & 0 deletions identity-core/src/crypto/merkle_tree/hash.rs
Original file line number Diff line number Diff line change
@@ -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<D>(Output<D>)
where
D: Digest;

impl<D: Digest> Hash<D> {
/// Creates a new [`struct@Hash`] from a slice.
pub fn from_slice(slice: &[u8]) -> Option<Self> {
if slice.len() != D::OutputSize::USIZE {
return None;
}

let mut this: Self = Self::default();

this.0.copy_from_slice(slice);

Some(this)
}
}

impl<D: Digest> Clone for Hash<D>
where
Output<D>: Clone,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

impl<D: Digest> Copy for Hash<D> where Output<D>: Copy {}

impl<D: Digest> PartialEq for Hash<D>
where
Output<D>: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}

impl<D: Digest> Eq for Hash<D> where Output<D>: Eq {}

impl<D: Digest> PartialOrd for Hash<D>
where
Output<D>: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}

impl<D: Digest> Ord for Hash<D>
where
Output<D>: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}

impl<D: Digest> Debug for Hash<D> {
fn fmt(&self, f: &mut Formatter) -> Result {
f.write_str(&encode_b58(self))
}
}

impl<D: Digest> Default for Hash<D> {
fn default() -> Self {
Self(Output::<D>::default())
}
}

impl<D: Digest> AsRef<[u8]> for Hash<D> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

impl<D: Digest> From<Output<D>> for Hash<D> {
fn from(other: Output<D>) -> Self {
Self(other)
}
}

impl<D: Digest> ConstantTimeEq for Hash<D> {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
47 changes: 47 additions & 0 deletions identity-core/src/crypto/merkle_tree/math.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit c1e7b20

Please sign in to comment.