Feat/disk cache (#354)

- Bump rust version to `nightly-2025-07-02` because some of the nightly features we were using have been stabilized.
- Introduce feature `disk_cache` which enables caching to disk.  Each time an artifact is retrieved from the cache it will be read and deserialized.  On a cache miss the artifact will be created, serialized and stored to disk.
- Introduce feature `mem_cache` which enables caching to memory.  All cached artifacts are kept in memory after they are created.  The mem cache implementation avoids cloning of artifacts by extending their lifetime to `'static`.  This is `unsafe` code, but I argue that this usage is safe.
- Add a `build.rs`
  - When the feature `disk_cache` is enabled, the `build.rs` will inject env variables to the process with the git commit information, which is used to index the cached artifacts
- Replace all previous cached artifacts from `LazyStatic` methods that call the cache API
- Derive `Serialize, Deserialize` for all `*Target` types so that they can be serialized for caching to disk
- Add finer level of caching: now we cache the `CircuitData` and `VerifierData` independently.  The reason for this is that `CircuitData` is a very big artifact which is not needed for verification.  So by only accessing `VerifierData` in verification we don't pay a big overhead for reading from disk and deserializing
- Add missing artifacts to the cache: like the `CircuitData` for the `MainPod` indexed by `Params`
- Add helper types to serialize and deserialize `CircuitData`, `CommonData` and `VerifierData` with the set of gates and generators used in the recursive MainPod circuit
- Tweak the ids of our custom gates so that they remain unique when their generic parameters change
- Bugfix: several tests were using the standard `vd_set` but were using MainPod circuits with non-default parameters.  This was working before because there was a bug: the MainPod circuit was reporting that the used verifier data was the standard one instead of picking the one corresponding to it's own Params.

Summary of breaking changes:
- One and only one of the features `mem_cache` or `disk_cache` need to be enabled.  By default it's `mem_cache`
  - To enable the `disk_cache` you need to disable the default features like this: `--no-default-features --features=backend_plonky2,zk,disk_cache`
- Removed `DEFAULT_PARAMS`, instead use `Params::default()`
- Removed `STANDARD_REC_MAIN_POD_CIRCUIT_DATA`, instead use `cache_get_standard_rec_main_pod_common_circuit_data`
- The library is now using `nightly-2025-07-02`.  Some rust language features are unstable in previous versions.
This commit is contained in:
Eduard S. 2025-07-24 12:15:31 +02:00 committed by GitHub
parent 745d654048
commit 8429cd224d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 831 additions and 207 deletions

View file

@ -21,9 +21,11 @@ jobs:
- name: Build default - name: Build default
run: cargo build run: cargo build
- name: Build non-zk # check without the zk feature enabled - name: Build non-zk # check without the zk feature enabled
run: cargo build --no-default-features --features backend_plonky2 run: cargo build --no-default-features --features backend_plonky2,mem_cache
- name: Build metrics - name: Build metrics
run: cargo build --features metrics run: cargo build --features metrics
- name: Build time - name: Build time
run: cargo build --features time run: cargo build --features time
- name: Build disk_cache
run: cargo build --no-default-features --features backend_plonky2,zk,disk_cache

View file

@ -2,6 +2,7 @@
name = "pod2" name = "pod2"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
build = "build.rs"
[lib] [lib]
name = "pod2" name = "pod2"
@ -19,7 +20,7 @@ env_logger = "0.11"
lazy_static = "1.5.0" lazy_static = "1.5.0"
thiserror = { version = "2.0.12" } thiserror = { version = "2.0.12" }
# enabled by features: # enabled by features:
plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true } plonky2 = { git = "https://github.com/0xPARC/plonky2.git", rev = "3defd60532c8693cf5e9d2e6a8412c77ca58760f", optional = true }
serde = "1.0.219" serde = "1.0.219"
serde_json = "1.0.140" serde_json = "1.0.140"
base64 = "0.22.1" base64 = "0.22.1"
@ -32,6 +33,11 @@ rand = "0.8.5"
hashbrown = { version = "0.14.3", default-features = false, features = ["serde"] } hashbrown = { version = "0.14.3", default-features = false, features = ["serde"] }
pest = "2.8.0" pest = "2.8.0"
pest_derive = "2.8.0" pest_derive = "2.8.0"
directories = { version = "6.0.0", optional = true }
minicbor-serde = { version = "0.5.0", features = ["std"], optional = true }
serde_bytes = "0.11"
serde_arrays = "0.2.0"
sha2 = { version = "0.10.9" }
# Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory. # Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory.
# [patch."https://github.com/0xPolygonZero/plonky2"] # [patch."https://github.com/0xPolygonZero/plonky2"]
@ -42,10 +48,15 @@ pretty_assertions = "1.4.1"
# Used only for testing JSON Schema generation and validation. # Used only for testing JSON Schema generation and validation.
jsonschema = "0.30.0" jsonschema = "0.30.0"
[build-dependencies]
vergen-gitcl = { version = "1.0.0", features = ["build"] }
[features] [features]
default = ["backend_plonky2", "zk"] default = ["backend_plonky2", "zk", "mem_cache"]
backend_plonky2 = ["plonky2"] backend_plonky2 = ["plonky2"]
zk = [] zk = []
metrics = [] metrics = []
time = [] time = []
examples = [] examples = []
disk_cache = ["directories", "minicbor-serde"]
mem_cache = []

21
build.rs Normal file
View file

@ -0,0 +1,21 @@
#[cfg(feature = "mem_cache")]
fn main() {}
#[cfg(feature = "disk_cache")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use vergen_gitcl::{Emitter, GitclBuilder};
// Example of injected vars:
// cargo:rustc-env=VERGEN_GIT_BRANCH=master
// cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR_EMAIL=emitter@vergen.com
// cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR_NAME=Jason Ozias
// cargo:rustc-env=VERGEN_GIT_COMMIT_COUNT=44
// cargo:rustc-env=VERGEN_GIT_COMMIT_DATE=2024-01-30
// cargo:rustc-env=VERGEN_GIT_COMMIT_MESSAGE=depsup
// cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP=2024-01-30T21:43:43.000000000Z
// cargo:rustc-env=VERGEN_GIT_DESCRIBE=0.1.0-beta.1-15-g728e25c
// cargo:rustc-env=VERGEN_GIT_SHA=728e25ca5bb7edbbc505f12b28c66b2b27883cf1
let gitcl = GitclBuilder::all_git()?;
Emitter::default().add_instructions(&gitcl)?.emit()?;
Ok(())
}

View file

@ -1,3 +1,4 @@
#![allow(clippy::uninlined_format_args)] // TODO: Remove this in another PR
//! Example of building main pods that verify signed pods and other main pods using custom //! Example of building main pods that verify signed pods and other main pods using custom
//! predicates //! predicates
//! //!
@ -21,6 +22,7 @@ use pod2::{
}; };
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let mock = args.get(1).is_some_and(|arg1| arg1 == "--mock"); let mock = args.get(1).is_some_and(|arg1| arg1 == "--mock");
if mock { if mock {

View file

@ -1,3 +1,4 @@
#![allow(clippy::uninlined_format_args)] // TODO: Remove this in another PR
//! Simple example of building a signed pod and verifying it //! Simple example of building a signed pod and verifying it
//! //!
//! Run: `cargo run --release --example signed_pod` //! Run: `cargo run --release --example signed_pod`

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2025-01-20" channel = "nightly-2025-07-02"
components = ["clippy", "rustfmt"] components = ["clippy", "rustfmt"]

View file

@ -42,20 +42,29 @@ use std::{collections::HashMap, sync::LazyLock};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
emptypod::STANDARD_EMPTY_POD_DATA, primitives::merkletree::MerkleClaimAndProof, emptypod::cache_get_standard_empty_pod_verifier_circuit_data,
DEFAULT_PARAMS, STANDARD_REC_MAIN_POD_CIRCUIT_DATA, mainpod::cache_get_rec_main_pod_verifier_circuit_data,
primitives::merkletree::MerkleClaimAndProof,
}, },
middleware::{containers::Array, Hash, RawValue, Result, Value}, middleware::{containers::Array, Hash, Params, RawValue, Result, Value},
}; };
pub static DEFAULT_VD_SET: LazyLock<VDSet> = LazyLock::new(|| { pub static DEFAULT_VD_LIST: LazyLock<Vec<VerifierOnlyCircuitData>> = LazyLock::new(|| {
let params = &*DEFAULT_PARAMS; 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(),
]
});
let vds = vec![ pub static DEFAULT_VD_SET: LazyLock<VDSet> = LazyLock::new(|| {
STANDARD_REC_MAIN_POD_CIRCUIT_DATA.verifier_only.clone(), let params = Params::default();
STANDARD_EMPTY_POD_DATA.1.verifier_only.clone(), let vds = &*DEFAULT_VD_LIST;
]; VDSet::new(params.max_depth_mt_vds, vds).unwrap()
VDSet::new(params.max_depth_mt_vds, &vds).unwrap()
}); });
/// VDSet is the set of the allowed verifier_data hashes. When proving a /// VDSet is the set of the allowed verifier_data hashes. When proving a

View file

@ -19,6 +19,7 @@ use plonky2::{
}, },
util::serialization::{Buffer, IoResult, Read, Write}, util::serialization::{Buffer, IoResult, Read, Write},
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -39,7 +40,7 @@ use crate::{
pub const CODE_SIZE: usize = HASH_SIZE + 2; pub const CODE_SIZE: usize = HASH_SIZE + 2;
const NUM_BITS: usize = 32; const NUM_BITS: usize = 32;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct ValueTarget { pub struct ValueTarget {
pub elements: [Target; VALUE_SIZE], pub elements: [Target; VALUE_SIZE],
} }
@ -75,8 +76,9 @@ impl ValueTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct StatementArgTarget { pub struct StatementArgTarget {
#[serde(with = "serde_arrays")]
pub elements: [Target; STATEMENT_ARG_F_LEN], pub elements: [Target; STATEMENT_ARG_F_LEN],
} }
@ -128,7 +130,7 @@ impl StatementArgTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct StatementTarget { pub struct StatementTarget {
pub predicate: PredicateTarget, pub predicate: PredicateTarget,
pub args: Vec<StatementArgTarget>, pub args: Vec<StatementArgTarget>,
@ -201,8 +203,9 @@ impl StatementTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct OperationTypeTarget { pub struct OperationTypeTarget {
#[serde(with = "serde_arrays")]
pub elements: [Target; Params::operation_type_size()], pub elements: [Target; Params::operation_type_size()],
} }
@ -249,10 +252,11 @@ impl OperationTypeTarget {
} }
// TODO: Implement Operation::to_field to determine the size of each element // TODO: Implement Operation::to_field to determine the size of each element
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct OperationTarget { pub struct OperationTarget {
pub op_type: OperationTypeTarget, pub op_type: OperationTypeTarget,
pub args: Vec<[Target; OPERATION_ARG_F_LEN]>, pub args: Vec<[Target; OPERATION_ARG_F_LEN]>,
#[serde(with = "serde_arrays")]
pub aux: [Target; OPERATION_AUX_F_LEN], pub aux: [Target; OPERATION_AUX_F_LEN],
} }
@ -304,8 +308,9 @@ impl NativePredicateTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct PredicateTarget { pub struct PredicateTarget {
#[serde(with = "serde_arrays")]
pub(crate) elements: [Target; Params::predicate_size()], pub(crate) elements: [Target; Params::predicate_size()],
} }
@ -386,8 +391,9 @@ impl LiteralOrWildcardTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct StatementTmplArgTarget { pub struct StatementTmplArgTarget {
#[serde(with = "serde_arrays")]
pub elements: [Target; Params::statement_tmpl_arg_size()], pub elements: [Target; Params::statement_tmpl_arg_size()],
} }
@ -432,7 +438,7 @@ impl StatementTmplArgTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct StatementTmplTarget { pub struct StatementTmplTarget {
pub pred: PredicateTarget, pub pred: PredicateTarget,
pub args: Vec<StatementTmplArgTarget>, pub args: Vec<StatementTmplArgTarget>,
@ -449,7 +455,7 @@ impl StatementTmplTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct CustomPredicateTarget { pub struct CustomPredicateTarget {
pub conjunction: BoolTarget, pub conjunction: BoolTarget,
// len = params.max_custom_predicate_arity // len = params.max_custom_predicate_arity
@ -468,7 +474,7 @@ impl CustomPredicateTarget {
} }
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct CustomPredicateBatchTarget { pub struct CustomPredicateBatchTarget {
pub predicates: Vec<CustomPredicateTarget>, pub predicates: Vec<CustomPredicateTarget>,
} }
@ -500,6 +506,7 @@ impl CustomPredicateBatchTarget {
} }
/// Custom predicate table entry /// Custom predicate table entry
#[derive(Clone, Serialize, Deserialize)]
pub struct CustomPredicateEntryTarget { pub struct CustomPredicateEntryTarget {
pub id: HashOutTarget, pub id: HashOutTarget,
pub index: Target, pub index: Target,
@ -575,6 +582,7 @@ impl CustomPredicateEntryTarget {
} }
// Custom predicate verification table entry // Custom predicate verification table entry
#[derive(Clone, Serialize, Deserialize)]
pub struct CustomPredicateVerifyEntryTarget { pub struct CustomPredicateVerifyEntryTarget {
pub custom_predicate_table_index: Target, pub custom_predicate_table_index: Target,
pub custom_predicate: CustomPredicateEntryTarget, pub custom_predicate: CustomPredicateEntryTarget,
@ -631,6 +639,7 @@ impl CustomPredicateVerifyEntryTarget {
} }
/// Query for the custom predicate verification table /// Query for the custom predicate verification table
#[derive(Clone, Serialize, Deserialize)]
pub struct CustomPredicateVerifyQueryTarget { pub struct CustomPredicateVerifyQueryTarget {
pub statement: StatementTarget, pub statement: StatementTarget,
pub op_type: OperationTypeTarget, pub op_type: OperationTypeTarget,
@ -1386,7 +1395,7 @@ impl CircuitBuilderPod<F, D> for CircuitBuilder {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct LtMaskGenerator { pub struct LtMaskGenerator {
pub(crate) n: Target, pub(crate) n: Target,
pub(crate) mask: Vec<Target>, pub(crate) mask: Vec<Target>,

View file

@ -14,6 +14,7 @@ use plonky2::{
}, },
plonk::config::AlgebraicHasher, plonk::config::AlgebraicHasher,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -28,7 +29,7 @@ use crate::{
}, },
signedpod::{verify_signed_pod_circuit, SignedPodVerifyTarget}, signedpod::{verify_signed_pod_circuit, SignedPodVerifyTarget},
}, },
emptypod::{EmptyPod, STANDARD_EMPTY_POD_DATA}, emptypod::{cache_get_standard_empty_pod_circuit_data, EmptyPod},
error::Result, error::Result,
mainpod::{self, pad_statement}, mainpod::{self, pad_statement},
primitives::merkletree::{ primitives::merkletree::{
@ -1303,6 +1304,7 @@ fn verify_main_pod_circuit(
Ok(id) Ok(id)
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct MainPodVerifyTarget { pub struct MainPodVerifyTarget {
params: Params, params: Params,
vds_root: HashOutTarget, vds_root: HashOutTarget,
@ -1427,9 +1429,13 @@ impl InnerCircuit for MainPodVerifyTarget {
self.vd_mt_proofs[i].set_targets(pw, true, vd_mt_proof)?; self.vd_mt_proofs[i].set_targets(pw, true, vd_mt_proof)?;
} }
// the rest of vd_mt_proofs set them to the empty_pod vd_mt_proof // the rest of vd_mt_proofs set them to the empty_pod vd_mt_proof
let vd_emptypod_mt_proof = input let vd_emptypod_mt_proof =
.vds_set input
.get_vds_proofs(&[STANDARD_EMPTY_POD_DATA.1.verifier_only.clone()])?; .vds_set
.get_vds_proofs(&[cache_get_standard_empty_pod_circuit_data()
.1
.verifier_only
.clone()])?;
let vd_emptypod_mt_proof = vd_emptypod_mt_proof[0].clone(); let vd_emptypod_mt_proof = vd_emptypod_mt_proof[0].clone();
for i in input.vd_mt_proofs.len()..self.vd_mt_proofs.len() { for i in input.vd_mt_proofs.len()..self.vd_mt_proofs.len() {
self.vd_mt_proofs[i].set_targets(pw, true, &vd_emptypod_mt_proof)?; self.vd_mt_proofs[i].set_targets(pw, true, &vd_emptypod_mt_proof)?;

View file

@ -6,6 +6,7 @@ use plonky2::{
iop::witness::{PartialWitness, WitnessWrite}, iop::witness::{PartialWitness, WitnessWrite},
plonk::circuit_builder::CircuitBuilder, plonk::circuit_builder::CircuitBuilder,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -71,6 +72,7 @@ pub fn verify_signed_pod_circuit(
Ok(()) Ok(())
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct SignedPodVerifyTarget { pub struct SignedPodVerifyTarget {
params: Params, params: Params,
id: HashOutTarget, id: HashOutTarget,

View file

@ -21,7 +21,7 @@ use plonky2::{
/// vec![v1, v2, v3], /// vec![v1, v2, v3],
/// )); /// ));
/// ``` /// ```
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct DebugGenerator { pub struct DebugGenerator {
pub(crate) name: String, pub(crate) name: String,
pub(crate) xs: Vec<Target>, pub(crate) xs: Vec<Target>,

View file

@ -1,8 +1,3 @@
use std::{
collections::HashMap,
sync::{LazyLock, Mutex},
};
use itertools::Itertools; use itertools::Itertools;
use plonky2::{ use plonky2::{
hash::hash_types::HashOutTarget, hash::hash_types::HashOutTarget,
@ -18,6 +13,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
basetypes::{Proof, C, D}, basetypes::{Proof, C, D},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::{ circuits::{
common::{Flattenable, StatementTarget}, common::{Flattenable, StatementTarget},
mainpod::{calculate_id_circuit, PI_OFFSET_ID}, mainpod::{calculate_id_circuit, PI_OFFSET_ID},
@ -26,8 +22,10 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
mainpod::{self, calculate_id}, mainpod::{self, calculate_id},
recursion::pad_circuit, recursion::pad_circuit,
serialize_proof, DEFAULT_PARAMS, STANDARD_REC_MAIN_POD_CIRCUIT_DATA, serialization::{CircuitDataSerializer, VerifierCircuitDataSerializer},
serialize_proof,
}, },
cache::{self, CacheEntry},
middleware::{ middleware::{
self, AnchoredKey, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement, ToFields, self, AnchoredKey, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement, ToFields,
VDSet, Value, VerifierOnlyCircuitData, F, HASH_SIZE, KEY_TYPE, SELF, VDSet, Value, VerifierOnlyCircuitData, F, HASH_SIZE, KEY_TYPE, SELF,
@ -60,6 +58,7 @@ impl EmptyPodVerifyCircuit {
} }
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct EmptyPodVerifyTarget { pub struct EmptyPodVerifyTarget {
vds_root: HashOutTarget, vds_root: HashOutTarget,
} }
@ -70,7 +69,7 @@ impl EmptyPodVerifyTarget {
} }
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct EmptyPod { pub struct EmptyPod {
params: Params, params: Params,
id: PodId, id: PodId,
@ -80,11 +79,26 @@ pub struct EmptyPod {
type CircuitData = circuit_data::CircuitData<F, C, D>; type CircuitData = circuit_data::CircuitData<F, C, D>;
pub static STANDARD_EMPTY_POD_DATA: LazyLock<(EmptyPodVerifyTarget, CircuitData)> = pub fn cache_get_standard_empty_pod_circuit_data(
LazyLock::new(|| build().expect("successful build")); ) -> CacheEntry<(EmptyPodVerifyTarget, CircuitDataSerializer)> {
cache::get("standard_empty_pod_circuit_data", &(), |_| {
let (target, circuit_data) = build().expect("successful build");
(target, CircuitDataSerializer(circuit_data))
})
.expect("cache ok")
}
pub fn cache_get_standard_empty_pod_verifier_circuit_data(
) -> CacheEntry<VerifierCircuitDataSerializer> {
cache::get("standard_empty_pod_verifier_circuit_data", &(), |_| {
let (_, standard_empty_pod_circuit_data) = &*cache_get_standard_empty_pod_circuit_data();
VerifierCircuitDataSerializer(standard_empty_pod_circuit_data.verifier_data().clone())
})
.expect("cache ok")
}
fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> { fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
let params = &*DEFAULT_PARAMS; let params = Params::default();
#[cfg(not(feature = "zk"))] #[cfg(not(feature = "zk"))]
let config = CircuitConfig::standard_recursion_config(); let config = CircuitConfig::standard_recursion_config();
@ -96,20 +110,18 @@ fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
params: params.clone(), params: params.clone(),
} }
.eval(&mut builder)?; .eval(&mut builder)?;
let circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA; let common_circuit_data = &*cache_get_standard_rec_main_pod_common_circuit_data();
pad_circuit(&mut builder, &circuit_data.common); pad_circuit(&mut builder, common_circuit_data);
let data = timed!("EmptyPod build", builder.build::<C>()); let data = timed!("EmptyPod build", builder.build::<C>());
assert_eq!(circuit_data.common, data.common); assert_eq!(common_circuit_data.0, data.common);
Ok((empty_pod_verify_target, data)) Ok((empty_pod_verify_target, data))
} }
static EMPTY_POD_CACHE: LazyLock<Mutex<HashMap<Hash, EmptyPod>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
impl EmptyPod { impl EmptyPod {
pub fn new(params: &Params, vd_set: VDSet) -> Result<EmptyPod> { pub fn new(params: &Params, vd_set: VDSet) -> Result<EmptyPod> {
let (empty_pod_verify_target, data) = &*STANDARD_EMPTY_POD_DATA; let standard_empty_pod_data = cache_get_standard_empty_pod_circuit_data();
let (empty_pod_verify_target, data) = &*standard_empty_pod_data;
let mut pw = PartialWitness::<F>::new(); let mut pw = PartialWitness::<F>::new();
empty_pod_verify_target.set_targets(&mut pw, vd_set.root())?; empty_pod_verify_target.set_targets(&mut pw, vd_set.root())?;
@ -124,16 +136,16 @@ impl EmptyPod {
}) })
} }
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn RecursivePod> { pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn RecursivePod> {
let default_params = &*DEFAULT_PARAMS; let default_params = Params::default();
assert_eq!(default_params.id_params(), params.id_params()); assert_eq!(default_params.id_params(), params.id_params());
let empty_pod = EMPTY_POD_CACHE let empty_pod = cache::get(
.lock() "empty_pod",
.unwrap() &(default_params, vd_set),
.entry(vd_set.root()) |(params, vd_set)| Self::new(params, vd_set.clone()).expect("prove EmptyPod"),
.or_insert_with(|| Self::new(params, vd_set).expect("prove EmptyPod")) )
.clone(); .expect("cache ok");
Box::new(empty_pod) Box::new(empty_pod.clone())
} }
} }
@ -164,12 +176,13 @@ impl Pod for EmptyPod {
.cloned() .cloned()
.collect_vec(); .collect_vec();
let (_, data) = &*STANDARD_EMPTY_POD_DATA; let standard_empty_pod_verifier_data = cache_get_standard_empty_pod_verifier_circuit_data();
data.verify(ProofWithPublicInputs { standard_empty_pod_verifier_data
proof: self.proof.clone(), .verify(ProofWithPublicInputs {
public_inputs, proof: self.proof.clone(),
}) public_inputs,
.map_err(|e| Error::plonky2_proof_fail("EmptyPod", e)) })
.map_err(|e| Error::plonky2_proof_fail("EmptyPod", e))
} }
fn id(&self) -> PodId { fn id(&self) -> PodId {
@ -193,8 +206,11 @@ impl Pod for EmptyPod {
impl RecursivePod for EmptyPod { impl RecursivePod for EmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData { fn verifier_data(&self) -> VerifierOnlyCircuitData {
let (_, data) = &*STANDARD_EMPTY_POD_DATA; let standard_empty_pod_verifier_circuit_data =
data.verifier_only.clone() cache_get_standard_empty_pod_verifier_circuit_data();
standard_empty_pod_verifier_circuit_data
.verifier_only
.clone()
} }
fn proof(&self) -> Proof { fn proof(&self) -> Proof {
self.proof.clone() self.proof.clone()
@ -209,8 +225,8 @@ impl RecursivePod for EmptyPod {
id: PodId, id: PodId,
) -> Result<Box<dyn RecursivePod>> { ) -> Result<Box<dyn RecursivePod>> {
let data: Data = serde_json::from_value(data)?; let data: Data = serde_json::from_value(data)?;
let circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA; let common_circuit_data = cache_get_standard_rec_main_pod_common_circuit_data();
let proof = deserialize_proof(&circuit_data.common, &data.proof)?; let proof = deserialize_proof(&common_circuit_data, &data.proof)?;
Ok(Box::new(Self { Ok(Box::new(Self {
params, params,
id, id,

View file

@ -4,31 +4,34 @@ use std::{any::Any, iter, sync::Arc};
use itertools::Itertools; use itertools::Itertools;
pub use operation::*; pub use operation::*;
use plonky2::{ use plonky2::{hash::poseidon::PoseidonHash, plonk::config::Hasher};
hash::poseidon::PoseidonHash,
plonk::{circuit_data::CommonCircuitData, config::Hasher},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use statement::*; pub use statement::*;
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
basetypes::{Proof, ProofWithPublicInputs, VerifierOnlyCircuitData, D}, basetypes::{CircuitData, Proof, ProofWithPublicInputs, VerifierOnlyCircuitData},
cache::{self, CacheEntry},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::mainpod::{CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget}, circuits::mainpod::{CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget},
deserialize_proof, deserialize_proof,
emptypod::EmptyPod, emptypod::EmptyPod,
error::{Error, Result}, error::{Error, Result},
mock::emptypod::MockEmptyPod, mock::emptypod::MockEmptyPod,
primitives::merkletree::MerkleClaimAndProof, primitives::merkletree::MerkleClaimAndProof,
recursion::{hash_verifier_data, RecursiveCircuit, RecursiveParams}, recursion::{
hash_verifier_data, prove_rec_circuit, RecursiveCircuit, RecursiveCircuitTarget,
},
serialization::{
CircuitDataSerializer, CommonCircuitDataSerializer, VerifierCircuitDataSerializer,
},
serialize_proof, serialize_proof,
signedpod::SignedPod, signedpod::SignedPod,
STANDARD_REC_MAIN_POD_CIRCUIT_DATA,
}, },
middleware::{ middleware::{
self, resolve_wildcard_values, value_from_op, AnchoredKey, CustomPredicateBatch, Hash, self, resolve_wildcard_values, value_from_op, AnchoredKey, CustomPredicateBatch, Hash,
MainPodInputs, NativeOperation, OperationType, Params, Pod, PodId, PodProver, PodType, MainPodInputs, NativeOperation, OperationType, Params, Pod, PodId, PodProver, PodType,
RecursivePod, StatementArg, ToFields, VDSet, F, KEY_TYPE, SELF, RecursivePod, StatementArg, ToFields, VDSet, KEY_TYPE, SELF,
}, },
timed, timed,
}; };
@ -434,24 +437,6 @@ impl PodProver for Prover {
vd_set: &VDSet, vd_set: &VDSet,
inputs: MainPodInputs, inputs: MainPodInputs,
) -> Result<Box<dyn RecursivePod>> { ) -> Result<Box<dyn RecursivePod>> {
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
let (main_pod_target, circuit_data) =
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data_padded(
params.max_input_recursive_pods,
&rec_circuit_data.common,
params,
)?;
let rec_params = RecursiveParams {
arity: params.max_input_recursive_pods,
common_data: circuit_data.common.clone(),
verifier_data: circuit_data.verifier_data(),
};
let main_pod = RecursiveCircuit {
params: rec_params,
prover: circuit_data.prover_data(),
target: main_pod_target,
};
let signed_pods_input: Vec<SignedPod> = inputs let signed_pods_input: Vec<SignedPod> = inputs
.signed_pods .signed_pods
.iter() .iter()
@ -541,9 +526,17 @@ impl PodProver for Prover {
custom_predicate_batches, custom_predicate_batches,
custom_predicate_verifications, custom_predicate_verifications,
}; };
let (main_pod_target, circuit_data) = &*cache_get_rec_main_pod_circuit_data(params);
let proof_with_pis = timed!( let proof_with_pis = timed!(
"MainPod::prove", "MainPod::prove",
main_pod.prove(&input, proofs, verifier_datas)? prove_rec_circuit(
main_pod_target,
circuit_data,
&input,
proofs,
verifier_datas
)?
); );
Ok(Box::new(MainPod { Ok(Box::new(MainPod {
@ -570,21 +563,59 @@ pub struct MainPod {
proof: Proof, proof: Proof,
} }
// This is a helper function to get the CommonCircuitData necessary to decode pub(crate) fn rec_main_pod_circuit_data(
// a serialized proof. At some point in the future, this data may be available params: &Params,
// as a constant or with static initialization, but in the meantime we can ) -> (RecursiveCircuitTarget<MainPodVerifyTarget>, CircuitData) {
// generate it on-demand. let rec_common_circuit_data = cache_get_standard_rec_main_pod_common_circuit_data();
pub fn get_common_data(params: &Params) -> Result<CommonCircuitData<F, D>, Error> { timed!(
// TODO: Cache this somehow "recursive MainPod circuit_data padded",
// https://github.com/0xPARC/pod2/issues/247
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA;
let (_, circuit_data) =
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data_padded( RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data_padded(
params.max_input_recursive_pods, params.max_input_recursive_pods,
&rec_circuit_data.common, &rec_common_circuit_data,
params, params,
)?; )
Ok(circuit_data.common.clone()) .expect("calculate target_and_circuit_data_padded")
)
}
fn cache_get_rec_main_pod_circuit_data(
params: &Params,
) -> CacheEntry<(
RecursiveCircuitTarget<MainPodVerifyTarget>,
CircuitDataSerializer,
)> {
// TODO(Edu): I believe that the standard_rec_main_pod_circuit data is the same as this when
// the params are Default: we're padding the circuit to itself, so we get the original one?
// If this is true we can deduplicate this cache entry because both rec_main_pod_circuit_data
// and standard_rec_main_pod_circuit_data are indexed by Params. This can be easily tested by
// comparing the cached artifacts on disk :)
cache::get("rec_main_pod_circuit_data", params, |params| {
let (target, circuit_data) = rec_main_pod_circuit_data(params);
(target, CircuitDataSerializer(circuit_data))
})
.expect("cache ok")
}
pub fn cache_get_rec_main_pod_verifier_circuit_data(
params: &Params,
) -> CacheEntry<VerifierCircuitDataSerializer> {
cache::get("rec_main_pod_verifier_circuit_data", params, |params| {
let (_, rec_main_pod_circuit_data_padded) = &*cache_get_rec_main_pod_circuit_data(params);
VerifierCircuitDataSerializer(rec_main_pod_circuit_data_padded.verifier_data().clone())
})
.expect("cache ok")
}
// This is a helper function to get the CommonCircuitData necessary to decode
// a serialized proof.
pub fn cache_get_rec_main_pod_common_circuit_data(
params: &Params,
) -> CacheEntry<CommonCircuitDataSerializer> {
cache::get("rec_main_pod_common_circuit_data", params, |params| {
let (_, rec_main_pod_circuit_data_padded) = &*cache_get_rec_main_pod_circuit_data(params);
CommonCircuitDataSerializer(rec_main_pod_circuit_data_padded.common.clone())
})
.expect("cache ok")
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -626,22 +657,15 @@ impl Pod for MainPod {
} }
// 1, 3, 4, 5 verification via the zkSNARK proof // 1, 3, 4, 5 verification via the zkSNARK proof
let rec_circuit_data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA; let rec_main_pod_verifier_circuit_data =
// TODO: cache these artefacts &*cache_get_rec_main_pod_verifier_circuit_data(&self.params);
// https://github.com/0xPARC/pod2/issues/247
let (_, circuit_data) =
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data_padded(
self.params.max_input_recursive_pods,
&rec_circuit_data.common,
&self.params,
)?;
let public_inputs = id let public_inputs = id
.to_fields(&self.params) .to_fields(&self.params)
.iter() .iter()
.chain(self.vd_set.root().0.iter()) .chain(self.vd_set.root().0.iter())
.cloned() .cloned()
.collect_vec(); .collect_vec();
circuit_data rec_main_pod_verifier_circuit_data
.verify(ProofWithPublicInputs { .verify(ProofWithPublicInputs {
proof: self.proof.clone(), proof: self.proof.clone(),
public_inputs, public_inputs,
@ -675,8 +699,9 @@ impl Pod for MainPod {
impl RecursivePod for MainPod { impl RecursivePod for MainPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData { fn verifier_data(&self) -> VerifierOnlyCircuitData {
let data = &*STANDARD_REC_MAIN_POD_CIRCUIT_DATA; let rec_main_pod_verifier_circuit_data =
data.verifier_only.clone() cache_get_rec_main_pod_verifier_circuit_data(&self.params);
rec_main_pod_verifier_circuit_data.verifier_only.clone()
} }
fn proof(&self) -> Proof { fn proof(&self) -> Proof {
self.proof.clone() self.proof.clone()
@ -691,7 +716,7 @@ impl RecursivePod for MainPod {
id: PodId, id: PodId,
) -> Result<Box<dyn RecursivePod>> { ) -> Result<Box<dyn RecursivePod>> {
let data: Data = serde_json::from_value(data)?; let data: Data = serde_json::from_value(data)?;
let common = get_common_data(&params)?; let common = cache_get_rec_main_pod_common_circuit_data(&params);
let proof = deserialize_proof(&common, &data.proof)?; let proof = deserialize_proof(&common, &data.proof)?;
Ok(Box::new(Self { Ok(Box::new(Self {
params, params,
@ -719,7 +744,8 @@ pub mod tests {
self, literal, CustomPredicateBatchBuilder, MainPodBuilder, StatementTmplBuilder as STB, self, literal, CustomPredicateBatchBuilder, MainPodBuilder, StatementTmplBuilder as STB,
}, },
middleware::{ middleware::{
self, containers::Set, CustomPredicateRef, NativePredicate as NP, DEFAULT_VD_SET, self, containers::Set, CustomPredicateRef, NativePredicate as NP, DEFAULT_VD_LIST,
DEFAULT_VD_SET,
}, },
op, op,
}; };
@ -735,7 +761,9 @@ pub mod tests {
..Default::default() ..Default::default()
}; };
println!("{:#?}", params); println!("{:#?}", params);
let vd_set = &*DEFAULT_VD_SET; let mut vds = DEFAULT_VD_LIST.clone();
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, sanction_list_builder) = let (gov_id_builder, pay_stub_builder, sanction_list_builder) =
zu_kyc_sign_pod_builders(&params); zu_kyc_sign_pod_builders(&params);
@ -747,7 +775,7 @@ pub mod tests {
let sanction_list_pod = sanction_list_builder.sign(&signer)?; let sanction_list_pod = sanction_list_builder.sign(&signer)?;
let kyc_builder = zu_kyc_pod_builder( let kyc_builder = zu_kyc_pod_builder(
&params, &params,
vd_set, &vd_set,
&gov_id_pod, &gov_id_pod,
&pay_stub_pod, &pay_stub_pod,
&sanction_list_pod, &sanction_list_pod,
@ -772,7 +800,9 @@ pub mod tests {
max_input_pods_public_statements: 10, max_input_pods_public_statements: 10,
..Default::default() ..Default::default()
}; };
let vd_set = &*DEFAULT_VD_SET; let mut vds = DEFAULT_VD_LIST.clone();
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::SignedPodBuilder::new(&params);
gov_id_builder.insert("idNumber", "4242424242"); gov_id_builder.insert("idNumber", "4242424242");
@ -781,7 +811,7 @@ pub mod tests {
let signer = Signer(SecretKey(42u64.into())); let signer = Signer(SecretKey(42u64.into()));
let gov_id = gov_id_builder.sign(&signer).unwrap(); let gov_id = gov_id_builder.sign(&signer).unwrap();
let now_minus_18y: i64 = 1169909388; let now_minus_18y: i64 = 1169909388;
let mut kyc_builder = frontend::MainPodBuilder::new(&params, vd_set); let mut kyc_builder = frontend::MainPodBuilder::new(&params, &vd_set);
kyc_builder.add_signed_pod(&gov_id); kyc_builder.add_signed_pod(&gov_id);
kyc_builder kyc_builder
.pub_op(op!(lt, (&gov_id, "dateOfBirth"), now_minus_18y)) .pub_op(op!(lt, (&gov_id, "dateOfBirth"), now_minus_18y))
@ -827,9 +857,11 @@ pub mod tests {
max_depth_mt_containers: 4, max_depth_mt_containers: 4,
max_depth_mt_vds: 6, max_depth_mt_vds: 6,
}; };
let vd_set = &*DEFAULT_VD_SET; let mut vds = DEFAULT_VD_LIST.clone();
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 pod_builder = frontend::MainPodBuilder::new(&params, vd_set); let pod_builder = frontend::MainPodBuilder::new(&params, &vd_set);
// Mock // Mock
let prover = MockProver {}; let prover = MockProver {};
@ -889,7 +921,9 @@ pub mod tests {
..Default::default() ..Default::default()
}; };
println!("{:#?}", params); println!("{:#?}", params);
let vd_set = &*DEFAULT_VD_SET; let mut vds = DEFAULT_VD_LIST.clone();
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 cpb_builder = CustomPredicateBatchBuilder::new(params.clone(), "cpb".into()); 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::Equal).arg(("id", "score")).arg(literal(42));
@ -908,7 +942,7 @@ pub mod tests {
let cpb_and = CustomPredicateRef::new(cpb.clone(), 0); let cpb_and = CustomPredicateRef::new(cpb.clone(), 0);
let _cpb_or = CustomPredicateRef::new(cpb.clone(), 1); let _cpb_or = CustomPredicateRef::new(cpb.clone(), 1);
let mut pod_builder = MainPodBuilder::new(&params, vd_set); let mut pod_builder = MainPodBuilder::new(&params, &vd_set);
let st0 = pod_builder.priv_op(op!(new_entry, "score", 42))?; let st0 = pod_builder.priv_op(op!(new_entry, "score", 42))?;
let st1 = pod_builder.priv_op(op!(new_entry, "key", 42))?; let st1 = pod_builder.priv_op(op!(new_entry, "key", 42))?;

View file

@ -69,7 +69,7 @@ impl fmt::Display for MockMainPod {
for (i, st) in self.statements.iter().enumerate() { for (i, st) in self.statements.iter().enumerate() {
if self.params.max_input_signed_pods > 0 if self.params.max_input_signed_pods > 0
&& (i >= offset_input_signed_pods && i < offset_input_recursive_pods) && (i >= offset_input_signed_pods && i < offset_input_recursive_pods)
&& ((i - offset_input_signed_pods) % self.params.max_signed_pod_values == 0) && (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 index = (i - offset_input_signed_pods) / self.params.max_signed_pod_values;
let pod = &self.input_signed_pods[index]; let pod = &self.input_signed_pods[index];
@ -84,9 +84,8 @@ impl fmt::Display for MockMainPod {
if self.params.max_input_recursive_pods > 0 if self.params.max_input_recursive_pods > 0
&& (i >= offset_input_recursive_pods) && (i >= offset_input_recursive_pods)
&& (i < offset_input_statements) && (i < offset_input_statements)
&& ((i - offset_input_recursive_pods) && (i - offset_input_recursive_pods)
% self.params.max_input_pods_public_statements .is_multiple_of(self.params.max_input_pods_public_statements)
== 0)
{ {
let index = (i - offset_input_recursive_pods) let index = (i - offset_input_recursive_pods)
/ self.params.max_input_pods_public_statements; / self.params.max_input_pods_public_statements;

View file

@ -6,39 +6,46 @@ pub mod mainpod;
pub mod mock; pub mod mock;
pub mod primitives; pub mod primitives;
pub mod recursion; pub mod recursion;
mod serialization;
pub mod signedpod; pub mod signedpod;
use std::sync::LazyLock;
use base64::{prelude::BASE64_STANDARD, Engine}; use base64::{prelude::BASE64_STANDARD, Engine};
pub use error::*; pub use error::*;
use plonky2::util::serialization::{Buffer, Read}; use plonky2::util::serialization::{Buffer, Read};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
basetypes::{CircuitData, CommonCircuitData, Proof}, basetypes::{CommonCircuitData, Proof},
circuits::mainpod::{MainPodVerifyTarget, NUM_PUBLIC_INPUTS}, circuits::mainpod::{MainPodVerifyTarget, NUM_PUBLIC_INPUTS},
recursion::RecursiveCircuit, recursion::RecursiveCircuit,
serialization::CommonCircuitDataSerializer,
}, },
cache::{self, CacheEntry},
middleware::Params, middleware::Params,
timed, timed,
}; };
pub static DEFAULT_PARAMS: LazyLock<Params> = LazyLock::new(Params::default); pub fn cache_get_standard_rec_main_pod_common_circuit_data(
) -> CacheEntry<CommonCircuitDataSerializer> {
pub static STANDARD_REC_MAIN_POD_CIRCUIT_DATA: LazyLock<CircuitData> = LazyLock::new(|| { let params = Params::default();
let params = &*DEFAULT_PARAMS; cache::get(
timed!( "standard_rec_main_pod_common_circuit_data",
"recursive MainPod circuit_data", &params,
RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data( |params| {
params.max_input_recursive_pods, let circuit_data = timed!(
NUM_PUBLIC_INPUTS, "recursive MainPod circuit_data",
params RecursiveCircuit::<MainPodVerifyTarget>::target_and_circuit_data(
) params.max_input_recursive_pods,
.expect("calculate circuit_data") NUM_PUBLIC_INPUTS,
.1 params
)
.expect("calculate circuit_data")
);
CommonCircuitDataSerializer(circuit_data.1.common)
},
) )
}); .expect("cache ok")
}
pub fn serialize_bytes(bytes: &[u8]) -> String { pub fn serialize_bytes(bytes: &[u8]) -> String {
BASE64_STANDARD.encode(bytes) BASE64_STANDARD.encode(bytes)

View file

@ -16,11 +16,12 @@ use plonky2::{
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData}, plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
util::serialization::{Buffer, IoResult, Read, Write}, util::serialization::{Buffer, IoResult, Read, Write},
}; };
use serde::{Deserialize, Serialize};
use crate::backends::plonky2::basetypes::{D, F}; use crate::backends::plonky2::basetypes::{D, F};
#[derive(Debug)] #[derive(Debug, Default, Clone)]
struct ConditionalZeroGenerator<F: RichField + Extendable<D>, const D: usize> { pub(crate) struct ConditionalZeroGenerator<F: RichField + Extendable<D>, const D: usize> {
if_zero: Target, if_zero: Target,
then_zero: Target, then_zero: Target,
quot: Target, quot: Target,
@ -78,9 +79,11 @@ impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D>
/// A big integer, represented in base `2^32` with 10 digits, in little endian /// A big integer, represented in base `2^32` with 10 digits, in little endian
/// form. /// form.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BigUInt320Target { pub struct BigUInt320Target {
#[serde(with = "serde_arrays")]
pub limbs: [Target; 10], pub limbs: [Target; 10],
#[serde(with = "serde_arrays")]
pub bits: [BoolTarget; 320], pub bits: [BoolTarget; 320],
} }
@ -313,7 +316,7 @@ fn biguint_limbs_to_bits(builder: &mut CircuitBuilder<F, D>, limbs: &[Target]) -
Copied from https://github.com/0xPolygonZero/plonky2/blob/82791c4809d6275682c34b926390ecdbdc2a5297/plonky2/src/gadgets/range_check.rs#L62 Copied from https://github.com/0xPolygonZero/plonky2/blob/82791c4809d6275682c34b926390ecdbdc2a5297/plonky2/src/gadgets/range_check.rs#L62
*/ */
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct LowHighGenerator { pub struct LowHighGenerator {
integer: Target, integer: Target,
n_log: usize, n_log: usize,

View file

@ -57,6 +57,7 @@ pub fn ec_field_sqrt(x: &ECField) -> Option<ECField> {
]); ]);
// Compute x^((r-1)/2) = x^(p*((1+p)/2)*(1+p^2)) // Compute x^((r-1)/2) = x^(p*((1+p)/2)*(1+p^2))
let x1 = x.frobenius(); let x1 = x.frobenius();
#[allow(clippy::manual_div_ceil)]
let x2 = x1.exp_u64((1 + GoldilocksField::ORDER) / 2); let x2 = x1.exp_u64((1 + GoldilocksField::ORDER) / 2);
let den = x2 * x2.repeated_frobenius(2); let den = x2 * x2.repeated_frobenius(2);
Some(num / den) Some(num / den)
@ -440,7 +441,7 @@ impl Mul<Point> for &BigUint {
type FieldTarget = OEFTarget<5, QuinticExtension<GoldilocksField>>; type FieldTarget = OEFTarget<5, QuinticExtension<GoldilocksField>>;
#[derive(Clone, Debug)] #[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct PointTarget { pub struct PointTarget {
pub x: FieldTarget, pub x: FieldTarget,
pub u: FieldTarget, pub u: FieldTarget,
@ -470,8 +471,8 @@ impl PointTarget {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Default, Debug)]
struct PointSquareRootGenerator { pub(crate) struct PointSquareRootGenerator {
pub orig: PointTarget, pub orig: PointTarget,
pub sqrt: PointTarget, pub sqrt: PointTarget,
} }

View file

@ -15,6 +15,7 @@ use plonky2::{
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData}, plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
util::serialization::{Buffer, IoError, Read, Write}, util::serialization::{Buffer, IoError, Read, Write},
}; };
use serde::{Deserialize, Serialize};
//use super::gates::field::NNFMulGate; //use super::gates::field::NNFMulGate;
use crate::{ use crate::{
@ -83,8 +84,9 @@ pub trait CircuitBuilderNNF<
} }
/// Target type modelled on OEF. /// Target type modelled on OEF.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OEFTarget<const DEG: usize, NNF: OEF<DEG>> { pub struct OEFTarget<const DEG: usize, NNF: OEF<DEG>> {
#[serde(with = "serde_arrays")]
pub components: [Target; DEG], pub components: [Target; DEG],
_phantom_data: PhantomData<NNF>, _phantom_data: PhantomData<NNF>,
} }
@ -106,8 +108,8 @@ impl<const DEG: usize, NNF: OEF<DEG>> Default for OEFTarget<DEG, NNF> {
/// Quotient generator for OEF targets. Allows us to automagically /// Quotient generator for OEF targets. Allows us to automagically
/// generate quotients as witnesses. /// generate quotients as witnesses.
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
struct QuotientGeneratorOEF<const DEG: usize, NNF: OEF<DEG>> { pub(crate) struct QuotientGeneratorOEF<const DEG: usize, NNF: OEF<DEG>> {
numerator: OEFTarget<DEG, NNF>, numerator: OEFTarget<DEG, NNF>,
denominator: OEFTarget<DEG, NNF>, denominator: OEFTarget<DEG, NNF>,
quotient: OEFTarget<DEG, NNF>, quotient: OEFTarget<DEG, NNF>,
@ -121,7 +123,11 @@ impl<
> SimpleGenerator<F, D> for QuotientGeneratorOEF<DEG, NNF> > SimpleGenerator<F, D> for QuotientGeneratorOEF<DEG, NNF>
{ {
fn id(&self) -> String { fn id(&self) -> String {
"QuotientGeneratorOEF".to_string() format!(
"QuotientGeneratorOEF<{}, {}>",
DEG,
std::any::type_name::<NNF>()
)
} }
fn dependencies(&self) -> Vec<Target> { fn dependencies(&self) -> Vec<Target> {

View file

@ -25,7 +25,7 @@ use crate::backends::plonky2::primitives::ec::{
/// operation when all its witness wire values are zero (so that when the gate is partially used, /// operation when all its witness wire values are zero (so that when the gate is partially used,
/// the unused slots still pass the constraints). This is the reason why this gate doesn't add the /// the unused slots still pass the constraints). This is the reason why this gate doesn't add the
/// final offset: if it did, the constraints wouldn't pass on the zero witness values. /// final offset: if it did, the constraints wouldn't pass on the zero witness values.
#[derive(Debug, Clone)] #[derive(Debug, Default, Clone)]
pub struct ECAddHomogOffset; pub struct ECAddHomogOffset;
impl SimpleGate for ECAddHomogOffset { impl SimpleGate for ECAddHomogOffset {

View file

@ -66,7 +66,7 @@ pub struct RecursiveGateAdapter<const D: usize, G: SimpleGate> {
_gate: PhantomData<G>, _gate: PhantomData<G>,
} }
#[derive(Debug)] #[derive(Debug, Default, Clone)]
pub struct RecursiveGenerator<const D: usize, G: SimpleGate> { pub struct RecursiveGenerator<const D: usize, G: SimpleGate> {
row: usize, row: usize,
index: usize, index: usize,
@ -175,7 +175,7 @@ where
G::F: RichField + Extendable<D> + Extendable<1>, G::F: RichField + Extendable<D> + Extendable<1>,
{ {
fn id(&self) -> String { fn id(&self) -> String {
G::ID.to_string() format!("GateAdapter<{}>", std::any::type_name::<G>())
} }
fn serialize( fn serialize(
@ -336,7 +336,7 @@ where
} }
fn id(&self) -> String { fn id(&self) -> String {
format!("Generator<{},{}>", D, G::ID) format!("RecursiveGenerator<{}, {}>", D, std::any::type_name::<G>())
} }
fn dependencies(&self) -> Vec<Target> { fn dependencies(&self) -> Vec<Target> {
@ -374,7 +374,11 @@ where
F: RichField + Extendable<D>, F: RichField + Extendable<D>,
{ {
fn id(&self) -> String { fn id(&self) -> String {
format!("Recursive<{},{}>", D, G::ID) format!(
"RecursiveGateAdapter<{}, {}>",
D,
std::any::type_name::<G>()
)
} }
fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> { fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> {

View file

@ -101,7 +101,7 @@ impl<'de> Deserialize<'de> for Signature {
} }
/// Targets for Schnorr signature over ecGFp5. /// Targets for Schnorr signature over ecGFp5.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignatureTarget { pub struct SignatureTarget {
pub s: BigUInt320Target, pub s: BigUInt320Target,
pub e: BigUInt320Target, pub e: BigUInt320Target,

View file

@ -23,6 +23,7 @@ use plonky2::{
}, },
plonk::circuit_builder::CircuitBuilder, plonk::circuit_builder::CircuitBuilder,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -35,7 +36,7 @@ use crate::{
middleware::{EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE}, middleware::{EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE},
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MerkleClaimAndProofTarget { pub struct MerkleClaimAndProofTarget {
pub(crate) max_depth: usize, pub(crate) max_depth: usize,
// `enabled` determines if the merkleproof verification is enabled // `enabled` determines if the merkleproof verification is enabled
@ -194,6 +195,7 @@ impl MerkleClaimAndProofTarget {
} }
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct MerkleProofExistenceTarget { pub struct MerkleProofExistenceTarget {
max_depth: usize, max_depth: usize,
// `enabled` determines if the merkleproof verification is enabled // `enabled` determines if the merkleproof verification is enabled

View file

@ -289,7 +289,7 @@ impl MerkleTree {
} }
/// returns an iterator over the leaves of the tree /// returns an iterator over the leaves of the tree
pub fn iter(&self) -> Iter { pub fn iter(&self) -> Iter<'_> {
Iter { Iter {
state: vec![&self.root], state: vec![&self.root],
} }

View file

@ -20,6 +20,7 @@ use plonky2::{
proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}, proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget},
}, },
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -38,6 +39,7 @@ use crate::{
// TODO: This is a very simple wrapper over the signature verification implemented on // TODO: This is a very simple wrapper over the signature verification implemented on
// `SignatureTarget`. I think we can remove this and use it directly. Also we're not using the // `SignatureTarget`. I think we can remove this and use it directly. Also we're not using the
// `enabled` flag, so it should be straight-forward to remove this. // `enabled` flag, so it should be straight-forward to remove this.
#[derive(Clone, Serialize, Deserialize)]
pub struct SignatureVerifyTarget { pub struct SignatureVerifyTarget {
// `enabled` determines if the signature verification is enabled // `enabled` determines if the signature verification is enabled
pub(crate) enabled: BoolTarget, pub(crate) enabled: BoolTarget,

View file

@ -32,6 +32,7 @@ use plonky2::{
}, },
util::log2_ceil, util::log2_ceil,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
@ -133,18 +134,59 @@ pub fn new_params_padded<I: InnerCircuit>(
/// RecursiveCircuit defines the circuit that verifies `arity` proofs. /// RecursiveCircuit defines the circuit that verifies `arity` proofs.
pub struct RecursiveCircuit<I: InnerCircuit> { pub struct RecursiveCircuit<I: InnerCircuit> {
pub(crate) params: RecursiveParams,
pub(crate) prover: ProverCircuitData<F, C, D>, pub(crate) prover: ProverCircuitData<F, C, D>,
pub(crate) target: RecursiveCircuitTarget<I>, pub(crate) target: RecursiveCircuitTarget<I>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RecursiveCircuitTarget<I: InnerCircuit> { pub struct RecursiveCircuitTarget<I: InnerCircuit> {
innercircuit_targ: I, innercircuit_targ: I,
proofs_targ: Vec<ProofWithPublicInputsTarget<D>>, proofs_targ: Vec<ProofWithPublicInputsTarget<D>>,
verifier_datas_targ: Vec<VerifierCircuitTarget>, verifier_datas_targ: Vec<VerifierCircuitTarget>,
} }
impl<I: InnerCircuit> RecursiveCircuitTarget<I> {
fn set_targets(
&self,
pw: &mut PartialWitness<F>,
innercircuit_input: &I::Input,
recursive_proofs: Vec<ProofWithPublicInputs<F, C, D>>,
verifier_datas: Vec<VerifierOnlyCircuitData<C, D>>,
) -> Result<()> {
let n = self.proofs_targ.len();
assert_eq!(n, recursive_proofs.len());
assert_eq!(n, verifier_datas.len());
// set the InnerCircuit related values
self.innercircuit_targ.set_targets(pw, innercircuit_input)?;
#[allow(clippy::needless_range_loop)]
for i in 0..n {
pw.set_verifier_data_target(&self.verifier_datas_targ[i], &verifier_datas[i])?;
pw.set_proof_with_pis_target(&self.proofs_targ[i], &recursive_proofs[i])?;
}
Ok(())
}
}
pub fn prove_rec_circuit<I: InnerCircuit>(
target: &RecursiveCircuitTarget<I>,
circuit_data: &CircuitData<F, C, D>,
inner_inputs: &I::Input,
proofs: Vec<ProofWithPublicInputs<F, C, D>>,
verifier_datas: Vec<VerifierOnlyCircuitData<C, D>>,
) -> Result<ProofWithPublicInputs<F, C, D>> {
let mut pw = PartialWitness::new();
target.set_targets(
&mut pw,
inner_inputs, // innercircuit_input
proofs,
verifier_datas,
)?;
Ok(circuit_data.prove(pw)?)
}
impl<I: InnerCircuit> RecursiveCircuit<I> { impl<I: InnerCircuit> RecursiveCircuit<I> {
pub fn prove( pub fn prove(
&self, &self,
@ -153,7 +195,7 @@ impl<I: InnerCircuit> RecursiveCircuit<I> {
verifier_datas: Vec<VerifierOnlyCircuitData<C, D>>, verifier_datas: Vec<VerifierOnlyCircuitData<C, D>>,
) -> Result<ProofWithPublicInputs<F, C, D>> { ) -> Result<ProofWithPublicInputs<F, C, D>> {
let mut pw = PartialWitness::new(); let mut pw = PartialWitness::new();
self.set_targets( self.target.set_targets(
&mut pw, &mut pw,
inner_inputs, // innercircuit_input inner_inputs, // innercircuit_input
proofs, proofs,
@ -182,7 +224,6 @@ impl<I: InnerCircuit> RecursiveCircuit<I> {
let prover: ProverCircuitData<F, C, D> = builder.build_prover::<C>(); let prover: ProverCircuitData<F, C, D> = builder.build_prover::<C>();
Ok(Self { Ok(Self {
params: params.clone(),
prover, prover,
target: targets, target: targets,
}) })
@ -242,31 +283,6 @@ impl<I: InnerCircuit> RecursiveCircuit<I> {
}) })
} }
fn set_targets(
&self,
pw: &mut PartialWitness<F>,
innercircuit_input: &I::Input,
recursive_proofs: Vec<ProofWithPublicInputs<F, C, D>>,
verifier_datas: Vec<VerifierOnlyCircuitData<C, D>>,
) -> Result<()> {
let n = recursive_proofs.len();
assert_eq!(n, self.params.arity);
assert_eq!(n, verifier_datas.len());
// set the InnerCircuit related values
self.target
.innercircuit_targ
.set_targets(pw, innercircuit_input)?;
#[allow(clippy::needless_range_loop)]
for i in 0..self.params.arity {
pw.set_verifier_data_target(&self.target.verifier_datas_targ[i], &verifier_datas[i])?;
pw.set_proof_with_pis_target(&self.target.proofs_targ[i], &recursive_proofs[i])?;
}
Ok(())
}
/// returns the target full-recursive circuit and its CircuitData /// returns the target full-recursive circuit and its CircuitData
pub fn target_and_circuit_data( pub fn target_and_circuit_data(
arity: usize, arity: usize,

View file

@ -1,5 +1,6 @@
pub mod circuit; pub mod circuit;
pub use circuit::{ pub use circuit::{
common_data_for_recursion, hash_verifier_data, new_params, new_params_padded, pad_circuit, common_data_for_recursion, hash_verifier_data, new_params, new_params_padded, pad_circuit,
InnerCircuit, RecursiveCircuit, RecursiveParams, VerifiedProofTarget, prove_rec_circuit, InnerCircuit, RecursiveCircuit, RecursiveCircuitTarget, RecursiveParams,
VerifiedProofTarget,
}; };

View file

@ -0,0 +1,252 @@
use std::ops::Deref;
use plonky2::{
field::extension::quintic::QuinticExtension,
gates::{
arithmetic_base::ArithmeticGate, arithmetic_extension::ArithmeticExtensionGate,
base_sum::BaseSumGate, constant::ConstantGate, coset_interpolation::CosetInterpolationGate,
exponentiation::ExponentiationGate, lookup::LookupGate, lookup_table::LookupTableGate,
multiplication_extension::MulExtensionGate, noop::NoopGate, poseidon::PoseidonGate,
poseidon_mds::PoseidonMdsGate, public_input::PublicInputGate,
random_access::RandomAccessGate, reducing::ReducingGate,
reducing_extension::ReducingExtensionGate,
},
get_gate_tag_impl, impl_gate_serializer, read_gate_impl,
util::serialization::GateSerializer,
};
use serde::{de, ser, Deserialize, Serialize};
use crate::backends::plonky2::{
basetypes::{CircuitData, CommonCircuitData, VerifierCircuitData, C, D, F},
circuits::{common::LtMaskGenerator, utils::DebugGenerator},
primitives::ec::{
bits::ConditionalZeroGenerator,
curve::PointSquareRootGenerator,
field::QuotientGeneratorOEF,
gates::{
curve::ECAddHomogOffset,
field::NNFMulSimple,
generic::{GateAdapter, RecursiveGateAdapter, RecursiveGenerator},
},
},
};
#[derive(Debug)]
pub(crate) struct Pod2GateSerializer;
impl GateSerializer<F, D> for Pod2GateSerializer {
impl_gate_serializer! {
Pod2GateSerializer,
ArithmeticGate,
ArithmeticExtensionGate<D>,
BaseSumGate<2>,
ConstantGate,
CosetInterpolationGate<F, D>,
ExponentiationGate<F, D>,
LookupGate,
LookupTableGate,
MulExtensionGate<D>,
NoopGate,
PoseidonMdsGate<F, D>,
PoseidonGate<F, D>,
PublicInputGate,
RandomAccessGate<F, D>,
ReducingExtensionGate<D>,
ReducingGate<D>,
// pod2 custom gates
GateAdapter::<NNFMulSimple<5, QuinticExtension<F>>>,
RecursiveGateAdapter::<D, NNFMulSimple<5, QuinticExtension<F>>>,
GateAdapter::<ECAddHomogOffset>,
RecursiveGateAdapter::<D, ECAddHomogOffset>
}
}
use plonky2::{
gadgets::{
arithmetic::EqualityGenerator,
arithmetic_extension::QuotientGeneratorExtension,
range_check::LowHighGenerator,
split_base::BaseSumGenerator,
split_join::{SplitGenerator, WireSplitGenerator},
},
gates::{
arithmetic_base::ArithmeticBaseGenerator,
arithmetic_extension::ArithmeticExtensionGenerator, base_sum::BaseSplitGenerator,
coset_interpolation::InterpolationGenerator, exponentiation::ExponentiationGenerator,
lookup::LookupGenerator, lookup_table::LookupTableGenerator,
multiplication_extension::MulExtensionGenerator, poseidon::PoseidonGenerator,
poseidon_mds::PoseidonMdsGenerator, random_access::RandomAccessGenerator,
reducing::ReducingGenerator,
reducing_extension::ReducingGenerator as ReducingExtensionGenerator,
},
get_generator_tag_impl, impl_generator_serializer,
iop::generator::{
ConstantGenerator, CopyGenerator, NonzeroTestGenerator, RandomValueGenerator,
},
read_generator_impl,
recursion::dummy_circuit::DummyProofGenerator,
util::serialization::WitnessGeneratorSerializer,
};
#[derive(Debug)]
pub(crate) struct Pod2GeneratorSerializer {}
// TODO: Add pod2 custom generators
impl WitnessGeneratorSerializer<F, D> for Pod2GeneratorSerializer {
impl_generator_serializer! {
Pod2GeneratorSerializer,
ArithmeticBaseGenerator<F, D>,
ArithmeticExtensionGenerator<F, D>,
BaseSplitGenerator<2>,
BaseSumGenerator<2>,
ConstantGenerator<F>,
CopyGenerator,
DummyProofGenerator<F, C, D>,
EqualityGenerator,
ExponentiationGenerator<F, D>,
InterpolationGenerator<F, D>,
LookupGenerator,
LookupTableGenerator,
LowHighGenerator,
MulExtensionGenerator<F, D>,
NonzeroTestGenerator,
PoseidonGenerator<F, D>,
PoseidonMdsGenerator<D>,
QuotientGeneratorExtension<D>,
RandomAccessGenerator<F, D>,
RandomValueGenerator,
ReducingGenerator<D>,
ReducingExtensionGenerator<D>,
SplitGenerator,
WireSplitGenerator,
// pod2 custom generators
DebugGenerator,
LtMaskGenerator,
QuotientGeneratorOEF<5, QuinticExtension<F>>,
PointSquareRootGenerator,
ConditionalZeroGenerator<F, D>,
RecursiveGenerator<D, NNFMulSimple<5, QuinticExtension<F>>>,
RecursiveGenerator<1, NNFMulSimple<5, QuinticExtension<F>>>,
RecursiveGenerator<D, ECAddHomogOffset>,
RecursiveGenerator<1, ECAddHomogOffset>
}
}
/// Helper type to serialize and deserialize the pod2 `CircuitData` using serde traits.
#[derive(Clone)]
pub struct CircuitDataSerializer(pub(crate) CircuitData);
impl Deref for CircuitDataSerializer {
type Target = CircuitData;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for CircuitDataSerializer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let gate_serializer = Pod2GateSerializer {};
let generator_serializer = Pod2GeneratorSerializer {};
let bytes = self
.0
.to_bytes(&gate_serializer, &generator_serializer)
.map_err(ser::Error::custom)?;
serde_bytes::ByteBuf::from(bytes).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CircuitDataSerializer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <&'de serde_bytes::Bytes>::deserialize(deserializer)?;
let gate_serializer = Pod2GateSerializer {};
let generator_serializer = Pod2GeneratorSerializer {};
let circuit_data = CircuitData::from_bytes(bytes, &gate_serializer, &generator_serializer)
.map_err(de::Error::custom)?;
Ok(CircuitDataSerializer(circuit_data))
}
}
/// Helper type to serialize and deserialize the pod2 `CommonCircuitData` using serde traits.
#[derive(Clone)]
pub struct CommonCircuitDataSerializer(pub(crate) CommonCircuitData);
impl Deref for CommonCircuitDataSerializer {
type Target = CommonCircuitData;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for CommonCircuitDataSerializer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let gate_serializer = Pod2GateSerializer {};
let bytes = self
.0
.to_bytes(&gate_serializer)
.map_err(ser::Error::custom)?;
serde_bytes::ByteBuf::from(bytes).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CommonCircuitDataSerializer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <&'de serde_bytes::Bytes>::deserialize(deserializer)?;
let gate_serializer = Pod2GateSerializer {};
let circuit_data =
CommonCircuitData::from_bytes(bytes, &gate_serializer).map_err(de::Error::custom)?;
Ok(CommonCircuitDataSerializer(circuit_data))
}
}
/// Helper type to serialize and deserialize the pod2 `VerifierCircuitData` using serde traits.
#[derive(Clone)]
pub struct VerifierCircuitDataSerializer(pub(crate) VerifierCircuitData);
impl Deref for VerifierCircuitDataSerializer {
type Target = VerifierCircuitData;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for VerifierCircuitDataSerializer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let gate_serializer = Pod2GateSerializer {};
let bytes = self
.0
.to_bytes(&gate_serializer)
.map_err(ser::Error::custom)?;
serde_bytes::ByteBuf::from(bytes).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for VerifierCircuitDataSerializer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes = <&'de serde_bytes::Bytes>::deserialize(deserializer)?;
let gate_serializer = Pod2GateSerializer {};
let circuit_data =
VerifierCircuitData::from_bytes(bytes, &gate_serializer).map_err(de::Error::custom)?;
Ok(VerifierCircuitDataSerializer(circuit_data))
}
}

120
src/cache/disk.rs vendored Normal file
View file

@ -0,0 +1,120 @@
use std::{
fs::{create_dir_all, rename, File, TryLockError},
io::{Error, ErrorKind, Read, Write},
ops::Deref,
thread, time,
};
use directories::BaseDirs;
use serde::{de::DeserializeOwned, Serialize};
use sha2::{Digest, Sha256};
pub struct CacheEntry<T> {
value: T,
}
impl<T> Deref for CacheEntry<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
/// Get the artifact named `name` from the disk cache. If it doesn't exist, it will be built by
/// calling `build_fn` and stored.
/// The artifact is indexed by git commit first and then by `params: P` second.
pub(crate) fn get<T: Serialize + DeserializeOwned, P: Serialize>(
name: &str,
params: &P,
build_fn: fn(&P) -> T,
) -> Result<CacheEntry<T>, Box<dyn std::error::Error>> {
let commit_hash_str = env!("VERGEN_GIT_SHA");
let params_json = serde_json::to_string(params)?;
let params_json_hash = Sha256::digest(&params_json);
let params_json_hash_str_long = format!("{:x}", params_json_hash);
let params_json_hash_str = format!("{}", &params_json_hash_str_long[..32]);
let log_name = format!("{}/{}/{}.cbor", commit_hash_str, params_json_hash_str, name);
log::debug!("getting {} from the disk cache", log_name);
let base_dirs =
BaseDirs::new().ok_or(Error::new(ErrorKind::Other, "no valid home directory"))?;
let user_cache_dir = base_dirs.cache_dir();
let pod2_cache_dir = user_cache_dir.join("pod2");
let commit_cache_dir = pod2_cache_dir.join(&commit_hash_str);
create_dir_all(&commit_cache_dir)?;
let cache_dir = commit_cache_dir.join(&params_json_hash_str);
create_dir_all(&cache_dir)?;
// Store the params.json if it doesn't exist for better debuggability
let params_path = cache_dir.join("params.json");
if !params_path.try_exists()? {
// First write the file to .tmp and then rename to avoid a corrupted file if we crash in
// the middle of the write.
let params_path_tmp = cache_dir.join("params.json.tmp");
let mut file = File::create(&params_path_tmp)?;
file.write_all(params_json.as_bytes())?;
rename(params_path_tmp, params_path)?;
}
let cache_path = cache_dir.join(format!("{}.cbor", name));
let cache_path_tmp = cache_dir.join(format!("{}.cbor.tmp", name));
// First try to open the cached file. If it exists we assume a previous build+cache succeeded
// so we read, deserialize it and return it.
// If it doesn't exist we open a corresponding tmp file and try to acquire it exclusively. If
// we can't acquire it means another process is building the artifact so we retry again in 100
// ms. If we acquire the lock we build the artifact store it in the tmp file and finally
// rename it to the final cached file. This way the final cached file either exists and is
// complete or doesn't exist at all (in case of a crash the corruputed file will be tmp).
loop {
let mut file = match File::open(&cache_path) {
Ok(file) => file,
Err(err) => {
if err.kind() == ErrorKind::NotFound {
let mut file_tmp = File::create(&cache_path_tmp)?;
match file_tmp.try_lock() {
Ok(_) => (),
Err(TryLockError::WouldBlock) => {
// Lock not acquired. Another process is building the artifact, let's
// try again in 100 ms.
thread::sleep(time::Duration::from_millis(100));
continue;
}
Err(TryLockError::Error(err)) => return Err(Box::new(err)),
}
// Exclusive lock acquired, build the artifact, serialize it and store it.
log::info!("building {} and storing to the disk cache", log_name);
let start = std::time::Instant::now();
let data = build_fn(params);
let elapsed = std::time::Instant::now() - start;
log::debug!("built {} in {:?}", log_name, elapsed);
let data_cbor = minicbor_serde::to_vec(&data)?;
// First write the file to .tmp and then rename to avoid a corrupted file if we
// crash in the middle of the write.
file_tmp.write_all(&data_cbor)?;
rename(cache_path_tmp, cache_path)?;
return Ok(CacheEntry { value: data });
} else {
return Err(Box::new(err));
}
}
};
log::debug!("found {} in the disk cache", log_name);
let start = std::time::Instant::now();
let mut data_cbor = Vec::new();
file.read_to_end(&mut data_cbor)?;
let elapsed = std::time::Instant::now() - start;
log::debug!("read {} from disk in {:?}", log_name, elapsed);
let start = std::time::Instant::now();
let data: T = minicbor_serde::from_slice(&data_cbor)?;
let elapsed = std::time::Instant::now() - start;
log::debug!("deserialized {} in {:?}", log_name, elapsed);
return Ok(CacheEntry { value: data });
}
}

83
src/cache/mem.rs vendored Normal file
View file

@ -0,0 +1,83 @@
use std::{
any::Any,
collections::HashMap,
ops::Deref,
sync::{LazyLock, Mutex},
thread, time,
};
use serde::{de::DeserializeOwned, Serialize};
use sha2::{Digest, Sha256};
#[allow(clippy::type_complexity)]
static CACHE: LazyLock<Mutex<HashMap<String, Option<Box<dyn Any + Send + 'static>>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub struct CacheEntry<T: 'static> {
value: &'static T,
}
impl<T> Deref for CacheEntry<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
/// Get the artifact named `name` from the memory cache. If it doesn't exist, it will be built by
/// calling `build_fn` and stored.
/// The artifact is indexed by `params: P`.
pub(crate) fn get<T: Serialize + DeserializeOwned + Send + 'static, P: Serialize>(
name: &str,
params: &P,
build_fn: fn(&P) -> T,
) -> Result<CacheEntry<T>, Box<dyn std::error::Error>> {
let params_json = serde_json::to_string(params)?;
let params_json_hash = Sha256::digest(&params_json);
let params_json_hash_str_long = format!("{:x}", params_json_hash);
let key = format!("{}/{}", &params_json_hash_str_long[..32], name);
log::debug!("getting {} from the mem cache", name);
loop {
let mut cache = CACHE.lock()?;
if let Some(entry) = cache.get(&key) {
if let Some(boxed_data) = entry {
if let Some(data) = boxed_data.downcast_ref::<T>() {
log::debug!("found {} in the mem cache", name);
// The data is now in the heap (boxed), and will never go away because we can
// only insert into the CACHE if there's no entry, we can't delete nor update.
// Since it's not going away, not moving, and the CACHE is 'static, it's safe
// to extend the lifetime of data to 'static.
let data_static = unsafe { std::mem::transmute::<&T, &'static T>(data) };
return Ok(CacheEntry { value: data_static });
} else {
panic!(
"type={} doesn't match the type in the cached boxed value with name={}",
std::any::type_name::<T>(),
name
);
}
} else {
// Another thread is building this entry, let's retry again in 100 ms
drop(cache); // release the lock
thread::sleep(time::Duration::from_millis(100));
continue;
}
}
// No entry in the cache, let's put a `None` to signal that we're building the
// artifact, release the lock, build the artifact and insert it. We do this to avoid
// locking for a long time.
cache.insert(key.clone(), None);
drop(cache); // release the lock
log::info!("building {} and storing to the mem cache", name);
let start = std::time::Instant::now();
let data = build_fn(params);
let elapsed = std::time::Instant::now() - start;
log::debug!("built {} in {:?}", name, elapsed);
CACHE.lock()?.insert(key, Some(Box::new(data)));
// Call `get` again and this time we'll retrieve the data from the cache
return get(name, params, build_fn);
}
}

9
src/cache/mod.rs vendored Normal file
View file

@ -0,0 +1,9 @@
#[cfg(feature = "disk_cache")]
mod disk;
#[cfg(feature = "disk_cache")]
pub(crate) use disk::{get, CacheEntry};
#[cfg(feature = "mem_cache")]
mod mem;
#[cfg(feature = "mem_cache")]
pub(crate) use mem::{get, CacheEntry};

View file

@ -119,7 +119,9 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
mainpod::Prover, mock::mainpod::MockProver, primitives::ec::schnorr::SecretKey, mainpod::{rec_main_pod_circuit_data, Prover},
mock::mainpod::MockProver,
primitives::ec::schnorr::SecretKey,
signedpod::Signer, signedpod::Signer,
}, },
examples::{ examples::{
@ -130,7 +132,7 @@ mod tests {
middleware::{ middleware::{
self, self,
containers::{Array, Dictionary, Set}, containers::{Array, Dictionary, Set},
Params, TypedValue, DEFAULT_VD_SET, Params, TypedValue, DEFAULT_VD_LIST,
}, },
}; };
@ -300,7 +302,9 @@ mod tests {
max_input_recursive_pods: 1, max_input_recursive_pods: 1,
..Default::default() ..Default::default()
}; };
let vd_set = &*DEFAULT_VD_SET; let mut vds = DEFAULT_VD_LIST.clone();
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, sanction_list_builder) = let (gov_id_builder, pay_stub_builder, sanction_list_builder) =
zu_kyc_sign_pod_builders(&params); zu_kyc_sign_pod_builders(&params);
@ -312,7 +316,7 @@ mod tests {
let sanction_list_pod = sanction_list_builder.sign(&signer)?; let sanction_list_pod = sanction_list_builder.sign(&signer)?;
let kyc_builder = zu_kyc_pod_builder( let kyc_builder = zu_kyc_pod_builder(
&params, &params,
vd_set, &vd_set,
&gov_id_pod, &gov_id_pod,
&pay_stub_pod, &pay_stub_pod,
&sanction_list_pod, &sanction_list_pod,

View file

@ -40,12 +40,11 @@ mod tests {
} }
fn assert_fails(rule: Rule, input: &str) { fn assert_fails(rule: Rule, input: &str) {
match PodlangParser::parse(rule, input) { if let Ok(pairs) = PodlangParser::parse(rule, input) {
Ok(pairs) => panic!( panic!(
"Expected parse to fail, but it succeeded. Parsed:\n{:#?}", "Expected parse to fail, but it succeeded. Parsed:\n{:#?}",
pairs pairs
), )
Err(_) => (), // Failed as expected
} }
} }

View file

@ -975,7 +975,7 @@ mod processor_tests {
middleware::Params, middleware::Params,
}; };
fn get_document_content_pairs(input: &str) -> Result<Pairs<Rule>, ProcessorError> { fn get_document_content_pairs(input: &str) -> Result<Pairs<'_, Rule>, ProcessorError> {
let full_parse_tree = parse_podlang(input) let full_parse_tree = parse_podlang(input)
.map_err(|e| ProcessorError::Internal(format!("Test parsing failed: {:?}", e)))?; .map_err(|e| ProcessorError::Internal(format!("Test parsing failed: {:?}", e)))?;

View file

@ -1,8 +1,11 @@
#![allow(clippy::get_first)] #![allow(clippy::get_first)]
#![feature(trait_upcasting)] #![allow(clippy::uninlined_format_args)] // TODO: Remove this in another PR
#![allow(clippy::manual_repeat_n)] // TODO: Remove this in another PR
#![allow(clippy::large_enum_variant)] // TODO: Remove this in another PR
#![feature(mapped_lock_guards)] #![feature(mapped_lock_guards)]
pub mod backends; pub mod backends;
mod cache;
pub mod frontend; pub mod frontend;
pub mod lang; pub mod lang;
pub mod middleware; pub mod middleware;

View file

@ -358,7 +358,7 @@ impl Eq for Value {}
impl PartialOrd for Value { impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.raw.cmp(&other.raw)) Some(self.cmp(other))
} }
} }