forked from iotaledger/identity.rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Verification Method Merkle Tree (iotaledger#111)
* Add generic Merkle tree * Disable import merging
- Loading branch information
Showing
13 changed files
with
747 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.