No Pod IDs (#394)

- middleware:
  - Add `Statement::Intro`
  - Add `SignedBy` native predicate and operation.  The signature is auxiliary data to the operation
  - Rename `PodSigner` to `Signer` with a new API (just for signing `RawValue`)
  - Removed `NewEntry` operation.  Use `ContainsFromEntries` instead
  - Remove `KEY_SIGNER` and `KEY_TYPE` which are no longer used
  - Merge `RecursivePod` and `Pod` traits
  - Change the `Pod::deserialize_data` method to use `Self` instead of `Box<dyn Pod>` 
  - Extend `Pod` trait with these methods:
    - `is_main`: when the pod is Main, in a (recursive) verification its vk will be checked to exist in the vd_set but not if it's intro pod
    - `is_mock`: skip some verifications in the recursive mock MainPod verification
    - `verifier_data_hash`
    - `pod_id` renamed to `statements_hash`
  - AnchoredKeys are now a pair of dictionary root and key
  - Entry statements are now defined as Contains with literal arguments
    - Operations that take Entries now use Contains statements with literal arguments
- frontend:
  - Rename `SignedPod` to `SignedDict` (which now contains the dict, public key and signature, and can still `verify(self)`ed)
  - The `SignedDict` keeps the method `get_statement` for convenience but now it returns a `Contains` statement that proves the existence of the key in the dict
  - The `MainPodBuilder` automatically inserts a `Contains` statement when an operation is added that uses an entry as argument that was not yet "opened".
  - Removed the `literal` methods from the `MainPodBuilder` that were loading literals to anchored keys: that was no longer needed after we introduced literal arguments
- backend
  - Only verify inclusion of the verifying key into the vd_set if the pod is MainPod.  A pod is not MainPod if the first statement is Intro.
  - Reject intro pods that have non-intro statements
  - Empty pod now returns an intro statement
  - Don't insert a type statement automatically in MainPod and MockMainPod.  We get rid of the type entry.
  - Implement `SignedBy` operation, which uses the muxed table to store signature verifications
- Rename `PodId` to `statements_hash` or `sts_hash` for short.  Now this is only used as a hash of the statements for the circuits public inputs.
- Refactor normalization of `self` statements:
  - Before: replace values that contain `SELF` by the given pod_id
  - After: place the verifying key hash into the Intro predicates
This commit is contained in:
Eduard S. 2025-08-27 13:19:40 +02:00 committed by GitHub
parent 122f9c3cac
commit 0e2f7b756e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2127 additions and 3064 deletions

View file

@ -37,12 +37,17 @@ pub type CircuitBuilder = circuit_builder::CircuitBuilder<F, D>;
pub type Proof = proof::Proof<F, C, D>;
pub type ProofWithPublicInputs = proof::ProofWithPublicInputs<F, C, D>;
pub type HashOut = hash_types::HashOut<F>;
use std::{collections::HashMap, sync::LazyLock};
pub use crate::backends::plonky2::{
primitives::ec::{
curve::Point as PublicKey,
schnorr::{SecretKey, Signature},
},
recursion::circuit::hash_verifier_data,
};
use crate::{
backends::plonky2::{
emptypod::cache_get_standard_empty_pod_verifier_circuit_data,
mainpod::cache_get_rec_main_pod_verifier_circuit_data,
primitives::merkletree::MerkleClaimAndProof,
},
@ -51,14 +56,12 @@ use crate::{
pub static DEFAULT_VD_LIST: LazyLock<Vec<VerifierOnlyCircuitData>> = LazyLock::new(|| {
let params = Params::default();
vec![
cache_get_rec_main_pod_verifier_circuit_data(&params)
.verifier_only
.clone(),
cache_get_standard_empty_pod_verifier_circuit_data()
.verifier_only
.clone(),
]
// NOTE: We only include the recursive MainPod with default parameters here. We don't need to
// include the verifying key of the EmptyPod because it's an Introduction pod and its verifying
// key appears in its statement in a self-describing way.
vec![cache_get_rec_main_pod_verifier_circuit_data(&params)
.verifier_only
.clone()]
});
pub static DEFAULT_VD_SET: LazyLock<VDSet> = LazyLock::new(|| {
@ -144,23 +147,16 @@ impl VDSet {
self.root
}
/// returns the vector of merkle proofs corresponding to the given verifier_datas
pub fn get_vds_proofs(
&self,
vds: &[VerifierOnlyCircuitData],
) -> Result<Vec<MerkleClaimAndProof>> {
let mut proofs: Vec<MerkleClaimAndProof> = vec![];
for vd in vds {
let verifier_data_hash =
crate::backends::plonky2::recursion::circuit::hash_verifier_data(vd);
let p = self
.proofs_map
.get(&Hash(verifier_data_hash.elements))
.ok_or(crate::middleware::Error::custom(
"verifier_data not found in VDSet".to_string(),
))?;
proofs.push(p.clone());
}
Ok(proofs)
pub fn get_vds_proof(&self, vd: &VerifierOnlyCircuitData) -> Result<MerkleClaimAndProof> {
let verifier_data_hash =
crate::backends::plonky2::recursion::circuit::hash_verifier_data(vd);
Ok(self
.proofs_map
.get(&Hash(verifier_data_hash.elements))
.ok_or(crate::middleware::Error::custom(
"verifier_data not found in VDSet".to_string(),
))?
.clone())
}
/// Returns true if the `verifier_data_hash` is in the set
pub fn contains(&self, verifier_data_hash: HashOut) -> bool {

View file

@ -111,10 +111,10 @@ impl StatementArgTarget {
pub fn anchored_key(
_builder: &mut CircuitBuilder,
pod_id: &ValueTarget,
dict: &ValueTarget,
key: &ValueTarget,
) -> Self {
Self::new(*pod_id, *key)
Self::new(*dict, *key)
}
pub fn wildcard_literal(builder: &mut CircuitBuilder, value: &ValueTarget) -> Self {
@ -360,6 +360,20 @@ impl PredicateTarget {
}
}
pub fn new_intro(builder: &mut CircuitBuilder, vd_hash: HashOutTarget) -> Self {
let prefix = builder.constant(F::from(PredicatePrefix::Intro));
let vh = vd_hash.elements;
let zero = builder.zero();
Self {
elements: [prefix, vh[0], vh[1], vh[2], vh[3], zero],
}
}
pub fn is_intro(&self, builder: &mut CircuitBuilder) -> BoolTarget {
let prefix = builder.constant(F::from(PredicatePrefix::Intro));
builder.is_equal(prefix, self.elements[0])
}
pub fn set_targets(
&self,
pw: &mut PartialWitness<F>,

File diff suppressed because it is too large Load diff

View file

@ -3,5 +3,4 @@ pub mod hash;
pub mod mainpod;
pub mod metrics;
pub mod mux_table;
pub mod signedpod;
pub mod utils;

View file

@ -1,252 +0,0 @@
use std::iter;
use itertools::Itertools;
use plonky2::{
hash::hash_types::{HashOut, HashOutTarget},
iop::witness::{PartialWitness, WitnessWrite},
plonk::circuit_builder::CircuitBuilder,
};
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
basetypes::D,
circuits::common::{
CircuitBuilderPod, PredicateTarget, StatementArgTarget, StatementTarget, ValueTarget,
},
error::Result,
primitives::{
merkletree::{
verify_merkle_proof_existence_circuit, MerkleClaimAndProof,
MerkleProofExistenceTarget,
},
signature::{verify_signature_circuit, SignatureVerifyTarget},
},
signedpod::SignedPod,
},
measure_gates_begin, measure_gates_end,
middleware::{
hash_str, Key, NativePredicate, Params, PodType, RawValue, Value, F, KEY_SIGNER, KEY_TYPE,
SELF,
},
};
pub fn verify_signed_pod_circuit(
builder: &mut CircuitBuilder<F, D>,
signed_pod: &SignedPodVerifyTarget,
) -> Result<()> {
let params = &signed_pod.params;
let measure = measure_gates_begin!(builder, "SignedPodVerify");
// 1. Verify id
assert_eq!(params.max_signed_pod_values, signed_pod.mt_proofs.len());
for mt_proof in &signed_pod.mt_proofs {
verify_merkle_proof_existence_circuit(builder, mt_proof);
builder.connect_hashes(signed_pod.id, mt_proof.root);
// mt_proofs.push(mt_proof);
}
// 2. Verify type
let type_mt_proof = &signed_pod.mt_proofs[0];
let key_type = builder.constant_value(hash_str(KEY_TYPE).into());
builder.connect_values(type_mt_proof.key, key_type);
let value_type = builder.constant_value(Value::from(PodType::Signed).raw());
builder.connect_values(type_mt_proof.value, value_type);
// 3.a. Verify signature
verify_signature_circuit(builder, &signed_pod.signature);
// 3.b. Verify signer (ie. hash(signature.pk) == merkletree.signer_leaf)
let signer_mt_proof = &signed_pod.mt_proofs[1];
let key_signer = builder.constant_value(Key::from(KEY_SIGNER).raw());
let pk_hash = signed_pod.signature.pk.to_value(builder);
builder.connect_values(signer_mt_proof.key, key_signer);
builder.connect_values(signer_mt_proof.value, pk_hash);
// 3.c. connect signed message to pod.id
builder.connect_values(
ValueTarget::from_slice(&signed_pod.id.elements),
signed_pod.signature.msg,
);
measure_gates_end!(builder, measure);
Ok(())
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SignedPodVerifyTarget {
params: Params,
id: HashOutTarget,
// the KEY_TYPE entry must be the first one
// the KEY_SIGNER entry must be the second one
mt_proofs: Vec<MerkleProofExistenceTarget>,
pub(crate) signature: SignatureVerifyTarget,
}
impl SignedPodVerifyTarget {
pub fn new_virtual(params: &Params, builder: &mut CircuitBuilder<F, D>) -> Self {
SignedPodVerifyTarget {
params: params.clone(),
id: builder.add_virtual_hash(),
mt_proofs: (0..params.max_signed_pod_values)
.map(|_| {
MerkleProofExistenceTarget::new_virtual(params.max_depth_mt_containers, builder)
})
.collect(),
signature: SignatureVerifyTarget::new_virtual(builder),
}
}
pub fn pub_statements(
&self,
builder: &mut CircuitBuilder<F, D>,
self_id: bool,
) -> Vec<StatementTarget> {
let mut statements = Vec::new();
let predicate = PredicateTarget::new_native(builder, &self.params, NativePredicate::Equal);
let pod_id = if self_id {
builder.constant_value(SELF.0.into())
} else {
ValueTarget {
elements: self.id.elements,
}
};
for mt_proof in &self.mt_proofs {
let args = [
StatementArgTarget::anchored_key(builder, &pod_id, &mt_proof.key),
StatementArgTarget::literal(builder, &mt_proof.value),
]
.into_iter()
.chain(iter::repeat_with(|| StatementArgTarget::none(builder)))
.take(self.params.max_statement_args)
.collect();
let statement = StatementTarget {
predicate: predicate.clone(),
args,
};
statements.push(statement);
}
statements
}
pub fn set_targets(&self, pw: &mut PartialWitness<F>, pod: &SignedPod) -> Result<()> {
// set the self.mt_proofs witness with the following order:
// - KEY_TYPE leaf proof
// - KEY_SIGNER leaf proof
// - rest of leaves
// - empty leaves (if needed)
// add proof verification of KEY_TYPE & KEY_SIGNER leaves
let key_type_key = Key::from(KEY_TYPE);
let key_signer_key = Key::from(KEY_SIGNER);
[&key_type_key, &key_signer_key]
.iter()
.enumerate()
.try_for_each(|(i, k)| {
let (v, proof) = pod.dict.prove(k)?;
self.mt_proofs[i].set_targets(
pw,
true,
&MerkleClaimAndProof::new(pod.dict.commitment(), k.raw(), Some(v.raw()), proof),
)
})?;
// add the verification of the rest of leaves
let mut curr = 2; // since we already added key_type and key_signer
for (k, v) in pod.dict.kvs().iter().sorted_by_key(|kv| kv.0.hash()) {
if *k == key_type_key || *k == key_signer_key {
// skip the key_type & key_signer leaves, since they have
// already been checked
continue;
}
let (obtained_v, proof) = pod.dict.prove(k)?;
assert_eq!(obtained_v, v); // sanity check
self.mt_proofs[curr].set_targets(
pw,
true,
&MerkleClaimAndProof::new(pod.dict.commitment(), k.raw(), Some(v.raw()), proof),
)?;
curr += 1;
}
// sanity check
assert!(curr <= self.params.max_signed_pod_values);
// add the proofs of empty leaves (if needed), till the max_signed_pod_values
let mut mp = MerkleClaimAndProof::empty();
mp.root = pod.dict.commitment();
for i in curr..self.params.max_signed_pod_values {
self.mt_proofs[i].set_targets(pw, false, &mp)?;
}
// get the signer pk
let pk = pod.signer;
// the msg signed is the pod.id
let msg = RawValue::from(pod.id.0);
// set signature targets values
self.signature
.set_targets(pw, true, pk, msg, pod.signature.clone())?;
// set the id target value
pw.set_hash_target(self.id, HashOut::from_vec(pod.id.0 .0.to_vec()))?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use std::any::Any;
use plonky2::plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig};
use super::*;
use crate::{
backends::plonky2::{
basetypes::C,
primitives::ec::schnorr::SecretKey,
signedpod::{SignedPod, Signer},
},
middleware::F,
};
#[test]
fn test_signed_pod_verify() -> Result<()> {
let params = Params {
max_signed_pod_values: 6,
..Default::default()
};
// set max_signed_pod_values to 6, and we insert 3 leaves, so that the
// circuit has enough space for the 3 leaves plus the KEY_TYPE and
// KEY_SIGNER and one empty leaf.
// prepare a signedpod
let mut pod = crate::frontend::SignedPodBuilder::new(&params);
pod.insert("idNumber", "4242424242");
pod.insert("dateOfBirth", 1169909384);
pod.insert("socialSecurityNumber", "G2121210");
let sk = SecretKey::new_rand();
let signer = Signer(sk);
let pod = pod.sign(&signer).unwrap();
let signed_pod = (pod.pod as Box<dyn Any>).downcast::<SignedPod>().unwrap();
// use the pod in the circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
// build the circuit logic
let signed_pod_verify = SignedPodVerifyTarget::new_virtual(&params, &mut builder);
verify_signed_pod_circuit(&mut builder, &signed_pod_verify)?;
// set the signed_pod as target values for the circuit
signed_pod_verify.set_targets(&mut pw, &signed_pod)?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
}

View file

@ -3,7 +3,6 @@ use plonky2::{
hash::hash_types::HashOutTarget,
iop::witness::{PartialWitness, WitnessWrite},
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::{self, CircuitConfig},
proof::ProofWithPublicInputs,
},
@ -12,16 +11,16 @@ use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
basetypes::{Proof, C, D},
basetypes::{CircuitBuilder, Proof, C, D},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::{
common::{Flattenable, StatementTarget},
mainpod::{calculate_id_circuit, PI_OFFSET_ID},
mainpod::{calculate_statements_hash_circuit, PI_OFFSET_STATEMENTS_HASH},
},
deserialize_proof, deserialize_verifier_only,
error::{Error, Result},
hash_common_data,
mainpod::{self, calculate_id},
mainpod::{self, calculate_statements_hash},
recursion::pad_circuit,
serialization::{
CircuitDataSerializer, VerifierCircuitDataSerializer, VerifierOnlyCircuitDataSerializer,
@ -30,52 +29,57 @@ use crate::{
},
cache::{self, CacheEntry},
middleware::{
self, AnchoredKey, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement, ToFields,
VDSet, Value, VerifierOnlyCircuitData, F, HASH_SIZE, KEY_TYPE, SELF,
self, Hash, IntroPredicateRef, Params, Pod, PodType, Statement, ToFields, VDSet,
VerifierOnlyCircuitData, EMPTY_HASH, F, HASH_SIZE,
},
timed,
};
struct EmptyPodVerifyCircuit {
params: Params,
}
fn type_statement() -> Statement {
Statement::equal(
AnchoredKey::from((SELF, KEY_TYPE)),
Value::from(PodType::Empty),
fn empty_statement() -> Statement {
Statement::Intro(
IntroPredicateRef {
name: "empty".to_string(),
args_len: 0,
verifier_data_hash: EMPTY_HASH,
},
vec![],
)
}
impl EmptyPodVerifyCircuit {
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<EmptyPodVerifyTarget> {
let type_statement = StatementTarget::from_flattened(
&self.params,
&builder.constants(&type_statement().to_fields(&self.params)),
);
let id = calculate_id_circuit(&self.params, builder, &[type_statement]);
let vds_root = builder.add_virtual_hash();
builder.register_public_inputs(&id.elements);
builder.register_public_inputs(&vds_root.elements);
Ok(EmptyPodVerifyTarget { vds_root })
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct EmptyPodVerifyTarget {
vds_root: HashOutTarget,
}
impl EmptyPodVerifyTarget {
pub fn new_virtual(builder: &mut CircuitBuilder) -> Self {
Self {
vds_root: builder.add_virtual_hash(),
}
}
pub fn set_targets(&self, pw: &mut PartialWitness<F>, vds_root: Hash) -> Result<()> {
Ok(pw.set_target_arr(&self.vds_root.elements, &vds_root.0)?)
}
}
fn verify_empty_pod_circuit(
params: &Params,
builder: &mut CircuitBuilder,
empty_pod: &EmptyPodVerifyTarget,
) {
let empty_statement = StatementTarget::from_flattened(
params,
&builder.constants(&empty_statement().to_fields(params)),
);
let sts_hash = calculate_statements_hash_circuit(params, builder, &[empty_statement]);
builder.register_public_inputs(&sts_hash.elements);
builder.register_public_inputs(&empty_pod.vds_root.elements);
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct EmptyPod {
params: Params,
id: PodId,
sts_hash: Hash,
verifier_only: VerifierOnlyCircuitDataSerializer,
common_hash: String,
vd_set: VDSet,
@ -110,17 +114,15 @@ fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
#[cfg(feature = "zk")]
let config = CircuitConfig::standard_recursion_zk_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let empty_pod_verify_target = EmptyPodVerifyCircuit {
params: params.clone(),
}
.eval(&mut builder)?;
let mut builder = CircuitBuilder::new(config);
let empty_pod = EmptyPodVerifyTarget::new_virtual(&mut builder);
verify_empty_pod_circuit(&params, &mut builder, &empty_pod);
let common_circuit_data = &*cache_get_standard_rec_main_pod_common_circuit_data();
pad_circuit(&mut builder, common_circuit_data);
let data = timed!("EmptyPod build", builder.build::<C>());
assert_eq!(common_circuit_data.0, data.common);
Ok((empty_pod_verify_target, data))
Ok((empty_pod, data))
}
impl EmptyPod {
@ -130,19 +132,22 @@ impl EmptyPod {
let mut pw = PartialWitness::<F>::new();
empty_pod_verify_target.set_targets(&mut pw, vd_set.root())?;
let proof = timed!("EmptyPod prove", data.prove(pw)?);
let id = &proof.public_inputs[PI_OFFSET_ID..PI_OFFSET_ID + HASH_SIZE];
let id = PodId(Hash([id[0], id[1], id[2], id[3]]));
let sts_hash = {
let v = &proof.public_inputs
[PI_OFFSET_STATEMENTS_HASH..PI_OFFSET_STATEMENTS_HASH + HASH_SIZE];
Hash([v[0], v[1], v[2], v[3]])
};
let common_hash = hash_common_data(&data.common).expect("hash ok");
Ok(EmptyPod {
params: params.clone(),
verifier_only: VerifierOnlyCircuitDataSerializer(data.verifier_only.clone()),
common_hash,
id,
sts_hash,
vd_set,
proof: proof.proof,
})
}
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn RecursivePod> {
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn Pod> {
let default_params = Params::default();
assert_eq!(default_params.id_params(), params.id_params());
@ -173,12 +178,12 @@ impl Pod for EmptyPod {
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let id = PodId(calculate_id(&statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
let sts_hash = calculate_statements_hash(&statements, &self.params);
if sts_hash != self.sts_hash {
return Err(Error::statements_hash_not_equal(self.sts_hash, sts_hash));
}
let public_inputs = id
let public_inputs = sts_hash
.to_fields(&self.params)
.iter()
.chain(self.vd_set.root().0.iter())
@ -194,28 +199,17 @@ impl Pod for EmptyPod {
.map_err(|e| Error::plonky2_proof_fail("EmptyPod", e))
}
fn id(&self) -> PodId {
self.id
fn statements_hash(&self) -> Hash {
self.sts_hash
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::Empty as usize, "Empty")
}
fn pub_self_statements(&self) -> Vec<middleware::Statement> {
vec![type_statement()]
vec![empty_statement()]
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
proof: serialize_proof(&self.proof),
verifier_only: serialize_verifier_only(&self.verifier_only),
common_hash: self.common_hash.clone(),
})
.expect("serialization to json")
}
}
impl RecursivePod for EmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
self.verifier_only.0.clone()
}
@ -228,24 +222,33 @@ impl RecursivePod for EmptyPod {
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
proof: serialize_proof(&self.proof),
verifier_only: serialize_verifier_only(&self.verifier_only),
common_hash: self.common_hash.clone(),
})
.expect("serialization to json")
}
fn deserialize_data(
params: Params,
data: serde_json::Value,
vd_set: VDSet,
id: PodId,
) -> Result<Box<dyn RecursivePod>> {
sts_hash: Hash,
) -> Result<Self> {
let data: Data = serde_json::from_value(data)?;
let common_circuit_data = cache_get_standard_rec_main_pod_common_circuit_data();
let proof = deserialize_proof(&common_circuit_data, &data.proof)?;
let verifier_only = deserialize_verifier_only(&data.verifier_only)?;
Ok(Box::new(Self {
Ok(Self {
params,
id,
sts_hash,
verifier_only: VerifierOnlyCircuitDataSerializer(verifier_only),
common_hash: data.common_hash,
vd_set,
proof,
}))
})
}
}

View file

@ -1,21 +1,15 @@
use std::{backtrace::Backtrace, fmt::Debug};
use crate::middleware::{PodId, PodType, Value};
use crate::middleware::Hash;
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(thiserror::Error, Debug)]
pub enum InnerError {
#[error("id does not match, expected {0}, found {1}")]
IdNotEqual(PodId, PodId),
#[error("type does not match, expected {0}, found {1}")]
TypeNotEqual(PodType, Value),
#[error("signer public key does not match, expected {0}, found {1}")]
SignerNotEqual(Value, Value),
#[error("Statements hash does not match, expected {0}, found {1}")]
StsHashNotEqual(Hash, Hash),
// POD related
#[error("invalid POD ID")]
PodIdInvalid,
#[error("verification failed: POD does not have type statement")]
NotTypeStatement,
#[error("repeated ValueOf")]
@ -85,16 +79,7 @@ impl Error {
pub fn not_type_statement() -> Self {
new!(NotTypeStatement)
}
pub fn pod_id_invalid() -> Self {
new!(PodIdInvalid)
}
pub fn id_not_equal(expected: PodId, found: PodId) -> Self {
new!(IdNotEqual(expected, found))
}
pub fn type_not_equal(expected: PodType, found: Value) -> Self {
new!(TypeNotEqual(expected, found))
}
pub(crate) fn signer_not_equal(expected: Value, found: Value) -> Self {
new!(SignerNotEqual(expected, found))
pub fn statements_hash_not_equal(expected: Hash, found: Hash) -> Self {
new!(StsHashNotEqual(expected, found))
}
}

View file

@ -1,8 +1,10 @@
pub mod operation;
use crate::middleware::PodType;
pub mod statement;
use std::{any::Any, iter, sync::Arc};
use std::{iter, sync::Arc};
use itertools::Itertools;
use num_bigint::BigUint;
pub use operation::*;
use plonky2::{hash::poseidon::PoseidonHash, plonk::config::Hasher};
use serde::{Deserialize, Serialize};
@ -10,7 +12,7 @@ pub use statement::*;
use crate::{
backends::plonky2::{
basetypes::{CircuitData, Proof, ProofWithPublicInputs, VerifierOnlyCircuitData},
basetypes::{CircuitData, Proof, ProofWithPublicInputs, VerifierOnlyCircuitData, F},
cache::{self, CacheEntry},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::mainpod::{CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget},
@ -20,7 +22,10 @@ use crate::{
hash_common_data,
mock::emptypod::MockEmptyPod,
primitives::{
ec::schnorr::SecretKey,
ec::{
curve::Point as PublicKey,
schnorr::{SecretKey, Signature},
},
merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
},
recursion::{
@ -30,32 +35,31 @@ use crate::{
CircuitDataSerializer, CommonCircuitDataSerializer, VerifierCircuitDataSerializer,
},
serialize_proof, serialize_verifier_only,
signedpod::SignedPod,
},
middleware::{
self, resolve_wildcard_values, value_from_op, AnchoredKey, CustomPredicateBatch,
Error as MiddlewareError, Hash, MainPodInputs, NativeOperation, OperationType, Params, Pod,
PodId, PodProver, PodType, RecursivePod, StatementArg, ToFields, VDSet, KEY_TYPE, SELF,
self, resolve_wildcard_values, value_from_op, CustomPredicateBatch,
Error as MiddlewareError, Hash, MainPodInputs, MainPodProver, NativeOperation,
OperationType, Params, Pod, RawValue, StatementArg, ToFields, VDSet,
},
timed,
};
/// Hash a list of public statements to derive the PodId. To make circuits with different number
/// of `max_public_statements compatible we pad the statements up to `num_public_statements_id`.
/// As an optimization we front pad with none-statements so that circuits with a small
/// `max_public_statements` only pay for `max_public_statements` by starting the poseidon state
/// with a precomputed constant corresponding to the front-padding part:
/// `id = hash(serialize(reverse(statements || none-statements)))`
pub fn calculate_id(statements: &[Statement], params: &Params) -> middleware::Hash {
assert!(statements.len() <= params.num_public_statements_id);
assert!(params.max_public_statements <= params.num_public_statements_id);
/// Hash a list of public statements to derive the Statements hash. To make circuits with
/// different number of `max_public_statements compatible we pad the statements up to
/// `num_public_statements_id`. As an optimization we front pad with none-statements so that
/// circuits with a small `max_public_statements` only pay for `max_public_statements` by starting
/// the poseidon state with a precomputed constant corresponding to the front-padding part: `id =
/// hash(serialize(reverse(statements || none-statements)))`
pub fn calculate_statements_hash(statements: &[Statement], params: &Params) -> middleware::Hash {
assert!(statements.len() <= params.num_public_statements_hash);
assert!(params.max_public_statements <= params.num_public_statements_hash);
let mut none_st: Statement = middleware::Statement::None.into();
pad_statement(params, &mut none_st);
let statements_back_padded = statements
.iter()
.chain(iter::repeat(&none_st))
.take(params.num_public_statements_id)
.take(params.num_public_statements_hash)
.collect_vec();
let field_elems = statements_back_padded
.iter()
@ -236,6 +240,60 @@ pub(crate) fn extract_public_key_of(
Ok(table)
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SignedBy {
pub msg: RawValue,
pub pk: PublicKey,
pub sig: Signature,
}
impl SignedBy {
/// A valid deterministic signature from a known private key and nonce, used for padding
pub fn dummy() -> Self {
let sk = SecretKey(BigUint::from(1u32));
let pk = sk.public_key();
let msg = RawValue([F(0), F(0), F(0), F(0)]);
let nonce = BigUint::from(2u32);
let sig = sk.sign(msg, &nonce);
Self { msg, pk, sig }
}
}
/// Extracts Signatures verification data from SignedBy ops.
pub(crate) fn extract_signatures(
params: &Params,
aux_list: &mut [OperationAux],
operations: &[middleware::Operation],
statements: &[middleware::Statement],
) -> Result<Vec<SignedBy>> {
let mut table = Vec::new();
for (i, (op, st)) in operations.iter().zip(statements.iter()).enumerate() {
let deduction_err = || MiddlewareError::invalid_deduction(op.clone(), st.clone());
if let (
middleware::Operation::SignedBy(msg_s, pk_s, sig),
middleware::Statement::SignedBy(msg_ref, pk_ref),
) = (op, st)
{
let msg = value_from_op(msg_s, msg_ref).ok_or_else(deduction_err)?;
let pk = value_from_op(pk_s, pk_ref).ok_or_else(deduction_err)?;
aux_list[i] = OperationAux::SignedByIndex(table.len());
table.push(SignedBy {
msg: msg.raw(),
pk: PublicKey::try_from(pk.typed())?,
sig: sig.clone(),
});
}
}
if table.len() > params.max_signed_by {
return Err(Error::custom(format!(
"The number of required signatures ({}) exceeds the maximum number ({}).",
table.len(),
params.max_signed_by
)));
}
Ok(table)
}
/// Find the operation argument statement in the list of previous statements and return the index.
fn find_op_arg(statements: &[Statement], op_arg: &middleware::Statement) -> Result<OperationArg> {
// NOTE: The `None` `Statement` always exists as a constant at index 0
@ -283,37 +341,17 @@ pub(crate) fn layout_statements(
// predicate statements
statements.push(middleware::Statement::None.into());
// Input signed pods region
let dummy_signed_pod_box: Box<dyn Pod> = Box::new(SignedPod::dummy());
let dummy_signed_pod = dummy_signed_pod_box.as_ref();
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).unwrap_or(&dummy_signed_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();
pad_statement(params, &mut st);
statements.push(st);
}
}
// Input main pods region
let empty_pod_box: Box<dyn RecursivePod> =
if mock || inputs.recursive_pods.len() == params.max_input_recursive_pods {
// We mocking or we don't need padding so we skip creating an EmptyPod
MockEmptyPod::new_boxed(params, inputs.vd_set.clone())
} else {
EmptyPod::new_boxed(params, inputs.vd_set.clone())
};
// Input pods region
let empty_pod_box: Box<dyn Pod> = if mock || inputs.pods.len() == params.max_input_pods {
// We mocking or we don't need padding so we skip creating an EmptyPod
MockEmptyPod::new_boxed(params, inputs.vd_set.clone())
} else {
EmptyPod::new_boxed(params, inputs.vd_set.clone())
};
let empty_pod = empty_pod_box.as_ref();
assert!(inputs.recursive_pods.len() <= params.max_input_recursive_pods);
for i in 0..params.max_input_recursive_pods {
let pod = inputs.recursive_pods.get(i).copied().unwrap_or(empty_pod);
assert!(inputs.pods.len() <= params.max_input_pods);
for i in 0..params.max_input_pods {
let pod = inputs.pods.get(i).copied().unwrap_or(empty_pod);
let sts = pod.pub_statements();
assert!(sts.len() <= params.max_public_statements);
for j in 0..params.max_input_pods_public_statements {
@ -347,20 +385,7 @@ pub(crate) fn layout_statements(
// Public statements
assert!(inputs.public_statements.len() < params.max_public_statements);
let pod_type = if mock {
PodType::MockMain
} else {
PodType::Main
};
let mut type_st = middleware::Statement::Equal(
AnchoredKey::from((SELF, KEY_TYPE)).into(),
middleware::Value::from(pod_type).into(),
)
.into();
pad_statement(params, &mut type_st);
statements.push(type_st);
for i in 0..(params.max_public_statements - 1) {
for i in 0..params.max_public_statements {
let mut st = inputs
.public_statements
.get(i)
@ -373,7 +398,7 @@ pub(crate) fn layout_statements(
let offset_public_statements = statements.len() - params.max_public_statements;
let public_statements = statements
[offset_public_statements..offset_public_statements + 1 + inputs.public_statements.len()]
[offset_public_statements..offset_public_statements + inputs.public_statements.len()]
.to_vec();
Ok((statements, public_statements))
}
@ -413,13 +438,7 @@ pub(crate) fn process_public_statements_operations(
mut operations: Vec<Operation>,
) -> Result<Vec<Operation>> {
let offset_public_statements = statements.len() - params.max_public_statements;
operations.push(Operation(
OperationType::Native(NativeOperation::NewEntry),
vec![],
OperationAux::None,
));
for i in 0..(params.max_public_statements - 1) {
let st = &statements[offset_public_statements + i + 1];
for st in statements.iter().skip(offset_public_statements) {
let mut op = if st.is_none() {
Operation(
OperationType::Native(NativeOperation::None),
@ -442,44 +461,28 @@ pub(crate) fn process_public_statements_operations(
pub struct Prover {}
impl PodProver for Prover {
fn prove(
&self,
params: &Params,
vd_set: &VDSet,
inputs: MainPodInputs,
) -> Result<Box<dyn RecursivePod>> {
let signed_pods_input: Vec<SignedPod> = inputs
.signed_pods
.iter()
.map(|p| {
let p = (*p as &dyn Any)
.downcast_ref::<SignedPod>()
.expect("type SignedPod");
p.clone()
})
.collect_vec();
impl MainPodProver for Prover {
fn prove(&self, params: &Params, inputs: MainPodInputs) -> Result<Box<dyn Pod>> {
// Pad input recursive pods with empty pods if necessary
let empty_pod = if inputs.recursive_pods.len() == params.max_input_recursive_pods {
let empty_pod = if inputs.pods.len() == params.max_input_pods {
// We don't need padding so we skip creating an EmptyPod
MockEmptyPod::new_boxed(params, inputs.vd_set.clone())
} else {
EmptyPod::new_boxed(params, inputs.vd_set.clone())
};
let inputs = MainPodInputs {
recursive_pods: &inputs
.recursive_pods
pods: &inputs
.pods
.iter()
.copied()
.chain(iter::repeat(&*empty_pod))
.take(params.max_input_recursive_pods)
.take(params.max_input_pods)
.collect_vec(),
..inputs
};
let recursive_pods_pub_self_statements = inputs
.recursive_pods
let input_pods_pub_self_statements = inputs
.pods
.iter()
.map(|pod| {
assert_eq!(params.id_params(), pod.params().id_params());
@ -500,6 +503,8 @@ impl PodProver for Prover {
)?;
let public_key_of_sks =
extract_public_key_of(params, &mut aux_list, inputs.operations, inputs.statements)?;
let signed_bys =
extract_signatures(params, &mut aux_list, inputs.operations, inputs.statements)?;
let merkle_tree_state_transition_proofs =
extract_merkle_tree_state_transition_proofs(params, &mut aux_list, inputs.operations)?;
@ -514,38 +519,54 @@ impl PodProver for Prover {
let operations = process_public_statements_operations(params, &statements, operations)?;
// get the id out of the public statements
let id: PodId = PodId(calculate_id(&public_statements, params));
let sts_hash = calculate_statements_hash(&public_statements, params);
let common_hash: String = cache_get_rec_main_pod_common_hash(params).clone();
let proofs = inputs
.recursive_pods
.pods
.iter()
.map(|pod| {
assert_eq!(pod.common_hash(), common_hash);
assert_eq!(inputs.vd_set.root(), pod.vd_set().root());
ProofWithPublicInputs {
proof: pod.proof(),
public_inputs: [pod.id().0 .0, inputs.vd_set.root().0].concat(),
public_inputs: [pod.statements_hash().0, inputs.vd_set.root().0].concat(),
}
})
.collect_vec();
let verifier_datas = inputs
.recursive_pods
.pods
.iter()
.map(|pod| pod.verifier_data())
.collect_vec();
let vd_mt_proofs = vd_set.get_vds_proofs(&verifier_datas)?;
let mut vd_mt_proofs = Vec::with_capacity(inputs.pods.len());
for (pod, vd) in inputs.pods.iter().zip(&verifier_datas) {
vd_mt_proofs.push(if pod.is_main() {
(true, inputs.vd_set.get_vds_proof(vd)?)
} else {
// For intro pods we don't verify inclusion of their vk into the vd set, so we
// generate a dummy mt proof with expected root and value to pass some constraints
(
false,
MerkleClaimAndProof {
root: inputs.vd_set.root(),
value: RawValue::from(pod.verifier_data_hash()),
..MerkleClaimAndProof::empty()
},
)
});
}
let input = MainPodVerifyInput {
vds_set: inputs.vd_set.clone(),
vd_mt_proofs,
signed_pods: signed_pods_input,
recursive_pods_pub_self_statements,
input_pods_pub_self_statements,
statements: statements[statements.len() - params.max_statements..].to_vec(),
operations,
merkle_proofs,
public_key_of_sks,
signed_bys,
merkle_tree_state_transition_proofs,
custom_predicate_batches,
custom_predicate_verifications,
@ -567,7 +588,7 @@ impl PodProver for Prover {
params: params.clone(),
verifier_only: circuit_data.verifier_only.clone(),
common_hash,
id,
sts_hash,
vd_set: inputs.vd_set,
public_statements,
proof: proof_with_pis.proof,
@ -578,7 +599,7 @@ impl PodProver for Prover {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MainPod {
params: Params,
id: PodId,
sts_hash: Hash,
verifier_only: VerifierOnlyCircuitData,
common_hash: String,
/// vds_root is the merkle-root of the `VDSet`, which contains the
@ -598,7 +619,7 @@ pub(crate) fn rec_main_pod_circuit_data(
timed!(
"recursive MainPod circuit_data padded",
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data_padded(
params.max_input_recursive_pods,
params.max_input_pods,
&rec_common_circuit_data,
params,
)
@ -676,6 +697,9 @@ impl Pod for MainPod {
fn params(&self) -> &Params {
&self.params
}
fn is_main(&self) -> bool {
true
}
fn verify(&self) -> Result<()> {
// 0. Assert that the CommonCircuitData of the pod is the current one
let expect_common_hash = &*cache_get_rec_main_pod_common_hash(&self.params);
@ -686,9 +710,9 @@ impl Pod for MainPod {
)));
}
// 2. get the id out of the public statements
let id = PodId(calculate_id(&self.public_statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
let sts_hash = calculate_statements_hash(&self.public_statements, &self.params);
if sts_hash != self.sts_hash {
return Err(Error::statements_hash_not_equal(self.sts_hash, sts_hash));
}
// 7. verifier_data_hash is in the VDSet
@ -705,7 +729,7 @@ impl Pod for MainPod {
// 1, 3, 4, 5 verification via the zkSNARK proof
let rec_main_pod_verifier_circuit_data =
&*cache_get_rec_main_pod_verifier_circuit_data(&self.params);
let public_inputs = id
let public_inputs = sts_hash
.to_fields(&self.params)
.iter()
.chain(self.vd_set.root().0.iter())
@ -719,8 +743,8 @@ impl Pod for MainPod {
.map_err(|e| Error::plonky2_proof_fail("MainPod", e))
}
fn id(&self) -> PodId {
self.id
fn statements_hash(&self) -> Hash {
self.sts_hash
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::Main as usize, "Main")
@ -734,18 +758,6 @@ impl Pod for MainPod {
.collect()
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
proof: serialize_proof(&self.proof),
public_statements: self.public_statements.clone(),
verifier_only: serialize_verifier_only(&self.verifier_only),
common_hash: self.common_hash.clone(),
})
.expect("serialization to json")
}
}
impl RecursivePod for MainPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
self.verifier_only.clone()
}
@ -758,30 +770,42 @@ impl RecursivePod for MainPod {
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
proof: serialize_proof(&self.proof),
public_statements: self.public_statements.clone(),
verifier_only: serialize_verifier_only(&self.verifier_only),
common_hash: self.common_hash.clone(),
})
.expect("serialization to json")
}
fn deserialize_data(
params: Params,
data: serde_json::Value,
vd_set: VDSet,
id: PodId,
) -> Result<Box<dyn RecursivePod>> {
id: Hash,
) -> Result<Self> {
let data: Data = serde_json::from_value(data)?;
let common = cache_get_rec_main_pod_common_circuit_data(&params);
let proof = deserialize_proof(&common, &data.proof)?;
let verifier_only = deserialize_verifier_only(&data.verifier_only)?;
Ok(Box::new(Self {
Ok(Self {
params,
id,
sts_hash: id,
verifier_only,
common_hash: data.common_hash,
vd_set,
proof,
public_statements: data.public_statements,
}))
})
}
}
#[cfg(test)]
pub mod tests {
use std::{any::Any, collections::HashSet};
use num::{BigUint, One};
use super::*;
@ -789,18 +813,19 @@ pub mod tests {
backends::plonky2::{
mock::mainpod::{MockMainPod, MockProver},
primitives::ec::schnorr::SecretKey,
signedpod::Signer,
signer::Signer,
},
dict,
examples::{
attest_eth_friend, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders,
EthDosHelper,
attest_eth_friend, tickets_pod_full_flow, zu_kyc_pod_builder,
zu_kyc_sign_dict_builders, EthDosHelper,
},
frontend::{
self, literal, CustomPredicateBatchBuilder, MainPodBuilder, StatementTmplBuilder as STB,
},
middleware::{
self, containers::Set, CustomPredicateRef, NativePredicate as NP, DEFAULT_VD_LIST,
DEFAULT_VD_SET,
self, containers::Set, CustomPredicateRef, NativePredicate as NP, Signer as _,
DEFAULT_VD_LIST, DEFAULT_VD_SET,
},
};
@ -809,7 +834,7 @@ pub mod tests {
let params = middleware::Params {
// Currently the circuit uses random access that only supports vectors of length 64.
// With max_input_main_pods=3 we need random access to a vector of length 73.
max_input_recursive_pods: 0,
max_input_pods: 0,
max_custom_predicate_batches: 0,
max_custom_predicate_verifications: 0,
..Default::default()
@ -819,7 +844,7 @@ pub mod tests {
vds.push(rec_main_pod_circuit_data(&params).1.verifier_only.clone());
let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap();
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_pod_builders(&params);
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(&params);
let signer = Signer(SecretKey(BigUint::one()));
let gov_id_pod = gov_id_builder.sign(&signer)?;
let signer = Signer(SecretKey(2u64.into()));
@ -850,9 +875,8 @@ pub mod tests {
#[test]
fn test_mini_0() {
let params = middleware::Params {
max_input_signed_pods: 1,
max_input_recursive_pods: 1,
max_signed_pod_values: 6,
max_signed_by: 1,
max_input_pods: 1,
max_statements: 8,
max_public_statements: 4,
max_input_pods_public_statements: 10,
@ -862,7 +886,7 @@ pub mod tests {
vds.push(rec_main_pod_circuit_data(&params).1.verifier_only.clone());
let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap();
let mut gov_id_builder = frontend::SignedPodBuilder::new(&params);
let mut gov_id_builder = frontend::SignedDictBuilder::new(&params);
gov_id_builder.insert("idNumber", "4242424242");
gov_id_builder.insert("dateOfBirth", 1169909384);
gov_id_builder.insert("socialSecurityNumber", "G2121210");
@ -870,7 +894,10 @@ pub mod tests {
let gov_id = gov_id_builder.sign(&signer).unwrap();
let now_minus_18y: i64 = 1169909388;
let mut kyc_builder = frontend::MainPodBuilder::new(&params, &vd_set);
kyc_builder.add_signed_pod(&gov_id);
kyc_builder
.priv_op(frontend::Operation::dict_signed_by(&gov_id))
.unwrap();
kyc_builder
.pub_op(frontend::Operation::lt(
(&gov_id, "dateOfBirth"),
@ -902,9 +929,8 @@ pub mod tests {
#[test]
fn test_mini_1() {
let params = middleware::Params {
max_input_signed_pods: 0,
max_input_recursive_pods: 0,
max_signed_pod_values: 0,
max_signed_by: 0,
max_input_pods: 0,
max_statements: 2,
max_public_statements: 1,
max_input_pods_public_statements: 0,
@ -939,13 +965,12 @@ pub mod tests {
#[test]
fn test_mainpod_small_empty() {
let params = middleware::Params {
max_input_signed_pods: 0,
max_input_recursive_pods: 0,
max_signed_by: 0,
max_input_pods: 0,
max_input_pods_public_statements: 2,
max_statements: 5,
max_signed_pod_values: 2,
max_public_statements: 2,
num_public_statements_id: 4,
num_public_statements_hash: 4,
max_statement_args: 4,
max_operation_args: 4,
max_custom_predicate_batches: 2,
@ -996,7 +1021,7 @@ pub mod tests {
let alice_attestation = attest_eth_friend(&params, &alice, bob.public_key());
let bob_attestation = attest_eth_friend(&params, &bob, charlie.public_key());
let helper = EthDosHelper::new(&params, vd_set, false, alice.public_key())?;
let helper = EthDosHelper::new(&params, vd_set, alice.public_key())?;
let prover = Prover {};
let dist_1 = helper.dist_1(&alice_attestation)?.prove(&prover)?;
crate::measure_gates_print!();
@ -1010,8 +1035,8 @@ pub mod tests {
#[test]
fn test_main_mini_custom_1() -> frontend::Result<()> {
let params = Params {
max_input_signed_pods: 0,
max_input_recursive_pods: 0,
max_signed_by: 0,
max_input_pods: 0,
max_statements: 9,
max_public_statements: 4,
max_statement_args: 4,
@ -1020,7 +1045,7 @@ pub mod tests {
max_custom_batch_size: 3,
max_custom_predicate_wildcards: 4,
max_custom_predicate_verifications: 2,
max_merkle_proofs_containers: 0,
max_merkle_proofs_containers: 3,
max_merkle_tree_state_transition_proofs_containers: 0,
..Default::default()
};
@ -1030,17 +1055,20 @@ pub mod tests {
let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap();
let mut cpb_builder = CustomPredicateBatchBuilder::new(params.clone(), "cpb".into());
let stb0 = STB::new(NP::Equal).arg(("id", "score")).arg(literal(42));
let stb0 = STB::new(NP::Contains)
.arg("dict")
.arg(literal("score"))
.arg(literal(42));
let stb1 = STB::new(NP::Equal)
.arg(("secret_id", "key"))
.arg(("id", "score"));
.arg(("secret_dict", "key"))
.arg(("dict", "score"));
let _ = cpb_builder.predicate_and(
"pred_and",
&["id"],
&["secret_id"],
&["dict"],
&["secret_dict"],
&[stb0.clone(), stb1.clone()],
)?;
let _ = cpb_builder.predicate_or("pred_or", &["id"], &["secret_id"], &[stb0, stb1])?;
let _ = cpb_builder.predicate_or("pred_or", &["dict"], &["secret_dict"], &[stb0, stb1])?;
let cpb = cpb_builder.finish();
let cpb_and = CustomPredicateRef::new(cpb.clone(), 0);
@ -1048,9 +1076,17 @@ pub mod tests {
let mut pod_builder = MainPodBuilder::new(&params, &vd_set);
let st0 = pod_builder.priv_op(frontend::Operation::new_entry("score", 42))?;
let st1 = pod_builder.priv_op(frontend::Operation::new_entry("key", 42))?;
let st2 = pod_builder.priv_op(frontend::Operation::eq(st1.clone(), st0.clone()))?;
let dict = dict!(32, {"score" => 42})?;
let secret_dict = dict!(32, {"key" => 42})?;
let st0 = pod_builder.priv_op(frontend::Operation::dict_contains(
dict.clone(),
"score",
42,
))?;
let st2 = pod_builder.priv_op(frontend::Operation::eq(
(&secret_dict.clone(), "key"),
(&dict, "score"),
))?;
let _st3 = pod_builder.priv_op(frontend::Operation::custom(cpb_and.clone(), [st0, st2]))?;
@ -1071,15 +1107,9 @@ pub mod tests {
fn test_set_contains() -> frontend::Result<()> {
let params = Params::default();
let mut builder = MainPodBuilder::new(&params, &DEFAULT_VD_SET);
let set = [1, 2, 3].into_iter().map(|n| n.into()).collect();
let st = builder
.pub_op(frontend::Operation::new_entry(
"entry",
Set::new(params.max_depth_mt_containers, set).unwrap(),
))
.unwrap();
builder.pub_op(frontend::Operation::set_contains(st, 1))?;
let set: HashSet<_> = [1, 2, 3].into_iter().map(|n| n.into()).collect();
let set = Set::new(params.max_depth_mt_containers, set).unwrap();
builder.pub_op(frontend::Operation::set_contains(set, 1))?;
let prover = Prover {};
let proof = builder.prove(&prover).unwrap();

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
error::{Error, Result},
mainpod::Statement,
mainpod::{SignedBy, Statement},
primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
},
middleware::{self, OperationType, Params},
@ -35,6 +35,7 @@ pub enum OperationAux {
None,
MerkleProofIndex(usize),
PublicKeyOfIndex(usize),
SignedByIndex(usize),
MerkleTreeStateTransitionProofIndex(usize),
CustomPredVerifyIndex(usize),
}
@ -47,9 +48,12 @@ impl OperationAux {
fn table_offset_public_key_of(params: &Params) -> usize {
Self::table_offset_merkle_proof(params) + params.max_merkle_proofs_containers
}
fn table_offset_merkle_tree_state_transition_proof(params: &Params) -> usize {
fn table_offset_signed_by(params: &Params) -> usize {
Self::table_offset_public_key_of(params) + params.max_public_key_of
}
fn table_offset_merkle_tree_state_transition_proof(params: &Params) -> usize {
Self::table_offset_signed_by(params) + params.max_signed_by
}
fn table_offset_custom_pred_verify(params: &Params) -> usize {
Self::table_offset_merkle_tree_state_transition_proof(params)
+ params.max_merkle_tree_state_transition_proofs_containers
@ -57,6 +61,7 @@ impl OperationAux {
pub(crate) fn table_size(params: &Params) -> usize {
1 + params.max_merkle_proofs_containers
+ params.max_public_key_of
+ params.max_signed_by
+ params.max_merkle_tree_state_transition_proofs_containers
+ params.max_custom_predicate_verifications
}
@ -65,6 +70,7 @@ impl OperationAux {
Self::None => 0,
Self::MerkleProofIndex(i) => Self::table_offset_merkle_proof(params) + *i,
Self::PublicKeyOfIndex(i) => Self::table_offset_public_key_of(params) + *i,
Self::SignedByIndex(i) => Self::table_offset_signed_by(params) + *i,
Self::MerkleTreeStateTransitionProofIndex(i) => {
Self::table_offset_merkle_tree_state_transition_proof(params) + *i
}
@ -89,6 +95,7 @@ impl Operation {
pub fn deref(
&self,
statements: &[Statement],
signatures: &[SignedBy],
merkle_proofs: &[MerkleClaimAndProof],
merkle_tree_state_transition_proofs: &[MerkleTreeStateTransitionProof],
) -> Result<crate::middleware::Operation> {
@ -125,6 +132,13 @@ impl Operation {
.clone(),
)
}
OperationAux::SignedByIndex(i) => crate::middleware::OperationAux::Signature(
signatures
.get(i)
.ok_or(Error::custom(format!("Missing SignedBy data index {}", i)))?
.sig
.clone(),
),
OperationAux::PublicKeyOfIndex(_) => crate::middleware::OperationAux::None,
};
Ok(middleware::Operation::op(
@ -154,6 +168,7 @@ impl fmt::Display for Operation {
OperationAux::MerkleProofIndex(i) => write!(f, " merkle_proof_{:02}", i)?,
OperationAux::CustomPredVerifyIndex(i) => write!(f, " custom_pred_verify_{:02}", i)?,
OperationAux::PublicKeyOfIndex(i) => write!(f, " public_key_of_{:02}", i)?,
OperationAux::SignedByIndex(i) => write!(f, " signed_by_{:02}", i)?,
OperationAux::MerkleTreeStateTransitionProofIndex(i) => {
write!(f, " merkle_tree_state_transition_proof_{:02}", i)?
}

View file

@ -74,6 +74,7 @@ impl TryFrom<Statement> for middleware::Statement {
S::HashOf(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
(NP::PublicKeyOf, &[a1, a2]) => S::PublicKeyOf(a1.try_into()?, a2.try_into()?),
(NP::SignedBy, &[a1, a2]) => S::SignedBy(a1.try_into()?, a2.try_into()?),
(NP::ContainerInsert, &[a1, a2, a3, a4]) => S::ContainerInsert(
a1.try_into()?,
a2.try_into()?,
@ -105,6 +106,17 @@ impl TryFrom<Statement> for middleware::Statement {
.collect();
S::Custom(cpr, vs)
}
Predicate::Intro(ir) => {
let vs: Vec<Value> = proper_args
.into_iter()
.filter_map(|arg| match arg {
SA::None => None,
SA::Literal(v) => Some(v),
_ => unreachable!(),
})
.collect();
S::Intro(ir, vs)
}
Predicate::BatchSelf(_) => {
unreachable!()
}
@ -123,6 +135,10 @@ impl From<middleware::Statement> for Statement {
middleware::Predicate::Custom(cpr),
s.args().into_iter().collect(),
),
middleware::Predicate::Intro(ir) => Statement(
middleware::Predicate::Intro(ir),
s.args().into_iter().collect(),
),
middleware::Predicate::BatchSelf(_) => unreachable!(),
}
}

View file

@ -4,35 +4,36 @@ use crate::{
backends::plonky2::{
basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result},
mainpod::{self, calculate_id},
},
middleware::{
AnchoredKey, Params, Pod, PodId, PodType, RecursivePod, Statement, VDSet, Value, KEY_TYPE,
SELF,
mainpod::{self, calculate_statements_hash},
},
middleware::{Hash, IntroPredicateRef, Params, Pod, PodType, Statement, VDSet, EMPTY_HASH},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MockEmptyPod {
params: Params,
id: PodId,
sts_hash: Hash,
vd_set: VDSet,
}
fn type_statement() -> Statement {
Statement::equal(
AnchoredKey::from((SELF, KEY_TYPE)),
Value::from(PodType::Empty),
fn empty_statement() -> Statement {
Statement::Intro(
IntroPredicateRef {
name: "mock_empty".to_string(),
args_len: 0,
verifier_data_hash: EMPTY_HASH,
},
vec![],
)
}
impl MockEmptyPod {
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn RecursivePod> {
let statements = [mainpod::Statement::from(type_statement())];
let id = PodId(calculate_id(&statements, params));
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn Pod> {
let statements = [mainpod::Statement::from(empty_statement())];
let sts_hash = calculate_statements_hash(&statements, params);
Box::new(Self {
params: params.clone(),
id,
sts_hash,
vd_set,
})
}
@ -48,28 +49,25 @@ impl Pod for MockEmptyPod {
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let id = PodId(calculate_id(&statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
let sts_hash = calculate_statements_hash(&statements, &self.params);
if sts_hash != self.sts_hash {
return Err(Error::statements_hash_not_equal(self.sts_hash, sts_hash));
}
Ok(())
}
fn id(&self) -> PodId {
self.id
fn statements_hash(&self) -> Hash {
self.sts_hash
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::MockEmpty as usize, "MockEmpty")
}
fn pub_self_statements(&self) -> Vec<Statement> {
vec![type_statement()]
vec![empty_statement()]
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::Value::Null
fn verifier_data_hash(&self) -> Hash {
EMPTY_HASH
}
}
impl RecursivePod for MockEmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
panic!("MockEmptyPod can't be verified in a recursive MainPod circuit");
}
@ -82,13 +80,20 @@ impl RecursivePod for MockEmptyPod {
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::Value::Null
}
fn deserialize_data(
params: Params,
_data: serde_json::Value,
vd_set: VDSet,
id: PodId,
) -> Result<Box<dyn RecursivePod>> {
Ok(Box::new(Self { params, id, vd_set }))
id: Hash,
) -> Result<Self> {
Ok(Self {
params,
sts_hash: id,
vd_set,
})
}
}

View file

@ -4,7 +4,6 @@
use std::{fmt, iter};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::{
@ -12,31 +11,25 @@ use crate::{
basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result},
mainpod::{
calculate_id, extract_merkle_proofs, extract_merkle_tree_state_transition_proofs,
layout_statements, process_private_statements_operations,
process_public_statements_operations, Operation, OperationAux, Statement,
calculate_statements_hash, extract_merkle_proofs,
extract_merkle_tree_state_transition_proofs, extract_signatures, layout_statements,
process_private_statements_operations, process_public_statements_operations, Operation,
OperationAux, SignedBy, Statement,
},
mock::emptypod::MockEmptyPod,
primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
recursion::hash_verifier_data,
signedpod::SignedPod,
},
middleware::{
self, deserialize_pod, deserialize_signed_pod, hash_str, AnchoredKey, Hash, MainPodInputs,
NativeOperation, NativePredicate, OperationType, Params, Pod, PodId, PodProver, PodType,
Predicate, RecursivePod, StatementArg, VDSet, Value, KEY_TYPE, SELF,
self, deserialize_pod, Hash, MainPodInputs, MainPodProver, Params, Pod, PodType, VDSet,
EMPTY_HASH,
},
};
pub struct MockProver {}
impl PodProver for MockProver {
fn prove(
&self,
params: &Params,
_vd_set: &VDSet,
inputs: MainPodInputs,
) -> Result<Box<dyn RecursivePod>> {
impl MainPodProver for MockProver {
fn prove(&self, params: &Params, inputs: MainPodInputs) -> Result<Box<dyn Pod>> {
Ok(Box::new(MockMainPod::new(params, inputs)?))
}
}
@ -44,10 +37,9 @@ impl PodProver for MockProver {
#[derive(Clone, Debug, PartialEq)]
pub struct MockMainPod {
params: Params,
id: PodId,
sts_hash: Hash,
vd_set: VDSet,
input_signed_pods: Vec<Box<dyn Pod>>,
input_recursive_pods: Vec<Box<dyn RecursivePod>>,
input_pods: Vec<Box<dyn Pod>>,
// All statements (inherited + newly introduced by this pod)
statements: Vec<Statement>,
operations: Vec<Operation>,
@ -57,42 +49,28 @@ pub struct MockMainPod {
merkle_proofs_containers: Vec<MerkleClaimAndProof>,
// All Merkle tree state transition proofs
merkle_tree_state_transition_proofs_containers: Vec<MerkleTreeStateTransitionProof>,
// All verified signatures
signatures: Vec<SignedBy>,
}
impl Eq for MockMainPod {}
impl fmt::Display for MockMainPod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "MockMainPod ({}):", self.id)?;
let offset_input_signed_pods = Self::offset_input_signed_pods();
let offset_input_recursive_pods = self.offset_input_recursive_pods();
writeln!(f, "MockMainPod ({}):", self.sts_hash)?;
let offset_input_pods = self.offset_input_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 self.params.max_input_signed_pods > 0
&& (i >= offset_input_signed_pods && i < offset_input_recursive_pods)
&& (i - offset_input_signed_pods).is_multiple_of(self.params.max_signed_pod_values)
{
let index = (i - offset_input_signed_pods) / self.params.max_signed_pod_values;
let pod = &self.input_signed_pods[index];
let id = pod.id();
let pod_type = pod.pod_type();
writeln!(
f,
" from input SignedPod {} (id={}, type={:?}):",
index, id, pod_type
)?;
}
if self.params.max_input_recursive_pods > 0
&& (i >= offset_input_recursive_pods)
if self.params.max_input_pods > 0
&& (i >= offset_input_pods)
&& (i < offset_input_statements)
&& (i - offset_input_recursive_pods)
&& (i - offset_input_pods)
.is_multiple_of(self.params.max_input_pods_public_statements)
{
let index = (i - offset_input_recursive_pods)
/ self.params.max_input_pods_public_statements;
let pod = &self.input_recursive_pods[index];
let id = pod.id();
let index = (i - offset_input_pods) / self.params.max_input_pods_public_statements;
let pod = &self.input_pods[index];
let id = pod.statements_hash();
let pod_type = pod.pod_type();
writeln!(
f,
@ -148,26 +126,21 @@ struct Data {
statements: Vec<Statement>,
merkle_proofs: Vec<MerkleClaimAndProof>,
merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProof>,
input_signed_pods: Vec<(usize, PodId, serde_json::Value)>,
input_recursive_pods: Vec<(usize, Params, PodId, VDSet, serde_json::Value)>,
signatures: Vec<SignedBy>,
input_pods: Vec<(usize, Params, Hash, VDSet, serde_json::Value)>,
}
/// Inputs are sorted as:
/// - SignedPods
/// - MainPods
/// - Pods
/// - private Statements
/// - public Statements
impl MockMainPod {
fn offset_input_signed_pods() -> usize {
fn offset_input_pods(&self) -> usize {
1
}
fn offset_input_recursive_pods(&self) -> usize {
Self::offset_input_signed_pods()
+ self.params.max_input_signed_pods * self.params.max_signed_pod_values
}
fn offset_input_statements(&self) -> usize {
self.offset_input_recursive_pods()
+ self.params.max_input_recursive_pods * self.params.max_input_pods_public_statements
self.offset_input_pods()
+ self.params.max_input_pods * self.params.max_input_pods_public_statements
}
fn offset_public_statements(&self) -> usize {
self.offset_input_statements() + self.params.max_priv_statements()
@ -175,6 +148,7 @@ impl MockMainPod {
pub fn new(params: &Params, inputs: MainPodInputs) -> Result<Self> {
let (statements, public_statements) = layout_statements(params, true, &inputs)?;
dbg!(public_statements.len());
let mut aux_list = vec![OperationAux::None; params.max_priv_statements()];
// Extract Merkle proofs and pad.
let merkle_proofs =
@ -182,6 +156,8 @@ impl MockMainPod {
// Similarly for Merkle state transition proofs.
let merkle_tree_state_transition_proofs =
extract_merkle_tree_state_transition_proofs(params, &mut aux_list, inputs.operations)?;
let signatures =
extract_signatures(params, &mut aux_list, inputs.operations, inputs.statements)?;
let operations = process_private_statements_operations(
params,
@ -192,35 +168,27 @@ impl MockMainPod {
let operations = process_public_statements_operations(params, &statements, operations)?;
// get the id out of the public statements
let id: PodId = PodId(calculate_id(&public_statements, params));
let sts_hash = calculate_statements_hash(&public_statements, params);
let pad_signed_pod: Box<dyn Pod> = Box::new(SignedPod::dummy());
let input_signed_pods: Vec<Box<dyn Pod>> = inputs
.signed_pods
.iter()
.map(|p| dyn_clone::clone_box(*p))
.chain(iter::repeat_with(|| pad_signed_pod.clone()))
.take(params.max_input_signed_pods)
.collect();
let pad_pod = MockEmptyPod::new_boxed(params, inputs.vd_set.clone());
let input_recursive_pods: Vec<Box<dyn RecursivePod>> = inputs
.recursive_pods
let input_pods: Vec<Box<dyn Pod>> = inputs
.pods
.iter()
.map(|p| dyn_clone::clone_box(*p))
.chain(iter::repeat_with(|| pad_pod.clone()))
.take(params.max_input_recursive_pods)
.take(params.max_input_pods)
.collect();
Ok(Self {
params: params.clone(),
id,
sts_hash,
vd_set: inputs.vd_set,
input_signed_pods,
input_recursive_pods,
input_pods,
public_statements,
statements,
operations,
merkle_proofs_containers: merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs,
signatures,
})
}
@ -233,13 +201,15 @@ impl Pod for MockMainPod {
fn params(&self) -> &Params {
&self.params
}
fn is_mock(&self) -> bool {
true
}
fn is_main(&self) -> bool {
true
}
fn verify(&self) -> Result<()> {
// 1. Verify input pods
for pod in &self.input_signed_pods {
pod.verify()?;
}
for pod in &self.input_recursive_pods {
for pod in &self.input_pods {
pod.verify()?;
if pod.vd_set().root() != self.vd_set.root() {
return Err(Error::custom(format!(
@ -248,67 +218,38 @@ impl Pod for MockMainPod {
self.vd_set.root(),
)));
}
let (pod_type, _) = pod.pod_type();
// If the pod is not mock, check that its verifier data is in the set
if pod_type != PodType::MockMain as usize && pod_type != PodType::MockEmpty as usize {
// If the pod is not mock and main (MainPod family) check that its verifier data is in
// the set
if !pod.is_mock() && pod.is_main() {
let verifier_data = pod.verifier_data();
let verifier_data_hash = hash_verifier_data(&verifier_data);
if !self.vd_set.contains(verifier_data_hash) {
return Err(Error::custom(format!(
"vds_root in input recursive pod not in the set: {} not in {}",
"vds_root in input recursive MainPod not in the set: {} not in {}",
Hash(verifier_data_hash.elements),
self.vd_set.root(),
)));
}
}
// Introduction pods can only have Introduction or None statements
if !pod.is_main() {
for self_st in pod.pub_self_statements() {
match self_st {
middleware::Statement::None | middleware::Statement::Intro(_, _) => {}
_ => {
return Err(Error::custom(format!(
"Introduction Pod has a non-introduction statement: {}",
self_st,
)))
}
}
}
}
}
let input_statement_offset = self.offset_input_statements();
// get the input_statements from the self.statements
let input_statements = &self.statements[input_statement_offset..];
// 2. get the id out of the public statements, and ensure it is equal to self.id
if self.id != PodId(calculate_id(&self.public_statements, &self.params)) {
return Err(Error::pod_id_invalid());
}
// 4. Verify type
// find a ValueOf statement from the public statements with key=KEY_TYPE and check that the
// value is PodType::MockMainPod
let type_statement = &self.public_statements[0];
let type_statement_ok = type_statement.0 == Predicate::Native(NativePredicate::Equal)
&& {
if let [StatementArg::Key(AnchoredKey { pod_id, ref key }), StatementArg::Literal(pod_type)] =
&type_statement.1[..2]
{
pod_id == &SELF
&& key.hash() == hash_str(KEY_TYPE)
&& *pod_type == Value::from(PodType::MockMain)
} else {
false
}
};
if !type_statement_ok {
return Err(Error::not_type_statement());
}
// 3. check that all `NewEntry` operations have unique keys
// (no duplicates)
let value_ofs_unique = input_statements
.iter()
.zip(self.operations.iter())
.filter_map(|(s, o)| {
if matches!(o.0, OperationType::Native(NativeOperation::NewEntry)) {
match s.1.get(0) {
Some(StatementArg::Key(k)) => Some(k),
// malformed NewEntry operations are caught in step 5
_ => None,
}
} else {
None
}
})
.all_unique();
if !value_ofs_unique {
return Err(Error::repeated_value_of());
}
// 5. verify that all `input_statements` are correctly generated
// by `self.operations` (where each operation can only access previous statements)
@ -319,6 +260,7 @@ impl Pod for MockMainPod {
self.operations[i]
.deref(
&self.statements[..input_statement_offset + i],
&self.signatures,
&self.merkle_proofs_containers,
&self.merkle_tree_state_transition_proofs_containers,
)?
@ -332,8 +274,8 @@ impl Pod for MockMainPod {
Ok(())
}
fn id(&self) -> PodId {
self.id
fn statements_hash(&self) -> Hash {
self.sts_hash
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::MockMain as usize, "MockMain")
@ -346,20 +288,31 @@ impl Pod for MockMainPod {
.collect()
}
fn verifier_data_hash(&self) -> Hash {
EMPTY_HASH
}
fn verifier_data(&self) -> VerifierOnlyCircuitData {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn common_hash(&self) -> String {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn proof(&self) -> Proof {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
fn serialize_data(&self) -> serde_json::Value {
let input_signed_pods = self
.input_signed_pods
.iter()
.map(|p| (p.pod_type().0, p.id(), p.serialize_data()))
.collect();
let input_recursive_pods = self
.input_recursive_pods
let input_pods = self
.input_pods
.iter()
.map(|p| {
(
p.pod_type().0,
p.params().clone(),
p.id(),
p.statements_hash(),
p.vd_set().clone(),
p.serialize_data(),
)
@ -373,26 +326,11 @@ impl Pod for MockMainPod {
merkle_tree_state_transition_proofs: self
.merkle_tree_state_transition_proofs_containers
.clone(),
input_signed_pods,
input_recursive_pods,
signatures: self.signatures.clone(),
input_pods,
})
.expect("serialization to json")
}
}
impl RecursivePod for MockMainPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn common_hash(&self) -> String {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn proof(&self) -> Proof {
panic!("MockMainPod can't be verified in a recursive MainPod circuit");
}
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
// MockMainPods include some internal private state which is necessary
// for verification. In non-mock Pods, this state will not be necessary,
// as the public statements can be verified using a ZK proof.
@ -400,39 +338,35 @@ impl RecursivePod for MockMainPod {
params: Params,
data: serde_json::Value,
vd_set: VDSet,
id: PodId,
) -> Result<Box<dyn RecursivePod>> {
id: Hash,
) -> Result<Self> {
let Data {
public_statements,
operations,
statements,
merkle_proofs,
merkle_tree_state_transition_proofs,
input_signed_pods,
input_recursive_pods,
signatures,
input_pods,
} = serde_json::from_value(data)?;
let input_signed_pods = input_signed_pods
.into_iter()
.map(|(pod_type, id, data)| deserialize_signed_pod(pod_type, id, data))
.collect::<Result<Vec<_>>>()?;
let input_recursive_pods = input_recursive_pods
let input_pods = input_pods
.into_iter()
.map(|(pod_type, params, id, vd_set, data)| {
deserialize_pod(pod_type, params, id, vd_set, data)
})
.collect::<Result<Vec<_>>>()?;
Ok(Box::new(Self {
Ok(Self {
params,
id,
sts_hash: id,
vd_set,
input_signed_pods,
input_recursive_pods,
input_pods,
public_statements,
operations,
statements,
merkle_proofs_containers: merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs,
}))
signatures,
})
}
}
@ -442,23 +376,24 @@ pub mod tests {
use super::*;
use crate::{
backends::plonky2::{primitives::ec::schnorr::SecretKey, signedpod::Signer},
backends::plonky2::{primitives::ec::schnorr::SecretKey, signer::Signer},
examples::{
great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_pod_request,
zu_kyc_sign_pod_builders, MOCK_VD_SET,
zu_kyc_sign_dict_builders, MOCK_VD_SET,
},
frontend, middleware,
middleware::{Signer as _, Value},
};
#[test]
fn test_mock_main_zu_kyc() -> frontend::Result<()> {
let params = middleware::Params::default();
let vd_set = &*MOCK_VD_SET;
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_pod_builders(&params);
let signer = Signer(SecretKey(1u32.into()));
let gov_id_pod = gov_id_builder.sign(&signer)?;
let signer = Signer(SecretKey(2u32.into()));
let pay_stub_pod = pay_stub_builder.sign(&signer)?;
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(&params);
let gov_id_signer = Signer(SecretKey(1u32.into()));
let gov_id_pod = gov_id_builder.sign(&gov_id_signer)?;
let pay_stub_signer = Signer(SecretKey(2u32.into()));
let pay_stub_pod = pay_stub_builder.sign(&pay_stub_signer)?;
let kyc_builder = zu_kyc_pod_builder(&params, vd_set, &gov_id_pod, &pay_stub_pod)?;
let prover = MockProver {};
@ -472,8 +407,8 @@ pub mod tests {
pod.verify()?;
let request = zu_kyc_pod_request(
gov_id_pod.get("_signer").unwrap(),
pay_stub_pod.get("_signer").unwrap(),
&Value::from(gov_id_signer.public_key()),
&Value::from(pay_stub_signer.public_key()),
)?;
assert!(request.exact_match_pod(&*pod).is_ok());

View file

@ -7,7 +7,7 @@ pub mod mock;
pub mod primitives;
pub mod recursion;
pub mod serialization;
pub mod signedpod;
pub mod signer;
use std::iter;
@ -49,7 +49,7 @@ pub fn cache_get_standard_rec_main_pod_common_circuit_data(
let circuit_data = timed!(
"recursive MainPod circuit_data",
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data(
params.max_input_recursive_pods,
params.max_input_pods,
NUM_PUBLIC_INPUTS,
params
)

View file

@ -24,6 +24,7 @@ use plonky2::{
util::serialization::{Read, Write},
};
use rand::rngs::OsRng;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::backends::plonky2::{
@ -102,6 +103,16 @@ pub struct Point {
pub u: ECField,
}
impl JsonSchema for Point {
fn schema_name() -> String {
"Point".to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<String>::json_schema(gen)
}
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(clippy::collapsible_else_if)]

View file

@ -20,6 +20,7 @@ use plonky2::{
plonk::circuit_builder::CircuitBuilder,
};
use rand::rngs::OsRng;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::curve::Point;
@ -43,6 +44,16 @@ pub struct Signature {
pub e: BigUint,
}
impl JsonSchema for Signature {
fn schema_name() -> String {
"Signature".to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<String>::json_schema(gen)
}
}
impl Signature {
pub fn verify(&self, public_key: Point, msg: RawValue) -> bool {
let r = &self.s * Point::generator() + &self.e * public_key;

View file

@ -1,246 +0,0 @@
use std::{collections::HashMap, sync::LazyLock};
use itertools::Itertools;
use num_bigint::{BigUint, RandBigInt};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
error::{Error, Result},
primitives::{
ec::{
curve::{Point, GROUP_ORDER},
schnorr::{SecretKey, Signature},
},
merkletree::MerkleTree,
},
},
middleware::{
containers::Dictionary, AnchoredKey, Hash, Key, Params, Pod, PodId, PodSigner, PodType,
RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
},
timed,
};
pub struct Signer(pub SecretKey);
impl Signer {
fn sign_with_nonce(
&self,
params: &Params,
nonce: BigUint,
kvs: &HashMap<Key, Value>,
) -> Result<SignedPod> {
let mut kvs = kvs.clone();
let pubkey = self.0.public_key();
kvs.insert(Key::from(KEY_SIGNER), Value::from(pubkey));
kvs.insert(Key::from(KEY_TYPE), Value::from(PodType::Signed));
let dict = Dictionary::new(params.max_depth_mt_containers, kvs)?;
let id = RawValue::from(dict.commitment()); // PodId as Value
let signature: Signature = timed!("SignedPod::sign", self.0.sign(id, &nonce));
Ok(SignedPod {
id: PodId(Hash::from(id)),
signature,
signer: pubkey,
dict,
})
}
fn _sign(&self, params: &Params, kvs: &HashMap<Key, Value>) -> Result<SignedPod> {
let nonce = OsRng.gen_biguint_below(&GROUP_ORDER);
self.sign_with_nonce(params, nonce, kvs)
}
pub fn public_key(&self) -> Value {
Value::from(self.0.public_key())
}
}
impl PodSigner for Signer {
fn sign(&self, params: &Params, kvs: &HashMap<Key, Value>) -> Result<Box<dyn Pod>> {
Ok(self._sign(params, kvs).map(Box::new)?)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedPod {
pub id: PodId,
pub signature: Signature,
pub signer: Point,
pub dict: Dictionary,
}
#[derive(Serialize, Deserialize)]
struct Data {
signer: Point,
signature: Signature,
kvs: Dictionary,
}
static DUMMY_POD: LazyLock<SignedPod> = LazyLock::new(dummy);
fn dummy() -> SignedPod {
let nonce = BigUint::from(2u32);
Signer(SecretKey(BigUint::from(1u32)))
.sign_with_nonce(&Params::default(), nonce, &HashMap::new())
.expect("valid")
}
impl SignedPod {
pub(crate) fn deserialize(id: PodId, data: serde_json::Value) -> Result<Box<dyn Pod>> {
let data: Data = serde_json::from_value(data)?;
Ok(Box::new(Self {
id,
signature: data.signature,
signer: data.signer,
dict: data.kvs,
}))
}
/// Generate a valid SignedPod with a public deterministic secret key and nonce and no other
/// key-values than the default ones. This is used for padding.
pub fn dummy() -> SignedPod {
DUMMY_POD.clone()
}
}
impl Pod for SignedPod {
fn params(&self) -> &Params {
panic!("SignedPod doesn't have params");
}
fn verify(&self) -> Result<()> {
// 1. Verify type
let value_at_type = self.dict.get(&Key::from(KEY_TYPE))?;
if Value::from(PodType::Signed) != *value_at_type {
return Err(Error::type_not_equal(
PodType::Signed,
value_at_type.clone(),
));
}
// 2. Verify id
let mt = MerkleTree::new(
self.dict.max_depth(),
&self
.dict
.kvs()
.iter()
.map(|(k, v)| (k.raw(), v.raw()))
.collect::<HashMap<RawValue, RawValue>>(),
)?;
let id = PodId(mt.root());
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
}
// 3. Verify signature
let embedded_pk_value = self.dict.get(&Key::from(KEY_SIGNER))?;
let pk = self.signer;
let pk_value = Value::from(pk);
if &pk_value != embedded_pk_value {
return Err(Error::signer_not_equal(embedded_pk_value.clone(), pk_value));
}
self.signature
.verify(pk, RawValue::from(id.0))
.then_some(())
.ok_or(Error::custom("Invalid signature!".into()))
}
fn id(&self) -> PodId {
self.id
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::Signed as usize, "Signed")
}
fn pub_self_statements(&self) -> Vec<Statement> {
// By convention we put the KEY_TYPE first and KEY_SIGNER second
let mut kvs: HashMap<Key, Value> = self.dict.kvs().clone();
let key_type = Key::from(KEY_TYPE);
let value_type = kvs.remove(&key_type).expect("KEY_TYPE");
let key_signer = Key::from(KEY_SIGNER);
let value_signer = kvs.remove(&key_signer).expect("KEY_SIGNER");
[(key_type, value_type), (key_signer, value_signer)]
.into_iter()
.chain(kvs.into_iter().sorted_by_key(|kv| kv.0.hash()))
.map(|(k, v)| Statement::equal(AnchoredKey::from((SELF, k)), v))
.collect()
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
signer: self.signer,
signature: self.signature.clone(),
kvs: self.dict.clone(),
})
.expect("serialization to json")
}
}
#[cfg(test)]
pub mod tests {
use std::{any::Any, iter};
use plonky2::field::types::Field;
use super::*;
use crate::{
frontend,
middleware::{self, EMPTY_VALUE, 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(123u64.into());
let signer = Signer(sk);
let pod = pod.sign(&signer).unwrap();
let pod = (pod.pod as Box<dyn Any>).downcast::<SignedPod>().unwrap();
pod.verify()?;
println!("id: {}", pod.id());
println!("kvs: {:?}", pod.kvs());
let mut bad_pod = pod.clone();
let nonce = 456u64.into();
bad_pod.signature = signer.0.sign(RawValue::from(42_i64), &nonce);
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 = (Key::from(KEY_SIGNER), Value::from(EMPTY_VALUE));
let bad_kvs = bad_pod
.dict
.kvs()
.clone()
.into_iter()
.chain(iter::once(bad_kv))
.collect::<HashMap<Key, Value>>();
bad_pod.dict = Dictionary::new(params.max_depth_mt_containers, bad_kvs).unwrap();
assert!(bad_pod.verify().is_err());
let mut bad_pod = pod.clone();
let bad_kv = (Key::from(KEY_TYPE), Value::from(0));
let bad_kvs = bad_pod
.dict
.kvs()
.clone()
.into_iter()
.chain(iter::once(bad_kv))
.collect::<HashMap<Key, Value>>();
bad_pod.dict = Dictionary::new(params.max_depth_mt_containers, bad_kvs).unwrap();
assert!(bad_pod.verify().is_err());
Ok(())
}
}

View file

@ -0,0 +1,31 @@
use num_bigint::{BigUint, RandBigInt};
use rand::rngs::OsRng;
use crate::{
backends::plonky2::primitives::ec::{
curve::{Point as PublicKey, GROUP_ORDER},
schnorr::{SecretKey, Signature},
},
middleware::{self, RawValue},
timed,
};
pub struct Signer(pub SecretKey);
impl Signer {
pub(crate) fn sign_with_nonce(&self, nonce: BigUint, msg: RawValue) -> Signature {
let signature: Signature = timed!("SignedPod::sign", self.0.sign(msg, &nonce));
signature
}
}
impl middleware::Signer for Signer {
fn sign(&self, msg: RawValue) -> Signature {
let nonce = OsRng.gen_biguint_below(&GROUP_ORDER);
self.sign_with_nonce(nonce, msg)
}
fn public_key(&self) -> PublicKey {
self.0.public_key()
}
}