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

feat: take an optional owner to create the initialization nullifier #2647

Merged
merged 5 commits into from
Oct 4, 2023
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
16 changes: 15 additions & 1 deletion docs/docs/dev_docs/contracts/syntax/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,20 @@ As mention, the singleton is initialized to create the first note and value. Her

#include_code initialize /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust

When this function is called, a nullifier of the storage slot is created, preventing this Singleton from being initialized again.
When this function is called, a nullifier of the storage slot is created, preventing this Singleton from being initialized again. If an `owner` is specified, the nullifier will be hashed with the owner's secret key. It's crucial to provide an owner if the Singleton is associated with an account. Initializing it without an owner may inadvertently reveal important information about the owner's intention.

Unlike public states, which have a default initial value of `0` (or many zeros, in the case of a struct, array or map), a private state (of type `Singleton`, `ImmutableSingleton` or `Set`) does not have a default initial value. The `initialize` method (or `insert`, in the case of a `Set`) must be called.

:::info
Extend on what happens if you try to use non-initialized state.
:::

### `is_initialized`

An unconstrained method to check whether the Singleton has been initialized or not.

#include_code is_initialized /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust

### `replace`

To update the value of a `Singleton`, we can use the `replace` method. The method takes a new note as input, and replaces the current note with the new one. It emits a nullifier for the old value, and inserts the new note into the data tree.
Expand Down Expand Up @@ -303,6 +309,8 @@ As part of the initialization of the `Storage` struct, the `Singleton` is create

### `initialize`

When this function is invoked, it creates a nullifier for the storage slot, ensuring that the ImmutableSingleton cannot be initialized again. If an owner is specified, the nullifier will be hashed with the owner's secret key. It is crucial to provide an owner if the ImmutableSingleton is linked to an account; initializing it without one may inadvertently disclose sensitive information about the owner's intent.

#include_code initialize /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust

Set the value of an ImmutableSingleton by calling the `initialize` method:
Expand All @@ -311,6 +319,12 @@ Set the value of an ImmutableSingleton by calling the `initialize` method:

Once initialized, an ImmutableSingleton's value remains unchangeable. This method can only be called once.

### `is_initialized`

An unconstrained method to check if the ImmutableSingleton has been initialized.

#include_code is_initialized /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust

### `get_note`

Similar to the `Singleton`, we can use the `get_note` method to read the value of an ImmutableSingleton.
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ export class Oracle {
return toACVMField(0);
}

async checkNullifierExists([innerNullifier]: ACVMField[]): Promise<ACVMField> {
const exists = await this.typedOracle.checkNullifierExists(fromACVMField(innerNullifier));
return toACVMField(exists);
}

async getL1ToL2Message([msgKey]: ACVMField[]): Promise<ACVMField[]> {
const { root, ...message } = await this.typedOracle.getL1ToL2Message(fromACVMField(msgKey));
return toAcvmL1ToL2MessageLoadOracleInputs(message, root);
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

checkNullifierExists(_innerNullifier: Fr): Promise<boolean> {
throw new Error('Not available.');
}

getL1ToL2Message(_msgKey: Fr): Promise<L1ToL2MessageOracleReturnData> {
throw new Error('Not available.');
}
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ export interface DBOracle extends CommitmentsDB {
*/
getPortalContractAddress(contractAddress: AztecAddress): Promise<EthAddress>;

/**
* Gets the index of a nullifier in the nullifier tree.
* @param nullifier - The nullifier.
* @returns - The index of the nullifier. Undefined if it does not exist in the tree.
*/
getNullifierIndex(nullifier: Fr): Promise<bigint | undefined>;

/**
* Retrieve the databases view of the Historic Block Data object.
* This structure is fed into the circuits simulator and is used to prove against certain historic roots.
Expand Down
15 changes: 14 additions & 1 deletion yarn-project/acir-simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HistoricBlockData, PublicKey } from '@aztec/circuits.js';
import { CircuitsWasm, HistoricBlockData, PublicKey } from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/abis';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
Expand Down Expand Up @@ -99,6 +100,18 @@ export class ViewDataOracle extends TypedOracle {
});
}

/**
* Check if a nullifier exists in the nullifier tree.
* @param innerNullifier - The inner nullifier.
* @returns A boolean indicating whether the nullifier exists in the tree or not.
*/
public async checkNullifierExists(innerNullifier: Fr) {
const wasm = await CircuitsWasm.get();
const nullifier = siloNullifier(wasm, this.contractAddress, innerNullifier!);
const index = await this.db.getNullifierIndex(nullifier);
return index !== undefined;
}

/**
* Fetches the a message from the db, given its key.
* @param msgKey - A buffer representing the message key.
Expand Down
10 changes: 7 additions & 3 deletions yarn-project/aztec-nr/aztec/src/oracle/notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ unconstrained fn get_notes<Note, N, M, S, NS>(
placeholder_opt_notes
}

unconstrained fn is_nullifier_emitted(nullifier: Field) -> bool {
// TODO
nullifier == 0
#[oracle(checkNullifierExists)]
fn check_nullifier_exists_oracle(
_inner_nullifier: Field,
) -> Field {}

unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool {
check_nullifier_exists_oracle(inner_nullifier) == 1
}
46 changes: 23 additions & 23 deletions yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use crate::constants_gen::{EMPTY_NULLIFIED_COMMITMENT, GENERATOR_INDEX__INITIALISATION_NULLIFIER};
use dep::std::option::Option;
use crate::constants_gen::EMPTY_NULLIFIED_COMMITMENT;
use crate::context::{PrivateContext, Context};
use crate::note::{
lifecycle::create_note,
note_getter::{get_note, view_notes},
note_interface::NoteInterface,
note_viewer_options::NoteViewerOptions,
};
use crate::oracle;
use dep::std::hash::pedersen_with_separator;
use dep::std::option::Option;
use crate::oracle::notes::check_nullifier_exists;
use crate::state_vars::singleton::compute_singleton_initialization_nullifier;

// docs:start:struct
struct ImmutableSingleton<Note, N> {
context: Context,
context: Option<&mut PrivateContext>,
storage_slot: Field,
note_interface: NoteInterface<Note, N>,
compute_initialization_nullifier: fn (Field, Option<Field>) -> Field,
}
// docs:end:struct

Expand All @@ -27,46 +28,45 @@ impl<Note, N> ImmutableSingleton<Note, N> {
) -> Self {
assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
ImmutableSingleton {
context,
context: context.private,
storage_slot,
note_interface,
compute_initialization_nullifier: compute_singleton_initialization_nullifier,
}
}
// docs:end:new

unconstrained fn is_initialized(self) -> bool {
let nullifier = self.compute_initialisation_nullifier();
oracle::notes::is_nullifier_emitted(nullifier)
// docs:start:is_initialized
unconstrained fn is_initialized(self, owner: Option<Field>) -> bool {
let compute_initialization_nullifier = self.compute_initialization_nullifier;
let nullifier = compute_initialization_nullifier(self.storage_slot, owner);
check_nullifier_exists(nullifier)
}
// docs:end:is_initialized

// docs:start:initialize
fn initialize(self, note: &mut Note) {
fn initialize(self, note: &mut Note, owner: Option<Field>) {
let context = self.context.unwrap();

// Nullify the storage slot.
let nullifier = self.compute_initialisation_nullifier();
self.context.private
.unwrap()
.push_new_nullifier(nullifier, EMPTY_NULLIFIED_COMMITMENT);
let compute_initialization_nullifier = self.compute_initialization_nullifier;
let nullifier = compute_initialization_nullifier(self.storage_slot, owner);
context.push_new_nullifier(nullifier, EMPTY_NULLIFIED_COMMITMENT);

create_note(
self.context.private.unwrap(),
context,
self.storage_slot,
note,
self.note_interface,
);
}
// docs:end:initialize

fn compute_initialisation_nullifier(self) -> Field {
pedersen_with_separator(
[self.storage_slot],
GENERATOR_INDEX__INITIALISATION_NULLIFIER,
)[0]
}

// docs:start:get_note
fn get_note(self) -> Note {
let context = self.context.unwrap();
let storage_slot = self.storage_slot;
get_note(self.context.private.unwrap(), storage_slot, self.note_interface)
get_note(context, storage_slot, self.note_interface)
}
// docs:end:get_note

Expand Down
46 changes: 32 additions & 14 deletions yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use dep::std::option::Option;
use crate::constants_gen::{EMPTY_NULLIFIED_COMMITMENT, GENERATOR_INDEX__INITIALISATION_NULLIFIER};
use crate::context::{PrivateContext, PublicContext, Context};
use crate::note::{
Expand All @@ -6,15 +7,33 @@ use crate::note::{
note_interface::NoteInterface,
note_viewer_options::NoteViewerOptions,
};
use crate::oracle;
use crate::oracle::{
get_secret_key::get_secret_key,
notes::check_nullifier_exists,
};
use dep::std::hash::pedersen_with_separator;
use dep::std::option::Option;

fn compute_singleton_initialization_nullifier(storage_slot: Field, owner: Option<Field>) -> Field {
if owner.is_some() {
let secret = get_secret_key(owner.unwrap_unchecked());
pedersen_with_separator(
[storage_slot, secret.low, secret.high],
GENERATOR_INDEX__INITIALISATION_NULLIFIER,
)[0]
} else {
pedersen_with_separator(
[storage_slot],
GENERATOR_INDEX__INITIALISATION_NULLIFIER,
)[0]
}
}

// docs:start:struct
struct Singleton<Note, N> {
context: Option<&mut PrivateContext>,
storage_slot: Field,
note_interface: NoteInterface<Note, N>,
compute_initialization_nullifier: fn (Field, Option<Field>) -> Field,
}
// docs:end:struct

Expand All @@ -30,33 +49,32 @@ impl<Note, N> Singleton<Note, N> {
context: context.private,
storage_slot,
note_interface,
compute_initialization_nullifier: compute_singleton_initialization_nullifier,
}
}
// docs:end:new

unconstrained fn is_initialized(self) -> bool {
let nullifier = self.compute_initialisation_nullifier();
oracle::notes::is_nullifier_emitted(nullifier)
// docs:start:is_initialized
unconstrained fn is_initialized(self, owner: Option<Field>) -> bool {
let compute_initialization_nullifier = self.compute_initialization_nullifier;
let nullifier = compute_initialization_nullifier(self.storage_slot, owner);
check_nullifier_exists(nullifier)
}
// docs:end:is_initialized

// docs:start:initialize
fn initialize(self, note: &mut Note) {
fn initialize(self, note: &mut Note, owner: Option<Field>) {
let context = self.context.unwrap();

// Nullify the storage slot.
let nullifier = self.compute_initialisation_nullifier();
let compute_initialization_nullifier = self.compute_initialization_nullifier;
let nullifier = compute_initialization_nullifier(self.storage_slot, owner);
context.push_new_nullifier(nullifier, EMPTY_NULLIFIED_COMMITMENT);

create_note(context, self.storage_slot, note, self.note_interface);
}
// docs:end:initialize

fn compute_initialisation_nullifier(self) -> Field {
pedersen_with_separator(
[self.storage_slot],
GENERATOR_INDEX__INITIALISATION_NULLIFIER,
)[0]
}

// docs:start:replace
fn replace(self, new_note: &mut Note) {
let context = self.context.unwrap();
Expand Down
Loading