From 7146f61415ac91b19f6d16ed79ddfd15bd1c716e Mon Sep 17 00:00:00 2001 From: iAmMichaelConnor Date: Sun, 30 Jul 2023 22:43:38 +0000 Subject: [PATCH] feat(noir-contracts): option for get_notes --- .../src/client/client_execution_context.ts | 37 +++- .../src/client/private_execution.test.ts | 2 +- .../client/unconstrained_execution.test.ts | 3 +- .../src/ecdsa_public_key_note.nr | 9 - .../escrow_contract/src/address_note.nr | 9 - .../pending_commitments_contract/src/main.nr | 35 ++- .../src/address_note.nr | 9 - .../pokeable_token_contract/src/main.nr | 20 +- .../pokeable_token_contract/src/storage.nr | 2 +- .../src/public_key_note.nr | 9 - .../zk_token_contract/src/claim_note.nr | 9 - .../src/easy_private_state.nr | 43 ++-- .../noir-aztec/src/note/lifecycle.nr | 44 ++-- .../noir-aztec/src/note/note_getter.nr | 77 ++++--- .../src/note/note_getter_options.nr | 7 +- .../noir-aztec/src/note/note_interface.nr | 2 - .../noir-libs/noir-aztec/src/oracle/notes.nr | 39 ++-- .../noir-aztec/src/state_vars/set.nr | 7 +- .../noir-libs/noir-aztec/src/types.nr | 3 +- .../noir-libs/noir-aztec/src/types/option.nr | 200 ++++++++++++++++++ .../noir-libs/value-note/src/balance_utils.nr | 7 +- .../noir-libs/value-note/src/filter.nr | 5 +- .../noir-libs/value-note/src/utils.nr | 26 ++- .../noir-libs/value-note/src/value_note.nr | 46 +--- 24 files changed, 411 insertions(+), 239 deletions(-) create mode 100644 yarn-project/noir-libs/noir-aztec/src/types/option.nr diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index dc3f275d2d5..2b7c150efc4 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -44,6 +44,8 @@ export class ClientTxExecutionContext { /** The list of nullifiers created in this transaction. The commitment/note which is nullified * might be pending or not (i.e., was generated in a previous transaction) */ private pendingNullifiers: Set = new Set(), + + private log = createDebugLogger('aztec:simulator:client_execution_context'), ) {} /** @@ -146,8 +148,20 @@ export class ClientTxExecutionContext { .join(', ')}`, ); + // TODO: notice, that if we don't have a note in our DB, we don't know how big the preimage needs to be, and so we don't actually know how many dummy notes to return, or big to make those dummy notes, or where to position `is_some` booleans to inform the noir program that _all_ the notes should be dummies. + // By a happy coincidence, a `0` field is interpreted as `is_none`, and since in this case (of an empty db) we'll return all zeros (paddedZeros), the noir program will treat the returned data as all dummies, but this is luck. Perhaps a preimage size should be conveyed by the get_notes noir oracle? + const preimageLength = notes?.[0]?.preimage.length ?? 0; + if ( + !notes.every(({ preimage }) => { + return preimageLength === preimage.length; + }) + ) + throw new Error('Preimages for a particular note type should all be the same length'); + // Combine pending and db preimages into a single flattened array. - const preimages = notes.flatMap(({ nonce, preimage }) => [nonce, ...preimage]); + const isSome = new Fr(1); // Boolean. Indicates whether the Noir Option::is_some(); + + const realNotePreimages = notes.flatMap(({ nonce, preimage }) => [nonce, isSome, ...preimage]); // Add a partial witness for each note. // It contains the note index for db notes. And flagged as transient for pending notes. @@ -157,8 +171,25 @@ export class ClientTxExecutionContext { ); }); - const paddedZeros = Array(Math.max(0, returnSize - 2 - preimages.length)).fill(Fr.ZERO); - return [notes.length, contractAddress, ...preimages, ...paddedZeros].map(v => toACVMField(v)); + const returnHeaderLength = 2; // is for the header values: `notes.length` and `contractAddress`. + const extraPreimageLength = 2; // is for the nonce and isSome fields. + const extendedPreimageLength = preimageLength + extraPreimageLength; + const numRealNotes = notes.length; + const numReturnNotes = Math.floor((returnSize - returnHeaderLength) / extendedPreimageLength); + const numDummyNotes = numReturnNotes - numRealNotes; + + const dummyNotePreimage = Array(extendedPreimageLength).fill(Fr.ZERO); + const dummyNotePreimages = Array(numDummyNotes) + .fill(dummyNotePreimage) + .flatMap(note => note); + + const paddedZeros = Array( + Math.max(0, returnSize - returnHeaderLength - realNotePreimages.length - dummyNotePreimages.length), + ).fill(Fr.ZERO); + + return [notes.length, contractAddress, ...realNotePreimages, ...dummyNotePreimages, ...paddedZeros].map(v => + toACVMField(v), + ); } /** diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 67878fc5b14..04b620895e0 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -173,7 +173,7 @@ describe('Private Execution test suite', () => { const buildNote = (amount: bigint, owner: AztecAddress, storageSlot = Fr.random()) => { const nonce = new Fr(currentNoteIndex); - const preimage = [new Fr(amount), owner.toField(), Fr.random(), new Fr(1n)]; + const preimage = [new Fr(amount), owner.toField(), Fr.random()]; return { contractAddress, storageSlot, index: currentNoteIndex++, nonce, nullifier: new Fr(0), preimage }; }; diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts index e632e45c7f2..bb93bb3cfe1 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts @@ -33,7 +33,7 @@ describe('Unconstrained Execution test suite', () => { let owner: AztecAddress; const buildNote = (amount: bigint, owner: AztecAddress) => { - return [new Fr(amount), owner, Fr.random(), new Fr(1n)]; + return [new Fr(amount), owner, Fr.random()]; }; const calculateAddress = (privateKey: PrivateKey) => { @@ -67,6 +67,7 @@ describe('Unconstrained Execution test suite', () => { contractAddress, storageSlot: Fr.random(), nonce: Fr.random(), + isSome: new Fr(1), preimage, nullifier: Fr.random(), index: BigInt(index), diff --git a/yarn-project/noir-contracts/src/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr b/yarn-project/noir-contracts/src/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr index 47d7810b754..18bdc91e711 100644 --- a/yarn-project/noir-contracts/src/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr +++ b/yarn-project/noir-contracts/src/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr @@ -74,10 +74,6 @@ impl EcdsaPublicKeyNote { header: NoteHeader::empty(), } } - - fn is_dummy(self) -> bool { - (self.x == [0;32]) & (self.y == [0;32]) & (self.owner == 0) - } } fn deserialise(preimage: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { @@ -116,10 +112,6 @@ fn dummy() -> EcdsaPublicKeyNote { EcdsaPublicKeyNote::dummy() } -fn is_dummy(note: EcdsaPublicKeyNote) -> bool { - note.is_dummy() -} - fn get_header(note: EcdsaPublicKeyNote) -> NoteHeader { note.header } @@ -134,7 +126,6 @@ global EcdsaPublicKeyNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, }; diff --git a/yarn-project/noir-contracts/src/contracts/escrow_contract/src/address_note.nr b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/address_note.nr index e2c5b812e48..4757ec80ba3 100644 --- a/yarn-project/noir-contracts/src/contracts/escrow_contract/src/address_note.nr +++ b/yarn-project/noir-contracts/src/contracts/escrow_contract/src/address_note.nr @@ -48,10 +48,6 @@ impl AddressNote { header: NoteHeader::empty(), } } - - fn is_dummy(self) -> bool { - (self.address == 0) & (self.owner == 0) - } } fn deserialise(preimage: [Field; ADDRESS_NOTE_LEN]) -> AddressNote { @@ -78,10 +74,6 @@ fn dummy() -> AddressNote { AddressNote::dummy() } -fn is_dummy(note: AddressNote) -> bool { - note.is_dummy() -} - fn get_header(note: AddressNote) -> NoteHeader { note.header } @@ -96,7 +88,6 @@ global AddressNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, }; diff --git a/yarn-project/noir-contracts/src/contracts/pending_commitments_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/pending_commitments_contract/src/main.nr index 39e9717581f..f85ca157e18 100644 --- a/yarn-project/noir-contracts/src/contracts/pending_commitments_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/pending_commitments_contract/src/main.nr @@ -66,14 +66,15 @@ contract PendingCommitments { let options = NoteGetterOptions::with_filter(get_2_notes, 0); // get note inserted above - let got_notes = owner_balance.get_notes(&mut context, options); + let maybe_notes = owner_balance.get_notes(&mut context, options); - assert(note.value == got_notes[0].value); - assert(!got_notes[1].is_real); + let note0 = maybe_notes[0].unwrap(); + assert(note.value == note0.value); + assert(maybe_notes[1].is_none()); - context.return_values.push(got_notes[0].value); + context.return_values.push(note0.value); - owner_balance.remove(&mut context, got_notes[0]); + owner_balance.remove(&mut context, note0); context.finish() } @@ -94,13 +95,12 @@ contract PendingCommitments { let options = NoteGetterOptions::with_filter(get_2_notes, 0); // get note (note inserted at bottom of function shouldn't exist yet) - let got_notes = owner_balance.get_notes(&mut context, options); + let maybe_notes = owner_balance.get_notes(&mut context, options); - assert(!got_notes[0].is_real); - assert(got_notes[0].value == 0); - assert(!got_notes[1].is_real); + assert(maybe_notes[0].is_none()); + assert(maybe_notes[1].is_none()); - context.return_values.push(got_notes[0].value); + context.return_values.push(0); // Insert note and emit encrypted note preimage via oracle call let mut note = ValueNote::new(amount, owner); @@ -145,14 +145,13 @@ contract PendingCommitments { let owner_balance = storage.balances.at(owner); let options = NoteGetterOptions::with_filter(get_2_notes, 0); - let got_notes = owner_balance.get_notes(&mut context, options); + let note = owner_balance.get_notes(&mut context, options)[0].unwrap(); - assert(expected_value == got_notes[0].value); - assert(!got_notes[1].is_real); + assert(expected_value == note.value); - context.return_values.push(got_notes[0].value); + context.return_values.push(expected_value); - owner_balance.remove(&mut context, got_notes[0]); + owner_balance.remove(&mut context, note); context.finish() } @@ -171,10 +170,10 @@ contract PendingCommitments { let owner_balance = storage.balances.at(owner); let options = NoteGetterOptions::with_filter(get_2_notes, 0); - let got_notes = owner_balance.get_notes(&mut context, options); + let maybe_notes = owner_balance.get_notes(&mut context, options); - assert(!got_notes[0].is_real); - assert(!got_notes[1].is_real); + assert(maybe_notes[0].is_none()); + assert(maybe_notes[1].is_none()); context.finish() } diff --git a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/address_note.nr b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/address_note.nr index 111b4178f75..3c2fe9044ff 100644 --- a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/address_note.nr +++ b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/address_note.nr @@ -46,10 +46,6 @@ impl AddressNote { header: NoteHeader::empty(), } } - - fn is_dummy(self) -> bool { - self.address == 0 - } } fn deserialise(preimage: [Field; ADDRESS_NOTE_LEN]) -> AddressNote { @@ -75,10 +71,6 @@ fn dummy() -> AddressNote { AddressNote::dummy() } -fn is_dummy(note: AddressNote) -> bool { - note.is_dummy() -} - fn get_header(note: AddressNote) -> NoteHeader { note.header } @@ -93,7 +85,6 @@ global AddressNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, }; diff --git a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr index 7ca90f91b8e..a7117046f60 100644 --- a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/main.nr @@ -6,7 +6,7 @@ contract PokeableToken { use dep::value_note::{ balance_utils, utils::{send_note, spend_notes}, - value_note::{VALUE_NOTE_LEN, ValueNoteInterface}, + value_note::{VALUE_NOTE_LEN, ValueNoteInterface, ValueNote}, filter::get_2_notes, }; use dep::aztec::abi; @@ -91,16 +91,22 @@ contract PokeableToken { let sender_balance = storage.balances.at(sender.address); let options = NoteGetterOptions::with_filter(get_2_notes, 0); - let notes = sender_balance.get_notes(&mut context, options); + let maybe_notes = sender_balance.get_notes(&mut context, options); - let note1 = notes[0]; - let note2 = notes[1]; + let note0 = maybe_notes[0].unwrap_or(ValueNote::dummy()); + let note1 = maybe_notes[1].unwrap_or(ValueNote::dummy()); - let note_sum = note1.value + note2.value; + let note_sum = note0.value + note1.value; // Removes the 2 notes from the sender's set of notes. - sender_balance.remove(&mut context, note1); - sender_balance.remove(&mut context, note2); + if maybe_notes[0].is_some() { + assert(sender.address == note0.owner); + sender_balance.remove(&mut context, note0); + } + if maybe_notes[1].is_some() { + assert(sender.address == note1.owner); + sender_balance.remove(&mut context, note1); + } // Create new note for the recipient. let recipient_balance = storage.balances.at(recipient.address); diff --git a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/storage.nr b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/storage.nr index 30a5a3c1f6e..671e0789b32 100644 --- a/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/storage.nr +++ b/yarn-project/noir-contracts/src/contracts/pokeable_token_contract/src/storage.nr @@ -25,7 +25,7 @@ impl Storage { Storage { sender: ImmutableSingleton::new(1, AddressNoteInterface), recipient: ImmutableSingleton::new(2, AddressNoteInterface), - balances: Map::new(1, |s| Set::new(s, ValueNoteInterface)), + balances: Map::new(3, |s| Set::new(s, ValueNoteInterface)), } } } \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_multi_key_account_contract/src/public_key_note.nr b/yarn-project/noir-contracts/src/contracts/schnorr_multi_key_account_contract/src/public_key_note.nr index ab54500684d..aece0c7621f 100644 --- a/yarn-project/noir-contracts/src/contracts/schnorr_multi_key_account_contract/src/public_key_note.nr +++ b/yarn-project/noir-contracts/src/contracts/schnorr_multi_key_account_contract/src/public_key_note.nr @@ -53,10 +53,6 @@ impl PublicKeyNote { header: NoteHeader::empty(), } } - - fn is_dummy(self) -> bool { - (self.x == 0) & (self.y == 0) & (self.owner == 0) - } } fn deserialise(preimage: [Field; PUBLIC_KEY_NOTE_LEN]) -> PublicKeyNote { @@ -84,10 +80,6 @@ fn dummy() -> PublicKeyNote { PublicKeyNote::dummy() } -fn is_dummy(note: PublicKeyNote) -> bool { - note.is_dummy() -} - fn get_header(note: PublicKeyNote) -> NoteHeader { note.header } @@ -102,7 +94,6 @@ global PublicKeyNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, }; diff --git a/yarn-project/noir-contracts/src/contracts/zk_token_contract/src/claim_note.nr b/yarn-project/noir-contracts/src/contracts/zk_token_contract/src/claim_note.nr index 0e156bef1bc..61dbc410eb4 100644 --- a/yarn-project/noir-contracts/src/contracts/zk_token_contract/src/claim_note.nr +++ b/yarn-project/noir-contracts/src/contracts/zk_token_contract/src/claim_note.nr @@ -57,10 +57,6 @@ impl ClaimNote { } } - fn is_dummy(self) -> bool { - self.value == 0 - } - fn set_header(&mut self, header: NoteHeader) { self.header = header; } @@ -86,10 +82,6 @@ fn dummy() -> ClaimNote { ClaimNote::dummy() } -fn is_dummy(note: ClaimNote) -> bool { - note.is_dummy() -} - fn get_header(note: ClaimNote) -> NoteHeader { note.header } @@ -104,7 +96,6 @@ global ClaimNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, }; diff --git a/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr b/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr index 363101c3ef0..60406b0fa21 100644 --- a/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr +++ b/yarn-project/noir-libs/easy-private-state/src/easy_private_state.nr @@ -13,7 +13,10 @@ use dep::aztec::{ note::note_getter_options::NoteGetterOptions, oracle::get_public_key::get_public_key, state_vars::set::Set, - types::point::Point, + types::{ + point::Point, + option::Option, + } }; struct EasyPrivateUint { @@ -66,37 +69,37 @@ impl EasyPrivateUint { owner: Field, ) { let options = NoteGetterOptions::with_filter(get_2_notes, 0); - let notes = self.set.get_notes(context, options); - let note1 = notes[0]; - let note2 = notes[1]; + let maybe_notes = self.set.get_notes(context, options); + + let note0 = maybe_notes[0].unwrap_or(ValueNote::dummy()); + let note1 = maybe_notes[1].unwrap_or(ValueNote::dummy()); // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while // nullifying someone else's notes). - let validate = |note: ValueNote, owner: Field| { - let condition = (owner == note.owner); - assert((!note.is_real) | condition); - }; - - validate(note1, owner); - validate(note2, owner); + if maybe_notes[0].is_some() { + assert(owner == note0.owner); + // Removes the note from the owner's set of notes. + self.set.remove(context, note0); + } + if maybe_notes[1].is_some() { + assert(owner == note1.owner); + // Removes the note from the owner's set of notes. + self.set.remove(context, note1); + } + let note0_value: u120 = note0.value as u120; let note1_value: u120 = note1.value as u120; - let note2_value: u120 = note2.value as u120; - let minuend = note1_value + note2_value; + let minuend = note0_value + note1_value; assert(minuend >= subtrahend); - // Removes the 2 notes from the owner's set of notes. - self.set.remove(context, note1); - self.set.remove(context, note2); - // Creates change note for the owner. - let result = minuend - subtrahend; - let mut result_note = ValueNote::new(result as Field, owner); + let result_value = minuend - subtrahend; + let mut result_note = ValueNote::new(result_value as Field, owner); self.set.insert(context, &mut result_note); // Emit the newly created encrypted note preimages via oracle calls. let mut encrypted_data = [0; VALUE_NOTE_LEN]; - if result_note.is_dummy() == false { + if result_value != 0 { encrypted_data = result_note.serialise(); }; diff --git a/yarn-project/noir-libs/noir-aztec/src/note/lifecycle.nr b/yarn-project/noir-libs/noir-aztec/src/note/lifecycle.nr index ce4e9db4bf9..87aa8047ffb 100644 --- a/yarn-project/noir-libs/noir-aztec/src/note/lifecycle.nr +++ b/yarn-project/noir-libs/noir-aztec/src/note/lifecycle.nr @@ -6,6 +6,7 @@ use crate::note::{ }; use crate::oracle::notes::{notify_created_note, notify_nullified_note}; use crate::constants_gen::EMPTY_NULLIFIED_COMMITMENT; +use crate::types::option::Option; fn create_note( context: &mut Context, @@ -14,18 +15,16 @@ fn create_note( note_interface: NoteInterface, ) { let mut inner_note_hash = 0; - let is_dummy = note_interface.is_dummy; - if is_dummy(*note) == false { - let contract_address = context.inputs.call_context.storage_contract_address; - let header = NoteHeader { contract_address, storage_slot, nonce: 0 }; - let set_header = note_interface.set_header; - set_header(note, header); - inner_note_hash = compute_inner_note_hash(note_interface, *note); + let contract_address = context.inputs.call_context.storage_contract_address; - let serialise = note_interface.serialise; - let preimage = serialise(*note); - assert(notify_created_note(storage_slot, preimage, inner_note_hash) == 0); - } + let header = NoteHeader { contract_address, storage_slot, nonce: 0 }; + let set_header = note_interface.set_header; + set_header(note, header); + inner_note_hash = compute_inner_note_hash(note_interface, *note); + + let serialise = note_interface.serialise; + let preimage = serialise(*note); + assert(notify_created_note(storage_slot, preimage, inner_note_hash) == 0); context.push_new_note_hash(inner_note_hash); } @@ -38,21 +37,16 @@ fn destroy_note( ) { let mut nullifier = 0; let mut nullified_commitment = 0; - let is_dummy = note_interface.is_dummy; - if is_dummy(note) == false { - let compute_nullifier = note_interface.compute_nullifier; - nullifier = compute_nullifier(note); + let compute_nullifier = note_interface.compute_nullifier; + nullifier = compute_nullifier(note); - let serialise = note_interface.serialise; - let preimage = serialise(note); - - // We also need the note commitment corresponding to the "nullifier" - // TODO(suyash): We're re-computing the note commitment, ideally we can reuse the one already computed. - nullified_commitment = compute_inner_note_hash(note_interface, note); - assert(notify_nullified_note(storage_slot, nullifier, preimage, nullified_commitment) == 0); - } else { - nullified_commitment = EMPTY_NULLIFIED_COMMITMENT; - } + let serialise = note_interface.serialise; + let preimage = serialise(note); + + // We also need the note commitment corresponding to the "nullifier" + // TODO(suyash): We're re-computing the note commitment, ideally we can reuse the one already computed. + nullified_commitment = compute_inner_note_hash(note_interface, note); + assert(notify_nullified_note(storage_slot, nullifier, preimage, nullified_commitment) == 0); context.push_new_nullifier(nullifier, nullified_commitment) } \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/note/note_getter.nr b/yarn-project/noir-libs/noir-aztec/src/note/note_getter.nr index 3ba4b62c7df..33088b0af86 100644 --- a/yarn-project/noir-libs/noir-aztec/src/note/note_getter.nr +++ b/yarn-project/noir-libs/noir-aztec/src/note/note_getter.nr @@ -12,6 +12,7 @@ use crate::note::{ utils::compute_unique_note_hash, }; use crate::oracle; +use crate::types::option::Option; fn check_note_header( context: Context, @@ -32,23 +33,18 @@ fn ensure_note_exists( note_interface: NoteInterface, note: &mut Note, ) { - let mut unique_note_hash = 0; - let is_dummy = note_interface.is_dummy; - if is_dummy(*note) == false { - // Get a note from oracle and early out if it doesn't exist. - let saved_note = get_note_internal(storage_slot, note_interface); - assert(is_dummy(saved_note) == false); + let saved_note = get_note_internal(storage_slot, note_interface); - // Only copy over the header to the original note to make sure the preimage is the same. - let get_header = note_interface.get_header; - let set_header = note_interface.set_header; - let note_header = get_header(saved_note); - set_header(note, note_header); + // Only copy over the header to the original note to make sure the preimage is the same. + let get_header = note_interface.get_header; + let set_header = note_interface.set_header; + let note_header = get_header(saved_note); + set_header(note, note_header); - check_note_header(*context, storage_slot, note_interface, *note); + check_note_header(*context, storage_slot, note_interface, *note); + + let unique_note_hash = compute_unique_note_hash(note_interface, *note); - unique_note_hash = compute_unique_note_hash(note_interface, *note); - }; context.push_read_request(unique_note_hash); } @@ -58,12 +54,11 @@ fn get_note( note_interface: NoteInterface, ) -> Note { let note = get_note_internal(storage_slot, note_interface); - let mut unique_note_hash = 0; - let is_dummy = note_interface.is_dummy; - if is_dummy(note) == false { - check_note_header(*context, storage_slot, note_interface, note); - unique_note_hash = compute_unique_note_hash(note_interface, note); - }; + + check_note_header(*context, storage_slot, note_interface, note); + + let unique_note_hash = compute_unique_note_hash(note_interface, note); + context.push_read_request(unique_note_hash); note } @@ -73,19 +68,19 @@ fn get_notes( storage_slot: Field, note_interface: NoteInterface, options: NoteGetterOptions, -) -> [Note; S] { - let notes = get_notes_internal(storage_slot, note_interface, options); - let is_dummy = note_interface.is_dummy; - for i in 0..notes.len() { - let note = notes[i]; +) -> [Option; S] { + let opt_notes = get_notes_internal(storage_slot, note_interface, options); + for i in 0..opt_notes.len() { + let opt_note = opt_notes[i]; let mut unique_note_hash = 0; - if is_dummy(note) == false { + if opt_note.is_some() { + let note = opt_note.unwrap_unchecked(); check_note_header(*context, storage_slot, note_interface, note); unique_note_hash = compute_unique_note_hash(note_interface, note); }; context.push_read_request(unique_note_hash); }; - notes + opt_notes } unconstrained fn get_note_internal( @@ -93,8 +88,8 @@ unconstrained fn get_note_internal( note_interface: NoteInterface, ) -> Note { let dummy = note_interface.dummy; - let dummy_note = [dummy()]; - let zero_fields = [0; GET_NOTE_ORACLE_RETURN_LENGTH]; + let placeholder_note = [Option::none(dummy())]; + let placeholder_fields = [0; GET_NOTE_ORACLE_RETURN_LENGTH]; oracle::notes::get_notes( storage_slot, note_interface, @@ -102,16 +97,16 @@ unconstrained fn get_note_internal( [], 1, // limit 0, // offset - dummy_note, - zero_fields, - )[0] + placeholder_note, + placeholder_fields, + )[0].unwrap() // Notice: we don't allow dummies to be returned from get_note (singular). } unconstrained fn get_notes_internal( storage_slot: Field, note_interface: NoteInterface, options: NoteGetterOptions, -) -> [Note; S] { +) -> [Option; S] { let dummy = note_interface.dummy; let sort_by = options.sort_by; let mut sort_by_indices = [0; N]; @@ -120,22 +115,22 @@ unconstrained fn get_notes_internal( sort_by_indices[i] = sort_by[i].field_index; sort_order[i] = sort_by[i].order; }; - let dummy_notes = [dummy(); MAX_READ_REQUESTS_PER_CALL]; - let zero_fields = [0; GET_NOTES_ORACLE_RETURN_LENGTH]; - let notes = oracle::notes::get_notes( + let placeholder_opt_notes = [Option::none(dummy()); MAX_READ_REQUESTS_PER_CALL]; + let placeholder_fields = [0; GET_NOTES_ORACLE_RETURN_LENGTH]; + let opt_notes = oracle::notes::get_notes( storage_slot, note_interface, sort_by_indices, sort_order, MAX_READ_REQUESTS_PER_CALL as u32, options.offset, - dummy_notes, - zero_fields, + placeholder_opt_notes, + placeholder_fields, ); let filter = options.filter; let filter_args = options.filter_args; - filter(notes, filter_args) + filter(opt_notes, filter_args) } unconstrained fn view_notes( @@ -143,11 +138,11 @@ unconstrained fn view_notes( note_interface: NoteInterface, limit: u32, offset: u32, -) -> [Note; MAX_NOTES_PER_PAGE] { +) -> [Option; MAX_NOTES_PER_PAGE] { let dummy = note_interface.dummy; let sort_by = [0; N]; let sort_order = [0; N]; - let dummy_notes = [dummy(); MAX_NOTES_PER_PAGE]; + let dummy_notes = [Option::none(dummy()); MAX_NOTES_PER_PAGE]; let zero_fields = [0; VIEW_NOTE_ORACLE_RETURN_LENGTH]; oracle::notes::get_notes(storage_slot, note_interface, sort_by, sort_order, limit, offset, dummy_notes, zero_fields) } \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/note/note_getter_options.nr b/yarn-project/noir-libs/noir-aztec/src/note/note_getter_options.nr index 8e9158473d9..6ed9d5ac972 100644 --- a/yarn-project/noir-libs/noir-aztec/src/note/note_getter_options.nr +++ b/yarn-project/noir-libs/noir-aztec/src/note/note_getter_options.nr @@ -1,4 +1,5 @@ use crate::constants_gen::MAX_READ_REQUESTS_PER_CALL; +use crate::types::option::Option; struct SortOrderEnum { DESC: u8, @@ -30,13 +31,13 @@ impl Sort { struct NoteGetterOptions { sort_by: [Sort; N], offset: u32, - filter: fn ([Note; MAX_READ_REQUESTS_PER_CALL], P) -> [Note; S], + filter: fn ([Option; MAX_READ_REQUESTS_PER_CALL], P) -> [Option; S], filter_args: P, } impl NoteGetterOptions { fn new( - filter: fn ([Note; MAX_READ_REQUESTS_PER_CALL], P) -> [Note; S], + filter: fn ([Option; MAX_READ_REQUESTS_PER_CALL], P) -> [Option; S], filter_args: P, sort_by: [Sort; N], offset: u32, @@ -50,7 +51,7 @@ impl NoteGetterOptions { } fn with_filter( - filter: fn ([Note; MAX_READ_REQUESTS_PER_CALL], P) -> [Note; S], + filter: fn ([Option; MAX_READ_REQUESTS_PER_CALL], P) -> [Option; S], filter_args: P, ) -> Self { NoteGetterOptions { diff --git a/yarn-project/noir-libs/noir-aztec/src/note/note_interface.nr b/yarn-project/noir-libs/noir-aztec/src/note/note_interface.nr index 4f073e30a7c..54f7c78849a 100644 --- a/yarn-project/noir-libs/noir-aztec/src/note/note_interface.nr +++ b/yarn-project/noir-libs/noir-aztec/src/note/note_interface.nr @@ -11,8 +11,6 @@ struct NoteInterface { dummy: fn () -> Note, - is_dummy: fn (Note) -> bool, - get_header: fn (Note) -> NoteHeader, set_header: fn (&mut Note, NoteHeader) -> (), diff --git a/yarn-project/noir-libs/noir-aztec/src/oracle/notes.nr b/yarn-project/noir-libs/noir-aztec/src/oracle/notes.nr index 3b0f58ceca1..9deeadcea62 100644 --- a/yarn-project/noir-libs/noir-aztec/src/oracle/notes.nr +++ b/yarn-project/noir-libs/noir-aztec/src/oracle/notes.nr @@ -3,6 +3,7 @@ use crate::note::{ note_interface::NoteInterface, }; use crate::utils::arr_copy_slice; +use crate::types::option::Option; #[oracle(notifyCreatedNote)] fn notify_created_note_oracle( @@ -44,7 +45,7 @@ fn get_notes_oracle( _limit: u32, _offset: u32, _return_size: u32, - _zero_fields: [Field; S], + _placeholder_fields: [Field; S], ) -> [Field; S] {} unconstrained fn get_notes_oracle_wrapper( @@ -53,10 +54,10 @@ unconstrained fn get_notes_oracle_wrapper( sort_order: [u8; N], limit: u32, offset: u32, - mut fields: [Field; S], + mut placeholder_fields: [Field; S], )-> [Field; S] { - let return_size = fields.len() as u32; - get_notes_oracle(storage_slot, sort_by, sort_order, limit, offset, return_size, fields) + let return_size = placeholder_fields.len() as u32; + get_notes_oracle(storage_slot, sort_by, sort_order, limit, offset, return_size, placeholder_fields) } unconstrained fn get_notes( @@ -66,28 +67,32 @@ unconstrained fn get_notes( sort_order: [u8; M], limit: u32, offset: u32, - mut notes: [Note; S], // TODO: Remove it and use `limit` to initialise the note array. - zero_fields: [Field; NS], // TODO: Remove it and use `limit` to initialise the note array. -) -> [Note; S] { - let fields = get_notes_oracle_wrapper(storage_slot, sort_by, sort_order, limit, offset, zero_fields); + mut placeholder_opt_notes: [Option; S], // TODO: Remove it and use `limit` to initialise the note array. + placeholder_fields: [Field; NS], // TODO: Remove it and use `limit` to initialise the note array. +) -> [Option; S] { + let fields = get_notes_oracle_wrapper(storage_slot, sort_by, sort_order, limit, offset, placeholder_fields); let num_notes = fields[0] as u32; let contract_address = fields[1]; let deserialise = note_interface.deserialise; let set_header = note_interface.set_header; - for i in 0..notes.len() { + for i in 0..placeholder_opt_notes.len() { if i as u32 < num_notes { - let read_offset: comptime Field = 2 + i * (N + 1); + // comptime lengths named as per typescript. + let return_header_length: comptime Field = 2; // num_notes & contract_address. + let extra_preimage_length: comptime Field = 2; // nonce & is_some. + let read_offset: comptime Field = return_header_length + i * (N + extra_preimage_length); let nonce = fields[read_offset]; let header = NoteHeader { contract_address, nonce, storage_slot }; - - let preimage = arr_copy_slice(fields, [0; N], read_offset + 1); - let mut note = deserialise(preimage); - - set_header(&mut note, header); - notes[i] = note; + let is_some = fields[read_offset + 1] as bool; + if is_some { + let preimage = arr_copy_slice(fields, [0; N], read_offset + 2); + let mut note = deserialise(preimage); + set_header(&mut note, header); + placeholder_opt_notes[i] = Option::some(note); + } }; }; - notes + placeholder_opt_notes } unconstrained fn is_nullifier_emitted(nullifier: Field) -> bool { diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars/set.nr b/yarn-project/noir-libs/noir-aztec/src/state_vars/set.nr index df3025a6e76..2f5066ceb0c 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars/set.nr +++ b/yarn-project/noir-libs/noir-aztec/src/state_vars/set.nr @@ -6,6 +6,7 @@ use crate::note::{ note_interface::NoteInterface, }; use crate::oracle::create_commitment::create_commitment; +use crate::types::option::Option; struct Set { storage_slot: Field, @@ -39,9 +40,9 @@ impl Set { self, context: &mut Context, options: NoteGetterOptions, - ) -> [Note; S] { + ) -> [Option; S] { let storage_slot = self.storage_slot; - let notes = get_notes(context, storage_slot, self.note_interface, options); - notes + let opt_notes = get_notes(context, storage_slot, self.note_interface, options); + opt_notes } } diff --git a/yarn-project/noir-libs/noir-aztec/src/types.nr b/yarn-project/noir-libs/noir-aztec/src/types.nr index 1d543caba37..4614a0c05bd 100644 --- a/yarn-project/noir-libs/noir-aztec/src/types.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types.nr @@ -1,2 +1,3 @@ mod point; -mod vec; // This can/should be moved out into another library \ No newline at end of file +mod vec; // This can/should be moved out into an official noir library +mod option; // This can/should be moved out into an official noir library \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/types/option.nr b/yarn-project/noir-libs/noir-aztec/src/types/option.nr new file mode 100644 index 00000000000..a9d25aacbba --- /dev/null +++ b/yarn-project/noir-libs/noir-aztec/src/types/option.nr @@ -0,0 +1,200 @@ +// TODO: this will soon be a part of the Noir stdlib, so we can remove this file, eventually: https://github.com/noir-lang/noir/pull/1781 + +struct Option { + _is_some: bool, + _value: T, +} + +impl Option { + // We'll be wrapping custom structs in the Option type, so we don't want to use the unsafe `zeroed` function. Instead, we'll use constructors which allow the caller to hint at what an 'empty' `T` is. + + // fn none() -> Self { + // Self { _is_some: false, value: crate::unsafe::zeroed() } + // } + + // fn some(value: T) -> Self { + // Self { _is_some: true, value } + // } + + fn none(none_value: T) -> Self { + Option { + _is_some: false, + _value: none_value, + } + } + + fn some(value: T) -> Self { + Option { + _is_some: true, + _value: value, + } + } + + fn is_none(self) -> bool { + !self._is_some + } + + fn is_some(self) -> bool { + self._is_some + } + + // Returns the contained Some value, consuming the self value, without checking that the value is not None. + // Safety: + // Calling this method on None is undefined behavior. + fn unwrap_unchecked(self) -> T { + self._value + } + + fn unwrap(self) -> T { + assert(self._is_some); + self._value + } + + fn unwrap_or(self, default: T) -> T { + if self._is_some { + self._value + } else { + default + } + } + + fn unwrap_or_else(self, default: fn() -> T) -> T { + if self._is_some { + self._value + } else { + default() + } + } + + // Maps an Option to Option by applying a function to a contained value (if Some) or returns None (if None). + fn map(self, f: fn(T) -> U, none_u: U) -> Option { + if self._is_some { + Option::some(f(self._value)) + } else { + Option::none(none_u) + } + } + + // Returns the provided default result (if none), or applies a function to the contained value (if any). + fn map_or(self, default: U, f: fn(T) -> U) -> U { + if self._is_some { + f(self._value) + } else { + default + } + } + + // Computes a default function result (if none), or applies a different function to the contained value (if any). + fn map_or_else(self, default: fn() -> U, f: fn(T) -> U) -> U { + if self._is_some { + f(self._value) + } else { + default() + } + } + + // Returns None if the option is None, otherwise returns other. + fn and(self, other: Self) -> Self { + if self.is_none() { + // Option::none() + self + } else { + other + } + } + + // Returns None if the option is None, otherwise calls f with the wrapped value and returns the result. + fn and_then(self, f: fn(T) -> Option, none_u: U) -> Option { + if self._is_some { + f(self._value) + } else { + Option::none(none_u) + } + } + + // Returns the option if it contains a value, otherwise returns other. + fn or(self, other: Self) -> Self { + if self._is_some { + self + } else { + other + } + } + + // Returns the option if it contains a value, otherwise calls f and returns the result. + fn or_else(self, f: fn() -> Option) -> Option { + if self._is_some { + self + } else { + f() + } + } + + fn xor(self, other: Self, none_value: T) -> Self { + if self._is_some { + if other._is_some { + Option::none(none_value) + } else { + self + } + } else if other._is_some { + other + } else { + // Option::none() + self + } + } +} + +#[test] +fn test_option() { + let none = Option::none(0); + let some = Option::some(3); + + assert(none.is_none()); + assert(some.is_some()); + + assert(some.unwrap() == 3); + + assert(none.unwrap_or(2) == 2); + assert(some.unwrap_or(2) == 3); + + assert(none.unwrap_or_else(|| 5) == 5); + assert(some.unwrap_or_else(|| 5) == 3); + + assert(none.map(|x| x * 2, 0).is_none()); + assert(some.map(|x| x * 2, 0).unwrap() == 6); + + assert(none.map_or(0, |x| x * 2) == 0); + assert(some.map_or(0, |x| x * 2) == 6); + + assert(none.map_or_else(|| 0, |x| x * 2) == 0); + assert(some.map_or_else(|| 0, |x| x * 2) == 6); + + assert(none.and(none).is_none()); + assert(none.and(some).is_none()); + assert(some.and(none).is_none()); + assert(some.and(some).is_some()); + + let add1_u64 = |value: Field| Option::some(value as u64 + 1); + + assert(none.and_then(|_value| Option::none(0), 0).is_none()); + assert(none.and_then(add1_u64, 0).is_none()); + assert(some.and_then(|_value| Option::none(0), 0).is_none()); + assert(some.and_then(add1_u64, 0).unwrap() == 4); + + assert(none.or(none).is_none()); + assert(none.or(some).is_some()); + assert(some.or(none).is_some()); + assert(some.or(some).is_some()); + + assert(none.or_else(|| Option::none(0)).is_none()); + assert(none.or_else(|| Option::some(5)).is_some()); + assert(some.or_else(|| Option::none(0)).is_some()); + assert(some.or_else(|| Option::some(5)).is_some()); + + assert(none.xor(none, 0).is_none()); + assert(none.xor(some, 0).is_some()); + assert(some.xor(none, 0).is_some()); + assert(some.xor(some, 0).is_none()); +} \ No newline at end of file diff --git a/yarn-project/noir-libs/value-note/src/balance_utils.nr b/yarn-project/noir-libs/value-note/src/balance_utils.nr index e292a7be96e..b7ad04140f7 100644 --- a/yarn-project/noir-libs/value-note/src/balance_utils.nr +++ b/yarn-project/noir-libs/value-note/src/balance_utils.nr @@ -11,10 +11,11 @@ unconstrained fn get_balance(storage_slot: Field) -> Field { unconstrained fn get_balance_internal(storage_slot: Field, limit: u32, offset: u32) -> Field { let mut balance = 0; - let notes = view_notes(storage_slot, ValueNoteInterface, limit, offset); - let len = notes.len(); + let opt_notes = view_notes(storage_slot, ValueNoteInterface, limit, offset); + let len = opt_notes.len(); + let dummy = ValueNoteInterface.dummy; for i in 0..len { - balance += notes[i].value; + balance += opt_notes[i].unwrap_or(dummy()).value; } // TODO // if (notes[len - 1].is_dummy()) { diff --git a/yarn-project/noir-libs/value-note/src/filter.nr b/yarn-project/noir-libs/value-note/src/filter.nr index 403a5f7f923..04c7488dd32 100644 --- a/yarn-project/noir-libs/value-note/src/filter.nr +++ b/yarn-project/noir-libs/value-note/src/filter.nr @@ -1,10 +1,11 @@ use dep::aztec::constants_gen::MAX_READ_REQUESTS_PER_CALL; use crate::value_note::ValueNote; +use dep::aztec::types::option::Option; -fn get_2_notes

(notes: [ValueNote; MAX_READ_REQUESTS_PER_CALL], _x: P) -> [ValueNote; 2] { +fn get_2_notes

(notes: [Option; MAX_READ_REQUESTS_PER_CALL], _x: P) -> [Option; 2] { [notes[0], notes[1]] } -fn get_all_notes

(notes: [ValueNote; MAX_READ_REQUESTS_PER_CALL], _x: P) -> [ValueNote; MAX_READ_REQUESTS_PER_CALL] { +fn get_all_notes

(notes: [Option; MAX_READ_REQUESTS_PER_CALL], _x: P) -> [Option; MAX_READ_REQUESTS_PER_CALL] { notes } \ No newline at end of file diff --git a/yarn-project/noir-libs/value-note/src/utils.nr b/yarn-project/noir-libs/value-note/src/utils.nr index d48cd84c9fb..3ff4299bbc4 100644 --- a/yarn-project/noir-libs/value-note/src/utils.nr +++ b/yarn-project/noir-libs/value-note/src/utils.nr @@ -16,22 +16,26 @@ fn spend_notes( owner: Field, ) { let options = NoteGetterOptions::with_filter(get_2_notes, 0); - let notes = balance.get_notes(context, options); - let note1 = notes[0]; - let note2 = notes[1]; + let maybe_notes = balance.get_notes(context, options); + + let note0 = maybe_notes[0].unwrap_or(ValueNote::dummy()); + let note1 = maybe_notes[1].unwrap_or(ValueNote::dummy()); // Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while // spending someone else's notes). - note1.validate(owner); - note2.validate(owner); + if maybe_notes[0].is_some() { + assert(owner == note0.owner); + // Removes the note from the owner's set of notes. + balance.remove(context, note0); + } + if maybe_notes[1].is_some() { + assert(owner == note1.owner); + balance.remove(context, note1); + } - let sum = note1.value + note2.value; + let sum = note0.value + note1.value; assert(sum as u120 >= amount as u120); - // Removes the 2 notes from the owner's set of notes. - balance.remove(context, note1); - balance.remove(context, note2); - // Creates change note for the owner. let change_value = sum - amount; let mut change_note = ValueNote::new(change_value, owner); @@ -39,7 +43,7 @@ fn spend_notes( // Emit the newly created encrypted note preimages via oracle calls. let mut encrypted_data = [0; VALUE_NOTE_LEN]; - if change_note.is_dummy() == false { + if change_value != 0 { encrypted_data = change_note.serialise(); }; diff --git a/yarn-project/noir-libs/value-note/src/value_note.nr b/yarn-project/noir-libs/value-note/src/value_note.nr index 596dd15f0a9..cbe0537139c 100644 --- a/yarn-project/noir-libs/value-note/src/value_note.nr +++ b/yarn-project/noir-libs/value-note/src/value_note.nr @@ -10,32 +10,29 @@ use dep::aztec::oracle::{ }; use dep::aztec::types::point::Point; -global VALUE_NOTE_LEN: Field = 4; +global VALUE_NOTE_LEN: Field = 3; // 3 plus a header. struct ValueNote { value: Field, owner: Field, randomness: Field, - is_real: bool, header: NoteHeader, } impl ValueNote { fn new(value: Field, owner: Field) -> Self { let randomness = rand(); - let is_real = value != 0; let header = NoteHeader::empty(); ValueNote { value, owner, randomness, - is_real, header, } } fn serialise(self) -> [Field; VALUE_NOTE_LEN] { - [self.value, self.owner, self.randomness, self.is_real as Field] + [self.value, self.owner, self.randomness] } fn deserialise(preimage: [Field; VALUE_NOTE_LEN]) -> Self { @@ -43,7 +40,6 @@ impl ValueNote { value: preimage[0], owner: preimage[1], randomness: preimage[2], - is_real: preimage[3] as bool, header: NoteHeader::empty(), } } @@ -53,24 +49,19 @@ impl ValueNote { self.value, self.owner, self.randomness, - self.is_real as Field, ])[0] } fn compute_nullifier(self) -> Field { - if (!self.is_real) { - 0 - } else { - let siloed_note_hash = compute_siloed_note_hash(ValueNoteInterface, self); - let owner_nullifying_public_key = get_public_key(self.owner); - // TODO: get_secret_key should just accept an address - // TODO! - let secret = get_secret_key(owner_nullifying_public_key); - dep::std::hash::pedersen([ - siloed_note_hash, - secret, - ])[0] - } + let siloed_note_hash = compute_siloed_note_hash(ValueNoteInterface, self); + let owner_nullifying_public_key = get_public_key(self.owner); + // TODO: get_secret_key should just accept an address + // TODO! + let secret = get_secret_key(owner_nullifying_public_key); + dep::std::hash::pedersen([ + siloed_note_hash, + secret, + ])[0] } fn dummy() -> Self { @@ -78,23 +69,13 @@ impl ValueNote { value: 0, owner: 0, randomness: 0, - is_real: false, header: NoteHeader::empty(), } } - fn is_dummy(self) -> bool { - !self.is_real - } - fn set_header(&mut self, header: NoteHeader) { self.header = header; } - - fn validate(self, sender: Field) { - let sender_equals = sender == self.owner; - assert((!self.is_real) | sender_equals); - } } fn deserialise(preimage: [Field; VALUE_NOTE_LEN]) -> ValueNote { @@ -117,10 +98,6 @@ fn dummy() -> ValueNote { ValueNote::dummy() } -fn is_dummy(note: ValueNote) -> bool { - note.is_dummy() -} - fn get_header(note: ValueNote) -> NoteHeader { note.header } @@ -135,7 +112,6 @@ global ValueNoteInterface = NoteInterface { compute_note_hash, compute_nullifier, dummy, - is_dummy, get_header, set_header, };