From aafebfbcd58aecb5c44fc3bd325716930d8ac3c5 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Mon, 3 Feb 2025 18:22:18 +0100 Subject: [PATCH] add SignerPod.id computation (integrate kvs with MerkleTree) (#17) - Add SignerPod.id computation (integrate kvs with MerkleTree). - Updates frontend::SignerPod to be a wrapper on top of backend::SignerPod with extra metadata (a keymap between hashes and their strings). - Get's rid of SignerPod.compile() since now the frontend::SignerPod uses the method `::new()` which internally calls backend::SignerPod::new which constructs the merkletree to use it's root as PodID. --- src/backend.rs | 60 ++++++++++++++++++++------------- src/frontend.rs | 85 ++++++++++++++++++++++++++++------------------- src/merkletree.rs | 27 +++++++++++++-- 3 files changed, 111 insertions(+), 61 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 322ea77..32b9db2 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use itertools::Itertools; use plonky2::field::types::{Field, PrimeField64}; use std::collections::HashMap; use std::fmt; @@ -5,7 +7,7 @@ use std::io::{self, Write}; use std::iter; use strum_macros::FromRepr; -use crate::{Hash, Params, PodId, F, NULL}; +use crate::{merkletree::MerkleTree, Hash, Params, PodId, F, NULL}; #[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq)] pub enum NativeStatement { @@ -69,10 +71,20 @@ impl Statement { pub struct SignedPod { pub params: Params, pub id: PodId, - pub kvs: HashMap, + pub kvs: MerkleTree, } impl SignedPod { + pub fn new(params: &Params, kvs: HashMap) -> Result { + let mt = MerkleTree::new(kvs); + let root = mt.root()?; + Ok(Self { + params: *params, + id: PodId(root), + kvs: mt, + }) + } + pub fn is_null(&self) -> bool { self.id.0 == NULL } @@ -101,15 +113,15 @@ impl MainPod { mut input_signed_pods: Vec, input_main_pods: Vec, mut statements: Vec, - ) -> Self { + ) -> Result { Self::pad_statements(¶ms, &mut statements, params.max_statements); - Self::pad_input_signed_pods(¶ms, &mut input_signed_pods); - Self { + Self::pad_input_signed_pods(¶ms, &mut input_signed_pods)?; + Ok(Self { params, id: PodId::default(), // TODO input_signed_pods, statements, - } + }) } fn statement_none(params: &Params) -> Statement { @@ -129,13 +141,9 @@ impl MainPod { fill_pad(args, StatementArg::None, params.max_statement_args) } - fn pad_input_signed_pods(params: &Params, pods: &mut Vec) { - let pod_none = SignedPod { - params: params.clone(), - id: PodId::default(), - kvs: HashMap::new(), - }; - fill_pad(pods, pod_none, params.max_input_signed_pods) + fn pad_input_signed_pods(params: &Params, pods: &mut Vec) -> Result<()> { + let pod_none = SignedPod::new(params, HashMap::new())?; + Ok(fill_pad(pods, pod_none, params.max_input_signed_pods)) } pub fn input_signed_pods_statements(&self) -> Vec> { @@ -200,9 +208,11 @@ impl Printer { pub fn fmt_signed_pod(&self, w: &mut dyn Write, pod: &SignedPod) -> io::Result<()> { writeln!(w, "SignedPod ({}):", pod.id)?; - // for (k, v) in pod.kvs.iter().sorted_by_key(|kv| kv.0) { - // TODO: print in a stable order - for (k, v) in pod.kvs.iter() { + // 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.kvs.iter().sorted_by_key(|kv| kv.0) { writeln!(w, " - {}: {}", k, v)?; } Ok(()) @@ -279,18 +289,20 @@ mod tests { use crate::frontend; #[test] - fn test_back_0() { + fn test_back_0() -> Result<()> { let params = Params::default(); - let (front_gov_id, front_pay_stub, front_kyc) = frontend::tests::data_zu_kyc(params); - let gov_id = front_gov_id.compile(); - let pay_stub = front_pay_stub.compile(); - let kyc = front_kyc.compile(); + let (front_gov_id, front_pay_stub, front_kyc) = frontend::tests::data_zu_kyc(params)?; + let gov_id = front_gov_id.pod; // get backend's pod + let pay_stub = front_pay_stub.pod; // get backend's pod + let kyc = front_kyc.compile()?; // println!("{:#?}", kyc); let printer = Printer { skip_none: true }; let mut w = io::stdout(); - printer.fmt_signed_pod(&mut w, &gov_id).unwrap(); - printer.fmt_signed_pod(&mut w, &pay_stub).unwrap(); - printer.fmt_main_pod(&mut w, &kyc).unwrap(); + printer.fmt_signed_pod(&mut w, &gov_id)?; + printer.fmt_signed_pod(&mut w, &pay_stub)?; + printer.fmt_main_pod(&mut w, &kyc)?; + + Ok(()) } } diff --git a/src/frontend.rs b/src/frontend.rs index a1a391e..7009a4e 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use itertools::Itertools; use plonky2::field::types::Field; use std::collections::HashMap; @@ -71,27 +72,42 @@ impl fmt::Display for Value { } } +/// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the +/// string<-->hash relation of the keys. #[derive(Clone, Debug)] pub struct SignedPod { - pub params: Params, - pub id: PodId, - pub kvs: HashMap, + 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, } impl SignedPod { - pub fn origin(&self) -> Origin { - Origin(PodType::Signed, self.id) + 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 compile(&self) -> backend::SignedPod { - backend::SignedPod { - params: self.params, - id: self.id, - kvs: self - .kvs - .iter() - .map(|(k, v)| (hash_str(k), backend::Value::from(v))) - .collect(), - } + pub fn id(&self) -> PodId { + self.pod.id + } + pub fn origin(&self) -> Origin { + Origin(PodType::Signed, self.pod.id) } } @@ -219,7 +235,7 @@ impl MainPod { Origin(PodType::Main, self.id) } - pub fn compile(&self) -> backend::MainPod { + pub fn compile(&self) -> Result { MainPodCompiler::new(self).compile() } @@ -278,7 +294,7 @@ impl<'a> MainPodCompiler<'a> { )); } - pub fn compile(mut self) -> backend::MainPod { + pub fn compile(mut self) -> Result { let MainPod { statements, params, @@ -291,7 +307,8 @@ impl<'a> MainPodCompiler<'a> { panic!("too many statements"); } } - let input_signed_pods = input_signed_pods.iter().map(|p| p.compile()).collect(); + 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) } } @@ -335,8 +352,12 @@ impl Printer { } pub fn fmt_signed_pod(&self, w: &mut dyn Write, pod: &SignedPod) -> io::Result<()> { - writeln!(w, "SignedPod (id:{}):", pod.id)?; - for (k, v) in pod.kvs.iter().sorted_by_key(|kv| kv.0) { + 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) { writeln!(w, " - {}: {}", k, v)?; } Ok(()) @@ -346,7 +367,7 @@ impl Printer { writeln!(w, "MainPod (id:{}):", pod.id)?; writeln!(w, " input_signed_pods:")?; for in_pod in &pod.input_signed_pods { - writeln!(w, " - {}", in_pod.id)?; + writeln!(w, " - {}", in_pod.id())?; } writeln!(w, " input_main_pods:")?; for in_pod in &pod.input_main_pods { @@ -394,25 +415,17 @@ pub mod tests { (max_of, $($arg:expr),+) => { Statement(NativeStatement::max_of, args!($($arg),*)) }; } - pub fn data_zu_kyc(params: Params) -> (SignedPod, SignedPod, MainPod) { + 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 { - params: params.clone(), - id: pod_id("1100000000000000000000000000000000000000000000000000000000000000"), - kvs, - }; + let gov_id = SignedPod::new(¶ms, kvs)?; let mut kvs = HashMap::new(); kvs.insert("socialSecurityNumber".into(), "G2121210".into()); kvs.insert("startDate".into(), 1706367566.into()); - let pay_stub = SignedPod { - params: params.clone(), - id: pod_id("2200000000000000000000000000000000000000000000000000000000000000"), - kvs, - }; + let pay_stub = SignedPod::new(¶ms, kvs)?; let sanction_list = Value::MerkleTree(MerkleTree { root: 1 }); let now_minus_18y: i64 = 1169909388; @@ -440,17 +453,19 @@ pub mod tests { statements, }; - (gov_id, pay_stub, kyc) + Ok((gov_id, pay_stub, kyc)) } #[test] - fn test_front_0() { - let (gov_id, pay_stub, kyc) = data_zu_kyc(Params::default()); + fn test_front_0() -> Result<()> { + let (gov_id, pay_stub, kyc) = data_zu_kyc(Params::default())?; let printer = Printer {}; let mut w = io::stdout(); printer.fmt_signed_pod(&mut w, &gov_id).unwrap(); printer.fmt_signed_pod(&mut w, &pay_stub).unwrap(); printer.fmt_main_pod(&mut w, &kyc).unwrap(); + + Ok(()) } } diff --git a/src/merkletree.rs b/src/merkletree.rs index 590dafd..45024a3 100644 --- a/src/merkletree.rs +++ b/src/merkletree.rs @@ -14,6 +14,7 @@ use plonky2::hash::{ use plonky2::plonk::config::GenericConfig; use plonky2::plonk::config::Hasher; use std::collections::HashMap; +use std::iter::IntoIterator; use crate::backend::Value; use crate::{Hash, C, D, F}; @@ -28,6 +29,11 @@ pub struct MerkleTree { tree: PlonkyMerkleTree>::Hasher>, // keyindex: key -> index mapping. This is just for the current plonky-tree wrapper keyindex: HashMap, + // kvs are a field in the MerkleTree in order to be able to iterate over the keyvalues. This is + // specific of the current implementation (Plonky2's tree wrapper), in the next iteration this + // will not be needed since the tree implementation itself will offer the hashmap + // functionallity. + kvs: HashMap, } pub struct MerkleProof { @@ -44,7 +50,7 @@ impl MerkleTree { // 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 (i, (k, v)) in kvs.into_iter().sorted_by_key(|kv| kv.0).enumerate() { + for (i, (k, v)) in kvs.clone().into_iter().sorted_by_key(|kv| kv.0).enumerate() { let input: Vec = [k.0, v.0].concat(); let leaf = PoseidonHash::hash_no_pad(&input).elements; leaves.push(leaf.into()); @@ -58,7 +64,11 @@ impl MerkleTree { } let tree = PlonkyMerkleTree::>::Hasher>::new(leaves, CAP_HEIGHT); - Self { tree, keyindex } + Self { + tree, + keyindex, + kvs, + } } pub fn root(&self) -> Result { @@ -114,6 +124,19 @@ impl MerkleTree { println!("WARNING: MerkleTree::verify_nonexistence is currently a mock"); Ok(()) } + + pub fn iter(&self) -> std::collections::hash_map::Iter { + self.kvs.iter() + } +} + +impl<'a> IntoIterator for &'a MerkleTree { + type Item = (&'a Hash, &'a Value); + type IntoIter = std::collections::hash_map::Iter<'a, Hash, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.kvs.iter() + } } #[cfg(test)]