feat: add container update ops (#390)

* Add container update ops

* Update src/middleware/operation.rs

Co-authored-by: Eduard S. <eduardsanou@posteo.net>

* Update src/backends/plonky2/mainpod/mod.rs

Co-authored-by: Eduard S. <eduardsanou@posteo.net>

* Code review

---------

Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
Ahmad Afuni 2025-08-13 06:34:45 +10:00 committed by GitHub
parent 656cae77e0
commit 1508dd6126
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1452 additions and 72 deletions

View file

@ -27,7 +27,7 @@ use crate::{
circuits::mainpod::CustomPredicateVerification, circuits::mainpod::CustomPredicateVerification,
error::Result, error::Result,
mainpod::{Operation, OperationArg, OperationAux, Statement}, mainpod::{Operation, OperationArg, OperationAux, Statement},
primitives::merkletree::MerkleClaimAndProofTarget, primitives::merkletree::{MerkleClaimAndProofTarget, MerkleTreeStateTransitionProofTarget},
}, },
middleware::{ middleware::{
CustomPredicate, CustomPredicateBatch, CustomPredicateRef, NativeOperation, CustomPredicate, CustomPredicateBatch, CustomPredicateRef, NativeOperation,
@ -743,6 +743,32 @@ impl From<MerkleClaimAndProofTarget> for MerkleClaimTarget {
} }
} }
/// For the purpose of op verification, we need only look up the
/// Merkle state transition claim rather than the Merkle state
/// transition proof since it is verified elsewhere.
#[derive(Copy, Clone)]
pub struct MerkleTreeStateTransitionClaimTarget {
pub(crate) enabled: BoolTarget,
pub(crate) op: Target,
pub(crate) old_root: HashOutTarget,
pub(crate) new_root: HashOutTarget,
pub(crate) op_key: ValueTarget,
pub(crate) op_value: ValueTarget,
}
impl From<MerkleTreeStateTransitionProofTarget> for MerkleTreeStateTransitionClaimTarget {
fn from(pf: MerkleTreeStateTransitionProofTarget) -> Self {
Self {
enabled: pf.enabled,
op: pf.op,
old_root: pf.old_root,
new_root: pf.new_root,
op_key: pf.op_key,
op_value: pf.op_value,
}
}
}
impl Flattenable for HashOutTarget { impl Flattenable for HashOutTarget {
fn flatten(&self) -> Vec<Target> { fn flatten(&self) -> Vec<Target> {
self.elements.to_vec() self.elements.to_vec()
@ -803,6 +829,42 @@ impl Flattenable for MerkleClaimTarget {
} }
} }
impl Flattenable for MerkleTreeStateTransitionClaimTarget {
fn flatten(&self) -> Vec<Target> {
[
vec![self.enabled.target, self.op],
self.old_root.elements.to_vec(),
self.new_root.elements.to_vec(),
self.op_key.elements.to_vec(),
self.op_value.elements.to_vec(),
]
.concat()
}
fn from_flattened(params: &Params, vs: &[Target]) -> Self {
assert_eq!(vs.len(), Self::size(params));
Self {
enabled: BoolTarget::new_unsafe(vs[0]),
op: vs[1],
old_root: HashOutTarget::from_vec(vs[2..2 + NUM_HASH_OUT_ELTS].to_vec()),
new_root: HashOutTarget::from_vec(
vs[2 + NUM_HASH_OUT_ELTS..2 * (1 + NUM_HASH_OUT_ELTS)].to_vec(),
),
op_key: ValueTarget::from_slice(
&vs[2 * (1 + NUM_HASH_OUT_ELTS)..2 * (1 + NUM_HASH_OUT_ELTS) + VALUE_SIZE],
),
op_value: ValueTarget::from_slice(
&vs[2 * (1 + NUM_HASH_OUT_ELTS) + VALUE_SIZE
..2 * (1 + NUM_HASH_OUT_ELTS) + 2 * VALUE_SIZE],
),
}
}
fn size(params: &Params) -> usize {
2 * (1 + HashOutTarget::size(params)) + 2 * ValueTarget::size(params)
}
}
impl Flattenable for PredicateTarget { impl Flattenable for PredicateTarget {
fn flatten(&self) -> Vec<Target> { fn flatten(&self) -> Vec<Target> {
self.elements.to_vec() self.elements.to_vec()

View file

@ -23,9 +23,10 @@ use crate::{
common::{ common::{
CircuitBuilderPod, CustomPredicateBatchTarget, CustomPredicateEntryTarget, CircuitBuilderPod, CustomPredicateBatchTarget, CustomPredicateEntryTarget,
CustomPredicateTarget, CustomPredicateVerifyEntryTarget, CustomPredicateTarget, CustomPredicateVerifyEntryTarget,
CustomPredicateVerifyQueryTarget, Flattenable, MerkleClaimTarget, OperationTarget, CustomPredicateVerifyQueryTarget, Flattenable, MerkleClaimTarget,
OperationTypeTarget, PredicateTarget, StatementArgTarget, StatementTarget, MerkleTreeStateTransitionClaimTarget, OperationTarget, OperationTypeTarget,
StatementTmplArgTarget, StatementTmplTarget, ValueTarget, PredicateTarget, StatementArgTarget, StatementTarget, StatementTmplArgTarget,
StatementTmplTarget, ValueTarget,
}, },
hash::{hash_from_state_circuit, precompute_hash_state}, hash::{hash_from_state_circuit, precompute_hash_state},
mux_table::{MuxTableTarget, TableEntryTarget}, mux_table::{MuxTableTarget, TableEntryTarget},
@ -41,7 +42,9 @@ use crate::{
schnorr::SecretKey, schnorr::SecretKey,
}, },
merkletree::{ merkletree::{
verify_merkle_proof_circuit, MerkleClaimAndProof, MerkleClaimAndProofTarget, verify_merkle_proof_circuit, verify_merkle_state_transition_circuit,
MerkleClaimAndProof, MerkleClaimAndProofTarget, MerkleTreeOp,
MerkleTreeStateTransitionProof, MerkleTreeStateTransitionProofTarget,
}, },
}, },
recursion::{InnerCircuit, VerifiedProofTarget}, recursion::{InnerCircuit, VerifiedProofTarget},
@ -65,7 +68,7 @@ pub const PI_OFFSET_VDSROOT: usize = 4;
pub const NUM_PUBLIC_INPUTS: usize = 8; pub const NUM_PUBLIC_INPUTS: usize = 8;
const MAX_VALUE_ARGS: usize = 3; const MAX_VALUE_ARGS: usize = 4;
struct StatementArgCache { struct StatementArgCache {
rhs: ValueTarget, rhs: ValueTarget,
@ -99,8 +102,8 @@ impl StatementCache {
.map(|i| builder.vec_ref(params, prev_statements, i)) .map(|i| builder.vec_ref(params, prev_statements, i))
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
assert!(params.max_operation_args >= 3); assert!(params.max_operation_args >= MAX_VALUE_ARGS);
assert!(params.max_statement_args >= 3); assert!(params.max_statement_args >= MAX_VALUE_ARGS);
let equations = array::from_fn(|i| { let equations = array::from_fn(|i| {
let pred_is_none = op_args[i].has_native_type(builder, params, NativePredicate::None); let pred_is_none = op_args[i].has_native_type(builder, params, NativePredicate::None);
let arg_is_value = builder.statement_arg_is_value(&st.args[i]); let arg_is_value = builder.statement_arg_is_value(&st.args[i]);
@ -189,13 +192,16 @@ enum OperationAuxTableTag {
None = 0, None = 0,
MerkleProof = 1, MerkleProof = 1,
PublicKeyOf = 2, PublicKeyOf = 2,
CustomPredVerify = 3, MerkleTreeStateTransitionProof = 3,
CustomPredVerify = 4,
} }
fn max_operation_aux_entry_len(params: &Params) -> usize { fn max_operation_aux_entry_len(params: &Params) -> usize {
[ [
(params.max_merkle_proofs_containers > 0).then(|| MerkleClaimTarget::size(params)), (params.max_merkle_proofs_containers > 0).then(|| MerkleClaimTarget::size(params)),
(params.max_public_key_of > 0).then(|| KeyPairTarget::size(params)), (params.max_public_key_of > 0).then(|| KeyPairTarget::size(params)),
(params.max_merkle_tree_state_transition_proofs_containers > 0)
.then(|| MerkleTreeStateTransitionClaimTarget::size(params)),
(params.max_custom_predicate_verifications > 0) (params.max_custom_predicate_verifications > 0)
.then(|| CustomPredicateVerifyQueryTarget::size(params)), .then(|| CustomPredicateVerifyQueryTarget::size(params)),
] ]
@ -236,6 +242,7 @@ fn build_operation_aux_table_circuit(
builder: &mut CircuitBuilder, builder: &mut CircuitBuilder,
merkle_proofs: &[MerkleClaimAndProofTarget], merkle_proofs: &[MerkleClaimAndProofTarget],
public_key_of_sks: &[BigUInt320Target], public_key_of_sks: &[BigUInt320Target],
merkle_tree_state_transition_proofs: &[MerkleTreeStateTransitionProofTarget],
custom_predicate_verifications: &[CustomPredicateVerifyEntryTarget], custom_predicate_verifications: &[CustomPredicateVerifyEntryTarget],
custom_predicate_table: &[HashOutTarget], custom_predicate_table: &[HashOutTarget],
) -> Result<MuxTableTarget> { ) -> Result<MuxTableTarget> {
@ -285,6 +292,19 @@ fn build_operation_aux_table_circuit(
measure_gates_end!(builder, measure); measure_gates_end!(builder, measure);
} }
// Merkle state transition proofs: verify op proof (insert/update/delete)
for merkle_tree_state_transition_proof in merkle_tree_state_transition_proofs {
verify_merkle_state_transition_circuit(builder, merkle_tree_state_transition_proof);
let entry =
MerkleTreeStateTransitionClaimTarget::from(merkle_tree_state_transition_proof.clone());
table.push(
builder,
OperationAuxTableTag::MerkleTreeStateTransitionProof as u32,
&entry,
);
}
// CustomPredVerify: verify custom predicate statements verification against operations // CustomPredVerify: verify custom predicate statements verification against operations
for entry in custom_predicate_verifications { for entry in custom_predicate_verifications {
let measure = measure_gates_begin!(builder, "CustomPredVerify"); let measure = measure_gates_begin!(builder, "CustomPredVerify");
@ -413,6 +433,34 @@ fn verify_operation_circuit(
&cache, &cache,
)); ));
} }
if params.max_merkle_tree_state_transition_proofs_containers > 0 {
op_checks.extend_from_slice(&[
verify_merkle_insert_circuit(
params,
builder,
st,
&op.op_type,
&resolved_aux,
&cache,
),
verify_merkle_update_circuit(
params,
builder,
st,
&op.op_type,
&resolved_aux,
&cache,
),
verify_merkle_delete_circuit(
params,
builder,
st,
&op.op_type,
&resolved_aux,
&cache,
),
]);
}
if params.max_custom_predicate_verifications > 0 { if params.max_custom_predicate_verifications > 0 {
op_checks.push(verify_custom_circuit( op_checks.push(verify_custom_circuit(
builder, builder,
@ -532,6 +580,224 @@ fn verify_not_contains_from_entries_circuit(
ok ok
} }
fn verify_merkle_insert_circuit(
params: &Params,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
aux: &TableEntryTarget,
cache: &StatementCache,
) -> BoolTarget {
let measure = measure_gates_begin!(builder, "MerkleInsertOp");
let (aux_tag_ok, resolved_merkle_tree_state_transition_claim) =
aux.as_type::<MerkleTreeStateTransitionClaimTarget>(
builder,
OperationAuxTableTag::MerkleTreeStateTransitionProof as u32,
);
let op_code_ok = op_type.has_native(builder, NativeOperation::ContainerInsertFromEntries);
let (arg_types_ok, [new_root_value, old_root_value, op_key_value, op_value_value]) =
cache.first_n_args_as_values();
let expected_merkle_op = builder.constant(F::from_canonical_u8(MerkleTreeOp::Insert as u8));
// Check Merkle proof (verified elsewhere) against op args.
let merkle_proof_checks = [
/* The supplied Merkle transition proof must be enabled. */
resolved_merkle_tree_state_transition_claim.enabled,
/* ...and it must be an insertion proof. */
builder.is_equal(
resolved_merkle_tree_state_transition_claim.op,
expected_merkle_op,
),
/* ...for the root-key-value combination in the resolved op args. */
builder.is_equal_slice(
&old_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.old_root
.elements,
),
builder.is_equal_slice(
&new_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.new_root
.elements,
),
builder.is_equal_slice(
&op_key_value.elements,
&resolved_merkle_tree_state_transition_claim.op_key.elements,
),
builder.is_equal_slice(
&op_value_value.elements,
&resolved_merkle_tree_state_transition_claim
.op_value
.elements,
),
];
let merkle_proof_ok = builder.all(merkle_proof_checks);
// Check output statement
let arg1_expected = cache.equations[0].lhs.clone();
let arg2_expected = cache.equations[1].lhs.clone();
let arg3_expected = cache.equations[2].lhs.clone();
let arg4_expected = cache.equations[3].lhs.clone();
let expected_statement = StatementTarget::new_native(
builder,
params,
NativePredicate::ContainerInsert,
&[arg1_expected, arg2_expected, arg3_expected, arg4_expected],
);
let st_ok = builder.is_equal_flattenable(st, &expected_statement);
let ok = builder.all([op_code_ok, aux_tag_ok, arg_types_ok, merkle_proof_ok, st_ok]);
measure_gates_end!(builder, measure);
ok
}
fn verify_merkle_update_circuit(
params: &Params,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
aux: &TableEntryTarget,
cache: &StatementCache,
) -> BoolTarget {
let measure = measure_gates_begin!(builder, "MerkleUpdateOp");
let (aux_tag_ok, resolved_merkle_tree_state_transition_claim) =
aux.as_type::<MerkleTreeStateTransitionClaimTarget>(
builder,
OperationAuxTableTag::MerkleTreeStateTransitionProof as u32,
);
let op_code_ok = op_type.has_native(builder, NativeOperation::ContainerUpdateFromEntries);
let (arg_types_ok, [new_root_value, old_root_value, op_key_value, op_value_value]) =
cache.first_n_args_as_values();
let expected_merkle_op = builder.constant(F::from_canonical_u8(MerkleTreeOp::Update as u8));
// Check Merkle proof (verified elsewhere) against op args.
let merkle_proof_checks = [
/* The supplied Merkle transition proof must be enabled. */
resolved_merkle_tree_state_transition_claim.enabled,
/* ...and it must be an update proof. */
builder.is_equal(
resolved_merkle_tree_state_transition_claim.op,
expected_merkle_op,
),
/* ...for the root-key-value combination in the resolved op args. */
builder.is_equal_slice(
&old_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.old_root
.elements,
),
builder.is_equal_slice(
&new_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.new_root
.elements,
),
builder.is_equal_slice(
&op_key_value.elements,
&resolved_merkle_tree_state_transition_claim.op_key.elements,
),
builder.is_equal_slice(
&op_value_value.elements,
&resolved_merkle_tree_state_transition_claim
.op_value
.elements,
),
];
let merkle_proof_ok = builder.all(merkle_proof_checks);
// Check output statement
let arg1_expected = cache.equations[0].lhs.clone();
let arg2_expected = cache.equations[1].lhs.clone();
let arg3_expected = cache.equations[2].lhs.clone();
let arg4_expected = cache.equations[3].lhs.clone();
let expected_statement = StatementTarget::new_native(
builder,
params,
NativePredicate::ContainerUpdate,
&[arg1_expected, arg2_expected, arg3_expected, arg4_expected],
);
let st_ok = builder.is_equal_flattenable(st, &expected_statement);
let ok = builder.all([op_code_ok, aux_tag_ok, arg_types_ok, merkle_proof_ok, st_ok]);
measure_gates_end!(builder, measure);
ok
}
fn verify_merkle_delete_circuit(
params: &Params,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
aux: &TableEntryTarget,
cache: &StatementCache,
) -> BoolTarget {
let measure = measure_gates_begin!(builder, "MerkleDeleteOp");
let (aux_tag_ok, resolved_merkle_tree_state_transition_claim) =
aux.as_type::<MerkleTreeStateTransitionClaimTarget>(
builder,
OperationAuxTableTag::MerkleTreeStateTransitionProof as u32,
);
let op_code_ok = op_type.has_native(builder, NativeOperation::ContainerDeleteFromEntries);
let (arg_types_ok, [new_root_value, old_root_value, op_key_value]) =
cache.first_n_args_as_values();
let expected_merkle_op = builder.constant(F::from_canonical_u8(MerkleTreeOp::Delete as u8));
// Check Merkle proof (verified elsewhere) against op args.
let merkle_proof_checks = [
/* The supplied Merkle transition proof must be enabled. */
resolved_merkle_tree_state_transition_claim.enabled,
/* ...and it must be a deletion proof. */
builder.is_equal(
resolved_merkle_tree_state_transition_claim.op,
expected_merkle_op,
),
/* ...for the root-key combination in the resolved op args. */
builder.is_equal_slice(
&old_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.old_root
.elements,
),
builder.is_equal_slice(
&new_root_value.elements,
&resolved_merkle_tree_state_transition_claim
.new_root
.elements,
),
builder.is_equal_slice(
&op_key_value.elements,
&resolved_merkle_tree_state_transition_claim.op_key.elements,
),
];
let merkle_proof_ok = builder.all(merkle_proof_checks);
// Check output statement
let arg1_expected = cache.equations[0].lhs.clone();
let arg2_expected = cache.equations[1].lhs.clone();
let arg3_expected = cache.equations[2].lhs.clone();
let expected_statement = StatementTarget::new_native(
builder,
params,
NativePredicate::ContainerDelete,
&[arg1_expected, arg2_expected, arg3_expected],
);
let st_ok = builder.is_equal_flattenable(st, &expected_statement);
let ok = builder.all([op_code_ok, aux_tag_ok, arg_types_ok, merkle_proof_ok, st_ok]);
measure_gates_end!(builder, measure);
ok
}
fn verify_custom_circuit( fn verify_custom_circuit(
builder: &mut CircuitBuilder, builder: &mut CircuitBuilder,
st: &StatementTarget, st: &StatementTarget,
@ -1117,9 +1383,9 @@ fn make_custom_statement_circuit(
params.max_statement_args, params.max_statement_args,
custom_predicate.predicate.args_len, custom_predicate.predicate.args_len,
); );
let st_args = (0..params.max_statement_args) let st_args = std::iter::zip(lt_mask, args)
.map(|i| { .map(|(mask, arg)| {
let v = builder.select_flattenable(params, lt_mask[i], &args[i], &arg_none); let v = builder.select_flattenable(params, mask, arg, &arg_none);
StatementArgTarget::wildcard_literal(builder, &v) StatementArgTarget::wildcard_literal(builder, &v)
}) })
.collect(); .collect();
@ -1380,6 +1646,7 @@ fn verify_main_pod_circuit(
builder, builder,
&main_pod.merkle_proofs, &main_pod.merkle_proofs,
&main_pod.public_key_of_sks, &main_pod.public_key_of_sks,
&main_pod.merkle_tree_state_transition_proofs,
&main_pod.custom_predicate_verifications, &main_pod.custom_predicate_verifications,
&custom_predicate_table, &custom_predicate_table,
)?; )?;
@ -1446,6 +1713,7 @@ pub struct MainPodVerifyTarget {
operations: Vec<OperationTarget>, operations: Vec<OperationTarget>,
merkle_proofs: Vec<MerkleClaimAndProofTarget>, merkle_proofs: Vec<MerkleClaimAndProofTarget>,
public_key_of_sks: Vec<BigUInt320Target>, public_key_of_sks: Vec<BigUInt320Target>,
merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProofTarget>,
custom_predicate_batches: Vec<CustomPredicateBatchTarget>, custom_predicate_batches: Vec<CustomPredicateBatchTarget>,
custom_predicate_verifications: Vec<CustomPredicateVerifyEntryTarget>, custom_predicate_verifications: Vec<CustomPredicateVerifyEntryTarget>,
} }
@ -1482,6 +1750,15 @@ impl MainPodVerifyTarget {
public_key_of_sks: (0..params.max_public_key_of) public_key_of_sks: (0..params.max_public_key_of)
.map(|_| builder.add_virtual_biguint320_target()) .map(|_| builder.add_virtual_biguint320_target())
.collect(), .collect(),
merkle_tree_state_transition_proofs: (0..params
.max_merkle_tree_state_transition_proofs_containers)
.map(|_| {
MerkleTreeStateTransitionProofTarget::new_virtual(
params.max_depth_mt_containers,
builder,
)
})
.collect(),
custom_predicate_batches: (0..params.max_custom_predicate_batches) custom_predicate_batches: (0..params.max_custom_predicate_batches)
.map(|_| builder.add_virtual_custom_predicate_batch(params)) .map(|_| builder.add_virtual_custom_predicate_batch(params))
.collect(), .collect(),
@ -1511,6 +1788,7 @@ pub struct MainPodVerifyInput {
pub operations: Vec<mainpod::Operation>, pub operations: Vec<mainpod::Operation>,
pub merkle_proofs: Vec<MerkleClaimAndProof>, pub merkle_proofs: Vec<MerkleClaimAndProof>,
pub public_key_of_sks: Vec<SecretKey>, pub public_key_of_sks: Vec<SecretKey>,
pub merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProof>,
pub custom_predicate_batches: Vec<Arc<CustomPredicateBatch>>, pub custom_predicate_batches: Vec<Arc<CustomPredicateBatch>>,
pub custom_predicate_verifications: Vec<CustomPredicateVerification>, pub custom_predicate_verifications: Vec<CustomPredicateVerification>,
} }
@ -1641,6 +1919,25 @@ impl InnerCircuit for MainPodVerifyTarget {
pw.set_biguint320_target(&self.public_key_of_sks[i], &pad_sk)?; pw.set_biguint320_target(&self.public_key_of_sks[i], &pad_sk)?;
} }
assert!(
input.merkle_tree_state_transition_proofs.len()
<= self
.params
.max_merkle_tree_state_transition_proofs_containers
);
for (i, mtp) in input.merkle_tree_state_transition_proofs.iter().enumerate() {
self.merkle_tree_state_transition_proofs[i].set_targets(pw, true, mtp)?;
}
// Padding
let pad_mtp = MerkleTreeStateTransitionProof::empty();
for i in input.merkle_tree_state_transition_proofs.len()
..self
.params
.max_merkle_tree_state_transition_proofs_containers
{
self.merkle_tree_state_transition_proofs[i].set_targets(pw, false, &pad_mtp)?;
}
assert!(input.custom_predicate_batches.len() <= self.params.max_custom_predicate_batches); assert!(input.custom_predicate_batches.len() <= self.params.max_custom_predicate_batches);
for (i, cpb) in input.custom_predicate_batches.iter().enumerate() { for (i, cpb) in input.custom_predicate_batches.iter().enumerate() {
self.custom_predicate_batches[i].set_targets(pw, &self.params, cpb)?; self.custom_predicate_batches[i].set_targets(pw, &self.params, cpb)?;
@ -1703,7 +2000,7 @@ mod tests {
mainpod::{calculate_id, OperationArg, OperationAux}, mainpod::{calculate_id, OperationArg, OperationAux},
primitives::{ primitives::{
ec::schnorr::SecretKey, ec::schnorr::SecretKey,
merkletree::{MerkleClaimAndProof, MerkleTree}, merkletree::{MerkleClaimAndProof, MerkleTree, MerkleTreeStateTransitionProof},
}, },
}, },
frontend::{self, literal, CustomPredicateBatchBuilder, StatementTmplBuilder}, frontend::{self, literal, CustomPredicateBatchBuilder, StatementTmplBuilder},
@ -1719,6 +2016,7 @@ mod tests {
prev_statements: Vec<mainpod::Statement>, prev_statements: Vec<mainpod::Statement>,
merkle_proofs: Vec<MerkleClaimAndProof>, merkle_proofs: Vec<MerkleClaimAndProof>,
secret_keys: Vec<SecretKey>, secret_keys: Vec<SecretKey>,
merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProof>,
) -> Result<()> { ) -> Result<()> {
let params = Params { let params = Params {
max_custom_predicate_batches: 0, max_custom_predicate_batches: 0,
@ -1749,11 +2047,23 @@ mod tests {
.map(|sk| builder.constant_biguint320(&sk.0)) .map(|sk| builder.constant_biguint320(&sk.0))
.collect(); .collect();
let merkle_tree_state_transition_proofs_target: Vec<_> =
merkle_tree_state_transition_proofs
.iter()
.map(|_| {
MerkleTreeStateTransitionProofTarget::new_virtual(
params.max_depth_mt_containers,
&mut builder,
)
})
.collect();
let aux_table = build_operation_aux_table_circuit( let aux_table = build_operation_aux_table_circuit(
&params, &params,
&mut builder, &mut builder,
&merkle_proofs_target, &merkle_proofs_target,
&secret_keys_target, &secret_keys_target,
&merkle_tree_state_transition_proofs_target,
&[], &[],
&[], &[],
)?; )?;
@ -1781,6 +2091,17 @@ mod tests {
{ {
merkle_proof_target.set_targets(&mut pw, true, merkle_proof)? merkle_proof_target.set_targets(&mut pw, true, merkle_proof)?
} }
for (merkle_tree_state_transition_proof_target, merkle_tree_state_transition_proof) in
merkle_tree_state_transition_proofs_target
.iter()
.zip(merkle_tree_state_transition_proofs.iter())
{
merkle_tree_state_transition_proof_target.set_targets(
&mut pw,
true,
merkle_tree_state_transition_proof,
)?
}
// generate & verify proof // generate & verify proof
let data = builder.build::<C>(); let data = builder.build::<C>();
@ -1927,7 +2248,7 @@ mod tests {
.into_iter() .into_iter()
.for_each(|(op, st)| { .for_each(|(op, st)| {
let check = std::panic::catch_unwind(|| { let check = std::panic::catch_unwind(|| {
operation_verify(st, op, prev_statements.to_vec(), vec![], vec![]) operation_verify(st, op, prev_statements.to_vec(), vec![], vec![], vec![])
}); });
match check { match check {
Err(e) => { Err(e) => {
@ -1992,7 +2313,9 @@ mod tests {
] ]
.into_iter() .into_iter()
.for_each(|(op, st)| { .for_each(|(op, st)| {
assert!(operation_verify(st, op, prev_statements.to_vec(), vec![], vec![]).is_err()) assert!(
operation_verify(st, op, prev_statements.to_vec(), vec![], vec![], vec![]).is_err()
)
}); });
} }
@ -2005,7 +2328,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![Statement::None.into()]; let prev_statements = vec![Statement::None.into()];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2023,7 +2346,7 @@ mod tests {
vec![], vec![],
OperationAux::None, OperationAux::None,
); );
operation_verify(st1, op, prev_statements, vec![], vec![]) operation_verify(st1, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2035,7 +2358,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![Statement::None.into()]; let prev_statements = vec![Statement::None.into()];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2058,7 +2381,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2]; let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2081,7 +2404,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2]; let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2104,7 +2427,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2.clone()]; let prev_statements = vec![st1, st2.clone()];
operation_verify(st, op, prev_statements, vec![], vec![])?; operation_verify(st, op, prev_statements, vec![], vec![], vec![])?;
// Also check negative < negative // Also check negative < negative
let st3: mainpod::Statement = Statement::equal( let st3: mainpod::Statement = Statement::equal(
@ -2128,7 +2451,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st3.clone(), st4]; let prev_statements = vec![st3.clone(), st4];
operation_verify(st, op, prev_statements, vec![], vec![])?; operation_verify(st, op, prev_statements, vec![], vec![], vec![])?;
// Also check negative < positive // Also check negative < positive
let st: mainpod::Statement = Statement::lt( let st: mainpod::Statement = Statement::lt(
@ -2142,7 +2465,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st3, st2]; let prev_statements = vec![st3, st2];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2165,7 +2488,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2.clone()]; let prev_statements = vec![st1, st2.clone()];
operation_verify(st, op, prev_statements, vec![], vec![])?; operation_verify(st, op, prev_statements, vec![], vec![], vec![])?;
// Also check negative <= negative // Also check negative <= negative
let st3: mainpod::Statement = Statement::equal( let st3: mainpod::Statement = Statement::equal(
@ -2189,7 +2512,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st3.clone(), st4]; let prev_statements = vec![st3.clone(), st4];
operation_verify(st, op, prev_statements, vec![], vec![])?; operation_verify(st, op, prev_statements, vec![], vec![], vec![])?;
// Also check negative <= positive // Also check negative <= positive
let st: mainpod::Statement = Statement::lt_eq( let st: mainpod::Statement = Statement::lt_eq(
@ -2203,7 +2526,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st3, st2]; let prev_statements = vec![st3, st2];
operation_verify(st, op, prev_statements.clone(), vec![], vec![])?; operation_verify(st, op, prev_statements.clone(), vec![], vec![], vec![])?;
// Also check equality, both positive and negative. // Also check equality, both positive and negative.
let st: mainpod::Statement = Statement::lt_eq( let st: mainpod::Statement = Statement::lt_eq(
@ -2216,7 +2539,7 @@ mod tests {
vec![OperationArg::Index(0), OperationArg::Index(0)], vec![OperationArg::Index(0), OperationArg::Index(0)],
OperationAux::None, OperationAux::None,
); );
operation_verify(st, op, prev_statements.clone(), vec![], vec![])?; operation_verify(st, op, prev_statements.clone(), vec![], vec![], vec![])?;
let st: mainpod::Statement = Statement::lt_eq( let st: mainpod::Statement = Statement::lt_eq(
AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")), AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")),
AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")), AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")),
@ -2227,7 +2550,7 @@ mod tests {
vec![OperationArg::Index(1), OperationArg::Index(1)], vec![OperationArg::Index(1), OperationArg::Index(1)],
OperationAux::None, OperationAux::None,
); );
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2276,7 +2599,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2, st3]; let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2322,7 +2645,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2, st3]; let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
}) })
} }
@ -2369,7 +2692,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2, st3]; let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
}) })
} }
@ -2411,7 +2734,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2, st3]; let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
}) })
} }
@ -2456,7 +2779,7 @@ mod tests {
let prev_statements = [st1, st2, st3]; let prev_statements = [st1, st2, st3];
let check = std::panic::catch_unwind(|| { let check = std::panic::catch_unwind(|| {
operation_verify(st, op, prev_statements.to_vec(), vec![], vec![]) operation_verify(st, op, prev_statements.to_vec(), vec![], vec![], vec![])
}); });
match check { match check {
Err(e) => { Err(e) => {
@ -2489,7 +2812,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1]; let prev_statements = vec![st1];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2515,7 +2838,7 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![st1, st2]; let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![], vec![]) operation_verify(st, op, prev_statements, vec![], vec![], vec![])
} }
#[test] #[test]
@ -2555,7 +2878,7 @@ mod tests {
no_key_pf, no_key_pf,
)]; )];
let prev_statements = vec![root_st, key_st]; let prev_statements = vec![root_st, key_st];
operation_verify(st, op, prev_statements, merkle_proofs, vec![]) operation_verify(st, op, prev_statements, merkle_proofs, vec![], vec![])
} }
#[test] #[test]
@ -2602,7 +2925,163 @@ mod tests {
key_pf, key_pf,
)]; )];
let prev_statements = vec![root_st, key_st, value_st]; let prev_statements = vec![root_st, key_st, value_st];
operation_verify(st, op, prev_statements, merkle_proofs, vec![]) operation_verify(st, op, prev_statements, merkle_proofs, vec![], vec![])
}
#[test]
fn test_operation_verify_merkle_insert() -> Result<()> {
let params = Params::default();
let mut tree = MerkleTree::new(params.max_depth_mt_containers, &[].into())?;
let key = 175.into();
let key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "key"));
let value = 0.into();
let value_ak = AnchoredKey::from((PodId(RawValue::from(72).into()), "value"));
let state_transition_proof = tree.insert(&key, &value)?;
let old_root = Value::from(state_transition_proof.old_root);
let old_root_ak = AnchoredKey::from((PodId(RawValue::from(73).into()), "old_root"));
let new_root = Value::from(state_transition_proof.new_root);
let new_root_ak = AnchoredKey::from((PodId(RawValue::from(74).into()), "new_root"));
let new_root_st: mainpod::Statement =
Statement::equal(new_root_ak.clone(), new_root.clone()).into();
let old_root_st: mainpod::Statement =
Statement::equal(old_root_ak.clone(), old_root.clone()).into();
let key_st: mainpod::Statement = Statement::equal(key_ak.clone(), key).into();
let value_st: mainpod::Statement = Statement::equal(value_ak.clone(), value).into();
let st: mainpod::Statement =
Statement::insert(new_root_ak, old_root_ak, key_ak, value_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::ContainerInsertFromEntries),
vec![
OperationArg::Index(0),
OperationArg::Index(1),
OperationArg::Index(2),
OperationArg::Index(3),
],
OperationAux::MerkleTreeStateTransitionProofIndex(0),
);
let merkle_tree_state_transition_proofs = vec![state_transition_proof];
let prev_statements = vec![new_root_st, old_root_st, key_st, value_st];
operation_verify(
st,
op,
prev_statements,
vec![],
vec![],
merkle_tree_state_transition_proofs,
)
}
#[test]
fn test_operation_verify_merkle_update() -> Result<()> {
let params = Params::default();
let mut tree = MerkleTree::new(
params.max_depth_mt_containers,
&[(175.into(), 55.into())].into(),
)?;
let key = 175.into();
let key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "key"));
let value = 0.into();
let value_ak = AnchoredKey::from((PodId(RawValue::from(72).into()), "value"));
let state_transition_proof = tree.update(&key, &value)?;
let old_root = Value::from(state_transition_proof.old_root);
let old_root_ak = AnchoredKey::from((PodId(RawValue::from(73).into()), "old_root"));
let new_root = Value::from(state_transition_proof.new_root);
let new_root_ak = AnchoredKey::from((PodId(RawValue::from(74).into()), "new_root"));
let new_root_st: mainpod::Statement =
Statement::equal(new_root_ak.clone(), new_root.clone()).into();
let old_root_st: mainpod::Statement =
Statement::equal(old_root_ak.clone(), old_root.clone()).into();
let key_st: mainpod::Statement = Statement::equal(key_ak.clone(), key).into();
let value_st: mainpod::Statement = Statement::equal(value_ak.clone(), value).into();
let st: mainpod::Statement =
Statement::update(new_root_ak, old_root_ak, key_ak, value_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::ContainerUpdateFromEntries),
vec![
OperationArg::Index(0),
OperationArg::Index(1),
OperationArg::Index(2),
OperationArg::Index(3),
],
OperationAux::MerkleTreeStateTransitionProofIndex(0),
);
let merkle_tree_state_transition_proofs = vec![state_transition_proof];
let prev_statements = vec![new_root_st, old_root_st, key_st, value_st];
operation_verify(
st,
op,
prev_statements,
vec![],
vec![],
merkle_tree_state_transition_proofs,
)
}
#[test]
fn test_operation_verify_merkle_delete() -> Result<()> {
let params = Params::default();
let mut tree = MerkleTree::new(
params.max_depth_mt_containers,
&[(175.into(), 55.into())].into(),
)?;
let key = 175.into();
let key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "key"));
let state_transition_proof = tree.delete(&key)?;
let old_root = Value::from(state_transition_proof.old_root);
let old_root_ak = AnchoredKey::from((PodId(RawValue::from(73).into()), "old_root"));
let new_root = Value::from(state_transition_proof.new_root);
let new_root_ak = AnchoredKey::from((PodId(RawValue::from(74).into()), "new_root"));
let new_root_st: mainpod::Statement =
Statement::equal(new_root_ak.clone(), new_root.clone()).into();
let old_root_st: mainpod::Statement =
Statement::equal(old_root_ak.clone(), old_root.clone()).into();
let key_st: mainpod::Statement = Statement::equal(key_ak.clone(), key).into();
let st: mainpod::Statement = Statement::delete(new_root_ak, old_root_ak, key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::ContainerDeleteFromEntries),
vec![
OperationArg::Index(0),
OperationArg::Index(1),
OperationArg::Index(2),
],
OperationAux::MerkleTreeStateTransitionProofIndex(0),
);
let merkle_tree_state_transition_proofs = vec![state_transition_proof];
let prev_statements = vec![new_root_st, old_root_st, key_st];
operation_verify(
st,
op,
prev_statements,
vec![],
vec![],
merkle_tree_state_transition_proofs,
)
} }
#[test] #[test]
@ -2631,7 +3110,7 @@ mod tests {
OperationAux::PublicKeyOfIndex(0), OperationAux::PublicKeyOfIndex(0),
); );
let prev_statements = vec![public_key_st, secret_key_st]; let prev_statements = vec![public_key_st, secret_key_st];
operation_verify(st, op, prev_statements, vec![], vec![secret_key]) operation_verify(st, op, prev_statements, vec![], vec![secret_key], vec![])
}) })
} }
@ -2654,7 +3133,9 @@ mod tests {
OperationAux::PublicKeyOfIndex(0), OperationAux::PublicKeyOfIndex(0),
); );
let prev_statements = vec![public_key_st, secret_key_st]; let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], vec![secret_key]).is_err()) assert!(
operation_verify(st, op, prev_statements, vec![], vec![secret_key], vec![]).is_err()
)
} }
#[test] #[test]
@ -2676,7 +3157,9 @@ mod tests {
OperationAux::None, OperationAux::None,
); );
let prev_statements = vec![public_key_st, secret_key_st]; let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], vec![secret_key]).is_err()) assert!(
operation_verify(st, op, prev_statements, vec![], vec![secret_key], vec![]).is_err()
)
} }
#[test] #[test]
@ -2703,7 +3186,8 @@ mod tests {
op, op,
prev_statements, prev_statements,
vec![], vec![],
vec![SecretKey(BigUint::from(123u32))] vec![SecretKey(BigUint::from(123u32))],
vec![]
) )
.is_err()) .is_err())
} }
@ -2727,7 +3211,9 @@ mod tests {
OperationAux::PublicKeyOfIndex(0), OperationAux::PublicKeyOfIndex(0),
); );
let prev_statements = vec![public_key_st, secret_key_st]; let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], vec![secret_key]).is_err()) assert!(
operation_verify(st, op, prev_statements, vec![], vec![secret_key], vec![]).is_err()
)
} }
fn helper_statement_arg_from_template( fn helper_statement_arg_from_template(

View file

@ -19,7 +19,10 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
hash_common_data, hash_common_data,
mock::emptypod::MockEmptyPod, mock::emptypod::MockEmptyPod,
primitives::{ec::schnorr::SecretKey, merkletree::MerkleClaimAndProof}, primitives::{
ec::schnorr::SecretKey,
merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
},
recursion::{ recursion::{
hash_verifier_data, prove_rec_circuit, RecursiveCircuit, RecursiveCircuitTarget, hash_verifier_data, prove_rec_circuit, RecursiveCircuit, RecursiveCircuitTarget,
}, },
@ -173,6 +176,33 @@ pub(crate) fn extract_merkle_proofs(
Ok(table) Ok(table)
} }
/// Extracts Merkle state transition proofs from container update ops.
pub(crate) fn extract_merkle_tree_state_transition_proofs(
params: &Params,
aux_list: &mut [OperationAux],
operations: &[middleware::Operation],
) -> Result<Vec<MerkleTreeStateTransitionProof>> {
let mut table = Vec::new();
for (i, op) in operations.iter().enumerate() {
let pf = match op {
middleware::Operation::ContainerInsertFromEntries(_, _, _, _, pf)
| middleware::Operation::ContainerUpdateFromEntries(_, _, _, _, pf)
| middleware::Operation::ContainerDeleteFromEntries(_, _, _, pf) => pf.clone(),
_ => continue,
};
aux_list[i] = OperationAux::MerkleTreeStateTransitionProofIndex(table.len());
table.push(pf);
}
if table.len() > params.max_merkle_tree_state_transition_proofs_containers {
return Err(Error::custom(format!(
"The number of required Merkle proofs ({}) exceeds the maximum number ({}).",
table.len(),
params.max_merkle_tree_state_transition_proofs_containers
)));
}
Ok(table)
}
pub(crate) fn extract_public_key_of( pub(crate) fn extract_public_key_of(
params: &Params, params: &Params,
aux_list: &mut [OperationAux], aux_list: &mut [OperationAux],
@ -471,6 +501,9 @@ impl PodProver for Prover {
let public_key_of_sks = let public_key_of_sks =
extract_public_key_of(params, &mut aux_list, inputs.operations, inputs.statements)?; extract_public_key_of(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,
@ -513,6 +546,7 @@ impl PodProver for Prover {
operations, operations,
merkle_proofs, merkle_proofs,
public_key_of_sks, public_key_of_sks,
merkle_tree_state_transition_proofs,
custom_predicate_batches, custom_predicate_batches,
custom_predicate_verifications, custom_predicate_verifications,
}; };
@ -912,14 +946,15 @@ pub mod tests {
max_signed_pod_values: 2, max_signed_pod_values: 2,
max_public_statements: 2, max_public_statements: 2,
num_public_statements_id: 4, num_public_statements_id: 4,
max_statement_args: 3, max_statement_args: 4,
max_operation_args: 3, max_operation_args: 4,
max_custom_predicate_batches: 2, max_custom_predicate_batches: 2,
max_custom_predicate_verifications: 2, max_custom_predicate_verifications: 2,
max_custom_predicate_arity: 2, max_custom_predicate_arity: 2,
max_custom_predicate_wildcards: 3, max_custom_predicate_wildcards: 3,
max_custom_batch_size: 2, max_custom_batch_size: 2,
max_merkle_proofs_containers: 2, 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_containers: 4,
max_depth_mt_vds: 6, max_depth_mt_vds: 6,
@ -979,13 +1014,14 @@ pub mod tests {
max_input_recursive_pods: 0, max_input_recursive_pods: 0,
max_statements: 9, max_statements: 9,
max_public_statements: 4, max_public_statements: 4,
max_statement_args: 3, max_statement_args: 4,
max_operation_args: 3, max_operation_args: 4,
max_custom_predicate_arity: 3, max_custom_predicate_arity: 3,
max_custom_batch_size: 3, max_custom_batch_size: 3,
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, max_merkle_proofs_containers: 0,
max_merkle_tree_state_transition_proofs_containers: 0,
..Default::default() ..Default::default()
}; };
println!("{:#?}", params); println!("{:#?}", params);

View file

@ -6,7 +6,7 @@ use crate::{
backends::plonky2::{ backends::plonky2::{
error::{Error, Result}, error::{Error, Result},
mainpod::Statement, mainpod::Statement,
primitives::merkletree::MerkleClaimAndProof, primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
}, },
middleware::{self, OperationType, Params}, middleware::{self, OperationType, Params},
}; };
@ -35,6 +35,7 @@ pub enum OperationAux {
None, None,
MerkleProofIndex(usize), MerkleProofIndex(usize),
PublicKeyOfIndex(usize), PublicKeyOfIndex(usize),
MerkleTreeStateTransitionProofIndex(usize),
CustomPredVerifyIndex(usize), CustomPredVerifyIndex(usize),
} }
@ -46,12 +47,17 @@ impl OperationAux {
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_merkle_proof(params) + params.max_merkle_proofs_containers
} }
fn table_offset_custom_pred_verify(params: &Params) -> usize { fn table_offset_merkle_tree_state_transition_proof(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_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.max_merkle_proofs_containers
+ params.max_public_key_of + params.max_public_key_of
+ params.max_merkle_tree_state_transition_proofs_containers
+ params.max_custom_predicate_verifications + params.max_custom_predicate_verifications
} }
pub fn table_index(&self, params: &Params) -> usize { pub fn table_index(&self, params: &Params) -> usize {
@ -59,6 +65,9 @@ impl OperationAux {
Self::None => 0, Self::None => 0,
Self::MerkleProofIndex(i) => Self::table_offset_merkle_proof(params) + *i, Self::MerkleProofIndex(i) => Self::table_offset_merkle_proof(params) + *i,
Self::PublicKeyOfIndex(i) => Self::table_offset_public_key_of(params) + *i, Self::PublicKeyOfIndex(i) => Self::table_offset_public_key_of(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,
} }
} }
@ -81,6 +90,7 @@ impl Operation {
&self, &self,
statements: &[Statement], statements: &[Statement],
merkle_proofs: &[MerkleClaimAndProof], merkle_proofs: &[MerkleClaimAndProof],
merkle_tree_state_transition_proofs: &[MerkleTreeStateTransitionProof],
) -> Result<crate::middleware::Operation> { ) -> Result<crate::middleware::Operation> {
let deref_args = self let deref_args = self
.1 .1
@ -104,6 +114,17 @@ impl Operation {
.proof .proof
.clone(), .clone(),
), ),
OperationAux::MerkleTreeStateTransitionProofIndex(i) => {
crate::middleware::OperationAux::MerkleTreeStateTransitionProof(
merkle_tree_state_transition_proofs
.get(i)
.ok_or(Error::custom(format!(
"Missing Merkle state transition proof index {}",
i
)))?
.clone(),
)
}
OperationAux::PublicKeyOfIndex(_) => crate::middleware::OperationAux::None, OperationAux::PublicKeyOfIndex(_) => crate::middleware::OperationAux::None,
}; };
Ok(middleware::Operation::op( Ok(middleware::Operation::op(
@ -133,6 +154,9 @@ impl fmt::Display for Operation {
OperationAux::MerkleProofIndex(i) => write!(f, " merkle_proof_{:02}", i)?, OperationAux::MerkleProofIndex(i) => write!(f, " merkle_proof_{:02}", 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::MerkleTreeStateTransitionProofIndex(i) => {
write!(f, " merkle_tree_state_transition_proof_{:02}", i)?
}
} }
Ok(()) Ok(())
} }

View file

@ -74,6 +74,21 @@ impl TryFrom<Statement> for middleware::Statement {
S::HashOf(a1.try_into()?, a2.try_into()?, a3.try_into()?) S::HashOf(a1.try_into()?, a2.try_into()?, a3.try_into()?)
} }
(NP::PublicKeyOf, &[a1, a2]) => S::PublicKeyOf(a1.try_into()?, a2.try_into()?), (NP::PublicKeyOf, &[a1, a2]) => S::PublicKeyOf(a1.try_into()?, a2.try_into()?),
(NP::ContainerInsert, &[a1, a2, a3, a4]) => S::ContainerInsert(
a1.try_into()?,
a2.try_into()?,
a3.try_into()?,
a4.try_into()?,
),
(NP::ContainerUpdate, &[a1, a2, a3, a4]) => S::ContainerUpdate(
a1.try_into()?,
a2.try_into()?,
a3.try_into()?,
a4.try_into()?,
),
(NP::ContainerDelete, &[a1, a2, a3]) => {
S::ContainerDelete(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
_ => Err(Error::custom(format!( _ => Err(Error::custom(format!(
"Ill-formed statement expression {:?}", "Ill-formed statement expression {:?}",
s s

View file

@ -12,12 +12,12 @@ use crate::{
basetypes::{Proof, VerifierOnlyCircuitData}, basetypes::{Proof, VerifierOnlyCircuitData},
error::{Error, Result}, error::{Error, Result},
mainpod::{ mainpod::{
calculate_id, extract_merkle_proofs, layout_statements, calculate_id, extract_merkle_proofs, extract_merkle_tree_state_transition_proofs,
process_private_statements_operations, process_public_statements_operations, Operation, layout_statements, process_private_statements_operations,
OperationAux, Statement, process_public_statements_operations, Operation, OperationAux, Statement,
}, },
mock::emptypod::MockEmptyPod, mock::emptypod::MockEmptyPod,
primitives::merkletree::MerkleClaimAndProof, primitives::merkletree::{MerkleClaimAndProof, MerkleTreeStateTransitionProof},
recursion::hash_verifier_data, recursion::hash_verifier_data,
signedpod::SignedPod, signedpod::SignedPod,
}, },
@ -55,6 +55,8 @@ pub struct MockMainPod {
public_statements: Vec<Statement>, public_statements: Vec<Statement>,
// All Merkle proofs // All Merkle proofs
merkle_proofs_containers: Vec<MerkleClaimAndProof>, merkle_proofs_containers: Vec<MerkleClaimAndProof>,
// All Merkle tree state transition proofs
merkle_tree_state_transition_proofs_containers: Vec<MerkleTreeStateTransitionProof>,
} }
impl Eq for MockMainPod {} impl Eq for MockMainPod {}
@ -145,6 +147,7 @@ struct Data {
operations: Vec<Operation>, operations: Vec<Operation>,
statements: Vec<Statement>, statements: Vec<Statement>,
merkle_proofs: Vec<MerkleClaimAndProof>, merkle_proofs: Vec<MerkleClaimAndProof>,
merkle_tree_state_transition_proofs: Vec<MerkleTreeStateTransitionProof>,
input_signed_pods: Vec<(usize, PodId, serde_json::Value)>, input_signed_pods: Vec<(usize, PodId, serde_json::Value)>,
input_recursive_pods: Vec<(usize, Params, PodId, VDSet, serde_json::Value)>, input_recursive_pods: Vec<(usize, Params, PodId, VDSet, serde_json::Value)>,
} }
@ -176,6 +179,9 @@ impl MockMainPod {
// Extract Merkle proofs and pad. // Extract Merkle proofs and pad.
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.
let merkle_tree_state_transition_proofs =
extract_merkle_tree_state_transition_proofs(params, &mut aux_list, inputs.operations)?;
let operations = process_private_statements_operations( let operations = process_private_statements_operations(
params, params,
@ -214,6 +220,7 @@ impl MockMainPod {
statements, statements,
operations, operations,
merkle_proofs_containers: merkle_proofs, merkle_proofs_containers: merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs,
}) })
} }
@ -313,6 +320,7 @@ impl Pod for MockMainPod {
.deref( .deref(
&self.statements[..input_statement_offset + i], &self.statements[..input_statement_offset + i],
&self.merkle_proofs_containers, &self.merkle_proofs_containers,
&self.merkle_tree_state_transition_proofs_containers,
)? )?
.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())
@ -362,6 +370,9 @@ impl Pod for MockMainPod {
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_containers.clone(),
merkle_tree_state_transition_proofs: self
.merkle_tree_state_transition_proofs_containers
.clone(),
input_signed_pods, input_signed_pods,
input_recursive_pods, input_recursive_pods,
}) })
@ -396,6 +407,7 @@ impl RecursivePod for MockMainPod {
operations, operations,
statements, statements,
merkle_proofs, merkle_proofs,
merkle_tree_state_transition_proofs,
input_signed_pods, input_signed_pods,
input_recursive_pods, input_recursive_pods,
} = serde_json::from_value(data)?; } = serde_json::from_value(data)?;
@ -419,6 +431,7 @@ impl RecursivePod for MockMainPod {
operations, operations,
statements, statements,
merkle_proofs_containers: merkle_proofs, merkle_proofs_containers: merkle_proofs,
merkle_tree_state_transition_proofs_containers: merkle_tree_state_transition_proofs,
})) }))
} }
} }

View file

@ -399,6 +399,7 @@ fn kv_hash_target(
/// has been done correctly for the given new_key. This will allow verifying /// has been done correctly for the given new_key. This will allow verifying
/// correct new leaf insertion, and leaf edition&deletion (if needed). /// correct new leaf insertion, and leaf edition&deletion (if needed).
/// See `MerkleTreeStateTransitionProof` struct for an explanation of the fields. /// See `MerkleTreeStateTransitionProof` struct for an explanation of the fields.
#[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 // `enabled` determines if the merkleproof state transition verification is enabled

View file

@ -344,6 +344,17 @@ impl MerkleTree {
// 2) at i==d, if old_siblings[i] != new_siblings[i]: // 2) at i==d, if old_siblings[i] != new_siblings[i]:
// old_siblings[i] == EMPTY_HASH // old_siblings[i] == EMPTY_HASH
// new_siblings[i] == old_leaf_hash // new_siblings[i] == old_leaf_hash
// First rule out the case of insertion into empty tree.
if new_siblings.is_empty() {
return (old_siblings.is_empty() && proof.old_root == EMPTY_HASH)
.then_some(())
.ok_or(TreeError::state_transition_fail(
"new tree has no siblings yet old tree is not the empty tree"
.to_string(),
));
}
let d = new_siblings.len() - 1; let d = new_siblings.len() - 1;
old_siblings.resize(d + 1, EMPTY_HASH); old_siblings.resize(d + 1, EMPTY_HASH);
for i in 0..d { for i in 0..d {
@ -550,6 +561,23 @@ pub struct MerkleTreeStateTransitionProof {
pub(crate) siblings: Vec<Hash>, pub(crate) siblings: Vec<Hash>,
} }
impl MerkleTreeStateTransitionProof {
/// Value used for padding.
pub fn empty() -> Self {
let empty_proof_and_claim = MerkleClaimAndProof::empty();
Self {
op: MerkleTreeOp::Insert,
old_root: empty_proof_and_claim.root,
op_proof: empty_proof_and_claim.proof,
new_root: empty_proof_and_claim.root,
op_key: empty_proof_and_claim.key,
op_value: empty_proof_and_claim.value,
value: None,
siblings: vec![],
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Node { enum Node {
None, None,

View file

@ -240,6 +240,60 @@ impl MainPodBuilder {
Operation(Native(LtFromEntries), vec![entry2, entry1], op.2) Operation(Native(LtFromEntries), vec![entry2, entry1], op.2)
}), }),
Native(GtToNotEqual) => Ok(Operation(Native(LtToNotEqual), op.1, op.2)), Native(GtToNotEqual) => Ok(Operation(Native(LtToNotEqual), op.1, op.2)),
Native(DictInsertFromEntries) => {
<[_; 4]>::try_from(op.1).map(|[new_dict, old_dict, key, value]| {
Operation(
Native(ContainerInsertFromEntries),
vec![new_dict, old_dict, key, value],
op.2,
)
})
}
Native(DictUpdateFromEntries) => {
<[_; 4]>::try_from(op.1).map(|[new_dict, old_dict, key, value]| {
Operation(
Native(ContainerUpdateFromEntries),
vec![new_dict, old_dict, key, value],
op.2,
)
})
}
Native(DictDeleteFromEntries) => {
<[_; 3]>::try_from(op.1).map(|[new_dict, old_dict, key]| {
Operation(
Native(ContainerDeleteFromEntries),
vec![new_dict, old_dict, key],
op.2,
)
})
}
Native(SetInsertFromEntries) => {
<[_; 3]>::try_from(op.1).map(|[new_set, old_set, value]| {
Operation(
Native(ContainerInsertFromEntries),
vec![new_set, old_set, value.clone(), value],
op.2,
)
})
}
Native(SetDeleteFromEntries) => {
<[_; 3]>::try_from(op.1).map(|[new_set, old_set, value]| {
Operation(
Native(ContainerDeleteFromEntries),
vec![new_set, old_set, value],
op.2,
)
})
}
Native(ArrayUpdateFromEntries) => {
<[_; 4]>::try_from(op.1).map(|[new_arr, old_arr, i, value]| {
Operation(
Native(ContainerUpdateFromEntries),
vec![new_arr, old_arr, i, value],
op.2,
)
})
}
_ => Ok(op), _ => Ok(op),
} }
.map_err(|_| { .map_err(|_| {
@ -249,7 +303,10 @@ impl MainPodBuilder {
/// Fills in auxiliary data if necessary/possible. /// Fills in auxiliary data if necessary/possible.
fn fill_in_aux(op: Operation) -> Result<Operation> { fn fill_in_aux(op: Operation) -> Result<Operation> {
use NativeOperation::{ContainsFromEntries, NotContainsFromEntries}; use NativeOperation::{
ContainerDeleteFromEntries, ContainerInsertFromEntries, ContainerUpdateFromEntries,
ContainsFromEntries, NotContainsFromEntries,
};
use OperationAux as OpAux; use OperationAux as OpAux;
use OperationType::Native; use OperationType::Native;
@ -279,6 +336,45 @@ impl MainPodBuilder {
}; };
Ok(Operation(op_type.clone(), op.1, OpAux::MerkleProof(proof))) Ok(Operation(op_type.clone(), op.1, OpAux::MerkleProof(proof)))
} }
(Native(ContainerInsertFromEntries), OpAux::None)
| (Native(ContainerUpdateFromEntries), OpAux::None)
| (Native(ContainerDeleteFromEntries), OpAux::None) => {
let old_container =
op.1.get(1)
.and_then(|arg| arg.value())
.ok_or(Error::custom(format!(
"Invalid container argument for op {}.",
op
)))?;
let key =
op.1.get(2)
.and_then(|arg| arg.value())
.ok_or(Error::custom(format!(
"Invalid key argument for op {}.",
op
)))?;
let value =
op.1.get(3)
.and_then(|arg| arg.value())
.ok_or(Error::custom(format!(
"Invalid key argument for op {}.",
op
)));
let proof = match op_type {
Native(ContainerInsertFromEntries) => {
old_container.prove_insertion(key, value?)?
}
Native(ContainerUpdateFromEntries) => {
old_container.prove_update(key, value?)?
}
_ => old_container.prove_deletion(key)?,
};
Ok(Operation(
op_type.clone(),
op.1,
OpAux::MerkleTreeStateTransitionProof(proof),
))
}
_ => Ok(op), _ => Ok(op),
} }
} }
@ -405,6 +501,29 @@ impl MainPodBuilder {
return Err(native_arg_error()); return Err(native_arg_error());
} }
} }
(ContainerInsertFromEntries, &[a1, a2, a3, a4]) => {
let (r1, _v1) = a1.value_and_ref().ok_or_else(native_arg_error)?;
let (r2, _v2) = a2.value_and_ref().ok_or_else(native_arg_error)?;
let (r3, _v3) = a3.value_and_ref().ok_or_else(native_arg_error)?;
let (r4, _v4) = a4.value_and_ref().ok_or_else(native_arg_error)?;
// TODO: validate proof
Statement::ContainerInsert(r1, r2, r3, r4)
}
(ContainerUpdateFromEntries, &[a1, a2, a3, a4]) => {
let (r1, _v1) = a1.value_and_ref().ok_or_else(native_arg_error)?;
let (r2, _v2) = a2.value_and_ref().ok_or_else(native_arg_error)?;
let (r3, _v3) = a3.value_and_ref().ok_or_else(native_arg_error)?;
let (r4, _v4) = a4.value_and_ref().ok_or_else(native_arg_error)?;
// TODO: validate proof
Statement::ContainerUpdate(r1, r2, r3, r4)
}
(ContainerDeleteFromEntries, &[a1, a2, a3]) => {
let (r1, _v1) = a1.value_and_ref().ok_or_else(native_arg_error)?;
let (r2, _v2) = a2.value_and_ref().ok_or_else(native_arg_error)?;
let (r3, _v3) = a3.value_and_ref().ok_or_else(native_arg_error)?;
// TODO: validate proof
Statement::ContainerDelete(r1, r2, r3)
}
(t, _) => { (t, _) => {
if t.is_syntactic_sugar() { if t.is_syntactic_sugar() {
return Err(Error::custom(format!( return Err(Error::custom(format!(
@ -741,7 +860,10 @@ pub mod tests {
tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_pod_request, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_pod_request,
zu_kyc_sign_pod_builders, EthDosHelper, MOCK_VD_SET, zu_kyc_sign_pod_builders, EthDosHelper, MOCK_VD_SET,
}, },
middleware::{containers::Dictionary, Value}, middleware::{
containers::{Array, Dictionary, Set},
Value,
},
}; };
// Check that frontend public statements agree with those // Check that frontend public statements agree with those
@ -987,19 +1109,140 @@ pub mod tests {
let st1 = builder.op(true, Operation::new_entry("key", "a")).unwrap(); let st1 = builder.op(true, Operation::new_entry("key", "a")).unwrap();
let st2 = builder.literal(false, Value::from(1)).unwrap(); let st2 = builder.literal(false, Value::from(1)).unwrap();
builder builder.pub_op(Operation(
.pub_op(Operation( // OperationType
// OperationType OperationType::Native(NativeOperation::DictContainsFromEntries),
OperationType::Native(NativeOperation::DictContainsFromEntries), // Vec<OperationArg>
// Vec<OperationArg> vec![
vec![ OperationArg::Statement(st0.clone()),
OperationArg::Statement(st0), OperationArg::Statement(st1),
OperationArg::Statement(st1), OperationArg::Statement(st2),
OperationArg::Statement(st2), ],
], OperationAux::MerkleProof(dict.prove(&Key::from("a")).unwrap().1),
OperationAux::MerkleProof(dict.prove(&Key::from("a")).unwrap().1), ))?;
))
.unwrap(); let mut new_dict = dict.clone();
new_dict.insert(&Key::from("d"), &Value::from(4))?;
builder.pub_op(Operation(
OperationType::Native(NativeOperation::DictInsertFromEntries),
vec![
Value::from(new_dict.clone()).into(),
OperationArg::Statement(st0.clone()),
"d".into(),
4.into(),
],
OperationAux::None,
))?;
let mut new_old_dict = new_dict.clone();
new_old_dict.delete(&Key::from("d"))?;
assert_eq!(new_old_dict, dict);
builder.pub_op(Operation(
OperationType::Native(NativeOperation::DictDeleteFromEntries),
vec![
OperationArg::Statement(st0.clone()),
Value::from(new_dict).into(),
"d".into(),
],
OperationAux::None,
))?;
new_old_dict.update(&Key::from("c"), &55.into())?;
builder.pub_op(Operation(
OperationType::Native(NativeOperation::DictUpdateFromEntries),
vec![
Value::from(new_old_dict).into(),
OperationArg::Statement(st0.clone()),
"c".into(),
55.into(),
],
OperationAux::None,
))?;
let main_prover = MockProver {};
let main_pod = builder.prove(&main_prover).unwrap();
println!("{}", main_pod);
Ok(())
}
#[test]
fn test_sets() -> Result<()> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let mut builder = MainPodBuilder::new(&params, vd_set);
let empty_set = Set::new(params.max_depth_mt_containers, [].into())?;
let mut set1 = empty_set.clone();
set1.insert(&1.into())?;
let mut set2 = set1.clone();
set2.delete(&1.into())?;
assert_eq!(set2, empty_set);
builder.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::SetInsertFromEntries),
// Vec<OperationArg>
vec![
Value::from(set1.clone()).into(),
Value::from(empty_set.clone()).into(),
1.into(),
],
OperationAux::None,
))?;
builder.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::SetDeleteFromEntries),
// Vec<OperationArg>
vec![
Value::from(empty_set.clone()).into(),
Value::from(set1.clone()).into(),
1.into(),
],
OperationAux::None,
))?;
let main_prover = MockProver {};
let main_pod = builder.prove(&main_prover).unwrap();
println!("{}", main_pod);
Ok(())
}
#[test]
fn test_arrays() -> Result<()> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let mut builder = MainPodBuilder::new(&params, vd_set);
let array1 = Array::new(params.max_depth_mt_containers, [1.into()].into())?;
let mut array2 = array1.clone();
array2.update(0, &5.into())?;
builder.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::ArrayUpdateFromEntries),
// Vec<OperationArg>
vec![
Value::from(array2.clone()).into(),
Value::from(array1.clone()).into(),
0.into(),
5.into(),
],
OperationAux::None,
))?;
let main_prover = MockProver {}; let main_prover = MockProver {};
let main_pod = builder.prove(&main_prover).unwrap(); let main_pod = builder.prove(&main_prover).unwrap();

View file

@ -139,6 +139,21 @@ macro_rules! op_impl_oa {
) )
} }
}; };
($fn_name: ident, $op_name: ident, 4) => {
pub fn $fn_name(
a1: impl Into<OperationArg>,
a2: impl Into<OperationArg>,
a3: impl Into<OperationArg>,
a4: impl Into<OperationArg>,
) -> Self {
Self(
OperationType::Native(NativeOperation::$op_name),
vec![a1.into(), a2.into(), a3.into(), a4.into()],
OperationAux::None,
)
}
};
} }
macro_rules! op_impl_st { macro_rules! op_impl_st {
@ -201,4 +216,10 @@ impl Operation {
op_impl_oa!(set_not_contains, SetNotContainsFromEntries, 2); op_impl_oa!(set_not_contains, SetNotContainsFromEntries, 2);
op_impl_oa!(array_contains, ArrayContainsFromEntries, 3); op_impl_oa!(array_contains, ArrayContainsFromEntries, 3);
op_impl_oa!(public_key_of, PublicKeyOf, 2); op_impl_oa!(public_key_of, PublicKeyOf, 2);
op_impl_oa!(dict_insert, DictInsertFromEntries, 4);
op_impl_oa!(dict_update, DictUpdateFromEntries, 4);
op_impl_oa!(dict_delete, DictDeleteFromEntries, 3);
op_impl_oa!(set_insert, SetInsertFromEntries, 3);
op_impl_oa!(set_delete, SetDeleteFromEntries, 3);
op_impl_oa!(array_update, ArrayUpdateFromEntries, 4);
} }

View file

@ -381,7 +381,16 @@ fn validate_and_build_statement_template(
| NativePredicate::SumOf | NativePredicate::SumOf
| NativePredicate::ProductOf | NativePredicate::ProductOf
| NativePredicate::MaxOf | NativePredicate::MaxOf
| NativePredicate::HashOf => 3, | NativePredicate::HashOf
| NativePredicate::ContainerDelete
| NativePredicate::DictDelete
| NativePredicate::SetInsert
| NativePredicate::SetDelete => 3,
NativePredicate::ContainerInsert
| NativePredicate::ContainerUpdate
| NativePredicate::DictInsert
| NativePredicate::DictUpdate
| NativePredicate::ArrayUpdate => 4,
NativePredicate::None | NativePredicate::False => 0, NativePredicate::None | NativePredicate::False => 0,
}; };

View file

@ -9,7 +9,10 @@ use serde::{Deserialize, Deserializer, Serialize};
use super::serialization::{ordered_map, ordered_set}; use super::serialization::{ordered_map, ordered_set};
#[cfg(feature = "backend_plonky2")] #[cfg(feature = "backend_plonky2")]
use crate::backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree}; use crate::backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree};
use crate::middleware::{Error, Hash, Key, RawValue, Result, Value}; use crate::{
backends::plonky2::primitives::merkletree::MerkleTreeStateTransitionProof,
middleware::{Error, Hash, Key, RawValue, Result, Value},
};
/// Dictionary: the user original keys and values are hashed to be used in the leaf. /// Dictionary: the user original keys and values are hashed to be used in the leaf.
/// leaf.key=hash(original_key) /// leaf.key=hash(original_key)
@ -52,6 +55,21 @@ impl Dictionary {
pub fn prove_nonexistence(&self, key: &Key) -> Result<MerkleProof> { pub fn prove_nonexistence(&self, key: &Key) -> Result<MerkleProof> {
Ok(self.mt.prove_nonexistence(&key.raw())?) Ok(self.mt.prove_nonexistence(&key.raw())?)
} }
pub fn insert(&mut self, key: &Key, value: &Value) -> Result<MerkleTreeStateTransitionProof> {
let mtp = self.mt.insert(&key.raw(), &value.raw())?;
self.kvs.insert(key.clone(), value.clone());
Ok(mtp)
}
pub fn update(&mut self, key: &Key, value: &Value) -> Result<MerkleTreeStateTransitionProof> {
let mtp = self.mt.update(&key.raw(), &value.raw())?;
self.kvs.insert(key.clone(), value.clone());
Ok(mtp)
}
pub fn delete(&mut self, key: &Key) -> Result<MerkleTreeStateTransitionProof> {
let mtp = self.mt.delete(&key.raw())?;
self.kvs.remove(key);
Ok(mtp)
}
pub fn verify( pub fn verify(
max_depth: usize, max_depth: usize,
root: Hash, root: Hash,
@ -79,6 +97,12 @@ impl Dictionary {
max_depth, root, proof, &key, max_depth, root, proof, &key,
)?) )?)
} }
pub fn verify_state_transition(
max_depth: usize,
proof: &MerkleTreeStateTransitionProof,
) -> Result<()> {
MerkleTree::verify_state_transition(max_depth, proof).map_err(|e| e.into())
}
// TODO: Rename to dict to be consistent maybe? // TODO: Rename to dict to be consistent maybe?
pub fn kvs(&self) -> &HashMap<Key, Value> { pub fn kvs(&self) -> &HashMap<Key, Value> {
&self.kvs &self.kvs
@ -156,6 +180,17 @@ impl Set {
let rv = value.raw(); let rv = value.raw();
Ok(self.mt.prove_nonexistence(&rv)?) Ok(self.mt.prove_nonexistence(&rv)?)
} }
pub fn insert(&mut self, value: &Value) -> Result<MerkleTreeStateTransitionProof> {
let raw_value = value.raw();
let mtp = self.mt.insert(&raw_value, &raw_value)?;
self.set.insert(value.clone());
Ok(mtp)
}
pub fn delete(&mut self, value: &Value) -> Result<MerkleTreeStateTransitionProof> {
let mtp = self.mt.delete(&value.raw())?;
self.set.remove(value);
Ok(mtp)
}
pub fn verify(max_depth: usize, root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> { pub fn verify(max_depth: usize, root: Hash, proof: &MerkleProof, value: &Value) -> Result<()> {
let rv = value.raw(); let rv = value.raw();
Ok(MerkleTree::verify(max_depth, root, proof, &rv, &rv)?) Ok(MerkleTree::verify(max_depth, root, proof, &rv, &rv)?)
@ -171,6 +206,12 @@ impl Set {
max_depth, root, proof, &rv, max_depth, root, proof, &rv,
)?) )?)
} }
pub fn verify_state_transition(
max_depth: usize,
proof: &MerkleTreeStateTransitionProof,
) -> Result<()> {
MerkleTree::verify_state_transition(max_depth, proof).map_err(|e| e.into())
}
pub fn set(&self) -> &HashSet<Value> { pub fn set(&self) -> &HashSet<Value> {
&self.set &self.set
} }
@ -244,6 +285,11 @@ impl Array {
let value = self.array.get(i).expect("valid index"); let value = self.array.get(i).expect("valid index");
Ok((value, mtp)) Ok((value, mtp))
} }
pub fn update(&mut self, i: usize, value: &Value) -> Result<MerkleTreeStateTransitionProof> {
let mtp = self.mt.update(&(i as i64).into(), &value.raw())?;
self.array[i] = value.clone();
Ok(mtp)
}
pub fn verify( pub fn verify(
max_depth: usize, max_depth: usize,
root: Hash, root: Hash,
@ -259,6 +305,12 @@ impl Array {
&value.raw(), &value.raw(),
)?) )?)
} }
pub fn verify_state_transition(
max_depth: usize,
proof: &MerkleTreeStateTransitionProof,
) -> Result<()> {
MerkleTree::verify_state_transition(max_depth, proof).map_err(|e| e.into())
}
pub fn array(&self) -> &[Value] { pub fn array(&self) -> &[Value] {
&self.array &self.array
} }

View file

@ -35,7 +35,7 @@ pub use statement::*;
use crate::backends::plonky2::primitives::{ use crate::backends::plonky2::primitives::{
ec::{curve::Point as PublicKey, schnorr::SecretKey}, ec::{curve::Point as PublicKey, schnorr::SecretKey},
merkletree::MerkleProof, merkletree::{MerkleProof, MerkleTreeStateTransitionProof},
}; };
pub const SELF: PodId = PodId(SELF_ID_HASH); pub const SELF: PodId = PodId(SELF_ID_HASH);
@ -535,6 +535,59 @@ impl Value {
))), ))),
} }
} }
/// Returns a Merkle state transition proof for inserting a
/// key-value pair (if applicable).
pub(crate) fn prove_insertion(
&self,
key: &Value,
value: &Value,
) -> Result<MerkleTreeStateTransitionProof> {
let container = self.typed().clone();
match container {
TypedValue::Dictionary(mut d) => d.insert(&key.typed().clone().try_into()?, value),
TypedValue::Set(mut s) => s.insert(value),
_ => Err(Error::custom(format!(
"Invalid container value {}",
self.typed()
))),
}
}
/// Returns a Merkle state transition proof for updating a
/// key-value pair (if applicable).
pub(crate) fn prove_update(
&self,
key: &Value,
value: &Value,
) -> Result<MerkleTreeStateTransitionProof> {
let container = self.typed().clone();
match container {
TypedValue::Array(mut a) => match key.typed() {
TypedValue::Int(i) if i >= &0 => a.update(*i as usize, value),
_ => Err(Error::custom(format!(
"Invalid key {} for container {}.",
key, self
)))?,
},
TypedValue::Dictionary(mut d) => d.update(&key.typed().clone().try_into()?, value),
_ => Err(Error::custom(format!(
"Invalid container value {} for update op",
self.typed()
))),
}
}
/// Returns a Merkle state transition proof for deleting a
/// key (if applicable).
pub(crate) fn prove_deletion(&self, key: &Value) -> Result<MerkleTreeStateTransitionProof> {
let container = self.typed().clone();
match container {
TypedValue::Dictionary(mut d) => d.delete(&key.typed().clone().try_into()?),
TypedValue::Set(mut s) => s.delete(key),
_ => Err(Error::custom(format!(
"Invalid container value {}",
self.typed()
))),
}
}
} }
// A Value can be created from any type Into<TypedValue> type: bool, string-like, i64, ... // A Value can be created from any type Into<TypedValue> type: bool, string-like, i64, ...
@ -760,6 +813,8 @@ pub struct Params {
pub max_custom_predicate_wildcards: usize, pub max_custom_predicate_wildcards: usize,
// maximum number of merkle proofs used for container operations // maximum number of merkle proofs used for container operations
pub max_merkle_proofs_containers: usize, 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 // maximum depth for merkle tree gadget used for container operations
pub max_depth_mt_containers: usize, 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
@ -804,6 +859,7 @@ impl Default for Params {
max_custom_predicate_wildcards: 10, max_custom_predicate_wildcards: 10,
max_custom_batch_size: 5, // TODO: Move down to 4? max_custom_batch_size: 5, // TODO: Move down to 4?
max_merkle_proofs_containers: 5, max_merkle_proofs_containers: 5,
max_merkle_tree_state_transition_proofs_containers: 5,
max_depth_mt_containers: 32, 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,

View file

@ -10,7 +10,7 @@ use crate::{
curve::{Point as PublicKey, GROUP_ORDER}, curve::{Point as PublicKey, GROUP_ORDER},
schnorr::SecretKey, schnorr::SecretKey,
}, },
merkletree::{MerkleProof, MerkleTree}, merkletree::{MerkleProof, MerkleTree, MerkleTreeOp, MerkleTreeStateTransitionProof},
}, },
middleware::{ middleware::{
hash_values, AnchoredKey, CustomPredicate, CustomPredicateRef, Error, NativePredicate, hash_values, AnchoredKey, CustomPredicate, CustomPredicateRef, Error, NativePredicate,
@ -29,6 +29,7 @@ pub enum OperationType {
pub enum OperationAux { pub enum OperationAux {
None, None,
MerkleProof(MerkleProof), MerkleProof(MerkleProof),
MerkleTreeStateTransitionProof(MerkleTreeStateTransitionProof),
} }
impl fmt::Display for OperationAux { impl fmt::Display for OperationAux {
@ -36,6 +37,10 @@ impl fmt::Display for OperationAux {
match self { match self {
Self::None => write!(f, "<no aux>")?, Self::None => write!(f, "<no aux>")?,
Self::MerkleProof(pf) => write!(f, "merkle_proof({})", pf)?, Self::MerkleProof(pf) => write!(f, "merkle_proof({})", pf)?,
// TODO: Make this look nicer.
Self::MerkleTreeStateTransitionProof(pf) => {
write!(f, "merkle_tree_state_transition_proof({:?})", pf)?
}
} }
Ok(()) Ok(())
} }
@ -80,6 +85,9 @@ pub enum NativeOperation {
MaxOf = 13, MaxOf = 13,
HashOf = 14, HashOf = 14,
PublicKeyOf = 15, PublicKeyOf = 15,
ContainerInsertFromEntries = 16,
ContainerUpdateFromEntries = 17,
ContainerDeleteFromEntries = 18,
// Syntactic sugar operations. These operations are not supported by the backend. The // Syntactic sugar operations. These operations are not supported by the backend. The
// frontend compiler is responsible of translating these operations into the operations above. // frontend compiler is responsible of translating these operations into the operations above.
@ -91,6 +99,12 @@ pub enum NativeOperation {
GtEqFromEntries = 1006, GtEqFromEntries = 1006,
GtFromEntries = 1007, GtFromEntries = 1007,
GtToNotEqual = 1008, GtToNotEqual = 1008,
DictInsertFromEntries = 1009,
DictUpdateFromEntries = 1010,
DictDeleteFromEntries = 1011,
SetInsertFromEntries = 1012,
SetDeleteFromEntries = 1013,
ArrayUpdateFromEntries = 1014,
} }
impl NativeOperation { impl NativeOperation {
@ -140,6 +154,15 @@ impl OperationType {
NativeOperation::PublicKeyOf => { NativeOperation::PublicKeyOf => {
Some(Predicate::Native(NativePredicate::PublicKeyOf)) Some(Predicate::Native(NativePredicate::PublicKeyOf))
} }
NativeOperation::ContainerInsertFromEntries => {
Some(Predicate::Native(NativePredicate::ContainerInsert))
}
NativeOperation::ContainerUpdateFromEntries => {
Some(Predicate::Native(NativePredicate::ContainerUpdate))
}
NativeOperation::ContainerDeleteFromEntries => {
Some(Predicate::Native(NativePredicate::ContainerDelete))
}
no => unreachable!("Unexpected syntactic sugar op {:?}", no), no => unreachable!("Unexpected syntactic sugar op {:?}", no),
}, },
OperationType::Custom(cpr) => Some(Predicate::Custom(cpr.clone())), OperationType::Custom(cpr) => Some(Predicate::Custom(cpr.clone())),
@ -175,6 +198,26 @@ pub enum Operation {
MaxOf(Statement, Statement, Statement), MaxOf(Statement, Statement, Statement),
HashOf(Statement, Statement, Statement), HashOf(Statement, Statement, Statement),
PublicKeyOf(Statement, Statement), PublicKeyOf(Statement, Statement),
ContainerInsertFromEntries(
/* new_root */ Statement,
/* old_root */ Statement,
/* key */ Statement,
/* value */ Statement,
/* proof */ MerkleTreeStateTransitionProof,
),
ContainerUpdateFromEntries(
/* new_root */ Statement,
/* old_root */ Statement,
/* key */ Statement,
/* value */ Statement,
/* proof */ MerkleTreeStateTransitionProof,
),
ContainerDeleteFromEntries(
/* new_root */ Statement,
/* old_root */ Statement,
/* key */ Statement,
/* proof */ MerkleTreeStateTransitionProof,
),
Custom(CustomPredicateRef, Vec<Statement>), Custom(CustomPredicateRef, Vec<Statement>),
} }
@ -215,6 +258,13 @@ impl Operation {
Self::MaxOf(_, _, _) => OT::Native(MaxOf), Self::MaxOf(_, _, _) => OT::Native(MaxOf),
Self::HashOf(_, _, _) => OT::Native(HashOf), Self::HashOf(_, _, _) => OT::Native(HashOf),
Self::PublicKeyOf(_, _) => OT::Native(PublicKeyOf), Self::PublicKeyOf(_, _) => OT::Native(PublicKeyOf),
Self::ContainerInsertFromEntries(_, _, _, _, _) => {
OT::Native(ContainerInsertFromEntries)
}
Self::ContainerUpdateFromEntries(_, _, _, _, _) => {
OT::Native(ContainerUpdateFromEntries)
}
Self::ContainerDeleteFromEntries(_, _, _, _) => OT::Native(ContainerDeleteFromEntries),
Self::Custom(cpr, _) => OT::Custom(cpr.clone()), Self::Custom(cpr, _) => OT::Custom(cpr.clone()),
} }
} }
@ -237,6 +287,9 @@ impl Operation {
Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3], Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3],
Self::HashOf(s1, s2, s3) => vec![s1, s2, s3], Self::HashOf(s1, s2, s3) => vec![s1, s2, s3],
Self::PublicKeyOf(s1, s2) => vec![s1, s2], Self::PublicKeyOf(s1, s2) => vec![s1, s2],
Self::ContainerInsertFromEntries(s1, s2, s3, s4, _pf) => vec![s1, s2, s3, s4],
Self::ContainerUpdateFromEntries(s1, s2, s3, s4, _pf) => vec![s1, s2, s3, s4],
Self::ContainerDeleteFromEntries(s1, s2, s3, _pf) => vec![s1, s2, s3],
Self::Custom(_, args) => args, Self::Custom(_, args) => args,
} }
} }
@ -290,6 +343,33 @@ impl Operation {
Self::HashOf(s1.clone(), s2.clone(), s3.clone()) Self::HashOf(s1.clone(), s2.clone(), s3.clone())
} }
(NO::PublicKeyOf, &[s1, s2], OA::None) => Self::PublicKeyOf(s1.clone(), s2.clone()), (NO::PublicKeyOf, &[s1, s2], OA::None) => Self::PublicKeyOf(s1.clone(), s2.clone()),
(
NO::ContainerInsertFromEntries,
&[s1, s2, s3, s4],
OA::MerkleTreeStateTransitionProof(pf),
) => Self::ContainerInsertFromEntries(
s1.clone(),
s2.clone(),
s3.clone(),
s4.clone(),
pf,
),
(
NO::ContainerUpdateFromEntries,
&[s1, s2, s3, s4],
OA::MerkleTreeStateTransitionProof(pf),
) => Self::ContainerUpdateFromEntries(
s1.clone(),
s2.clone(),
s3.clone(),
s4.clone(),
pf,
),
(
NO::ContainerDeleteFromEntries,
&[s1, s2, s3],
OA::MerkleTreeStateTransitionProof(pf),
) => Self::ContainerDeleteFromEntries(s1.clone(), s2.clone(), s3.clone(), pf),
_ => Err(Error::custom(format!( _ => Err(Error::custom(format!(
"Ill-formed operation {:?} with {} arguments {:?} and aux {:?}.", "Ill-formed operation {:?} with {} arguments {:?} and aux {:?}.",
op_code, op_code,
@ -392,6 +472,67 @@ impl Operation {
(Self::PublicKeyOf(s1, s2), PublicKeyOf(v3, v4)) => { (Self::PublicKeyOf(s1, s2), PublicKeyOf(v3, v4)) => {
Self::check_public_key(&val(v3, s1)?, &val(v4, s2)?)? Self::check_public_key(&val(v3, s1)?, &val(v4, s2)?)?
} }
(
Self::ContainerInsertFromEntries(new_root_s, old_root_s, key_s, val_s, pf),
ContainerInsert(new_root_v, old_root_v, key_v, val_v),
) => {
let old_root = val(old_root_v, old_root_s)?;
let new_root = val(new_root_v, new_root_s)?;
let key = val(key_v, key_s)?;
let value = val(val_v, val_s)?;
(pf.op == MerkleTreeOp::Insert
&& Value::from(pf.old_root) == old_root
&& Value::from(pf.new_root) == new_root
&& pf.op_key == key.raw()
&& pf.op_value == value.raw())
.then_some(())
.ok_or(Error::custom(
"The provided Merkle tree state transition proof does not match the claim."
.into(),
))?;
MerkleTree::verify_state_transition(params.max_depth_mt_containers, pf)?;
true
}
(
Self::ContainerUpdateFromEntries(new_root_s, old_root_s, key_s, val_s, pf),
ContainerUpdate(new_root_v, old_root_v, key_v, val_v),
) => {
let old_root = val(old_root_v, old_root_s)?;
let new_root = val(new_root_v, new_root_s)?;
let key = val(key_v, key_s)?;
let value = val(val_v, val_s)?;
(pf.op == MerkleTreeOp::Update
&& Value::from(pf.old_root) == old_root
&& Value::from(pf.new_root) == new_root
&& pf.op_key == key.raw()
&& pf.op_value == value.raw())
.then_some(())
.ok_or(Error::custom(
"The provided Merkle tree state transition proof does not match the claim."
.into(),
))?;
MerkleTree::verify_state_transition(params.max_depth_mt_containers, pf)?;
true
}
(
Self::ContainerDeleteFromEntries(new_root_s, old_root_s, key_s, pf),
ContainerDelete(new_root_v, old_root_v, key_v),
) => {
let old_root = val(old_root_v, old_root_s)?;
let new_root = val(new_root_v, new_root_s)?;
let key = val(key_v, key_s)?;
(pf.op == MerkleTreeOp::Delete
&& Value::from(pf.old_root) == old_root
&& Value::from(pf.new_root) == new_root
&& pf.op_key == key.raw())
.then_some(())
.ok_or(Error::custom(
"The provided Merkle tree state transition proof does not match the claim."
.into(),
))?;
MerkleTree::verify_state_transition(params.max_depth_mt_containers, pf)?;
true
}
(Self::Custom(CustomPredicateRef { batch, index }, args), Custom(cpr, s_args)) (Self::Custom(CustomPredicateRef { batch, index }, args), Custom(cpr, s_args))
if batch == &cpr.batch && index == &cpr.index => if batch == &cpr.batch && index == &cpr.index =>
{ {
@ -687,6 +828,126 @@ mod tests {
}) })
} }
#[test]
fn check_container_update_ops() -> Result<()> {
let params = Params::default();
let pod_id = PodId::default();
let new_root_ak = AnchoredKey::new(pod_id, Key::new("new_root".into()));
let old_root_ak = AnchoredKey::new(pod_id, Key::new("new_root".into()));
let key_ak = AnchoredKey::new(pod_id, Key::new("key".into()));
let val_ak = AnchoredKey::new(pod_id, Key::new("value".into()));
// Form Merkle tree
let kvs = (0..10)
.map(|i| (hash_value(&i.into()).into(), i.into()))
.collect::<HashMap<_, _>>();
let mut mt = MerkleTree::new(params.max_depth_mt_containers, &kvs)?;
// Check insertion proofs
(11..20)
.map(|i| (hash_value(&i.into()).into(), i.into()))
.try_for_each(|(k, v)| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let mtp = mt.insert(&k, &v)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let value_s = Statement::Equal(val_ak.clone().into(), v.into());
// Form op
let op = Operation::ContainerInsertFromEntries(
new_root_s, old_root_s, key_s, value_s, mtp,
);
// Form output statement
let st = Statement::ContainerInsert(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
val_ak.clone().into(),
);
// Check op against output statement
op.check(&params, &st).and_then(|ind| {
if ind {
Ok(())
} else {
Err(Error::custom(format!(
"Insertion op check failed for pair ({},{})",
k, v
)))
}
})
})?;
// Check update proofs
(11..20)
.map(|i| (hash_value(&i.into()).into(), (i + 1).into()))
.try_for_each(|(k, v)| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let mtp = mt.update(&k, &v)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let value_s = Statement::Equal(val_ak.clone().into(), v.into());
// Form op
let op = Operation::ContainerUpdateFromEntries(
new_root_s, old_root_s, key_s, value_s, mtp,
);
// Form output statement
let st = Statement::ContainerUpdate(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
val_ak.clone().into(),
);
// Check op against output statement
op.check(&params, &st).and_then(|ind| {
if ind {
Ok(())
} else {
Err(Error::custom(format!(
"Update op check failed for pair ({},{})",
k, v
)))
}
})
})?;
// Check deletion proofs
(11..20)
.map(|i| hash_value(&i.into()).into())
.try_for_each(|k| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let mtp = mt.delete(&k)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
// Form op
let op = Operation::ContainerDeleteFromEntries(new_root_s, old_root_s, key_s, mtp);
// Form output statement
let st = Statement::ContainerDelete(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
);
// Check op against output statement
op.check(&params, &st).and_then(|ind| {
if ind {
Ok(())
} else {
Err(Error::custom(format!(
"Deletion op check failed for key {}",
k
)))
}
})
})
}
#[test] #[test]
fn check_public_key_of_op() -> Result<()> { fn check_public_key_of_op() -> Result<()> {
let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64)); let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64));

View file

@ -34,6 +34,9 @@ pub enum NativePredicate {
MaxOf = 10, MaxOf = 10,
HashOf = 11, HashOf = 11,
PublicKeyOf = 12, PublicKeyOf = 12,
ContainerInsert = 13,
ContainerUpdate = 14,
ContainerDelete = 15,
// Syntactic sugar predicates. These predicates are not supported by the backend. The // Syntactic sugar predicates. These predicates are not supported by the backend. The
// frontend compiler is responsible of translating these predicates into the predicates above. // frontend compiler is responsible of translating these predicates into the predicates above.
@ -44,6 +47,12 @@ pub enum NativePredicate {
ArrayContains = 1004, // there is no ArrayNotContains ArrayContains = 1004, // there is no ArrayNotContains
GtEq = 1005, GtEq = 1005,
Gt = 1006, Gt = 1006,
DictInsert = 1009,
DictUpdate = 1010,
DictDelete = 1011,
SetInsert = 1012,
SetDelete = 1013,
ArrayUpdate = 1014,
} }
impl Display for NativePredicate { impl Display for NativePredicate {
@ -64,11 +73,20 @@ impl Display for NativePredicate {
NativePredicate::MaxOf => "MaxOf", NativePredicate::MaxOf => "MaxOf",
NativePredicate::HashOf => "HashOf", NativePredicate::HashOf => "HashOf",
NativePredicate::PublicKeyOf => "PublicKeyOf", NativePredicate::PublicKeyOf => "PublicKeyOf",
NativePredicate::ContainerInsert => "ContainerInsert",
NativePredicate::ContainerUpdate => "ContainerUpdate",
NativePredicate::ContainerDelete => "ContainerDelete",
NativePredicate::DictContains => "DictContains", NativePredicate::DictContains => "DictContains",
NativePredicate::DictNotContains => "DictNotContains", NativePredicate::DictNotContains => "DictNotContains",
NativePredicate::ArrayContains => "ArrayContains", NativePredicate::ArrayContains => "ArrayContains",
NativePredicate::SetContains => "SetContains", NativePredicate::SetContains => "SetContains",
NativePredicate::SetNotContains => "SetNotContains", NativePredicate::SetNotContains => "SetNotContains",
NativePredicate::DictInsert => "DictInsert",
NativePredicate::DictUpdate => "DictUpdate",
NativePredicate::DictDelete => "DictDelete",
NativePredicate::SetInsert => "SetInsert",
NativePredicate::SetDelete => "SetDelete",
NativePredicate::ArrayUpdate => "ArrayUpdate",
}; };
write!(f, "{}", s) write!(f, "{}", s)
} }
@ -178,6 +196,23 @@ pub enum Statement {
MaxOf(ValueRef, ValueRef, ValueRef), MaxOf(ValueRef, ValueRef, ValueRef),
HashOf(ValueRef, ValueRef, ValueRef), HashOf(ValueRef, ValueRef, ValueRef),
PublicKeyOf(ValueRef, ValueRef), PublicKeyOf(ValueRef, ValueRef),
ContainerInsert(
/* new_root */ ValueRef,
/* old_root */ ValueRef,
/* key */ ValueRef,
/* value */ ValueRef,
),
ContainerUpdate(
/* new_root */ ValueRef,
/* old_root */ ValueRef,
/* key */ ValueRef,
/* value */ ValueRef,
),
ContainerDelete(
/* new_root */ ValueRef,
/* old_root */ ValueRef,
/* key */ ValueRef,
),
Custom(CustomPredicateRef, Vec<Value>), Custom(CustomPredicateRef, Vec<Value>),
} }
@ -196,6 +231,16 @@ macro_rules! statement_constructor {
Self::$cons_name(v1.into(), v2.into(), v3.into()) Self::$cons_name(v1.into(), v2.into(), v3.into())
} }
}; };
($var_name: ident, $cons_name: ident, 4) => {
pub fn $var_name(
v1: impl Into<ValueRef>,
v2: impl Into<ValueRef>,
v3: impl Into<ValueRef>,
v4: impl Into<ValueRef>,
) -> Self {
Self::$cons_name(v1.into(), v2.into(), v3.into(), v4.into())
}
};
} }
impl Statement { impl Statement {
@ -213,6 +258,9 @@ impl Statement {
statement_constructor!(max_of, MaxOf, 3); statement_constructor!(max_of, MaxOf, 3);
statement_constructor!(hash_of, HashOf, 3); statement_constructor!(hash_of, HashOf, 3);
statement_constructor!(public_key_of, PublicKeyOf, 2); statement_constructor!(public_key_of, PublicKeyOf, 2);
statement_constructor!(insert, ContainerInsert, 4);
statement_constructor!(update, ContainerUpdate, 4);
statement_constructor!(delete, ContainerDelete, 3);
pub fn predicate(&self) -> Predicate { pub fn predicate(&self) -> Predicate {
use Predicate::*; use Predicate::*;
match self { match self {
@ -228,6 +276,9 @@ impl Statement {
Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf), Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf),
Self::HashOf(_, _, _) => Native(NativePredicate::HashOf), Self::HashOf(_, _, _) => Native(NativePredicate::HashOf),
Self::PublicKeyOf(_, _) => Native(NativePredicate::PublicKeyOf), Self::PublicKeyOf(_, _) => Native(NativePredicate::PublicKeyOf),
Self::ContainerInsert(_, _, _, _) => Native(NativePredicate::ContainerInsert),
Self::ContainerUpdate(_, _, _, _) => Native(NativePredicate::ContainerUpdate),
Self::ContainerDelete(_, _, _) => Native(NativePredicate::ContainerDelete),
Self::Custom(cpr, _) => Custom(cpr.clone()), Self::Custom(cpr, _) => Custom(cpr.clone()),
} }
} }
@ -246,6 +297,13 @@ impl Statement {
Self::MaxOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()], Self::MaxOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::HashOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()], Self::HashOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::PublicKeyOf(ak1, ak2) => vec![ak1.into(), ak2.into()], Self::PublicKeyOf(ak1, ak2) => vec![ak1.into(), ak2.into()],
Self::ContainerInsert(ak1, ak2, ak3, ak4) => {
vec![ak1.into(), ak2.into(), ak3.into(), ak4.into()]
}
Self::ContainerUpdate(ak1, ak2, ak3, ak4) => {
vec![ak1.into(), ak2.into(), ak3.into(), ak4.into()]
}
Self::ContainerDelete(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::Custom(_, args) => Vec::from_iter(args.into_iter().map(Literal)), Self::Custom(_, args) => Vec::from_iter(args.into_iter().map(Literal)),
} }
} }
@ -293,6 +351,21 @@ impl Statement {
(Native(NativePredicate::PublicKeyOf), &[a1, a2]) => { (Native(NativePredicate::PublicKeyOf), &[a1, a2]) => {
Self::PublicKeyOf(a1.try_into()?, a2.try_into()?) Self::PublicKeyOf(a1.try_into()?, a2.try_into()?)
} }
(Native(NativePredicate::ContainerInsert), &[a1, a2, a3, a4]) => Self::ContainerInsert(
a1.try_into()?,
a2.try_into()?,
a3.try_into()?,
a4.try_into()?,
),
(Native(NativePredicate::ContainerUpdate), &[a1, a2, a3, a4]) => Self::ContainerUpdate(
a1.try_into()?,
a2.try_into()?,
a3.try_into()?,
a4.try_into()?,
),
(Native(NativePredicate::ContainerDelete), &[a1, a2, a3]) => {
Self::ContainerDelete(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
(Native(np), _) => { (Native(np), _) => {
return Err(Error::custom(format!("Predicate {:?} is syntax sugar", np))) return Err(Error::custom(format!("Predicate {:?} is syntax sugar", np)))
} }