Featurize middleware types that are actually defined by the backend (#94)

At the middleware we were defining some types that actually are dependant on the
backend no matter how we define them in the middleware.

For example, we were hardcoding the `Hash` and `Value` types and their related
behaviour (eg. `.to_fields()`) to be based on the length of 4 field elements,
but that's not a choice of the middleware, and in fact this is determined by the
backend itself. On the same time, those types and related methods do not belong
to the backend, since conceptually they are part of the middleware reasoning.

The intention of this PR is not to prematurely abstract the library, but 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" (hash, value, etc)
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).

As mentioned earlier, 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.
This commit is contained in:
arnaucube 2025-02-27 14:15:31 +01:00 committed by GitHub
parent af46ab7a8d
commit 423605f867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 359 additions and 278 deletions

View file

@ -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<F>, usize) {
(self.0.to_vec(), VALUE_SIZE)
}
}
impl Value {
pub fn to_bytes(self) -> Vec<u8> {
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<Ordering> {
Some(self.cmp(other))
}
}
impl From<i64> 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<Hash> for Value {
fn from(h: Hash) -> Self {
Value(h.0)
}
}
impl TryInto<i64> for Value {
type Error = Error;
fn try_into(self) -> std::result::Result<i64, Self::Error> {
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<Value> 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<F>, 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<Ordering> {
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<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
// 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<F> = 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)
}

View file

@ -0,0 +1,542 @@
use anyhow::{anyhow, Result};
use itertools::Itertools;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::plonk::config::Hasher;
use std::any::Any;
use std::fmt;
use crate::middleware::{
self, hash_str, AnchoredKey, Hash, MainPodInputs, NativeOperation, NativePredicate, NonePod,
Params, Pod, PodId, PodProver, StatementArg, ToFields, KEY_TYPE, SELF,
};
mod operation;
mod statement;
pub use operation::*;
pub use statement::*;
pub const VALUE_TYPE: &str = "MockMainPOD";
pub struct MockProver {}
impl PodProver for MockProver {
fn prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result<Box<dyn Pod>> {
Ok(Box::new(MockMainPod::new(params, inputs)?))
}
}
#[derive(Clone, Debug)]
pub struct MockMainPod {
params: Params,
id: PodId,
input_signed_pods: Vec<Box<dyn Pod>>,
input_main_pods: Vec<Box<dyn Pod>>,
// New statements introduced by this pod
input_statements: Vec<Statement>,
public_statements: Vec<Statement>,
operations: Vec<Operation>,
// All statements (inherited + new)
statements: Vec<Statement>,
}
impl fmt::Display for MockMainPod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "MockMainPod ({}):", self.id)?;
// TODO print input signed pods id and type
// TODO print input main pods id and type
let offset_input_main_pods = self.offset_input_main_pods();
let offset_input_statements = self.offset_input_statements();
let offset_public_statements = self.offset_public_statements();
for (i, st) in self.statements.iter().enumerate() {
if (i < self.offset_input_main_pods()) && (i % self.params.max_signed_pod_values == 0) {
writeln!(
f,
" from input SignedPod {}:",
i / self.params.max_signed_pod_values
)?;
}
if (i >= offset_input_main_pods)
&& (i < offset_input_statements)
&& (i % self.params.max_public_statements == 0)
{
writeln!(
f,
" from input MainPod {}:",
(i - offset_input_main_pods) / self.params.max_signed_pod_values
)?;
}
if i == offset_input_statements {
writeln!(f, " private statements:")?;
}
if i == offset_public_statements {
writeln!(f, " public statements:")?;
}
let op = (i >= offset_input_statements)
.then(|| &self.operations[i - offset_input_statements]);
fmt_statement_index(f, &st, op, i)?;
}
Ok(())
}
}
fn fmt_statement_index(
f: &mut fmt::Formatter,
st: &Statement,
op: Option<&Operation>,
index: usize,
) -> fmt::Result {
if !(!f.alternate() && st.is_none()) {
write!(f, " {:03}. ", index)?;
if f.alternate() {
write!(f, "{:#}", &st)?;
} else {
write!(f, "{}", &st)?;
}
if let Some(op) = op {
write!(f, " <- ")?;
if f.alternate() {
write!(f, "{:#}", op)?;
} else {
write!(f, "{}", op)?;
}
}
write!(f, "\n")?;
}
Ok(())
}
fn fill_pad<T: Clone>(v: &mut Vec<T>, pad_value: T, len: usize) {
if v.len() > len {
panic!("length exceeded");
}
while v.len() < len {
v.push(pad_value.clone());
}
}
/// Inputs are sorted as:
/// - SignedPods
/// - MainPods
/// - private Statements
/// - public Statements
impl MockMainPod {
fn offset_input_signed_pods(&self) -> usize {
0
}
fn offset_input_main_pods(&self) -> usize {
self.params.max_input_signed_pods * self.params.max_signed_pod_values
}
fn offset_input_statements(&self) -> usize {
self.offset_input_main_pods()
+ self.params.max_input_main_pods * self.params.max_public_statements
}
fn offset_public_statements(&self) -> usize {
self.offset_input_statements() + self.params.max_priv_statements()
}
fn pad_statement(params: &Params, s: &mut Statement) {
fill_pad(&mut s.1, StatementArg::None, params.max_statement_args)
}
fn pad_operation(params: &Params, op: &mut Operation) {
fill_pad(&mut op.1, OperationArg::None, params.max_operation_args)
}
/// Returns the statements from the given MainPodInputs, padding to the
/// respective max lengths defined at the given Params.
fn layout_statements(params: &Params, inputs: &MainPodInputs) -> Vec<Statement> {
let mut statements = Vec::new();
// Input signed pods region
let none_sig_pod: Box<dyn Pod> = Box::new(NonePod {});
assert!(inputs.signed_pods.len() <= params.max_input_signed_pods);
for i in 0..params.max_input_signed_pods {
let pod = inputs
.signed_pods
.get(i)
.map(|p| *p)
.unwrap_or(&none_sig_pod);
let sts = pod.pub_statements();
assert!(sts.len() <= params.max_signed_pod_values);
for j in 0..params.max_signed_pod_values {
let mut st = sts
.get(j)
.unwrap_or(&middleware::Statement::None)
.clone()
.into();
Self::pad_statement(params, &mut st);
statements.push(st);
}
}
// Input main pods region
let none_main_pod: Box<dyn Pod> = Box::new(NonePod {});
assert!(inputs.main_pods.len() <= params.max_input_main_pods);
for i in 0..params.max_input_main_pods {
let pod = inputs
.main_pods
.get(i)
.map(|p| *p)
.unwrap_or(&none_main_pod);
let sts = pod.pub_statements();
assert!(sts.len() <= params.max_public_statements);
for j in 0..params.max_public_statements {
let mut st = sts
.get(j)
.unwrap_or(&middleware::Statement::None)
.clone()
.into();
Self::pad_statement(params, &mut st);
statements.push(st);
}
}
// Input statements
assert!(inputs.statements.len() <= params.max_priv_statements());
for i in 0..params.max_priv_statements() {
let mut st = inputs
.statements
.get(i)
.unwrap_or(&middleware::Statement::None)
.clone()
.into();
Self::pad_statement(params, &mut st);
statements.push(st);
}
// Public statements
assert!(inputs.public_statements.len() < params.max_public_statements);
let mut type_st = middleware::Statement::ValueOf(
AnchoredKey(SELF, hash_str(KEY_TYPE)),
middleware::Value(hash_str(VALUE_TYPE).0),
)
.into();
Self::pad_statement(params, &mut type_st);
statements.push(type_st);
for i in 0..(params.max_public_statements - 1) {
let mut st = inputs
.public_statements
.get(i)
.unwrap_or(&middleware::Statement::None)
.clone()
.into();
Self::pad_statement(params, &mut st);
statements.push(st);
}
statements
}
fn find_op_arg(
statements: &[Statement],
op_arg: &middleware::Statement,
) -> Result<OperationArg> {
match op_arg {
middleware::Statement::None => Ok(OperationArg::None),
_ => statements
.iter()
.enumerate()
.find_map(|(i, s)| {
(&middleware::Statement::try_from(s.clone()).ok()? == op_arg).then_some(i)
})
.map(OperationArg::Index)
.ok_or(anyhow!("statement not found")),
}
}
fn process_private_statements_operations(
params: &Params,
statements: &[Statement],
input_operations: &[middleware::Operation],
) -> Result<Vec<Operation>> {
let mut operations = Vec::new();
for i in 0..params.max_priv_statements() {
let op = input_operations
.get(i)
.unwrap_or(&middleware::Operation::None)
.clone();
let mid_args = op.args();
let mut args = mid_args
.iter()
.map(|mid_arg| Self::find_op_arg(statements, mid_arg))
.collect::<Result<Vec<_>>>()?;
Self::pad_operation_args(params, &mut args);
operations.push(Operation(op.code(), args));
}
Ok(operations)
}
// NOTE: In this implementation public statements are always copies from
// previous statements, so we fill in the operations accordingly.
/// This method assumes that the given `statements` array has been padded to
/// `params.max_statements`.
fn process_public_statements_operations(
params: &Params,
statements: &[Statement],
mut operations: Vec<Operation>,
) -> Result<Vec<Operation>> {
let offset_public_statements = statements.len() - params.max_public_statements;
operations.push(Operation(NativeOperation::NewEntry, vec![]));
for i in 0..(params.max_public_statements - 1) {
let st = &statements[offset_public_statements + i + 1];
let mut op = if st.is_none() {
Operation(NativeOperation::None, vec![])
} else {
let mid_arg = st.clone();
Operation(
NativeOperation::CopyStatement,
// TODO
vec![Self::find_op_arg(statements, &mid_arg.try_into().unwrap())?],
)
};
fill_pad(&mut op.1, OperationArg::None, params.max_operation_args);
operations.push(op);
}
Ok(operations)
}
pub fn new(params: &Params, inputs: MainPodInputs) -> Result<Self> {
// TODO: Figure out a way to handle public statements. For example, in the public slots
// use copy operations taking the private statements that need to be public. We may change
// the MainPodInputs type to accommodate for that.
// TODO: Insert a new public statement of ValueOf with `key=KEY_TYPE,
// value=PodType::MockMainPod`
let statements = Self::layout_statements(params, &inputs);
let operations =
Self::process_private_statements_operations(params, &statements, inputs.operations)?;
let operations =
Self::process_public_statements_operations(params, &statements, operations)?;
let input_signed_pods = inputs
.signed_pods
.iter()
.map(|p| (*p).clone())
.collect_vec();
let input_main_pods = inputs.main_pods.iter().map(|p| (*p).clone()).collect_vec();
let input_statements = inputs
.statements
.iter()
.cloned()
.map(|s| {
let mut s = s.into();
Self::pad_statement(params, &mut s);
s
})
.collect_vec();
let public_statements =
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));
Ok(Self {
params: params.clone(),
id,
input_signed_pods,
input_main_pods,
input_statements,
public_statements,
statements,
operations,
})
}
fn statement_none(params: &Params) -> Statement {
let mut args = Vec::with_capacity(params.max_statement_args);
Self::pad_statement_args(&params, &mut args);
Statement(NativePredicate::None, args)
}
fn operation_none(params: &Params) -> Operation {
let mut op = Operation(NativeOperation::None, vec![]);
fill_pad(&mut op.1, OperationArg::None, params.max_operation_args);
op
}
fn pad_statement_args(params: &Params, args: &mut Vec<StatementArg>) {
fill_pad(args, StatementArg::None, params.max_statement_args)
}
fn pad_operation_args(params: &Params, args: &mut Vec<OperationArg>) {
fill_pad(args, OperationArg::None, params.max_operation_args)
}
}
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)
.collect::<Vec<_>>();
Hash(PoseidonHash::hash_no_pad(&field_elems).elements)
}
impl Pod for MockMainPod {
fn verify(&self) -> bool {
let input_statement_offset = self.offset_input_statements();
// 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));
// 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
.public_statements
.iter()
.find(|s| {
s.0 == NativePredicate::ValueOf
&& s.1.len() > 0
&& if let StatementArg::Key(AnchoredKey(pod_id, key_hash)) = s.1[0] {
pod_id == SELF && key_hash == hash_str(KEY_TYPE)
} else {
false
}
})
.is_some();
// check that all `input_statements` of type `ValueOf` with origin=SELF have unique keys
// (no duplicates)
// TODO: Instead of doing this, do a uniqueness check when verifying the output of a
// `NewValue` operation.
let value_ofs_unique = {
let key_id_pairs = input_statements
.into_iter()
.enumerate()
.map(|(i, s)| {
(
// Separate private from public statements.
if i < self.params.max_priv_statements() {
0
} else {
1
},
s,
)
})
.filter(|(_, s)| s.0 == NativePredicate::ValueOf)
.flat_map(|(i, s)| {
if let StatementArg::Key(ak) = &s.1[0] {
vec![(i, ak.1, ak.0)]
} else {
vec![]
}
})
.collect::<Vec<_>>();
!(0..key_id_pairs.len() - 1).any(|i| key_id_pairs[i + 1..].contains(&key_id_pairs[i]))
};
// verify that all `input_statements` are correctly generated
// by `self.operations` (where each operation can only access previous statements)
let statement_check = input_statements
.iter()
.enumerate()
.map(|(i, s)| {
self.operations[i]
.deref(&self.statements[..input_statement_offset + i])
.unwrap()
.check(&s.clone().try_into().unwrap())
})
.collect::<Result<Vec<_>>>()
.unwrap();
ids_match && has_type_statement && value_ofs_unique & statement_check.into_iter().all(|b| b)
}
fn id(&self) -> PodId {
self.id
}
fn pub_statements(&self) -> Vec<middleware::Statement> {
// return the public statements, where when origin=SELF is replaced by origin=self.id()
self.statements
.iter()
.skip(self.offset_public_statements())
.cloned()
.map(|statement| {
Statement(
statement.0.clone(),
statement
.1
.iter()
.map(|sa| match &sa {
StatementArg::Key(AnchoredKey(pod_id, h)) if *pod_id == SELF => {
StatementArg::Key(AnchoredKey(self.id(), *h))
}
_ => sa.clone(),
})
.collect(),
)
.try_into()
.unwrap()
})
.collect()
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
#[cfg(test)]
pub mod tests {
use super::*;
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,
};
use crate::middleware;
#[test]
fn test_mock_main_zu_kyc() -> Result<()> {
let params = middleware::Params::default();
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_pod_builders(&params);
let mut signer = MockSigner {
pk: "ZooGov".into(),
};
let gov_id_pod = gov_id_builder.sign(&mut signer)?;
let mut signer = MockSigner {
pk: "ZooDeel".into(),
};
let pay_stub_pod = pay_stub_builder.sign(&mut signer)?;
let kyc_builder = zu_kyc_pod_builder(&params, &gov_id_pod, &pay_stub_pod)?;
let mut prover = MockProver {};
let kyc_pod = kyc_builder.prove(&mut prover)?;
let pod = kyc_pod.pod.into_any().downcast::<MockMainPod>().unwrap();
println!("{:#}", pod);
assert_eq!(pod.verify(), true); // TODO
// println!("id: {}", pod.id());
// println!("pub_statements: {:?}", pod.pub_statements());
Ok(())
}
#[test]
fn test_mock_main_great_boy() -> Result<()> {
let great_boy_builder = great_boy_pod_full_flow()?;
let mut prover = MockProver {};
let great_boy_pod = great_boy_builder.prove(&mut prover)?;
let pod = great_boy_pod
.pod
.into_any()
.downcast::<MockMainPod>()
.unwrap();
println!("{}", pod);
assert_eq!(pod.verify(), true);
Ok(())
}
#[test]
fn test_mock_main_tickets() -> Result<()> {
let tickets_builder = tickets_pod_full_flow()?;
let mut prover = MockProver {};
let proof_pod = tickets_builder.prove(&mut prover)?;
let pod = proof_pod.pod.into_any().downcast::<MockMainPod>().unwrap();
println!("{}", pod);
assert_eq!(pod.verify(), true);
Ok(())
}
}

View file

@ -0,0 +1,52 @@
use anyhow::Result;
use std::fmt;
use super::Statement;
use crate::middleware::{self, NativeOperation};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OperationArg {
None,
Index(usize),
}
impl OperationArg {
pub fn is_none(&self) -> bool {
matches!(self, OperationArg::None)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Operation(pub NativeOperation, pub Vec<OperationArg>);
impl Operation {
pub fn deref(&self, statements: &[Statement]) -> Result<crate::middleware::Operation> {
let deref_args = self
.1
.iter()
.flat_map(|arg| match arg {
OperationArg::None => None,
OperationArg::Index(i) => Some(statements[*i].clone().try_into()),
})
.collect::<Result<Vec<crate::middleware::Statement>>>()?;
middleware::Operation::op(self.0, &deref_args)
}
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?} ", self.0)?;
for (i, arg) in self.1.iter().enumerate() {
if !(!f.alternate() && arg.is_none()) {
if i != 0 {
write!(f, " ")?;
}
match arg {
OperationArg::None => write!(f, "none")?,
OperationArg::Index(i) => write!(f, "{:02}", i)?,
}
}
}
Ok(())
}
}

View file

@ -0,0 +1,109 @@
use anyhow::{anyhow, Result};
use std::fmt;
use crate::middleware::{self, NativePredicate, Params, StatementArg, ToFields};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Statement(pub NativePredicate, pub Vec<StatementArg>);
impl Statement {
pub fn is_none(&self) -> bool {
self.0 == NativePredicate::None
}
/// Argument method. Trailing Nones are filtered out.
pub fn args(&self) -> Vec<StatementArg> {
let maybe_last_arg_index = (0..self.1.len()).rev().find(|i| !self.1[*i].is_none());
match maybe_last_arg_index {
None => vec![],
Some(i) => self.1[0..i + 1].to_vec(),
}
}
}
impl ToFields for Statement {
fn to_fields(&self, _params: &Params) -> (Vec<middleware::F>, 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))
.fold((Vec::new(), 0), |mut acc, (f, l)| {
acc.0.extend(f);
acc.1 += l;
acc
});
(
[native_statement_f, vec_statementarg_f].concat(),
native_statement_f_len + vec_statementarg_f_len,
)
}
}
impl TryFrom<Statement> for middleware::Statement {
type Error = anyhow::Error;
fn try_from(s: Statement) -> Result<Self> {
type S = middleware::Statement;
type NP = NativePredicate;
type SA = StatementArg;
let proper_args = s.args();
let args = (
proper_args.get(0).cloned(),
proper_args.get(1).cloned(),
proper_args.get(2).cloned(),
);
Ok(match (s.0, args, proper_args.len()) {
(NP::None, _, 0) => S::None,
(NP::ValueOf, (Some(SA::Key(ak)), Some(SA::Literal(v)), None), 2) => S::ValueOf(ak, v),
(NP::Equal, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => S::Equal(ak1, ak2),
(NP::NotEqual, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => {
S::NotEqual(ak1, ak2)
}
(NP::Gt, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => S::Gt(ak1, ak2),
(NP::Lt, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => S::Lt(ak1, ak2),
(NP::Contains, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => {
S::Contains(ak1, ak2)
}
(NP::NotContains, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), None), 2) => {
S::NotContains(ak1, ak2)
}
(NP::SumOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => {
S::SumOf(ak1, ak2, ak3)
}
(NP::ProductOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => {
S::ProductOf(ak1, ak2, ak3)
}
(NP::MaxOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => {
S::MaxOf(ak1, ak2, ak3)
}
_ => Err(anyhow!("Ill-formed statement expression {:?}", s))?,
})
}
}
impl From<middleware::Statement> for Statement {
fn from(s: middleware::Statement) -> Self {
match s.code() {
middleware::Predicate::Native(c) => {
Statement(c, s.args().into_iter().map(|arg| arg).collect())
}
// TODO: Custom statements
_ => todo!(),
}
}
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?} ", self.0)?;
for (i, arg) in self.1.iter().enumerate() {
if !(!f.alternate() && arg.is_none()) {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{}", arg)?;
}
}
Ok(())
}
}

View file

@ -0,0 +1,159 @@
use anyhow::Result;
use std::any::Any;
use std::collections::HashMap;
use crate::constants::MAX_DEPTH;
use crate::middleware::{
containers::Dictionary, hash_str, AnchoredKey, Hash, Params, Pod, PodId, PodSigner, PodType,
Statement, Value, KEY_SIGNER, KEY_TYPE,
};
use crate::primitives::merkletree::MerkleTree;
pub struct MockSigner {
pub pk: String,
}
impl PodSigner for MockSigner {
fn sign(&mut self, _params: &Params, kvs: &HashMap<Hash, Value>) -> Result<Box<dyn Pod>> {
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 dict = Dictionary::new(&kvs)?;
let id = PodId(dict.commitment());
let signature = format!("{}_signed_by_{}", id, pk_hash);
Ok(Box::new(MockSignedPod {
dict,
id,
signature,
}))
}
}
#[derive(Clone, Debug)]
pub struct MockSignedPod {
id: PodId,
signature: String,
dict: Dictionary,
}
impl Pod for MockSignedPod {
fn verify(&self) -> bool {
// Verify type
let value_at_type = match self.dict.get(&hash_str(&KEY_TYPE).into()) {
Ok(v) => v,
Err(_) => return false,
};
if Value::from(PodType::MockSigned) != value_at_type {
return false;
}
// Verify id
let mt = match MerkleTree::new(
MAX_DEPTH,
&self
.dict
.iter()
.map(|(&k, &v)| (k, v))
.collect::<HashMap<Value, Value>>(),
) {
Ok(mt) => mt,
Err(_) => return false,
};
let id = PodId(mt.root());
if id != self.id {
return false;
}
// Verify signature
let pk_hash = match self.dict.get(&hash_str(&KEY_SIGNER).into()) {
Ok(v) => v,
Err(_) => 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 pub_statements(&self) -> Vec<Statement> {
let id = self.id();
self.dict
.iter()
.map(|(k, v)| Statement::ValueOf(AnchoredKey(id, Hash(k.0)), *v))
.collect()
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
#[cfg(test)]
pub mod tests {
use plonky2::field::types::Field;
use std::iter;
use super::*;
use crate::constants::MAX_DEPTH;
use crate::frontend;
use crate::middleware::{self, F, NULL};
#[test]
fn test_mock_signed_0() -> Result<()> {
let params = middleware::Params::default();
let mut pod = frontend::SignedPodBuilder::new(&params);
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).unwrap();
let pod = pod.pod.into_any().downcast::<MockSignedPod>().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();
let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(NULL).0 .0));
let bad_kvs_mt = &bad_pod
.kvs()
.into_iter()
.map(|(AnchoredKey(_, k), v)| (Value(k.0), v))
.chain(iter::once(bad_kv))
.collect::<HashMap<Value, Value>>();
let bad_mt = MerkleTree::new(MAX_DEPTH, &bad_kvs_mt)?;
bad_pod.dict.mt = bad_mt;
assert_eq!(bad_pod.verify(), false);
let mut bad_pod = pod.clone();
let bad_kv = (hash_str(KEY_TYPE).into(), Value::from(0));
let bad_kvs_mt = &bad_pod
.kvs()
.into_iter()
.map(|(AnchoredKey(_, k), v)| (Value(k.0), v))
.chain(iter::once(bad_kv))
.collect::<HashMap<Value, Value>>();
let bad_mt = MerkleTree::new(MAX_DEPTH, &bad_kvs_mt)?;
bad_pod.dict.mt = bad_mt;
assert_eq!(bad_pod.verify(), false);
Ok(())
}
}

View file

@ -0,0 +1,3 @@
pub mod basetypes;
pub mod mock_main;
pub mod mock_signed;