From 150e4e11e79d52226d6a8b454af41ec12f5e2ad0 Mon Sep 17 00:00:00 2001 From: Noah Golub Date: Wed, 14 Jun 2023 14:13:51 -0400 Subject: [PATCH 1/2] feat: Reorganize NMT repo (#319) Signed-off-by: nomaxg --- .../namespaced_merkle_tree/hash.rs | 113 ++++++ .../mod.rs} | 378 +----------------- .../namespaced_merkle_tree/proof.rs | 269 +++++++++++++ 3 files changed, 393 insertions(+), 367 deletions(-) create mode 100644 primitives/src/merkle_tree/namespaced_merkle_tree/hash.rs rename primitives/src/merkle_tree/{namespaced_merkle_tree.rs => namespaced_merkle_tree/mod.rs} (58%) create mode 100644 primitives/src/merkle_tree/namespaced_merkle_tree/proof.rs diff --git a/primitives/src/merkle_tree/namespaced_merkle_tree/hash.rs b/primitives/src/merkle_tree/namespaced_merkle_tree/hash.rs new file mode 100644 index 000000000..991fc79ff --- /dev/null +++ b/primitives/src/merkle_tree/namespaced_merkle_tree/hash.rs @@ -0,0 +1,113 @@ +use alloc::vec; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{string::ToString, vec::Vec}; +use core::{fmt::Debug, hash::Hash, marker::PhantomData}; + +use crate::errors::PrimitivesError; + +use super::{BindNamespace, DigestAlgorithm, Element, Index, Namespace, Namespaced, NodeValue}; + +/// NamespacedHasher wraps a standard hash function (implementer of +/// DigestAlgorithm), turning it into a hash function that tags internal nodes +/// with namespace ranges. +#[derive(Debug, Clone)] +pub struct NamespacedHasher +where + H: DigestAlgorithm, + E: Element + Namespaced, + N: Namespace, + I: Index, + T: NodeValue, +{ + phantom1: PhantomData, + phantom2: PhantomData, + phantom3: PhantomData, + phantom4: PhantomData, + phantom5: PhantomData, +} + +#[derive( + CanonicalSerialize, + CanonicalDeserialize, + Hash, + Copy, + Clone, + Debug, + Default, + Ord, + Eq, + PartialEq, + PartialOrd, +)] +/// Represents a namespaced internal tree node +pub struct NamespacedHash +where + N: Namespace, + T: NodeValue, +{ + pub(crate) min_namespace: N, + pub(crate) max_namespace: N, + pub(crate) hash: T, +} + +impl NamespacedHash +where + N: Namespace, + T: NodeValue, +{ + /// Constructs a new NamespacedHash + pub fn new(min_namespace: N, max_namespace: N, hash: T) -> Self { + Self { + min_namespace, + max_namespace, + hash, + } + } +} + +impl DigestAlgorithm> for NamespacedHasher +where + E: Element + Namespaced, + I: Index, + N: Namespace, + T: NodeValue, + H: DigestAlgorithm + BindNamespace, +{ + // Assumes that data is sorted by namespace, will be enforced by "append" + fn digest(data: &[NamespacedHash]) -> Result, PrimitivesError> { + if data.is_empty() { + return Ok(NamespacedHash::default()); + } + let first_node = data[0]; + let min_namespace = first_node.min_namespace; + let mut max_namespace = first_node.max_namespace; + let mut nodes = vec![H::generate_namespaced_commitment(first_node)]; + for node in &data[1..] { + if node == &NamespacedHash::default() { + continue; + } + // Ensure that namespaced nodes are sorted + if node.min_namespace < max_namespace { + return Err(PrimitivesError::InternalError( + "Namespace Merkle tree leaves are out of order".to_string(), + )); + } + max_namespace = node.max_namespace; + nodes.push(H::generate_namespaced_commitment(*node)); + } + + let inner_hash = H::digest(&nodes)?; + + Ok(NamespacedHash::new( + min_namespace, + max_namespace, + inner_hash, + )) + } + + fn digest_leaf(pos: &I, elem: &E) -> Result, PrimitivesError> { + let namespace = elem.get_namespace(); + let hash = H::digest_leaf(pos, elem)?; + Ok(NamespacedHash::new(namespace, namespace, hash)) + } +} diff --git a/primitives/src/merkle_tree/namespaced_merkle_tree.rs b/primitives/src/merkle_tree/namespaced_merkle_tree/mod.rs similarity index 58% rename from primitives/src/merkle_tree/namespaced_merkle_tree.rs rename to primitives/src/merkle_tree/namespaced_merkle_tree/mod.rs index f9961e560..d3014c779 100644 --- a/primitives/src/merkle_tree/namespaced_merkle_tree.rs +++ b/primitives/src/merkle_tree/namespaced_merkle_tree/mod.rs @@ -5,24 +5,27 @@ // along with the Jellyfish library. If not, see . //! Implementation of a Namespaced Merkle Tree. -use alloc::{ - collections::{btree_map::Entry, BTreeMap}, - vec, -}; +use alloc::collections::{btree_map::Entry, BTreeMap}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{string::ToString, vec::Vec}; +use ark_std::vec::Vec; use core::{borrow::Borrow, fmt::Debug, hash::Hash, marker::PhantomData, ops::Range}; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; use typenum::Unsigned; use crate::errors::{PrimitivesError, VerificationResult}; +use self::{ + hash::{NamespacedHash, NamespacedHasher}, + proof::{NaiveNamespaceProof, NamespaceProofType}, +}; + use super::{ append_only::MerkleTree, internal::MerkleProof, AppendableMerkleTreeScheme, DigestAlgorithm, Element, Index, LookupResult, MerkleCommitment, MerkleTreeScheme, NodeValue, }; +mod hash; +mod proof; + /// Namespaced Merkle Tree where leaves are sorted by a namespace identifier. /// The data structure supports namespace inclusion proofs. pub trait NamespacedMerkleTreeScheme: AppendableMerkleTreeScheme @@ -47,25 +50,6 @@ where ) -> Result; } -/// NamespacedHasher wraps a standard hash function (implementer of -/// DigestAlgorithm), turning it into a hash function that tags internal nodes -/// with namespace ranges. -#[derive(Debug, Clone)] -pub struct NamespacedHasher -where - H: DigestAlgorithm, - E: Element + Namespaced, - N: Namespace, - I: Index, - T: NodeValue, -{ - phantom1: PhantomData, - phantom2: PhantomData, - phantom3: PhantomData, - phantom4: PhantomData, - phantom5: PhantomData, -} - /// Completeness proof for a namespace pub trait NamespaceProof { /// Namespace type @@ -126,92 +110,6 @@ impl Namespace for u64 { } } -#[derive( - CanonicalSerialize, - CanonicalDeserialize, - Hash, - Copy, - Clone, - Debug, - Default, - Ord, - Eq, - PartialEq, - PartialOrd, -)] -/// Represents a namespaced internal tree node -pub struct NamespacedHash -where - N: Namespace, - T: NodeValue, -{ - min_namespace: N, - max_namespace: N, - hash: T, -} - -impl NamespacedHash -where - N: Namespace, - T: NodeValue, -{ - /// Constructs a new NamespacedHash - pub fn new(min_namespace: N, max_namespace: N, hash: T) -> Self { - Self { - min_namespace, - max_namespace, - hash, - } - } -} - -impl DigestAlgorithm> for NamespacedHasher -where - E: Element + Namespaced, - I: Index, - N: Namespace, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, -{ - // Assumes that data is sorted by namespace, will be enforced by "append" - fn digest(data: &[NamespacedHash]) -> Result, PrimitivesError> { - if data.is_empty() { - return Ok(NamespacedHash::default()); - } - let first_node = data[0]; - let min_namespace = first_node.min_namespace; - let mut max_namespace = first_node.max_namespace; - let mut nodes = vec![H::generate_namespaced_commitment(first_node)]; - for node in &data[1..] { - if node == &NamespacedHash::default() { - continue; - } - // Ensure that namespaced nodes are sorted - if node.min_namespace < max_namespace { - return Err(PrimitivesError::InternalError( - "Namespace Merkle tree leaves are out of order".to_string(), - )); - } - max_namespace = node.max_namespace; - nodes.push(H::generate_namespaced_commitment(*node)); - } - - let inner_hash = H::digest(&nodes)?; - - Ok(NamespacedHash::new( - min_namespace, - max_namespace, - inner_hash, - )) - } - - fn digest_leaf(pos: &I, elem: &E) -> Result, PrimitivesError> { - let namespace = elem.get_namespace(); - let hash = H::digest_leaf(pos, elem)?; - Ok(NamespacedHash::new(namespace, namespace, hash)) - } -} - type InnerTree = MerkleTree, u64, Arity, NamespacedHash>; @@ -248,7 +146,7 @@ where fn from_elems( height: usize, - elems: impl IntoIterator>, + elems: impl IntoIterator>, ) -> Result { let mut namespace_ranges: BTreeMap> = BTreeMap::new(); let mut max_namespace = ::min(); @@ -336,260 +234,6 @@ where } } -/// Indicates whether the namespace proof represents a populated set or an empty -/// set -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -enum NamespaceProofType { - Presence, - Absence, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(bound = "E: CanonicalSerialize + CanonicalDeserialize, - T: CanonicalSerialize + CanonicalDeserialize,")] -/// Namespace Proof -pub struct NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, - Arity: Unsigned, -{ - proof_type: NamespaceProofType, - // TODO(#140) Switch to a batch proof - proofs: Vec, Arity>>, - left_boundary_proof: Option, Arity>>, - right_boundary_proof: Option, Arity>>, - first_index: u64, - phantom: PhantomData, -} -impl NamespaceProof for NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, - Arity: Unsigned, -{ - type Leaf = E; - type Node = T; - type Namespace = N; - - fn get_namespace_leaves(&self) -> Vec<&Self::Leaf> { - let num_leaves = match self.proof_type { - NamespaceProofType::Presence => self.proofs.len(), - NamespaceProofType::Absence => 0, - }; - self.proofs - .iter() - // This unwrap is safe assuming that the proof is valid - .map(|proof| proof.elem().unwrap()) - .take(num_leaves) - .collect_vec() - } - - fn verify( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - match self.proof_type { - NamespaceProofType::Presence => self.verify_presence_proof(root, namespace), - NamespaceProofType::Absence => self.verify_absence_proof(root, namespace), - } - } -} - -impl NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, - Arity: Unsigned, -{ - fn verify_left_namespace_boundary( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if let Some(boundary_proof) = self.left_boundary_proof.as_ref() { - // If there is a leaf to the left of the namespace range, check that it is less - // than the target namespace - if boundary_proof - .elem() - .ok_or(PrimitivesError::InconsistentStructureError( - "Boundary proof does not contain an element".into(), - ))? - .get_namespace() - >= namespace - || *boundary_proof.index() != self.first_index - 1 - { - return Ok(Err(())); - } - // Verify the boundary proof - if >::verify(root, boundary_proof.index(), boundary_proof)? - .is_err() - { - return Ok(Err(())); - } - } else { - // If there is no left boundary, ensure that target namespace is the tree's - // minimum namespace - if root.min_namespace != namespace { - return Ok(Err(())); - } - } - Ok(Ok(())) - } - - fn verify_right_namespace_boundary( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if let Some(boundary_proof) = self.right_boundary_proof.as_ref() { - // If there is a leaf to the left of the namespace range, check that it is less - // than the target namespace - if boundary_proof - .elem() - .ok_or(PrimitivesError::InconsistentStructureError( - "Boundary proof does not contain an element".to_string(), - ))? - .get_namespace() - <= namespace - || *boundary_proof.index() != self.first_index + self.proofs.len() as u64 - { - return Ok(Err(())); - } - // Verify the boundary proof - if >::verify(root, boundary_proof.index(), boundary_proof)? - .is_err() - { - return Ok(Err(())); - } - } else { - // If there is no left boundary, ensure that target namespace is the tree's - // minimum namespace - if root.max_namespace != namespace { - return Ok(Err(())); - } - } - Ok(Ok(())) - } - - fn verify_absence_proof( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if namespace < root.min_namespace || namespace > root.max_namespace { - // Easy case where the namespace isn't covered by the range of the tree root - return Ok(Ok(())); - } else { - // Harder case: Find an element whose namespace is greater than our - // target and show that the namespace to the left is less than our - // target - let left_proof = &self.left_boundary_proof.as_ref().cloned().ok_or( - PrimitivesError::InconsistentStructureError( - "Left Boundary proof must be present".into(), - ), - )?; - let right_proof = &self.right_boundary_proof.as_ref().cloned().ok_or( - PrimitivesError::InconsistentStructureError( - "Right boundary proof must be present".into(), - ), - )?; - let left_index = left_proof.index(); - let left_ns = left_proof - .elem() - .ok_or(PrimitivesError::InconsistentStructureError( - "The left boundary proof is missing an element".into(), - ))? - .get_namespace(); - let right_index = right_proof.index(); - let right_ns = right_proof - .elem() - .ok_or(PrimitivesError::InconsistentStructureError( - "The left boundary proof is missing an element".into(), - ))? - .get_namespace(); - // Ensure that leaves are adjacent - if *right_index != left_index + 1 { - return Ok(Err(())); - } - // And that our target namespace is in between the leaves' - // namespaces - if namespace <= left_ns || namespace >= right_ns { - return Ok(Err(())); - } - // Verify the boundary proofs - if >::verify(root, left_proof.index(), left_proof)? - .is_err() - { - return Ok(Err(())); - } - if >::verify(root, right_proof.index(), right_proof)? - .is_err() - { - return Ok(Err(())); - } - } - - Ok(Ok(())) - } - - fn verify_presence_proof( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - let mut last_idx: Option = None; - for (idx, proof) in self.proofs.iter().enumerate() { - let leaf_index = self.first_index + idx as u64; - if >::verify(root, leaf_index, proof)?.is_err() { - return Ok(Err(())); - } - if proof - .elem() - .ok_or(PrimitivesError::InconsistentStructureError( - "Missing namespace element".into(), - ))? - .get_namespace() - != namespace - { - return Ok(Err(())); - } - // Indices must be sequential, this checks that there are no gaps in the - // namespace - if let Some(prev_index) = last_idx { - if leaf_index != prev_index + 1 { - return Ok(Err(())); - } - last_idx = Some(leaf_index); - } - } - // Verify that the proof contains the left boundary of the namespace - if self - .verify_left_namespace_boundary(root, namespace) - .is_err() - { - return Ok(Err(())); - } - - // Verify that the proof contains the right boundary of the namespace - if self - .verify_right_namespace_boundary(root, namespace) - .is_err() - { - return Ok(Err(())); - } - - Ok(Ok(())) - } -} - impl NMT where H: DigestAlgorithm + BindNamespace + Clone, diff --git a/primitives/src/merkle_tree/namespaced_merkle_tree/proof.rs b/primitives/src/merkle_tree/namespaced_merkle_tree/proof.rs new file mode 100644 index 000000000..4d196d66a --- /dev/null +++ b/primitives/src/merkle_tree/namespaced_merkle_tree/proof.rs @@ -0,0 +1,269 @@ +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{string::ToString, vec::Vec}; +use core::{fmt::Debug, marker::PhantomData}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use typenum::Unsigned; + +use crate::{ + errors::{PrimitivesError, VerificationResult}, + merkle_tree::{internal::MerkleProof, DigestAlgorithm, MerkleTreeScheme, NodeValue}, +}; + +use super::{ + hash::NamespacedHash, BindNamespace, Element, InnerTree, Namespace, NamespaceProof, Namespaced, +}; + +/// Indicates whether the namespace proof represents a populated set or an empty +/// set +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub(crate) enum NamespaceProofType { + Presence, + Absence, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(bound = "E: CanonicalSerialize + CanonicalDeserialize, + T: CanonicalSerialize + CanonicalDeserialize,")] +/// Namespace Proof +pub struct NaiveNamespaceProof +where + E: Element + Namespaced, + T: NodeValue, + H: DigestAlgorithm + BindNamespace, + N: Namespace, + Arity: Unsigned, +{ + pub(crate) proof_type: NamespaceProofType, + // TODO(#140) Switch to a batch proof + pub(crate) proofs: Vec, Arity>>, + pub(crate) left_boundary_proof: Option, Arity>>, + pub(crate) right_boundary_proof: Option, Arity>>, + pub(crate) first_index: u64, + pub(crate) phantom: PhantomData, +} +impl NamespaceProof for NaiveNamespaceProof +where + E: Element + Namespaced, + T: NodeValue, + H: DigestAlgorithm + BindNamespace, + N: Namespace, + Arity: Unsigned, +{ + type Leaf = E; + type Node = T; + type Namespace = N; + + fn get_namespace_leaves(&self) -> Vec<&Self::Leaf> { + let num_leaves = match self.proof_type { + NamespaceProofType::Presence => self.proofs.len(), + NamespaceProofType::Absence => 0, + }; + self.proofs + .iter() + // This unwrap is safe assuming that the proof is valid + .map(|proof| proof.elem().unwrap()) + .take(num_leaves) + .collect_vec() + } + + fn verify( + &self, + root: &NamespacedHash, + namespace: N, + ) -> Result { + match self.proof_type { + NamespaceProofType::Presence => self.verify_presence_proof(root, namespace), + NamespaceProofType::Absence => self.verify_absence_proof(root, namespace), + } + } +} + +impl NaiveNamespaceProof +where + E: Element + Namespaced, + T: NodeValue, + H: DigestAlgorithm + BindNamespace, + N: Namespace, + Arity: Unsigned, +{ + fn verify_left_namespace_boundary( + &self, + root: &NamespacedHash, + namespace: N, + ) -> Result { + if let Some(boundary_proof) = self.left_boundary_proof.as_ref() { + // If there is a leaf to the left of the namespace range, check that it is less + // than the target namespace + if boundary_proof + .elem() + .ok_or(PrimitivesError::InconsistentStructureError( + "Boundary proof does not contain an element".into(), + ))? + .get_namespace() + >= namespace + || *boundary_proof.index() != self.first_index - 1 + { + return Ok(Err(())); + } + // Verify the boundary proof + if >::verify(root, boundary_proof.index(), boundary_proof)? + .is_err() + { + return Ok(Err(())); + } + } else { + // If there is no left boundary, ensure that target namespace is the tree's + // minimum namespace + if root.min_namespace != namespace { + return Ok(Err(())); + } + } + Ok(Ok(())) + } + + fn verify_right_namespace_boundary( + &self, + root: &NamespacedHash, + namespace: N, + ) -> Result { + if let Some(boundary_proof) = self.right_boundary_proof.as_ref() { + // If there is a leaf to the left of the namespace range, check that it is less + // than the target namespace + if boundary_proof + .elem() + .ok_or(PrimitivesError::InconsistentStructureError( + "Boundary proof does not contain an element".to_string(), + ))? + .get_namespace() + <= namespace + || *boundary_proof.index() != self.first_index + self.proofs.len() as u64 + { + return Ok(Err(())); + } + // Verify the boundary proof + if >::verify(root, boundary_proof.index(), boundary_proof)? + .is_err() + { + return Ok(Err(())); + } + } else { + // If there is no left boundary, ensure that target namespace is the tree's + // minimum namespace + if root.max_namespace != namespace { + return Ok(Err(())); + } + } + Ok(Ok(())) + } + + fn verify_absence_proof( + &self, + root: &NamespacedHash, + namespace: N, + ) -> Result { + if namespace < root.min_namespace || namespace > root.max_namespace { + // Easy case where the namespace isn't covered by the range of the tree root + return Ok(Ok(())); + } else { + // Harder case: Find an element whose namespace is greater than our + // target and show that the namespace to the left is less than our + // target + let left_proof = &self.left_boundary_proof.as_ref().cloned().ok_or( + PrimitivesError::InconsistentStructureError( + "Left Boundary proof must be present".into(), + ), + )?; + let right_proof = &self.right_boundary_proof.as_ref().cloned().ok_or( + PrimitivesError::InconsistentStructureError( + "Right boundary proof must be present".into(), + ), + )?; + let left_index = left_proof.index(); + let left_ns = left_proof + .elem() + .ok_or(PrimitivesError::InconsistentStructureError( + "The left boundary proof is missing an element".into(), + ))? + .get_namespace(); + let right_index = right_proof.index(); + let right_ns = right_proof + .elem() + .ok_or(PrimitivesError::InconsistentStructureError( + "The left boundary proof is missing an element".into(), + ))? + .get_namespace(); + // Ensure that leaves are adjacent + if *right_index != left_index + 1 { + return Ok(Err(())); + } + // And that our target namespace is in between the leaves' + // namespaces + if namespace <= left_ns || namespace >= right_ns { + return Ok(Err(())); + } + // Verify the boundary proofs + if >::verify(root, left_proof.index(), left_proof)? + .is_err() + { + return Ok(Err(())); + } + if >::verify(root, right_proof.index(), right_proof)? + .is_err() + { + return Ok(Err(())); + } + } + + Ok(Ok(())) + } + + fn verify_presence_proof( + &self, + root: &NamespacedHash, + namespace: N, + ) -> Result { + let mut last_idx: Option = None; + for (idx, proof) in self.proofs.iter().enumerate() { + let leaf_index = self.first_index + idx as u64; + if >::verify(root, leaf_index, proof)?.is_err() { + return Ok(Err(())); + } + if proof + .elem() + .ok_or(PrimitivesError::InconsistentStructureError( + "Missing namespace element".into(), + ))? + .get_namespace() + != namespace + { + return Ok(Err(())); + } + // Indices must be sequential, this checks that there are no gaps in the + // namespace + if let Some(prev_index) = last_idx { + if leaf_index != prev_index + 1 { + return Ok(Err(())); + } + last_idx = Some(leaf_index); + } + } + // Verify that the proof contains the left boundary of the namespace + if self + .verify_left_namespace_boundary(root, namespace) + .is_err() + { + return Ok(Err(())); + } + + // Verify that the proof contains the right boundary of the namespace + if self + .verify_right_namespace_boundary(root, namespace) + .is_err() + { + return Ok(Err(())); + } + + Ok(Ok(())) + } +} From 86f221f47c43b6b46d180ac48ad90858b121a5ab Mon Sep 17 00:00:00 2001 From: chancharles92 Date: Wed, 14 Jun 2023 17:31:12 -0400 Subject: [PATCH 2/2] feat: non-native ECC circuit APIs: followup (#302) * add select and enforce_eq apis for emulated vars * add derived traits and to_vec * make rescue sponge apis public * cargo fmt * update changelog --------- Co-authored-by: MRain --- CHANGELOG.md | 1 + primitives/src/rescue/sponge.rs | 8 +-- relation/src/gadgets/ecc/non_native.rs | 94 ++++++++++++++++++++++++- relation/src/gadgets/emulated.rs | 95 +++++++++++++++++++++++++- 4 files changed, 192 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab855e57d..3565cfcdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and follow [semantic versioning](https://semver.org/) for our releases. - [#251](https://github.com/EspressoSystems/jellyfish/pull/251) add sign_key_ref api for BLSKeyPair - [#297](https://github.com/EspressoSystems/jellyfish/pull/297) Updated `tagged-base64` dependency to the `crates.io` package - [#299](https://github.com/EspressoSystems/jellyfish/pull/299) For Merkle tree, `DigestAlgorithm` now returns a `Result` type. +- [#302](https://github.com/EspressoSystems/jellyfish/pull/302) Followup APIs for non-native ECC circuit support. ### Removed diff --git a/primitives/src/rescue/sponge.rs b/primitives/src/rescue/sponge.rs index f4b22ed29..aed4a9983 100644 --- a/primitives/src/rescue/sponge.rs +++ b/primitives/src/rescue/sponge.rs @@ -27,7 +27,7 @@ struct RescueSponge { /// CRHF #[derive(Debug, Clone)] -pub(crate) struct RescueCRHF { +pub struct RescueCRHF { sponge: RescueSponge, } @@ -47,7 +47,7 @@ impl RescueCRHF { /// multiple of RATE /// /// [padding]: https://en.wikipedia.org/wiki/Padding_(cryptography)#Bit_padding - pub(crate) fn sponge_with_bit_padding(input: &[F], num_outputs: usize) -> Vec { + pub fn sponge_with_bit_padding(input: &[F], num_outputs: usize) -> Vec { let mut padded = input.to_vec(); padded.push(F::one()); pad_with_zeros(&mut padded, CRHF_RATE); @@ -60,7 +60,7 @@ impl RescueCRHF { /// overall length to be a multiple of RATE. /// /// [padding]: https://en.wikipedia.org/wiki/Padding_(cryptography)#Zero_padding - pub(crate) fn sponge_with_zero_padding(input: &[F], num_outputs: usize) -> Vec { + pub fn sponge_with_zero_padding(input: &[F], num_outputs: usize) -> Vec { let mut padded = input.to_vec(); pad_with_zeros(&mut padded, CRHF_RATE); Self::sponge_no_padding(padded.as_slice(), num_outputs) @@ -70,7 +70,7 @@ impl RescueCRHF { /// Sponge hashing based on rescue permutation for RATE 3 and CAPACITY 1. It /// allows inputs with length that is a multiple of `CRHF_RATE` and /// returns a vector of `num_outputs` elements. - pub(crate) fn sponge_no_padding(input: &[F], num_output: usize) -> Result, RescueError> { + pub fn sponge_no_padding(input: &[F], num_output: usize) -> Result, RescueError> { if input.len() % CRHF_RATE != 0 { return Err(RescueError::ParameterError( "Rescue sponge Error : input to sponge hashing function is not multiple of RATE." diff --git a/relation/src/gadgets/ecc/non_native.rs b/relation/src/gadgets/ecc/non_native.rs index 2aff54f1e..73bf5858e 100644 --- a/relation/src/gadgets/ecc/non_native.rs +++ b/relation/src/gadgets/ecc/non_native.rs @@ -10,11 +10,12 @@ use super::Point; use crate::{ errors::CircuitError, gadgets::{EmulatedVariable, EmulationConfig}, - PlonkCircuit, + BoolVar, PlonkCircuit, }; use ark_ff::PrimeField; /// The variable represents an EC point in the emulated field. +#[derive(Debug, Clone)] pub struct EmulatedPointVariable(pub EmulatedVariable, pub EmulatedVariable); impl PlonkCircuit { @@ -110,6 +111,34 @@ impl PlonkCircuit { self.emulated_ecc_add_gate(a, b, &c, d)?; Ok(c) } + + /// Obtain an emulated point variable of the conditional selection from 2 + /// emulated point variables. `b` is a boolean variable that indicates + /// selection of P_b from (P0, P1). + /// Return error if invalid input parameters are provided. + pub fn binary_emulated_point_vars_select>( + &mut self, + b: BoolVar, + point0: &EmulatedPointVariable, + point1: &EmulatedPointVariable, + ) -> Result, CircuitError> { + let select_x = self.conditional_select_emulated(b, &point0.0, &point1.0)?; + let select_y = self.conditional_select_emulated(b, &point0.1, &point1.1)?; + + Ok(EmulatedPointVariable::(select_x, select_y)) + } + + /// Constrain two emulated point variables to be the same. + /// Return error if the input point variables are invalid. + pub fn enforce_emulated_point_equal>( + &mut self, + point0: &EmulatedPointVariable, + point1: &EmulatedPointVariable, + ) -> Result<(), CircuitError> { + self.enforce_emulated_var_equal(&point0.0, &point1.0)?; + self.enforce_emulated_var_equal(&point0.1, &point1.1)?; + Ok(()) + } } #[cfg(test)] @@ -164,4 +193,67 @@ mod tests { .unwrap(); assert!(circuit.check_circuit_satisfiability(&[]).is_err()); } + + #[test] + fn test_emulated_point_select() { + test_emulated_point_select_helper::(); + } + + fn test_emulated_point_select_helper() + where + E: EmulationConfig + SWToTEConParam, + F: PrimeField, + P: SWCurveConfig, + { + let mut rng = jf_utils::test_rng(); + let p1 = Projective::

::rand(&mut rng).into_affine(); + let p2 = Projective::

::rand(&mut rng).into_affine(); + let p1: Point = (&p1).into(); + let p2: Point = (&p2).into(); + + let mut circuit = PlonkCircuit::::new_turbo_plonk(); + + let var_p1 = circuit.create_emulated_point_variable(p1).unwrap(); + let var_p2 = circuit.create_emulated_point_variable(p2).unwrap(); + let b = circuit.create_boolean_variable(true).unwrap(); + let var_p3 = circuit + .binary_emulated_point_vars_select(b, &var_p1, &var_p2) + .unwrap(); + assert_eq!(circuit.emulated_point_witness(&var_p3).unwrap(), p2); + assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); + *circuit.witness_mut(var_p3.0 .0[0]) = F::zero(); + assert!(circuit.check_circuit_satisfiability(&[]).is_err()); + } + + #[test] + fn test_enforce_emulated_point_eq() { + test_enforce_emulated_point_eq_helper::(); + } + + fn test_enforce_emulated_point_eq_helper() + where + E: EmulationConfig + SWToTEConParam, + F: PrimeField, + P: SWCurveConfig, + { + let mut rng = jf_utils::test_rng(); + let p1 = Projective::

::rand(&mut rng).into_affine(); + let p2 = (p1 + Projective::

::generator()).into_affine(); + let p1: Point = (&p1).into(); + let p2: Point = (&p2).into(); + + let mut circuit = PlonkCircuit::::new_turbo_plonk(); + + let var_p1 = circuit.create_emulated_point_variable(p1).unwrap(); + let var_p2 = circuit.create_emulated_point_variable(p2).unwrap(); + let var_p3 = circuit.create_emulated_point_variable(p1).unwrap(); + circuit + .enforce_emulated_point_equal(&var_p1, &var_p3) + .unwrap(); + assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); + circuit + .enforce_emulated_point_equal(&var_p1, &var_p2) + .unwrap(); + assert!(circuit.check_circuit_satisfiability(&[]).is_err()); + } } diff --git a/relation/src/gadgets/emulated.rs b/relation/src/gadgets/emulated.rs index a14ad0d72..9482a4530 100644 --- a/relation/src/gadgets/emulated.rs +++ b/relation/src/gadgets/emulated.rs @@ -12,7 +12,7 @@ //! componenet, with modulus 2^T, will be divided into limbs each with B bits //! where 2^{2B} < p. -use crate::{errors::CircuitError, Circuit, PlonkCircuit, Variable}; +use crate::{errors::CircuitError, BoolVar, Circuit, PlonkCircuit, Variable}; use ark_ff::PrimeField; use ark_std::{string::ToString, vec, vec::Vec, One, Zero}; use core::marker::PhantomData; @@ -71,8 +71,16 @@ where } /// The variable represents an element in the emulated field. +#[derive(Debug, Clone)] pub struct EmulatedVariable(pub(crate) Vec, PhantomData); +impl EmulatedVariable { + /// Return the list of variables that simulate the field element + pub fn to_vec(&self) -> Vec { + self.0.clone() + } +} + impl PlonkCircuit { /// Return the witness point for the circuit pub fn emulated_witness>( @@ -542,6 +550,44 @@ impl PlonkCircuit { Ok(c) } + /// Obtain an emulated variable of the conditional selection from 2 emulated + /// variables. `b` is a boolean variable that indicates selection of P_b + /// from (P0, P1). + /// Return error if invalid input parameters are provided. + pub fn conditional_select_emulated>( + &mut self, + b: BoolVar, + p0: &EmulatedVariable, + p1: &EmulatedVariable, + ) -> Result, CircuitError> { + self.check_var_bound(b.into())?; + self.check_vars_bound(&p0.0[..])?; + self.check_vars_bound(&p1.0[..])?; + + let mut vals = vec![]; + for (&x_0, &x_1) in p0.0.iter().zip(p1.0.iter()) { + let selected = self.conditional_select(b, x_0, x_1)?; + vals.push(selected); + } + + Ok(EmulatedVariable::(vals, PhantomData::)) + } + + /// Constrain two emulated variables to be the same. + /// Return error if the input variables are invalid. + pub fn enforce_emulated_var_equal>( + &mut self, + p0: &EmulatedVariable, + p1: &EmulatedVariable, + ) -> Result<(), CircuitError> { + self.check_vars_bound(&p0.0[..])?; + self.check_vars_bound(&p1.0[..])?; + for (&a, &b) in p0.0.iter().zip(p1.0.iter()) { + self.enforce_equal(a, b)?; + } + Ok(()) + } + /// Given an emulated field element `a`, return `a mod F::MODULUS` in the /// native field. fn mod_to_native_field>( @@ -709,4 +755,51 @@ mod tests { .check_circuit_satisfiability(&from_emulated_field(x)) .is_err()); } + + #[test] + fn test_select() { + test_select_helper::(); + test_select_helper::(); + } + + fn test_select_helper() + where + E: EmulationConfig, + F: PrimeField, + { + let mut circuit = PlonkCircuit::::new_turbo_plonk(); + let var_x = circuit.create_emulated_variable(E::one()).unwrap(); + let overflow = E::from(E::MODULUS.into() - 1u64); + let var_y = circuit.create_emulated_variable(overflow).unwrap(); + let b = circuit.create_boolean_variable(true).unwrap(); + let var_z = circuit + .conditional_select_emulated(b, &var_x, &var_y) + .unwrap(); + assert_eq!(circuit.emulated_witness(&var_z).unwrap(), overflow); + assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); + *circuit.witness_mut(var_z.0[0]) = F::zero(); + assert!(circuit.check_circuit_satisfiability(&[]).is_err()); + } + + #[test] + fn test_enforce_equal() { + test_enforce_equal_helper::(); + test_enforce_equal_helper::(); + } + + fn test_enforce_equal_helper() + where + E: EmulationConfig, + F: PrimeField, + { + let mut circuit = PlonkCircuit::::new_turbo_plonk(); + let var_x = circuit.create_emulated_variable(E::one()).unwrap(); + let overflow = E::from(E::MODULUS.into() - 1u64); + let var_y = circuit.create_emulated_variable(overflow).unwrap(); + let var_z = circuit.create_emulated_variable(overflow).unwrap(); + circuit.enforce_emulated_var_equal(&var_y, &var_z).unwrap(); + assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); + circuit.enforce_emulated_var_equal(&var_x, &var_y).unwrap(); + assert!(circuit.check_circuit_satisfiability(&[]).is_err()); + } }