From 7dddab6dfc4b6a90d7bb3035f21722cec6c6fd0d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 13 Mar 2025 02:02:32 +1000 Subject: [PATCH 1/5] Add branch, leaf and null tree primitives to middleware --- src/backends/plonky2/basetypes.rs | 15 ++ src/backends/plonky2/primitives/merkletree.rs | 10 +- src/frontend/mod.rs | 3 + src/middleware/basetypes.rs | 4 +- src/middleware/operation.rs | 195 +++++++++--------- src/middleware/statement.rs | 46 ++++- 6 files changed, 160 insertions(+), 113 deletions(-) diff --git a/src/backends/plonky2/basetypes.rs b/src/backends/plonky2/basetypes.rs index 12568756..6cab1bee 100644 --- a/src/backends/plonky2/basetypes.rs +++ b/src/backends/plonky2/basetypes.rs @@ -131,6 +131,21 @@ pub fn hash_fields(input: &[F]) -> Hash { Hash(PoseidonHash::hash_no_pad(&input).elements) } +/// Hash function for key-value pairs. Different branch pair hashes to +/// mitigate fake proofs. +pub fn kv_hash(key: &Value, value: Option) -> Hash { + value + .map(|v| { + Hash( + PoseidonHash::hash_no_pad( + &[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat(), + ) + .elements, + ) + }) + .unwrap_or(Hash([GoldilocksField(0); 4])) +} + impl From for Hash { fn from(v: Value) -> Self { Hash(v.0) diff --git a/src/backends/plonky2/primitives/merkletree.rs b/src/backends/plonky2/primitives/merkletree.rs index 7ec4636c..b3b2acbc 100644 --- a/src/backends/plonky2/primitives/merkletree.rs +++ b/src/backends/plonky2/primitives/merkletree.rs @@ -1,13 +1,13 @@ //! Module that implements the MerkleTree specified at //! https://0xparc.github.io/pod2/merkletree.html . use anyhow::{anyhow, Result}; -use plonky2::field::goldilocks_field::GoldilocksField; use std::collections::HashMap; use std::fmt; use std::iter::IntoIterator; use crate::backends::counter; use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, F, NULL}; +use crate::middleware::kv_hash; /// Implements the MerkleTree specified at /// https://0xparc.github.io/pod2/merkletree.html @@ -174,14 +174,6 @@ impl MerkleTree { } } -/// Hash function for key-value pairs. Different branch pair hashes to -/// mitigate fake proofs. -pub fn kv_hash(key: &Value, value: Option) -> Hash { - value - .map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat())) - .unwrap_or(Hash([GoldilocksField(0); 4])) -} - impl<'a> IntoIterator for &'a MerkleTree { type Item = (&'a Value, &'a Value); type IntoIter = Iter<'a>; diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 69a42363..a2aa2ab4 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -437,6 +437,9 @@ impl MainPodBuilder { }, ContainsFromEntries => self.op_args_entries(public, args)?, NotContainsFromEntries => self.op_args_entries(public, args)?, + BranchesFromEntries => self.op_args_entries(public, args)?, + LeafFromEntries => self.op_args_entries(public, args)?, + IsNullTree => self.op_args_entries(public, args)?, SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) { ( OperationArg::Statement(Statement( diff --git a/src/middleware/basetypes.rs b/src/middleware/basetypes.rs index 3f893e84..b9006c1f 100644 --- a/src/middleware/basetypes.rs +++ b/src/middleware/basetypes.rs @@ -35,6 +35,6 @@ /// then the Value, Hash and F types would come from the plonky3 backend. #[cfg(feature = "backend_plonky2")] pub use crate::backends::plonky2::basetypes::{ - hash_fields, hash_str, hash_value, Hash, Value, EMPTY, F, HASH_SIZE, NULL, SELF_ID_HASH, - VALUE_SIZE, + hash_fields, hash_str, hash_value, kv_hash, Hash, Value, EMPTY, F, HASH_SIZE, NULL, + SELF_ID_HASH, VALUE_SIZE, }; diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index cdfcd020..1555eca8 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -3,7 +3,9 @@ use std::fmt; use anyhow::{anyhow, Result}; use super::{CustomPredicateRef, NativePredicate, Statement, StatementArg}; -use crate::middleware::{AnchoredKey, Params, Predicate, Value, SELF}; +use crate::middleware::{ + hash_fields, kv_hash, AnchoredKey, Hash, Params, Predicate, Value, EMPTY, SELF, VALUE_SIZE, +}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum OperationType { @@ -25,9 +27,12 @@ pub enum NativeOperation { LtToNotEqual = 9, ContainsFromEntries = 10, NotContainsFromEntries = 11, - SumOf = 13, - ProductOf = 14, - MaxOf = 15, + BranchesFromEntries = 12, + LeafFromEntries = 13, + IsNullTree = 14, + SumOf = 15, + ProductOf = 16, + MaxOf = 17, } impl OperationType { @@ -59,6 +64,13 @@ impl OperationType { NativeOperation::NotContainsFromEntries => { Some(Predicate::Native(NativePredicate::NotContains)) } + NativeOperation::BranchesFromEntries => { + Some(Predicate::Native(NativePredicate::Branches)) + } + NativeOperation::LeafFromEntries => Some(Predicate::Native(NativePredicate::Leaf)), + + NativeOperation::IsNullTree => Some(Predicate::Native(NativePredicate::IsNullTree)), + NativeOperation::SumOf => Some(Predicate::Native(NativePredicate::SumOf)), NativeOperation::ProductOf => Some(Predicate::Native(NativePredicate::ProductOf)), NativeOperation::MaxOf => Some(Predicate::Native(NativePredicate::MaxOf)), @@ -83,6 +95,9 @@ pub enum Operation { LtToNotEqual(Statement), ContainsFromEntries(Statement, Statement), NotContainsFromEntries(Statement, Statement), + BranchesFromEntries(Statement, Statement, Statement), + LeafFromEntries(Statement, Statement, Statement), + IsNullTree(Statement), SumOf(Statement, Statement, Statement), ProductOf(Statement, Statement, Statement), MaxOf(Statement, Statement, Statement), @@ -106,6 +121,9 @@ impl Operation { Self::LtToNotEqual(_) => OT::Native(LtToNotEqual), Self::ContainsFromEntries(_, _) => OT::Native(ContainsFromEntries), Self::NotContainsFromEntries(_, _) => OT::Native(NotContainsFromEntries), + Self::BranchesFromEntries(_, _, _) => OT::Native(BranchesFromEntries), + Self::LeafFromEntries(_, _, _) => OT::Native(LeafFromEntries), + Self::IsNullTree(_) => OT::Native(IsNullTree), Self::SumOf(_, _, _) => OT::Native(SumOf), Self::ProductOf(_, _, _) => OT::Native(ProductOf), Self::MaxOf(_, _, _) => OT::Native(MaxOf), @@ -127,6 +145,9 @@ impl Operation { Self::LtToNotEqual(s) => vec![s], Self::ContainsFromEntries(s1, s2) => vec![s1, s2], Self::NotContainsFromEntries(s1, s2) => vec![s1, s2], + Self::BranchesFromEntries(s1, s2, s3) => vec![s1, s2, s3], + Self::LeafFromEntries(s1, s2, s3) => vec![s1, s2, s3], + Self::IsNullTree(s) => vec![s], Self::SumOf(s1, s2, s3) => vec![s1, s2, s3], Self::ProductOf(s1, s2, s3) => vec![s1, s2, s3], Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3], @@ -183,124 +204,100 @@ impl Operation { Self::None => Some(vec![]), Self::NewEntry => Option::None, Self::CopyStatement(s1) => Some(s1.args()), - Self::EqualFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => { - if v1 == v2 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::EqualFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); - } - Self::NotEqualFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => { - if v1 != v2 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::NotEqualFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); - } - Self::GtFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => { - if v1 > v2 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::GtFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); + Self::EqualFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) if v1 == v2 => { + Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::LtFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => { - if v1 < v2 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } + Self::NotEqualFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) if v1 != v2 => { + Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::LtFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); + Self::GtFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) if v1 > v2 => { + Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::TransitiveEqualFromStatements(Equal(ak1, ak2), Equal(ak3, ak4)) => { - if ak2 == ak3 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak3)]) - } else { - return Err(anyhow!("Invalid operation")); - } + Self::LtFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) if v1 < v2 => { + Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::TransitiveEqualFromStatements(_, _) => { - return Err(anyhow!("Invalid operation")); + Self::TransitiveEqualFromStatements(Equal(ak1, ak2), Equal(ak3, ak4)) if ak2 == ak3 => { + Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak4)]) } Self::GtToNotEqual(Gt(ak1, ak2)) => { Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::GtToNotEqual(_) => { - return Err(anyhow!("Invalid operation")); - } Self::LtToNotEqual(Gt(ak1, ak2)) => { Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::LtToNotEqual(_) => { - return Err(anyhow!("Invalid operation")); - } Self::ContainsFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => /* TODO */ { Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::ContainsFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); - } Self::NotContainsFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2)) => /* TODO */ { Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) } - Self::NotContainsFromEntries(_, _) => { - return Err(anyhow!("Invalid operation")); - } - Self::SumOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) => { - let v1: i64 = (*v1).try_into()?; - let v2: i64 = (*v2).try_into()?; - let v3: i64 = (*v3).try_into()?; - if v1 == v2 + v3 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::SumOf(_, _, _) => { - return Err(anyhow!("Invalid operation")); - } - Self::ProductOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) => { - let v1: i64 = (*v1).try_into()?; - let v2: i64 = (*v2).try_into()?; - let v3: i64 = (*v3).try_into()?; - if v1 == v2 * v3 { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::ProductOf(_, _, _) => { - return Err(anyhow!("Invalid operation")); - } - Self::MaxOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) => { - let v1: i64 = (*v1).try_into()?; - let v2: i64 = (*v2).try_into()?; - let v3: i64 = (*v3).try_into()?; - if v1 == std::cmp::max(v2, v3) { - Some(vec![StatementArg::Key(*ak1), StatementArg::Key(*ak2)]) - } else { - return Err(anyhow!("Invalid operation")); - } - } - Self::MaxOf(_, _, _) => { - return Err(anyhow!("Invalid operation")); + Self::BranchesFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if Hash::from(*v1) == hash_fields(&[v2.0, v3.0].concat()) => + { + Some(vec![ + StatementArg::Key(*ak1), + StatementArg::Key(*ak2), + StatementArg::Key(*ak3), + ]) + } + Self::LeafFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if Hash::from(*v1) == kv_hash(v2, Some(*v3)) => + { + Some(vec![ + StatementArg::Key(*ak1), + StatementArg::Key(*ak2), + StatementArg::Key(*ak3), + ]) + } + Self::IsNullTree(ValueOf(ak, v)) if v == &EMPTY => Some(vec![StatementArg::Key(*ak)]), + Self::SumOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if ({ + let v1: i64 = (*v1).try_into()?; + let v2: i64 = (*v2).try_into()?; + let v3: i64 = (*v3).try_into()?; + v1 == v2 + v3 + }) => + { + Some(vec![ + StatementArg::Key(*ak1), + StatementArg::Key(*ak2), + StatementArg::Key(*ak3), + ]) + } + Self::ProductOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if ({ + let v1: i64 = (*v1).try_into()?; + let v2: i64 = (*v2).try_into()?; + let v3: i64 = (*v3).try_into()?; + v1 == v2 * v3 + }) => + { + Some(vec![ + StatementArg::Key(*ak1), + StatementArg::Key(*ak2), + StatementArg::Key(*ak3), + ]) + } + Self::MaxOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if ({ + let v1: i64 = (*v1).try_into()?; + let v2: i64 = (*v2).try_into()?; + let v3: i64 = (*v3).try_into()?; + v1 == std::cmp::max(v2, v3) + }) => + { + Some(vec![ + StatementArg::Key(*ak1), + StatementArg::Key(*ak2), + StatementArg::Key(*ak3), + ]) } Self::Custom(_, _) => todo!(), + _ => return Err(anyhow!("Invalid operation: {}", self)), }; let x: Option> = pred diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index 6b03d511..f7685911 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -19,9 +19,12 @@ pub enum NativePredicate { Lt = 5, Contains = 6, NotContains = 7, - SumOf = 8, - ProductOf = 9, - MaxOf = 10, + Branches = 8, + Leaf = 9, + IsNullTree = 10, + SumOf = 11, + ProductOf = 12, + MaxOf = 13, } impl ToFields for NativePredicate { @@ -38,9 +41,15 @@ pub enum Statement { Equal(AnchoredKey, AnchoredKey), NotEqual(AnchoredKey, AnchoredKey), Gt(AnchoredKey, AnchoredKey), + // TODO: Remove. Lt(AnchoredKey, AnchoredKey), + // TODO: Remove. Contains(AnchoredKey, AnchoredKey), + // TODO: Remove. NotContains(AnchoredKey, AnchoredKey), + Branches(AnchoredKey, AnchoredKey, AnchoredKey), + Leaf(AnchoredKey, AnchoredKey, AnchoredKey), + IsNullTree(AnchoredKey), SumOf(AnchoredKey, AnchoredKey, AnchoredKey), ProductOf(AnchoredKey, AnchoredKey, AnchoredKey), MaxOf(AnchoredKey, AnchoredKey, AnchoredKey), @@ -62,6 +71,9 @@ impl Statement { Self::Lt(_, _) => Native(NativePredicate::Lt), Self::Contains(_, _) => Native(NativePredicate::Contains), Self::NotContains(_, _) => Native(NativePredicate::NotContains), + Self::Branches(_, _, _) => Native(NativePredicate::Branches), + Self::Leaf(_, _, _) => Native(NativePredicate::Leaf), + Self::IsNullTree(_) => Native(NativePredicate::IsNullTree), Self::SumOf(_, _, _) => Native(NativePredicate::SumOf), Self::ProductOf(_, _, _) => Native(NativePredicate::ProductOf), Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf), @@ -79,6 +91,9 @@ impl Statement { Self::Lt(ak1, ak2) => vec![Key(ak1), Key(ak2)], Self::Contains(ak1, ak2) => vec![Key(ak1), Key(ak2)], Self::NotContains(ak1, ak2) => vec![Key(ak1), Key(ak2)], + Self::Branches(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], + Self::Leaf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], + Self::IsNullTree(ak) => vec![Key(ak)], Self::SumOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::ProductOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::MaxOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], @@ -138,6 +153,31 @@ impl Statement { Err(anyhow!("Incorrect statement args")) } } + Native(NativePredicate::Branches) => { + if let (StatementArg::Key(a0), StatementArg::Key(a1), StatementArg::Key(a2)) = + (args[0], args[1], args[2]) + { + Ok(Self::Branches(a0, a1, a2)) + } else { + Err(anyhow!("Incorrect statement args")) + } + } + Native(NativePredicate::Leaf) => { + if let (StatementArg::Key(a0), StatementArg::Key(a1), StatementArg::Key(a2)) = + (args[0], args[1], args[2]) + { + Ok(Self::Leaf(a0, a1, a2)) + } else { + Err(anyhow!("Incorrect statement args")) + } + } + Native(NativePredicate::IsNullTree) => { + if let StatementArg::Key(a) = args[0] { + Ok(Self::IsNullTree(a)) + } else { + Err(anyhow!("Incorrect statement args")) + } + } Native(NativePredicate::SumOf) => { if let (StatementArg::Key(a0), StatementArg::Key(a1), StatementArg::Key(a2)) = (args[0], args[1], args[2]) From 7e69795b8330b17eec416a08710143c7e555d1dd Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 13 Mar 2025 02:02:53 +1000 Subject: [PATCH 2/5] Add GoesLeft/GoesRight to middleware --- src/backends/plonky2/basetypes.rs | 25 ++++++++ src/backends/plonky2/primitives/merkletree.rs | 26 +-------- src/constants.rs | 2 +- src/frontend/mod.rs | 2 + src/middleware/basetypes.rs | 2 +- src/middleware/operation.rs | 57 ++++++++++++++++--- src/middleware/statement.rs | 28 ++++++++- 7 files changed, 104 insertions(+), 38 deletions(-) diff --git a/src/backends/plonky2/basetypes.rs b/src/backends/plonky2/basetypes.rs index 6cab1bee..364d7c3c 100644 --- a/src/backends/plonky2/basetypes.rs +++ b/src/backends/plonky2/basetypes.rs @@ -146,6 +146,31 @@ pub fn kv_hash(key: &Value, value: Option) -> Hash { .unwrap_or(Hash([GoldilocksField(0); 4])) } +// NOTE 1: think if maybe the length of the returned vector can be <256 +// (8*bytes.len()), so that we can do fewer iterations. For example, if the +// tree.max_depth is set to 20, we just need 20 iterations of the loop, not 256. +// NOTE 2: which approach do we take with keys that are longer than the +// max-depth? ie, what happens when two keys share the same path for more bits +// than the max_depth? +/// returns the path (bit decomposition) of the given key +pub fn keypath(max_depth: usize, k: Value) -> Result> { + let bytes = k.to_bytes(); + if max_depth > 8 * bytes.len() { + // note that our current keys are of Value type, which are 4 Goldilocks + // field elements, ie ~256 bits, therefore the max_depth can not be + // bigger than 256. + Err(anyhow!( + "key too short (key length: {}) for the max_depth: {}", + 8 * bytes.len(), + max_depth + )) + } else { + Ok((0..max_depth) + .map(|n| bytes[n / 8] & (1 << (n % 8)) != 0) + .collect()) + } +} + impl From for Hash { fn from(v: Value) -> Self { Hash(v.0) diff --git a/src/backends/plonky2/primitives/merkletree.rs b/src/backends/plonky2/primitives/merkletree.rs index b3b2acbc..d17351b5 100644 --- a/src/backends/plonky2/primitives/merkletree.rs +++ b/src/backends/plonky2/primitives/merkletree.rs @@ -7,7 +7,7 @@ use std::iter::IntoIterator; use crate::backends::counter; use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, F, NULL}; -use crate::middleware::kv_hash; +use crate::middleware::{keypath, kv_hash}; /// Implements the MerkleTree specified at /// https://0xparc.github.io/pod2/merkletree.html @@ -505,30 +505,6 @@ impl Leaf { } } -// NOTE 1: think if maybe the length of the returned vector can be <256 -// (8*bytes.len()), so that we can do fewer iterations. For example, if the -// tree.max_depth is set to 20, we just need 20 iterations of the loop, not 256. -// NOTE 2: which approach do we take with keys that are longer than the -// max-depth? ie, what happens when two keys share the same path for more bits -// than the max_depth? -/// returns the path of the given key -fn keypath(max_depth: usize, k: Value) -> Result> { - let bytes = k.to_bytes(); - if max_depth > 8 * bytes.len() { - // note that our current keys are of Value type, which are 4 Goldilocks - // field elements, ie ~256 bits, therefore the max_depth can not be - // bigger than 256. - return Err(anyhow!( - "key to short (key length: {}) for the max_depth: {}", - 8 * bytes.len(), - max_depth - )); - } - Ok((0..max_depth) - .map(|n| bytes[n / 8] & (1 << (n % 8)) != 0) - .collect()) -} - pub struct Iter<'a> { state: Vec<&'a Node>, } diff --git a/src/constants.rs b/src/constants.rs index 1b5be372..5aa05680 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1 +1 @@ -pub const MAX_DEPTH: usize = 32; +pub const MAX_DEPTH: usize = 256; diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index a2aa2ab4..737851c6 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -440,6 +440,8 @@ impl MainPodBuilder { BranchesFromEntries => self.op_args_entries(public, args)?, LeafFromEntries => self.op_args_entries(public, args)?, IsNullTree => self.op_args_entries(public, args)?, + GoesLeft => self.op_args_entries(public, args)?, + GoesRight => self.op_args_entries(public, args)?, SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) { ( OperationArg::Statement(Statement( diff --git a/src/middleware/basetypes.rs b/src/middleware/basetypes.rs index b9006c1f..d039fe40 100644 --- a/src/middleware/basetypes.rs +++ b/src/middleware/basetypes.rs @@ -35,6 +35,6 @@ /// then the Value, Hash and F types would come from the plonky3 backend. #[cfg(feature = "backend_plonky2")] pub use crate::backends::plonky2::basetypes::{ - hash_fields, hash_str, hash_value, kv_hash, Hash, Value, EMPTY, F, HASH_SIZE, NULL, + hash_fields, hash_str, hash_value, keypath, kv_hash, Hash, Value, EMPTY, F, HASH_SIZE, NULL, SELF_ID_HASH, VALUE_SIZE, }; diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index 1555eca8..1e1bbfda 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -3,8 +3,12 @@ use std::fmt; use anyhow::{anyhow, Result}; use super::{CustomPredicateRef, NativePredicate, Statement, StatementArg}; -use crate::middleware::{ - hash_fields, kv_hash, AnchoredKey, Hash, Params, Predicate, Value, EMPTY, SELF, VALUE_SIZE, +use crate::{ + constants::MAX_DEPTH, + middleware::{ + hash_fields, keypath, kv_hash, AnchoredKey, Hash, Params, Predicate, Value, EMPTY, SELF, + VALUE_SIZE, + }, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -29,10 +33,12 @@ pub enum NativeOperation { NotContainsFromEntries = 11, BranchesFromEntries = 12, LeafFromEntries = 13, - IsNullTree = 14, - SumOf = 15, - ProductOf = 16, - MaxOf = 17, + GoesLeft = 14, + GoesRight = 15, + IsNullTree = 16, + SumOf = 17, + ProductOf = 18, + MaxOf = 19, } impl OperationType { @@ -70,6 +76,8 @@ impl OperationType { NativeOperation::LeafFromEntries => Some(Predicate::Native(NativePredicate::Leaf)), NativeOperation::IsNullTree => Some(Predicate::Native(NativePredicate::IsNullTree)), + NativeOperation::GoesLeft => Some(Predicate::Native(NativePredicate::GoesLeft)), + NativeOperation::GoesRight => Some(Predicate::Native(NativePredicate::GoesRight)), NativeOperation::SumOf => Some(Predicate::Native(NativePredicate::SumOf)), NativeOperation::ProductOf => Some(Predicate::Native(NativePredicate::ProductOf)), @@ -98,6 +106,8 @@ pub enum Operation { BranchesFromEntries(Statement, Statement, Statement), LeafFromEntries(Statement, Statement, Statement), IsNullTree(Statement), + GoesLeft(Statement, Statement), + GoesRight(Statement, Statement), SumOf(Statement, Statement, Statement), ProductOf(Statement, Statement, Statement), MaxOf(Statement, Statement, Statement), @@ -124,6 +134,8 @@ impl Operation { Self::BranchesFromEntries(_, _, _) => OT::Native(BranchesFromEntries), Self::LeafFromEntries(_, _, _) => OT::Native(LeafFromEntries), Self::IsNullTree(_) => OT::Native(IsNullTree), + Self::GoesLeft(_, _) => OT::Native(GoesLeft), + Self::GoesRight(_, _) => OT::Native(GoesRight), Self::SumOf(_, _, _) => OT::Native(SumOf), Self::ProductOf(_, _, _) => OT::Native(ProductOf), Self::MaxOf(_, _, _) => OT::Native(MaxOf), @@ -148,6 +160,8 @@ impl Operation { Self::BranchesFromEntries(s1, s2, s3) => vec![s1, s2, s3], Self::LeafFromEntries(s1, s2, s3) => vec![s1, s2, s3], Self::IsNullTree(s) => vec![s], + Self::GoesLeft(s1, s2) => vec![s1, s2], + Self::GoesRight(s1, s2) => vec![s1, s2], Self::SumOf(s1, s2, s3) => vec![s1, s2, s3], Self::ProductOf(s1, s2, s3) => vec![s1, s2, s3], Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3], @@ -181,6 +195,15 @@ impl Operation { (NO::NotContainsFromEntries, (Some(s1), Some(s2), None), 2) => { Self::NotContainsFromEntries(s1, s2) } + (NO::BranchesFromEntries, (Some(s1), Some(s2), Some(s3)), 3) => { + Self::BranchesFromEntries(s1, s2, s3) + } + (NO::LeafFromEntries, (Some(s1), Some(s2), Some(s3)), 3) => { + Self::LeafFromEntries(s1, s2, s3) + } + (NO::IsNullTree, (Some(s), None, None), 1) => Self::IsNullTree(s), + (NO::GoesLeft, (Some(s1), Some(s2), None), 2) => Self::GoesLeft(s1, s2), + (NO::GoesRight, (Some(s1), Some(s2), None), 2) => Self::GoesRight(s1, s2), (NO::SumOf, (Some(s1), Some(s2), Some(s3)), 3) => Self::SumOf(s1, s2, s3), (NO::ProductOf, (Some(s1), Some(s2), Some(s3)), 3) => Self::ProductOf(s1, s2, s3), (NO::MaxOf, (Some(s1), Some(s2), Some(s3)), 3) => Self::MaxOf(s1, s2, s3), @@ -254,13 +277,31 @@ impl Operation { ]) } Self::IsNullTree(ValueOf(ak, v)) if v == &EMPTY => Some(vec![StatementArg::Key(*ak)]), - Self::SumOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + Self::GoesLeft(ValueOf(ak, key), ValueOf(_, depth)) if ({ + let depth_index = >::try_into(*depth)? as usize; + let key_bits = keypath(MAX_DEPTH, *key)?; + !key_bits[depth_index] + }) => + { + Some(vec![StatementArg::Key(*ak), StatementArg::Literal(*depth)]) + } + Self::GoesRight(ValueOf(ak, key), ValueOf(_, depth)) + if { + let depth_index = >::try_into(*depth)? as usize; + let key_bits = keypath(MAX_DEPTH, *key)?; + key_bits[depth_index] + } => + { + Some(vec![StatementArg::Key(*ak), StatementArg::Literal(*depth)]) + } + Self::SumOf(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)) + if { let v1: i64 = (*v1).try_into()?; let v2: i64 = (*v2).try_into()?; let v3: i64 = (*v3).try_into()?; v1 == v2 + v3 - }) => + } => { Some(vec![ StatementArg::Key(*ak1), diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index f7685911..5ece25ef 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -22,9 +22,11 @@ pub enum NativePredicate { Branches = 8, Leaf = 9, IsNullTree = 10, - SumOf = 11, - ProductOf = 12, - MaxOf = 13, + GoesLeft = 11, + GoesRight = 12, + SumOf = 13, + ProductOf = 14, + MaxOf = 15, } impl ToFields for NativePredicate { @@ -50,6 +52,8 @@ pub enum Statement { Branches(AnchoredKey, AnchoredKey, AnchoredKey), Leaf(AnchoredKey, AnchoredKey, AnchoredKey), IsNullTree(AnchoredKey), + GoesLeft(AnchoredKey, Value), + GoesRight(AnchoredKey, Value), SumOf(AnchoredKey, AnchoredKey, AnchoredKey), ProductOf(AnchoredKey, AnchoredKey, AnchoredKey), MaxOf(AnchoredKey, AnchoredKey, AnchoredKey), @@ -74,6 +78,8 @@ impl Statement { Self::Branches(_, _, _) => Native(NativePredicate::Branches), Self::Leaf(_, _, _) => Native(NativePredicate::Leaf), Self::IsNullTree(_) => Native(NativePredicate::IsNullTree), + Self::GoesLeft(_, _) => Native(NativePredicate::GoesLeft), + Self::GoesRight(_, _) => Native(NativePredicate::GoesRight), Self::SumOf(_, _, _) => Native(NativePredicate::SumOf), Self::ProductOf(_, _, _) => Native(NativePredicate::ProductOf), Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf), @@ -94,6 +100,8 @@ impl Statement { Self::Branches(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::Leaf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::IsNullTree(ak) => vec![Key(ak)], + Self::GoesLeft(ak, v) => vec![Key(ak), Literal(v)], + Self::GoesRight(ak, v) => vec![Key(ak), Literal(v)], Self::SumOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::ProductOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], Self::MaxOf(ak1, ak2, ak3) => vec![Key(ak1), Key(ak2), Key(ak3)], @@ -178,6 +186,20 @@ impl Statement { Err(anyhow!("Incorrect statement args")) } } + Native(NativePredicate::GoesLeft) => { + if let (StatementArg::Key(a), StatementArg::Literal(v)) = (args[0], args[1]) { + Ok(Self::GoesLeft(a, v)) + } else { + Err(anyhow!("Incorrect statement args")) + } + } + Native(NativePredicate::GoesRight) => { + if let (StatementArg::Key(a), StatementArg::Literal(v)) = (args[0], args[1]) { + Ok(Self::GoesRight(a, v)) + } else { + Err(anyhow!("Incorrect statement args")) + } + } Native(NativePredicate::SumOf) => { if let (StatementArg::Key(a0), StatementArg::Key(a1), StatementArg::Key(a2)) = (args[0], args[1], args[2]) From d286ff37079988267350b76b39ac9951f15a5df3 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 13 Mar 2025 23:41:40 +1000 Subject: [PATCH 3/5] Fill in gaps in frontend & middleware --- src/backends/plonky2/mock_main/statement.rs | 13 +++++++++++ src/frontend/statement.rs | 13 +++++++++++ src/middleware/operation.rs | 26 +++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/src/backends/plonky2/mock_main/statement.rs b/src/backends/plonky2/mock_main/statement.rs index 452a29c0..6dedef7a 100644 --- a/src/backends/plonky2/mock_main/statement.rs +++ b/src/backends/plonky2/mock_main/statement.rs @@ -74,6 +74,19 @@ impl TryFrom for middleware::Statement { (NP::NotContains, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => { S::NotContains(ak1, ak2) } + (NP::Branches, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => { + S::Branches(ak1, ak2, ak3) + } + (NP::Leaf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => { + S::Leaf(ak1, ak2, ak3) + } + (NP::IsNullTree, (Some(SA::Key(ak)), None, None), 1) => S::IsNullTree(ak), + (NP::GoesLeft, (Some(SA::Key(ak)), Some(SA::Literal(depth)), None), 2) => { + S::GoesLeft(ak, depth) + } + (NP::GoesRight, (Some(SA::Key(ak)), Some(SA::Literal(depth)), None), 2) => { + S::GoesRight(ak, depth) + } (NP::SumOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => { S::SumOf(ak1, ak2, ak3) } diff --git a/src/frontend/statement.rs b/src/frontend/statement.rs index 5b4b3963..cca2504c 100644 --- a/src/frontend/statement.rs +++ b/src/frontend/statement.rs @@ -75,6 +75,19 @@ impl TryFrom for middleware::Statement { (NP::NotContains, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None)) => { MS::NotContains(ak1.into(), ak2.into()) } + (NP::Branches, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3)))) => { + MS::Branches(ak1.into(), ak2.into(), ak3.into()) + } + (NP::Leaf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3)))) => { + MS::Leaf(ak1.into(), ak2.into(), ak3.into()) + } + (NP::IsNullTree, (Some(SA::Key(ak)), None, None)) => MS::IsNullTree(ak.into()), + (NP::GoesLeft, (Some(SA::Key(ak)), Some(SA::Literal(depth)), None)) => { + MS::GoesLeft(ak.into(), (&depth).into()) + } + (NP::GoesRight, (Some(SA::Key(ak)), Some(SA::Literal(depth)), None)) => { + MS::GoesRight(ak.into(), (&depth).into()) + } (NP::SumOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3)))) => { MS::SumOf(ak1.into(), ak2.into(), ak3.into()) } diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index 1e1bbfda..d9696b57 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -216,6 +216,7 @@ impl Operation { OperationType::Custom(cpr) => Self::Custom(cpr, args.to_vec()), }) } + /// Gives the output statement of the given operation, where determined /// A ValueOf statement is not determined by the NewEntry operation, so returns Ok(None) /// The outer Result is error handling @@ -375,6 +376,31 @@ impl Operation { { Ok(true) } + ( + Self::BranchesFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)), + Branches(ak4, ak5, ak6), + ) => Ok(Hash::from(*v1) == hash_fields(&[v2.0, v3.0].concat()) + && ak1 == ak4 + && ak2 == ak5 + && ak3 == ak6), + ( + Self::LeafFromEntries(ValueOf(ak1, v1), ValueOf(ak2, v2), ValueOf(ak3, v3)), + Leaf(ak4, ak5, ak6), + ) => Ok(Hash::from(*v1) == kv_hash(v2, Some(*v3)) + && ak1 == ak4 + && ak2 == ak5 + && ak3 == ak6), + (Self::IsNullTree(ValueOf(ak1, v)), IsNullTree(ak2)) => Ok(v == &EMPTY && ak1 == ak2), + (Self::GoesLeft(ValueOf(ak1, key), ValueOf(_, depth)), GoesLeft(ak2, d)) => { + let depth_index = >::try_into(*depth)? as usize; + let key_bits = keypath(MAX_DEPTH, *key)?; + Ok(!key_bits[depth_index] && ak1 == ak2 && depth == d) + } + (Self::GoesRight(ValueOf(ak1, key), ValueOf(_, depth)), GoesRight(ak2, d)) => { + let depth_index = >::try_into(*depth)? as usize; + let key_bits = keypath(MAX_DEPTH, *key)?; + Ok(key_bits[depth_index] && ak1 == ak2 && depth == d) + } ( Self::TransitiveEqualFromStatements(Equal(ak1, ak2), Equal(ak3, ak4)), Equal(ak5, ak6), From 3853aa2676aad0e772be2a20c9e4db267b97647d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 14 Mar 2025 03:12:37 +1000 Subject: [PATCH 4/5] Add custom predicate formulation of Contains --- src/frontend/mod.rs | 10 ++- src/frontend/statement.rs | 125 +++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 737851c6..066d0262 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -7,13 +7,15 @@ use std::collections::HashMap; use std::convert::From; use std::{fmt, hash as h}; +use crate::backends::plonky2::primitives::merkletree::MerkleProof; use crate::middleware::{ self, containers::{Array, Dictionary, Set}, hash_str, Hash, MainPodInputs, NativeOperation, NativePredicate, Params, PodId, PodProver, PodSigner, SELF, }; -use crate::middleware::{OperationType, Predicate, KEY_SIGNER, KEY_TYPE}; +use crate::middleware::{kv_hash, OperationType, Predicate, KEY_SIGNER, KEY_TYPE}; +use crate::op; mod custom; mod operation; @@ -925,6 +927,12 @@ pub mod build_utils { (not_contains, $($arg:expr),+) => { crate::frontend::Operation( crate::middleware::OperationType::Native(crate::middleware::NativeOperation::NotContainsFromEntries), crate::op_args!($($arg),*)) }; + (branches, $($arg:expr),+) => { crate::frontend::Operation( + crate::middleware::OperationType::Native(crate::middleware::NativeOperation::BranchesFromEntries), + crate::op_args!($($arg),*)) }; + (leaf, $($arg:expr),+) => { crate::frontend::Operation( + crate::middleware::OperationType::Native(crate::middleware::NativeOperation::LeafFromEntries), + crate::op_args!($($arg),*)) }; (sum_of, $($arg:expr),+) => { crate::frontend::Operation( crate::middleware::OperationType::Native(crate::middleware::NativeOperation::SumOf), crate::op_args!($($arg),*)) }; diff --git a/src/frontend/statement.rs b/src/frontend/statement.rs index cca2504c..6a34f185 100644 --- a/src/frontend/statement.rs +++ b/src/frontend/statement.rs @@ -1,9 +1,15 @@ use anyhow::{anyhow, Result}; -use std::fmt; +use std::{fmt, sync::Arc}; use super::{AnchoredKey, SignedPod, Value}; -use crate::middleware::{self, NativePredicate, Predicate}; +use crate::{ + frontend::{CustomPredicateBatchBuilder, StatementTmplBuilder}, + middleware::{ + self, CustomPredicateBatch, CustomPredicateRef, NativePredicate, Params, Predicate, + }, +}; +/// Frontend statement arguments are either anchored keys or values. #[derive(Clone, Debug, PartialEq, Eq)] pub enum StatementArg { Literal(Value), @@ -19,9 +25,24 @@ impl fmt::Display for StatementArg { } } +/// A frontend statement is a predicate code together with a vector of +/// arguments. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Statement(pub Predicate, pub Vec); +impl Statement { + pub fn value(&self) -> Option { + match (&self.0, self.1.get(1)) { + (Predicate::Native(NativePredicate::ValueOf), Some(StatementArg::Literal(v))) => { + Some(v.clone()) + } + _ => None, + } + } +} + +/// Given a signed POD and a key string, we may produce the statement +/// containing this key and its corresponding value from the KV store. impl From<(&SignedPod, &str)> for Statement { fn from((pod, key): (&SignedPod, &str)) -> Self { // TODO: TryFrom. @@ -40,6 +61,8 @@ impl From<(&SignedPod, &str)> for Statement { } } +/// A frontend statement may be converted to a middleware statement +/// provided that it is well-formed. impl TryFrom for middleware::Statement { type Error = anyhow::Error; fn try_from(s: Statement) -> Result { @@ -125,3 +148,101 @@ impl fmt::Display for Statement { Ok(()) } } + +// Useful custom predicates follow. + +use NativePredicate as NP; +use StatementTmplBuilder as STB; + +pub fn merkle_subtree_predicate(params: &Params) -> Result { + let mut builder = CustomPredicateBatchBuilder::new("merkle_subtree".into()); + + let merkle_subtree = Predicate::BatchSelf(3); + + let merkle_subtree_base = builder.predicate_and( + params, + &["root_ori", "root_key", "node_ori", "node_key"], + &[], + &[STB::new(NP::Equal) + .arg(("root_ori", "root_key")) + .arg(("node_ori", "node_key"))], + )?; + let merkle_subtree_ind1 = builder.predicate_and( + params, + &["root_ori", "root_key", "node_ori", "node_key"], + &["parent_ori", "parent_key", "other_ori", "other_key"], + &[ + STB::new(merkle_subtree.clone()) + .arg(("root_ori", "root_key")) + .arg(("parent_ori", "parent_key")), + STB::new(NP::Branches) + .arg(("parent_ori", "parent_key")) + .arg(("node_ori", "node_key")) + .arg(("other_ori", "other_key")), + ], + )?; + let merkle_subtree_ind2 = builder.predicate_and( + params, + &["root_ori", "root_key", "node_ori", "node_key"], + &["parent_ori", "parent_key", "other_ori", "other_key"], + &[ + STB::new(merkle_subtree) + .arg(("root_ori", "root_key")) + .arg(("parent_ori", "parent_key")), + STB::new(NP::Branches) + .arg(("parent_ori", "parent_key")) + .arg(("other_ori", "other_key")) + .arg(("node_ori", "node_key")), + ], + )?; + let _merkle_subtree_general = builder.predicate_or( + params, + &["root_ori", "root_key", "node_ori", "node_key"], + &[], + &[ + STB::new(merkle_subtree_base) + .arg(("root_ori", "root_key")) + .arg(("node_ori", "node_key")), + STB::new(merkle_subtree_ind1) + .arg(("root_ori", "root_key")) + .arg(("node_ori", "node_key")), + STB::new(merkle_subtree_ind2) + .arg(("root_ori", "root_key")) + .arg(("node_ori", "node_key")), + ], + )?; + let batch = builder.finish(); + + Ok(Predicate::Custom(CustomPredicateRef(batch, 3))) +} + +pub fn merkle_contains_predicate(params: &Params) -> Result { + let mut builder = CustomPredicateBatchBuilder::new("merkle_subtree".into()); + let merkle_subtree = merkle_subtree_predicate(params)?; + + builder.predicate_and( + params, + &[ + "root_ori", + "root_key", + "key_ori", + "key_key", + "value_ori", + "value_key", + ], + &["node_ori", "node_key"], + &[ + STB::new(merkle_subtree) + .arg(("root_ori", "root_key")) + .arg(("node_ori", "node_key")), + STB::new(NP::Leaf) + .arg(("node_ori", "node_key")) + .arg(("key_ori", "key_key")) + .arg(("value_ori", "value_key")), + ], + )?; + + let batch = builder.finish(); + + Ok(Predicate::Custom(CustomPredicateRef(batch, 0))) +} From 7b93834d4341320f2052108d33ffe40854906a28 Mon Sep 17 00:00:00 2001 From: tideofwords Date: Mon, 17 Mar 2025 09:20:37 -0700 Subject: [PATCH 5/5] Add test for merkle statements, and some debug prints to verify() (#140) --- src/backends/plonky2/mock_main/mod.rs | 2 +- src/backends/plonky2/primitives/merkletree.rs | 34 +++++++-- src/frontend/mod.rs | 71 +++++++++++++++++++ src/middleware/operation.rs | 9 +++ 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/backends/plonky2/mock_main/mod.rs b/src/backends/plonky2/mock_main/mod.rs index a4b4cad3..e1f7f221 100644 --- a/src/backends/plonky2/mock_main/mod.rs +++ b/src/backends/plonky2/mock_main/mod.rs @@ -430,7 +430,7 @@ impl Pod for MockMainPod { self.operations[i] .deref(&self.statements[..input_statement_offset + i]) .unwrap() - .check(&self.params, &s.clone().try_into().unwrap()) + .check_and_print(&self.params, &s.clone().try_into().unwrap()) }) .collect::>>() .unwrap(); diff --git a/src/backends/plonky2/primitives/merkletree.rs b/src/backends/plonky2/primitives/merkletree.rs index d17351b5..7c7beaf0 100644 --- a/src/backends/plonky2/primitives/merkletree.rs +++ b/src/backends/plonky2/primitives/merkletree.rs @@ -13,8 +13,8 @@ use crate::middleware::{keypath, kv_hash}; /// https://0xparc.github.io/pod2/merkletree.html #[derive(Clone, Debug)] pub struct MerkleTree { - max_depth: usize, - root: Node, + pub max_depth: usize, + pub root: Node, } impl MerkleTree { @@ -248,7 +248,7 @@ impl MerkleProof { } #[derive(Clone, Debug)] -enum Node { +pub enum Node { None, Leaf(Leaf), Intermediate(Intermediate), @@ -285,7 +285,7 @@ impl fmt::Display for Node { } impl Node { - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { match self { Self::None => true, Self::Leaf(_l) => false, @@ -299,7 +299,7 @@ impl Node { Self::Intermediate(n) => n.compute_hash(), } } - fn hash(&self) -> Hash { + pub fn hash(&self) -> Hash { match self { Self::None => NULL, Self::Leaf(l) => l.hash(), @@ -307,6 +307,30 @@ impl Node { } } + pub fn left(&self) -> Option<&Box> { + match self { + Self::None => None, + Self::Leaf(_l) => None, + Self::Intermediate(Intermediate { + hash: _h, + left: l, + right: _r + }) => Some(l), + } + } + + pub fn right(&self) -> Option<&Box> { + match self { + Self::None => None, + Self::Leaf(_l) => None, + Self::Intermediate(Intermediate { + hash: _h, + left: _l, + right: r + }) => Some(r), + } + } + /// Goes down from the current node until it encounters a terminal node, /// viz. a leaf or empty node, or until it reaches the maximum depth. The /// `siblings` parameter is used to store the siblings while going down to diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 066d0262..9d8c9498 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -953,6 +953,7 @@ pub mod tests { use super::*; use crate::backends::plonky2::mock_main::MockProver; use crate::backends::plonky2::mock_signed::MockSigner; + use crate::backends::plonky2::primitives::merkletree::MerkleTree; use crate::examples::{ eth_dos_pod_builder, eth_friend_signed_pod_builder, great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders, @@ -1165,4 +1166,74 @@ pub mod tests { println!("{}", builder); println!("{}", false_pod); } + + #[test] + fn test_merkle_proofs() -> Result<()> { + let mut kvs = HashMap::new(); + for i in 0..8 { + if i == 1 { + continue; + } + kvs.insert(middleware::Value::from(i), middleware::Value::from(1000 + i)); + } + let key = middleware::Value::from(13); + let value = middleware::Value::from(1013); + kvs.insert(key, value); + + let tree = MerkleTree::new(32, &kvs)?; + // when printing the tree, it should print the same tree as in + // https://0xparc.github.io/pod2/merkletree.html#example-2 + println!("{}", tree); + + println!("{}", tree.root()); + + let root: Hash = tree.root(); + let left: Hash = (*tree.root.left().unwrap().clone()).hash(); + let right: Hash = (*tree.root.right().unwrap().clone()).hash(); + + let params = Params::default(); + let mut signed_builder = SignedPodBuilder::new(¶ms); + + let mut builder = MainPodBuilder::new(¶ms); + let introduce_root_op = Operation( + OperationType::Native(NativeOperation::NewEntry), + vec![ + OperationArg::Entry("root".into(), Value::Raw(middleware::Value::from(root))), + ] + ); + let introduce_left_op = Operation( + OperationType::Native(NativeOperation::NewEntry), + vec![ + OperationArg::Entry("left".into(), Value::Raw(middleware::Value::from(left))), + ] + ); + let introduce_right_op = Operation( + OperationType::Native(NativeOperation::NewEntry), + vec![ + OperationArg::Entry("right".into(), Value::Raw(middleware::Value::from(right))), + ] + ); + let st1 = builder.op(false, introduce_root_op).unwrap(); + let st2 = builder.op(false, introduce_left_op).unwrap(); + let st3 = builder.op(false, introduce_right_op).unwrap(); + + // verify Branches statement + let branches_op = Operation( + OperationType::Native(NativeOperation::BranchesFromEntries), + vec![ + OperationArg::Statement(st1), + OperationArg::Statement(st2), + OperationArg::Statement(st3), + ] + ); + + let _branches_st = builder.op(true, branches_op).unwrap(); + + let mut prover = MockProver {}; + let pod = builder.prove(&mut prover, ¶ms).unwrap(); + print!("{}", pod); + assert_eq!(pod.pod.verify(), true); + + Ok(()) + } } diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index d9696b57..286133b7 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -347,6 +347,15 @@ impl Operation { .map(|(pred, st_args)| Statement::from_args(pred, st_args)); x.transpose() } + /// Checks the given operation against a statement, and prints information if the check does not pass + pub fn check_and_print(&self, params: &Params, output_statement: &Statement) -> Result { + let valid: bool = self.check(params, output_statement)?; + if !valid { + println!("Check failed on the following statement"); + println!("{}", output_statement); + } + Ok(valid) + } /// Checks the given operation against a statement. pub fn check(&self, _params: &Params, output_statement: &Statement) -> Result { use Statement::*;