diff --git a/Cargo.toml b/Cargo.toml index 657f0c6..8c0e9bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ itertools = "0.14.0" strum = "0.26" strum_macros = "0.26" anyhow = "1.0.56" +dyn-clone = "1.0.18" diff --git a/src/backend.rs b/src/backend.rs index 32b9db2..90a21b3 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,71 +1,15 @@ +// TODO: Move the SignedPod.id calculation to mock_signed +// TODO: Move the MainPod logic to mock_main and implement the MainPod trait +/* use anyhow::Result; use itertools::Itertools; use plonky2::field::types::{Field, PrimeField64}; use std::collections::HashMap; -use std::fmt; use std::io::{self, Write}; use std::iter; -use strum_macros::FromRepr; -use crate::{merkletree::MerkleTree, Hash, Params, PodId, F, NULL}; - -#[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq)] -pub enum NativeStatement { - None = 0, - ValueOf = 1, - Equal = 2, - NotEqual, - Gt, - Lt, - Contains, - NotContains, - SumOf, - ProductOf, - MaxOf, -} - -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] -pub struct Value(pub [F; 4]); - -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, Debug, PartialEq, Eq, Hash)] -pub struct AnchoredKey(pub PodId, pub Hash); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum StatementArg { - None, - Literal(Value), - Ref(AnchoredKey), -} - -impl StatementArg { - pub fn is_none(&self) -> bool { - matches!(self, Self::None) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Statement(pub NativeStatement, pub Vec); - -impl Statement { - pub fn is_none(&self) -> bool { - matches!(self.0, NativeStatement::None) - } -} +use crate::merkletree::MerkleTree; +use crate::middleware::{Hash, Params, PodId, Value, NULL}; #[derive(Clone, Debug)] pub struct SignedPod { @@ -306,3 +250,4 @@ mod tests { Ok(()) } } +*/ diff --git a/src/backends.rs b/src/backends.rs new file mode 100644 index 0000000..0086830 --- /dev/null +++ b/src/backends.rs @@ -0,0 +1,3 @@ +pub mod mock_main; +pub mod mock_signed; +pub mod plonky2; diff --git a/src/backends/mock_main.rs b/src/backends/mock_main.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/backends/mock_signed.rs b/src/backends/mock_signed.rs new file mode 100644 index 0000000..8c1cb7b --- /dev/null +++ b/src/backends/mock_signed.rs @@ -0,0 +1,124 @@ +use crate::middleware::{ + hash_str, Hash, Params, PodId, PodSigner, PodType, SignedPod, Value, KEY_SIGNER, KEY_TYPE, +}; +use itertools::Itertools; +use std::any::Any; +use std::collections::HashMap; + +pub struct MockSigner { + pub pk: String, +} + +fn calculate_pod_id(kvs: &HashMap) -> PodId { + let mut s = String::new(); + for (k, v) in kvs.iter().sorted_by_key(|kv| kv.0) { + s += &format!("{}:{},", k, v); + } + PodId(hash_str(&s)) +} + +impl PodSigner for MockSigner { + fn sign(&mut self, _params: &Params, kvs: &HashMap) -> Box { + let mut kvs = kvs.clone(); + let pk_hash = hash_str(&self.pk); + kvs.insert(hash_str(&KEY_SIGNER), Value(pk_hash.0)); + kvs.insert(hash_str(&KEY_TYPE), Value::from(PodType::MockSigned)); + + let id = calculate_pod_id(&kvs); + let signature = format!("{}_signed_by_{}", id, pk_hash); + Box::new(MockSignedPod { + kvs: kvs.clone(), + id, + signature, + }) + } +} + +#[derive(Clone, Debug)] +pub struct MockSignedPod { + pub id: PodId, + pub signature: String, + pub kvs: HashMap, +} + +impl SignedPod for MockSignedPod { + fn verify(&self) -> bool { + // Verify type + if Some(&Value::from(PodType::MockSigned)) != self.kvs.get(&hash_str(&KEY_TYPE)) { + return false; + } + + // Verify id + let id = calculate_pod_id(&self.kvs); + if id != self.id { + return false; + } + + // Verify signature + let pk_hash = match self.kvs.get(&hash_str(&KEY_SIGNER)) { + Some(v) => v, + None => return false, + }; + let signature = format!("{}_signed_by_{}", id, pk_hash); + if signature != self.signature { + return false; + } + + return true; + } + + fn id(&self) -> PodId { + self.id + } + + fn kvs(&self) -> HashMap { + self.kvs.clone() + } + + fn into_any(self: Box) -> Box { + self + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::frontend; + use crate::middleware::{self, F, NULL}; + use plonky2::field::types::Field; + + #[test] + fn test_mock_signed_0() { + let params = middleware::Params::default(); + let mut pod = frontend::SignedPodBuilder::new(¶ms); + pod.insert("idNumber", "4242424242"); + pod.insert("dateOfBirth", 1169909384); + pod.insert("socialSecurityNumber", "G2121210"); + + let mut signer = MockSigner { pk: "Molly".into() }; + let pod = pod.sign(&mut signer); + let pod = pod.pod.into_any().downcast::().unwrap(); + + assert_eq!(pod.verify(), true); + println!("id: {}", pod.id()); + println!("kvs: {:?}", pod.kvs()); + + let mut bad_pod = pod.clone(); + bad_pod.signature = "".into(); + assert_eq!(bad_pod.verify(), false); + + let mut bad_pod = pod.clone(); + bad_pod.id.0 .0[0] = F::ZERO; + assert_eq!(bad_pod.verify(), false); + + let mut bad_pod = pod.clone(); + bad_pod + .kvs + .insert(hash_str(KEY_SIGNER), Value(PodId(NULL).0 .0)); + assert_eq!(bad_pod.verify(), false); + + let mut bad_pod = pod.clone(); + bad_pod.kvs.insert(hash_str(KEY_TYPE), Value::from(0)); + assert_eq!(bad_pod.verify(), false); + } +} diff --git a/src/backends/plonky2.rs b/src/backends/plonky2.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/frontend.rs b/src/frontend.rs index 7009a4e..924410c 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -1,3 +1,6 @@ +//! The frontend includes the user-level abstractions and user-friendly types to define and work +//! with Pods. + use anyhow::Result; use itertools::Itertools; use plonky2::field::types::Field; @@ -6,19 +9,22 @@ use std::convert::From; use std::fmt; use std::io::{self, Write}; -use crate::backend; -use crate::{hash_str, Params, PodId, F, SELF}; +use crate::middleware::{ + self, hash_str, Hash, MainPodInputs, NativeOperation, NativeStatement, Params, PodId, + PodProver, PodSigner, F, SELF, +}; +/// This type is just for presentation purposes. #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] -pub enum PodType { +pub enum PodClass { #[default] - Signed = 1, + Signed, Main, } // An Origin, which represents a reference to an ancestor POD. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct Origin(pub PodType, pub PodId); +pub struct Origin(pub PodClass, pub PodId); #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleTree { @@ -44,15 +50,13 @@ impl From for Value { } } -impl From<&Value> for backend::Value { +impl From<&Value> for middleware::Value { fn from(v: &Value) -> Self { match v { - Value::String(s) => backend::Value(hash_str(s).0), - Value::Int(v) => { - backend::Value([F::from_canonical_u64(*v as u64), F::ZERO, F::ZERO, F::ZERO]) - } + Value::String(s) => middleware::Value(hash_str(s).0), + Value::Int(v) => middleware::Value::from(*v), // TODO - Value::MerkleTree(mt) => backend::Value([ + Value::MerkleTree(mt) => middleware::Value([ F::from_canonical_u64(mt.root as u64), F::ZERO, F::ZERO, @@ -72,61 +76,61 @@ impl fmt::Display for Value { } } +#[derive(Clone, Debug)] +pub struct SignedPodBuilder { + pub params: Params, + pub kvs: HashMap, +} + +impl SignedPodBuilder { + pub fn new(params: &Params) -> Self { + Self { + params: params.clone(), + kvs: HashMap::new(), + } + } + + pub fn insert(&mut self, key: impl Into, value: impl Into) { + self.kvs.insert(key.into(), value.into()); + } + + pub fn sign(&self, signer: &mut S) -> SignedPod { + let mut kvs = HashMap::new(); + let mut key_string_map = HashMap::new(); + for (k, v) in self.kvs.iter() { + let k_hash = hash_str(k); + kvs.insert(k_hash, middleware::Value::from(v)); + key_string_map.insert(k_hash, k.clone()); + } + let pod = signer.sign(&self.params, &kvs); + SignedPod { + pod, + key_string_map, + } + } +} + /// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the /// string<-->hash relation of the keys. -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub struct SignedPod { - pub pod: backend::SignedPod, - // `string_key_map` is a hashmap to store the relation between key strings and key hashes - // TODO review if maybe store it as , so that when iterating we can get each - // hash respective string - string_key_map: HashMap, + pub pod: Box, + /// HashMap to store the reverse relation between key strings and key hashes + pub key_string_map: HashMap, } impl SignedPod { - pub fn new(params: &Params, kvs: HashMap) -> Result { - let (hashed_kvs, string_key_map): ( - HashMap, - HashMap, - ) = kvs.iter().fold( - (HashMap::new(), HashMap::new()), - |(mut hashed_kvs, mut key_to_hash), (k, v)| { - let h = hash_str(k); - hashed_kvs.insert(h, backend::Value::from(v)); - key_to_hash.insert(k.clone(), h); - (hashed_kvs, key_to_hash) - }, - ); - let pod = backend::SignedPod::new(params, hashed_kvs)?; - Ok(Self { - pod, - string_key_map, - }) - } pub fn id(&self) -> PodId { - self.pod.id + self.pod.id() } pub fn origin(&self) -> Origin { - Origin(PodType::Signed, self.pod.id) + Origin(PodClass::Signed, self.id()) } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum NativeStatement { - Equal = 2, - NotEqual, - Gt, - Lt, - Contains, - NotContains, - SumOf, - ProductOf, - MaxOf, -} - -impl From for backend::NativeStatement { - fn from(v: NativeStatement) -> Self { - Self::from_repr(v as usize).unwrap() + pub fn verify(&self) -> bool { + self.pod.verify() + } + pub fn kvs(&self) -> HashMap { + self.pod.kvs() } } @@ -136,48 +140,18 @@ pub struct AnchoredKey(pub Origin, pub String); #[derive(Clone, Debug, PartialEq, Eq)] pub enum StatementArg { Literal(Value), - Ref(AnchoredKey), + Key(AnchoredKey), } impl fmt::Display for StatementArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Literal(v) => write!(f, "{}", v), - Self::Ref(r) => write!(f, "{}.{}", r.0 .1, r.1), + Self::Key(r) => write!(f, "{}.{}", r.0 .1, r.1), } } } -impl From for StatementArg { - fn from(v: Value) -> Self { - StatementArg::Literal(v) - } -} - -impl From<&str> for StatementArg { - fn from(s: &str) -> Self { - StatementArg::Literal(Value::from(s)) - } -} - -impl From for StatementArg { - fn from(v: i64) -> Self { - StatementArg::Literal(Value::from(v)) - } -} - -impl From<(Origin, &str)> for StatementArg { - fn from((origin, key): (Origin, &str)) -> Self { - StatementArg::Ref(AnchoredKey(origin, key.to_string())) - } -} - -impl From<(&SignedPod, &str)> for StatementArg { - fn from((pod, key): (&SignedPod, &str)) -> Self { - StatementArg::Ref(AnchoredKey(pod.origin(), key.to_string())) - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct Statement(pub NativeStatement, pub Vec); @@ -194,149 +168,305 @@ impl fmt::Display for Statement { } } -#[derive(Clone, Debug)] -pub struct MainPodBuilder { - pub params: Params, - pub statements: Vec, - pub operations: Vec, -} - -impl MainPodBuilder { - pub fn push_statement(&mut self, st: Statement, op: Operation) { - self.statements.push(st); - self.operations.push(op); - } - pub fn build(self) -> MainPod { - MainPod { - params: self.params, - id: PodId::default(), // TODO - input_signed_pods: vec![], // TODO - input_main_pods: vec![], // TODO - statements: self - .statements - .into_iter() - .zip(self.operations.into_iter()) - .collect(), - } - } -} - -#[derive(Clone, Debug)] -pub struct MainPod { - pub params: Params, - pub id: PodId, - pub input_signed_pods: Vec, - pub input_main_pods: Vec, - pub statements: Vec<(Statement, Operation)>, -} - -impl MainPod { - pub fn origin(&self) -> Origin { - Origin(PodType::Main, self.id) - } - - pub fn compile(&self) -> Result { - MainPodCompiler::new(self).compile() - } - - pub fn max_priv_statements(&self) -> usize { - self.params.max_statements - self.params.max_public_statements - } -} - -struct MainPodCompiler<'a> { - // Input - pod: &'a MainPod, - // Output - statements: Vec, - // Internal state - const_cnt: usize, -} - -impl<'a> MainPodCompiler<'a> { - fn new(pod: &'a MainPod) -> Self { - Self { - pod, - statements: Vec::new(), - const_cnt: 0, - } - } - - fn compile_st(&mut self, st: &Statement) { - let mut args = Vec::new(); - let Statement(front_typ, front_args) = st; - for front_arg in front_args { - let key = match front_arg { - StatementArg::Literal(v) => { - let key = format!("_c{}", self.const_cnt); - let key_hash = hash_str(&key); - self.const_cnt += 1; - let value_of_args = vec![ - backend::StatementArg::Ref(backend::AnchoredKey(SELF, key_hash)), - backend::StatementArg::Literal(backend::Value::from(v)), - ]; - self.statements.push(backend::Statement( - backend::NativeStatement::ValueOf, - value_of_args, - )); - backend::AnchoredKey(SELF, key_hash) - } - StatementArg::Ref(k) => backend::AnchoredKey(k.0 .1, hash_str(&k.1)), - }; - args.push(backend::StatementArg::Ref(key)); - if args.len() > self.pod.params.max_statement_args { - panic!("too many statement args"); - } - } - self.statements.push(backend::Statement( - backend::NativeStatement::from(*front_typ), - args, - )); - } - - pub fn compile(mut self) -> Result { - let MainPod { - statements, - params, - input_signed_pods, - .. - } = self.pod; - for st in statements { - self.compile_st(&st.0); - if self.statements.len() > params.max_statements { - panic!("too many statements"); - } - } - let input_signed_pods: Vec = - input_signed_pods.iter().map(|p| p.pod.clone()).collect(); - backend::MainPod::new(params.clone(), input_signed_pods, vec![], self.statements) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NativeOperation { - TransitiveEqualityFromStatements = 1, - GtToNonequality = 2, - LtToNonequality = 3, - Auto = 1024, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum OperationArg { Statement(Statement), Key(AnchoredKey), + Literal(Value), + Entry(String, Value), +} + +impl From for OperationArg { + fn from(v: Value) -> Self { + Self::Literal(v) + } +} + +impl From<&Value> for OperationArg { + fn from(v: &Value) -> Self { + Self::Literal(v.clone()) + } +} + +impl From<&str> for OperationArg { + fn from(s: &str) -> Self { + Self::Literal(Value::from(s)) + } +} + +impl From for OperationArg { + fn from(v: i64) -> Self { + Self::Literal(Value::from(v)) + } +} + +impl From<(Origin, &str)> for OperationArg { + fn from((origin, key): (Origin, &str)) -> Self { + Self::Key(AnchoredKey(origin, key.to_string())) + } +} + +impl From<(&SignedPod, &str)> for OperationArg { + fn from((pod, key): (&SignedPod, &str)) -> Self { + Self::Key(AnchoredKey(pod.origin(), key.to_string())) + } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Operation(pub NativeOperation, pub Vec); +#[derive(Debug)] +pub struct MainPodBuilder { + pub params: Params, + pub input_signed_pods: Vec, + pub input_main_pods: Vec, + pub statements: Vec, + pub operations: Vec, + // Internal state + const_cnt: usize, +} + +impl MainPodBuilder { + pub fn new(params: &Params) -> Self { + Self { + params: params.clone(), + input_signed_pods: Vec::new(), + input_main_pods: Vec::new(), + statements: Vec::new(), + operations: Vec::new(), + const_cnt: 0, + } + } + pub fn add_signed_pod(&mut self, pod: &SignedPod) { + self.input_signed_pods.push(pod.clone()); + } + pub fn add_main_pod(&mut self, pod: MainPod) { + self.input_main_pods.push(pod); + } + pub fn insert(&mut self, st_op: (Statement, Operation)) { + let (st, op) = st_op; + self.statements.push(st); + self.operations.push(op); + } + + /// Convert [OperationArg]s to [StatementArg]s for the operations that work with entries + fn op_args_entries(&mut self, args: &mut [OperationArg]) -> Vec { + let mut st_args = Vec::new(); + for arg in args.iter_mut() { + match arg { + OperationArg::Statement(s) => panic!("can't convert Statement to StatementArg"), + OperationArg::Key(k) => st_args.push(StatementArg::Key(k.clone())), + OperationArg::Literal(v) => { + let k = format!("c{}", self.const_cnt); + self.const_cnt += 1; + let value_of_st = self.op(Operation( + NativeOperation::NewEntry, + vec![OperationArg::Entry(k.clone(), v.clone())], + )); + *arg = OperationArg::Key(AnchoredKey(Origin(PodClass::Main, SELF), k.clone())); + st_args.push(value_of_st.1[0].clone()) + } + OperationArg::Entry(k, v) => { + st_args.push(StatementArg::Key(AnchoredKey( + Origin(PodClass::Main, SELF), + k.clone(), + ))); + st_args.push(StatementArg::Literal(v.clone())) + } + }; + } + st_args + } + + pub fn op(&mut self, mut op: Operation) -> &Statement { + use NativeOperation::*; + let Operation(op_type, ref mut args) = op; + // TODO: argument type checking + let st = match op_type { + None => Statement(NativeStatement::None, vec![]), + NewEntry => Statement(NativeStatement::ValueOf, self.op_args_entries(args)), + CopyStatement => todo!(), + EqualFromEntries => Statement(NativeStatement::Equal, self.op_args_entries(args)), + NotEqualFromEntries => Statement(NativeStatement::NotEqual, self.op_args_entries(args)), + GtFromEntries => Statement(NativeStatement::Gt, self.op_args_entries(args)), + LtFromEntries => Statement(NativeStatement::Lt, self.op_args_entries(args)), + TransitiveEqualFromStatements => todo!(), + GtToNotEqual => todo!(), + LtToNotEqual => todo!(), + ContainsFromEntries => Statement(NativeStatement::Contains, self.op_args_entries(args)), + NotContainsFromEntries => { + Statement(NativeStatement::NotContains, self.op_args_entries(args)) + } + RenameContainedBy => todo!(), + SumOf => todo!(), + ProductOf => todo!(), + MaxOf => todo!(), + }; + self.operations.push(op); + self.statements.push(st); + &self.statements[self.statements.len() - 1] + } + + pub fn prove(&self, prover: &mut P) -> MainPod { + let compiler = MainPodCompiler::new(&self.params); + let inputs = MainPodCompilerInputs { + // signed_pods: &self.input_signed_pods, + // main_pods: &self.input_main_pods, + statements: &self.statements, + operations: &self.operations, + }; + let (statements, operations) = compiler.compile(inputs).expect("TODO"); + + let inputs = middleware::MainPodInputs { + signed_pods: &self + .input_signed_pods + .iter() + .map(|p| p.pod.as_ref()) + .collect_vec(), + main_pods: &self + .input_main_pods + .iter() + .map(|p| p.pod.as_ref()) + .collect_vec(), + statements: &statements, + operations: &operations, + }; + let pod = prover.prove(&self.params, inputs); + MainPod { pod } + } +} + +#[derive(Debug)] +pub struct MainPod { + pub pod: Box, + // TODO: metadata +} + +impl MainPod { + pub fn id(&self) -> PodId { + self.pod.id() + } + pub fn origin(&self) -> Origin { + Origin(PodClass::Signed, self.id()) + } +} + +struct MainPodCompilerInputs<'a> { + // pub signed_pods: &'a [Box], + // pub main_pods: &'a [Box], + pub statements: &'a [Statement], + pub operations: &'a [Operation], +} + +struct MainPodCompiler { + params: Params, + // Output + statements: Vec, + operations: Vec, +} + +impl MainPodCompiler { + fn new(params: &Params) -> Self { + Self { + params: params.clone(), + statements: Vec::new(), + operations: Vec::new(), + } + } + + fn max_priv_statements(&self) -> usize { + self.params.max_statements - self.params.max_public_statements + } + + fn push_st_op(&mut self, st: middleware::Statement, op: middleware::Operation) { + self.statements.push(st); + self.operations.push(op); + } + + fn compile_op_arg(&self, op_arg: &OperationArg) -> middleware::OperationArg { + match op_arg { + OperationArg::Statement(s) => middleware::OperationArg::Statement(self.compile_st(s)), + OperationArg::Key(k) => middleware::OperationArg::Key(Self::compile_anchored_key(k)), + OperationArg::Literal(_v) => { + // OperationArg::Literal is a syntax sugar for the frontend. This is translated to + // a new ValueOf statement and it's key used instead. + unreachable!() + } + OperationArg::Entry(_k, _v) => { + // OperationArg::Entry is only used in the frontend. The (key, value) will only + // appear in the ValueOf statement in the backend. + middleware::OperationArg::None + } + } + } + + fn compile_anchored_key(key: &AnchoredKey) -> middleware::AnchoredKey { + middleware::AnchoredKey(key.0 .1, hash_str(&key.1)) + } + + fn compile_st(&self, st: &Statement) -> middleware::Statement { + let mut st_args = Vec::new(); + let Statement(front_st_typ, front_st_args) = st; + for front_st_arg in front_st_args { + match front_st_arg { + StatementArg::Literal(v) => { + st_args.push(middleware::StatementArg::Literal(middleware::Value::from( + v, + ))); + } + StatementArg::Key(k) => { + let key = Self::compile_anchored_key(k); + st_args.push(middleware::StatementArg::Key(key)); + } + }; + if st_args.len() > self.params.max_statement_args { + panic!("too many statement st_args"); + } + } + + middleware::Statement(*front_st_typ, st_args) + } + + fn compile_st_op(&mut self, st: &Statement, op: &Operation) { + let middle_st = self.compile_st(st); + self.push_st_op( + middle_st, + middleware::Operation( + op.0, + op.1.iter().map(|arg| self.compile_op_arg(arg)).collect(), + ), + ); + } + + pub fn compile<'a>( + mut self, + inputs: MainPodCompilerInputs<'a>, + ) -> Result<(Vec, Vec)> { + let MainPodCompilerInputs { + // signed_pods: _, + // main_pods: _, + statements, + operations, + } = inputs; + for (st, op) in statements.iter().zip_eq(operations.iter()) { + self.compile_st_op(st, op); + if self.statements.len() > self.params.max_statements { + panic!("too many statements"); + } + } + Ok((self.statements, self.operations)) + } +} + pub struct Printer {} impl Printer { pub fn fmt_op_arg(&self, w: &mut dyn Write, arg: &OperationArg) -> io::Result<()> { match arg { OperationArg::Statement(s) => write!(w, "{}", s), - OperationArg::Key(r) => write!(w, "{}.{}", r.0 .1, r.1), + OperationArg::Key(k) => write!(w, "{}.{}", k.0 .1, k.1), + OperationArg::Literal(v) => write!(w, "{}", v), + OperationArg::Entry(k, v) => write!(w, "({}, {})", k, v), } } @@ -351,43 +481,47 @@ impl Printer { Ok(()) } + // TODO fn fmt_signed_pod_builder + pub fn fmt_signed_pod(&self, w: &mut dyn Write, pod: &SignedPod) -> io::Result<()> { writeln!(w, "SignedPod (id:{}):", pod.id())?; // Note: current version iterates sorting by keys of the kvs, but the merkletree defined at // https://0xparc.github.io/pod2/merkletree.html will not need it since it will be // deterministic based on the keys values not on the order of the keys when added into the // tree. - for (k, v) in pod.pod.kvs.iter().sorted_by_key(|kv| kv.0) { + for (k, v) in pod.pod.kvs().iter().sorted_by_key(|kv| kv.0) { writeln!(w, " - {}: {}", k, v)?; } Ok(()) } - pub fn fmt_main_pod(&self, w: &mut dyn Write, pod: &MainPod) -> io::Result<()> { - writeln!(w, "MainPod (id:{}):", pod.id)?; + pub fn fmt_main_pod_builder(&self, w: &mut dyn Write, pod: &MainPodBuilder) -> io::Result<()> { + writeln!(w, "MainPod:")?; writeln!(w, " input_signed_pods:")?; for in_pod in &pod.input_signed_pods { writeln!(w, " - {}", in_pod.id())?; } writeln!(w, " input_main_pods:")?; for in_pod in &pod.input_main_pods { - writeln!(w, " - {}", in_pod.id)?; + writeln!(w, " - {}", in_pod.id())?; } writeln!(w, " statements:")?; - for st in &pod.statements { - let (st, op) = st; + for (st, op) in pod.statements.iter().zip_eq(pod.operations.iter()) { write!(w, " - {} <- ", st)?; self.fmt_op(w, op)?; write!(w, "\n")?; } Ok(()) } + + // TODO fn fmt_main_pod } #[cfg(test)] pub mod tests { use super::*; - use crate::Hash; + use crate::backends::mock_signed::MockSigner; + use crate::middleware::Hash; use hex::FromHex; use std::io; @@ -395,76 +529,82 @@ pub mod tests { PodId(Hash::from_hex(hex).unwrap()) } - fn auto() -> Operation { - Operation(NativeOperation::Auto, vec![]) - } - macro_rules! args { - ($($arg:expr),+) => {vec![$(StatementArg::from($arg)),*]} + ($($arg:expr),+) => {vec![$(OperationArg::from($arg)),*]} } - macro_rules! st { - (eq, $($arg:expr),+) => { Statement(NativeStatement::Equal, args!($($arg),*)) }; - (ne, $($arg:expr),+) => { Statement(NativeStatement::NotEqual, args!($($arg),*)) }; - (gt, $($arg:expr),+) => { Statement(NativeStatement::Gt, args!($($arg),*)) }; - (lt, $($arg:expr),+) => { Statement(NativeStatement::Lt, args!($($arg),*)) }; - (contains, $($arg:expr),+) => { Statement(NativeStatement::Contains, args!($($arg),*)) }; - (not_contains, $($arg:expr),+) => { Statement(NativeStatement::NotContains, args!($($arg),*)) }; - (sum_of, $($arg:expr),+) => { Statement(NativeStatement::SumOf, args!($($arg),*)) }; - (product_of, $($arg:expr),+) => { Statement(NativeStatement::product_of, args!($($arg),*)) }; - (max_of, $($arg:expr),+) => { Statement(NativeStatement::max_of, args!($($arg),*)) }; + macro_rules! op { + (eq, $($arg:expr),+) => { Operation(NativeOperation::EqualFromEntries, args!($($arg),*)) }; + (ne, $($arg:expr),+) => { Operation(NativeOperation::NotEqualFromEntries, args!($($arg),*)) }; + (gt, $($arg:expr),+) => { Operation(NativeOperation::GtFromEntries, args!($($arg),*)) }; + (lt, $($arg:expr),+) => { Operation(NativeOperation::LtFromEntries, args!($($arg),*)) }; + (contains, $($arg:expr),+) => { Operation(NativeOperation::ContainsFromEntries, args!($($arg),*)) }; + (not_contains, $($arg:expr),+) => { Operation(NativeOperation::NotContainsFromEntries, args!($($arg),*)) }; } - pub fn data_zu_kyc(params: Params) -> Result<(SignedPod, SignedPod, MainPod)> { - let mut kvs = HashMap::new(); - kvs.insert("idNumber".into(), "4242424242".into()); - kvs.insert("dateOfBirth".into(), 1169909384.into()); - kvs.insert("socialSecurityNumber".into(), "G2121210".into()); - let gov_id = SignedPod::new(¶ms, kvs)?; + pub fn zu_kyc_sign_pod_builders(params: &Params) -> (SignedPodBuilder, SignedPodBuilder) { + let mut gov_id = SignedPodBuilder::new(params); + gov_id.insert("idNumber", "4242424242"); + gov_id.insert("dateOfBirth", 1169909384); + gov_id.insert("socialSecurityNumber", "G2121210"); - let mut kvs = HashMap::new(); - kvs.insert("socialSecurityNumber".into(), "G2121210".into()); - kvs.insert("startDate".into(), 1706367566.into()); - let pay_stub = SignedPod::new(¶ms, kvs)?; + let mut pay_stub = SignedPodBuilder::new(params); + pay_stub.insert("socialSecurityNumber", "G2121210"); + pay_stub.insert("startDate", 1706367566); + (gov_id, pay_stub) + } + + pub fn zu_kyc_pod_builder( + params: &Params, + gov_id: &SignedPod, + pay_stub: &SignedPod, + ) -> MainPodBuilder { let sanction_list = Value::MerkleTree(MerkleTree { root: 1 }); let now_minus_18y: i64 = 1169909388; let now_minus_1y: i64 = 1706367566; - let mut statements: Vec<(Statement, Operation)> = Vec::new(); - statements.push(( - st!(not_contains, sanction_list, (&gov_id, "idNumber")), - auto(), - )); - statements.push((st!(lt, (&gov_id, "dateOfBirth"), now_minus_18y), auto())); - statements.push(( - st!( - eq, - (&gov_id, "socialSecurityNumber"), - (&pay_stub, "socialSecurityNumber") - ), - auto(), - )); - statements.push((st!(eq, (&pay_stub, "startDate"), now_minus_1y), auto())); - let kyc = MainPod { - params: params.clone(), - id: pod_id("3300000000000000000000000000000000000000000000000000000000000000"), - input_signed_pods: vec![gov_id.clone(), pay_stub.clone()], - input_main_pods: vec![], - statements, - }; - Ok((gov_id, pay_stub, kyc)) + let mut kyc = MainPodBuilder::new(¶ms); + kyc.add_signed_pod(&gov_id); + kyc.add_signed_pod(&pay_stub); + kyc.op(op!(not_contains, &sanction_list, (gov_id, "idNumber"))); + kyc.op(op!(lt, (gov_id, "dateOfBirth"), now_minus_18y)); + kyc.op(op!( + eq, + (gov_id, "socialSecurityNumber"), + (pay_stub, "socialSecurityNumber") + )); + kyc.op(op!(eq, (pay_stub, "startDate"), now_minus_1y)); + + kyc } #[test] fn test_front_0() -> Result<()> { - let (gov_id, pay_stub, kyc) = data_zu_kyc(Params::default())?; - let printer = Printer {}; let mut w = io::stdout(); + + let params = Params::default(); + let (gov_id, pay_stub) = zu_kyc_sign_pod_builders(¶ms); + + // TODO: print pods from the builder + + let mut signer = MockSigner { + pk: "ZooGov".into(), + }; + let gov_id = gov_id.sign(&mut signer); printer.fmt_signed_pod(&mut w, &gov_id).unwrap(); + + let mut signer = MockSigner { + pk: "ZooDeel".into(), + }; + let pay_stub = pay_stub.sign(&mut signer); printer.fmt_signed_pod(&mut w, &pay_stub).unwrap(); - printer.fmt_main_pod(&mut w, &kyc).unwrap(); + + let kyc = zu_kyc_pod_builder(¶ms, &gov_id, &pay_stub); + printer.fmt_main_pod_builder(&mut w, &kyc).unwrap(); + + // TODO: prove kyc with MockProver and print it Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index c0ddee9..46f0741 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,121 +1,5 @@ -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::cmp::Ordering; -use std::fmt; - pub mod backend; +pub mod backends; pub mod frontend; pub mod merkletree; - -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; - -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] -pub struct Hash(pub [F; 4]); -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 Ord for Hash { - fn cmp(&self, other: &Self) -> Ordering { - self.to_string().cmp(&other.to_string()) - } -} -impl PartialOrd for Hash { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub struct PodId(pub Hash); -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 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)] -pub struct Params { - pub max_input_signed_pods: usize, - pub max_input_main_pods: usize, - pub max_statements: usize, - pub max_signed_pod_values: usize, - pub max_public_statements: usize, - pub max_statement_args: usize, -} - -impl Params { - pub fn max_priv_statements(&self) -> usize { - self.max_statements - self.max_public_statements - } -} - -impl Default for Params { - fn default() -> Self { - Self { - max_input_signed_pods: 3, - max_input_main_pods: 3, - max_statements: 20, - max_signed_pod_values: 8, - max_public_statements: 10, - max_statement_args: 5, - } - } -} +pub mod middleware; diff --git a/src/merkletree.rs b/src/merkletree.rs index 45024a3..83f1bac 100644 --- a/src/merkletree.rs +++ b/src/merkletree.rs @@ -16,8 +16,7 @@ use plonky2::plonk::config::Hasher; use std::collections::HashMap; use std::iter::IntoIterator; -use crate::backend::Value; -use crate::{Hash, C, D, F}; +use crate::middleware::{Hash, Value, C, D, F}; const CAP_HEIGHT: usize = 0; @@ -143,7 +142,7 @@ impl<'a> IntoIterator for &'a MerkleTree { pub mod tests { use super::*; - use crate::hash_str; + use crate::middleware::hash_str; #[test] fn test_merkletree() -> Result<()> { diff --git a/src/middleware.rs b/src/middleware.rs new file mode 100644 index 0000000..9ccc298 --- /dev/null +++ b/src/middleware.rs @@ -0,0 +1,317 @@ +//! The middleware includes the type definitions and the traits used to connect the frontend and +//! the backend. + +use dyn_clone::DynClone; +use hex::{FromHex, FromHexError}; +use itertools::Itertools; +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; +use strum_macros::FromRepr; + +pub const KEY_SIGNER: &str = "_signer"; +pub const KEY_TYPE: &str = "_type"; + +/// 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; + +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] +pub struct Value(pub [F; 4]); + +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 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 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 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)) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct PodId(pub Hash); + +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, + MockMain = 2, + Signed = 3, + Main = 4, +} + +impl From for Value { + fn from(v: PodType) -> Self { + Value::from(v as i64) + } +} + +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)] +pub struct Params { + pub max_input_signed_pods: usize, + pub max_input_main_pods: usize, + pub max_statements: usize, + pub max_signed_pod_values: usize, + pub max_public_statements: usize, + pub max_statement_args: usize, +} + +impl Params { + pub fn max_priv_statements(&self) -> usize { + self.max_statements - self.max_public_statements + } +} + +impl Default for Params { + fn default() -> Self { + Self { + max_input_signed_pods: 3, + max_input_main_pods: 3, + max_statements: 20, + max_signed_pod_values: 8, + max_public_statements: 10, + max_statement_args: 5, + } + } +} + +pub trait SignedPod: fmt::Debug + DynClone { + fn verify(&self) -> bool; + fn id(&self) -> PodId; + // NOTE: Maybe replace this by + // - `get(key: Hash) -> Option` + // - `iter() -> impl Iter<(Hash, Value)>` + fn kvs(&self) -> HashMap; + fn pub_statements(&self) -> Vec { + let id = self.id(); + let mut statements = Vec::new(); + for (k, v) in self.kvs().iter().sorted_by_key(|kv| kv.0) { + statements.push(Statement( + NativeStatement::ValueOf, + vec![ + StatementArg::Key(AnchoredKey(id, *k)), + StatementArg::Literal(*v), + ], + )); + } + statements + } + // Used for downcasting + fn into_any(self: Box) -> Box; +} + +// impl Clone for Box +dyn_clone::clone_trait_object!(SignedPod); + +pub trait PodSigner { + fn sign(&mut self, params: &Params, kvs: &HashMap) -> Box; +} + +#[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq)] +pub enum NativeStatement { + None = 0, + ValueOf = 1, + Equal = 2, + NotEqual = 3, + Gt = 4, + Lt = 5, + Contains = 6, + NotContains = 7, + SumOf = 8, + ProductOf = 9, + MaxOf = 10, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct AnchoredKey(pub PodId, pub Hash); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum StatementArg { + None, + Literal(Value), + Key(AnchoredKey), +} + +impl StatementArg { + pub fn is_none(&self) -> bool { + matches!(self, Self::None) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Statement(pub NativeStatement, pub Vec); + +impl Statement { + pub fn is_none(&self) -> bool { + matches!(self.0, NativeStatement::None) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NativeOperation { + None = 0, + NewEntry = 1, + CopyStatement = 2, + EqualFromEntries = 3, + NotEqualFromEntries = 4, + GtFromEntries = 5, + LtFromEntries = 6, + TransitiveEqualFromStatements = 7, + GtToNotEqual = 8, + LtToNotEqual = 9, + ContainsFromEntries = 10, + NotContainsFromEntries = 11, + RenameContainedBy = 12, + SumOf = 13, + ProductOf = 14, + MaxOf = 15, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OperationArg { + None, + Statement(Statement), + Key(AnchoredKey), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Operation(pub NativeOperation, pub Vec); + +pub trait MainPod: fmt::Debug + DynClone { + fn verify(&self) -> bool; + fn id(&self) -> PodId; + fn pub_statements(&self) -> Vec; + // Used for downcasting + fn into_any(self: Box) -> Box; +} + +// impl Clone for Box +dyn_clone::clone_trait_object!(MainPod); + +#[derive(Debug)] +pub struct MainPodInputs<'a> { + pub signed_pods: &'a [&'a dyn SignedPod], + pub main_pods: &'a [&'a dyn MainPod], + pub statements: &'a [Statement], + pub operations: &'a [Operation], +} + +pub trait PodProver { + fn prove(&mut self, params: &Params, inputs: MainPodInputs) -> Box; +}