implement SignedPod (non-mock) using proof-based signatures (#160)

This commit is contained in:
arnaucube 2025-03-25 22:17:14 +01:00 committed by GitHub
parent 30f26a94ef
commit d6033b7090
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 259 additions and 60 deletions

View file

@ -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"

View file

@ -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::<Result<Vec<_>>>()
.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,7 +541,7 @@ pub mod tests {
println!("{:#}", pod);
assert!(pod.verify()); // TODO
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::<MockMainPod>().unwrap();
println!("{}", pod);
assert!(pod.verify());
pod.verify()?;
Ok(())
}

View file

@ -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::<HashMap<Value, Value>>(),
) {
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::<MockSignedPod>().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::<HashMap<Value, Value>>();
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::<HashMap<Value, Value>>();
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(())
}

View file

@ -2,3 +2,4 @@ pub mod basetypes;
pub mod circuits;
pub mod mock;
pub mod primitives;
pub mod signedpod;

View file

@ -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<F, C, D>,
circuit: SignatureCircuit,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct VerifierParams(VerifierCircuitData<F, C, D>);
#[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<Signature> {
pub fn sign(&self, msg: Value) -> Result<Signature> {
let pk = self.public_key();
let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements);
let mut pw = PartialWitness::<F>::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<ProverParams> {
let (builder, circuit) = Self::builder()?;
let prover = builder.build_prover::<C>();
Ok(ProverParams { prover, circuit })
}
pub fn verifier_params() -> Result<VerifierParams> {
let (builder, _) = Self::builder()?;
let circuit_data = builder.build::<C>();
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<F, D>, 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<F> = [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(())
}

View file

@ -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<Hash, Value>) -> Result<Box<dyn Pod>> {
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::<HashMap<Value, Value>>(),
)?;
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<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
}
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(&params);
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::<SignedPod>().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::<HashMap<Value, Value>>();
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::<HashMap<Value, Value>>();
let bad_mt = MerkleTree::new(MAX_DEPTH, bad_kvs_mt)?;
bad_pod.dict.mt = bad_mt;
assert!(bad_pod.verify().is_err());
Ok(())
}
}

View file

@ -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<Hash, middleware::Value> {
@ -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)?;

View file

@ -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()
);
}
}

View file

@ -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<PodType> 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<Statement>;
/// 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)