Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: slot part of note hiding point preimage #7767

Merged
merged 6 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,24 @@ sequenceDiagram
BalanceSet->>Set: insert(note)
Set->>LifeCycle: create_note(derived_slot, note)
LifeCycle->>LifeCycle: note.header = NoteHeader { contract_address, <br> storage_slot: derived_slot, nonce: 0, note_hash_counter }
LifeCycle->>Utils: compute_slotted_note_hash(note)
Utils->>TokenNote: note.compute_note_hiding_point()
TokenNote->>Utils: note_hiding_point = MSM([G_amt, G_to, G_rand], [amount, to, randomness])
Utils->>NoteHash: compute_slotted_note_hash(derived_slot, note_hiding_point)
NoteHash->>LifeCycle: slotted_note_hash = CURVE_ADD(derived_slot_point, note_hiding_point).x
LifeCycle->>Context: push_note_hash(slotted_note_hash)
Utils->>TokenNote: note_hiding_point = note.compute_note_hiding_point()
TokenNote->>Utils: note_hash = note_hiding_point.x
LifeCycle->>Context: push_note_hash(note_hash)
end
Context->>Kernel: siloed_note_hash = H(contract_address, slotted_note_hash)
Context->>Kernel: unique_note_hash = H(nonce, note_hash)
Context->>Kernel: siloed_note_hash = H(contract_address, unique_note_hash)
```

Notice the `siloed_note_hash` at the very end. It's a hash that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree.

```rust
siloed_note_hash = H(contract_address, slotted_note_hash)
siloed_note_hash = H(contract_address, CURVE_ADD(derived_slot_point, note_hiding_point).x)
siloed_note_hash = H(contract_address, CURVE_ADD(MSM([G_slot], [derived_slot]), note_hiding_point).x)
siloed_note_hash = H(contract_address, CURVE_ADD(MSM([G_slot], [derived_slot]), MSM([G_amt, G_to, G_rand], [amount, to, randomness])).x)
siloed_note_hash = H(contract_address, unique_note_hash)
siloed_note_hash = H(contract_address, H(nonce, note_hash))
siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), note_hash))
siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), MSM([G_amt, G_to, G_rand, G_slot], [amount, to, randomness, derived_slot]).x))
```

CURVE_ADD is a point addition and MSM is a multi scalar multiplication on a grumpkin curve and G_* values are generators.
MSM is a multi scalar multiplication on a grumpkin curve and G_* values are generators.

And `to` is the actor who receives the note, `amount` of the note and `randomness` is the randomness used to make the note hiding. Without the `randomness` the note could just as well be plaintext (computational cost of a preimage attack would be trivial in such a case).

Expand Down
16 changes: 9 additions & 7 deletions noir-projects/aztec-nr/aztec/src/note/lifecycle.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::context::{PrivateContext, PublicContext};
use crate::note::{
note_header::NoteHeader, note_interface::NoteInterface,
utils::{compute_slotted_note_hash, compute_note_hash_for_consumption}, note_emission::NoteEmission
note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption,
note_emission::NoteEmission
};
use crate::oracle::notes::{notify_created_note, notify_nullified_note};

Expand All @@ -15,21 +15,22 @@ pub fn create_note<Note, N, M>(

let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter };
note.set_header(header);
let slotted_note_hash = compute_slotted_note_hash(*note);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;

let serialized_note = Note::serialize_content(*note);
assert(
notify_created_note(
storage_slot,
Note::get_note_type_id(),
serialized_note,
slotted_note_hash,
note_hash,
note_hash_counter
)
== 0
);

context.push_note_hash(slotted_note_hash);
context.push_note_hash(note_hash);

NoteEmission::new(*note)
}
Expand All @@ -43,9 +44,10 @@ pub fn create_note_hash_from_public<Note, N, M>(
// Public note hashes are transient, but have no side effect counters, so we just need note_hash_counter != 0
let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter: 1 };
note.set_header(header);
let slotted_note_hash = compute_slotted_note_hash(*note);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;

context.push_note_hash(slotted_note_hash);
context.push_note_hash(note_hash);
}

pub fn destroy_note<Note, N, M>(
Expand Down
39 changes: 13 additions & 26 deletions noir-projects/aztec-nr/aztec/src/note/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,6 @@ use dep::protocol_types::{
};
use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe};

pub fn compute_slotted_note_hash<Note, let N: u32, let M: u32>(note: Note) -> Field where Note: NoteInterface<N, M> {
let storage_slot = note.get_header().storage_slot;
let note_hiding_point = note.compute_note_hiding_point();

// 1. We derive the storage slot point by multiplying the storage slot with the generator G_slot.
// We use the unsafe version because the multi_scalar_mul will constrain the scalars.
let storage_slot_scalar = from_field_unsafe(storage_slot);
let storage_slot_point = multi_scalar_mul([G_slot], [storage_slot_scalar]);

// 2. Then we compute the slotted note hiding point by adding the storage slot point to the note hiding point.
let slotted_note_hiding_point = storage_slot_point + note_hiding_point;

// 3. Finally, we return the slotted note hash which is the x-coordinate of the slotted note hiding point.
slotted_note_hiding_point.x
}

pub fn compute_siloed_nullifier<Note, let N: u32, let M: u32>(
note_with_header: Note,
context: &mut PrivateContext
Expand All @@ -39,14 +23,15 @@ pub fn compute_siloed_nullifier<Note, let N: u32, let M: u32>(
}

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);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;
let nonce = note.get_header().nonce;
let counter = note.get_header().note_hash_counter;

if counter != 0 {
slotted_note_hash
note_hash
} else {
compute_unique_note_hash(nonce, slotted_note_hash)
compute_unique_note_hash(nonce, note_hash)
}
}

Expand All @@ -59,12 +44,13 @@ pub fn compute_note_hash_for_consumption<Note, let N: u32, let M: u32>(note: Not
// the same transaction: (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);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;

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
// If a note is transient, we just read the note_hash (kernel will hash it with nonce and silo by contract address).
note_hash
} else {
// Case 2: If a note is non-revertible, and is nullified by a revertible nullifier, we cannot squash them in the
// private reset circuit. Because if the tx reverts, we will have to keep the note hash and throw away the
Expand All @@ -78,7 +64,7 @@ pub fn compute_note_hash_for_consumption<Note, let N: u32, let M: u32>(note: Not
// 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_unique_note_hash(header.nonce, slotted_note_hash);
let unique_note_hash = compute_unique_note_hash(header.nonce, 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 @@ -100,8 +86,9 @@ pub fn compute_note_hash_and_optionally_a_nullifier<T, let N: u32, let M: u32, l
let mut note = deserialize_content(arr_copy_slice(serialized_note, [0; N], 0));
note.set_header(note_header);

let slotted_note_hash = compute_slotted_note_hash(note);
let unique_note_hash = compute_unique_note_hash(note_header.nonce, slotted_note_hash);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;
let unique_note_hash = compute_unique_note_hash(note_header.nonce, note_hash);
let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, unique_note_hash);

let inner_nullifier = if compute_nullifier {
Expand All @@ -111,6 +98,6 @@ pub fn compute_note_hash_and_optionally_a_nullifier<T, let N: u32, let M: u32, l
0
};
// docs:start:compute_note_hash_and_optionally_a_nullifier_returns
[slotted_note_hash, unique_note_hash, siloed_note_hash, inner_nullifier]
[note_hash, unique_note_hash, siloed_note_hash, inner_nullifier]
// docs:end:compute_note_hash_and_optionally_a_nullifier_returns
}
18 changes: 6 additions & 12 deletions noir-projects/aztec-nr/aztec/src/oracle/notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,29 @@ unconstrained fn notify_created_note_oracle<let N: u32>(
_storage_slot: Field,
_note_type_id: Field,
_serialized_note: [Field; N],
_slotted_note_hash: Field,
_note_hash: Field,
_counter: u32
) -> Field {}

unconstrained pub fn notify_created_note<let N: u32>(
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; N],
slotted_note_hash: Field,
note_hash: Field,
counter: u32
) -> Field {
notify_created_note_oracle(
storage_slot,
note_type_id,
serialized_note,
slotted_note_hash,
counter
)
notify_created_note_oracle(storage_slot, note_type_id, serialized_note, note_hash, counter)
}

#[oracle(notifyNullifiedNote)]
unconstrained fn notify_nullified_note_oracle<let N: u32>(_nullifier: Field, _slotted_note_hash: Field, _counter: u32) -> Field {}
unconstrained fn notify_nullified_note_oracle<let N: u32>(_nullifier: Field, _note_hash: Field, _counter: u32) -> Field {}

unconstrained pub fn notify_nullified_note<let N: u32>(
nullifier: Field,
slotted_note_hash: Field,
note_hash: Field,
counter: u32
) -> Field {
notify_nullified_note_oracle(nullifier, slotted_note_hash, counter)
notify_nullified_note_oracle(nullifier, note_hash, counter)
}

#[oracle(getNotes)]
Expand Down
6 changes: 3 additions & 3 deletions noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ unconstrained pub fn add_nullifiers(contractAddress: AztecAddress, nullifiers: [
oracle_add_nullifiers(contractAddress, nullifiers)
}

unconstrained pub fn add_note_hashes(contractAddress: AztecAddress, slotted_note_hashes: [Field]) {
oracle_add_note_hashes(contractAddress, slotted_note_hashes)
unconstrained pub fn add_note_hashes(contractAddress: AztecAddress, note_hashes: [Field]) {
oracle_add_note_hashes(contractAddress, note_hashes)
}

unconstrained pub fn get_function_selector() -> FunctionSelector {
Expand Down Expand Up @@ -180,7 +180,7 @@ unconstrained fn oracle_assert_private_call_fails(
unconstrained fn oracle_add_nullifiers(contractAddress: AztecAddress, nullifiers: [Field]) {}

#[oracle(addNoteHashes)]
unconstrained fn oracle_add_note_hashes(contractAddress: AztecAddress, slotted_note_hashes: [Field]) {}
unconstrained fn oracle_add_note_hashes(contractAddress: AztecAddress, note_hashes: [Field]) {}

#[oracle(getFunctionSelector)]
unconstrained fn oracle_get_function_selector() -> FunctionSelector {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ use crate::test::helpers::{cheatcodes, utils::{apply_side_effects_private, Deplo
use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX};
use crate::hash::{hash_args, hash_args_array};

use crate::note::{
note_header::NoteHeader, note_interface::NoteInterface,
utils::{compute_slotted_note_hash, compute_note_hash_for_consumption}
};
use crate::note::{note_header::NoteHeader, note_interface::NoteInterface};
use crate::oracle::{execution::{get_block_number, get_contract_address}, notes::notify_created_note};

struct TestEnvironment {}
Expand Down Expand Up @@ -219,14 +216,15 @@ impl TestEnvironment {

let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter };
note.set_header(header);
let slotted_note_hash = compute_slotted_note_hash(*note);
// TODO(#7771): inject compute_note_hash(...) func to notes with macros.
let note_hash = note.compute_note_hiding_point().x;
let serialized_note = Note::serialize_content(*note);
assert(
notify_created_note(
storage_slot,
Note::get_note_type_id(),
serialized_note,
slotted_note_hash,
note_hash,
note_hash_counter
)
== 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ contract PendingNoteHashes {

// Nested/inner function to create and insert a note
// TESTING: inserts a static randomness value to test notes with
// the same slotted note hash are dealt with correctly
// the same note hash are dealt with correctly
#[aztec(private)]
fn insert_note_static_randomness(
amount: Field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use dep::aztec::{

// TODO(#7738): Nuke the following imports
use dep::aztec::{
generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd},
generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot},
protocol_types::{point::Point, scalar::Scalar}
};
use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe};
Expand Down Expand Up @@ -60,16 +60,20 @@ impl NoteInterface<TOKEN_NOTE_LEN, TOKEN_NOTE_BYTES_LEN> for TokenNote {

// TODO(#7738): Nuke this function and have it auto-generated by macros
fn compute_note_hiding_point(self) -> Point {
assert(self.header.storage_slot != 0, "Storage slot must be set before computing note hiding point");
Copy link
Contributor Author

@benesjan benesjan Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I placed the assert here to sanity check that everywhere in the codebase the slot is assigned before we try to compute note hiding point. In plenty of the state vars we have the slot check inserted as well.

I think it would make sense to keep the slot check in all the notes and nuke it from the state vars. Does the reviewer agree?

@LHerskind I remember you once explained to me why the 0 slot was not working. I think it had something to do with pedersen but I can't exactly remember now. Do you still remember what was the cause? Maybe we can nuke the checks altogether now that pedersen is not used to compute note hashes.


// TODO(#7772): decompose amount with from_field_unsafe or constrain it fits into 1 limb
let amount_scalar = Scalar {
lo: self.amount.to_integer(),
hi: 0
};
// We use the unsafe version because the multi_scalar_mul will constrain the scalars.
let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash);
let randomness_scalar = from_field_unsafe(self.randomness);
let slot_scalar = from_field_unsafe(self.header.storage_slot);
multi_scalar_mul(
[G_amt, G_npk, G_rnd],
[Scalar {
lo: self.amount.to_integer(),
hi: 0
},
npk_m_hash_scalar,
randomness_scalar]
[G_amt, G_npk, G_rnd, G_slot],
[amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar]
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,35 +469,41 @@ contract TokenWithRefunds {
// to the user in the `complete_refund(...)` function.
storage.balances.sub(user, U128::from_integer(funded_amount)).emit(encode_and_encrypt_note_with_keys(&mut context, user_ovpk, user_ivpk, user));

// 4. We create the partial notes for the fee payer and the user.
// 4. Now we "manually" compute the slots (by setting the slots we insert the notes to the balances map under
Copy link
Contributor Author

@benesjan benesjan Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think computing the slot here manually is pretty ugly. If we decide to make partial notes a first class citizen I think it would make sense to modify the state vars such that they return the note hiding point instead of immediately emitting the note hashes. Or maybe just having separate for 2 flows on the state vars (one returning note hiding point, another directly emitting).

What does the reviewer think?

// the correct keys)
let fee_payer_balances_slot = derive_storage_slot_in_map(TokenWithRefunds::storage().balances.slot, fee_payer);
let user_balances_slot = derive_storage_slot_in_map(TokenWithRefunds::storage().balances.slot, user);

// 5. We create the partial notes for the fee payer and the user.
// --> Called "partial" because they don't have the amount set yet (that will be done in `complete_refund(...)`).
let fee_payer_partial_note = TokenNote {
header: NoteHeader::empty(),
header: NoteHeader {
contract_address: AztecAddress::zero(),
Copy link
Contributor Author

@benesjan benesjan Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just setting the address to zero since it's unused.

@nventuro would make the decision whether to nuke it as it's looking pretty weird here.

nonce: 0,
storage_slot: fee_payer_balances_slot,
note_hash_counter: 0
},
amount: U128::zero(),
npk_m_hash: fee_payer_npk_m_hash,
randomness: fee_payer_randomness
};
let user_partial_note = TokenNote {
header: NoteHeader::empty(),
header: NoteHeader {
contract_address: AztecAddress::zero(),
nonce: 0,
storage_slot: user_balances_slot,
note_hash_counter: 0
},
amount: U128::zero(),
npk_m_hash: user_npk_m_hash,
randomness: user_randomness
};

// 5. Now we get the note hiding points.
// 6. Now we get the note hiding points.
let mut fee_payer_point = fee_payer_partial_note.to_note_hiding_point();
let mut user_point = user_partial_note.to_note_hiding_point();

// 6. Now we "manually" compute the slot points and add them to hiding points.
let fee_payer_balances_slot = derive_storage_slot_in_map(TokenWithRefunds::storage().balances.slot, fee_payer);
let user_balances_slot = derive_storage_slot_in_map(TokenWithRefunds::storage().balances.slot, user);

// 7. We add the slot to the points --> this way we insert the notes into the balances Map under the respective key.
// TODO(#7753): Consider making slots part of the initital note hiding point creation.
fee_payer_point.add_slot(fee_payer_balances_slot);
user_point.add_slot(user_balances_slot);

// 8. Set the public teardown function to `complete_refund(...)`. Public teardown is the only time when a public
// 7. Set the public teardown function to `complete_refund(...)`. Public teardown is the only time when a public
// function has access to the final transaction fee, which is needed to compute the actual refund amount.
context.set_public_teardown_function(
context.this_address(),
Expand All @@ -513,14 +519,14 @@ contract TokenWithRefunds {
#[aztec(public)]
#[aztec(internal)]
fn complete_refund(
// TODO: the following makes macros crash --> try getting it work once we migrate to metaprogramming
// TODO(#7771): the following makes macros crash --> try getting it work once we migrate to metaprogramming
// mut fee_payer_point: TokenNoteHidingPoint,
// mut user_point: TokenNoteHidingPoint,
fee_payer_point_immutable: TokenNoteHidingPoint,
user_point_immutable: TokenNoteHidingPoint,
funded_amount: Field
) {
// TODO: nuke the following 2 lines once we have mutable args
// TODO(#7771): nuke the following 2 lines once we have mutable args
let mut fee_payer_point = fee_payer_point_immutable;
let mut user_point = user_point_immutable;

Expand Down
Loading
Loading