Skip to content

Commit 56c31a0

Browse files
committed
add check 5.3, to prevent tampering (at insert-proofs circuit)
1 parent 79a532e commit 56c31a0

File tree

2 files changed

+111
-44
lines changed

2 files changed

+111
-44
lines changed

src/backends/plonky2/primitives/merkletree/circuit.rs

Lines changed: 92 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ pub struct MerkleClaimAndProofTarget {
5050
pub(crate) other_value: ValueTarget,
5151
}
5252

53-
/// Allows to verify both proofs of existence and proofs non-existence with the same circuit. If
54-
/// only proofs of existence are needed, use `verify_merkle_proof_existence_circuit`, which
55-
/// requires less amount of constraints.
53+
/// Allows to verify both proofs of existence and proofs non-existence with the
54+
/// same circuit. If only proofs of existence are needed, use
55+
/// `verify_merkle_proof_existence_circuit`, which requires less amount of
56+
/// constraints.
5657
pub fn verify_merkle_proof_circuit(
5758
builder: &mut CircuitBuilder<F, D>,
5859
proof: &MerkleClaimAndProofTarget,
@@ -203,12 +204,14 @@ pub struct MerkleProofExistenceTarget {
203204
pub(crate) siblings: Vec<HashOutTarget>,
204205
}
205206

206-
/// Allows to verify proofs of existence only. If proofs of non-existence are needed, use
207-
/// `verify_merkle_proof_circuit`.
207+
/// Allows to verify proofs of existence only. If proofs of non-existence are
208+
/// needed, use `verify_merkle_proof_circuit`.
209+
/// It returns the computed path, in case is needed at other parts of the upper
210+
/// logic to avoid recomputing it again.
208211
pub fn verify_merkle_proof_existence_circuit(
209212
builder: &mut CircuitBuilder<F, D>,
210213
proof: &MerkleProofExistenceTarget,
211-
) {
214+
) -> Vec<BoolTarget> {
212215
let max_depth = proof.max_depth;
213216
let measure = measure_gates_begin!(builder, format!("MerkleProofExist_{}", max_depth));
214217

@@ -234,6 +237,8 @@ pub fn verify_merkle_proof_existence_circuit(
234237
builder.connect(computed_root[j], expected_root[j]);
235238
}
236239
measure_gates_end!(builder, measure);
240+
241+
return path;
237242
}
238243

239244
impl MerkleProofExistenceTarget {
@@ -386,10 +391,9 @@ fn kv_hash_target(
386391
builder.hash_n_to_hash_no_pad::<PoseidonHash>(inputs)
387392
}
388393

389-
/// Verifies that the merkletree state
390-
/// transition (from old_root to new_root) has been done correctly for the given
391-
/// new_key. This will allow verifying correct new leaf insertion, and leaf
392-
/// edition&deletion (if needed).
394+
/// Verifies that the merkletree state transition (from old_root to new_root)
395+
/// has been done correctly for the given new_key. This will allow verifying
396+
/// correct new leaf insertion, and leaf edition&deletion (if needed).
393397
pub struct MerkleProofStateTransitionTarget {
394398
pub(crate) max_depth: usize,
395399
// `enabled` determines if the merkleproof state transition verification is enabled
@@ -421,9 +425,10 @@ pub fn verify_merkle_state_transition_circuit(
421425
// for now, only type=0 (insertion proof) is supported
422426
builder.connect(proof.typ, zero);
423427

424-
// check that for the old_root, the new_key does not exist in the tree
428+
// 1) check that for the old_root, the new_key does not exist in the tree
425429
verify_merkle_proof_circuit(builder, &proof.proof_non_existence);
426430

431+
// 2) check that for the new_root, the new_key does exist in the tree
427432
let new_key_proof = MerkleProofExistenceTarget {
428433
max_depth: proof.max_depth,
429434
enabled: proof.enabled,
@@ -432,25 +437,30 @@ pub fn verify_merkle_state_transition_circuit(
432437
value: proof.new_value,
433438
siblings: proof.new_siblings.clone(),
434439
};
435-
verify_merkle_proof_existence_circuit(builder, &new_key_proof);
440+
let new_leaf_path = verify_merkle_proof_existence_circuit(builder, &new_key_proof);
436441

437-
// assert that proof_non_existence.existence==false
442+
// 3.1) assert that proof_non_existence.existence==false
438443
builder.conditional_assert_eq(
439444
proof.enabled.target,
440445
proof.proof_non_existence.existence.target,
441446
zero,
442447
);
448+
// 3.2) assert that proof.enabled matches with proof_non_existance.enabled
449+
builder.connect(
450+
proof.proof_non_existence.enabled.target,
451+
proof.enabled.target,
452+
);
443453

444-
// assert proof_non_existence.root==old_root, and that it uses new_key &
454+
// 4) assert proof_non_existence.root==old_root, and that it uses new_key &
445455
// new_value
446456
for j in 0..HASH_SIZE {
447-
// assert that proof.proof_non_existence.root == proof.old_root
457+
// 4.1) assert that proof.proof_non_existence.root == proof.old_root
448458
builder.conditional_assert_eq(
449459
proof.enabled.target,
450460
proof.proof_non_existence.root.elements[j],
451461
proof.old_root.elements[j],
452462
);
453-
// assert that the non-existence proof uses the new_key & new_value
463+
// 4.2) assert that the non-existence proof uses the new_key & new_value
454464
builder.conditional_assert_eq(
455465
proof.enabled.target,
456466
proof.proof_non_existence.key.elements[j],
@@ -463,26 +473,39 @@ pub fn verify_merkle_state_transition_circuit(
463473
);
464474
}
465475

466-
// sanity check
467-
builder.connect(
468-
proof.proof_non_existence.enabled.target,
469-
proof.enabled.target,
476+
// prepare values for check 5.3).
477+
let other_key_is_empty =
478+
proof
479+
.proof_non_existence
480+
.other_key
481+
.elements
482+
.iter()
483+
.fold(builder._true(), |acc, e| {
484+
let e_is_zero = builder.is_equal(*e, zero);
485+
builder.and(acc, e_is_zero)
486+
});
487+
let old_key_not_empty = builder.not(other_key_is_empty);
488+
let old_leaf_path = keypath_target(
489+
proof.max_depth,
490+
builder,
491+
&proof.proof_non_existence.other_key,
470492
);
471493

472-
// check that old_siblings & new_siblings match as expected.
473-
// Let d=divergence_level, assert that:
474-
// 1) old_siblings[i] == new_siblings[i] ∀ i \ {d}
475-
// 2) at i==d, if old_siblings[i] != new_siblings[i]:
476-
// old_siblings[i] == EMPTY_HASH
477-
// new_siblings[i] == old_leaf_hash
494+
// 5) check that old_siblings & new_siblings match as expected. Let
495+
// d=divergence_level, assert that:
496+
// 5.1) old_siblings[i] == new_siblings[i] ∀ i \ {d}
497+
// 5.2) at i==d, if old_siblings[i] != new_siblings[i]: old_siblings[i] ==
498+
// EMPTY_HASH new_siblings[i] == old_leaf_hash
499+
// 5.3) assert that if old_key!=empty, both old_leaf_path&new_leaf_path
500+
// should diverge at the inputted divergence level
478501
let old_siblings = proof.proof_non_existence.siblings.clone();
479502
let new_siblings = proof.new_siblings.clone();
480503
for i in 0..proof.max_depth {
481504
let i_targ = builder.constant(F::from_canonical_u64(i as u64));
482505
let is_divergence_level = builder.is_equal(i_targ, proof.divergence_level);
483506

484-
// 1) for all i except for i==divergence_level, assert that the siblings
485-
// are the same
507+
// 5.1) for all i except for i==divergence_level, assert that the
508+
// siblings are the same
486509
let old_sibling_i: Vec<Target> = (0..HASH_SIZE)
487510
.map(|j| builder.select(is_divergence_level, zero, old_siblings[i].elements[j]))
488511
.collect();
@@ -493,10 +516,10 @@ pub fn verify_merkle_state_transition_circuit(
493516
builder.conditional_assert_eq(proof.enabled.target, old_sibling_i[j], new_sibling_i[j]);
494517
}
495518

496-
// 2) when i==d && if old_siblings[i] != new_siblings[i], check that:
497-
// old_siblings[i] == EMPTY_HASH && new_siblings[i] == old_leaf_hash
519+
// 5.2) when i==d && if old_siblings[i] != new_siblings[i], check that:
520+
// old_siblings[i] == EMPTY_HASH && new_siblings[i] == old_leaf_hash
498521

499-
// in_case_2=true if: i==d (= is_divergence_level) && old_siblings[i]!=new_siblings[i]
522+
// in_case_5_2=true if: i==d (= is_divergence_level) && old_siblings[i]!=new_siblings[i]
500523
let old_is_eq_new = zip_eq(old_siblings[i].elements, new_siblings[i].elements).fold(
501524
builder._true(),
502525
|acc, (old, new)| {
@@ -505,15 +528,15 @@ pub fn verify_merkle_state_transition_circuit(
505528
},
506529
);
507530
let old_is_noteq_new = builder.not(old_is_eq_new);
508-
let in_case_2 = builder.and(old_is_noteq_new, is_divergence_level);
531+
let in_case_5_2 = builder.and(old_is_noteq_new, is_divergence_level);
509532

510533
// do the case2's checks
511534
let old_leaf_hash = kv_hash_target(
512535
builder,
513536
&proof.proof_non_existence.other_key,
514537
&proof.proof_non_existence.other_value,
515538
);
516-
let sel = builder.and(proof.enabled, in_case_2);
539+
let sel = builder.and(proof.enabled, in_case_5_2);
517540
for j in 0..HASH_SIZE {
518541
builder.conditional_assert_eq(sel.target, old_siblings[i].elements[j], zero);
519542
builder.conditional_assert_eq(
@@ -522,6 +545,18 @@ pub fn verify_merkle_state_transition_circuit(
522545
old_leaf_hash.elements[j],
523546
);
524547
}
548+
549+
// 5.3) assert that if old_key!=empty, both old_leaf_path&new_leaf_path
550+
// should diverge at the inputted divergence level.
551+
let old_key_not_empty_and_divergence_level =
552+
builder.and(old_key_not_empty, is_divergence_level);
553+
let paths_eq_at_d = builder.is_equal(old_leaf_path[i].target, new_leaf_path[i].target);
554+
builder.conditional_assert_eq(
555+
old_key_not_empty_and_divergence_level.target,
556+
// expect them to not be equal, ie. the is_equal check to be 0
557+
paths_eq_at_d.target,
558+
zero,
559+
);
525560
}
526561

527562
measure_gates_end!(builder, measure);
@@ -937,11 +972,18 @@ pub mod tests {
937972
}
938973

939974
fn run_state_transition_circuit(
975+
expect_pass: bool,
940976
max_depth: usize,
941977
state_transition_proof: &MerkleProofStateTransition,
942978
) -> Result<()> {
943979
// sanity check, run the out-circuit proof verification
944-
MerkleTree::verify_state_transition(max_depth, &state_transition_proof)?;
980+
if expect_pass {
981+
MerkleTree::verify_state_transition(max_depth, &state_transition_proof)?;
982+
} else {
983+
// expect out-circuit verification to fail
984+
let _ =
985+
MerkleTree::verify_state_transition(max_depth, &state_transition_proof).is_err();
986+
}
945987

946988
let config = CircuitConfig::standard_recursion_config();
947989
let mut builder = CircuitBuilder::<F, D>::new(config);
@@ -953,8 +995,12 @@ pub mod tests {
953995

954996
// generate & verify proof
955997
let data = builder.build::<C>();
956-
let proof = data.prove(pw)?;
957-
data.verify(proof)?;
998+
if expect_pass {
999+
let proof = data.prove(pw)?;
1000+
data.verify(proof)?;
1001+
} else {
1002+
assert!(data.prove(pw).is_err()); // expect prove to fail
1003+
}
9581004
Ok(())
9591005
}
9601006

@@ -974,7 +1020,7 @@ pub mod tests {
9741020
let key = RawValue::from(37);
9751021
let value = RawValue::from(1037);
9761022
let state_transition_proof = tree.insert(&key, &value)?;
977-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1023+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
9781024
assert_eq!(state_transition_proof.old_root, old_root);
9791025
assert_eq!(state_transition_proof.new_root, tree.root());
9801026

@@ -984,7 +1030,7 @@ pub mod tests {
9841030
let key = RawValue::from(21);
9851031
let value = RawValue::from(1021);
9861032
let state_transition_proof = tree.insert(&key, &value)?;
987-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1033+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
9881034
assert_eq!(state_transition_proof.old_root, old_root);
9891035
assert_eq!(state_transition_proof.new_root, tree.root());
9901036

@@ -993,7 +1039,7 @@ pub mod tests {
9931039
let key = RawValue::from(101);
9941040
let value = RawValue::from(1101);
9951041
let state_transition_proof = tree.insert(&key, &value)?;
996-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1042+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
9971043
assert_eq!(state_transition_proof.old_root, old_root);
9981044
assert_eq!(state_transition_proof.new_root, tree.root());
9991045

@@ -1011,7 +1057,7 @@ pub mod tests {
10111057
let key = RawValue::from(4294967295); // 0xffffffff
10121058
let value = RawValue::from(4294967295);
10131059
let state_transition_proof = tree.insert(&key, &value)?;
1014-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1060+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
10151061
assert_eq!(state_transition_proof.old_root, old_root);
10161062
assert_eq!(state_transition_proof.new_root, tree.root());
10171063

@@ -1022,7 +1068,7 @@ pub mod tests {
10221068
let key = RawValue::from(4026531839); // 0xefffffff
10231069
let value = RawValue::from(4026531839);
10241070
let state_transition_proof = tree.insert(&key, &value)?;
1025-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1071+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
10261072
assert_eq!(state_transition_proof.old_root, old_root);
10271073
assert_eq!(state_transition_proof.new_root, tree.root());
10281074

@@ -1045,7 +1091,7 @@ pub mod tests {
10451091
let key = RawValue::from(37);
10461092
let value = RawValue::from(1037);
10471093
let state_transition_proof = tree.insert(&key, &value)?;
1048-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1094+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
10491095
assert_eq!(state_transition_proof.old_root, old_root);
10501096
assert_eq!(state_transition_proof.new_root, tree.root());
10511097

@@ -1055,7 +1101,7 @@ pub mod tests {
10551101
let key = RawValue::from(21);
10561102
let value = RawValue::from(1021);
10571103
let state_transition_proof = tree.insert(&key, &value)?;
1058-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1104+
run_state_transition_circuit(true, max_depth, &state_transition_proof)?;
10591105
assert_eq!(state_transition_proof.old_root, old_root);
10601106
assert_eq!(state_transition_proof.new_root, tree.root());
10611107

@@ -1064,6 +1110,7 @@ pub mod tests {
10641110
let key = RawValue::from(101);
10651111
let value = RawValue::from(1101);
10661112
let mut state_transition_proof = tree.insert(&key, &value)?;
1113+
10671114
// Tamper with state transition.
10681115
const OFFSET: usize = 20;
10691116
let other_leaf = state_transition_proof
@@ -1087,7 +1134,8 @@ pub mod tests {
10871134
)?;
10881135
state_transition_proof.siblings = altered_proof.siblings;
10891136
state_transition_proof.new_root = altered_root;
1090-
run_state_transition_circuit(max_depth, &state_transition_proof)?;
1137+
1138+
run_state_transition_circuit(false, max_depth, &state_transition_proof)?;
10911139
assert_eq!(state_transition_proof.old_root, old_root);
10921140
assert_ne!(state_transition_proof.new_root, tree.root()); // Tamper check
10931141
Ok(())

src/backends/plonky2/primitives/merkletree/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! https://0xparc.github.io/pod2/merkletree.html .
33
use std::{collections::HashMap, fmt, iter::IntoIterator};
44

5+
use itertools::zip_eq;
56
use plonky2::field::types::Field;
67
use serde::{Deserialize, Serialize};
78

@@ -231,6 +232,24 @@ impl MerkleTree {
231232
&proof.new_value,
232233
)?;
233234

235+
// if other_leaf exists, check path divergence
236+
if proof.proof_non_existence.other_leaf.is_some() {
237+
let (other_key, _) = proof.proof_non_existence.other_leaf.unwrap();
238+
let old_path = keypath(max_depth, other_key)?;
239+
let new_path = keypath(max_depth, proof.new_key)?;
240+
241+
let divergence_lvl: usize = match zip_eq(old_path, new_path).position(|(x, y)| x != y) {
242+
Some(d) => d,
243+
None => return Err(TreeError::max_depth()),
244+
};
245+
246+
if divergence_lvl != new_siblings.len() - 1 {
247+
return Err(TreeError::state_transition_fail(
248+
"paths divergence does not match".to_string(),
249+
));
250+
}
251+
}
252+
234253
// let d=divergence_level, assert that:
235254
// 1) old_siblings[i] == new_siblings[i] ∀ i \ {d}
236255
// 2) at i==d, if old_siblings[i] != new_siblings[i]:

0 commit comments

Comments
 (0)