Skip to content

Commit

Permalink
feat: Add aztec-nr private functions for initialization nullifier (#4807
Browse files Browse the repository at this point in the history
)

Adds helper functions for emitting and checking an initialization
nullifier in private-land.
  • Loading branch information
spalladino authored Feb 28, 2024
1 parent d367cc4 commit 4feaea5
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 5 deletions.
29 changes: 29 additions & 0 deletions noir-projects/aztec-nr/aztec/src/initializer.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use dep::protocol_types::hash::silo_nullifier;
use crate::context::PrivateContext;

pub fn mark_as_initialized(context: &mut PrivateContext) {
let init_nullifier = compute_unsiloed_contract_initialization_nullifier(context);
context.push_new_nullifier(init_nullifier, 0);

// We push a commitment as well and use this value to check initialization,
// since we cannot yet read a nullifier from the same tx in which it was emitted.
// Eventually, when that's supported, we should delete this note_hash and
// have all checks rely on reading the nullifier directly.
// TODO(@spalladino) Remove when possible.
context.push_new_note_hash(init_nullifier);
}

// TODO(@spalladino): Add a variant using PublicContext once we can read nullifiers or note hashes from public-land.
pub fn assert_is_initialized(context: &mut PrivateContext) {
let init_nullifier = compute_contract_initialization_nullifier(context);
context.push_read_request(init_nullifier);
}

pub fn compute_contract_initialization_nullifier(context: &mut PrivateContext) -> Field {
let address = context.this_address();
silo_nullifier(address, compute_unsiloed_contract_initialization_nullifier(context))
}

pub fn compute_unsiloed_contract_initialization_nullifier(context: &mut PrivateContext) -> Field {
context.this_address().to_field()
}
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod deploy;
mod hash;
mod hasher;
mod history;
mod initializer;
mod key;
mod log;
mod messaging;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests.
contract StatefulTest {
use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector};
use dep::std::option::Option;
use dep::value_note::{balance_utils, utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote}};
use dep::aztec::{
deploy::{deploy_contract as aztec_deploy_contract},
context::{PrivateContext, PublicContext, Context},
note::{note_header::NoteHeader, utils as note_utils},
state_vars::{Map, PublicMutable, PrivateSet},
oracle::get_contract_instance::get_contract_instance
oracle::get_contract_instance::get_contract_instance,
initializer::{mark_as_initialized, assert_is_initialized},
protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector},
};

struct Storage {
Expand All @@ -18,12 +19,22 @@ contract StatefulTest {

#[aztec(private)]
fn constructor(owner: AztecAddress, value: Field) {
let selector = FunctionSelector::from_signature("create_note((Field),Field)");
let selector = FunctionSelector::from_signature("internal_create_note((Field),Field)");
let _res = context.call_private_function(context.this_address(), selector, [owner.to_field(), value]);
mark_as_initialized(&mut context);
}

#[aztec(private)]
fn create_note(owner: AztecAddress, value: Field) {
assert_is_initialized(&mut context);
if (value != 0) {
let loc = storage.notes.at(owner);
increment(loc, value, owner);
}
}

#[aztec(private)]
internal fn internal_create_note(owner: AztecAddress, value: Field) {
if (value != 0) {
let loc = storage.notes.at(owner);
increment(loc, value, owner);
Expand All @@ -32,6 +43,7 @@ contract StatefulTest {

#[aztec(private)]
fn destroy_and_create(recipient: AztecAddress, amount: Field) {
assert_is_initialized(&mut context);
let sender = context.msg_sender();

let sender_notes = storage.notes.at(sender);
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/circuits.js/src/structs/side_effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export class SideEffect implements SideEffectType {
public counter: Fr,
) {}

toString(): string {
return `value=${this.value.toString()} counter=${this.counter.toString()}`;
}

/**
* Serialize this as a buffer.
* @returns The buffer.
Expand Down
33 changes: 33 additions & 0 deletions yarn-project/end-to-end/src/e2e_deploy_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,39 @@ describe('e2e_deploy_contract', () => {
expect(await contracts[0].methods.summed_values(owner).view()).toEqual(42n);
expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n);
}, 30_000);

it('refuses to initialize a contract twice', async () => {
const owner = await registerRandomAccount(pxe);
const initArgs: StatefulContractCtorArgs = [owner, 42];
const contract = await registerContract(wallet, StatefulTestContract, initArgs);
await contract.methods
.constructor(...initArgs)
.send()
.wait();
await expect(
contract.methods
.constructor(...initArgs)
.send()
.wait(),
).rejects.toThrow(/dropped/);
});

it('refuses to call a private function that requires initialization', async () => {
const owner = await registerRandomAccount(pxe);
const initArgs: StatefulContractCtorArgs = [owner, 42];
const contract = await registerContract(wallet, StatefulTestContract, initArgs);
// TODO(@spalladino): It'd be nicer to be able to fail the assert with a more descriptive message,
// but the best we can do for now is pushing a read request to the kernel and wait for it to fail.
// Maybe we need an unconstrained check for the read request that runs within the app circuit simulation
// so we can bail earlier with a more descriptive error? I should create an issue for this.
await expect(contract.methods.create_note(owner, 10).send().wait()).rejects.toThrow(
/The read request.*does not match/,
);
});

it('refuses to call a public function that requires initialization', async () => {
// TODO(@spalladino)
});
});

describe('registering a contract class', () => {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/pxe/src/kernel_prover/kernel_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ export class KernelProver {
const result = noteHashes.findIndex(equalToRR);
if (result == -1) {
throw new Error(
`The read request at index ${i} with value ${readRequests[i].toString()} does not match to any commitment.`,
`The read request at index ${i} ${readRequests[i].toString()} does not match to any commitment.`,
);
} else {
hints[i] = new Fr(result);
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,8 @@ describe('Private Execution test suite', () => {
expect(changeNote.note.items[0]).toEqual(new Fr(40n));

const readRequests = sideEffectArrayToValueArray(
nonEmptySideEffects(result.callStackItem.publicInputs.readRequests),
// We remove the first element which is the read request for the initialization commitment
nonEmptySideEffects(result.callStackItem.publicInputs.readRequests.slice(1)),
);

expect(readRequests).toHaveLength(consumedNotes.length);
Expand Down

0 comments on commit 4feaea5

Please sign in to comment.