Skip to content
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
2 changes: 1 addition & 1 deletion boxes/boxes/react/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ contract BoxReact {
use dep::aztec::{
encrypted_logs::log_assembly_strategies::default_aes128::note::encode_and_encrypt_note,
macros::{functions::{initializer, private}, storage::storage},
prelude::{AztecAddress, Map, NoteInterface, PrivateMutable},
prelude::{AztecAddress, Map, PrivateMutable},
};
use dep::value_note::value_note::ValueNote;

Expand Down
4 changes: 2 additions & 2 deletions boxes/boxes/vanilla/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ contract Vanilla {
use dep::aztec::{
encrypted_logs::log_assembly_strategies::default_aes128::note::encode_and_encrypt_note,
macros::{functions::{initializer, private}, storage::storage},
prelude::{AztecAddress, Map, NoteInterface, PrivateMutable},
prelude::{AztecAddress, Map, PrivateMutable},
};
use dep::value_note::value_note::{VALUE_NOTE_LEN, ValueNote};
use dep::value_note::value_note::ValueNote;

#[storage]
struct Storage<Context> {
Expand Down
36 changes: 14 additions & 22 deletions docs/docs/aztec/smart_contracts/functions/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,30 +178,23 @@ This macro inserts a check at the beginning of the function to ensure that the c
assert(context.msg_sender() == context.this_address(), "Function can only be called internally");
```

## Custom notes #[note]
## Implementing notes

The `#[note]` attribute is used to define custom note types in Aztec contracts. Learn more about notes [here](../../concepts/storage/index.md).
The `#[note]` attribute is used to define notes in Aztec contracts. Learn more about notes [here](../../concepts/storage/index.md).

When a struct is annotated with `#[note]`, the Aztec macro applies a series of transformations and generates implementations to turn it into a note that can be used in contracts to store private data.

1. **NoteInterface Implementation**: The macro automatically implements most methods of the `NoteInterface` trait for the annotated struct. This includes:
1. **Note Interface Implementation**: The macro automatically implements the `NoteType`, `NoteHash` and `Packable<N>` traits for the annotated struct. This includes the following methods:

- `pack_content` and `unpack_content`
- `get_header` and `set_header`
- `get_note_type_id`
- `compute_note_hiding_point`
- `to_be_bytes`
- A `properties` method in the note's implementation
- `get_id`
- `compute_note_hash`
- `compute_nullifier`
- `pack`
- `unpack`

2. **Automatic Header Field**: If the struct doesn't already have a `header` field of type `NoteHeader`, one is automatically created
2. **Property Metadata**: A separate struct is generated to describe the note's fields, which is used for efficient retrieval of note data

3. **Note Type ID Generation**: A unique `note_type_id` is automatically computed for the note type using a Keccak hash of the struct name

4. **Serialization and Deserialization**: Methods for converting the note to and from a series of `Field` elements are generated, assuming each field can be converted to/from a `Field`

5. **Property Metadata**: A separate struct is generated to describe the note's fields, which is used for efficient retrieval of note data

6. **Export Information**: The note type and its ID are automatically exported
3. **Export Information**: The note type and its ID are automatically exported

### Before expansion

Expand All @@ -218,19 +211,18 @@ struct CustomNote {
### After expansion

```rust
impl NoteInterface for CustomNote {
fn get_note_type_id() -> Field {
impl NoteType for CustomNote {
fn get_id() -> Field {
// Assigned by macros by incrementing a counter
2
}
}

impl NoteHash for CustomNote {
fn compute_note_hash(self, storage_slot: Field) -> Field {
let inputs = array_concat(self.pack(), [storage_slot]);
poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH)
}
}

impl NullifiableNote for CustomNote {

fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field {
let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ PS: when calling from private to public, `msg_sender` is the contract address wh

In the [Prevent the same user flow from happening twice using nullifier](#prevent-the-same-user-flow-from-happening-twice-using-nullifiers), we recommended using nullifiers. But what you put in the nullifier is also as important.

E.g. for a voting contract, if your nullifier simply emits just the `user_address`, then privacy can easily be leaked as nullifiers are deterministic (have no randomness), especially if there are few users of the contract. So you need some kind of randomness. You can add the user's secret key into the nullifier to add randomness. We call this "nullifier secrets" as explained [here](../../../../../aztec/concepts/accounts/keys.md#nullifier-keys). E.g.:
E.g. for a voting contract, if your nullifier simply emits just the `user_address`, then privacy can easily be leaked via a preimage attack as nullifiers are deterministic (have no randomness), especially if there are few users of the contract. So you need some kind of randomness. You can add the user's secret key into the nullifier to add randomness. We call this "nullifier secrets" as explained [here](../../../../../aztec/concepts/accounts/keys.md#nullifier-keys).

#include_code nullifier /noir-projects/aztec-nr/value-note/src/value_note.nr rust
Here is an example from the voting contract:

#include_code cast_vote /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ title: Using Address Note in Aztec.nr
tags: [contracts, notes]
---

Address notes hold one main property of the type `AztecAddress`. It also holds `npk_m_hash` and `randomness`, similar to other note types.
Address notes hold one main property of the type `AztecAddress`. It also holds `owner` and `randomness`, similar to other note types.

## AddressNote

This is the AddressNote struct:
This is the AddressNote:

#include_code address_note_struct noir-projects/aztec-nr/address-note/src/address_note.nr rust
#include_code address_note_def noir-projects/aztec-nr/address-note/src/address_note.nr rust

## Importing AddressNote

Expand Down Expand Up @@ -39,5 +39,5 @@ In this example, `owner` is the `address` and the `npk_m_hash` of the donor was
## Learn more

- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md)
- [How to write a custom note](./custom_note.md)
- [How to implement a note](./implementing_a_note.md)
- [AddressNote reference](../../../../reference/smart_contract_reference/aztec-nr/address-note/address_note.md)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: Implementing a note in Aztec.nr
tags: [contracts, notes]
keywords: [implementing note, note]
---

You may want to create your own note type if you need to use a specific type of private data or struct that is not already implemented in Aztec.nr, or if you want to experiment with custom note hashing and nullifier schemes. For custom hashing and nullifier schemes, use the `#[custom_note]` macro instead of `#[note]`, as it does not automatically derive the `NoteHash` trait.

For example, if you are developing a card game, you may want to store multiple pieces of data in each card. Rather than storing each piece of data in its own note, you can define a card note type that contains all the data, and then nullify (or exchange ownership of) the card when it has been used.

If you want to work with values, addresses or integers, you can check out [ValueNote](./value_note.md), [AddressNote](./address_note.md) or `UintNote`.

## Define a note type

You will likely want to define your note in a new file and import it into your contract.

A note type can be defined with the macro `#[note]` used on a struct:

#include_code state_vars-CardNote noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr rust

In this example, we are implementing a card note that holds a number of `points` as `u8`.

`randomness` is not enforced by the protocol and should be implemented by the application developer. If you do not include `randomness`, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks.

`owner` is used when nullifying the note to obtain a nullifer secret key.
It ensures that when a note is spent, only the owner can spend it and the note sender cannot figure out that the note has been spent!
Providing the `owner` with improved privacy.

Why is it delivering privacy from sender?

Because a sender cannot derive a note nullifier.
We could derive the nullifier based solely on the note itself (for example, by computing `hash([note.points, note.owner, note.randomness], NULLIFIER_SEPARATOR)`).
This would work since the nullifier would be unique and only the note recipient could spend it (as contract logic typically only allows the note owner to obtain a note, e.g. from a `Map<...>`).
However, if we did this, the sender could also derive the nullifier off-chain and monitor the nullifier tree for its inclusion, allowing them to determine when a note has been spent.
This would leak privacy.

## Further reading

- [What is `#[note]` actually doing? + functions such as serialize() and deserialize()](../../../../../aztec/smart_contracts/functions//attributes.md#implementing-notes-note)
- [Macros reference](../../../../reference/smart_contract_reference/macros.md)
- [Keys, including npk_m_hash (nullifier public key master)](../../../../../aztec/concepts/accounts/keys.md)
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ tags: [contracts, notes]

Notes are the fundamental data structure in Aztec when working with private state. Using Aztec.nr, developers can define note types which allow flexibility in how notes are stored and nullified.

In this section there are guides about how to work with `AddressNote`, `ValueNote`, and custom notes in Aztec.nr. You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/notes.md).
In this section there are guides about how to work with `AddressNote`, `ValueNote` from Aztec.nr and how to implement your own notes.
You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/notes.md).
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ The `decrement` function works similarly except the `amount` is the number that
## Learn more

- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md)
- [How to write a custom note](./custom_note.md)
- [How to implement a note](./implementing_a_note.md)
- [ValueNote reference](../../../../reference/smart_contract_reference/aztec-nr/value-note/value_note.md)
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,20 @@ Unlike public state variables, which can be arbitrary types, private state varia

Notes are the fundamental elements in the private world.

A note should implement the following traits:
A note has to implement the following traits:

#include_code note_interface /noir-projects/aztec-nr/aztec/src/note/note_interface.nr rust

#include_code serialize /noir-projects/noir-protocol-circuits/crates/types/src/traits.nr rust

#include_code deserialize /noir-projects/noir-protocol-circuits/crates/types/src/traits.nr rust
#include_code note_interfaces /noir-projects/aztec-nr/aztec/src/note/note_interface.nr rust

The interplay between a private state variable and its notes can be confusing. Here's a summary to aid intuition:

A private state variable (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) may be declared in storage.

Every note contains a header, which contains the contract address and storage slot of the state variable to which it is associated. A note is associated with a private state variable if the storage slot of the private state variable matches the storage slot contained in the note's header. The header provides information that helps the user interpret the note's data.
A private state variable (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) may be declared in storage and the purpose of private state variables is to manage notes (inserting their note hashes into the note hash tree, obtaining the notes, grouping the notes together using the storage slot etc.).

Management of the header is abstracted-away from developers who use the `PrivateImmutable`, `PrivateMutable` and `PrivateSet` types.
:::info
Note that storage slots in private state are not real.
They do not point to a specific leaf in a merkle tree (as is the case in public).
Instead, in the case of notes they can be understood only as a tag that is used to associate notes with a private state variable.
The state variable storage slot can commonly represent an owner, as is the case when using the `at(...)` function of a `Map<>` with an `AztecAddress` as the key.
:::

A private state variable points to one or many notes (depending on the type). The note(s) are all valid private state if the note(s) haven't yet been nullified.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,12 @@ Below the dependencies, paste the following Storage struct:

## Custom Notes

The contract storage uses a [custom note](../../../guides/smart_contracts/writing_contracts/notes/custom_note.md) implementation. Custom notes are useful for defining your own data types. You can think of a custom note as a "chunk" of private data, the entire thing is added, updated or nullified (deleted) together. This NFT note is very simple and stores only the owner and the `token_id` and uses `randomness` to hide its contents.
The contract storage uses a [custom note](../../../guides/smart_contracts/writing_contracts/notes/implementing_a_note.md) implementation. Custom notes are useful for defining your own data types. You can think of a custom note as a "chunk" of private data, the entire thing is added, updated or nullified (deleted) together. This NFT note is very simple and stores only the owner and the `token_id` and uses `randomness` to hide its contents.

Randomness is required because notes are stored as commitments (hashes) in the note hash tree. Without randomness, the contents of a note may be derived through brute force (e.g. without randomness, if you know my Aztec address, you may be able to figure out which note hash in the tree is mine by hashing my address with many potential `token_id`s).

#include_code nft_note /noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr rust

The custom note implementation also includes the nullifier computation function. This tells the protocol how the note should be nullified.

#include_code compute_nullifier /noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr rust

## Functions

Copy and paste the body of each function into the appropriate place in your project if you are following along.
Expand Down
Loading
Loading