Skip to content

Commit

Permalink
fix: hash nonce to note hashes created in public (#7715)
Browse files Browse the repository at this point in the history
Fixes #1386

Silo note hashes emitted from public functions with proper nonces in
public_kernel_tail.
  • Loading branch information
LeilaWang authored Aug 2, 2024
1 parent e851b97 commit 6e8eecd
Show file tree
Hide file tree
Showing 25 changed files with 95 additions and 114 deletions.
4 changes: 2 additions & 2 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ library Constants {
uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH = 43;
uint256 internal constant PRIVATE_ACCUMULATED_DATA_LENGTH = 1336;
uint256 internal constant PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2167;
uint256 internal constant PUBLIC_ACCUMULATED_DATA_LENGTH = 1215;
uint256 internal constant PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 3437;
uint256 internal constant PUBLIC_ACCUMULATED_DATA_LENGTH = 1279;
uint256 internal constant PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 3565;
uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 417;
uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 12;
uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 29;
Expand Down
2 changes: 0 additions & 2 deletions noir-projects/aztec-nr/aztec/src/note/note_header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ struct NoteHeader {
contract_address: AztecAddress,
nonce: Field,
storage_slot: Field,
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386)
// Check the nonce to see whether a note is transient or not.
note_hash_counter: u32, // a note_hash_counter of 0 means non-transient
}

Expand Down
25 changes: 7 additions & 18 deletions noir-projects/aztec-nr/aztec/src/note/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,6 @@ pub fn compute_siloed_nullifier<Note, let N: u32, let M: u32>(
compute_siloed_nullifier_from_preimage(header.contract_address, inner_nullifier)
}

fn compute_note_hash_for_read_request_from_slotted_and_nonce(slotted_note_hash: Field, nonce: Field) -> Field {
// TODO(#1386): This if-else can be nuked once we have nonces injected from public
if (nonce == 0) {
// If nonce is zero, that means we are reading a public note.
slotted_note_hash
} else {
compute_unique_note_hash(nonce, slotted_note_hash)
}
}

pub fn compute_note_hash_for_read_request<Note, let N: u32, let M: u32>(note: Note) -> Field where Note: NoteInterface<N, M> {
let slotted_note_hash = compute_slotted_note_hash(note);
let nonce = note.get_header().nonce;
Expand All @@ -57,23 +47,22 @@ pub fn compute_note_hash_for_read_request<Note, let N: u32, let M: u32>(note: No
if counter != 0 {
slotted_note_hash
} else {
compute_note_hash_for_read_request_from_slotted_and_nonce(slotted_note_hash, nonce)
compute_unique_note_hash(nonce, slotted_note_hash)
}
}

pub fn compute_note_hash_for_consumption<Note, let N: u32, let M: u32>(note: Note) -> Field where Note: NoteInterface<N, M> {
let header = note.get_header();
// There are 4 cases for reading a note intended for consumption:
// There are 3 cases for reading a note intended for consumption:
// 1. The note was inserted in this transaction, is revertible, or is not nullified by a revertible nullifier in
// the same transaction: (note_hash_counter != 0) & (nonce == 0)
// 2. The note was inserted in this transaction, is non-revertible, and is nullified by a revertible nullifier in
// the same transaction: (note_hash_counter != 0) & (nonce != 0)
// 3. The note was inserted in a previous transaction, and was inserted in public: (note_hash_counter == 0) & (nonce == 0)
// 4. The note was inserted in a previous transaction, and was inserted in private: (note_hash_counter == 0) & (nonce != 0)
// 3. The note was inserted in a previous transaction: (note_hash_counter == 0) & (nonce != 0)

let slotted_note_hash = compute_slotted_note_hash(note);

if ((header.note_hash_counter != 0) & (header.nonce == 0)) {
if header.nonce == 0 {
// Case 1.
// If a note is transient, we just read the slotted_note_hash (kernel will silo by contract address).
slotted_note_hash
Expand All @@ -86,11 +75,11 @@ pub fn compute_note_hash_for_consumption<Note, let N: u32, let M: u32>(note: Not
// The kernel circuit will check that a nullifier with non-zero note_nonce is linked to a note hash, whose
// siloed note hash matches the note hash specified in the nullifier.

// Case 3 & 4: If a note is not from the current transaction, that means we are reading a settled note (from
// Case 3: If a note is not from the current transaction, that means we are reading a settled note (from
// tree) created in a previous TX. So we need the siloed_note_hash which has already been hashed with
// nonce and then contract address. This hash will match the existing leaf in the note hash
// tree, so the kernel can just perform a membership check directly on this hash/leaf.
let unique_note_hash = compute_note_hash_for_read_request_from_slotted_and_nonce(slotted_note_hash, header.nonce);
let unique_note_hash = compute_unique_note_hash(header.nonce, slotted_note_hash);
compute_siloed_note_hash(header.contract_address, unique_note_hash)
// IMPORTANT NOTE ON REDUNDANT SILOING BY CONTRACT ADDRESS: The note hash computed above is
// "siloed" by contract address. When a note hash is computed solely for the purpose of
Expand All @@ -113,7 +102,7 @@ pub fn compute_note_hash_and_optionally_a_nullifier<T, let N: u32, let M: u32, l
note.set_header(note_header);

let slotted_note_hash = compute_slotted_note_hash(note);
let unique_note_hash = compute_note_hash_for_read_request_from_slotted_and_nonce(slotted_note_hash, note_header.nonce);
let unique_note_hash = compute_unique_note_hash(note_header.nonce, slotted_note_hash);
let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, unique_note_hash);

let inner_nullifier = if compute_nullifier {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ impl NoteInterface<TRANSPARENT_NOTE_LEN, TRANSPARENT_NOTE_BYTES_LEN> for Transpa
}
}

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): Ensure nullifier collisions are prevented
fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) {
self.compute_note_hash_and_nullifier_without_context()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ impl NoteInterface<TRANSPARENT_NOTE_LEN, TRANSPARENT_NOTE_BYTES_LEN> for Transpa
}
}

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): Ensure nullifier collisions are prevented
fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) {
self.compute_note_hash_and_nullifier_without_context()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use dep::types::{
log_hash::{LogHash, ScopedEncryptedLogHash, NoteLogHash, ScopedLogHash}, note_hash::NoteHash,
nullifier::Nullifier, public_call_request::PublicCallRequest
},
address::AztecAddress,
hash::{silo_encrypted_log_hash, silo_l2_to_l1_message, silo_note_hash, silo_nullifier},
traits::{Empty, is_empty, is_empty_array},
utils::arrays::{
Expand Down Expand Up @@ -104,7 +105,7 @@ impl TailToPublicOutputValidator {

assert_split_sorted_transformed_value_arrays_asc(
prev_data.note_hashes,
siloed_note_hashes.map(|value: Field| NoteHash { value, counter: 0 }),
siloed_note_hashes.map(|value: Field| NoteHash { value, counter: 0 }.scope(AztecAddress::zero())),
split_counter,
output_non_revertible.note_hashes,
output_revertible.note_hashes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ mod tests {

// A helper function that uses the first nullifer in the previous kernel to compute the unique siloed
// note_hashes for the given note_hashes.
pub fn compute_output_note_hashes<N>(self, note_hashes: [ScopedNoteHash; N]) -> [NoteHash; N] {
pub fn compute_output_note_hashes<N>(self, note_hashes: [ScopedNoteHash; N]) -> [ScopedNoteHash; N] {
// First nullifier is tx hash.
let tx_hash = self.previous_kernel.nullifiers.get_unchecked(0).value();
let mut output = [NoteHash::empty(); N];
let mut output = [ScopedNoteHash::empty(); N];
for i in 0..N {
output[i] = NoteHash {
value: silo_note_hash(note_hashes[i], tx_hash, i),
counter: 0, // Counter is cleared so it's not exposed to the public.
};
}.scope(AztecAddress::zero());
}
output
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use dep::types::{
abis::{
public_call_stack_item::PublicCallStackItem,
kernel_circuit_public_inputs::PublicKernelCircuitPublicInputsBuilder,
public_kernel_data::PublicKernelData, note_hash::NoteHash, nullifier::Nullifier,
public_kernel_data::PublicKernelData, note_hash::ScopedNoteHash, nullifier::Nullifier,
public_call_data::PublicCallData, public_call_request::PublicCallRequest,
public_data_read::PublicDataRead, public_data_update_request::PublicDataUpdateRequest,
log_hash::{ScopedLogHash, LogHash}, global_variables::GlobalVariables,
Expand All @@ -16,7 +16,7 @@ use dep::types::{
MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL,
MAX_PUBLIC_DATA_READS_PER_CALL, MAX_UNENCRYPTED_LOGS_PER_CALL
},
hash::{compute_siloed_note_hash, compute_siloed_nullifier, compute_l2_to_l1_hash},
hash::{compute_siloed_nullifier, compute_l2_to_l1_hash},
utils::{arrays::{array_length, array_to_bounded_vec}}, traits::{is_empty, is_empty_array}
};

Expand Down Expand Up @@ -397,15 +397,14 @@ fn propagate_note_hashes_non_revertible(
let note_hashes = public_call.call_stack_item.public_inputs.note_hashes;
let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address;

let mut siloed_note_hashes : BoundedVec<NoteHash, MAX_NOTE_HASHES_PER_CALL> = BoundedVec::new();
let mut scoped_note_hashes : BoundedVec<ScopedNoteHash, MAX_NOTE_HASHES_PER_CALL> = BoundedVec::new();
for i in 0..MAX_NOTE_HASHES_PER_CALL {
let new_note_hash = note_hashes[i].value;
if new_note_hash != 0 {
let siloed_new_note_hash = compute_siloed_note_hash(storage_contract_address, new_note_hash);
siloed_note_hashes.push(NoteHash { value: siloed_new_note_hash, counter: note_hashes[i].counter });
let note_hash = note_hashes[i];
if note_hash.value != 0 {
scoped_note_hashes.push(note_hash.scope(storage_contract_address));
}
}
circuit_outputs.end_non_revertible.note_hashes.extend_from_bounded_vec(siloed_note_hashes);
circuit_outputs.end_non_revertible.note_hashes.extend_from_bounded_vec(scoped_note_hashes);
}

fn propagate_note_hashes(public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder) {
Expand All @@ -414,15 +413,14 @@ fn propagate_note_hashes(public_call: PublicCallData, circuit_outputs: &mut Publ
let note_hashes = public_call.call_stack_item.public_inputs.note_hashes;
let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address;

let mut siloed_note_hashes : BoundedVec<NoteHash, MAX_NOTE_HASHES_PER_CALL> = BoundedVec::new();
let mut scoped_note_hashes : BoundedVec<ScopedNoteHash, MAX_NOTE_HASHES_PER_CALL> = BoundedVec::new();
for i in 0..MAX_NOTE_HASHES_PER_CALL {
let new_note_hash = note_hashes[i].value;
if new_note_hash != 0 {
let siloed_new_note_hash = compute_siloed_note_hash(storage_contract_address, new_note_hash);
siloed_note_hashes.push(NoteHash { value: siloed_new_note_hash, counter: note_hashes[i].counter });
let note_hash = note_hashes[i];
if note_hash.value != 0 {
scoped_note_hashes.push(note_hash.scope(storage_contract_address));
}
}
circuit_outputs.end.note_hashes.extend_from_bounded_vec(siloed_note_hashes);
circuit_outputs.end.note_hashes.extend_from_bounded_vec(scoped_note_hashes);
}

fn propagate_nullifiers_non_revertible(public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ mod tests {
read_request::ReadRequest
},
address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId,
hash::{compute_l2_to_l1_hash, compute_siloed_note_hash, compute_siloed_nullifier},
hash::{compute_l2_to_l1_hash, compute_siloed_nullifier},
messaging::l2_to_l1_message::L2ToL1Message,
tests::{
fixture_builder::FixtureBuilder, public_call_data_builder::PublicCallDataBuilder,
Expand Down Expand Up @@ -236,19 +236,17 @@ mod tests {
let contract_address = builder.public_call.contract_address;
// Setup 2 new note hashes and logs on the previous kernel.
builder.previous_kernel.append_note_hashes_with_logs(2);
let previous = builder.previous_kernel.note_hashes.storage.map(|n: ScopedNoteHash| n.note_hash);
let previous = builder.previous_kernel.note_hashes.storage;
let prev_data = builder.previous_kernel.to_public_accumulated_data();
let prev_note_logs = prev_data.note_encrypted_logs_hashes;
// Setup 2 new note hashes on the current public inputs.
let current = [
NoteHash { value: previous[1].value + 1, counter: 5 },
NoteHash { value: previous[1].value + 2, counter: 6 }
NoteHash { value: previous[1].value() + 1, counter: 5 },
NoteHash { value: previous[1].value() + 2, counter: 6 }
];
builder.public_call.public_inputs.note_hashes.extend_from_array(current);
let siloed = current.map(|c: NoteHash| compute_siloed_note_hash(contract_address, c.value));
let note_hashes = [
previous[0], previous[1], NoteHash { value: siloed[0], counter: 5 }, NoteHash { value: siloed[1], counter: 6 }
];
let current = current.map(|n: NoteHash| n.scope(contract_address));
let note_hashes = [previous[0], previous[1], current[0], current[1]];

let public_inputs = builder.execute();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ mod tests {
kernel_circuit_public_inputs::KernelCircuitPublicInputs, public_kernel_data::PublicKernelData,
nullifier::ScopedNullifier, nullifier_leaf_preimage::NullifierLeafPreimage,
accumulated_data::{CombinedAccumulatedData, CombineHints},
public_data_update_request::PublicDataUpdateRequest, note_hash::NoteHash,
public_data_update_request::PublicDataUpdateRequest, note_hash::ScopedNoteHash,
log_hash::{ScopedLogHash, LogHash}
},
address::AztecAddress,
Expand Down Expand Up @@ -319,7 +319,10 @@ mod tests {
previous_kernel.public_inputs.end_non_revertible.note_hashes,
previous_kernel.public_inputs.end.note_hashes
);
let sorted = sort_get_sorted_hints(merged, |a: NoteHash, b: NoteHash| a.counter() < b.counter());
let sorted = sort_get_sorted_hints(
merged,
|a: ScopedNoteHash, b: ScopedNoteHash| a.counter() < b.counter()
);
let sorted_note_hashes = sorted.sorted_array;
let sorted_note_hashes_indexes = sorted.sorted_index_hints;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
hash::{compute_tx_logs_hash, compute_tx_note_logs_hash},
abis::{
accumulated_data::public_accumulated_data::PublicAccumulatedData, note_hash::NoteHash,
accumulated_data::public_accumulated_data::PublicAccumulatedData, note_hash::ScopedNoteHash,
nullifier::Nullifier, public_data_update_request::PublicDataUpdateRequest,
log_hash::{LogHash, NoteLogHash, ScopedLogHash}, gas::Gas, side_effect::{Ordered, Positioned}
},
Expand All @@ -10,12 +10,13 @@ use crate::{
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, COMBINED_ACCUMULATED_DATA_LENGTH,
MAX_UNENCRYPTED_LOGS_PER_TX
},
hash::silo_note_hash,
utils::{arrays::{array_merge, assert_sorted_array, assert_deduped_array, check_permutation}, reader::Reader},
traits::{Empty, Serialize, Deserialize}
};

struct CombineHints {
sorted_note_hashes: [NoteHash; MAX_NOTE_HASHES_PER_TX],
sorted_note_hashes: [ScopedNoteHash; MAX_NOTE_HASHES_PER_TX],
sorted_note_hashes_indexes: [u32; MAX_NOTE_HASHES_PER_TX],
sorted_unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX],
sorted_unencrypted_logs_hashes_indexes: [u32; MAX_UNENCRYPTED_LOGS_PER_TX],
Expand Down Expand Up @@ -65,6 +66,19 @@ impl CombinedAccumulatedData {
asc_sort_by_counters
);

let mut siloed_note_hashes = [0; MAX_NOTE_HASHES_PER_TX];
let sorted_note_hashes = combine_hints.sorted_note_hashes;
let tx_hash = non_revertible.nullifiers[0].value;
for i in 0..sorted_note_hashes.len() {
let note_hash = sorted_note_hashes[i];
siloed_note_hashes[i] = if note_hash.counter() == 0 {
// If counter is zero, the note hash was emitted from private and has been siloed in private_kernel_tail_to_public.
note_hash.value()
} else {
silo_note_hash(note_hash, tx_hash, i)
};
}

let merged_public_data_update_requests = array_merge(
non_revertible.public_data_update_requests,
revertible.public_data_update_requests
Expand Down Expand Up @@ -115,7 +129,7 @@ impl CombinedAccumulatedData {
let unencrypted_log_preimages_length = non_revertible.unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length)
+ revertible.unencrypted_logs_hashes.fold(0, |a, b: ScopedLogHash| a + b.log_hash.length);
CombinedAccumulatedData {
note_hashes: combine_hints.sorted_note_hashes.map(|n: NoteHash| n.value),
note_hashes: siloed_note_hashes,
nullifiers: array_merge(non_revertible.nullifiers, revertible.nullifiers).map(|n: Nullifier| n.value),
l2_to_l1_msgs: array_merge(non_revertible.l2_to_l1_msgs, revertible.l2_to_l1_msgs),
note_encrypted_logs_hash,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
abis::{
public_data_update_request::PublicDataUpdateRequest, gas::Gas, note_hash::NoteHash,
public_data_update_request::PublicDataUpdateRequest, gas::Gas, note_hash::ScopedNoteHash,
nullifier::Nullifier, log_hash::{LogHash, ScopedLogHash, NoteLogHash},
public_call_request::PublicCallRequest
},
Expand All @@ -13,7 +13,7 @@ use crate::{
};

struct PublicAccumulatedData {
note_hashes: [NoteHash; MAX_NOTE_HASHES_PER_TX],
note_hashes: [ScopedNoteHash; MAX_NOTE_HASHES_PER_TX],
nullifiers: [Nullifier; MAX_NULLIFIERS_PER_TX],
l2_to_l1_msgs: [Field; MAX_L2_TO_L1_MSGS_PER_TX],

Expand All @@ -31,7 +31,7 @@ struct PublicAccumulatedData {
impl Empty for PublicAccumulatedData {
fn empty() -> Self {
PublicAccumulatedData {
note_hashes: [NoteHash::empty(); MAX_NOTE_HASHES_PER_TX],
note_hashes: [ScopedNoteHash::empty(); MAX_NOTE_HASHES_PER_TX],
nullifiers: [Nullifier::empty(); MAX_NULLIFIERS_PER_TX],
l2_to_l1_msgs: [0; MAX_L2_TO_L1_MSGS_PER_TX],
note_encrypted_logs_hashes: [LogHash::empty(); MAX_NOTE_ENCRYPTED_LOGS_PER_TX],
Expand Down Expand Up @@ -91,7 +91,7 @@ impl Deserialize<PUBLIC_ACCUMULATED_DATA_LENGTH> for PublicAccumulatedData {
let mut reader = Reader::new(fields);

let item = PublicAccumulatedData {
note_hashes: reader.read_struct_array(NoteHash::deserialize, [NoteHash::empty(); MAX_NOTE_HASHES_PER_TX]),
note_hashes: reader.read_struct_array(ScopedNoteHash::deserialize, [ScopedNoteHash::empty(); MAX_NOTE_HASHES_PER_TX]),
nullifiers: reader.read_struct_array(Nullifier::deserialize, [Nullifier::empty(); MAX_NULLIFIERS_PER_TX]),
l2_to_l1_msgs: reader.read_array([0; MAX_L2_TO_L1_MSGS_PER_TX]),
note_encrypted_logs_hashes: reader.read_struct_array(LogHash::deserialize, [LogHash::empty(); MAX_NOTE_ENCRYPTED_LOGS_PER_TX]),
Expand Down
Loading

0 comments on commit 6e8eecd

Please sign in to comment.