diff --git a/Cargo.toml b/Cargo.toml index 8c0e9bd..240b9e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,15 @@ name = "pod2" path = "src/lib.rs" [dependencies] -plonky2 = { git = "https://github.com/0xPolygonZero/plonky2" } hex = "0.4.3" itertools = "0.14.0" strum = "0.26" strum_macros = "0.26" anyhow = "1.0.56" dyn-clone = "1.0.18" +# enabled by features: +plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true } + +[features] +default = ["backend_plonky2"] +backend_plonky2 = ["plonky2"] diff --git a/README.md b/README.md index c4cdbb1..6982eaa 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## Usage - Run tests: `cargo test --release` +- Rustfmt: `cargo fmt` +- Check [typos](https://github.com/crate-ci/typos): `typos -c .github/workflows/typos.toml` ## Book The `book` contains the specification of POD2. A rendered version of the site can be found at: https://0xparc.github.io/pod2/ diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 0086830..0d13726 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,3 +1,2 @@ -pub mod mock_main; -pub mod mock_signed; +#[cfg(feature = "backend_plonky2")] pub mod plonky2; diff --git a/src/backends/plonky2.rs b/src/backends/plonky2.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/backends/plonky2.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/backends/plonky2/basetypes.rs b/src/backends/plonky2/basetypes.rs new file mode 100644 index 0000000..a0e70c3 --- /dev/null +++ b/src/backends/plonky2/basetypes.rs @@ -0,0 +1,203 @@ +//! This file exposes the middleware::basetypes to be used in the middleware when the +//! `backend_plonky2` feature is enabled. +//! See src/middleware/basetypes.rs for more details. + +use anyhow::{anyhow, Error, Result}; +use hex::{FromHex, FromHexError}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::{Field, PrimeField64}; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::plonk::config::Hasher; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use std::cmp::{Ord, Ordering}; +use std::fmt; + +use crate::middleware::{Params, ToFields}; + +/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2 +pub type F = GoldilocksField; +/// C is the Plonky2 config used in POD2 to work with Plonky2 recursion. +pub type C = PoseidonGoldilocksConfig; +/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension). +pub const D: usize = 2; + +pub const HASH_SIZE: usize = 4; +pub const VALUE_SIZE: usize = 4; + +pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); +pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]); +pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] +pub struct Value(pub [F; VALUE_SIZE]); + +impl ToFields for Value { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { + (self.0.to_vec(), VALUE_SIZE) + } +} + +impl Value { + pub fn to_bytes(self) -> Vec { + self.0 + .iter() + .flat_map(|e| e.to_canonical_u64().to_le_bytes()) + .collect() + } +} + +impl Ord for Value { + fn cmp(&self, other: &Self) -> Ordering { + for (lhs, rhs) in self.0.iter().zip(other.0.iter()).rev() { + let (lhs, rhs) = (lhs.to_canonical_u64(), rhs.to_canonical_u64()); + if lhs < rhs { + return Ordering::Less; + } else if lhs > rhs { + return Ordering::Greater; + } + } + return Ordering::Equal; + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Value { + fn from(v: i64) -> Self { + let lo = F::from_canonical_u64((v as u64) & 0xffffffff); + let hi = F::from_canonical_u64((v as u64) >> 32); + Value([lo, hi, F::ZERO, F::ZERO]) + } +} + +impl From for Value { + fn from(h: Hash) -> Self { + Value(h.0) + } +} + +impl TryInto for Value { + type Error = Error; + fn try_into(self) -> std::result::Result { + let value = self.0; + if &value[2..] != &[F::ZERO, F::ZERO] + || value[..2] + .iter() + .all(|x| x.to_canonical_u64() > u32::MAX as u64) + { + Err(anyhow!("Value not an element of the i64 embedding.")) + } else { + Ok((value[0].to_canonical_u64() + value[1].to_canonical_u64() << 32) as i64) + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0[2].is_zero() && self.0[3].is_zero() { + // Assume this is an integer + let (l0, l1) = (self.0[0].to_canonical_u64(), self.0[1].to_canonical_u64()); + assert!(l0 < (1 << 32)); + assert!(l1 < (1 << 32)); + write!(f, "{}", l0 + l1 * (1 << 32)) + } else { + // Assume this is a hash + Hash(self.0).fmt(f) + } + } +} + +#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)] +pub struct Hash(pub [F; HASH_SIZE]); + +pub fn hash_value(input: &Value) -> Hash { + Hash(PoseidonHash::hash_no_pad(&input.0).elements) +} +pub fn hash_fields(input: &[F]) -> Hash { + Hash(PoseidonHash::hash_no_pad(&input).elements) +} + +impl From for Hash { + fn from(v: Value) -> Self { + Hash(v.0) + } +} +impl Hash { + pub fn value(self) -> Value { + Value(self.0) + } +} + +impl ToFields for Hash { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { + (self.0.to_vec(), VALUE_SIZE) + } +} + +impl Ord for Hash { + fn cmp(&self, other: &Self) -> Ordering { + Value(self.0).cmp(&Value(other.0)) + } +} + +impl PartialOrd for Hash { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let v0 = self.0[0].to_canonical_u64(); + for i in 0..4 { + write!(f, "{:02x}", (v0 >> (i * 8)) & 0xff)?; + } + write!(f, "…") + } +} + +impl FromHex for Hash { + type Error = FromHexError; + + // TODO make it dependant on backend::Value len + fn from_hex>(hex: T) -> Result { + // In little endian + let bytes = <[u8; 32]>::from_hex(hex)?; + let mut buf: [u8; 8] = [0; 8]; + let mut inner = [F::ZERO; 4]; + for i in 0..4 { + buf.copy_from_slice(&bytes[8 * i..8 * (i + 1)]); + inner[i] = F::from_canonical_u64(u64::from_le_bytes(buf)); + } + Ok(Self(inner)) + } +} + +impl From<&str> for Hash { + fn from(s: &str) -> Self { + hash_str(s) + } +} + +pub fn hash_str(s: &str) -> Hash { + let mut input = s.as_bytes().to_vec(); + input.push(1); // padding + + // Merge 7 bytes into 1 field, because the field is slightly below 64 bits + let input: Vec = input + .chunks(7) + .map(|bytes| { + let mut v: u64 = 0; + for b in bytes.iter().rev() { + v <<= 8; + v += *b as u64; + } + F::from_canonical_u64(v) + }) + .collect(); + Hash(PoseidonHash::hash_no_pad(&input).elements) +} diff --git a/src/backends/mock_main/mod.rs b/src/backends/plonky2/mock_main/mod.rs similarity index 98% rename from src/backends/mock_main/mod.rs rename to src/backends/plonky2/mock_main/mod.rs index 915a0f7..a708331 100644 --- a/src/backends/mock_main/mod.rs +++ b/src/backends/plonky2/mock_main/mod.rs @@ -327,7 +327,7 @@ impl MockMainPod { statements[statements.len() - params.max_public_statements..].to_vec(); // get the id out of the public statements - let id: PodId = PodId(hash_statements(&public_statements, *params)); + let id: PodId = PodId(hash_statements(&public_statements, params)); Ok(Self { params: params.clone(), @@ -362,10 +362,10 @@ impl MockMainPod { } } -pub fn hash_statements(statements: &[Statement], params: Params) -> middleware::Hash { +pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware::Hash { let field_elems = statements .into_iter() - .flat_map(|statement| statement.clone().to_fields(params).0) + .flat_map(|statement| statement.clone().to_fields(_params).0) .collect::>(); Hash(PoseidonHash::hash_no_pad(&field_elems).elements) } @@ -376,7 +376,7 @@ impl Pod for MockMainPod { // get the input_statements from the self.statements let input_statements = &self.statements[input_statement_offset..]; // get the id out of the public statements, and ensure it is equal to self.id - let ids_match = self.id == PodId(hash_statements(&self.public_statements, self.params)); + let ids_match = self.id == PodId(hash_statements(&self.public_statements, &self.params)); // find a ValueOf statement from the public statements with key=KEY_TYPE and check that the // value is PodType::MockMainPod let has_type_statement = self @@ -474,7 +474,7 @@ impl Pod for MockMainPod { #[cfg(test)] pub mod tests { use super::*; - use crate::backends::mock_signed::MockSigner; + use crate::backends::plonky2::mock_signed::MockSigner; use crate::examples::{ great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders, diff --git a/src/backends/mock_main/operation.rs b/src/backends/plonky2/mock_main/operation.rs similarity index 100% rename from src/backends/mock_main/operation.rs rename to src/backends/plonky2/mock_main/operation.rs diff --git a/src/backends/mock_main/statement.rs b/src/backends/plonky2/mock_main/statement.rs similarity index 95% rename from src/backends/mock_main/statement.rs rename to src/backends/plonky2/mock_main/statement.rs index f8fdd0c..a9b27dd 100644 --- a/src/backends/mock_main/statement.rs +++ b/src/backends/plonky2/mock_main/statement.rs @@ -21,13 +21,13 @@ impl Statement { } impl ToFields for Statement { - fn to_fields(&self, params: Params) -> (Vec, usize) { - let (native_statement_f, native_statement_f_len) = self.0.to_fields(params); + fn to_fields(&self, _params: &Params) -> (Vec, usize) { + let (native_statement_f, native_statement_f_len) = self.0.to_fields(_params); let (vec_statementarg_f, vec_statementarg_f_len) = self .1 .clone() .into_iter() - .map(|statement_arg| statement_arg.to_fields(params)) + .map(|statement_arg| statement_arg.to_fields(_params)) .fold((Vec::new(), 0), |mut acc, (f, l)| { acc.0.extend(f); acc.1 += l; diff --git a/src/backends/mock_signed.rs b/src/backends/plonky2/mock_signed.rs similarity index 100% rename from src/backends/mock_signed.rs rename to src/backends/plonky2/mock_signed.rs diff --git a/src/backends/plonky2/mod.rs b/src/backends/plonky2/mod.rs new file mode 100644 index 0000000..0f89007 --- /dev/null +++ b/src/backends/plonky2/mod.rs @@ -0,0 +1,3 @@ +pub mod basetypes; +pub mod mock_main; +pub mod mock_signed; diff --git a/src/examples.rs b/src/examples.rs index 818807a..dd6c51f 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::collections::HashMap; -use crate::backends::mock_signed::MockSigner; +use crate::backends::plonky2::mock_signed::MockSigner; use crate::frontend::{MainPodBuilder, SignedPod, SignedPodBuilder, Value}; use crate::middleware::{containers::Dictionary, Params, PodType, KEY_SIGNER, KEY_TYPE}; use crate::op; diff --git a/src/frontend/custom.rs b/src/frontend/custom.rs index 20f4657..8a051ff 100644 --- a/src/frontend/custom.rs +++ b/src/frontend/custom.rs @@ -316,7 +316,7 @@ mod tests { ); let eth_dos_batch_b = builder.finish(); - let fields = eth_dos_batch_b.to_fields(params); + let fields = eth_dos_batch_b.to_fields(¶ms); println!("Batch b, serialized: {:?}", fields); } } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index e8e8765..2f0275c 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -65,12 +65,12 @@ impl From for Value { impl From<&Value> for middleware::Value { fn from(v: &Value) -> Self { match v { - Value::String(s) => middleware::Value(hash_str(s).0), + Value::String(s) => hash_str(s).value(), Value::Int(v) => middleware::Value::from(*v), Value::Bool(b) => middleware::Value::from(*b as i64), - Value::Dictionary(d) => middleware::Value(d.commitment().0), - Value::Set(s) => middleware::Value(s.commitment().0), - Value::Array(a) => middleware::Value(a.commitment().0), + Value::Dictionary(d) => d.commitment().value(), + Value::Set(s) => s.commitment().value(), + Value::Array(a) => a.commitment().value(), Value::Raw(v) => v.clone(), } } @@ -513,8 +513,8 @@ pub mod build_utils { #[cfg(test)] pub mod tests { use super::*; - use crate::backends::mock_main::MockProver; - use crate::backends::mock_signed::MockSigner; + use crate::backends::plonky2::mock_main::MockProver; + use crate::backends::plonky2::mock_signed::MockSigner; use crate::examples::{ great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders, diff --git a/src/middleware/basetypes.rs b/src/middleware/basetypes.rs new file mode 100644 index 0000000..3f893e8 --- /dev/null +++ b/src/middleware/basetypes.rs @@ -0,0 +1,40 @@ +//! This file exposes the backend dependent basetypes as middleware types, +//! taking them from the feature-enabled backend. +//! +//! This is done in order to avoid inconsistencies where a type or parameter is +//! defined in the middleware to have certain carachteristic and later in the +//! backend it gets used differently. The idea is that those types and +//! parameters (eg. lengths) have a single source of truth in the code; and in +//! the case of the "base types" this is determined by the backend being used +//! under the hood, not by a choice of the middleware parameters. +//! +//! The idea with this approach, is that the frontend & middleware should not +//! need to import the proving library used by the backend (eg. plonky2, +//! plonky3, etc). +//! +//! For example, the `Hash` and `Value` types are types belonging at the +//! middleware, and is the middleware who reasons about them, but depending on +//! the backend being used, the `Hash` and `Value` types will have different +//! sizes. So it's the backend being used who actually defines their nature +//! under the hood. For example with a plonky2 backend, these types will have a +//! length of 4 field elements, whereas with a plonky3 backend they will have a +//! length of 8 field eleements. +//! +//! Note that his approach does not introduce new traits or abstract code, +//! just makes use of rust features to define 'base types' that are being used +//! in the middleware. +//! +//! +//! NOTE (TMP): current implementation still uses plonky2 in the middleware for +//! u64/i64 to F conversion. Eventually we will do those conversions through the +//! approach described in this file, removing the imports of plonky2 in the +//! middleware. + +/// Value, Hash and F are imported based on 'features'. For example by default +/// we use the 'plonky2' feature, but it could be used a 'plonky3' feature, so +/// 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, +}; diff --git a/src/middleware/containers.rs b/src/middleware/containers.rs index 743402d..ec7e116 100644 --- a/src/middleware/containers.rs +++ b/src/middleware/containers.rs @@ -1,14 +1,13 @@ /// This file implements the types defined at /// https://0xparc.github.io/pod2/values.html#dictionary-array-set . use anyhow::Result; -use plonky2::hash::poseidon::PoseidonHash; -use plonky2::plonk::config::Hasher; use std::collections::HashMap; -use super::{Hash, Value, EMPTY}; use crate::constants::MAX_DEPTH; use crate::primitives::merkletree::{MerkleProof, MerkleTree}; +use super::basetypes::{hash_value, Hash, Value, EMPTY}; + /// Dictionary: the user original keys and values are hashed to be used in the leaf. /// leaf.key=hash(original_key) /// leaf.value=hash(original_value) @@ -76,8 +75,8 @@ impl Set { let kvs: HashMap = set .into_iter() .map(|e| { - let h = PoseidonHash::hash_no_pad(&e.0).elements; - (Value(h), EMPTY) + let h = hash_value(e); + (Value::from(h), EMPTY) }) .collect(); Ok(Self { diff --git a/src/middleware/custom.rs b/src/middleware/custom.rs index 13066f8..8756b97 100644 --- a/src/middleware/custom.rs +++ b/src/middleware/custom.rs @@ -3,14 +3,10 @@ use std::{fmt, hash as h, iter::zip}; use anyhow::{anyhow, Result}; use plonky2::field::types::Field; -use plonky2::hash::poseidon::PoseidonHash; -use plonky2::plonk::config::Hasher; - -use crate::middleware::{Operation, SELF}; use super::{ - hash_str, AnchoredKey, Hash, NativePredicate, Params, PodId, Statement, StatementArg, ToFields, - Value, F, + hash_fields, AnchoredKey, Hash, NativePredicate, Params, PodId, Statement, StatementArg, + ToFields, Value, F, }; // BEGIN Custom 1b @@ -44,9 +40,9 @@ impl fmt::Display for HashOrWildcard { } impl ToFields for HashOrWildcard { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { match self { - HashOrWildcard::Hash(h) => h.to_fields(params), + HashOrWildcard::Hash(h) => h.to_fields(_params), HashOrWildcard::Wildcard(w) => { let usizes: Vec = vec![0, 0, 0, *w]; let fields: Vec = usizes @@ -86,7 +82,7 @@ impl StatementTmplArg { } impl ToFields for StatementTmplArg { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { // None => (0, ...) // Literal(value) => (1, [value], 0, 0, 0, 0) // Key(hash_or_wildcard1, hash_or_wildcard2) @@ -103,15 +99,15 @@ impl ToFields for StatementTmplArg { } StatementTmplArg::Literal(v) => { let fields: Vec = std::iter::once(F::from_canonical_u64(1)) - .chain(v.to_fields(params).0.into_iter()) + .chain(v.to_fields(_params).0.into_iter()) .chain(std::iter::repeat_with(|| F::from_canonical_u64(0)).take(hash_size)) .collect(); (fields, statement_tmpl_arg_size) } StatementTmplArg::Key(hw1, hw2) => { let fields: Vec = std::iter::once(F::from_canonical_u64(2)) - .chain(hw1.to_fields(params).0.into_iter()) - .chain(hw2.to_fields(params).0.into_iter()) + .chain(hw1.to_fields(_params).0.into_iter()) + .chain(hw2.to_fields(_params).0.into_iter()) .collect(); (fields, statement_tmpl_arg_size) } @@ -173,13 +169,18 @@ impl StatementTmpl { } impl ToFields for StatementTmpl { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, params: &Params) -> (Vec, usize) { // serialize as: // predicate (6 field elements) // then the StatementTmplArgs + + // TODO think if this check should go into the StatementTmpl creation, + // instead of at the `to_fields` method, where we should assume that the + // values are already valid if self.1.len() > params.max_statement_args { panic!("Statement template has too many arguments"); } + let mut fields: Vec = self .0 .to_fields(params) @@ -203,16 +204,21 @@ pub struct CustomPredicate { } impl ToFields for CustomPredicate { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, params: &Params) -> (Vec, usize) { // serialize as: // conjunction (one field element) // args_len (one field element) // statements // (params.max_custom_predicate_arity * params.statement_tmpl_size()) // field elements + + // TODO think if this check should go into the StatementTmpl creation, + // instead of at the `to_fields` method, where we should assume that the + // values are already valid if self.statements.len() > params.max_custom_predicate_arity { panic!("Custom predicate depends on too many statements"); } + let mut fields: Vec = std::iter::once(F::from_bool(self.conjunction)) .chain(std::iter::once(F::from_canonical_usize(self.args_len))) .chain(self.statements.iter().flat_map(|st| st.to_fields(params).0)) @@ -254,11 +260,16 @@ pub struct CustomPredicateBatch { } impl ToFields for CustomPredicateBatch { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, params: &Params) -> (Vec, usize) { // all the custom predicates in order + + // TODO think if this check should go into the StatementTmpl creation, + // instead of at the `to_fields` method, where we should assume that the + // values are already valid if self.predicates.len() > params.max_custom_batch_size { panic!("Predicate batch exceeds maximum size"); } + let mut fields: Vec = self .predicates .iter() @@ -273,9 +284,9 @@ impl ToFields for CustomPredicateBatch { } impl CustomPredicateBatch { - pub fn hash(&self, params: Params) -> Hash { - let input = self.to_fields(params).0; - let h = Hash(PoseidonHash::hash_no_pad(&input).elements); + pub fn hash(&self, _params: &Params) -> Hash { + let input = self.to_fields(_params).0; + let h = hash_fields(&input); h } } @@ -297,7 +308,7 @@ impl From for Predicate { } impl ToFields for Predicate { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { // serialize: // NativePredicate(id) as (0, id, 0, 0, 0, 0) -- id: usize // BatchSelf(i) as (1, i, 0, 0, 0, 0) -- i: usize @@ -306,27 +317,20 @@ impl ToFields for Predicate { // -- i: usize // in every case: pad to (hash_size + 2) field elements - let mut fields: Vec = Vec::new(); - match self { - Self::Native(p) => { - fields = std::iter::once(F::from_canonical_u64(1)) - .chain(p.to_fields(params).0.into_iter()) - .collect(); - } - Self::BatchSelf(i) => { - fields = std::iter::once(F::from_canonical_u64(2)) - .chain(std::iter::once(F::from_canonical_usize(*i))) - .collect(); - } - Self::Custom(CustomPredicateRef(pb, i)) => { - fields = std::iter::once(F::from_canonical_u64(3)) - .chain(pb.hash(params).0) - .chain(std::iter::once(F::from_canonical_usize(*i))) - .collect(); - } - } - fields.resize_with(params.predicate_size(), || F::from_canonical_u64(0)); - (fields, params.predicate_size()) + let mut fields: Vec = match self { + Self::Native(p) => std::iter::once(F::from_canonical_u64(1)) + .chain(p.to_fields(_params).0.into_iter()) + .collect(), + Self::BatchSelf(i) => std::iter::once(F::from_canonical_u64(2)) + .chain(std::iter::once(F::from_canonical_usize(*i))) + .collect(), + Self::Custom(CustomPredicateRef(pb, i)) => std::iter::once(F::from_canonical_u64(3)) + .chain(pb.hash(_params).0) + .chain(std::iter::once(F::from_canonical_usize(*i))) + .collect(), + }; + fields.resize_with(Params::predicate_size(), || F::from_canonical_u64(0)); + (fields, Params::predicate_size()) } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 9a62ad3..4fcbeba 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,36 +1,38 @@ //! The middleware includes the type definitions and the traits used to connect the frontend and //! the backend. +mod basetypes; +pub mod containers; mod custom; mod operation; mod statement; +pub use basetypes::*; pub use custom::*; pub use operation::*; pub use statement::*; -use anyhow::{anyhow, Error, Result}; +use anyhow::Result; use dyn_clone::DynClone; -use hex::{FromHex, FromHexError}; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::field::types::{Field, PrimeField64}; -use plonky2::hash::poseidon::PoseidonHash; -use plonky2::plonk::config::{Hasher, PoseidonGoldilocksConfig}; use std::any::Any; -use std::cmp::{Ord, Ordering}; use std::collections::HashMap; use std::fmt; -pub mod containers; +pub const SELF: PodId = PodId(SELF_ID_HASH); -/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2 -pub type F = GoldilocksField; -/// C is the Plonky2 config used in POD2 to work with Plonky2 recursion. -pub type C = PoseidonGoldilocksConfig; -/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension). -pub const D: usize = 2; +impl fmt::Display for PodId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if *self == SELF { + write!(f, "self") + } else if self.0 == NULL { + write!(f, "null") + } else { + write!(f, "{}", self.0) + } + } +} -#[derive(Clone, Debug, PartialEq, Eq, Hash)] /// AnchoredKey is a tuple containing (OriginId: PodId, key: Hash) +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AnchoredKey(pub PodId, pub Hash); impl AnchoredKey { @@ -52,168 +54,15 @@ impl fmt::Display for AnchoredKey { /// An entry consists of a key-value pair. pub type Entry = (String, Value); -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] -pub struct Value(pub [F; 4]); - -impl ToFields for Value { - fn to_fields(&self, params: Params) -> (Vec, usize) { - (self.0.to_vec(), 4) - } -} - -impl Value { - pub fn to_bytes(self) -> Vec { - self.0 - .iter() - .flat_map(|e| e.to_canonical_u64().to_le_bytes()) - .collect() - } -} - -impl Ord for Value { - fn cmp(&self, other: &Self) -> Ordering { - for (lhs, rhs) in self.0.iter().zip(other.0.iter()).rev() { - let (lhs, rhs) = (lhs.to_canonical_u64(), rhs.to_canonical_u64()); - if lhs < rhs { - return Ordering::Less; - } else if lhs > rhs { - return Ordering::Greater; - } - } - return Ordering::Equal; - } -} - -impl PartialOrd for Value { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl From for Value { - fn from(v: i64) -> Self { - let lo = F::from_canonical_u64((v as u64) & 0xffffffff); - let hi = F::from_canonical_u64((v as u64) >> 32); - Value([lo, hi, F::ZERO, F::ZERO]) - } -} - -impl From for Value { - fn from(h: Hash) -> Self { - Value(h.0) - } -} - -impl TryInto for Value { - type Error = Error; - fn try_into(self) -> std::result::Result { - let value = self.0; - if &value[2..] != &[F::ZERO, F::ZERO] - || value[..2] - .iter() - .all(|x| x.to_canonical_u64() > u32::MAX as u64) - { - Err(anyhow!("Value not an element of the i64 embedding.")) - } else { - Ok((value[0].to_canonical_u64() + value[1].to_canonical_u64() << 32) as i64) - } - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0[2].is_zero() && self.0[3].is_zero() { - // Assume this is an integer - let (l0, l1) = (self.0[0].to_canonical_u64(), self.0[1].to_canonical_u64()); - assert!(l0 < (1 << 32)); - assert!(l1 < (1 << 32)); - write!(f, "{}", l0 + l1 * (1 << 32)) - } else { - // Assume this is a hash - Hash(self.0).fmt(f) - } - } -} - -#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)] -pub struct Hash(pub [F; 4]); - -impl ToFields for Hash { - fn to_fields(&self, params: Params) -> (Vec, usize) { - (self.0.to_vec(), 4) - } -} - -impl Ord for Hash { - fn cmp(&self, other: &Self) -> Ordering { - Value(self.0).cmp(&Value(other.0)) - } -} - -impl PartialOrd for Hash { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); -pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); - -impl fmt::Display for Hash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let v0 = self.0[0].to_canonical_u64(); - for i in 0..4 { - write!(f, "{:02x}", (v0 >> (i * 8)) & 0xff)?; - } - write!(f, "…") - } -} - -impl FromHex for Hash { - type Error = FromHexError; - - fn from_hex>(hex: T) -> Result { - // In little endian - let bytes = <[u8; 32]>::from_hex(hex)?; - let mut buf: [u8; 8] = [0; 8]; - let mut inner = [F::ZERO; 4]; - for i in 0..4 { - buf.copy_from_slice(&bytes[8 * i..8 * (i + 1)]); - inner[i] = F::from_canonical_u64(u64::from_le_bytes(buf)); - } - Ok(Self(inner)) - } -} - -impl From<&str> for Hash { - fn from(s: &str) -> Self { - hash_str(s) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] pub struct PodId(pub Hash); impl ToFields for PodId { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, params: &Params) -> (Vec, usize) { self.0.to_fields(params) } } -pub const SELF: PodId = PodId(Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO])); - -impl fmt::Display for PodId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if *self == SELF { - write!(f, "self") - } else if self.0 == NULL { - write!(f, "null") - } else { - write!(f, "{}", self.0) - } - } -} - pub enum PodType { None = 0, MockSigned = 1, @@ -228,26 +77,7 @@ impl From for Value { } } -pub fn hash_str(s: &str) -> Hash { - let mut input = s.as_bytes().to_vec(); - input.push(1); // padding - - // Merge 7 bytes into 1 field, because the field is slightly below 64 bits - let input: Vec = input - .chunks(7) - .map(|bytes| { - let mut v: u64 = 0; - for b in bytes.iter().rev() { - v <<= 8; - v += *b as u64; - } - F::from_canonical_u64(v) - }) - .collect(); - Hash(PoseidonHash::hash_no_pad(&input).elements) -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Params { pub max_input_signed_pods: usize, pub max_input_main_pods: usize, @@ -260,8 +90,6 @@ pub struct Params { // in a custom predicate pub max_custom_predicate_arity: usize, pub max_custom_batch_size: usize, - // number of field elements in a hash - pub hash_size: usize, } impl Params { @@ -269,33 +97,33 @@ impl Params { self.max_statements - self.max_public_statements } - pub fn statement_tmpl_arg_size(self) -> usize { - 2 * self.hash_size + 1 + pub fn statement_tmpl_arg_size() -> usize { + 2 * HASH_SIZE + 1 } - pub fn predicate_size(self) -> usize { - self.hash_size + 2 + pub fn predicate_size() -> usize { + HASH_SIZE + 2 } - pub fn statement_tmpl_size(self) -> usize { - self.predicate_size() + self.max_statement_args * self.statement_tmpl_arg_size() + pub fn statement_tmpl_size(&self) -> usize { + Self::predicate_size() + self.max_statement_args * Self::statement_tmpl_arg_size() } - pub fn custom_predicate_size(self) -> usize { + pub fn custom_predicate_size(&self) -> usize { self.max_custom_predicate_arity * self.statement_tmpl_size() + 2 } - pub fn custom_predicate_batch_size_field_elts(self) -> usize { + pub fn custom_predicate_batch_size_field_elts(&self) -> usize { self.max_custom_batch_size * self.custom_predicate_size() } - pub fn print_serialized_sizes(self) -> () { + pub fn print_serialized_sizes(&self) -> () { println!("Parameter sizes:"); println!( " Statement template argument: {}", - self.statement_tmpl_arg_size() + Self::statement_tmpl_arg_size() ); - println!(" Predicate: {}", self.predicate_size()); + println!(" Predicate: {}", Self::predicate_size()); println!(" Statement template: {}", self.statement_tmpl_size()); println!(" Custom predicate: {}", self.custom_predicate_size()); println!( @@ -318,7 +146,6 @@ impl Default for Params { max_operation_args: 5, max_custom_predicate_arity: 5, max_custom_batch_size: 5, - hash_size: 4, } } } @@ -386,5 +213,5 @@ pub trait PodProver { pub trait ToFields { /// returns Vec representation of the type, and a usize indicating how many field elements /// does the vector contain - fn to_fields(&self, params: Params) -> (Vec, usize); + fn to_fields(&self, params: &Params) -> (Vec, usize); } diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index a46c44b..17414bb 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -1,9 +1,9 @@ use anyhow::{anyhow, Result}; use plonky2::field::types::Field; -use std::{collections::HashMap, fmt}; +use std::fmt; use strum_macros::FromRepr; -use super::{AnchoredKey, CustomPredicateRef, Hash, Params, Predicate, ToFields, Value, F}; +use super::{AnchoredKey, CustomPredicateRef, Params, Predicate, ToFields, Value, F}; pub const KEY_SIGNER: &str = "_signer"; pub const KEY_TYPE: &str = "_type"; @@ -25,7 +25,7 @@ pub enum NativePredicate { } impl ToFields for NativePredicate { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { (vec![F::from_canonical_u64(*self as u64)], 1) } } @@ -88,12 +88,12 @@ impl Statement { } impl ToFields for Statement { - fn to_fields(&self, params: Params) -> (Vec, usize) { - let (native_statement_f, native_statement_f_len) = self.code().to_fields(params); + fn to_fields(&self, _params: &Params) -> (Vec, usize) { + let (native_statement_f, native_statement_f_len) = self.code().to_fields(_params); let (vec_statementarg_f, vec_statementarg_f_len) = self .args() .into_iter() - .map(|statement_arg| statement_arg.to_fields(params)) + .map(|statement_arg| statement_arg.to_fields(_params)) .fold((Vec::new(), 0), |mut acc, (f, l)| { acc.0.extend(f); acc.1 += l; @@ -156,7 +156,7 @@ impl StatementArg { } impl ToFields for StatementArg { - fn to_fields(&self, params: Params) -> (Vec, usize) { + fn to_fields(&self, _params: &Params) -> (Vec, usize) { // NOTE: current version returns always the same amount of field elements in the returned // vector, which means that the `None` case is padded with 8 zeroes, and the `Literal` case // is padded with 4 zeroes. Since the returned vector will mostly be hashed (and reproduced @@ -175,8 +175,8 @@ impl ToFields for StatementArg { .concat() } StatementArg::Key(ak) => { - let (podid_f, _) = ak.0.to_fields(params); - let (hash_f, _) = ak.1.to_fields(params); + let (podid_f, _) = ak.0.to_fields(_params); + let (hash_f, _) = ak.1.to_fields(_params); [podid_f, hash_f].concat() } };