Skip to content

Commit

Permalink
addressing feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
sklppy88 committed Sep 23, 2024
1 parent 2b46c5d commit 159f51e
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 67 deletions.
6 changes: 3 additions & 3 deletions docs/docs/aztec/concepts/storage/partial_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ Now let's do the same for partial notes.

## Partial notes life cycle

1. Create a partial/unfinished note in a private function of your contract --> partial here means that the values within the note are not yet considered finalized (e.g. `amount` in a `TokenNote`),
2. compute a note hiding point of the partial note using a multi scalar multiplication on an elliptic curve. For `TokenNote` this would be done as `G_amt * amount0 + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot`, where each `G_` is a generator point for a specific field in the note,
1. Create a partial/unfinished note in a private function of your contract --> partial here means that the values within the note are not yet considered finalized (e.g. `value` in a `UintNote`),
2. compute a note hiding point of the partial note using a multi scalar multiplication on an elliptic curve. For `UintNote` this would be done as `G_amt * value0 + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot`, where each `G_` is a generator point for a specific field in the note,
3. pass the note hiding point to a public function,
4. in a public function determine the value you want to add to the note (e.g. adding a value to an amount) and add it to the note hiding point (e.g. `NOTE_HIDING_POINT + G_amt * amount`),
5. get the note hash by finalizing the note hiding point (the note hash is the x coordinate of the point),
Expand Down Expand Up @@ -136,7 +136,7 @@ Then we just emit `P_a.x` and `P_b.x` as a note hashes, and we're done!

This is implemented in the example token contract:

#include_code compute_note_hiding_point noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr rust
#include_code compute_note_hiding_point noir-projects/aztec-nr/uint-note/src/uint_note.nr rust

Those `G_x` are generators that generated [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-projects/aztec-nr/aztec/src/generators.nr). Anyone can use them for separating different fields in a "partial note".

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export class TokenContract extends ContractBase {
},
balances: {
slot: new Fr(3n),
typ: 'BalancesMap<TokenNote>',
typ: 'BalancesMap<UintNote>',
},
total_supply: {
slot: new Fr(4n),
Expand Down Expand Up @@ -185,16 +185,16 @@ export class TokenContract extends ContractBase {
>;
}

public static get notes(): ContractNotes<'TransparentNote' | 'TokenNote'> {
public static get notes(): ContractNotes<'TransparentNote' | 'UintNote'> {
const notes = this.artifact.outputs.globals.notes ? (this.artifact.outputs.globals.notes as any) : [];
return {
TransparentNote: {
id: new Fr(84114971101151129711410111011678111116101n),
},
TokenNote: {
UintNote: {
id: new Fr(8411110710111078111116101n),
},
} as ContractNotes<'TransparentNote' | 'TokenNote'>;
} as ContractNotes<'TransparentNote' | 'UintNote'>;
}

/** Type-safe wrappers for the public methods exposed by the contract. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ sequenceDiagram
BalanceMap->>BalanceMap: derived_slot = H(map_slot, to)
BalanceMap->>BalanceSet: BalanceSet::new(to, derived_slot)
Token->>BalanceSet: to_bal.add(amount)
BalanceSet->>BalanceSet: note = TokenNote::new(amount, to)
BalanceSet->>BalanceSet: note = UintNote::new(amount, to)
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 }
Utils->>TokenNote: note_hiding_point = note.compute_note_hiding_point()
TokenNote->>Utils: note_hash = note_hiding_point.x
Utils->>UintNote: note_hiding_point = note.compute_note_hiding_point()
UintNote->>Utils: note_hash = note_hiding_point.x
LifeCycle->>Context: push_note_hash(note_hash)
end
Context->>Kernel: unique_note_hash = H(nonce, note_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag

We will be working within `main.nr` for the rest of the tutorial.

## How this will work
## How this will work

Before writing the functions, let's go through them to see how this contract will work:

Expand Down Expand Up @@ -158,7 +158,7 @@ Reading through the storage variables:

- `admin` an Aztec address stored in public state.
- `minters` is a mapping of Aztec addresses in public state. This will store whether an account is an approved minter on the contract.
- `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `TokenNote`s. The balance is the sum of all of an account's `TokenNote`s.
- `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `UintNote`s. The balance is the sum of all of an account's `UintNote`s.
- `total_supply` is an unsigned integer (max 128 bit value) stored in public state and represents the total number of tokens minted.
- `pending_shields` is a `PrivateSet` of `TransparentNote`s stored in private state. What is stored publicly is a set of commitments to `TransparentNote`s.
- `public_balances` is a mapping of Aztec addresses in public state and represents the publicly viewable balances of accounts.
Expand Down Expand Up @@ -259,7 +259,7 @@ Storage is referenced as `storage.variable`.

#### `redeem_shield`

This private function enables an account to move tokens from a `TransparentNote` in the `pending_shields` mapping to a `TokenNote` in private `balances`. The `TokenNote` will be associated with a nullifier key, so any account that knows this key can spend this note.
This private function enables an account to move tokens from a `TransparentNote` in the `pending_shields` mapping to a `UintNote` in private `balances`. The `UintNote` will be associated with a nullifier key, so any account that knows this key can spend this note.

Going through the function logic, first the `secret_hash` is generated from the given secret. This ensures that only the entity possessing the secret can use it to redeem the note. Following this, a `TransparentNote` is retrieved from the set, using the provided amount and secret. The note is subsequently removed from the set, allowing it to be redeemed only once. The recipient's private balance is then increased using the `increment` helper function from the `value_note` [library (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/utils.nr).

Expand All @@ -269,7 +269,7 @@ The function returns `1` to indicate successful execution.

#### `unshield`

This private function enables un-shielding of private `TokenNote`s stored in `balances` to any Aztec account's `public_balance`.
This private function enables un-shielding of private `UintNote`s stored in `balances` to any Aztec account's `public_balance`.

After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After the authorization check, the sender's private balance is decreased using the `decrement` helper function for the `value_note` library. Then it stages a public function call on this contract ([`_increase_public_balance`](#_increase_public_balance)) to be executed in the [public execution phase](#execution-contexts) of transaction execution. `_increase_public_balance` is marked as an `internal` function, so can only be called by this token contract.

Expand Down
14 changes: 10 additions & 4 deletions noir-projects/aztec-nr/uint-note/src/uint_note.nr
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use dep::aztec::{
generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot},
prelude::{NoteInterface, PrivateContext},
prelude::{NoteHeader, NoteInterface, PrivateContext},
protocol_types::{
constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH},
constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, scalar::Scalar,
hash::poseidon2_hash_with_separator, traits::Serialize
},
note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app
note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand,
keys::getters::get_nsk_app
};
use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe};

global UINT_NOTE_LEN: u32 = 3; // 3 plus a header.
global UINT_NOTE_BYTES_LEN: u32 = 3 * 32 + 64;

// docs:start:TokenNote
#[aztec(note)]
struct UintNote {
// The integer stored by the note
Expand All @@ -21,8 +23,10 @@ struct UintNote {
// Randomness of the note to hide its contents
randomness: Field,
}
// docs:end:TokenNote

impl NoteInterface<UINT_NOTE_LEN, UINT_NOTE_BYTES_LEN> for UintNote {
// docs:start:nullifier
fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field {
let secret = context.request_nsk_app(self.npk_m_hash);
poseidon2_hash_with_separator(
Expand All @@ -33,6 +37,7 @@ impl NoteInterface<UINT_NOTE_LEN, UINT_NOTE_BYTES_LEN> for UintNote {
GENERATOR_INDEX__NOTE_NULLIFIER as Field
)
}
// docs:end:nullifier

fn compute_nullifier_without_context(self) -> Field {
let note_hash_for_nullify = compute_note_hash_for_nullify(self);
Expand Down Expand Up @@ -63,13 +68,14 @@ impl NoteInterface<UINT_NOTE_LEN, UINT_NOTE_BYTES_LEN> for UintNote {
}

impl UintNote {
// TODO: Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have
// TODO(#8290): Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have
// to modify macros and all the related funcs in it.
fn to_note_hiding_point(self) -> UintNoteHidingPoint {
UintNoteHidingPoint::new(self.compute_note_hiding_point())
}
}

// TODO(#8290): Auto-generate this
struct UintNoteHidingPoint {
inner: Point
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ contract Token {
minters: Map<AztecAddress, PublicMutable<bool>>,
// docs:end:storage_minters
// docs:start:storage_balances
balances: Map<AztecAddress, BalanceSet<UintNote>>,
balances: Map<AztecAddress, BalanceSet>,
// docs:end:storage_balances
total_supply: PublicMutable<U128>,
// docs:start:storage_pending_shields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ unconstrained fn setup_refund_success() {
// `executePublicFunction` TXE oracle) but we need to notify TXE of the note (preimage).
env.add_note(
&mut UintNote {
value: U128::from_integer(funded_amount - 1),
npk_m_hash: user_npk_m_hash,
randomness: user_randomness,
header: NoteHeader::empty()
},
value: U128::from_integer(funded_amount - 1),
npk_m_hash: user_npk_m_hash,
randomness: user_randomness,
header: NoteHeader::empty()
},
user_balances_slot,
token_contract_address
);
env.add_note(
&mut UintNote {
value: U128::from_integer(1),
npk_m_hash: fee_payer_npk_m_hash,
randomness: fee_payer_randomness,
header: NoteHeader::empty()
},
value: U128::from_integer(1),
npk_m_hash: fee_payer_npk_m_hash,
randomness: fee_payer_randomness,
header: NoteHeader::empty()
},
fee_payer_balances_slot,
token_contract_address
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet, Point};
use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, PrivateSet};
use dep::aztec::{
context::{PrivateContext, UnconstrainedContext},
protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
note::{note_getter::view_notes, note_emission::{NoteEmission, OuterNoteEmission}},
keys::{getters::get_public_keys, public_keys::NpkM}
note::note_emission::OuterNoteEmission, keys::public_keys::NpkM
};
use dep::uint_note::uint_note::OwnedNote;
use dep::uint_note::uint_note::UintNote;

struct BalanceSet<T, Context> {
set: PrivateSet<T, Context>,
struct BalanceSet<Context> {
set: PrivateSet<UintNote, Context>,
}

impl<T, Context> BalanceSet<T, Context> {
impl<Context> BalanceSet<Context> {
pub fn new(context: Context, storage_slot: Field) -> Self {
assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
Self { set: PrivateSet::new(context, storage_slot) }
}
}

impl<T> BalanceSet<T, UnconstrainedContext> {
unconstrained pub fn balance_of<let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(self: Self) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
impl BalanceSet <UnconstrainedContext> {
unconstrained pub fn balance_of(self: Self) -> U128 {
self.balance_of_with_offset(0)
}

unconstrained pub fn balance_of_with_offset<let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(
self: Self,
offset: u32
) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
unconstrained pub fn balance_of_with_offset(self: Self, offset: u32) -> U128 {
let mut balance = U128::from_integer(0);
// docs:start:view_notes
let mut options = NoteViewerOptions::new();
let notes = self.set.view_notes(options.set_offset(offset));
// docs:end:view_notes
for i in 0..options.limit {
if i < notes.len() {
balance = balance + notes.get_unchecked(i).get_amount();
balance = balance + notes.get_unchecked(i).value;
}
}
if (notes.len() == options.limit) {
Expand All @@ -45,29 +41,21 @@ impl<T> BalanceSet<T, UnconstrainedContext> {
}
}

impl<T> BalanceSet<T, &mut PrivateContext> {
pub fn add<let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(
self: Self,
owner_npk_m: NpkM,
addend: U128
) -> OuterNoteEmission<T> where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
impl BalanceSet<&mut PrivateContext> {
pub fn add(self: Self, owner_npk_m: NpkM, addend: U128) -> OuterNoteEmission<UintNote> {
if addend == U128::from_integer(0) {
OuterNoteEmission::new(Option::none())
} else {
// We fetch the nullifier public key hash from the registry / from our PXE
let mut addend_note = T::new(addend, owner_npk_m.hash());
let mut addend_note = UintNote::new(addend, owner_npk_m.hash());

// docs:start:insert
OuterNoteEmission::new(Option::some(self.set.insert(&mut addend_note)))
// docs:end:insert
}
}

pub fn sub<let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(
self: Self,
owner_npk_m: NpkM,
amount: U128
) -> OuterNoteEmission<T> where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
pub fn sub(self: Self, owner_npk_m: NpkM, amount: U128) -> OuterNoteEmission<UintNote> {
let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL);

// try_sub may have substracted more or less than amount. We must ensure that we subtracted at least as much as
Expand All @@ -85,11 +73,7 @@ impl<T> BalanceSet<T, &mut PrivateContext> {
// The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count
// scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of
// `try_sub` subtracting an amount smaller than `target_amount`.
pub fn try_sub<let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(
self: Self,
target_amount: U128,
max_notes: u32
) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
pub fn try_sub(self: Self, target_amount: U128, max_notes: u32) -> U128 {
// We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because
// we do not need to prove correct execution of the preprocessor.
// Because the `min_sum` notes is not constrained, users could choose to e.g. not call it. However, all this
Expand All @@ -102,7 +86,7 @@ impl<T> BalanceSet<T, &mut PrivateContext> {
for i in 0..options.limit {
if i < notes.len() {
let note = notes.get_unchecked(i);
subtracted = subtracted + note.get_amount();
subtracted = subtracted + note.value;
}
}

Expand All @@ -115,10 +99,10 @@ impl<T> BalanceSet<T, &mut PrivateContext> {
// The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to
// 'min_sum' - all it does is remove extra notes if it does reach that value.
// Note that proper usage of this preprocessor requires for notes to be sorted in descending order.
pub fn preprocess_notes_min_sum<T, let T_SERIALIZED_LEN: u32, let T_SERIALIZED_BYTES_LEN: u32>(
notes: [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL],
pub fn preprocess_notes_min_sum(
notes: [Option<UintNote>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL],
min_sum: U128
) -> [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
) -> [Option<UintNote>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] {
let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL];
let mut sum = U128::from_integer(0);
for i in 0..notes.len() {
Expand All @@ -129,7 +113,7 @@ pub fn preprocess_notes_min_sum<T, let T_SERIALIZED_LEN: u32, let T_SERIALIZED_B
if notes[i].is_some() & sum < min_sum {
let note = notes[i].unwrap_unchecked();
selected[i] = Option::some(note);
sum = sum.add(note.get_amount());
sum = sum.add(note.value);
}
}
selected
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('e2e_fees/private_refunds', () => {
t.aliceAddress,
token.address,
deriveStorageSlotInMap(TokenContract.storage.balances.slot, t.aliceAddress),
TokenContract.notes.IntNote.id,
TokenContract.notes.UintNote.id,
txHash,
),
);
Expand All @@ -120,7 +120,7 @@ describe('e2e_fees/private_refunds', () => {
t.bobAddress,
token.address,
deriveStorageSlotInMap(TokenContract.storage.balances.slot, t.bobAddress),
TokenContract.notes.IntNote.id,
TokenContract.notes.UintNote.id,
txHash,
),
);
Expand Down

0 comments on commit 159f51e

Please sign in to comment.