Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Proof of Work #75

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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>;
}
tsunrise marked this conversation as resolved.
Show resolved Hide resolved

/// 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