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/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ export class PrivateEnv {

export const deployerEnv = await PrivateEnv.create(process.env.PXE_URL || 'http://localhost:8080');

const IGNORE_FUNCTIONS = ['constructor', 'compute_note_hash_and_optionally_a_nullifier'];
const IGNORE_FUNCTIONS = ['constructor'];
export const filteredInterface = BoxReactContractArtifact.functions.filter(f => !IGNORE_FUNCTIONS.includes(f.name));
7 changes: 1 addition & 6 deletions boxes/boxes/vite/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,7 @@ export class PrivateEnv {

export const deployerEnv = new PrivateEnv();

const IGNORE_FUNCTIONS = [
"constructor",
"compute_note_hash_and_optionally_a_nullifier",
"process_log",
"sync_notes",
];
const IGNORE_FUNCTIONS = ["constructor", "process_log", "sync_notes"];
export const filteredInterface = BoxReactContractArtifact.functions.filter(
(f) => !IGNORE_FUNCTIONS.includes(f.name),
);
2 changes: 1 addition & 1 deletion docs/docs/aztec/concepts/accounts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ A side-effect of not having nonces at the protocol level is that it is not possi

Since the `entrypoint` interface is not enshrined, there is nothing that differentiates an account contract from an application one in the protocol. This means that a transaction can be initiated in any contract. This allows implementing functions that do not need to be called by any particular user and are just intended to advance the state of a contract.

As an example, we can think of a lottery contract, where at some point a prize needs to be paid out to its winners. This `pay` action does not require authentication and does not need to be executed by any user in particular, so anyone could submit a transaction that defines the lottery contract itself as `origin` and `pay` as entrypoint function. For an example of this behavior see our [non_contract_account test](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts) and the [SignerLess wallet](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/wallet/signerless_wallet.ts) implementation.
Comment thread
nventuro marked this conversation as resolved.
As an example, we can think of a lottery contract, where at some point a prize needs to be paid out to its winners. This `pay` action does not require authentication and does not need to be executed by any user in particular, so anyone could submit a transaction that defines the lottery contract itself as `origin` and `pay` as entrypoint function.
Notice that the Signerless wallet doesn't invoke an entrypoint function of an account contract but instead invokes the target contract function directly.

:::info
Expand Down
39 changes: 1 addition & 38 deletions docs/docs/aztec/smart_contracts/functions/function_transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,43 +129,6 @@ This process allows the return values to be included in the function's computati

In public functions, the return value is directly used, and the function's return type remains as specified by the developer.

## Computing note hash and nullifier

A function called `compute_note_hash_and_optionally_a_nullifier` is automatically generated and injected into all contracts that use notes. This function tells Aztec how to compute hashes and nullifiers for notes used in the contract. You can optionally write this function yourself if you want notes to be handled a specific way.

The function is automatically generated based on the note types defined in the contract. Here's how it works:

- The function takes several parameters:
```rust
fn compute_note_hash_and_optionally_a_nullifier(
contract_address: AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
compute_nullifier: bool,
serialized_note: [Field; MAX_NOTE_FIELDS_LENGTH],
) -> [Field; 4]
```

- It creates a `NoteHeader` using the provided args:
```rust
let note_header = NoteHeader::new(contract_address, nonce, storage_slot);
```

- The function then checks the `note_type_id` against all note types defined in the contract. For each note type, it includes a condition like this:
```rust
if (note_type_id == NoteType::get_note_type_id()) {
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier(
NoteType::unpack,
note_header,
compute_nullifier,
packed_note
)
}
```

- The function returns an array of 4 Field elements, which represent the note hash and, if computed, the nullifier.

## Function signature generation

Unique function signatures are generated for each contract function.
Expand Down Expand Up @@ -274,4 +237,4 @@ Contract artifacts are important because:
- They help decode function return values in the simulator

## Further reading
- [Function attributes and macros](./attributes.md)
- [Function attributes and macros](./attributes.md)
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,6 @@ export class TokenContract extends ContractBase {
cancel_authwit: ((inner_hash: FieldLike) => ContractFunctionInteraction) &
Pick<ContractMethod, "selector">;

/** compute_note_hash_and_optionally_a_nullifier(contract_address: struct, nonce: field, storage_slot: field, note_type_id: field, compute_nullifier: boolean, serialized_note: array) */
compute_note_hash_and_optionally_a_nullifier: ((
contract_address: AztecAddressLike,
nonce: FieldLike,
storage_slot: FieldLike,
note_type_id: FieldLike,
compute_nullifier: boolean,
serialized_note: FieldLike[]
) => ContractFunctionInteraction) &
Pick<ContractMethod, "selector">;

/** constructor(admin: struct, name: string, symbol: string, decimals: integer) */
constructor: ((
admin: AztecAddressLike,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ See [partial notes](../../../../../aztec/concepts/advanced/storage/partial_notes
When you send someone a note, the note hash gets added to the note hash tree. To spend the note, the receiver needs to get the note itself (the note hash preimage). There are two ways you can get a hold of your notes:

1. When sending someone a note, emit the note log to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md)
2. Manually using `pxe.addNote()` - If you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner.
2. Manually delivering it via a custom contract method, if you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner.

#include_code pxe_add_note yarn-project/end-to-end/src/composed/e2e_persistence.test.ts typescript
#include_code offchain_delivery yarn-project/end-to-end/src/composed/e2e_persistence.test.ts typescript

Note that this requires your contract to have an unconstrained function that processes these notes and adds them to PXE.

#include_code deliver_note_contract_method noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

### Revealing encrypted logs conditionally

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@ Furthermore, if not emitting the note, one should explicitly `discard` the value

### Successfully process the encrypted event

One of the functions of the PXE is constantly loading encrypted logs from the `AztecNode` and decrypting them.
When new encrypted logs are obtained, the PXE will try to decrypt them using the private encryption key of all the accounts registered inside PXE.
If the decryption is successful, the PXE will store the decrypted note inside a database.
If the decryption fails, the specific log will be discarded.

For the PXE to successfully process the decrypted note we need to compute the note's 'note hash' and 'nullifier'.
Aztec.nr enables smart contract developers to design custom notes, meaning developers can also customize how a note's note hash and nullifier should be computed. Because of this customizability, and because there will be a potentially-unlimited number of smart contracts deployed to Aztec, an PXE needs to be 'taught' how to compute the custom note hashes and nullifiers for a particular contract. This is done by a function called `compute_note_hash_and_optionally_a_nullifier`, which is automatically injected into every contract when compiled.
Contracts created using aztec-nr will try to discover newly created notes by searching for logs emitted for any of the accounts registered inside PXE, decrypting their contents and notifying PXE of any notes found. This process is automatic and occurs whenever a contract function is invoked.

## Unencrypted Events

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,6 @@ Options:
- `-ca, --contract-address <address>`: Contract address to filter logs by.
- `--follow`: Keep polling for new logs until interrupted.

### add-note
Adds a note to the database in the PXE.

```
aztec add-note <address> <contractAddress> <storageSlot> <noteTypeId> <txHash> [options]
```

Required option:
- `-n, --note [note...]`: The members of a Note serialized as hex strings.

## Development and Debugging Tools

### flamegraph
Expand Down Expand Up @@ -382,4 +372,4 @@ Commands: list, add, remove, who-next
Required option:
- `--l1-rpc-url <string>`: URL of the Ethereum host.

Note: Most commands accept a `--rpc-url` option to specify the Aztec node URL, and many accept fee-related options for gas limit and price configuration.
Note: Most commands accept a `--rpc-url` option to specify the Aztec node URL, and many accept fee-related options for gas limit and price configuration.
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,5 @@ This example mints and bridges 1000 units of fee juice and bridges it to the `ma
aztec-wallet bridge-fee-juice --mint 1000 master_yoda
```

### Add Note

The Add Note method makes it easy to store notes on your local PXE if they haven't been broadcasted yet. For example, if a JediMember note was sent to you, and you want to spend it on another transaction, you can use this method with the `--transaction-hash` flag to pass the transaction hash that contains the note.

It expects `name` and `storageFieldName`. For example, if the `#[storage]` struct had a `available_members: PrivateMutable<JediMember>` property:

```bash
aztec-wallet add-note JediMember available_members -a master_yoda -ca jedi_order -h 0x00000
```

## Proving
You can prove a transaction using the aztec-wallet with a running sandbox. Follow the guide [here](../../guides/local_env/sandbox_proving.md#proving-with-aztec-wallet)
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ The `increment` function works very similarly to the `constructor`, but instead

## Prevent double spending

Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key. Although this isn't really an issue in this simple smart contract, Aztec injects a special function called `compute_note_hash_and_optionally_a_nullifier` to determine these values for any given note produced by this contract.
Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key.

## Getting a counter

Expand Down
60 changes: 60 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,66 @@ Aztec is in full-speed development. Literally every version breaks compatibility

## TBD

### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifer`

This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it.

### [PXE] Removed `addNote` and `addNullifiedNote`

These functions have been removed from PXE and the base `Wallet` interface. If you need to deliver a note manually because its creation is not being broadcast in an encrypted log, then create an unconstrained contract function to process it and simulate execution of it. The `aztec::discovery::private_logs::do_process_log` function can be used to perform note discovery and add to it to PXE.

See an example of how to handle a `TransparentNote`:

```rust
unconstrained fn deliver_transparent_note(
contract_address: AztecAddress,
amount: Field,
secret_hash: Field,
tx_hash: Field,
unique_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
recipient: AztecAddress,
) {
// do_process_log expects a standard aztec-nr encoded note, which has the following shape:
// [ storage_slot, note_type_id, ...packed_note ]
let note = TransparentNote::new(amount, secret_hash);
let log_plaintext = BoundedVec::from_array(array_concat(
[
MyContract::storage_layout().my_state_variable.slot,
TransparentNote::get_note_type_id(),
],
note.pack(),
));

do_process_log(
contract_address,
log_plaintext,
tx_hash,
unique_note_hashes_in_tx,
first_nullifier_in_tx,
recipient,
_compute_note_hash_and_nullifier,
);
}
```

The note is then processed by calling this function:

```typescript
const txEffects = await wallet.getTxEffect(txHash);
await contract.methods
.deliver_transparent_note(
contract.address,
new Fr(amount),
secretHash,
txHash.hash,
toBoundedVec(txEffects!.data.noteHashes, MAX_NOTE_HASHES_PER_TX),
txEffects!.data.nullifiers[0],
wallet.getAddress(),
)
.simulate();
```

### Fee is mandatory

All transactions must now pay fees. Previously, the default payment method was `NoFeePaymentMethod`; It has been changed to `FeeJuicePaymentMethod`, with the wallet owner as the fee payer.
Expand Down
25 changes: 13 additions & 12 deletions noir-projects/aztec-nr/aztec/src/discovery/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,32 @@ pub struct NoteHashAndNullifier {
/// address).
///
/// This function must be user-provided as its implementation requires knowledge of how note type IDs are allocated in a
/// contract. A typical implementation would look like this:
/// contract. The `#[aztec]` macro automatically creates such a contract library method called
/// `_compute_note_hash_and_nullifier`, which looks something like this:
///
/// ```
/// |packed_note_content, contract_address, nonce, storage_slot, note_type_id| {
/// if note_type_id == MyNoteType::get_note_type_id() {
/// assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
/// let hashes = dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier(
/// MyNoteType::unpack_content,
/// note_header,
/// true,
/// packed_note_content.storage(),
/// )
///
/// Option::some(dep::aztec::oracle::management::NoteHashesAndNullifier {
/// note_hash: hashes[0],
/// inner_nullifier: hashes[3],
/// })
/// let note = MyNoteType::unpack(aztec::utils::array::subarray(packed_note.storage(), 0));
///
/// let note_hash = note.compute_note_hash(storage_slot);
/// let inner_nullifier = note.compute_nullifier_without_context(storage_slot, contract_address, nonce);
///
/// Option::some(
/// aztec::discovery::NoteHashAndNullifier {
/// note_hash, inner_nullifier
/// }
/// )
/// } else if note_type_id == MyOtherNoteType::get_note_type_id() {
/// ... // Similar to above but calling MyOtherNoteType::unpack_content
/// } else {
/// Option::none() // Unknown note type ID
/// };
/// }
/// ```
type ComputeNoteHashAndNullifier<Env> = fn[Env](/* packed_note_content */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* contract_address */ AztecAddress, /* nonce */ Field, /* storage_slot */ Field, /* note_type_id */ Field) -> Option<NoteHashAndNullifier>;
type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note_content */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* nonce */ Field) -> Option<NoteHashAndNullifier>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I changed this order to one that was more natural - the old one was related to the original order of compute_note_hash....

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's "natural"?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is storage_slot before contract_address natural, ser? I do not respect this nature

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because you need the packed content and storage slot to compute the note hash. It later gets siloed by contract address, and later by nonce. If you just compute the inner note hash you never use contract address. Storage slot and note type id are emitted in that order in the logs and passed in that order in the functions - once we extend logs to have things that are not note logs we'll likely change this (since there'll be no storage slot), but this is highly internally consistent right now.


/// Performs the note discovery process, in which private and public logs are downloaded and inspected to find private
/// notes, partial notes, and their completion. This is the mechanism via which PXE learns of new notes.
Expand Down
8 changes: 4 additions & 4 deletions noir-projects/aztec-nr/aztec/src/discovery/nonce_discovery.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{discovery::{MAX_NOTE_PACKED_LEN, NoteHashAndNullifier}, utils::array};
use crate::{discovery::{ComputeNoteHashAndNullifier, MAX_NOTE_PACKED_LEN}, utils::array};

use dep::protocol_types::{
address::AztecAddress,
Expand Down Expand Up @@ -26,7 +26,7 @@ pub struct DiscoveredNoteInfo {
pub unconstrained fn attempt_note_nonce_discovery<Env>(
unique_note_hashes_in_tx: BoundedVec<Field, MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
compute_note_hash_and_nullifier: fn[Env](BoundedVec<Field, MAX_NOTE_PACKED_LEN>, AztecAddress, Field, Field, Field) -> Option<NoteHashAndNullifier>,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
contract_address: AztecAddress,
storage_slot: Field,
note_type_id: Field,
Expand All @@ -53,10 +53,10 @@ pub unconstrained fn attempt_note_nonce_discovery<Env>(
// TODO(#11157): handle failed note_hash_and_nullifier computation
let hashes = compute_note_hash_and_nullifier(
packed_note_content,
contract_address,
candidate_nonce,
storage_slot,
note_type_id,
contract_address,
candidate_nonce,
)
.expect(f"Failed to compute a note hash for note type {note_type_id}");

Expand Down
12 changes: 1 addition & 11 deletions noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -348,17 +348,7 @@ comptime fn create_note_discovery_call() -> Quoted {
unsafe {
dep::aztec::discovery::discover_new_notes(
context.this_address(),
|packed_note_content: BoundedVec<Field, _>, contract_address: aztec::protocol_types::address::AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field| {
// _compute_note_hash_and_optionally_a_nullifier is a contract library method injected by `generate_contract_library_method_compute_note_hash_and_optionally_a_nullifier`
let hashes = _compute_note_hash_and_optionally_a_nullifier(contract_address, nonce, storage_slot, note_type_id, true, packed_note_content);

Option::some(
aztec::discovery::NoteHashAndNullifier {
note_hash: hashes[0],
inner_nullifier: hashes[3],
},
)
},
_compute_note_hash_and_nullifier,
)
};
}
Expand Down
Loading