Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
pull_request:
push:
branches:
- master
- main
env:
RUST_BACKTRACE: 1

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod crh;
pub mod merkle_tree;

pub mod encryption;
pub mod pow;
pub mod prf;
pub mod signature;
pub mod snark;
Expand Down
99 changes: 60 additions & 39 deletions src/merkle_tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@ use crate::crh::TwoToOneCRHScheme;
use crate::{CRHScheme, Error};
use ark_ff::ToBytes;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
use ark_std::borrow::Borrow;
use ark_std::hash::Hash;
use ark_std::vec::Vec;
use ark_std::{borrow::Borrow, hash::Hash, vec::Vec};

#[cfg(test)]
mod tests;
pub(crate) mod tests;

#[cfg(feature = "r1cs")]
pub mod constraints;

/// Convert the hash digest in different layers by converting previous layer's output to
/// `TargetType`, which is a `Borrow` to next layer's input.
/// Convert the hash digest in different layers by converting previous layer's
/// output to `TargetType`, which is a `Borrow` to next layer's input.
pub trait DigestConverter<From, To: ?Sized> {
type TargetType: Borrow<To>;
fn convert(item: From) -> Result<Self::TargetType, Error>;
}

/// A trivial converter where digest of previous layer's hash is the same as next layer's input.
/// A trivial converter where digest of previous layer's hash is the same as
/// next layer's input.
pub struct IdentityDigestConverter<T> {
_prev_layer_digest: T,
}
Expand All @@ -34,8 +33,8 @@ impl<T> DigestConverter<T, T> for IdentityDigestConverter<T> {
}
}

/// Convert previous layer's digest to bytes and use bytes as input for next layer's digest.
/// TODO: `ToBytes` trait will be deprecated in future versions.
/// Convert previous layer's digest to bytes and use bytes as input for next
/// layer's digest. TODO: `ToBytes` trait will be deprecated in future versions.
pub struct ByteDigestConverter<T: CanonicalSerialize + ToBytes> {
_prev_layer_digest: T,
}
Expand All @@ -44,15 +43,18 @@ impl<T: CanonicalSerialize + ToBytes> DigestConverter<T, [u8]> for ByteDigestCon
type TargetType = Vec<u8>;

fn convert(item: T) -> Result<Self::TargetType, Error> {
// TODO: In some tests, `serialize` is not consistent with constraints. Try fix those.
// TODO: In some tests, `serialize` is not consistent with constraints. Try fix
// those.
Ok(crate::to_unchecked_bytes!(item)?)
}
}

/// Merkle tree have three types of hashes.
/// * `LeafHash`: Convert leaf to leaf digest
/// * `TwoLeavesToOneHash`: Convert two leaf digests to one inner digest. This one can be a wrapped
/// version `TwoHashesToOneHash`, which first converts leaf digest to inner digest.
/// * `TwoLeavesToOneHash`: Convert two leaf digests to one inner digest. This
/// one can be a wrapped
/// version `TwoHashesToOneHash`, which first converts leaf digest to inner
/// digest.
/// * `TwoHashesToOneHash`: Compress two inner digests to one inner digest
pub trait Config {
type Leaf: ?Sized; // merkle tree does not store the leaf
Expand Down Expand Up @@ -80,12 +82,14 @@ pub trait Config {
+ CanonicalSerialize
+ CanonicalDeserialize;

// Tom's Note: in the future, if we want different hash function, we can simply add more
// types of digest here and specify a digest converter. Same for constraints.
// Tom's Note: in the future, if we want different hash function, we can simply
// add more types of digest here and specify a digest converter. Same for
// constraints.

/// leaf -> leaf digest
/// If leaf hash digest and inner hash digest are different, we can create a new
/// leaf hash which wraps the original leaf hash and convert its output to `Digest`.
/// If leaf hash digest and inner hash digest are different, we can create a
/// new leaf hash which wraps the original leaf hash and convert its
/// output to `Digest`.
type LeafHash: CRHScheme<Input = Self::Leaf, Output = Self::LeafDigest>;
/// 2 inner digest -> inner digest
type TwoToOneHash: TwoToOneCRHScheme<Output = Self::InnerDigest>;
Expand All @@ -105,7 +109,8 @@ pub type LeafParam<P> = <<P as Config>::LeafHash as CRHScheme>::Parameters;
/// .. / \ ....
/// [I] J
/// ```
/// Suppose we want to prove I, then `leaf_sibling_hash` is J, `auth_path` is `[C,D]`
/// Suppose we want to prove I, then `leaf_sibling_hash` is J, `auth_path` is
/// `[C,D]`
#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)]
#[derivative(
Clone(bound = "P: Config"),
Expand All @@ -114,17 +119,20 @@ pub type LeafParam<P> = <<P as Config>::LeafHash as CRHScheme>::Parameters;
)]
pub struct Path<P: Config> {
pub leaf_sibling_hash: P::LeafDigest,
/// The sibling of path node ordered from higher layer to lower layer (does not include root node).
/// The sibling of path node ordered from higher layer to lower layer (does
/// not include root node).
pub auth_path: Vec<P::InnerDigest>,
/// stores the leaf index of the node
pub leaf_index: usize,
}

impl<P: Config> Path<P> {
/// The position of on_path node in `leaf_and_sibling_hash` and `non_leaf_and_sibling_hash_path`.
/// `position[i]` is 0 (false) iff `i`th on-path node from top to bottom is on the left.
/// The position of on_path node in `leaf_and_sibling_hash` and
/// `non_leaf_and_sibling_hash_path`. `position[i]` is 0 (false) iff
/// `i`th on-path node from top to bottom is on the left.
///
/// This function simply converts `self.leaf_index` to boolean array in big endian form.
/// This function simply converts `self.leaf_index` to boolean array in big
/// endian form.
#[allow(unused)] // this function is actually used when r1cs feature is on
fn position_list(&'_ self) -> impl '_ + Iterator<Item = bool> {
(0..self.auth_path.len() + 1)
Expand All @@ -137,7 +145,8 @@ impl<P: Config> Path<P> {
/// Verify that a leaf is at `self.index` of the merkle tree.
/// * `leaf_size`: leaf size in number of bytes
///
/// `verify` infers the tree height by setting `tree_height = self.auth_path.len() + 2`
/// `verify` infers the tree height by setting `tree_height =
/// self.auth_path.len() + 2`
pub fn verify<L: Borrow<P::Leaf>>(
&self,
leaf_hash_params: &LeafParam<P>,
Expand Down Expand Up @@ -184,8 +193,9 @@ impl<P: Config> Path<P> {
/// `index` is the first `path.len()` bits of
/// the position of tree.
///
/// If the least significant bit of `index` is 0, then `sibling` will be left and `computed` will be right.
/// Otherwise, `sibling` will be right and `computed` will be left.
/// If the least significant bit of `index` is 0, then `sibling` will be left
/// and `computed` will be right. Otherwise, `sibling` will be right and
/// `computed` will be left.
///
/// Returns: (left, right)
fn select_left_right_child<L: Clone>(
Expand All @@ -203,17 +213,21 @@ fn select_left_right_child<L: Clone>(
}

/// Defines a merkle tree data structure.
/// This merkle tree has runtime fixed height, and assumes number of leaves is 2^height.
/// This merkle tree has runtime fixed height, and assumes number of leaves is
/// 2^height.
///
/// TODO: add RFC-6962 compatible merkle tree in the future.
/// For this release, padding will not be supported because of security concerns: if the leaf hash and two to one hash uses same underlying
/// CRH, a malicious prover can prove a leaf while the actual node is an inner node. In the future, we can prefix leaf hashes in different layers to
/// For this release, padding will not be supported because of security
/// concerns: if the leaf hash and two to one hash uses same underlying
/// CRH, a malicious prover can prove a leaf while the actual node is an inner
/// node. In the future, we can prefix leaf hashes in different layers to
/// solve the problem.
#[derive(Derivative)]
#[derivative(Clone(bound = "P: Config"))]
pub struct MerkleTree<P: Config> {
/// stores the non-leaf nodes in level order. The first element is the root node.
/// The ith nodes (starting at 1st) children are at indices `2*i`, `2*i+1`
/// stores the non-leaf nodes in level order. The first element is the root
/// node. The ith nodes (starting at 1st) children are at indices `2*i`,
/// `2*i+1`
non_leaf_nodes: Vec<P::InnerDigest>,
/// store the hash of leaf nodes from left to right
leaf_nodes: Vec<P::LeafDigest>,
Expand All @@ -227,7 +241,8 @@ pub struct MerkleTree<P: Config> {

impl<P: Config> MerkleTree<P> {
/// Create an empty merkle tree such that all leaves are zero-filled.
/// Consider using a sparse merkle tree if you need the tree to be low memory
/// Consider using a sparse merkle tree if you need the tree to be low
/// memory
pub fn blank(
leaf_hash_param: &LeafParam<P>,
two_to_one_hash_param: &TwoToOneParam<P>,
Expand Down Expand Up @@ -288,9 +303,10 @@ impl<P: Config> MerkleTree<P> {
let start_index = level_indices.pop().unwrap();
let upper_bound = left_child(start_index);
for current_index in start_index..upper_bound {
// `left_child(current_index)` and `right_child(current_index) returns the position of
// leaf in the whole tree (represented as a list in level order). We need to shift it
// by `-upper_bound` to get the index in `leaf_nodes` list.
// `left_child(current_index)` and `right_child(current_index) returns the
// position of leaf in the whole tree (represented as a list in
// level order). We need to shift it by `-upper_bound` to get
// the index in `leaf_nodes` list.
let left_leaf_index = left_child(current_index) - upper_bound;
let right_leaf_index = right_child(current_index) - upper_bound;
// compute hash
Expand Down Expand Up @@ -352,9 +368,11 @@ impl<P: Config> MerkleTree<P> {
self.leaf_nodes[index - 1].clone()
};

// path.len() = `tree height - 2`, the two missing elements being the leaf sibling hash and the root
// path.len() = `tree height - 2`, the two missing elements being the leaf
// sibling hash and the root
let mut path = Vec::with_capacity(tree_height - 2);
// Iterate from the bottom layer after the leaves, to the top, storing all sibling node's hash values.
// Iterate from the bottom layer after the leaves, to the top, storing all
// sibling node's hash values.
let mut current_node = parent(leaf_index_in_tree).unwrap();
while !is_root(current_node) {
let sibling_node = sibling(current_node).unwrap();
Expand All @@ -374,8 +392,9 @@ impl<P: Config> MerkleTree<P> {
})
}

/// Given the index and new leaf, return the hash of leaf and an updated path in order from root to bottom non-leaf level.
/// This does not mutate the underlying tree.
/// Given the index and new leaf, return the hash of leaf and an updated
/// path in order from root to bottom non-leaf level. This does not
/// mutate the underlying tree.
fn updated_path<T: Borrow<P::Leaf>>(
&self,
index: usize,
Expand Down Expand Up @@ -438,7 +457,8 @@ impl<P: Config> MerkleTree<P> {
/// .. / \ ....
/// [I] J
/// ```
/// update(3, {new leaf}) would swap the leaf value at `[I]` and cause a recomputation of `[A]`, `[B]`, and `[E]`.
/// update(3, {new leaf}) would swap the leaf value at `[I]` and cause a
/// recomputation of `[A]`, `[B]`, and `[E]`.
pub fn update(&mut self, index: usize, new_leaf: &P::Leaf) -> Result<(), crate::Error> {
assert!(index < self.leaf_nodes.len(), "index out of range");
let (updated_leaf_hash, mut updated_path) = self.updated_path(index, new_leaf)?;
Expand All @@ -451,7 +471,8 @@ impl<P: Config> MerkleTree<P> {
Ok(())
}

/// Update the leaf and check if the updated root is equal to `asserted_new_root`.
/// Update the leaf and check if the updated root is equal to
/// `asserted_new_root`.
///
/// Tree will not be modified if the check fails.
pub fn check_update<T: Borrow<P::Leaf>>(
Expand Down
11 changes: 6 additions & 5 deletions src/merkle_tree/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(feature = "r1cs")]
mod constraints;
mod test_utils;
pub(crate) mod test_utils;

mod bytes_mt_tests {

Expand Down Expand Up @@ -119,10 +119,11 @@ mod bytes_mt_tests {
}

mod field_mt_tests {
use crate::crh::poseidon;
use crate::merkle_tree::tests::test_utils::poseidon_parameters;
use crate::merkle_tree::{Config, IdentityDigestConverter};
use crate::MerkleTree;
use crate::{
crh::poseidon,
merkle_tree::{tests::test_utils::poseidon_parameters, Config, IdentityDigestConverter},
MerkleTree,
};
use ark_std::{test_rng, One, UniformRand};

type F = ark_ed_on_bls12_381::Fr;
Expand Down
54 changes: 54 additions & 0 deletions src/pow/constraints/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
pub mod poseidon;

use ark_ff::PrimeField;
use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean};
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use ark_std::borrow::Borrow;

use super::{CryptoHash, PoW};

/// R1CS Gadget for Crypto Hash.
pub trait CryptoHashGadget<CF: PrimeField> {
type Parameters;
/// Input of the hash
type InputVar: ?Sized;
/// Outout of the Hash
type OutputVar;

/// Given the input var and parameters, compute the output var.
fn digest<T: Borrow<Self::InputVar>>(
cs: ConstraintSystemRef<CF>,
param: &Self::Parameters,
input: T,
) -> Result<Self::OutputVar, SynthesisError>;
}

/// R1CS Gadget for Proof of Work
pub trait PoWGadget<CF: PrimeField>: CryptoHashGadget<CF> {
type NonceVar;
/// Given input var and nonce var, check whether `H(input||nonce)` is a
/// valid proof of work under certain difficulty.
fn verify(
cs: ConstraintSystemRef<CF>,
param: &Self::Parameters,
input: &Self::InputVar,
nonce: &Self::NonceVar,
difficulty: usize,
) -> Result<Boolean<CF>, SynthesisError>;
}

/// Extension trait for crypto hash to get the gadget.
pub trait CryptoHashWithGadget<CF: PrimeField>: CryptoHash
where
<Self::Gadget as CryptoHashGadget<CF>>::OutputVar: AllocVar<Self::Output, CF>,
{
type Gadget: CryptoHashGadget<CF, Parameters = Self::Parameters>;
}

/// Extension trait for PoW to get the gadget.
pub trait PoWWithGadget<CF: PrimeField>: PoW
where
<Self::Gadget as CryptoHashGadget<CF>>::OutputVar: AllocVar<Self::Output, CF>,
{
type Gadget: PoWGadget<CF, Parameters = Self::Parameters>;
}
Loading