diff --git a/Cargo.toml b/Cargo.toml index d3a3d2e..7db3e83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1.0.56" dyn-clone = "1.0.18" log = "0.4" env_logger = "0.11" +lazy_static = "1.5.0" # enabled by features: plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true } serde = "1.0.219" diff --git a/src/backends/plonky2/mock/mainpod/mod.rs b/src/backends/plonky2/mock/mainpod/mod.rs index c3c5fb9..6f2e507 100644 --- a/src/backends/plonky2/mock/mainpod/mod.rs +++ b/src/backends/plonky2/mock/mainpod/mod.rs @@ -382,7 +382,7 @@ pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware } impl Pod for MockMainPod { - fn verify(&self) -> bool { + fn verify(&self) -> Result<()> { // 1. TODO: Verify input pods let input_statement_offset = self.offset_input_statements(); @@ -451,18 +451,20 @@ impl Pod for MockMainPod { .collect::>>() .unwrap(); if !ids_match { - error!("Verification failed: POD ID is incorrect."); + return Err(anyhow!("Verification failed: POD ID is incorrect.")); } if !has_type_statement { - error!("Verification failed: POD does not have type statement."); + return Err(anyhow!( + "Verification failed: POD does not have type statement." + )); } if !value_ofs_unique { - error!("Verification failed: Repeated ValueOf"); + return Err(anyhow!("Verification failed: Repeated ValueOf")); } if !statement_check.iter().all(|b| *b) { - error!("Verification failed: Statement did not check.") + return Err(anyhow!("Verification failed: Statement did not check.")); } - ids_match && has_type_statement && value_ofs_unique & statement_check.into_iter().all(|b| b) + Ok(()) } fn id(&self) -> PodId { self.id @@ -539,9 +541,9 @@ pub mod tests { println!("{:#}", pod); - assert!(pod.verify()); // TODO - // println!("id: {}", pod.id()); - // println!("pub_statements: {:?}", pod.pub_statements()); + pod.verify()?; // TODO + // println!("id: {}", pod.id()); + // println!("pub_statements: {:?}", pod.pub_statements()); Ok(()) } @@ -560,7 +562,7 @@ pub mod tests { println!("{}", pod); - assert!(pod.verify()); + pod.verify()?; Ok(()) } @@ -574,7 +576,7 @@ pub mod tests { let pod = proof_pod.pod.into_any().downcast::().unwrap(); println!("{}", pod); - assert!(pod.verify()); + pod.verify()?; Ok(()) } diff --git a/src/backends/plonky2/mock/signedpod.rs b/src/backends/plonky2/mock/signedpod.rs index d69523e..f3c3a6c 100644 --- a/src/backends/plonky2/mock/signedpod.rs +++ b/src/backends/plonky2/mock/signedpod.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::any::Any; use std::collections::HashMap; @@ -55,44 +55,47 @@ impl MockSignedPod { } impl Pod for MockSignedPod { - fn verify(&self) -> bool { + fn verify(&self) -> Result<()> { // 1. Verify type - let value_at_type = match self.dict.get(&hash_str(KEY_TYPE).into()) { - Ok(v) => v, - Err(_) => return false, - }; + let value_at_type = self.dict.get(&hash_str(KEY_TYPE).into())?; if Value::from(PodType::MockSigned) != value_at_type { - return false; + return Err(anyhow!( + "type does not match, expected MockSigned ({}), found {}", + PodType::MockSigned, + value_at_type + )); } // 2. Verify id - let mt = match MerkleTree::new( + let mt = MerkleTree::new( MAX_DEPTH, &self .dict .iter() .map(|(&k, &v)| (k, v)) .collect::>(), - ) { - Ok(mt) => mt, - Err(_) => return false, - }; + )?; let id = PodId(mt.root()); if id != self.id { - return false; + return Err(anyhow!( + "id does not match, expected {}, computed {}", + self.id, + id + )); } // 3. Verify signature - let pk_hash = match self.dict.get(&hash_str(KEY_SIGNER).into()) { - Ok(v) => v, - Err(_) => return false, - }; + let pk_hash = self.dict.get(&hash_str(KEY_SIGNER).into())?; let signature = format!("{}_signed_by_{}", id, pk_hash); if signature != self.signature { - return false; + return Err(anyhow!( + "signature does not match, expected {}, computed {}", + self.id, + id + )); } - true + Ok(()) } fn id(&self) -> PodId { @@ -138,17 +141,17 @@ pub mod tests { let pod = pod.sign(&mut signer).unwrap(); let pod = pod.pod.into_any().downcast::().unwrap(); - assert!(pod.verify()); + pod.verify()?; println!("id: {}", pod.id()); println!("kvs: {:?}", pod.kvs()); let mut bad_pod = pod.clone(); bad_pod.signature = "".into(); - assert!(!bad_pod.verify()); + assert!(bad_pod.verify().is_err()); let mut bad_pod = pod.clone(); bad_pod.id.0 .0[0] = F::ZERO; - assert!(!bad_pod.verify()); + assert!(bad_pod.verify().is_err()); let mut bad_pod = pod.clone(); let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(EMPTY_HASH).0 .0)); @@ -160,7 +163,7 @@ pub mod tests { .collect::>(); let bad_mt = MerkleTree::new(MAX_DEPTH, bad_kvs_mt)?; bad_pod.dict.mt = bad_mt; - assert!(!bad_pod.verify()); + assert!(bad_pod.verify().is_err()); let mut bad_pod = pod.clone(); let bad_kv = (hash_str(KEY_TYPE).into(), Value::from(0)); @@ -172,7 +175,7 @@ pub mod tests { .collect::>(); let bad_mt = MerkleTree::new(MAX_DEPTH, bad_kvs_mt)?; bad_pod.dict.mt = bad_mt; - assert!(!bad_pod.verify()); + assert!(bad_pod.verify().is_err()); Ok(()) } diff --git a/src/backends/plonky2/mod.rs b/src/backends/plonky2/mod.rs index 3b92b5d..11a6e16 100644 --- a/src/backends/plonky2/mod.rs +++ b/src/backends/plonky2/mod.rs @@ -2,3 +2,4 @@ pub mod basetypes; pub mod circuits; pub mod mock; pub mod primitives; +pub mod signedpod; diff --git a/src/backends/plonky2/primitives/signature.rs b/src/backends/plonky2/primitives/signature.rs index 4357f06..3c4487f 100644 --- a/src/backends/plonky2/primitives/signature.rs +++ b/src/backends/plonky2/primitives/signature.rs @@ -21,22 +21,29 @@ use plonky2::{ use crate::backends::plonky2::basetypes::{Proof, Value, C, D, F, VALUE_SIZE}; +use lazy_static::lazy_static; + +lazy_static! { + static ref PP: ProverParams = Signature::prover_params().unwrap(); + static ref VP: VerifierParams = Signature::verifier_params().unwrap(); +} + pub struct ProverParams { prover: ProverCircuitData, circuit: SignatureCircuit, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VerifierParams(VerifierCircuitData); #[derive(Clone, Debug)] pub struct SecretKey(Value); #[derive(Clone, Debug)] -pub struct PublicKey(Value); +pub struct PublicKey(pub(crate) Value); #[derive(Clone, Debug)] -pub struct Signature(Proof); +pub struct Signature(pub(crate) Proof); /// Implements the key generation and the computation of proof-based signatures. impl SecretKey { @@ -49,14 +56,14 @@ impl SecretKey { PublicKey(Value(PoseidonHash::hash_no_pad(&self.0 .0).elements)) } - pub fn sign(&self, pp: &ProverParams, msg: Value) -> Result { + pub fn sign(&self, msg: Value) -> Result { let pk = self.public_key(); let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements); let mut pw = PartialWitness::::new(); - pp.circuit.set_targets(&mut pw, self.clone(), pk, msg, s)?; + PP.circuit.set_targets(&mut pw, self.clone(), pk, msg, s)?; - let proof = pp.prover.prove(pw)?; + let proof = PP.prover.prove(pw)?; Ok(Signature(proof.proof)) } @@ -65,15 +72,22 @@ impl SecretKey { /// Implements the parameters generation and the verification of proof-based /// signatures. impl Signature { - pub fn params() -> Result<(ProverParams, VerifierParams)> { + pub fn prover_params() -> Result { let (builder, circuit) = Self::builder()?; let prover = builder.build_prover::(); - + Ok(ProverParams { prover, circuit }) + } + pub fn verifier_params() -> Result { let (builder, _) = Self::builder()?; let circuit_data = builder.build::(); let vp = circuit_data.verifier_data(); - Ok((ProverParams { prover, circuit }, VerifierParams(vp))) + Ok(VerifierParams(vp)) + } + pub fn params() -> Result<(ProverParams, VerifierParams)> { + let pp = Self::prover_params()?; + let vp = Self::verifier_params()?; + Ok((pp, vp)) } fn builder() -> Result<(CircuitBuilder, SignatureCircuit)> { @@ -86,13 +100,13 @@ impl Signature { Ok((builder, circuit)) } - pub fn verify(&self, vp: &VerifierParams, pk: &PublicKey, msg: Value) -> Result<()> { + pub fn verify(&self, pk: &PublicKey, msg: Value) -> Result<()> { // prepare public inputs as [pk, msg, s] let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements); let public_inputs: Vec = [pk.0 .0, msg.0, s.0].concat(); // verify plonky2 proof - vp.0.verify(ProofWithPublicInputs { + VP.0.verify(ProofWithPublicInputs { proof: self.0.clone(), public_inputs, }) @@ -171,23 +185,21 @@ pub mod tests { // Note: this test must be run with the `--release` flag. #[test] fn test_signature() -> Result<()> { - let (pp, vp) = Signature::params()?; - let sk = SecretKey::new(); let pk = sk.public_key(); let msg = Value::from(42); - let sig = sk.sign(&pp, msg)?; - sig.verify(&vp, &pk, msg)?; + let sig = sk.sign(msg)?; + sig.verify(&pk, msg)?; // expect the signature verification to fail when using a different msg - let v = sig.verify(&vp, &pk, Value::from(24)); + let v = sig.verify(&pk, Value::from(24)); assert!(v.is_err(), "should fail to verify"); // perform a 2nd signature over another msg and verify it let msg_2 = Value::from(Hash::from("message")); - let sig2 = sk.sign(&pp, msg_2)?; - sig2.verify(&vp, &pk, msg_2)?; + let sig2 = sk.sign(msg_2)?; + sig2.verify(&pk, msg_2)?; Ok(()) } diff --git a/src/backends/plonky2/signedpod.rs b/src/backends/plonky2/signedpod.rs new file mode 100644 index 0000000..fcf105e --- /dev/null +++ b/src/backends/plonky2/signedpod.rs @@ -0,0 +1,165 @@ +use anyhow::{anyhow, Result}; +use std::any::Any; +use std::collections::HashMap; + +use super::primitives::merkletree::MerkleTree; +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 super::primitives::signature::{PublicKey, SecretKey, Signature}; + +pub struct Signer(SecretKey); + +impl PodSigner for Signer { + fn sign(&mut self, _params: &Params, kvs: &HashMap) -> Result> { + let mut kvs = kvs.clone(); + let pubkey = self.0.public_key(); + kvs.insert(hash_str(KEY_SIGNER), pubkey.0); + kvs.insert(hash_str(KEY_TYPE), Value::from(PodType::Signed)); + + let dict = Dictionary::new(&kvs)?; + let id = Value::from(dict.commitment()); // PodId as Value + + let signature: Signature = self.0.sign(id)?; + Ok(Box::new(SignedPod { + id: PodId(Hash::from(id)), + signature, + dict, + })) + } +} + +#[derive(Clone, Debug)] +pub struct SignedPod { + id: PodId, + signature: Signature, + dict: Dictionary, +} + +impl Pod for SignedPod { + fn verify(&self) -> Result<()> { + // 1. Verify type + let value_at_type = self.dict.get(&hash_str(KEY_TYPE).into())?; + if Value::from(PodType::Signed) != value_at_type { + return Err(anyhow!( + "type does not match, expected Signed ({}), found {}", + PodType::Signed, + value_at_type + )); + } + + // 2. Verify id + let mt = MerkleTree::new( + MAX_DEPTH, + &self + .dict + .iter() + .map(|(&k, &v)| (k, v)) + .collect::>(), + )?; + let id = PodId(mt.root()); + if id != self.id { + return Err(anyhow!( + "id does not match, expected {}, computed {}", + self.id, + id + )); + } + + // 3. Verify signature + let pk_value = self.dict.get(&hash_str(KEY_SIGNER).into())?; + let pk = PublicKey(pk_value); + self.signature.verify(&pk, Value::from(id.0))?; + + Ok(()) + } + + fn id(&self) -> PodId { + self.id + } + + fn pub_statements(&self) -> Vec { + let id = self.id(); + self.dict + .iter() + .map(|(k, v)| Statement::ValueOf(AnchoredKey(id, Hash(k.0)), *v)) + .collect() + } + + fn into_any(self: Box) -> Box { + self + } + + fn serialized_proof(&self) -> String { + let mut buffer = Vec::new(); + use plonky2::util::serialization::Write; + buffer.write_proof(&self.signature.0).unwrap(); + hex::encode(buffer) + } +} + +#[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, EMPTY_HASH, F}; + + #[test] + fn test_signed_0() -> Result<()> { + 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 sk = SecretKey::new(); + let mut signer = Signer(sk); + let pod = pod.sign(&mut signer).unwrap(); + let pod = pod.pod.into_any().downcast::().unwrap(); + + pod.verify()?; + println!("id: {}", pod.id()); + println!("kvs: {:?}", pod.kvs()); + + let mut bad_pod = pod.clone(); + bad_pod.signature = signer.0.sign(Value::from(42_i64))?; + assert!(bad_pod.verify().is_err()); + + let mut bad_pod = pod.clone(); + bad_pod.id.0 .0[0] = F::ZERO; + assert!(bad_pod.verify().is_err()); + + let mut bad_pod = pod.clone(); + let bad_kv = (hash_str(KEY_SIGNER).into(), Value(PodId(EMPTY_HASH).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::>(); + let bad_mt = MerkleTree::new(MAX_DEPTH, bad_kvs_mt)?; + bad_pod.dict.mt = bad_mt; + assert!(bad_pod.verify().is_err()); + + 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::>(); + let bad_mt = MerkleTree::new(MAX_DEPTH, bad_kvs_mt)?; + bad_pod.dict.mt = bad_mt; + assert!(bad_pod.verify().is_err()); + + Ok(()) + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 49b6331..109dc1f 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -249,7 +249,7 @@ impl SignedPod { pub fn origin(&self) -> Origin { Origin::new(PodClass::Signed, self.id()) } - pub fn verify(&self) -> bool { + pub fn verify(&self) -> Result<()> { self.pod.verify() } pub fn kvs(&self) -> HashMap { @@ -808,7 +808,7 @@ pub struct MainPod { impl fmt::Display for MainPod { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "MainPod: {}", self.pod.id())?; - writeln!(f, " valid? {}", self.pod.verify())?; + writeln!(f, " valid? {}", self.pod.verify().is_ok())?; writeln!(f, " statements:")?; for st in &self.pod.pub_statements() { writeln!(f, " - {}", st)?; diff --git a/src/frontend/serialization.rs b/src/frontend/serialization.rs index 4865220..036f3fe 100644 --- a/src/frontend/serialization.rs +++ b/src/frontend/serialization.rs @@ -255,7 +255,7 @@ mod tests { assert_eq!(pod.kvs, deserialized.kvs); assert_eq!(pod.origin(), deserialized.origin()); - assert_eq!(pod.verify(), deserialized.verify()); + assert_eq!(pod.verify().is_ok(), deserialized.verify().is_ok()); assert_eq!(pod.id(), deserialized.id()) } @@ -289,6 +289,9 @@ mod tests { assert_eq!(kyc_pod.public_statements, deserialized.public_statements); assert_eq!(kyc_pod.pod.id(), deserialized.pod.id()); - assert_eq!(kyc_pod.pod.verify(), deserialized.pod.verify()); + assert_eq!( + kyc_pod.pod.verify().is_ok(), + deserialized.pod.verify().is_ok() + ); } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index b07d478..6074039 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -66,6 +66,7 @@ impl ToFields for PodId { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PodType { None = 0, MockSigned = 1, @@ -73,6 +74,17 @@ pub enum PodType { Signed = 3, Main = 4, } +impl fmt::Display for PodType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PodType::None => write!(f, "None"), + PodType::MockSigned => write!(f, "MockSigned"), + PodType::MockMain => write!(f, "MockMain"), + PodType::Signed => write!(f, "Signed"), + PodType::Main => write!(f, "Main"), + } + } +} impl From for Value { fn from(v: PodType) -> Self { @@ -169,7 +181,7 @@ impl Params { } pub trait Pod: fmt::Debug + DynClone { - fn verify(&self) -> bool; + fn verify(&self) -> Result<()>; fn id(&self) -> PodId; fn pub_statements(&self) -> Vec; /// Extract key-values from ValueOf public statements @@ -208,8 +220,8 @@ pub trait PodSigner { pub struct NonePod {} impl Pod for NonePod { - fn verify(&self) -> bool { - true + fn verify(&self) -> Result<()> { + Ok(()) } fn id(&self) -> PodId { PodId(EMPTY_HASH)