Skip to content

Commit 100a143

Browse files
authored
refactor!: use shared protocol circuit utilities in history module (backport #22493) (#22546)
2 parents 0ea98ea + a216246 commit 100a143

5 files changed

Lines changed: 89 additions & 57 deletions

File tree

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12+
### [aztec-nr] Nullifier membership witness oracle returns split types
13+
14+
`get_nullifier_membership_witness` and `get_low_nullifier_membership_witness` now return `(NullifierLeafPreimage, MembershipWitness<NULLIFIER_TREE_HEIGHT>)` instead of the bundled `NullifierMembershipWitness` struct (which has been removed).
15+
16+
If you were using these oracle functions directly (e.g. in `schnorr_account_contract`'s `lookup_validity`), update your code to destructure the tuple:
17+
18+
```diff
19+
- let witness = get_low_nullifier_membership_witness(block_header, siloed_nullifier);
20+
- let nullifier_value = witness.leaf_preimage.nullifier;
21+
- let index = witness.index;
22+
- let path = witness.path;
23+
+ let (leaf_preimage, witness) = get_low_nullifier_membership_witness(block_header, siloed_nullifier);
24+
+ let nullifier_value = leaf_preimage.nullifier;
25+
+ let index = witness.leaf_index;
26+
+ let path = witness.sibling_path;
27+
```
28+
29+
Note the field renames: `index` is now `leaf_index`, and `path` is now `sibling_path` (matching the protocol circuit's `MembershipWitness` type).
30+
31+
This has been done because this is the format expected by the functionality in protocol circuits and given that this is sensitive security-wise it made sense to reuse that functionality in Aztec.nr.
1232
### [Aztec.js] `GasSettings.default()` renamed to `GasSettings.fallback()`
1333

1434
`GasSettings.default()` has been renamed to `GasSettings.fallback()` to clarify that these gas limits are not protocol defaults — the protocol has no concept of "default" gas settings. `fallback()` is a convenience for cases where gas estimation is not being used, but callers should prefer estimating gas via simulation for accurate limits.

noir-projects/aztec-nr/aztec/src/history/note.nr

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::protocol::{
44
abis::block_header::BlockHeader,
55
hash::{compute_siloed_note_hash, compute_siloed_nullifier, compute_unique_note_hash},
6-
merkle_tree::root::root_from_sibling_path,
6+
merkle_tree::membership::check_membership,
77
};
88

99
use crate::{
@@ -34,9 +34,12 @@ where
3434

3535
// Note inclusion is fairly straightforward, since all we need to prove is that a note exists in the note tree - we
3636
// don't even care _where_ in the tree it is stored. This is because entries in the note hash tree are unique.
37-
assert_eq(
38-
block_header.state.partial.note_hash_tree.root,
39-
root_from_sibling_path(unique_note_hash, witness.leaf_index, witness.sibling_path),
37+
assert(
38+
check_membership(
39+
unique_note_hash,
40+
witness,
41+
block_header.state.partial.note_hash_tree.root,
42+
),
4043
"Proving note inclusion failed",
4144
);
4245

noir-projects/aztec-nr/aztec/src/history/nullifier.nr

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use crate::oracle::get_nullifier_membership_witness::{
44
get_low_nullifier_membership_witness, get_nullifier_membership_witness,
55
};
66

7-
use crate::protocol::{abis::block_header::BlockHeader, merkle_tree::root::root_from_sibling_path, traits::Hash};
7+
use crate::protocol::{
8+
abis::block_header::BlockHeader,
9+
merkle_tree::membership::{check_membership, check_non_membership},
10+
traits::Hash,
11+
};
812

913
mod test;
1014

@@ -28,22 +32,24 @@ mod test;
2832
/// is typically cheaper. Note that there are semantic differences though, as that function also considers _pending_
2933
/// nullifiers.
3034
pub fn assert_nullifier_existed_by(block_header: BlockHeader, siloed_nullifier: Field) {
31-
// 1) Get the membership witness of the nullifier.
32-
// Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe.
33-
let witness = unsafe { get_nullifier_membership_witness(block_header, siloed_nullifier) };
35+
// 1) Get the leaf preimage and membership witness of the nullifier.
36+
// Safety: These are only used as "magical values" that make the proof below pass. Hence it's safe.
37+
let (leaf_preimage, witness) = unsafe { get_nullifier_membership_witness(block_header, siloed_nullifier) };
3438

35-
// 2) First we prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be the
36-
// leaf that contains the nullifier we're proving inclusion for.
37-
assert_eq(
38-
block_header.state.partial.nullifier_tree.root,
39-
root_from_sibling_path(witness.leaf_preimage.hash(), witness.index, witness.path),
39+
// 2) Prove that the leaf is present in the nullifier tree.
40+
assert(
41+
check_membership(
42+
leaf_preimage.hash(),
43+
witness,
44+
block_header.state.partial.nullifier_tree.root,
45+
),
4046
"Proving nullifier inclusion failed",
4147
);
4248

43-
// 3) Then we simply check that the value in the leaf is the expected one. Note that we don't need to perform any
44-
// checks on the rest of the values in the leaf preimage (the next index or next nullifier), since all we care
45-
// about is showing that the tree contains an entry with the expected nullifier.
46-
assert_eq(witness.leaf_preimage.nullifier, siloed_nullifier, "Nullifier does not match value in witness");
49+
// 3) Check that the value in the leaf is the expected one. We don't need to check the rest of the leaf preimage
50+
// (next index or next nullifier), since all we care about is that the tree contains an entry with the expected
51+
// nullifier.
52+
assert_eq(leaf_preimage.nullifier, siloed_nullifier, "Nullifier does not match value in witness");
4753
}
4854

4955
/// Asserts that a nullifier did not exist by the time a block was mined.
@@ -79,36 +85,26 @@ pub fn assert_nullifier_existed_by(block_header: BlockHeader, siloed_nullifier:
7985
///
8086
/// This function performs a single merkle tree inclusion proof, which is ~4k gates.
8187
pub fn assert_nullifier_did_not_exist_by(block_header: BlockHeader, siloed_nullifier: Field) {
82-
// 1) Get the membership witness of a low nullifier of the nullifier.
83-
// Safety: The witness is only used as a "magical value" that makes the proof below pass. Hence it's safe.
84-
let witness = unsafe { get_low_nullifier_membership_witness(block_header, siloed_nullifier) };
88+
// 1) Get the leaf preimage and membership witness of the low nullifier.
89+
// Safety: These are only used as "magical values" that make the proof below pass. Hence it's safe.
90+
let (low_leaf_preimage, witness) = unsafe { get_low_nullifier_membership_witness(block_header, siloed_nullifier) };
8591

8692
// 2) Check that the leaf preimage is not empty. An empty leaf preimage would pass validation as a low leaf.
8793
// However, it's not a valid low leaf. It's used to pad the nullifiers emitted from a tx so they can be inserted
8894
// into the tree in a fixed-size batch.
89-
assert(!witness.leaf_preimage.is_empty(), "The provided nullifier tree leaf preimage cannot be empty");
95+
assert(!low_leaf_preimage.is_empty(), "The provided nullifier tree leaf preimage cannot be empty");
9096

91-
// 3) Prove that the tree leaf in the witness is present in the nullifier tree. This is expected to be the 'low
92-
// leaf', i.e. the leaf that would come immediately before the nullifier's leaf, if the nullifier were to be in the
93-
// tree.
94-
let low_nullifier_leaf = witness.leaf_preimage;
95-
assert_eq(
97+
// 3) Prove non-inclusion: the low leaf must exist in the tree and the nullifier must be in range of the low leaf
98+
// (i.e. greater than the low leaf's nullifier and less than the low leaf's next nullifier, or the low leaf points
99+
// to infinity meaning it is the largest entry). This guarantees the nullifier is not in the tree because, if it
100+
// were, it would need to be between the low leaf and the next leaf.
101+
let (non_inclusion, is_valid_low_leaf, low_leaf_exists) = check_non_membership(
102+
siloed_nullifier,
103+
low_leaf_preimage,
104+
witness,
96105
block_header.state.partial.nullifier_tree.root,
97-
root_from_sibling_path(low_nullifier_leaf.hash(), witness.index, witness.path),
98-
"Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion",
99-
);
100-
101-
// 4) Prove that the low leaf is indeed smaller than the nullifier
102-
assert(
103-
low_nullifier_leaf.nullifier.lt(siloed_nullifier),
104-
"Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed",
105-
);
106-
107-
// 5) Prove that the low leaf is pointing "over" the nullifier, which means that the nullifier is not included in
108-
// the nullifier tree, since if it were it'd need to be between the low leaf and the next leaf. Note the special
109-
// case in which the low leaf is the largest of all entries, in which case there's no 'next' entry.
110-
assert(
111-
siloed_nullifier.lt(low_nullifier_leaf.next_nullifier) | (low_nullifier_leaf.next_index == 0),
112-
"Proving nullifier non-inclusion failed: low_nullifier.next_value > nullifier.value check failed",
113106
);
107+
assert(low_leaf_exists, "Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion");
108+
assert(is_valid_low_leaf, "Proving nullifier non-inclusion failed: invalid low leaf for the given nullifier");
109+
assert(non_inclusion, "Proving nullifier non-inclusion failed");
114110
}
Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,62 @@
11
use crate::protocol::{
22
abis::{block_header::BlockHeader, nullifier_leaf_preimage::NullifierLeafPreimage},
33
constants::NULLIFIER_TREE_HEIGHT,
4+
merkle_tree::MembershipWitness,
45
traits::{Deserialize, Hash, Serialize},
56
};
67

8+
// This internal type matches the oracle response. This module's functions (e.g.
9+
// [`get_low_nullifier_membership_witness`]) wrap the oracle call and convert this into their respective
10+
// return types.
11+
// TODO(F-554): Remove once the TypeScript side serializes preimage and witness separately, as an
12+
// oracle interface breaking change.
713
#[derive(Deserialize, Eq, Serialize)]
8-
pub struct NullifierMembershipWitness {
9-
pub index: Field,
10-
pub leaf_preimage: NullifierLeafPreimage,
11-
pub path: [Field; NULLIFIER_TREE_HEIGHT],
14+
struct RawNullifierMembershipWitness {
15+
index: Field,
16+
leaf_preimage: NullifierLeafPreimage,
17+
path: [Field; NULLIFIER_TREE_HEIGHT],
18+
}
19+
20+
impl RawNullifierMembershipWitness {
21+
fn into_split(self) -> (NullifierLeafPreimage, MembershipWitness<NULLIFIER_TREE_HEIGHT>) {
22+
(self.leaf_preimage, MembershipWitness { leaf_index: self.index, sibling_path: self.path })
23+
}
1224
}
1325

1426
#[oracle(aztec_utl_getLowNullifierMembershipWitness)]
1527
unconstrained fn get_low_nullifier_membership_witness_oracle(
1628
_block_hash: Field,
1729
_nullifier: Field,
18-
) -> NullifierMembershipWitness {}
30+
) -> RawNullifierMembershipWitness {}
1931

20-
/// Returns a membership witness for the low nullifier of `nullifier` in the nullifier tree whose root is defined in
21-
/// `block_header`.
32+
/// Returns a leaf preimage and membership witness for the low nullifier of `nullifier` in the nullifier tree whose
33+
/// root is defined in `block_header`.
2234
///
2335
/// The low nullifier is the leaf with the largest value that is still smaller than `nullifier`. This is used to prove
2436
/// non-inclusion: if the low nullifier's `next_value` is greater than `nullifier`, then `nullifier` is not in the
2537
/// tree.
2638
pub unconstrained fn get_low_nullifier_membership_witness(
2739
block_header: BlockHeader,
2840
nullifier: Field,
29-
) -> NullifierMembershipWitness {
41+
) -> (NullifierLeafPreimage, MembershipWitness<NULLIFIER_TREE_HEIGHT>) {
3042
let block_hash = block_header.hash();
31-
get_low_nullifier_membership_witness_oracle(block_hash, nullifier)
43+
get_low_nullifier_membership_witness_oracle(block_hash, nullifier).into_split()
3244
}
3345

3446
#[oracle(aztec_utl_getNullifierMembershipWitness)]
3547
unconstrained fn get_nullifier_membership_witness_oracle(
3648
_block_hash: Field,
3749
_nullifier: Field,
38-
) -> NullifierMembershipWitness {}
50+
) -> RawNullifierMembershipWitness {}
3951

40-
/// Returns a membership witness for `nullifier` in the nullifier tree whose root is defined in `block_header`.
52+
/// Returns a leaf preimage and membership witness for `nullifier` in the nullifier tree whose root is defined in
53+
/// `block_header`.
4154
///
4255
/// This is used to prove that a nullifier exists in the tree (inclusion proof).
4356
pub unconstrained fn get_nullifier_membership_witness(
4457
block_header: BlockHeader,
4558
nullifier: Field,
46-
) -> NullifierMembershipWitness {
59+
) -> (NullifierLeafPreimage, MembershipWitness<NULLIFIER_TREE_HEIGHT>) {
4760
let block_hash = block_header.hash();
48-
get_nullifier_membership_witness_oracle(block_hash, nullifier)
61+
get_nullifier_membership_witness_oracle(block_hash, nullifier).into_split()
4962
}

noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ pub contract SchnorrAccount {
143143
// it is not as part of execution of the contract, so we are good.
144144
let nullifier = compute_authwit_nullifier(self.address, inner_hash);
145145
let siloed_nullifier = compute_siloed_nullifier(consumer, nullifier);
146-
let lower_wit =
146+
let (low_leaf_preimage, _witness) =
147147
get_low_nullifier_membership_witness(self.context.block_header(), siloed_nullifier);
148-
let is_spent = lower_wit.leaf_preimage.nullifier == siloed_nullifier;
148+
let is_spent = low_leaf_preimage.nullifier == siloed_nullifier;
149149

150150
!is_spent & valid_in_private
151151
}

0 commit comments

Comments
 (0)