Support mixed depth container merkle proofs (#508)
Some checks failed
Rust Build with features / Rust tests (push) Has been cancelled
Clippy Check / Rust formatting (push) Has been cancelled
Publish MainPod circuit info / Update Wiki with new MainPod circuit info (push) Has been cancelled
Check mdbook compilation / compile (push) Has been cancelled
Publish mdbook / build (push) Has been cancelled
Rustfmt Check / Rust formatting (push) Has been cancelled
Rust Tests / Rust tests (push) Has been cancelled
typos / Spell Check with Typos (push) Has been cancelled
Publish mdbook / deploy (push) Has been cancelled

* remove enabled flag from merkle tree proofs

* add small existence mpt proofs in MainPod

* refactor params, add small transition proofs

* complete

* fix edge case in vdset

* fix: use existence only proof for vdset

* use consistent order for aux table
This commit is contained in:
Eduard S. 2026-05-06 12:39:27 +02:00 committed by GitHub
parent 111b132a00
commit 5e3ac9a101
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2366 additions and 2189 deletions

View file

@ -51,7 +51,7 @@ use crate::{
mainpod::cache_get_rec_main_pod_verifier_circuit_data, mainpod::cache_get_rec_main_pod_verifier_circuit_data,
primitives::merkletree::MerkleClaimAndProof, primitives::merkletree::MerkleClaimAndProof,
}, },
middleware::{containers::Array, Hash, Params, RawValue, Result, Value}, middleware::{containers::Array, Hash, Params, RawValue, Result, Value, EMPTY_HASH},
}; };
pub static DEFAULT_VD_LIST: LazyLock<Vec<VerifierOnlyCircuitData>> = LazyLock::new(|| { pub static DEFAULT_VD_LIST: LazyLock<Vec<VerifierOnlyCircuitData>> = LazyLock::new(|| {
@ -95,6 +95,12 @@ impl Eq for VDSet {}
impl VDSet { impl VDSet {
fn new_from_vds_hashes(mut vds_hashes: Vec<Hash>) -> Self { fn new_from_vds_hashes(mut vds_hashes: Vec<Hash>) -> Self {
// If vds_hashes is empty we add an zero entry to be used as padding when verifying merkle
// proofs of inclusion in the vds set. This zero entry can't be abused because no circuit
// exists with a vds_hash = 0.
if vds_hashes.is_empty() {
vds_hashes.push(EMPTY_HASH);
}
// before using the hash values, sort them, so that each set of // before using the hash values, sort them, so that each set of
// verifier_datas gets the same VDSet root // verifier_datas gets the same VDSet root
vds_hashes.sort(); vds_hashes.sort();
@ -150,6 +156,9 @@ impl VDSet {
))? ))?
.clone()) .clone())
} }
pub fn get_vds_proof_0(&self) -> MerkleClaimAndProof {
self.proofs_map[&self.vds_hashes[0]].clone()
}
/// Returns true if the `verifier_data_hash` is in the set /// Returns true if the `verifier_data_hash` is in the set
pub fn contains(&self, verifier_data_hash: HashOut) -> bool { pub fn contains(&self, verifier_data_hash: HashOut) -> bool {
self.proofs_map self.proofs_map

View file

@ -30,7 +30,7 @@ use crate::{
mainpod::{Operation, OperationArg, OperationAux, Statement}, mainpod::{Operation, OperationArg, OperationAux, Statement},
primitives::merkletree::{ primitives::merkletree::{
verify_merkle_proof_circuit, MerkleClaimAndProof, MerkleClaimAndProofTarget, verify_merkle_proof_circuit, MerkleClaimAndProof, MerkleClaimAndProofTarget,
MerkleProof, MerkleTreeStateTransitionProofTarget, MerkleProof, MerkleProofExistenceTarget, MerkleTreeStateTransitionProofTarget,
}, },
}, },
middleware::{ middleware::{
@ -725,7 +725,6 @@ impl CustomPredicateInBatchTarget {
let mtp = let mtp =
MerkleClaimAndProofTarget::new_virtual(Params::max_depth_custom_batch_mt(), builder); MerkleClaimAndProofTarget::new_virtual(Params::max_depth_custom_batch_mt(), builder);
let _true = builder._true(); let _true = builder._true();
builder.connect(_true.target, mtp.enabled.target);
builder.connect(_true.target, mtp.existence.target); builder.connect(_true.target, mtp.existence.target);
let zero = builder.constant(F(0)); let zero = builder.constant(F(0));
let key = ValueTarget { let key = ValueTarget {
@ -763,7 +762,7 @@ impl CustomPredicateInBatchTarget {
value: RawValue::from(hash_fields(&predicate.to_fields())), value: RawValue::from(hash_fields(&predicate.to_fields())),
proof: mtp.clone(), proof: mtp.clone(),
}; };
self.mtp.set_targets(pw, true, &mtp_claim)?; self.mtp.set_targets(pw, &mtp_claim)?;
Ok(()) Ok(())
} }
} }
@ -987,7 +986,6 @@ pub trait Flattenable {
/// elsewhere. /// elsewhere.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct MerkleClaimTarget { pub struct MerkleClaimTarget {
pub(crate) enabled: BoolTarget,
pub(crate) root: HashOutTarget, pub(crate) root: HashOutTarget,
pub(crate) key: ValueTarget, pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget, pub(crate) value: ValueTarget,
@ -997,7 +995,6 @@ pub struct MerkleClaimTarget {
impl From<MerkleClaimAndProofTarget> for MerkleClaimTarget { impl From<MerkleClaimAndProofTarget> for MerkleClaimTarget {
fn from(pf: MerkleClaimAndProofTarget) -> Self { fn from(pf: MerkleClaimAndProofTarget) -> Self {
Self { Self {
enabled: pf.enabled,
root: pf.root, root: pf.root,
key: pf.key, key: pf.key,
value: pf.value, value: pf.value,
@ -1006,12 +1003,25 @@ impl From<MerkleClaimAndProofTarget> for MerkleClaimTarget {
} }
} }
impl MerkleClaimTarget {
pub fn from_proof_existence(
builder: &mut CircuitBuilder,
pf: MerkleProofExistenceTarget,
) -> Self {
Self {
root: pf.root,
key: pf.key,
value: pf.value,
existence: builder._true(),
}
}
}
/// For the purpose of op verification, we need only look up the /// For the purpose of op verification, we need only look up the
/// Merkle state transition claim rather than the Merkle state /// Merkle state transition claim rather than the Merkle state
/// transition proof since it is verified elsewhere. /// transition proof since it is verified elsewhere.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct MerkleTreeStateTransitionClaimTarget { pub struct MerkleTreeStateTransitionClaimTarget {
pub(crate) enabled: BoolTarget,
pub(crate) op: Target, pub(crate) op: Target,
pub(crate) old_root: HashOutTarget, pub(crate) old_root: HashOutTarget,
pub(crate) new_root: HashOutTarget, pub(crate) new_root: HashOutTarget,
@ -1022,7 +1032,6 @@ pub struct MerkleTreeStateTransitionClaimTarget {
impl From<MerkleTreeStateTransitionProofTarget> for MerkleTreeStateTransitionClaimTarget { impl From<MerkleTreeStateTransitionProofTarget> for MerkleTreeStateTransitionClaimTarget {
fn from(pf: MerkleTreeStateTransitionProofTarget) -> Self { fn from(pf: MerkleTreeStateTransitionProofTarget) -> Self {
Self { Self {
enabled: pf.enabled,
op: pf.op, op: pf.op,
old_root: pf.old_root, old_root: pf.old_root,
new_root: pf.new_root, new_root: pf.new_root,
@ -1063,7 +1072,6 @@ impl Flattenable for ValueTarget {
impl Flattenable for MerkleClaimTarget { impl Flattenable for MerkleClaimTarget {
fn flatten(&self) -> Vec<Target> { fn flatten(&self) -> Vec<Target> {
[ [
vec![self.enabled.target],
self.root.elements.to_vec(), self.root.elements.to_vec(),
self.key.elements.to_vec(), self.key.elements.to_vec(),
self.value.elements.to_vec(), self.value.elements.to_vec(),
@ -1075,31 +1083,28 @@ impl Flattenable for MerkleClaimTarget {
fn from_flattened(params: &Params, vs: &[Target]) -> Self { fn from_flattened(params: &Params, vs: &[Target]) -> Self {
assert_eq!(vs.len(), Self::size(params)); assert_eq!(vs.len(), Self::size(params));
Self { Self {
enabled: BoolTarget::new_unsafe(vs[0]), root: HashOutTarget::from_vec(vs[0..NUM_HASH_OUT_ELTS].to_vec()),
root: HashOutTarget::from_vec(vs[1..1 + NUM_HASH_OUT_ELTS].to_vec()), key: ValueTarget::from_slice(&vs[NUM_HASH_OUT_ELTS..NUM_HASH_OUT_ELTS + VALUE_SIZE]),
key: ValueTarget::from_slice(
&vs[1 + NUM_HASH_OUT_ELTS..1 + NUM_HASH_OUT_ELTS + VALUE_SIZE],
),
value: ValueTarget::from_slice( value: ValueTarget::from_slice(
&vs[1 + NUM_HASH_OUT_ELTS + VALUE_SIZE..1 + NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE], &vs[NUM_HASH_OUT_ELTS + VALUE_SIZE..NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE],
), ),
existence: BoolTarget::new_unsafe(vs[1 + NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE]), existence: BoolTarget::new_unsafe(vs[NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE]),
} }
} }
fn size(params: &Params) -> usize { fn size(params: &Params) -> usize {
2 + HashOutTarget::size(params) + 2 * ValueTarget::size(params) HashOutTarget::size(params) + 2 * ValueTarget::size(params) + 1
} }
} }
impl Flattenable for MerkleTreeStateTransitionClaimTarget { impl Flattenable for MerkleTreeStateTransitionClaimTarget {
fn flatten(&self) -> Vec<Target> { fn flatten(&self) -> Vec<Target> {
[ [
vec![self.enabled.target, self.op],
self.old_root.elements.to_vec(), self.old_root.elements.to_vec(),
self.new_root.elements.to_vec(), self.new_root.elements.to_vec(),
self.op_key.elements.to_vec(), self.op_key.elements.to_vec(),
self.op_value.elements.to_vec(), self.op_value.elements.to_vec(),
vec![self.op],
] ]
.concat() .concat()
} }
@ -1107,24 +1112,22 @@ impl Flattenable for MerkleTreeStateTransitionClaimTarget {
fn from_flattened(params: &Params, vs: &[Target]) -> Self { fn from_flattened(params: &Params, vs: &[Target]) -> Self {
assert_eq!(vs.len(), Self::size(params)); assert_eq!(vs.len(), Self::size(params));
Self { Self {
enabled: BoolTarget::new_unsafe(vs[0]), old_root: HashOutTarget::from_vec(vs[0..NUM_HASH_OUT_ELTS].to_vec()),
op: vs[1],
old_root: HashOutTarget::from_vec(vs[2..2 + NUM_HASH_OUT_ELTS].to_vec()),
new_root: HashOutTarget::from_vec( new_root: HashOutTarget::from_vec(
vs[2 + NUM_HASH_OUT_ELTS..2 * (1 + NUM_HASH_OUT_ELTS)].to_vec(), vs[NUM_HASH_OUT_ELTS..2 * NUM_HASH_OUT_ELTS].to_vec(),
), ),
op_key: ValueTarget::from_slice( op_key: ValueTarget::from_slice(
&vs[2 * (1 + NUM_HASH_OUT_ELTS)..2 * (1 + NUM_HASH_OUT_ELTS) + VALUE_SIZE], &vs[2 * NUM_HASH_OUT_ELTS..2 * NUM_HASH_OUT_ELTS + VALUE_SIZE],
), ),
op_value: ValueTarget::from_slice( op_value: ValueTarget::from_slice(
&vs[2 * (1 + NUM_HASH_OUT_ELTS) + VALUE_SIZE &vs[2 * NUM_HASH_OUT_ELTS + VALUE_SIZE..2 * NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE],
..2 * (1 + NUM_HASH_OUT_ELTS) + 2 * VALUE_SIZE],
), ),
op: vs[2 * NUM_HASH_OUT_ELTS + 2 * VALUE_SIZE],
} }
} }
fn size(params: &Params) -> usize { fn size(params: &Params) -> usize {
2 * (1 + HashOutTarget::size(params)) + 2 * ValueTarget::size(params) 2 * HashOutTarget::size(params) + 2 * ValueTarget::size(params) + 1
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -148,14 +148,20 @@ pub(crate) fn extract_custom_predicate_verifications(
Ok(table) Ok(table)
} }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MerkleProofs {
pub(crate) medium: Vec<MerkleClaimAndProof>,
pub(crate) small: Vec<MerkleClaimAndProof>,
}
/// Extracts Merkle proofs from Contains/NotContains ops. /// Extracts Merkle proofs from Contains/NotContains ops.
pub(crate) fn extract_merkle_proofs( pub(crate) fn extract_merkle_proofs(
params: &Params, params: &Params,
aux_list: &mut [OperationAux], aux_list: &mut [OperationAux],
operations: &[middleware::Operation], operations: &[middleware::Operation],
statements: &[middleware::Statement], statements: &[middleware::Statement],
) -> Result<Vec<MerkleClaimAndProof>> { ) -> Result<MerkleProofs> {
let mut table = Vec::new(); let mut tables = MerkleProofs::default();
for (i, (op, st)) in operations.iter().zip(statements.iter()).enumerate() { for (i, (op, st)) in operations.iter().zip(statements.iter()).enumerate() {
let deduction_err = || MiddlewareError::invalid_deduction(op.clone(), st.clone()); let deduction_err = || MiddlewareError::invalid_deduction(op.clone(), st.clone());
let (root, key, value, pf) = match (op, st) { let (root, key, value, pf) = match (op, st) {
@ -178,31 +184,42 @@ pub(crate) fn extract_merkle_proofs(
} }
_ => continue, _ => continue,
}; };
aux_list[i] = OperationAux::MerkleProofIndex(table.len()); let claim_proof = MerkleClaimAndProof::new(Hash::from(root), key, value, pf.clone());
table.push(MerkleClaimAndProof::new( if pf.existence
Hash::from(root), // TODO: Make sure there's no off-by-one error here
key, && pf.siblings.len() <= params.containers.max_depth_small
value, && tables.small.len() < params.containers.state.max_small
pf.clone(), {
)); aux_list[i] = OperationAux::MerkleProofIndex(Size::Small, tables.small.len());
tables.small.push(claim_proof);
} else {
aux_list[i] = OperationAux::MerkleProofIndex(Size::Medium, tables.medium.len());
tables.medium.push(claim_proof);
} }
if table.len() > params.max_merkle_proofs_containers { }
if tables.medium.len() > params.containers.state.max_medium {
return Err(Error::custom(format!( return Err(Error::custom(format!(
"The number of required Merkle proofs ({}) exceeds the maximum number ({}).", "The number of required Merkle proofs ({}) exceeds the maximum number ({}).",
table.len(), tables.medium.len(),
params.max_merkle_proofs_containers params.containers.state.max_medium
))); )));
} }
Ok(table) Ok(tables)
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MerkleTransitionProofs {
pub(crate) medium: Vec<MerkleTreeStateTransitionProof>,
pub(crate) small: Vec<MerkleTreeStateTransitionProof>,
} }
/// Extracts Merkle state transition proofs from container update ops. /// Extracts Merkle state transition proofs from container update ops.
pub(crate) fn extract_merkle_tree_state_transition_proofs( pub(crate) fn extract_merkle_transition_proofs(
params: &Params, params: &Params,
aux_list: &mut [OperationAux], aux_list: &mut [OperationAux],
operations: &[middleware::Operation], operations: &[middleware::Operation],
) -> Result<Vec<MerkleTreeStateTransitionProof>> { ) -> Result<MerkleTransitionProofs> {
let mut table = Vec::new(); let mut tables = MerkleTransitionProofs::default();
for (i, op) in operations.iter().enumerate() { for (i, op) in operations.iter().enumerate() {
let pf = match op { let pf = match op {
middleware::Operation::ContainerInsertFromEntries(_, _, _, _, pf) middleware::Operation::ContainerInsertFromEntries(_, _, _, _, pf)
@ -210,17 +227,27 @@ pub(crate) fn extract_merkle_tree_state_transition_proofs(
| middleware::Operation::ContainerDeleteFromEntries(_, _, _, pf) => pf.clone(), | middleware::Operation::ContainerDeleteFromEntries(_, _, _, pf) => pf.clone(),
_ => continue, _ => continue,
}; };
aux_list[i] = OperationAux::MerkleTreeStateTransitionProofIndex(table.len()); if pf.op_proof.existence
table.push(pf); // TODO: Make sure there's no off-by-one error here
&& pf.siblings.len() <= params.containers.max_depth_small
&& tables.small.len() < params.containers.transition.max_small
{
aux_list[i] = OperationAux::MerkleTransitionProofIndex(Size::Small, tables.small.len());
tables.small.push(pf);
} else {
aux_list[i] =
OperationAux::MerkleTransitionProofIndex(Size::Medium, tables.medium.len());
tables.medium.push(pf);
} }
if table.len() > params.max_merkle_tree_state_transition_proofs_containers { }
if tables.medium.len() > params.containers.transition.max_medium {
return Err(Error::custom(format!( return Err(Error::custom(format!(
"The number of required Merkle proofs ({}) exceeds the maximum number ({}).", "The number of required Merkle proofs ({}) exceeds the maximum number ({}).",
table.len(), tables.medium.len(),
params.max_merkle_tree_state_transition_proofs_containers params.containers.transition.max_medium
))); )));
} }
Ok(table) Ok(tables)
} }
pub(crate) fn extract_public_key_of( pub(crate) fn extract_public_key_of(
@ -513,6 +540,8 @@ impl MainPodProver for Prover {
let mut aux_list = vec![OperationAux::None; params.max_priv_statements()]; let mut aux_list = vec![OperationAux::None; params.max_priv_statements()];
let merkle_proofs = let merkle_proofs =
extract_merkle_proofs(params, &mut aux_list, inputs.operations, inputs.statements)?; extract_merkle_proofs(params, &mut aux_list, inputs.operations, inputs.statements)?;
let merkle_transition_proofs =
extract_merkle_transition_proofs(params, &mut aux_list, inputs.operations)?;
let custom_predicates = extract_custom_predicates(params, inputs.operations)?; let custom_predicates = extract_custom_predicates(params, inputs.operations)?;
let custom_predicate_verifications = extract_custom_predicate_verifications( let custom_predicate_verifications = extract_custom_predicate_verifications(
params, params,
@ -537,9 +566,6 @@ impl MainPodProver for Prover {
let signed_bys = let signed_bys =
extract_signatures(params, &mut aux_list, inputs.operations, inputs.statements)?; 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)?;
let (statements, public_statements) = layout_statements(params, false, &inputs)?; let (statements, public_statements) = layout_statements(params, false, &inputs)?;
let operations = process_private_statements_operations( let operations = process_private_statements_operations(
params, params,
@ -572,20 +598,15 @@ impl MainPodProver for Prover {
.collect_vec(); .collect_vec();
let mut vd_mt_proofs = Vec::with_capacity(inputs.pods.len()); let mut vd_mt_proofs = Vec::with_capacity(inputs.pods.len());
let pad_vd_mt_proof = inputs.vd_set.get_vds_proof_0();
for (pod, vd) in inputs.pods.iter().zip(&verifier_datas) { for (pod, vd) in inputs.pods.iter().zip(&verifier_datas) {
vd_mt_proofs.push(if pod.is_main() { vd_mt_proofs.push(if pod.is_main() {
(true, inputs.vd_set.get_vds_proof(vd)?) inputs.vd_set.get_vds_proof(vd)?
} else { } else {
// For intro pods we don't verify inclusion of their vk into the vd set, so we // 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 // use a valid vds proof that matches the expected root but not the value to pass
( // the constraints
false, pad_vd_mt_proof.clone()
MerkleClaimAndProof {
root: inputs.vd_set.root(),
value: RawValue::from(pod.verifier_data_hash()),
..MerkleClaimAndProof::empty()
},
)
}); });
} }
@ -598,7 +619,7 @@ impl MainPodProver for Prover {
merkle_proofs, merkle_proofs,
public_key_of_sks, public_key_of_sks,
signed_bys, signed_bys,
merkle_tree_state_transition_proofs, merkle_transition_proofs,
custom_predicates_with_mpt_proofs, custom_predicates_with_mpt_proofs,
custom_predicate_verifications, custom_predicate_verifications,
}; };
@ -985,7 +1006,18 @@ pub mod tests {
max_statements: 2, max_statements: 2,
max_public_statements: 1, max_public_statements: 1,
max_input_pods_public_statements: 0, max_input_pods_public_statements: 0,
max_merkle_proofs_containers: 0, containers: middleware::ParamsContainers {
state: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 0,
},
transition: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 0,
},
max_depth_small: 8,
max_depth_medium: 32,
},
max_public_key_of: 0, max_public_key_of: 0,
max_custom_predicate_verifications: 0, max_custom_predicate_verifications: 0,
max_custom_predicates: 0, max_custom_predicates: 0,
@ -1024,11 +1056,20 @@ pub mod tests {
max_custom_predicates: 2, max_custom_predicates: 2,
max_custom_predicate_verifications: 2, max_custom_predicate_verifications: 2,
max_custom_predicate_wildcards: 3, max_custom_predicate_wildcards: 3,
max_merkle_proofs_containers: 2,
max_merkle_tree_state_transition_proofs_containers: 2,
max_public_key_of: 2, max_public_key_of: 2,
max_depth_mt_containers: 4,
max_depth_mt_vds: 6, max_depth_mt_vds: 6,
containers: middleware::ParamsContainers {
state: middleware::ParamsMerkleProofs {
max_small: 2,
max_medium: 2,
},
transition: middleware::ParamsMerkleProofs {
max_small: 2,
max_medium: 2,
},
max_depth_small: 2,
max_depth_medium: 4,
},
}; };
let mut vds = DEFAULT_VD_LIST.clone(); let mut vds = DEFAULT_VD_LIST.clone();
vds.push(rec_main_pod_circuit_data(&params).1.verifier_only.clone()); vds.push(rec_main_pod_circuit_data(&params).1.verifier_only.clone());
@ -1087,8 +1128,18 @@ pub mod tests {
max_public_statements: 4, max_public_statements: 4,
max_custom_predicate_wildcards: 4, max_custom_predicate_wildcards: 4,
max_custom_predicate_verifications: 2, max_custom_predicate_verifications: 2,
max_merkle_proofs_containers: 3, containers: middleware::ParamsContainers {
max_merkle_tree_state_transition_proofs_containers: 0, state: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 3,
},
transition: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 0,
},
max_depth_small: 8,
max_depth_medium: 32,
},
..Default::default() ..Default::default()
}; };
println!("{:#?}", params); println!("{:#?}", params);
@ -1156,8 +1207,18 @@ pub mod tests {
max_public_statements: 2, max_public_statements: 2,
max_custom_predicate_wildcards: 4, max_custom_predicate_wildcards: 4,
max_custom_predicate_verifications: 2, max_custom_predicate_verifications: 2,
max_merkle_proofs_containers: 0, containers: middleware::ParamsContainers {
max_merkle_tree_state_transition_proofs_containers: 0, state: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 0,
},
transition: middleware::ParamsMerkleProofs {
max_small: 0,
max_medium: 0,
},
max_depth_small: 8,
max_depth_medium: 32,
},
..Default::default() ..Default::default()
}; };
let mut vds = DEFAULT_VD_LIST.clone(); let mut vds = DEFAULT_VD_LIST.clone();

View file

@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
backends::plonky2::{ backends::plonky2::{
error::{Error, Result}, error::{Error, Result},
mainpod::{SignedBy, Statement}, mainpod::{MerkleProofs, MerkleTransitionProofs, SignedBy, Statement},
primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
}, },
middleware::{self, OperationType, Params}, middleware::{self, OperationType, Params},
}; };
@ -30,50 +29,89 @@ impl OperationArg {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Size {
Small,
Medium,
}
impl fmt::Display for Size {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Small => write!(f, "small"),
Self::Medium => write!(f, "medium"),
}
}
}
impl Size {
pub const fn min() -> Self {
Self::Small
}
pub const fn max() -> Self {
Self::Medium
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum OperationAux { pub enum OperationAux {
None, None,
MerkleProofIndex(usize), MerkleProofIndex(Size, usize),
MerkleTransitionProofIndex(Size, usize),
PublicKeyOfIndex(usize), PublicKeyOfIndex(usize),
SignedByIndex(usize), SignedByIndex(usize),
MerkleTreeStateTransitionProofIndex(usize),
CustomPredVerifyIndex(usize), CustomPredVerifyIndex(usize),
} }
impl OperationAux { impl OperationAux {
fn table_offset_merkle_proof(_params: &Params) -> usize { fn table_offset_merkle_proof(params: &Params, size: Size) -> usize {
match size {
// At index 0 we store a zero entry // At index 0 we store a zero entry
1 Size::Small => 1,
Size::Medium => {
Self::table_offset_merkle_proof(params, Size::Small)
+ params.containers.state.max_small
}
}
}
fn table_offset_merkle_transition_proof(params: &Params, size: Size) -> usize {
match size {
Size::Small => {
Self::table_offset_merkle_proof(params, Size::min())
+ params.containers.state.max_total()
}
Size::Medium => {
Self::table_offset_merkle_transition_proof(params, Size::Small)
+ params.containers.transition.max_small
}
}
}
fn table_offset_custom_pred_verify(params: &Params) -> usize {
Self::table_offset_merkle_transition_proof(params, Size::min())
+ params.containers.transition.max_total()
} }
fn table_offset_public_key_of(params: &Params) -> usize { fn table_offset_public_key_of(params: &Params) -> usize {
Self::table_offset_merkle_proof(params) + params.max_merkle_proofs_containers Self::table_offset_custom_pred_verify(params) + params.max_custom_predicate_verifications
} }
fn table_offset_signed_by(params: &Params) -> usize { fn table_offset_signed_by(params: &Params) -> usize {
Self::table_offset_public_key_of(params) + params.max_public_key_of 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
}
pub(crate) fn table_size(params: &Params) -> usize { pub(crate) fn table_size(params: &Params) -> usize {
1 + params.max_merkle_proofs_containers 1 + params.containers.state.max_total()
+ params.containers.transition.max_total()
+ params.max_custom_predicate_verifications
+ params.max_public_key_of + params.max_public_key_of
+ params.max_signed_by + params.max_signed_by
+ params.max_merkle_tree_state_transition_proofs_containers
+ params.max_custom_predicate_verifications
} }
pub fn table_index(&self, params: &Params) -> usize { pub fn table_index(&self, params: &Params) -> usize {
match self { match self {
Self::None => 0, Self::None => 0,
Self::MerkleProofIndex(i) => Self::table_offset_merkle_proof(params) + *i, Self::MerkleProofIndex(size, i) => Self::table_offset_merkle_proof(params, *size) + *i,
Self::MerkleTransitionProofIndex(size, i) => {
Self::table_offset_merkle_transition_proof(params, *size) + *i
}
Self::PublicKeyOfIndex(i) => Self::table_offset_public_key_of(params) + *i, Self::PublicKeyOfIndex(i) => Self::table_offset_public_key_of(params) + *i,
Self::SignedByIndex(i) => Self::table_offset_signed_by(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
}
Self::CustomPredVerifyIndex(i) => Self::table_offset_custom_pred_verify(params) + *i, Self::CustomPredVerifyIndex(i) => Self::table_offset_custom_pred_verify(params) + *i,
} }
} }
@ -96,8 +134,8 @@ impl Operation {
&self, &self,
statements: &[Statement], statements: &[Statement],
signatures: &[SignedBy], signatures: &[SignedBy],
merkle_proofs: &[MerkleClaimAndProof], merkle_proofs: &MerkleProofs,
merkle_tree_state_transition_proofs: &[MerkleTreeStateTransitionProof], merkle_transition_proofs: &MerkleTransitionProofs,
) -> Result<crate::middleware::Operation> { ) -> Result<crate::middleware::Operation> {
let deref_args = self let deref_args = self
.1 .1
@ -113,17 +151,26 @@ impl Operation {
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
let deref_aux = match self.2 { let deref_aux = match self.2 {
OperationAux::None => crate::middleware::OperationAux::None, OperationAux::None => crate::middleware::OperationAux::None,
OperationAux::CustomPredVerifyIndex(_) => crate::middleware::OperationAux::None, OperationAux::MerkleProofIndex(size, i) => {
OperationAux::MerkleProofIndex(i) => crate::middleware::OperationAux::MerkleProof( let table = match size {
merkle_proofs Size::Small => &merkle_proofs.small,
Size::Medium => &merkle_proofs.medium,
};
crate::middleware::OperationAux::MerkleProof(
table
.get(i) .get(i)
.ok_or(Error::custom(format!("Missing Merkle proof index {}", i)))? .ok_or(Error::custom(format!("Missing Merkle proof index {}", i)))?
.proof .proof
.clone(), .clone(),
), )
OperationAux::MerkleTreeStateTransitionProofIndex(i) => { }
OperationAux::MerkleTransitionProofIndex(size, i) => {
let table = match size {
Size::Small => &merkle_transition_proofs.small,
Size::Medium => &merkle_transition_proofs.medium,
};
crate::middleware::OperationAux::MerkleTreeStateTransitionProof( crate::middleware::OperationAux::MerkleTreeStateTransitionProof(
merkle_tree_state_transition_proofs table
.get(i) .get(i)
.ok_or(Error::custom(format!( .ok_or(Error::custom(format!(
"Missing Merkle state transition proof index {}", "Missing Merkle state transition proof index {}",
@ -132,6 +179,7 @@ impl Operation {
.clone(), .clone(),
) )
} }
OperationAux::CustomPredVerifyIndex(_) => crate::middleware::OperationAux::None,
OperationAux::SignedByIndex(i) => crate::middleware::OperationAux::Signature( OperationAux::SignedByIndex(i) => crate::middleware::OperationAux::Signature(
signatures signatures
.get(i) .get(i)
@ -165,12 +213,14 @@ impl fmt::Display for Operation {
} }
match self.2 { match self.2 {
OperationAux::None => (), OperationAux::None => (),
OperationAux::MerkleProofIndex(i) => write!(f, " merkle_proof_{:02}", i)?, OperationAux::MerkleProofIndex(size, i) => {
write!(f, " {}_merkle_proof_{:02}", size, i)?
}
OperationAux::CustomPredVerifyIndex(i) => write!(f, " custom_pred_verify_{:02}", i)?, OperationAux::CustomPredVerifyIndex(i) => write!(f, " custom_pred_verify_{:02}", i)?,
OperationAux::PublicKeyOfIndex(i) => write!(f, " public_key_of_{:02}", i)?, OperationAux::PublicKeyOfIndex(i) => write!(f, " public_key_of_{:02}", i)?,
OperationAux::SignedByIndex(i) => write!(f, " signed_by_{:02}", i)?, OperationAux::SignedByIndex(i) => write!(f, " signed_by_{:02}", i)?,
OperationAux::MerkleTreeStateTransitionProofIndex(i) => { OperationAux::MerkleTransitionProofIndex(size, i) => {
write!(f, " merkle_tree_state_transition_proof_{:02}", i)? write!(f, " {}_merkle_transition_proof_{:02}", size, i)?
} }
} }
Ok(()) Ok(())

View file

@ -11,13 +11,12 @@ use crate::{
basetypes::{Proof, VerifierOnlyCircuitData}, basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result}, error::{Error, Result},
mainpod::{ mainpod::{
calculate_statements_hash, extract_merkle_proofs, calculate_statements_hash, extract_merkle_proofs, extract_merkle_transition_proofs,
extract_merkle_tree_state_transition_proofs, extract_signatures, layout_statements, extract_signatures, layout_statements, process_private_statements_operations,
process_private_statements_operations, process_public_statements_operations, Operation, process_public_statements_operations, MerkleProofs, MerkleTransitionProofs, Operation,
OperationAux, SignedBy, Statement, OperationAux, SignedBy, Statement,
}, },
mock::emptypod::MockEmptyPod, mock::emptypod::MockEmptyPod,
primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
recursion::hash_verifier_data, recursion::hash_verifier_data,
}, },
middleware::{ middleware::{
@ -45,10 +44,10 @@ pub struct MockMainPod {
operations: Vec<Operation>, operations: Vec<Operation>,
// public subset of the `statements` vector // public subset of the `statements` vector
public_statements: Vec<Statement>, public_statements: Vec<Statement>,
// All Merkle proofs // All Merkle proofs for containers
merkle_proofs_containers: Vec<MerkleClaimAndProof>, merkle_proofs: MerkleProofs,
// All Merkle tree state transition proofs // All Merkle tree state transition proofs for containers
merkle_tree_state_transition_proofs_containers: Vec<MerkleTreeStateTransitionProof>, merkle_transition_proofs: MerkleTransitionProofs,
// All verified signatures // All verified signatures
signatures: Vec<SignedBy>, signatures: Vec<SignedBy>,
} }
@ -124,8 +123,8 @@ struct Data {
public_statements: Vec<Statement>, public_statements: Vec<Statement>,
operations: Vec<Operation>, operations: Vec<Operation>,
statements: Vec<Statement>, statements: Vec<Statement>,
merkle_proofs: Vec<MerkleClaimAndProof>, merkle_proofs: MerkleProofs,
merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProof>, merkle_transition_proofs: MerkleTransitionProofs,
signatures: Vec<SignedBy>, signatures: Vec<SignedBy>,
input_pods: Vec<(usize, Params, Hash, VDSet, serde_json::Value)>, input_pods: Vec<(usize, Params, Hash, VDSet, serde_json::Value)>,
} }
@ -153,8 +152,8 @@ impl MockMainPod {
let merkle_proofs = let merkle_proofs =
extract_merkle_proofs(params, &mut aux_list, inputs.operations, inputs.statements)?; extract_merkle_proofs(params, &mut aux_list, inputs.operations, inputs.statements)?;
// Similarly for Merkle state transition proofs. // Similarly for Merkle state transition proofs.
let merkle_tree_state_transition_proofs = let merkle_transition_proofs =
extract_merkle_tree_state_transition_proofs(params, &mut aux_list, inputs.operations)?; extract_merkle_transition_proofs(params, &mut aux_list, inputs.operations)?;
let signatures = let signatures =
extract_signatures(params, &mut aux_list, inputs.operations, inputs.statements)?; extract_signatures(params, &mut aux_list, inputs.operations, inputs.statements)?;
@ -185,8 +184,8 @@ impl MockMainPod {
public_statements, public_statements,
statements, statements,
operations, operations,
merkle_proofs_containers: merkle_proofs, merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs, merkle_transition_proofs,
signatures, signatures,
}) })
} }
@ -260,8 +259,8 @@ impl Pod for MockMainPod {
.deref( .deref(
&self.statements[..input_statement_offset + i], &self.statements[..input_statement_offset + i],
&self.signatures, &self.signatures,
&self.merkle_proofs_containers, &self.merkle_proofs,
&self.merkle_tree_state_transition_proofs_containers, &self.merkle_transition_proofs,
)? )?
.check_and_log(&self.params, &s.clone().try_into()?) .check_and_log(&self.params, &s.clone().try_into()?)
.map_err(|e| e.into()) .map_err(|e| e.into())
@ -321,10 +320,8 @@ impl Pod for MockMainPod {
public_statements: self.public_statements.clone(), public_statements: self.public_statements.clone(),
operations: self.operations.clone(), operations: self.operations.clone(),
statements: self.statements.clone(), statements: self.statements.clone(),
merkle_proofs: self.merkle_proofs_containers.clone(), merkle_proofs: self.merkle_proofs.clone(),
merkle_tree_state_transition_proofs: self merkle_transition_proofs: self.merkle_transition_proofs.clone(),
.merkle_tree_state_transition_proofs_containers
.clone(),
signatures: self.signatures.clone(), signatures: self.signatures.clone(),
input_pods, input_pods,
}) })
@ -344,7 +341,7 @@ impl Pod for MockMainPod {
operations, operations,
statements, statements,
merkle_proofs, merkle_proofs,
merkle_tree_state_transition_proofs, merkle_transition_proofs,
signatures, signatures,
input_pods, input_pods,
} = serde_json::from_value(data)?; } = serde_json::from_value(data)?;
@ -362,8 +359,8 @@ impl Pod for MockMainPod {
public_statements, public_statements,
operations, operations,
statements, statements,
merkle_proofs_containers: merkle_proofs, merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs, merkle_transition_proofs,
signatures, signatures,
}) })
} }

View file

@ -42,8 +42,6 @@ use crate::{
#[derive(Clone, Debug, Serialize, Deserialize)] #[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
pub(crate) enabled: BoolTarget,
pub(crate) root: HashOutTarget, pub(crate) root: HashOutTarget,
pub(crate) key: ValueTarget, pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget, pub(crate) value: ValueTarget,
@ -121,16 +119,9 @@ pub fn verify_merkle_proof_circuit(
let obtained_root = let obtained_root =
compute_root_from_leaf(max_depth, builder, &path, &leaf_hash, &proof.siblings); compute_root_from_leaf(max_depth, builder, &path, &leaf_hash, &proof.siblings);
// check that obtained_root==root (from inputs), when enabled==true // check that obtained_root==root (from inputs)
let zero = builder.zero();
let expected_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(proof.enabled, proof.root.elements[j], zero))
.collect();
let computed_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(proof.enabled, obtained_root.elements[j], zero))
.collect();
for j in 0..HASH_SIZE { for j in 0..HASH_SIZE {
builder.connect(computed_root[j], expected_root[j]); builder.connect(obtained_root.elements[j], proof.root.elements[j]);
} }
measure_gates_end!(builder, measure); measure_gates_end!(builder, measure);
} }
@ -139,7 +130,6 @@ impl MerkleClaimAndProofTarget {
pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self { pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self {
MerkleClaimAndProofTarget { MerkleClaimAndProofTarget {
max_depth, max_depth,
enabled: builder.add_virtual_bool_target_safe(),
root: builder.add_virtual_hash(), root: builder.add_virtual_hash(),
key: builder.add_virtual_value(), key: builder.add_virtual_value(),
value: builder.add_virtual_value(), value: builder.add_virtual_value(),
@ -154,12 +144,7 @@ impl MerkleClaimAndProofTarget {
} }
/// assigns the given values to the targets /// assigns the given values to the targets
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn set_targets( pub fn set_targets(&self, pw: &mut PartialWitness<F>, mp: &MerkleClaimAndProof) -> Result<()> {
&self,
pw: &mut PartialWitness<F>,
enabled: bool,
mp: &MerkleClaimAndProof,
) -> Result<()> {
if mp.proof.siblings.len() > self.max_depth { if mp.proof.siblings.len() > self.max_depth {
return Err(Error::Tree(TreeError::circuit_depth_too_small( return Err(Error::Tree(TreeError::circuit_depth_too_small(
self.max_depth, self.max_depth,
@ -167,7 +152,6 @@ impl MerkleClaimAndProofTarget {
))); )));
} }
pw.set_bool_target(self.enabled, enabled)?;
pw.set_hash_target(self.root, HashOut::from_vec(mp.root.0.to_vec()))?; pw.set_hash_target(self.root, HashOut::from_vec(mp.root.0.to_vec()))?;
pw.set_target_arr(&self.key.elements, &mp.key.0)?; pw.set_target_arr(&self.key.elements, &mp.key.0)?;
pw.set_target_arr(&self.value.elements, &mp.value.0)?; pw.set_target_arr(&self.value.elements, &mp.value.0)?;
@ -207,8 +191,6 @@ impl MerkleClaimAndProofTarget {
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct MerkleProofExistenceTarget { pub struct MerkleProofExistenceTarget {
max_depth: usize, max_depth: usize,
// `enabled` determines if the merkleproof verification is enabled
pub(crate) enabled: BoolTarget,
pub(crate) root: HashOutTarget, pub(crate) root: HashOutTarget,
pub(crate) key: ValueTarget, pub(crate) key: ValueTarget,
pub(crate) value: ValueTarget, pub(crate) value: ValueTarget,
@ -236,16 +218,9 @@ pub fn verify_merkle_proof_existence_circuit(
let obtained_root = let obtained_root =
compute_root_from_leaf(max_depth, builder, &path, &leaf_hash, &proof.siblings); compute_root_from_leaf(max_depth, builder, &path, &leaf_hash, &proof.siblings);
// check that obtained_root==root (from inputs), when enabled==true // check that obtained_root==root (from inputs)
let zero = builder.zero();
let expected_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(proof.enabled, proof.root.elements[j], zero))
.collect();
let computed_root: Vec<Target> = (0..HASH_SIZE)
.map(|j| builder.select(proof.enabled, obtained_root.elements[j], zero))
.collect();
for j in 0..HASH_SIZE { for j in 0..HASH_SIZE {
builder.connect(computed_root[j], expected_root[j]); builder.connect(obtained_root.elements[j], proof.root.elements[j]);
} }
measure_gates_end!(builder, measure); measure_gates_end!(builder, measure);
@ -256,7 +231,6 @@ impl MerkleProofExistenceTarget {
pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self { pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self {
MerkleProofExistenceTarget { MerkleProofExistenceTarget {
max_depth, max_depth,
enabled: builder.add_virtual_bool_target_safe(),
root: builder.add_virtual_hash(), root: builder.add_virtual_hash(),
key: builder.add_virtual_value(), key: builder.add_virtual_value(),
value: builder.add_virtual_value(), value: builder.add_virtual_value(),
@ -265,12 +239,7 @@ impl MerkleProofExistenceTarget {
} }
} }
/// assigns the given values to the targets /// assigns the given values to the targets
pub fn set_targets( pub fn set_targets(&self, pw: &mut PartialWitness<F>, mp: &MerkleClaimAndProof) -> Result<()> {
&self,
pw: &mut PartialWitness<F>,
enabled: bool,
mp: &MerkleClaimAndProof,
) -> Result<()> {
assert!(mp.proof.existence); // sanity check assert!(mp.proof.existence); // sanity check
if mp.proof.siblings.len() > self.max_depth { if mp.proof.siblings.len() > self.max_depth {
return Err(Error::Tree(TreeError::circuit_depth_too_small( return Err(Error::Tree(TreeError::circuit_depth_too_small(
@ -279,7 +248,6 @@ impl MerkleProofExistenceTarget {
))); )));
} }
pw.set_bool_target(self.enabled, enabled)?;
pw.set_hash_target(self.root, HashOut::from_vec(mp.root.0.to_vec()))?; pw.set_hash_target(self.root, HashOut::from_vec(mp.root.0.to_vec()))?;
pw.set_target_arr(&self.key.elements, &mp.key.0)?; pw.set_target_arr(&self.key.elements, &mp.key.0)?;
pw.set_target_arr(&self.value.elements, &mp.value.0)?; pw.set_target_arr(&self.value.elements, &mp.value.0)?;
@ -456,8 +424,6 @@ fn hash_with_flag_target<H: AlgebraicHasher<F>>(
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct MerkleTreeStateTransitionProofTarget { pub struct MerkleTreeStateTransitionProofTarget {
pub(crate) max_depth: usize, pub(crate) max_depth: usize,
// `enabled` determines if the merkleproof state transition verification is enabled
pub(crate) enabled: BoolTarget,
pub(crate) op: Target, pub(crate) op: Target,
pub(crate) old_root: HashOutTarget, pub(crate) old_root: HashOutTarget,
pub(crate) op_proof: MerkleClaimAndProofTarget, pub(crate) op_proof: MerkleClaimAndProofTarget,
@ -511,7 +477,6 @@ pub fn verify_merkle_state_transition_circuit(
}; };
let new_key_proof = MerkleProofExistenceTarget { let new_key_proof = MerkleProofExistenceTarget {
max_depth: proof.max_depth, max_depth: proof.max_depth,
enabled: proof.enabled,
root, root,
key: proof.op_key, key: proof.op_key,
value: proof.op_value, value: proof.op_value,
@ -523,13 +488,7 @@ pub fn verify_merkle_state_transition_circuit(
// Insert/Delete: Non-existence // Insert/Delete: Non-existence
// Update: Existence // Update: Existence
let proof_type = is_update; let proof_type = is_update;
builder.conditional_assert_eq( builder.connect(proof.op_proof.existence.target, proof_type.target);
proof.enabled.target,
proof.op_proof.existence.target,
proof_type.target,
);
// 3.2) assert that proof.enabled matches with op_proof.enabled
builder.connect(proof.op_proof.enabled.target, proof.enabled.target);
// 4) assert proof_non_existence.root corresponds to the root // 4) assert proof_non_existence.root corresponds to the root
// specified by the op (old_root for Insert/Update and new_root // specified by the op (old_root for Insert/Update and new_root
@ -545,17 +504,9 @@ pub fn verify_merkle_state_transition_circuit(
}; };
for j in 0..HASH_SIZE { for j in 0..HASH_SIZE {
// 4.1) assert that proof.proof_non_existence.root == proof.old_root // 4.1) assert that proof.proof_non_existence.root == proof.old_root
builder.conditional_assert_eq( builder.connect(proof.op_proof.root.elements[j], claim_root.elements[j]);
proof.enabled.target,
proof.op_proof.root.elements[j],
claim_root.elements[j],
);
// 4.2) assert that the non-existence proof uses the op_key (value not needed). // 4.2) assert that the non-existence proof uses the op_key (value not needed).
builder.conditional_assert_eq( builder.connect(proof.op_proof.key.elements[j], proof.op_key.elements[j]);
proof.enabled.target,
proof.op_proof.key.elements[j],
proof.op_key.elements[j],
);
} }
// prepare value for check 5.2) // prepare value for check 5.2)
@ -593,7 +544,7 @@ pub fn verify_merkle_state_transition_circuit(
.map(|j| builder.select(is_divergence_level, zero, new_siblings[i].elements[j])) .map(|j| builder.select(is_divergence_level, zero, new_siblings[i].elements[j]))
.collect(); .collect();
for j in 0..HASH_SIZE { for j in 0..HASH_SIZE {
builder.conditional_assert_eq(proof.enabled.target, old_sibling_i[j], new_sibling_i[j]); builder.connect(old_sibling_i[j], new_sibling_i[j]);
} }
// 5.2) when i==d && if old_siblings[i] != new_siblings[i], check that: // 5.2) when i==d && if old_siblings[i] != new_siblings[i], check that:
@ -611,7 +562,7 @@ pub fn verify_merkle_state_transition_circuit(
let in_case_5_2 = builder.and(old_is_noteq_new, is_divergence_level); let in_case_5_2 = builder.and(old_is_noteq_new, is_divergence_level);
// do the case2's checks // do the case2's checks
let sel = builder.and(proof.enabled, in_case_5_2); let sel = in_case_5_2;
for j in 0..HASH_SIZE { for j in 0..HASH_SIZE {
builder.conditional_assert_eq(sel.target, old_siblings[i].elements[j], zero); builder.conditional_assert_eq(sel.target, old_siblings[i].elements[j], zero);
builder.conditional_assert_eq( builder.conditional_assert_eq(
@ -641,7 +592,6 @@ impl MerkleTreeStateTransitionProofTarget {
pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self { pub fn new_virtual(max_depth: usize, builder: &mut CircuitBuilder<F, D>) -> Self {
Self { Self {
max_depth, max_depth,
enabled: builder.add_virtual_bool_target_safe(),
op: builder.add_virtual_target(), op: builder.add_virtual_target(),
old_root: builder.add_virtual_hash(), old_root: builder.add_virtual_hash(),
@ -661,7 +611,6 @@ impl MerkleTreeStateTransitionProofTarget {
pub fn set_targets( pub fn set_targets(
&self, &self,
pw: &mut PartialWitness<F>, pw: &mut PartialWitness<F>,
enabled: bool,
mp: &MerkleTreeStateTransitionProof, mp: &MerkleTreeStateTransitionProof,
) -> Result<()> { ) -> Result<()> {
let new_siblings = mp.siblings.clone(); let new_siblings = mp.siblings.clone();
@ -672,13 +621,11 @@ impl MerkleTreeStateTransitionProofTarget {
))); )));
} }
pw.set_bool_target(self.enabled, enabled)?;
pw.set_target(self.op, F::from_canonical_u8(mp.op as u8))?; pw.set_target(self.op, F::from_canonical_u8(mp.op as u8))?;
pw.set_hash_target(self.old_root, HashOut::from_vec(mp.old_root.0.to_vec()))?; pw.set_hash_target(self.old_root, HashOut::from_vec(mp.old_root.0.to_vec()))?;
self.op_proof.set_targets( self.op_proof.set_targets(
pw, pw,
enabled,
&MerkleClaimAndProof { &MerkleClaimAndProof {
root: if mp.op == MerkleTreeOp::Delete { root: if mp.op == MerkleTreeOp::Delete {
mp.new_root mp.new_root
@ -859,7 +806,6 @@ pub mod tests {
verify_merkle_proof_circuit(&mut builder, &targets); verify_merkle_proof_circuit(&mut builder, &targets);
targets.set_targets( targets.set_targets(
&mut pw, &mut pw,
true,
&MerkleClaimAndProof::new(tree.root(), key, Some(value), proof), &MerkleClaimAndProof::new(tree.root(), key, Some(value), proof),
)?; )?;
@ -871,6 +817,42 @@ pub mod tests {
Ok(()) Ok(())
} }
#[test]
fn test_merkleproof_pad_valid() -> Result<()> {
// circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleClaimAndProofTarget::new_virtual(32, &mut builder);
verify_merkle_proof_circuit(&mut builder, &targets);
targets.set_targets(&mut pw, &MerkleClaimAndProof::pad())?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
#[test]
fn test_merkleproof_transition_pad_valid() -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleTreeStateTransitionProofTarget::new_virtual(32, &mut builder);
verify_merkle_state_transition_circuit(&mut builder, &targets);
targets.set_targets(&mut pw, &MerkleTreeStateTransitionProof::pad())?;
// generate & verify proof
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
#[test] #[test]
fn test_merkleproof_only_existence_verify() -> Result<()> { fn test_merkleproof_only_existence_verify() -> Result<()> {
for max_depth in [10, 16, 32, 40, 64, 128, 130, 250, 256] { for max_depth in [10, 16, 32, 40, 64, 128, 130, 250, 256] {
@ -906,7 +888,6 @@ pub mod tests {
verify_merkle_proof_circuit(&mut builder, &targets); verify_merkle_proof_circuit(&mut builder, &targets);
targets.set_targets( targets.set_targets(
&mut pw, &mut pw,
true,
&MerkleClaimAndProof::new(tree.root(), key, Some(value), proof), &MerkleClaimAndProof::new(tree.root(), key, Some(value), proof),
)?; )?;
@ -982,7 +963,6 @@ pub mod tests {
verify_merkle_proof_circuit(&mut builder, &targets); verify_merkle_proof_circuit(&mut builder, &targets);
targets.set_targets( targets.set_targets(
&mut pw, &mut pw,
true,
&MerkleClaimAndProof::new(tree.root(), key, Some(value), proof), &MerkleClaimAndProof::new(tree.root(), key, Some(value), proof),
)?; )?;
@ -1028,32 +1008,15 @@ pub mod tests {
let targets = MerkleClaimAndProofTarget::new_virtual(max_depth, &mut builder); let targets = MerkleClaimAndProofTarget::new_virtual(max_depth, &mut builder);
verify_merkle_proof_circuit(&mut builder, &targets); verify_merkle_proof_circuit(&mut builder, &targets);
// verification enabled & proof of existence // proof of existence
let mp = MerkleClaimAndProof::new(tree2.root(), key, Some(value), proof); let mp = MerkleClaimAndProof::new(tree2.root(), key, Some(value), proof);
targets.set_targets(&mut pw, true, &mp)?; targets.set_targets(&mut pw, &mp)?;
// generate proof, expecting it to fail (since we're using the wrong // generate proof, expecting it to fail (since we're using the wrong
// root) // root)
let data = builder.build::<C>(); let data = builder.build::<C>();
assert!(data.prove(pw).is_err()); assert!(data.prove(pw).is_err());
// Now generate a new proof, using `enabled=false`, which should pass the verification
// despite containing 'wrong' witness.
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleClaimAndProofTarget::new_virtual(max_depth, &mut builder);
verify_merkle_proof_circuit(&mut builder, &targets);
// verification disabled & proof of existence
targets.set_targets(&mut pw, false, &mp)?;
// generate proof, should pass despite using wrong witness, since the
// `enabled=false`
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(()) Ok(())
} }
@ -1076,7 +1039,7 @@ pub mod tests {
let targets = MerkleTreeStateTransitionProofTarget::new_virtual(max_depth, &mut builder); let targets = MerkleTreeStateTransitionProofTarget::new_virtual(max_depth, &mut builder);
verify_merkle_state_transition_circuit(&mut builder, &targets); verify_merkle_state_transition_circuit(&mut builder, &targets);
targets.set_targets(&mut pw, true, state_transition_proof)?; targets.set_targets(&mut pw, state_transition_proof)?;
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();
@ -1273,71 +1236,4 @@ pub mod tests {
assert_ne!(state_transition_proof.new_root, tree.root()); // Tamper check assert_ne!(state_transition_proof.new_root, tree.root()); // Tamper check
Ok(()) Ok(())
} }
#[test]
fn test_state_transition_gadget_disabled() -> Result<()> {
let max_depth: usize = 32;
let mut kvs = HashMap::new();
for i in 0..8 {
kvs.insert(RawValue::from(i), RawValue::from(1000 + i));
}
let mut tree = MerkleTree::new(&kvs);
let key = RawValue::from(37);
let value = RawValue::from(1037);
let _ = tree.insert(&key, &value)?;
let key = RawValue::from(21);
let value = RawValue::from(1021);
let original_state_transition_proof = tree.insert(&key, &value)?;
let mut state_transition_proof = original_state_transition_proof.clone();
// modify the proof, so that it should fail when `enabled=true`, by
// changing the new_root
state_transition_proof.new_root = state_transition_proof.old_root;
run_circuit_disabled(max_depth, &state_transition_proof)?;
// modify the proof, so that it should fail when `enabled=true`, by
// changing the new_sibling at the divergence level, which should not
// pass the verification in the case where we're inserting key=21
let mut state_transition_proof = original_state_transition_proof.clone();
state_transition_proof.siblings[4] = EMPTY_HASH;
run_circuit_disabled(max_depth, &state_transition_proof)?;
Ok(())
}
fn run_circuit_disabled(
max_depth: usize,
state_transition_proof: &MerkleTreeStateTransitionProof,
) -> Result<()> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleTreeStateTransitionProofTarget::new_virtual(max_depth, &mut builder);
verify_merkle_state_transition_circuit(&mut builder, &targets);
targets.set_targets(&mut pw, true, state_transition_proof)?;
// generate proof, and expect it to fail
let data = builder.build::<C>();
assert!(data.prove(pw).is_err()); // expect prove to fail
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut pw = PartialWitness::<F>::new();
let targets = MerkleTreeStateTransitionProofTarget::new_virtual(max_depth, &mut builder);
verify_merkle_state_transition_circuit(&mut builder, &targets);
targets.set_targets(&mut pw, false, state_transition_proof)?;
// generate and expect it to pass
let data = builder.build::<C>();
let proof = data.prove(pw)?;
data.verify(proof)?;
Ok(())
}
} }

View file

@ -921,6 +921,21 @@ impl MerkleClaimAndProof {
}, },
} }
} }
/// Value used for padding. This is a valid merkle proof.
pub fn pad() -> Self {
let [key, value] = [EMPTY_VALUE, EMPTY_VALUE];
let root = kv_hash(&key, Some(value));
Self {
root,
key,
value,
proof: MerkleProof {
existence: true,
siblings: vec![],
other_leaf: None,
},
}
}
pub fn new(root: Hash, key: RawValue, value: Option<RawValue>, proof: MerkleProof) -> Self { pub fn new(root: Hash, key: RawValue, value: Option<RawValue>, proof: MerkleProof) -> Self {
Self { Self {
root, root,
@ -974,7 +989,6 @@ pub struct MerkleTreeStateTransitionProof {
} }
impl MerkleTreeStateTransitionProof { impl MerkleTreeStateTransitionProof {
/// Value used for padding.
pub fn empty() -> Self { pub fn empty() -> Self {
let empty_proof_and_claim = MerkleClaimAndProof::empty(); let empty_proof_and_claim = MerkleClaimAndProof::empty();
Self { Self {
@ -988,6 +1002,20 @@ impl MerkleTreeStateTransitionProof {
siblings: vec![], siblings: vec![],
} }
} }
/// Value used for padding. This is a valid transition proof.
pub fn pad() -> Self {
let pad_proof_and_claim = MerkleClaimAndProof::pad();
Self {
op: MerkleTreeOp::Update,
old_root: pad_proof_and_claim.root,
op_proof: pad_proof_and_claim.proof,
new_root: pad_proof_and_claim.root,
op_key: pad_proof_and_claim.key,
op_value: pad_proof_and_claim.value,
value: Some(pad_proof_and_claim.value),
siblings: vec![],
}
}
} }
// NOTE: currently we use automatic serialization/deserialization, which is // NOTE: currently we use automatic serialization/deserialization, which is
@ -1165,6 +1193,15 @@ pub mod tests {
Ok(()) Ok(())
} }
#[test]
fn test_merkletree_pad() {
let claim = MerkleClaimAndProof::pad();
MerkleTree::verify(claim.root, &claim.proof, &claim.key, &claim.value).unwrap();
let proof = MerkleTreeStateTransitionProof::pad();
MerkleTree::verify_state_transition(&proof).unwrap();
}
#[test] #[test]
fn test_key_not_found() -> Result<()> { fn test_key_not_found() -> Result<()> {
let db = Box::new(db::MemDB::new()); let db = Box::new(db::MemDB::new());

View file

@ -78,12 +78,12 @@ fn aggregate_rows<'a>(
UtilizationRow { UtilizationRow {
name: "merkle proofs", name: "merkle proofs",
used: merkle_proofs, used: merkle_proofs,
limit: params.max_merkle_proofs_containers, limit: params.containers.state.max_medium,
}, },
UtilizationRow { UtilizationRow {
name: "merkle state transitions", name: "merkle state transitions",
used: merkle_state_transitions, used: merkle_state_transitions,
limit: params.max_merkle_tree_state_transition_proofs_containers, limit: params.containers.transition.max_medium,
}, },
UtilizationRow { UtilizationRow {
name: "custom pred verifications", name: "custom pred verifications",
@ -278,15 +278,24 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
frontend::multi_pod::cost::CustomPredicateId, frontend::multi_pod::cost::CustomPredicateId,
middleware::{Hash, RawValue}, middleware::{Hash, ParamsContainers, ParamsMerkleProofs, RawValue},
}; };
fn default_params() -> Params { fn default_params() -> Params {
Params { Params {
max_statements: 48, max_statements: 48,
max_public_statements: 8, max_public_statements: 8,
max_merkle_proofs_containers: 8, containers: ParamsContainers {
max_merkle_tree_state_transition_proofs_containers: 4, state: ParamsMerkleProofs {
max_small: 0,
max_medium: 8,
},
transition: ParamsMerkleProofs {
max_small: 0,
max_medium: 4,
},
..Default::default()
},
max_custom_predicate_verifications: 10, max_custom_predicate_verifications: 10,
max_custom_predicates: 2, max_custom_predicates: 2,
max_signed_by: 4, max_signed_by: 4,

View file

@ -395,13 +395,11 @@ pub fn solve(input: &SolverInput) -> Result<MultiPodSolution> {
let lb_statement_groups = lower_bound_from_total(input.num_statements, max_stmts_per_pod); let lb_statement_groups = lower_bound_from_total(input.num_statements, max_stmts_per_pod);
let lb_merkle = lower_bound_from_total( let lb_merkle = lower_bound_from_total(
resource_totals.merkle_proofs, resource_totals.merkle_proofs,
input.params.max_merkle_proofs_containers, input.params.containers.state.max_medium,
); );
let lb_merkle_transitions = lower_bound_from_total( let lb_merkle_transitions = lower_bound_from_total(
resource_totals.merkle_state_transitions, resource_totals.merkle_state_transitions,
input input.params.containers.transition.max_medium,
.params
.max_merkle_tree_state_transition_proofs_containers,
); );
let lb_custom_pred_verifications = lower_bound_from_total( let lb_custom_pred_verifications = lower_bound_from_total(
resource_totals.custom_pred_verifications, resource_totals.custom_pred_verifications,
@ -753,7 +751,7 @@ fn try_solve_with_pods(
.map(|s| (input.costs[s].merkle_proofs as f64) * prove[s][p]) .map(|s| (input.costs[s].merkle_proofs as f64) * prove[s][p])
.sum(); .sum();
model.add_constraint(constraint!( model.add_constraint(constraint!(
merkle_sum <= (input.params.max_merkle_proofs_containers as f64) * pod_used[p] merkle_sum <= (input.params.containers.state.max_medium as f64) * pod_used[p]
)); ));
// 6d: Merkle state transitions // 6d: Merkle state transitions
@ -761,11 +759,7 @@ fn try_solve_with_pods(
.map(|s| (input.costs[s].merkle_state_transitions as f64) * prove[s][p]) .map(|s| (input.costs[s].merkle_state_transitions as f64) * prove[s][p])
.sum(); .sum();
model.add_constraint(constraint!( model.add_constraint(constraint!(
mst_sum mst_sum <= (input.params.containers.transition.max_medium as f64) * pod_used[p]
<= (input
.params
.max_merkle_tree_state_transition_proofs_containers as f64)
* pod_used[p]
)); ));
// 6e: Custom predicate verifications // 6e: Custom predicate verifications

View file

@ -780,6 +780,50 @@ pub const BASE_PARAMS: BaseParams = BaseParams {
max_operation_args: 5 + 1, max_operation_args: 5 + 1,
}; };
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
#[serde(rename_all = "camelCase")]
pub struct ParamsMerkleProofs {
pub max_small: usize,
pub max_medium: usize,
}
impl ParamsMerkleProofs {
pub fn max_total(&self) -> usize {
self.max_small + self.max_medium
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
#[serde(rename_all = "camelCase")]
pub struct ParamsContainers {
// Parameters for exists/nonexists container operations. The small set only supports exists
pub state: ParamsMerkleProofs,
// Parameters for transition container operations (insert, delete, update). The small set only
// supports update.
pub transition: ParamsMerkleProofs,
// Max depth of small proofs
pub max_depth_small: usize,
// Max depth of medium proofs
pub max_depth_medium: usize,
}
impl Default for ParamsContainers {
fn default() -> Self {
Self {
state: ParamsMerkleProofs {
max_small: 22,
max_medium: 8,
},
transition: ParamsMerkleProofs {
max_small: 12,
max_medium: 6,
},
max_depth_small: 8,
max_depth_medium: 32,
}
}
}
/// Params: non dynamic parameters that define the circuit. /// Params: non dynamic parameters that define the circuit.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -793,12 +837,7 @@ pub struct Params {
// max number of operations using custom predicates that can be verified in the MainPod // max number of operations using custom predicates that can be verified in the MainPod
pub max_custom_predicate_verifications: usize, pub max_custom_predicate_verifications: usize,
pub max_custom_predicate_wildcards: usize, pub max_custom_predicate_wildcards: usize,
// maximum number of merkle proofs used for container operations pub containers: ParamsContainers,
pub max_merkle_proofs_containers: usize,
// maximum number of merkle tree state transition proofs used for container update operations
pub max_merkle_tree_state_transition_proofs_containers: usize,
// maximum depth for merkle tree gadget used for container operations
pub max_depth_mt_containers: usize,
// maximum depth of the merkle tree gadget used for verifier_data membership // maximum depth of the merkle tree gadget used for verifier_data membership
// check. This allows creating verifying sets of pod circuits of size // check. This allows creating verifying sets of pod circuits of size
// 2^max_depth_mt_vds. Limits the number of container operations of the type Contains, // 2^max_depth_mt_vds. Limits the number of container operations of the type Contains,
@ -820,9 +859,7 @@ impl Default for Params {
max_custom_predicates: 8, max_custom_predicates: 8,
max_custom_predicate_verifications: 8, max_custom_predicate_verifications: 8,
max_custom_predicate_wildcards: 8, max_custom_predicate_wildcards: 8,
max_merkle_proofs_containers: 20, containers: ParamsContainers::default(),
max_merkle_tree_state_transition_proofs_containers: 6,
max_depth_mt_containers: 32,
max_depth_mt_vds: 6, // up to 64 (2^6) different pod circuits max_depth_mt_vds: 6, // up to 64 (2^6) different pod circuits
max_public_key_of: 2, max_public_key_of: 2,
max_signed_by: 4, max_signed_by: 4,