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.
This commit is contained in:
arnaucube 2025-02-03 18:22:18 +01:00 committed by GitHub
parent 5b455acbd6
commit aafebfbcd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 111 additions and 61 deletions

View file

@ -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<Hash, Value>,
pub kvs: MerkleTree,
}
impl SignedPod {
pub fn new(params: &Params, kvs: HashMap<Hash, Value>) -> Result<Self> {
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<SignedPod>,
input_main_pods: Vec<MainPod>,
mut statements: Vec<Statement>,
) -> Self {
) -> Result<Self> {
Self::pad_statements(&params, &mut statements, params.max_statements);
Self::pad_input_signed_pods(&params, &mut input_signed_pods);
Self {
Self::pad_input_signed_pods(&params, &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<SignedPod>) {
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<SignedPod>) -> 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<Vec<Statement>> {
@ -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(())
}
}

View file

@ -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<String, Value>,
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 <Hash, String>, so that when iterating we can get each
// hash respective string
string_key_map: HashMap<String, crate::Hash>,
}
impl SignedPod {
pub fn new(params: &Params, kvs: HashMap<String, Value>) -> Result<Self> {
let (hashed_kvs, string_key_map): (
HashMap<crate::Hash, backend::Value>,
HashMap<String, crate::Hash>,
) = 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
}
pub fn origin(&self) -> Origin {
Origin(PodType::Signed, self.id)
}
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(),
}
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<backend::MainPod> {
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<backend::MainPod> {
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<backend::SignedPod> =
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(&params, 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(&params, 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(())
}
}

View file

@ -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<F, <C as GenericConfig<D>>::Hasher>,
// keyindex: key -> index mapping. This is just for the current plonky-tree wrapper
keyindex: HashMap<Hash, usize>,
// 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<Hash, Value>,
}
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<F> = [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::<F, <C as GenericConfig<D>>::Hasher>::new(leaves, CAP_HEIGHT);
Self { tree, keyindex }
Self {
tree,
keyindex,
kvs,
}
}
pub fn root(&self) -> Result<Hash> {
@ -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<Hash, Value> {
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)]