diff --git a/src/backends/plonky2/mainpod/mod.rs b/src/backends/plonky2/mainpod/mod.rs index c438217c..a35b22f8 100644 --- a/src/backends/plonky2/mainpod/mod.rs +++ b/src/backends/plonky2/mainpod/mod.rs @@ -804,7 +804,10 @@ impl Pod for MainPod { #[cfg(test)] pub mod tests { - use std::{any::Any, collections::HashSet}; + use std::{ + any::Any, + collections::{HashMap, HashSet}, + }; use num::{BigUint, One}; @@ -821,11 +824,15 @@ pub mod tests { zu_kyc_sign_dict_builders, EthDosHelper, }, frontend::{ - self, literal, CustomPredicateBatchBuilder, MainPodBuilder, StatementTmplBuilder as STB, + self, literal, CustomPredicateBatchBuilder, MainPodBuilder, SignedDictBuilder, + StatementTmplBuilder as STB, }, + lang::parse, middleware::{ - self, containers::Set, CustomPredicateRef, NativePredicate as NP, Signer as _, - DEFAULT_VD_LIST, DEFAULT_VD_SET, + self, + containers::{Dictionary, Set}, + CustomPredicateRef, NativePredicate as NP, Signer as _, Value, DEFAULT_VD_LIST, + DEFAULT_VD_SET, EMPTY_HASH, }, }; @@ -859,6 +866,93 @@ pub mod tests { Ok(pod.verify()?) } + #[test] + fn test_main_dict_updates() -> frontend::Result<()> { + let params = &middleware::Params::default(); + let mut vds = DEFAULT_VD_LIST.clone(); + vds.push(rec_main_pod_circuit_data(params).1.verifier_only.clone()); + let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap(); + let signer = Signer(SecretKey(1u32.into())); + + let empty_dict = Dictionary::new_empty(params.max_depth_mt_containers); + assert_eq!(empty_dict.commitment(), EMPTY_HASH); + + let mut hash_map: HashMap = HashMap::new(); + hash_map.insert(middleware::Key::from("i"), Value::from(123456)); + let int_dict = Dictionary::new(params.max_depth_mt_containers, hash_map)?; + + let mut before_dict_builder = SignedDictBuilder::new(params); + before_dict_builder.insert("a", "A"); + before_dict_builder.insert("i", 123456); + let before_dict = before_dict_builder.sign(&signer)?; + + let mut after_dict_builder = SignedDictBuilder::new(params); + after_dict_builder.insert("a", "B"); + after_dict_builder.insert("i", 123456); + let after_dict = after_dict_builder.sign(&signer)?; + + let mut pod_builder = MainPodBuilder::new(params, &vd_set); + pod_builder.pub_op(frontend::Operation::dict_signed_by(&before_dict))?; + pod_builder.pub_op(frontend::Operation::dict_signed_by(&after_dict))?; + pod_builder.pub_op(frontend::Operation::dict_insert( + int_dict.clone(), + empty_dict, + "i", + 123456, + ))?; + pod_builder.pub_op(frontend::Operation::dict_insert( + before_dict.dict.clone(), + int_dict.clone(), + "a", + "A", + ))?; + pod_builder.pub_op(frontend::Operation::dict_update( + after_dict.dict.clone(), + before_dict.dict.clone(), + "a", + "B", + ))?; + + let prover = Prover {}; + let proven = pod_builder.prove(&prover)?; + crate::measure_gates_print!(); + let pod = (proven.pod as Box).downcast::().unwrap(); + + pod.verify()?; + + // The exact_match_pod() search doesn't know about syntactic sugar + // statements, so we need to use Container* not Dict*. + let podlang_input1 = r#" + REQUEST( + SignedBy(?before, ?signer) + SignedBy(?after, ?signer) + ContainerInsert(?int_only, {}, "i", 123456) + ContainerInsert(?before, ?int_only, "a", "A") + ContainerUpdate(?after, ?before, "a", "B") + ) + "#; + + let request1 = parse(podlang_input1, params, &[])?.request; + assert!(request1.exact_match_pod(&*pod).is_ok()); + + // Try the same again but use the null literal instead of {}, since + // they're intended to be equivalent (equating to EMPTY_HASH) + let podlang_input2 = r#" + REQUEST( + SignedBy(?before, ?signer) + SignedBy(?after, ?signer) + ContainerInsert(?int_only, null, "i", 123456) + ContainerInsert(?before, ?int_only, "a", "A") + ContainerUpdate(?after, ?before, "a", "B") + ) + "#; + + let request2 = parse(podlang_input2, params, &[])?.request; + assert!(request2.exact_match_pod(&*pod).is_ok()); + + Ok(()) + } + // `RUST_LOG=pod2::backends=debug cargo test --release --no-default-features --features=backend_plonky2,mem_cache,zk,metrics test_measure_main_pod -- --nocapture --ignored` #[ignore] #[test] diff --git a/src/backends/plonky2/mock/mainpod.rs b/src/backends/plonky2/mock/mainpod.rs index 2fe68078..3c39f7f0 100644 --- a/src/backends/plonky2/mock/mainpod.rs +++ b/src/backends/plonky2/mock/mainpod.rs @@ -372,7 +372,7 @@ impl Pod for MockMainPod { #[cfg(test)] pub mod tests { - use std::any::Any; + use std::{any::Any, collections::HashMap}; use super::*; use crate::{ @@ -381,8 +381,9 @@ pub mod tests { great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_pod_request, zu_kyc_sign_dict_builders, MOCK_VD_SET, }, - frontend, middleware, - middleware::{Signer as _, Value}, + frontend::{self, MainPodBuilder, SignedDictBuilder}, + lang::parse, + middleware::{self, containers::Dictionary, Signer as _, Value}, }; #[test] @@ -447,4 +448,92 @@ pub mod tests { Ok(()) } + + #[test] + fn test_mock_main_dict_updates() -> frontend::Result<()> { + let params = &middleware::Params::default(); + let vd_set = &*MOCK_VD_SET; + let signer = Signer(SecretKey(1u32.into())); + + let empty_dict = Dictionary::new_empty(params.max_depth_mt_containers); + assert_eq!(empty_dict.commitment(), EMPTY_HASH); + + let mut hash_map: HashMap = HashMap::new(); + hash_map.insert(middleware::Key::from("i"), Value::from(123456)); + let int_dict = Dictionary::new(params.max_depth_mt_containers, hash_map)?; + + let mut before_dict_builder = SignedDictBuilder::new(params); + before_dict_builder.insert("a", "A"); + before_dict_builder.insert("i", 123456); + let before_dict = before_dict_builder.sign(&signer)?; + + let mut after_dict_builder = SignedDictBuilder::new(params); + after_dict_builder.insert("a", "B"); + after_dict_builder.insert("i", 123456); + let after_dict = after_dict_builder.sign(&signer)?; + + let mut pod_builder = MainPodBuilder::new(params, vd_set); + pod_builder.pub_op(frontend::Operation::dict_signed_by(&before_dict))?; + pod_builder.pub_op(frontend::Operation::dict_signed_by(&after_dict))?; + pod_builder.pub_op(frontend::Operation::dict_insert( + int_dict.clone(), + empty_dict, + "i", + 123456, + ))?; + pod_builder.pub_op(frontend::Operation::dict_insert( + before_dict.dict.clone(), + int_dict.clone(), + "a", + "A", + ))?; + pod_builder.pub_op(frontend::Operation::dict_update( + after_dict.dict.clone(), + before_dict.dict.clone(), + "a", + "B", + ))?; + + let prover = MockProver {}; + let proven = pod_builder.prove(&prover)?; + let pod = (proven.pod as Box) + .downcast::() + .unwrap(); + + println!("{:#}", pod); + + pod.verify()?; + + // The exact_match_pod() search doesn't know about syntactic sugar + // statements, so we need to use Container* not Dict*. + let podlang_input1 = r#" + REQUEST( + SignedBy(?before, ?signer) + SignedBy(?after, ?signer) + ContainerInsert(?int_only, {}, "i", 123456) + ContainerInsert(?before, ?int_only, "a", "A") + ContainerUpdate(?after, ?before, "a", "B") + ) + "#; + + let request1 = parse(podlang_input1, params, &[])?.request; + assert!(request1.exact_match_pod(&*pod).is_ok()); + + // Try the same again but use the null literal instead of {}, since + // they're intended to be equivalent (equating to EMPTY_HASH) + let podlang_input2 = r#" + REQUEST( + SignedBy(?before, ?signer) + SignedBy(?after, ?signer) + ContainerInsert(?int_only, null, "i", 123456) + ContainerInsert(?before, ?int_only, "a", "A") + ContainerUpdate(?after, ?before, "a", "B") + ) + "#; + + let request2 = parse(podlang_input2, params, &[])?.request; + assert!(request2.exact_match_pod(&*pod).is_ok()); + + Ok(()) + } } diff --git a/src/lang/grammar.pest b/src/lang/grammar.pest index 1dbf6b45..5ed68699 100644 --- a/src/lang/grammar.pest +++ b/src/lang/grammar.pest @@ -11,7 +11,7 @@ COMMENT = _{ "//" ~ (!NEWLINE ~ ANY)* } // Define rules for identifiers (predicate names, variable names without '?') // Must start with alpha or _, followed by alpha, numeric, or _ -identifier = @{ !("private") ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } +identifier = @{ !("private" | "null") ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } private_kw = { "private:" } @@ -65,12 +65,14 @@ literal_value = { literal_bool | literal_raw | literal_string | - literal_int + literal_int | + literal_null } // Primitive literal types literal_int = @{ "-"? ~ ASCII_DIGIT+ } literal_bool = @{ "true" | "false" } +literal_null = @{ "null" } // hash_hex: 0x followed by exactly 32 PAIRS of hex digits (64 hex characters) // representing a 32-byte value in big-endian order diff --git a/src/lang/parser.rs b/src/lang/parser.rs index 0fc4fd42..c0584b09 100644 --- a/src/lang/parser.rs +++ b/src/lang/parser.rs @@ -98,9 +98,17 @@ mod tests { assert_parses(Rule::literal_int, "-45"); assert_parses(Rule::literal_int, "0"); assert_fails(Rule::test_literal_int, "1.23"); // Use test_literal_int rule - // Bool + + // Bool assert_parses(Rule::literal_bool, "true"); + assert_fails(Rule::literal_null, "TRUE"); assert_parses(Rule::literal_bool, "false"); + assert_fails(Rule::literal_null, "FALSE"); + + // Null + assert_parses(Rule::literal_null, "null"); + assert_fails(Rule::literal_null, "NULL"); + assert_fails(Rule::literal_null, "0"); // Raw - Require 64 hex digits (32 bytes, equal to 4 * 64-bit field elements) assert_parses( diff --git a/src/lang/processor.rs b/src/lang/processor.rs index 32b296e7..421248a5 100644 --- a/src/lang/processor.rs +++ b/src/lang/processor.rs @@ -16,7 +16,7 @@ use crate::{ lang::parser::Rule, middleware::{ self, CustomPredicateBatch, CustomPredicateRef, Key, NativePredicate, Params, Predicate, - StatementTmpl, StatementTmplArg, Value, Wildcard, F, VALUE_SIZE, + StatementTmpl, StatementTmplArg, Value, Wildcard, EMPTY_VALUE, F, VALUE_SIZE, }, }; @@ -752,6 +752,7 @@ fn process_literal_value( let val = inner_lit.as_str().parse::().unwrap(); Ok(Value::from(val)) } + Rule::literal_null => Ok(Value::from(EMPTY_VALUE)), Rule::literal_raw => { let full_literal_str = inner_lit.clone().into_inner().next().unwrap(); let hex_str_no_prefix = full_literal_str @@ -1255,7 +1256,7 @@ mod processor_tests { REQUEST( EQUAL(?A["b"], ?C["d"]) ) - "#; + "#; let pairs = get_document_content_pairs(input)?; let params = Params::default(); let mut ctx = ProcessingContext::new(¶ms); diff --git a/src/middleware/containers.rs b/src/middleware/containers.rs index 8a7f1ccb..826f08ea 100644 --- a/src/middleware/containers.rs +++ b/src/middleware/containers.rs @@ -51,6 +51,11 @@ impl Dictionary { kvs, }) } + + pub fn new_empty(max_depth: usize) -> Self { + Self::new(max_depth, HashMap::new()).unwrap() + } + pub fn commitment(&self) -> Hash { self.mt.root() }