From 17e6c2a092c1577502bd061fbb0d6fbccd4ae0d3 Mon Sep 17 00:00:00 2001 From: Ahmad Afuni Date: Thu, 17 Apr 2025 21:51:02 +1000 Subject: [PATCH] chore(frontend): make Merkle proofs optional (#198) * Make frontend Merkle proofs optional * Code review * Clippy --- .../plonky2/primitives/merkletree_circuit.rs | 2 +- src/examples/mod.rs | 33 ++--------- src/frontend/custom.rs | 2 +- src/frontend/mod.rs | 59 +++++++++++++------ src/frontend/operation.rs | 12 ++++ src/middleware/mod.rs | 36 +++++++++++ src/middleware/operation.rs | 2 +- 7 files changed, 98 insertions(+), 48 deletions(-) diff --git a/src/backends/plonky2/primitives/merkletree_circuit.rs b/src/backends/plonky2/primitives/merkletree_circuit.rs index fef4bd6..ffbd896 100644 --- a/src/backends/plonky2/primitives/merkletree_circuit.rs +++ b/src/backends/plonky2/primitives/merkletree_circuit.rs @@ -548,7 +548,7 @@ pub mod tests { let key = RawValue::from(hash_value(&RawValue::from(5))); let (value, proof) = tree.prove(&key)?; assert_eq!(value, RawValue::from(5)); - assert_eq!(proof.existence, true); + assert!(proof.existence); MerkleTree::verify(max_depth, tree.root(), &proof, &key, &value)?; diff --git a/src/examples/mod.rs b/src/examples/mod.rs index 63296d3..ef9fdb0 100644 --- a/src/examples/mod.rs +++ b/src/examples/mod.rs @@ -2,14 +2,14 @@ pub mod custom; use std::collections::HashSet; -use anyhow::{anyhow, Result}; +use anyhow::Result; use custom::{eth_dos_batch, eth_friend_batch}; use crate::{ backends::plonky2::mock::signedpod::MockSigner, frontend::{MainPodBuilder, SignedPod, SignedPodBuilder}, middleware::{ - containers::Set, CustomPredicateRef, Key, Params, PodType, Statement, TypedValue, Value, + containers::Set, CustomPredicateRef, Params, PodType, Statement, TypedValue, Value, KEY_SIGNER, KEY_TYPE, }, op, @@ -45,16 +45,9 @@ pub fn zu_kyc_pod_builder( pay_stub: &SignedPod, sanction_list: &SignedPod, ) -> Result { - let sanction_set = match sanction_list.get("sanctionList").map(|v| v.typed()) { - Some(TypedValue::Set(s)) => Ok(s), - _ => Err(anyhow!("Missing sanction list!")), - }?; let now_minus_18y: i64 = 1169909388; let now_minus_1y: i64 = 1706367566; - let gov_id_kvs = gov_id.kvs(); - let id_number_value = gov_id_kvs.get(&Key::from("idNumber")).unwrap(); - let mut kyc = MainPodBuilder::new(params); kyc.add_signed_pod(gov_id); kyc.add_signed_pod(pay_stub); @@ -62,8 +55,7 @@ pub fn zu_kyc_pod_builder( kyc.pub_op(op!( set_not_contains, (sanction_list, "sanctionList"), - (gov_id, "idNumber"), - sanction_set.prove_nonexistence(id_number_value)? + (gov_id, "idNumber") ))?; kyc.pub_op(op!(lt, (gov_id, "dateOfBirth"), now_minus_18y))?; kyc.pub_op(op!( @@ -269,8 +261,6 @@ pub fn great_boy_pod_builder( PodType::MockSigned as i64 ))?; for issuer_idx in 0..2 { - let pod_kvs = good_boy_pods[good_boy_idx * 2 + issuer_idx].kvs(); - // Type check great_boy.pub_op(op!( eq, @@ -278,16 +268,10 @@ pub fn great_boy_pod_builder( PodType::MockSigned as i64 ))?; // Each good boy POD comes from a valid issuer - let good_boy_proof = match good_boy_issuers.typed() { - TypedValue::Set(set) => Ok(set), - _ => Err(anyhow!("Invalid good boy issuers!")), - }? - .prove(pod_kvs.get(&Key::from(KEY_SIGNER)).unwrap())?; great_boy.pub_op(op!( set_contains, good_boy_issuers, - (good_boy_pods[good_boy_idx * 2 + issuer_idx], KEY_SIGNER), - good_boy_proof + (good_boy_pods[good_boy_idx * 2 + issuer_idx], KEY_SIGNER) ))?; // Each good boy has 2 good boy pods great_boy.pub_op(op!( @@ -403,12 +387,6 @@ pub fn tickets_pod_builder( expect_consumed: bool, blacklisted_emails: &Set, ) -> Result { - let attendee_email_value = signed_pod - .kvs() - .get(&Key::from("attendeeEmail")) - .unwrap() - .clone(); - let attendee_nin_blacklist_pf = blacklisted_emails.prove_nonexistence(&attendee_email_value)?; let blacklisted_email_set_value = Value::from(TypedValue::Set(blacklisted_emails.clone())); // Create a main pod referencing this signed pod with some statements let mut builder = MainPodBuilder::new(params); @@ -419,8 +397,7 @@ pub fn tickets_pod_builder( builder.pub_op(op!( dict_not_contains, blacklisted_email_set_value, - (signed_pod, "attendeeEmail"), - attendee_nin_blacklist_pf + (signed_pod, "attendeeEmail") ))?; Ok(builder) } diff --git a/src/frontend/custom.rs b/src/frontend/custom.rs index d7e6298..ac2898d 100644 --- a/src/frontend/custom.rs +++ b/src/frontend/custom.rs @@ -233,7 +233,7 @@ mod tests { let eth_dos_batch = eth_dos_batch(¶ms)?; let eth_dos_batch_mw: middleware::CustomPredicateBatch = - Arc::unwrap_or_clone(eth_dos_batch).into(); + Arc::unwrap_or_clone(eth_dos_batch); let fields = eth_dos_batch_mw.to_fields(¶ms); println!("Batch b, serialized: {:?}", fields); diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index a38759e..ff3692d 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -262,9 +262,39 @@ impl MainPodBuilder { } } + /// Fills in auxiliary data if necessary/possible. + fn fill_in_aux(op: Operation) -> Result { + use NativeOperation::{ContainsFromEntries, NotContainsFromEntries}; + use OperationAux as OpAux; + use OperationType::Native; + + let op_type = &op.0; + + match (op_type, &op.2) { + (Native(ContainsFromEntries), OpAux::None) + | (Native(NotContainsFromEntries), OpAux::None) => { + let container = + op.1.get(0) + .and_then(|arg| arg.value()) + .ok_or(anyhow!("Invalid container argument for op {}.", op))?; + let key = + op.1.get(1) + .and_then(|arg| arg.value()) + .ok_or(anyhow!("Invalid key argument for op {}.", op))?; + let proof = if op_type == &Native(ContainsFromEntries) { + container.prove_existence(key)?.1 + } else { + container.prove_nonexistence(key)? + }; + Ok(Operation(op_type.clone(), op.1, OpAux::MerkleProof(proof))) + } + _ => Ok(op), + } + } + fn op(&mut self, public: bool, op: Operation) -> Result { use NativeOperation::*; - let mut op = Self::lower_op(op); + let mut op = Self::fill_in_aux(Self::lower_op(op))?; let Operation(op_type, ref mut args, _) = &mut op; // TODO: argument type checking let pred = op_type.output_predicate().map(Ok).unwrap_or_else(|| { @@ -736,21 +766,21 @@ pub mod build_utils { (custom, $op:expr, $($arg:expr),+) => { $crate::frontend::Operation( $crate::middleware::OperationType::Custom($op), $crate::op_args!($($arg),*), $crate::middleware::OperationAux::None) }; - (dict_contains, $dict:expr, $key:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation( + (dict_contains, $dict:expr, $key:expr, $value:expr) => { $crate::frontend::Operation( $crate::middleware::OperationType::Native($crate::middleware::NativeOperation::DictContainsFromEntries), - $crate::op_args!($dict, $key, $value), $crate::middleware::OperationAux::MerkleProof($aux)) }; - (dict_not_contains, $dict:expr, $key:expr, $aux:expr) => { $crate::frontend::Operation( + $crate::op_args!($dict, $key, $value), $crate::middleware::OperationAux::None) }; + (dict_not_contains, $dict:expr, $key:expr) => { $crate::frontend::Operation( $crate::middleware::OperationType::Native($crate::middleware::NativeOperation::DictNotContainsFromEntries), - $crate::op_args!($dict, $key), $crate::middleware::OperationAux::MerkleProof($aux)) }; - (set_contains, $set:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation( + $crate::op_args!($dict, $key), $crate::middleware::OperationAux::None) }; + (set_contains, $set:expr, $value:expr) => { $crate::frontend::Operation( $crate::middleware::OperationType::Native($crate::middleware::NativeOperation::SetContainsFromEntries), - $crate::op_args!($set, $value), $crate::middleware::OperationAux::MerkleProof($aux)) }; - (set_not_contains, $set:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation( + $crate::op_args!($set, $value), $crate::middleware::OperationAux::None) }; + (set_not_contains, $set:expr, $value:expr) => { $crate::frontend::Operation( $crate::middleware::OperationType::Native($crate::middleware::NativeOperation::SetNotContainsFromEntries), - $crate::op_args!($set, $value), $crate::middleware::OperationAux::MerkleProof($aux)) }; - (array_contains, $array:expr, $index:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation( + $crate::op_args!($set, $value), $crate::middleware::OperationAux::None) }; + (array_contains, $array:expr, $index:expr, $value:expr) => { $crate::frontend::Operation( $crate::middleware::OperationType::Native($crate::middleware::NativeOperation::ArrayContainsFromEntries), - $crate::op_args!($array, $index, $value), $crate::middleware::OperationAux::MerkleProof($aux)) }; + $crate::op_args!($array, $index, $value), $crate::middleware::OperationAux::None) }; } } @@ -781,12 +811,7 @@ pub mod tests { // Check that frontend key-values agree with those embedded in a // SignedPod. fn check_kvs(pod: &SignedPod) -> Result<()> { - let kvs = pod - .kvs - .clone() - .into_iter() - .map(|(k, v)| (k, v)) - .collect::>(); + let kvs = pod.kvs.clone().into_iter().collect::>(); let embedded_kvs = pod .pod .kvs() diff --git a/src/frontend/operation.rs b/src/frontend/operation.rs index 15730de..f4284f0 100644 --- a/src/frontend/operation.rs +++ b/src/frontend/operation.rs @@ -13,6 +13,18 @@ pub enum OperationArg { Entry(String, Value), } +impl OperationArg { + /// Extracts the value underlying literal and `ValueOf` statement + /// operation args. + pub(crate) fn value(&self) -> Option<&Value> { + match self { + Self::Literal(v) => Some(v), + Self::Statement(Statement::ValueOf(_, v)) => Some(v), + _ => None, + } + } +} + impl fmt::Display for OperationArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index b39591c..59a8cb6 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -26,6 +26,8 @@ pub use operation::*; // use serde::{Deserialize, Serialize}; pub use statement::*; +use crate::backends::plonky2::primitives::merkletree::MerkleProof; + pub const SELF: PodId = PodId(SELF_ID_HASH); // TODO: Move all value-related types to to `value.rs` @@ -136,6 +138,16 @@ impl TryFrom<&TypedValue> for i64 { } } +impl TryFrom for Key { + type Error = anyhow::Error; + fn try_from(tv: TypedValue) -> Result { + match tv { + TypedValue::String(s) => Ok(Key::new(s)), + _ => Err(anyhow!("Value {} cannot be converted to a key.", tv)), + } + } +} + impl fmt::Display for TypedValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -218,6 +230,30 @@ impl Value { pub fn raw(&self) -> RawValue { self.raw } + /// Determines Merkle existence proof for `key` in `self` (if applicable). + pub(crate) fn prove_existence<'a>( + &'a self, + key: &'a Value, + ) -> Result<(&'a Value, MerkleProof)> { + match &self.typed() { + TypedValue::Array(a) => match key.typed() { + TypedValue::Int(i) if i >= &0 => a.prove((*i) as usize), + _ => Err(anyhow!("Invalid key {} for container {}.", key, self))?, + }, + TypedValue::Dictionary(d) => d.prove(&key.typed().clone().try_into()?), + TypedValue::Set(s) => Ok((key, s.prove(key)?)), + _ => Err(anyhow!("Invalid container value {}", self.typed())), + } + } + /// Determines Merkle non-existence proof for `key` in `self` (if applicable). + pub(crate) fn prove_nonexistence<'a>(&'a self, key: &'a Value) -> Result { + match &self.typed() { + TypedValue::Array(_) => Err(anyhow!("Arrays do not support `NotContains` operation.")), + TypedValue::Dictionary(d) => d.prove_nonexistence(&key.typed().clone().try_into()?), + TypedValue::Set(s) => s.prove_nonexistence(key), + _ => Err(anyhow!("Invalid container value {}", self.typed())), + } + } } // A Value can be created from any type Into type: bool, string-like, i64, ... diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index 91ca516..22f5d34 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -6,7 +6,7 @@ use plonky2::field::types::Field; // use serde::{Deserialize, Serialize}; use crate::{ - backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree}, + backends::plonky2::primitives::merkletree::MerkleProof, middleware::{ custom::KeyOrWildcard, AnchoredKey, CustomPredicateBatch, CustomPredicateRef, NativePredicate, Params, Predicate, Statement, StatementArg, StatementTmplArg, ToFields,