Integrate recursion into MainPod (#243)
* calculate MainPod id in a dynamic-friendly way The MainPod id is now calculated with front padding and a fixed size independent of max_public_statements so that introduction gadgets can be verified by a MainPod while paying only for the number of statements they use. This is because with front padding of none-statements we can precompute the poseidon state corresponding to absorbing all the padding statements and only pay constraints for the non-padding statements. The id is calculated as follows: `id = hash(serialize(reverse(statements || none-statements)))` * add time feature and disable timing by default * apply suggestions from @arnaucube * link issues in todos
This commit is contained in:
parent
d3fef8392e
commit
88a75986b8
23 changed files with 1405 additions and 729 deletions
|
|
@ -1,45 +1,30 @@
|
|||
// TODO: Update this doc
|
||||
//! This file exposes the backend dependent basetypes as middleware types,
|
||||
//! taking them from the feature-enabled backend.
|
||||
//! This file exposes the imported backend dependent basetypes as middleware types, taking them
|
||||
//! from the feature-enabled backend.
|
||||
//!
|
||||
//! This is done in order to avoid inconsistencies where a type or parameter is
|
||||
//! defined in the middleware to have certain carachteristic and later in the
|
||||
//! backend it gets used differently. The idea is that those types and
|
||||
//! parameters (eg. lengths) have a single source of truth in the code; and in
|
||||
//! the case of the "base types" this is determined by the backend being used
|
||||
//! under the hood, not by a choice of the middleware parameters.
|
||||
//! This is done in order to avoid inconsistencies where a type or parameter is defined in the
|
||||
//! middleware to have certain carachteristic and later in the backend it gets used differently.
|
||||
//! The idea is that those types and parameters (eg. lengths) have a single source of truth in the
|
||||
//! code; and in the case of the "base types" this is determined by the backend being used under
|
||||
//! the hood, not by a choice of the middleware parameters.
|
||||
//!
|
||||
//! The idea with this approach, is that the frontend & middleware should not
|
||||
//! need to import the proving library used by the backend (eg. plonky2,
|
||||
//! plonky3, etc).
|
||||
//! The idea with this approach, is that the frontend & middleware should not need to import the
|
||||
//! proving library used by the backend (eg. plonky2, plonky3, etc).
|
||||
//!
|
||||
//! For example, the `Hash` and `Value` types are types belonging at the
|
||||
//! middleware, and is the middleware who reasons about them, but depending on
|
||||
//! the backend being used, the `Hash` and `Value` types will have different
|
||||
//! sizes. So it's the backend being used who actually defines their nature
|
||||
//! under the hood. For example with a plonky2 backend, these types will have a
|
||||
//! length of 4 field elements, whereas with a plonky3 backend they will have a
|
||||
//! length of 8 field eleements.
|
||||
//! For example, the `Hash` and `Value` types are types belonging at the middleware, and is the
|
||||
//! middleware who reasons about them, but depending on the backend being used, the `Hash` and
|
||||
//! `Value` types will have different sizes. So it's the backend being used who actually defines
|
||||
//! their nature under the hood. For example with a plonky2 backend, these types will have a length
|
||||
//! of 4 field elements, whereas with a plonky3 backend they will have a length of 8 field
|
||||
//! eleements.
|
||||
//!
|
||||
//! Note that his approach does not introduce new traits or abstract code,
|
||||
//! just makes use of rust features to define 'base types' that are being used
|
||||
//! in the middleware.
|
||||
//! Note that his approach does not introduce new traits or abstract code, just makes use of rust
|
||||
//! features to define 'base types' that are being used in the middleware.
|
||||
//!
|
||||
//!
|
||||
//! NOTE (TMP): current implementation still uses plonky2 in the middleware for
|
||||
//! u64/i64 to F conversion. Eventually we will do those conversions through the
|
||||
//! approach described in this file, removing the imports of plonky2 in the
|
||||
//! middleware.
|
||||
//! TODO: Update this doc
|
||||
//! NOTE (TMP): current implementation still uses plonky2 in the middleware for u64/i64 to F
|
||||
//! conversion. Eventually we will do those conversions through the approach described in this
|
||||
//! file, removing the imports of plonky2 in the middleware.
|
||||
|
||||
/// Value, Hash and F are imported based on 'features'. For example by default
|
||||
/// we use the 'plonky2' feature, but it could be used a 'plonky3' feature, so
|
||||
/// then the Value, Hash and F types would come from the plonky3 backend.
|
||||
// #[cfg(feature = "backend_plonky2")]
|
||||
// pub use crate::backends::plonky2::basetypes::{
|
||||
// hash_fields, hash_str, hash_value, Hash, RawValue, EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE,
|
||||
// SELF_ID_HASH, VALUE_SIZE,
|
||||
// };
|
||||
use std::{
|
||||
cmp::{Ord, Ordering},
|
||||
fmt,
|
||||
|
|
@ -47,10 +32,7 @@ use std::{
|
|||
|
||||
use hex::{FromHex, FromHexError};
|
||||
use plonky2::{
|
||||
field::{
|
||||
goldilocks_field::GoldilocksField,
|
||||
types::{Field, PrimeField64},
|
||||
},
|
||||
field::types::{Field, PrimeField64},
|
||||
hash::poseidon::PoseidonHash,
|
||||
plonk::config::Hasher,
|
||||
};
|
||||
|
|
@ -58,11 +40,14 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::serialization::*;
|
||||
// Plonky2 specific types.
|
||||
// Value, Hash, F and other types are imported based on 'features'. For example by default we use
|
||||
// theg'plonky2' feature, but it could be used a 'plonky3' feature, so then the Value, Hash and F
|
||||
// types would come from the plonky3 backend.
|
||||
#[cfg(feature = "backend_plonky2")]
|
||||
pub use crate::backends::plonky2::basetypes::*;
|
||||
use crate::middleware::{Params, ToFields, Value};
|
||||
|
||||
/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2
|
||||
pub type F = GoldilocksField;
|
||||
|
||||
pub const HASH_SIZE: usize = 4;
|
||||
pub const VALUE_SIZE: usize = 4;
|
||||
|
||||
|
|
|
|||
|
|
@ -558,8 +558,10 @@ pub enum PodType {
|
|||
None = 0,
|
||||
MockSigned = 1,
|
||||
MockMain = 2,
|
||||
Signed = 3,
|
||||
Main = 4,
|
||||
MockEmpty = 3,
|
||||
Signed = 4,
|
||||
Main = 5,
|
||||
Empty = 6,
|
||||
}
|
||||
|
||||
impl fmt::Display for PodType {
|
||||
|
|
@ -568,45 +570,56 @@ impl fmt::Display for PodType {
|
|||
PodType::None => write!(f, "None"),
|
||||
PodType::MockSigned => write!(f, "MockSigned"),
|
||||
PodType::MockMain => write!(f, "MockMain"),
|
||||
PodType::MockEmpty => write!(f, "MockEmpty"),
|
||||
PodType::Signed => write!(f, "Signed"),
|
||||
PodType::Main => write!(f, "Main"),
|
||||
PodType::Empty => write!(f, "Empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Params {
|
||||
pub max_input_signed_pods: usize,
|
||||
pub max_input_main_pods: usize,
|
||||
pub max_input_recursive_pods: usize,
|
||||
pub max_input_pods_public_statements: usize,
|
||||
pub max_statements: usize,
|
||||
pub max_signed_pod_values: usize,
|
||||
pub max_public_statements: usize,
|
||||
// Number of public statements to hash to calculate the id. Must be equal or greater than
|
||||
// `max_public_statements`.
|
||||
pub num_public_statements_id: usize,
|
||||
pub max_statement_args: usize,
|
||||
pub max_operation_args: usize,
|
||||
// max number of custom predicates batches that a MainPod can use
|
||||
pub max_custom_predicate_batches: usize,
|
||||
// max number of operations using custom predicates that can be verified in the MainPod
|
||||
pub max_custom_predicate_verifications: usize,
|
||||
// max number of statements that can be ANDed or ORed together
|
||||
// in a custom predicate
|
||||
pub max_custom_predicate_arity: usize,
|
||||
pub max_custom_predicate_wildcards: usize,
|
||||
pub max_custom_batch_size: usize,
|
||||
// maximum number of merkle proofs
|
||||
pub max_merkle_proofs: usize,
|
||||
// maximum depth for merkle tree gadget
|
||||
pub max_depth_mt_gadget: usize,
|
||||
//
|
||||
// The following parameters define how a pod id is calculated. They need to be the same among
|
||||
// different circuits to be compatible in their verification.
|
||||
//
|
||||
// Number of public statements to hash to calculate the id. Must be equal or greater than
|
||||
// `max_public_statements`.
|
||||
pub num_public_statements_id: usize,
|
||||
pub max_statement_args: usize,
|
||||
//
|
||||
// The following parameters define how a custom predicate batch id is calculated.
|
||||
//
|
||||
// max number of statements that can be ANDed or ORed together
|
||||
// in a custom predicate
|
||||
pub max_custom_predicate_arity: usize,
|
||||
pub max_custom_batch_size: usize,
|
||||
}
|
||||
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_input_signed_pods: 3,
|
||||
max_input_main_pods: 3,
|
||||
max_input_recursive_pods: 2,
|
||||
max_input_pods_public_statements: 10,
|
||||
max_statements: 20,
|
||||
max_signed_pod_values: 8,
|
||||
max_public_statements: 10,
|
||||
|
|
@ -661,6 +674,16 @@ impl Params {
|
|||
self.max_custom_batch_size * self.custom_predicate_size()
|
||||
}
|
||||
|
||||
/// Parameters that define how the id is calculated
|
||||
pub fn id_params(&self) -> Vec<usize> {
|
||||
vec![
|
||||
self.num_public_statements_id,
|
||||
self.max_statement_args,
|
||||
self.max_custom_predicate_arity,
|
||||
self.max_custom_batch_size,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn print_serialized_sizes(&self) {
|
||||
println!("Parameter sizes:");
|
||||
println!(
|
||||
|
|
@ -678,12 +701,39 @@ impl Params {
|
|||
}
|
||||
}
|
||||
|
||||
/// Replace references to SELF by `self_id` in anchored keys of the statement.
|
||||
pub fn normalize_statement(statement: &Statement, self_id: PodId) -> Statement {
|
||||
let predicate = statement.predicate();
|
||||
let args = statement
|
||||
.args()
|
||||
.iter()
|
||||
.map(|sa| match &sa {
|
||||
StatementArg::Key(AnchoredKey { pod_id, key }) if *pod_id == SELF => {
|
||||
StatementArg::Key(AnchoredKey::new(self_id, key.clone()))
|
||||
}
|
||||
_ => sa.clone(),
|
||||
})
|
||||
.collect();
|
||||
Statement::from_args(predicate, args).expect("statement was valid before normalization")
|
||||
}
|
||||
|
||||
pub type DynError = dyn std::error::Error + Send + Sync;
|
||||
|
||||
pub trait Pod: fmt::Debug + DynClone + Any {
|
||||
fn params(&self) -> &Params;
|
||||
fn verify(&self) -> Result<(), Box<DynError>>;
|
||||
fn id(&self) -> PodId;
|
||||
fn pub_statements(&self) -> Vec<Statement>;
|
||||
/// Statements as internally generated, where self-referencing arguments use SELF in the
|
||||
/// anchored key. The serialization of these statements is used to calculate the id.
|
||||
fn pub_self_statements(&self) -> Vec<Statement>;
|
||||
/// Normalized statements, where self-referencing arguments use the pod id instead of SELF in
|
||||
/// the anchored key.
|
||||
fn pub_statements(&self) -> Vec<Statement> {
|
||||
self.pub_self_statements()
|
||||
.into_iter()
|
||||
.map(|statement| normalize_statement(&statement, self.id()))
|
||||
.collect()
|
||||
}
|
||||
/// Extract key-values from ValueOf public statements
|
||||
fn kvs(&self) -> HashMap<AnchoredKey, Value> {
|
||||
self.pub_statements()
|
||||
|
|
@ -708,9 +758,21 @@ pub trait Pod: fmt::Debug + DynClone + Any {
|
|||
fn serialized_proof(&self) -> String;
|
||||
}
|
||||
|
||||
// impl Clone for Box<dyn SignedPod>
|
||||
// impl Clone for Box<dyn Pod>
|
||||
dyn_clone::clone_trait_object!(Pod);
|
||||
|
||||
/// Trait for pods that are generated with a plonky2 circuit and that can be verified by a
|
||||
/// recursive MainPod circuit. A Pod implementing this trait does not necesarilly come from
|
||||
/// recursion: for example an introduction Pod in general is not recursive.
|
||||
pub trait RecursivePod: Pod {
|
||||
fn verifier_data(&self) -> VerifierOnlyCircuitData;
|
||||
fn proof(&self) -> Proof;
|
||||
fn vds_root(&self) -> Hash;
|
||||
}
|
||||
|
||||
// impl Clone for Box<dyn RecursivePod>
|
||||
dyn_clone::clone_trait_object!(RecursivePod);
|
||||
|
||||
pub trait PodSigner {
|
||||
fn sign(
|
||||
&mut self,
|
||||
|
|
@ -719,19 +781,24 @@ pub trait PodSigner {
|
|||
) -> Result<Box<dyn Pod>, Box<DynError>>;
|
||||
}
|
||||
|
||||
// TODO: Delete once we have a fully working EmptyPod and a dumb SignedPod
|
||||
// https://github.com/0xPARC/pod2/issues/246
|
||||
/// This is a filler type that fulfills the Pod trait and always verifies. It's empty. This
|
||||
/// can be used to simulate padding in a circuit.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NonePod {}
|
||||
|
||||
impl Pod for NonePod {
|
||||
fn params(&self) -> &Params {
|
||||
panic!("NonePod doesn't have params");
|
||||
}
|
||||
fn verify(&self) -> Result<(), Box<DynError>> {
|
||||
Ok(())
|
||||
}
|
||||
fn id(&self) -> PodId {
|
||||
PodId(EMPTY_HASH)
|
||||
}
|
||||
fn pub_statements(&self) -> Vec<Statement> {
|
||||
fn pub_self_statements(&self) -> Vec<Statement> {
|
||||
Vec::new()
|
||||
}
|
||||
fn serialized_proof(&self) -> String {
|
||||
|
|
@ -742,20 +809,21 @@ impl Pod for NonePod {
|
|||
#[derive(Debug)]
|
||||
pub struct MainPodInputs<'a> {
|
||||
pub signed_pods: &'a [&'a dyn Pod],
|
||||
pub main_pods: &'a [&'a dyn Pod],
|
||||
pub recursive_pods: &'a [&'a dyn RecursivePod],
|
||||
pub statements: &'a [Statement],
|
||||
pub operations: &'a [Operation],
|
||||
/// Statements that need to be made public (they can come from input pods or input
|
||||
/// statements)
|
||||
pub public_statements: &'a [Statement],
|
||||
pub vds_root: Hash, // TODO: Figure out if we use Hash or a Map here https://github.com/0xPARC/pod2/issues/249
|
||||
}
|
||||
|
||||
pub trait PodProver {
|
||||
fn prove(
|
||||
&mut self,
|
||||
&self,
|
||||
params: &Params,
|
||||
inputs: MainPodInputs,
|
||||
) -> Result<Box<dyn Pod>, Box<DynError>>;
|
||||
) -> Result<Box<dyn RecursivePod>, Box<DynError>>;
|
||||
}
|
||||
|
||||
pub trait ToFields {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue