Skip to content

Commit d327da1

Browse files
feat: Do not make unique revertible note hashes in the private kernels (#10524)
- Changes note_hash => unique => siloed to note_hash => siloed => unique - Adds a new hint in the private kernels of wether the tx is private only or not, verified in tail - Makes unique only nonrevertible note hashes. Uses the previous hint to know wether everything will be nonrevertible or if we need to look at sideffect counters - In the transitional adapters of the AVM simulator, makes unique revertible note hashes from private --------- Co-authored-by: MirandaWood <miranda@aztecprotocol.com>
1 parent 333d6ce commit d327da1

File tree

49 files changed

+6737
-6201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+6737
-6201
lines changed

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

+20-20
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::{
55
};
66

77
use dep::protocol_types::hash::{
8-
compute_siloed_note_hash as compute_siloed_note_hash,
9-
compute_siloed_nullifier as compute_siloed_nullifier_from_preimage, compute_unique_note_hash,
8+
compute_siloed_note_hash, compute_siloed_nullifier as compute_siloed_nullifier_from_preimage,
9+
compute_unique_note_hash,
1010
};
1111

1212
pub fn compute_siloed_nullifier<Note, let N: u32>(
@@ -29,13 +29,19 @@ where
2929
Note: NoteInterface<N> + NullifiableNote,
3030
{
3131
let note_hash = note.compute_note_hash();
32-
let nonce = note.get_header().nonce;
33-
let counter = note.get_header().note_hash_counter;
32+
let header = note.get_header();
33+
let nonce = header.nonce;
34+
let counter = header.note_hash_counter;
3435

36+
// If same tx note, read request always uses the normal note hash
3537
if counter != 0 {
3638
note_hash
3739
} else {
38-
compute_unique_note_hash(nonce, note_hash)
40+
// If the note comes from a different tx, we need to compute the note hash that reached the tree
41+
compute_unique_note_hash(
42+
nonce,
43+
compute_siloed_note_hash(header.contract_address, note_hash),
44+
)
3945
}
4046
}
4147

@@ -49,20 +55,14 @@ where
4955
{
5056
let header = note.get_header();
5157

52-
if header.note_hash_counter != 0 {
53-
if header.nonce == 0 {
54-
// Case 1: Transient note
55-
note_hash_for_read_request
56-
} else {
57-
// Case 2: Non-revertible note, nullified by a revertible nullifier
58-
let unique_note_hash =
59-
compute_unique_note_hash(header.nonce, note_hash_for_read_request);
60-
compute_siloed_note_hash(header.contract_address, unique_note_hash)
61-
}
58+
if (header.note_hash_counter != 0) & (header.nonce != 0) {
59+
// Non-revertible note, nullified by a revertible nullifier, we need to nullify the note hash that will reach the tree
60+
let siloed_note_hash =
61+
compute_siloed_note_hash(header.contract_address, note_hash_for_read_request);
62+
63+
compute_unique_note_hash(header.nonce, siloed_note_hash)
6264
} else {
63-
// Case 3: Note from a previous transaction
64-
// note_hash_for_read_request is already the unique_note_hash in this case
65-
compute_siloed_note_hash(header.contract_address, note_hash_for_read_request)
65+
note_hash_for_read_request
6666
}
6767
}
6868

@@ -129,8 +129,8 @@ where
129129
note.set_header(note_header);
130130

131131
let note_hash = note.compute_note_hash();
132-
let unique_note_hash = compute_unique_note_hash(note_header.nonce, note_hash);
133-
let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, unique_note_hash);
132+
let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, note_hash);
133+
let unique_note_hash = compute_unique_note_hash(note_header.nonce, siloed_note_hash);
134134

135135
let inner_nullifier = if compute_nullifier {
136136
note.compute_nullifier_without_context()

noir-projects/noir-protocol-circuits/crates/private-kernel-init-simulated/src/main.nr

+2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ unconstrained fn main(
1010
protocol_contract_tree_root: Field,
1111
private_call: PrivateCallDataWithoutPublicInputs,
1212
app_public_inputs: PrivateCircuitPublicInputs,
13+
is_private_only: bool,
1314
) -> pub PrivateKernelCircuitPublicInputs {
1415
let private_inputs = PrivateKernelInitCircuitPrivateInputs::new(
1516
tx_request,
1617
vk_tree_root,
1718
protocol_contract_tree_root,
1819
private_call,
1920
app_public_inputs,
21+
is_private_only,
2022
);
2123
private_inputs.execute()
2224
}

noir-projects/noir-protocol-circuits/crates/private-kernel-init/SampleInputs.toml

+179-178
Large diffs are not rendered by default.

noir-projects/noir-protocol-circuits/crates/private-kernel-init/src/main.nr

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ fn main(
99
vk_tree_root: Field,
1010
protocol_contract_tree_root: Field,
1111
private_call: PrivateCallDataWithoutPublicInputs,
12+
is_private_only: bool,
1213
app_public_inputs: call_data(1) PrivateCircuitPublicInputs,
1314
) -> return_data PrivateKernelCircuitPublicInputs {
1415
let private_inputs = PrivateKernelInitCircuitPrivateInputs::new(
@@ -17,6 +18,7 @@ fn main(
1718
protocol_contract_tree_root,
1819
private_call,
1920
app_public_inputs,
21+
is_private_only,
2022
);
2123
private_inputs.execute()
2224
}

noir-projects/noir-protocol-circuits/crates/private-kernel-inner/SampleInputs.toml

+495-494
Large diffs are not rendered by default.

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/previous_kernel_validator.nr

+8
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,19 @@ impl PreviousKernelValidator {
2727
}
2828

2929
pub fn validate_for_private_tail(self) {
30+
assert(
31+
self.previous_kernel.public_inputs.is_private_only,
32+
"Must be private only to be processed in tail",
33+
);
3034
self.validate_common();
3135
self.validate_empty_data();
3236
}
3337

3438
pub fn validate_for_private_tail_to_public(self) {
39+
assert(
40+
!self.previous_kernel.public_inputs.is_private_only,
41+
"Must not be private only to be processed in tail to public",
42+
);
3543
self.validate_common();
3644
self.validate_non_empty_data();
3745
}

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_output_validator.nr

+9
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ impl PrivateKernelCircuitOutputValidator {
3434
private_call_array_lengths: PrivateCircuitPublicInputsArrayLengths,
3535
vk_tree_root: Field,
3636
protocol_contract_tree_root: Field,
37+
is_private_only: bool,
3738
) {
3839
self.validate_initial_values(
3940
tx_request,
4041
private_call,
4142
vk_tree_root,
4243
protocol_contract_tree_root,
44+
is_private_only,
4345
);
4446
let mut offsets = PrivateKernelCircuitPublicInputsArrayLengths::empty();
4547
offsets.nullifiers = 1; // The first nullifier is not propagated from the private call.
@@ -77,8 +79,10 @@ impl PrivateKernelCircuitOutputValidator {
7779
private_call: PrivateCircuitPublicInputs,
7880
vk_tree_root: Field,
7981
protocol_contract_tree_root: Field,
82+
is_private_only: bool,
8083
) {
8184
// Constants.
85+
assert_eq(self.output.is_private_only, is_private_only, "mismatch is_private_only");
8286
assert_eq(self.output.constants.tx_context, tx_request.tx_context, "mismatch tx_context");
8387
assert_eq(
8488
self.output.constants.historical_header,
@@ -193,6 +197,11 @@ impl PrivateKernelCircuitOutputValidator {
193197
previous_kernel: PrivateKernelCircuitPublicInputs,
194198
array_lengths: PrivateKernelCircuitPublicInputsArrayLengths,
195199
) {
200+
assert_eq(
201+
self.output.is_private_only,
202+
previous_kernel.is_private_only,
203+
"mismatch is_private_only",
204+
);
196205
assert_eq(self.output.constants, previous_kernel.constants, "mismatch constants");
197206

198207
assert_eq(

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_kernel_circuit_public_inputs_composer.nr

+3
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ impl PrivateKernelCircuitPublicInputsComposer {
4747
private_call_public_inputs: PrivateCircuitPublicInputs,
4848
vk_tree_root: Field,
4949
protocol_contract_tree_root: Field,
50+
is_private_only: bool,
5051
) -> Self {
5152
let mut public_inputs = PrivateKernelCircuitPublicInputsBuilder::empty();
53+
public_inputs.is_private_only = is_private_only;
5254

5355
public_inputs.constants = TxConstantData {
5456
historical_header: private_call_public_inputs.historical_header,
@@ -71,6 +73,7 @@ impl PrivateKernelCircuitPublicInputsComposer {
7173
) -> Self {
7274
let mut public_inputs = PrivateKernelCircuitPublicInputsBuilder::empty();
7375

76+
public_inputs.is_private_only = previous_kernel_public_inputs.is_private_only;
7477
public_inputs.constants = previous_kernel_public_inputs.constants;
7578
public_inputs.min_revertible_side_effect_counter =
7679
previous_kernel_public_inputs.min_revertible_side_effect_counter;

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_composer.nr

+19-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use dep::types::{
1111
},
1212
address::AztecAddress,
1313
constants::{MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX},
14-
hash::{compute_siloed_private_log_field, silo_note_hash, silo_nullifier},
14+
hash::{
15+
compute_siloed_private_log_field, compute_unique_siloed_note_hash, silo_note_hash,
16+
silo_nullifier,
17+
},
1518
utils::arrays::sort_by_counter_asc,
1619
};
1720

@@ -73,10 +76,23 @@ impl<let NH_RR_PENDING: u32, let NH_RR_SETTLED: u32, let NLL_RR_PENDING: u32, le
7376
unconstrained fn get_sorted_siloed_note_hashes(
7477
self,
7578
) -> [ScopedNoteHash; MAX_NOTE_HASHES_PER_TX] {
76-
let mut note_hashes = sort_by_counter_asc(self.hints.kept_note_hashes);
79+
let is_private_only = self.previous_kernel.is_private_only;
80+
let min_revertible_side_effect_counter =
81+
self.previous_kernel.min_revertible_side_effect_counter;
7782
let first_nullifier = self.previous_kernel.end.nullifiers[0].value();
83+
let mut note_hashes = sort_by_counter_asc(self.hints.kept_note_hashes);
7884
for i in 0..note_hashes.len() {
79-
note_hashes[i].note_hash.value = silo_note_hash(note_hashes[i], first_nullifier, i);
85+
let note_hash = note_hashes[i];
86+
let siloed_note_hash = silo_note_hash(note_hash);
87+
let unique_note_hash =
88+
compute_unique_siloed_note_hash(siloed_note_hash, first_nullifier, i);
89+
// We don't silo with nonce revertible note hashes, since we don't know their final position in the tx
90+
note_hashes[i].note_hash.value = if is_private_only
91+
| (note_hash.counter() < min_revertible_side_effect_counter) {
92+
unique_note_hash
93+
} else {
94+
siloed_note_hash
95+
};
8096
note_hashes[i].contract_address = AztecAddress::zero();
8197
}
8298
note_hashes

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/reset_output_validator.nr

+28-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use dep::types::{
1111
side_effect::scoped::Scoped,
1212
},
1313
constants::PRIVATE_LOG_SIZE_IN_FIELDS,
14-
hash::{compute_siloed_private_log_field, silo_note_hash, silo_nullifier},
14+
hash::{
15+
compute_siloed_private_log_field, compute_unique_siloed_note_hash, silo_note_hash,
16+
silo_nullifier,
17+
},
1518
traits::is_empty,
1619
utils::arrays::assert_sorted_transformed_value_array_capped_size,
1720
};
@@ -77,6 +80,7 @@ impl<let NH_RR_PENDING: u32, let NH_RR_SETTLED: u32, let NLL_RR_PENDING: u32, le
7780
}
7881

7982
fn validate_unchanged_data(self) {
83+
assert_eq(self.output.is_private_only, self.previous_kernel.is_private_only);
8084
assert_eq(self.output.constants, self.previous_kernel.constants);
8185

8286
assert_eq(
@@ -173,7 +177,7 @@ impl<let NH_RR_PENDING: u32, let NH_RR_SETTLED: u32, let NLL_RR_PENDING: u32, le
173177

174178
fn validate_sorted_siloed_note_hashes(self) {
175179
// Check that the values are not already siloed in a previous reset.
176-
// Note hashes need to be siloed alltogether because new note hashes added later might affect the ordering and result in wrong nonces.
180+
// Note hashes need to be siloed all together because new note hashes added later might affect the ordering and result in wrong nonces.
177181
// We only need to check the first item, since we always start siloing from index 0.
178182
// The first item should either be empty or not siloed (contract_address != 0).
179183
let note_hash = self.previous_kernel.end.note_hashes[0];
@@ -184,16 +188,31 @@ impl<let NH_RR_PENDING: u32, let NH_RR_SETTLED: u32, let NLL_RR_PENDING: u32, le
184188

185189
// Check siloing.
186190
let kept_note_hashes = self.hints.kept_note_hashes;
187-
let siloed_note_hashes = self.output.end.note_hashes;
191+
let output_note_hashes = self.output.end.note_hashes;
188192
let sorted_indexes = self.hints.sorted_note_hash_indexes;
189-
let tx_hash = self.output.end.nullifiers[0].value(); // First nullifier is tx hash.
193+
let first_nullifier = self.output.end.nullifiers[0].value();
194+
let is_private_only = self.output.is_private_only;
195+
let min_revertible_side_effect_counter = self.output.min_revertible_side_effect_counter;
196+
190197
for i in 0..kept_note_hashes.len() {
191198
if i < self.note_hash_siloing_amount {
192-
let note_hash = kept_note_hashes[i];
193199
let sorted_index = sorted_indexes[i];
194-
let siloed_note_hash = siloed_note_hashes[sorted_index];
195-
let siloed_value = silo_note_hash(note_hash, tx_hash, sorted_index);
196-
assert_eq(siloed_note_hash.value(), siloed_value, "incorrect siloed note hashes");
200+
let output_note_hash = output_note_hashes[sorted_index];
201+
let note_hash = kept_note_hashes[i];
202+
let siloed_note_hash = silo_note_hash(note_hash);
203+
let siloed_unique_note_hash = compute_unique_siloed_note_hash(
204+
siloed_note_hash,
205+
first_nullifier,
206+
sorted_index,
207+
);
208+
// We don't silo with nonce revertible note hashes, since we don't know their final position in the tx
209+
let expected_value = if is_private_only
210+
| (note_hash.counter() < min_revertible_side_effect_counter) {
211+
siloed_unique_note_hash
212+
} else {
213+
siloed_note_hash
214+
};
215+
assert_eq(output_note_hash.value(), expected_value, "incorrect siloed note hashes");
197216
} else {
198217
// Don't have to check empty items here.
199218
// assert_sorted_transformed_value_array_capped_size ensures that there are the same amount of empty items padded in kept_note_hashes and in self.output.end.note_hashes.
@@ -203,7 +222,7 @@ impl<let NH_RR_PENDING: u32, let NH_RR_SETTLED: u32, let NLL_RR_PENDING: u32, le
203222
// Check ordering.
204223
assert_sorted_transformed_value_array_capped_size(
205224
kept_note_hashes,
206-
siloed_note_hashes,
225+
output_note_hashes,
207226
|prev: ScopedNoteHash, out: ScopedNoteHash| {
208227
out.contract_address.is_zero() & (out.counter() == prev.counter())
209228
},

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_init.nr

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct PrivateKernelInitCircuitPrivateInputs {
1818
vk_tree_root: Field,
1919
protocol_contract_tree_root: Field,
2020
private_call: PrivateCallData,
21+
is_private_only: bool,
2122
}
2223

2324
impl PrivateKernelInitCircuitPrivateInputs {
@@ -27,12 +28,14 @@ impl PrivateKernelInitCircuitPrivateInputs {
2728
protocol_contract_tree_root: Field,
2829
private_call: PrivateCallDataWithoutPublicInputs,
2930
app_public_inputs: PrivateCircuitPublicInputs,
31+
is_private_only: bool,
3032
) -> Self {
3133
Self {
3234
tx_request,
3335
vk_tree_root,
3436
protocol_contract_tree_root,
3537
private_call: private_call.to_private_call_data(app_public_inputs),
38+
is_private_only,
3639
}
3740
}
3841

@@ -43,6 +46,7 @@ impl PrivateKernelInitCircuitPrivateInputs {
4346
private_call_public_inputs,
4447
self.vk_tree_root,
4548
self.protocol_contract_tree_root,
49+
self.is_private_only,
4650
)
4751
.with_private_call(private_call_public_inputs)
4852
.finish()
@@ -73,6 +77,7 @@ impl PrivateKernelInitCircuitPrivateInputs {
7377
private_call_data_validator.array_lengths,
7478
self.vk_tree_root,
7579
self.protocol_contract_tree_root,
80+
self.is_private_only,
7681
);
7782
}
7883
output
@@ -106,6 +111,7 @@ mod tests {
106111
private_call,
107112
vk_tree_root: FixtureBuilder::vk_tree_root(),
108113
protocol_contract_tree_root: 0,
114+
is_private_only: false,
109115
}
110116
.execute()
111117
}

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_reset.nr

+18-5
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ mod tests {
149149
private_log::PrivateLogData, side_effect::scoped::Scoped,
150150
},
151151
address::AztecAddress,
152-
hash::{silo_note_hash, silo_nullifier, silo_private_log},
152+
hash::{compute_unique_siloed_note_hash, silo_note_hash, silo_nullifier, silo_private_log},
153153
point::Point,
154154
tests::{fixture_builder::FixtureBuilder, utils::{assert_array_eq, swap_items}},
155155
traits::is_empty_array,
@@ -245,14 +245,27 @@ mod tests {
245245
}
246246

247247
pub fn compute_output_note_hashes<let N: u32>(
248-
self,
248+
self: Self,
249249
note_hashes: [ScopedNoteHash; N],
250250
) -> [ScopedNoteHash; N] {
251-
// First nullifier is tx hash.
252-
let tx_hash = self.previous_kernel.nullifiers.get_unchecked(0).value();
251+
let first_nullifier = self.previous_kernel.nullifiers.get_unchecked(0).value();
252+
let is_private_only = self.previous_kernel.is_private_only;
253+
let min_revertible_side_effect_counter =
254+
self.previous_kernel.min_revertible_side_effect_counter;
255+
253256
let mut output = note_hashes;
254257
for i in 0..N {
255-
output[i].note_hash.value = silo_note_hash(note_hashes[i], tx_hash, i);
258+
let note_hash = note_hashes[i];
259+
let siloed_note_hash = silo_note_hash(note_hash);
260+
let unique_note_hash =
261+
compute_unique_siloed_note_hash(siloed_note_hash, first_nullifier, i);
262+
// We don't silo with nonce revertible note hashes, since we don't know their final position in the tx
263+
output[i].note_hash.value = if is_private_only
264+
| (note_hash.counter() < min_revertible_side_effect_counter) {
265+
unique_note_hash
266+
} else {
267+
siloed_note_hash
268+
};
256269
output[i].contract_address = AztecAddress::zero();
257270
}
258271
output

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ mod tests {
8484
let mut previous_kernel = FixtureBuilder::new().in_vk_tree(PRIVATE_KERNEL_INNER_INDEX);
8585
previous_kernel.tx_context.gas_settings.gas_limits = Gas::new(1_000_000, 1_000_000);
8686
previous_kernel.set_first_nullifier();
87+
previous_kernel.is_private_only = true;
8788

8889
PrivateKernelTailInputsBuilder { previous_kernel }
8990
}

0 commit comments

Comments
 (0)