From 95c6b4af7db62162935b123277cfae51c23d30d3 Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Wed, 15 May 2024 10:54:25 +0200 Subject: [PATCH] feat: add nullifying key to Token Note (#6130) --- docs/docs/misc/migration_notes.md | 15 ++++++ .../aztec/src/context/private_context.nr | 38 ++++++++++++--- .../aztec-nr/aztec/src/keys/getters.nr | 17 ++++++- .../aztec-nr/aztec/src/oracle/keys.nr | 22 +++++++++ .../aztec/src/oracle/nullifier_key.nr | 24 ++++++++++ .../token_contract/src/types/balances_map.nr | 9 ++-- .../token_contract/src/types/token_note.nr | 35 +++++++------- .../circuit-types/src/keys/key_store.ts | 12 ++--- .../src/benchmarks/bench_proving.test.ts | 2 +- .../end-to-end/src/e2e_2_pxes.test.ts | 1 + .../writing_an_account_contract.test.ts | 6 +-- yarn-project/key-store/src/test_key_store.ts | 46 ++++++++++++++----- yarn-project/p2p/package.json | 2 +- .../pxe/src/database/kv_pxe_database.ts | 22 +++++++-- yarn-project/pxe/src/database/pxe_database.ts | 8 ++-- .../pxe/src/pxe_service/pxe_service.ts | 8 ---- .../pxe/src/simulator_oracle/index.ts | 13 +++--- .../simulator/src/acvm/oracle/oracle.ts | 21 +++++++++ .../simulator/src/acvm/oracle/typed_oracle.ts | 4 +- .../simulator/src/client/db_oracle.ts | 21 ++++----- .../src/client/private_execution.test.ts | 40 ++++++++-------- .../client/unconstrained_execution.test.ts | 6 +-- .../simulator/src/client/view_data_oracle.ts | 24 +++++----- 23 files changed, 276 insertions(+), 120 deletions(-) diff --git a/docs/docs/misc/migration_notes.md b/docs/docs/misc/migration_notes.md index 6c117f69099..fdec5f66bb5 100644 --- a/docs/docs/misc/migration_notes.md +++ b/docs/docs/misc/migration_notes.md @@ -8,6 +8,21 @@ Aztec is in full-speed development. Literally every version breaks compatibility ## TBD +### [Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address. + +i.e. + +struct TokenNote { + amount: U128, + ```diff + - owner: AztecAddress, + + npk_m_hash: Field, + ``` + randomness: Field, +} + +Computing the nullifier similarly changes to use this master nullifying public key hash. + ### [Aztec.nr] Debug logging The function `debug_log_array_with_prefix` has been removed. Use `debug_log_format` with `{}` instead. The special sequence `{}` will be replaced with the whole array. You can also use `{0}`, `{1}`, ... as usual with `debug_log_format`. diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index 9579b1cd46d..ac174898373 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -3,14 +3,13 @@ use crate::{ messaging::process_l1_to_l2_message, hash::{hash_args_array, ArgsHasher, compute_encrypted_log_hash, compute_unencrypted_log_hash}, oracle::{ - arguments, returns, call_private_function::call_private_function_internal, + arguments, returns, call_private_function::call_private_function_internal, header::get_header_at, + logs::emit_encrypted_log, logs_traits::{LensForEncryptedLog, ToBytesForUnencryptedLog}, + nullifier_key::{get_nullifier_keys, get_nullifier_keys_with_npk_m_hash, NullifierKeys}, enqueue_public_function_call::{ enqueue_public_function_call_internal, set_public_teardown_function_call_internal, parse_public_call_stack_item_from_oracle -}, - header::get_header_at, logs::emit_encrypted_log, - logs_traits::{LensForEncryptedLog, ToBytesForUnencryptedLog}, - nullifier_key::{get_nullifier_keys, NullifierKeys} +} } }; use dep::protocol_types::{ @@ -29,8 +28,10 @@ use dep::protocol_types::{ MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_ENCRYPTED_LOGS_PER_CALL, MAX_UNENCRYPTED_LOGS_PER_CALL }, - grumpkin_point::GrumpkinPoint, header::Header, messaging::l2_to_l1_message::L2ToL1Message, - traits::{is_empty, Deserialize, Empty} + contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, + grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint, header::Header, + messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader, + traits::{is_empty, Deserialize, Empty}, hash::poseidon2_hash }; // When finished, one can call .finish() to convert back to the abi @@ -227,6 +228,29 @@ impl PrivateContext { keys.app_nullifier_secret_key } + // TODO(#5630) Replace request_app_nullifier_secret_key above with this once we no longer get app nullifier secret key with address + pub fn request_nsk_app_with_npk_m_hash(&mut self, npk_m_hash: Field) -> Field { + let keys = if self.nullifier_key.is_none() { + let keys = get_nullifier_keys_with_npk_m_hash(npk_m_hash); + let request = NullifierKeyValidationRequest { + master_nullifier_public_key: keys.master_nullifier_public_key, + app_nullifier_secret_key: keys.app_nullifier_secret_key + }; + self.nullifier_key_validation_requests.push(request); + self.nullifier_key = Option::some(keys); + keys + } else { + let keys = self.nullifier_key.unwrap_unchecked(); + // If MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL is larger than 1, need to update the way the key pair is cached. + assert(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL == 1); + keys + }; + + // We have to check if the key that was requested or cached corresponds to the one we request for + assert_eq(poseidon2_hash(keys.master_nullifier_public_key.serialize()), npk_m_hash); + keys.app_nullifier_secret_key + } + // docs:start:context_message_portal pub fn message_portal(&mut self, recipient: EthAddress, content: Field) { // docs:end:context_message_portal diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters.nr b/noir-projects/aztec-nr/aztec/src/keys/getters.nr index 543a3458d13..41be7f71073 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters.nr @@ -1,6 +1,10 @@ -use dep::protocol_types::{address::AztecAddress, constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint}; +use dep::protocol_types::{ + address::AztecAddress, constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint, + hash::poseidon2_hash +}; use crate::{ - context::PrivateContext, oracle::keys::get_public_keys_and_partial_address, + context::PrivateContext, + oracle::keys::{get_public_keys_and_partial_address, get_public_keys_and_partial_address_with_npk_m_hash}, keys::public_keys::{PublicKeys, NULLIFIER_INDEX, INCOMING_INDEX}, state_vars::{ map::derive_storage_slot_in_map, @@ -14,6 +18,10 @@ pub fn get_npk_m(context: &mut PrivateContext, address: AztecAddress) -> Grumpki get_master_key(context, address, NULLIFIER_INDEX) } +pub fn get_npk_m_hash(context: &mut PrivateContext, address: AztecAddress) -> Field { + poseidon2_hash(get_master_key(context, address, NULLIFIER_INDEX).serialize()) +} + pub fn get_ivpk_m(context: &mut PrivateContext, address: AztecAddress) -> GrumpkinPoint { get_master_key(context, address, INCOMING_INDEX) } @@ -80,3 +88,8 @@ fn fetch_and_constrain_keys(address: AztecAddress) -> PublicKeys { public_keys } + +pub fn get_ivpk_m_with_npk_m_hash(npk_m_hash: Field) -> GrumpkinPoint { + let result = get_public_keys_and_partial_address_with_npk_m_hash(npk_m_hash); + result.0.ivpk_m +} diff --git a/noir-projects/aztec-nr/aztec/src/oracle/keys.nr b/noir-projects/aztec-nr/aztec/src/oracle/keys.nr index c3c191b6ba5..451d9bbff63 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/keys.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/keys.nr @@ -22,3 +22,25 @@ fn get_public_keys_and_partial_address(address: AztecAddress) -> (PublicKeys, Pa (keys, partial_address) } + +#[oracle(getPublicKeysAndPartialAddressWithNpkMHash)] +fn get_public_keys_and_partial_address_with_npk_m_hash_oracle(_npk_m_hash: Field) -> [Field; 9] {} + +unconstrained fn get_public_keys_and_partial_address_with_npk_m_hash_oracle_wrapper(npk_m_hash: Field) -> [Field; 9] { + get_public_keys_and_partial_address_with_npk_m_hash_oracle(npk_m_hash) +} + +fn get_public_keys_and_partial_address_with_npk_m_hash(npk_m_hash: Field) -> (PublicKeys, PartialAddress) { + let result = get_public_keys_and_partial_address_with_npk_m_hash_oracle_wrapper(npk_m_hash); + + let keys = PublicKeys { + npk_m: GrumpkinPoint::new(result[0], result[1]), + ivpk_m: GrumpkinPoint::new(result[2], result[3]), + ovpk_m: GrumpkinPoint::new(result[4], result[5]), + tpk_m: GrumpkinPoint::new(result[6], result[7]) + }; + + let partial_address = PartialAddress::from_field(result[8]); + + (keys, partial_address) +} diff --git a/noir-projects/aztec-nr/aztec/src/oracle/nullifier_key.nr b/noir-projects/aztec-nr/aztec/src/oracle/nullifier_key.nr index 39282a12e2a..c15e36bc6db 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/nullifier_key.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/nullifier_key.nr @@ -2,6 +2,7 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint, // Nullifier keys pertaining to a specific account struct NullifierKeys { + // TODO(#5630): Replace get_nullifier_keys above with this once we no longer get nullifier keys with address account: AztecAddress, master_nullifier_public_key: GrumpkinPoint, app_nullifier_secret_key: Field, @@ -26,3 +27,26 @@ pub fn get_nullifier_keys(account: AztecAddress) -> NullifierKeys { pub fn get_app_nullifier_secret_key(account: AztecAddress) -> Field { get_nullifier_keys_internal(account).app_nullifier_secret_key } + +// TODO(#5630): Replace get_nullifier_keys above with this once we no longer get nullifier keys with address +#[oracle(getNullifierKeysWithNpkMHash)] +fn get_nullifier_keys_with_npk_m_hash_oracle(_npk_m_hash: Field) -> [Field; 3] {} + +unconstrained fn get_nullifier_keys_with_npk_m_hash_internal(npk_m_hash: Field) -> NullifierKeys { + let result = get_nullifier_keys_with_npk_m_hash_oracle(npk_m_hash); + NullifierKeys { + account: AztecAddress::zero(), + master_nullifier_public_key: GrumpkinPoint { x: result[0], y: result[1] }, + app_nullifier_secret_key: result[2] + } +} + +// We get the full struct Nullifier Keys here +pub fn get_nullifier_keys_with_npk_m_hash(npk_m_hash: Field) -> NullifierKeys { + get_nullifier_keys_with_npk_m_hash_internal(npk_m_hash) +} + +// We are only getting the app_nullifier_secret_key here +pub fn get_nsk_app_with_npk_m_hash(npk_m_hash: Field) -> Field { + get_nullifier_keys_with_npk_m_hash_internal(npk_m_hash).app_nullifier_secret_key +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr index 3862e9d0f38..6ae314da25c 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr @@ -4,8 +4,8 @@ use dep::aztec::prelude::{ }; use dep::aztec::{ context::{PublicContext, Context}, hash::pedersen_hash, - protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, - note::{note_getter::view_notes, note_getter_options::SortOrder} + protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, hash::poseidon2_hash}, + note::{note_getter::view_notes, note_getter_options::SortOrder}, keys::getters::get_npk_m_hash }; use crate::types::token_note::{TokenNote, OwnedNote}; @@ -60,7 +60,10 @@ impl BalancesMap { owner: AztecAddress, addend: U128 ) where T: NoteInterface + OwnedNote { - let mut addend_note = T::new(addend, owner); + // We fetch the nullifier public key hash in the registry / from our PXE + let owner_npk_m_hash = get_npk_m_hash(self.map.context.private.unwrap(), owner); + + let mut addend_note = T::new(addend, owner_npk_m_hash); // docs:start:insert self.map.at(owner).insert(&mut addend_note, true); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index 8492b92a1fc..fa13976f9d5 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -1,27 +1,27 @@ use dep::aztec::{ - keys::getters::get_ivpk_m, prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, + keys::getters::get_ivpk_m_with_npk_m_hash, + prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER, note::utils::compute_note_hash_for_consumption, hash::poseidon2_hash, - oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_app_nullifier_secret_key} + oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_nsk_app_with_npk_m_hash} }; trait OwnedNote { - fn new(amount: U128, owner: AztecAddress) -> Self; + fn new(amount: U128, owner_npk_m_hash: Field) -> Self; fn get_amount(self) -> U128; - fn get_owner(self) -> AztecAddress; + fn get_owner_npk_m_hash(self) -> Field; } global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header. #[aztec(note)] struct TokenNote { - // the amount of tokens in the note + // The amount of tokens in the note amount: U128, - // the provider of secrets for the nullifier. The owner (recipient) to ensure that the note - // can be privately spent. When nullifier secret and encryption private key is same - // we can simply use the owner for this one. - owner: AztecAddress, - // randomness of the note to hide contents. + // The nullifying public key hash of the person who owns the note. + // This is used with the app_nullifier_secret_key to ensure that the note can be privately spent. + npk_m_hash: Field, + // Randomness of the note to hide its contents randomness: Field, } @@ -29,7 +29,7 @@ impl NoteInterface for TokenNote { // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_app_nullifier_secret_key(self.owner); + let secret = context.request_nsk_app_with_npk_m_hash(self.npk_m_hash); poseidon2_hash([ note_hash_for_nullify, secret, @@ -40,7 +40,7 @@ impl NoteInterface for TokenNote { fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_app_nullifier_secret_key(self.owner); + let secret = get_nsk_app_with_npk_m_hash(self.npk_m_hash); poseidon2_hash([ note_hash_for_nullify, secret, @@ -51,8 +51,9 @@ impl NoteInterface for TokenNote { // Broadcasts the note as an encrypted log on L1. fn broadcast(self, context: &mut PrivateContext, slot: Field) { // We only bother inserting the note if non-empty to save funds on gas. + // TODO: (#5901) This will be changed a lot, as it should use the updated encrypted log format if !(self.amount == U128::from_integer(0)) { - let ivpk_m = get_ivpk_m(context, self.owner); + let ivpk_m = get_ivpk_m_with_npk_m_hash(self.npk_m_hash); context.emit_encrypted_log( (*context).this_address(), slot, @@ -65,10 +66,10 @@ impl NoteInterface for TokenNote { } impl OwnedNote for TokenNote { - fn new(amount: U128, owner: AztecAddress) -> Self { + fn new(amount: U128, owner_npk_m_hash: Field) -> Self { Self { amount, - owner, + npk_m_hash: owner_npk_m_hash, randomness: unsafe_rand(), header: NoteHeader::empty(), } @@ -78,7 +79,7 @@ impl OwnedNote for TokenNote { self.amount } - fn get_owner(self) -> AztecAddress { - self.owner + fn get_owner_npk_m_hash(self) -> Field { + self.npk_m_hash } } diff --git a/yarn-project/circuit-types/src/keys/key_store.ts b/yarn-project/circuit-types/src/keys/key_store.ts index b4a0d7ce300..256db6e36a9 100644 --- a/yarn-project/circuit-types/src/keys/key_store.ts +++ b/yarn-project/circuit-types/src/keys/key_store.ts @@ -32,12 +32,12 @@ export interface KeyStore { getAccounts(): Promise; /** - * Gets the master nullifier public key for a given account. + * Gets the master nullifier public key for a given account or master nullifier public key hash. * @throws If the account does not exist in the key store. - * @param account - The account address for which to retrieve the master nullifier public key. + * @param accountOrNpkMHash - account address or master nullifier public key hash. * @returns The master nullifier public key for the account. */ - getMasterNullifierPublicKey(account: AztecAddress): Promise; + getMasterNullifierPublicKey(accountOrNpkMHash: AztecAddress | Fr): Promise; /** * Gets the master incoming viewing public key for a given account. @@ -64,13 +64,13 @@ export interface KeyStore { getMasterTaggingPublicKey(account: AztecAddress): Promise; /** - * Retrieves application nullifier secret key. + * Derives and returns the application nullifier secret key for a given account or master nullifier public key hash. * @throws If the account does not exist in the key store. - * @param account - The account to retrieve the application nullifier secret key for. + * @param accountOrNpkMHash - account address or master nullifier public key hash. * @param app - The application address to retrieve the nullifier secret key for. * @returns A Promise that resolves to the application nullifier secret key. */ - getAppNullifierSecretKey(account: AztecAddress, app: AztecAddress): Promise; + getAppNullifierSecretKey(accountOrNpkMHash: AztecAddress | Fr, app: AztecAddress): Promise; /** * Retrieves application incoming viewing secret key. diff --git a/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts index 954c88c2653..52f1d2f8826 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts @@ -120,5 +120,5 @@ describe('benchmarks/proving', () => { const receipts = await Promise.all(txs.map(tx => tx.wait({ timeout: txTimeoutSec }))); expect(receipts.every(r => r.status === TxStatus.MINED)).toBe(true); - }); + }, 1_200_000); }); diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index ebb1ec71e14..fcd544eecce 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -310,6 +310,7 @@ describe('e2e_2_pxes', () => { await sharedAccountOnB.register(); const sharedWalletOnB = await sharedAccountOnB.getWallet(); + // Register wallet B in the pxe of wallet A await pxeA.registerRecipient(walletB.getCompleteAddress()); // deploy the contract on PXE A diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index 1d4b7fc7007..c5a56d197f2 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -64,7 +64,7 @@ describe('guides/writing_an_account_contract', () => { logger.info(`Deployed account contract at ${address}`); // docs:start:account-contract-works - const token = await TokenContract.deploy(wallet, { address }, 'TokenName', 'TokenSymbol', 18).send().deployed(); + const token = await TokenContract.deploy(wallet, address, 'TokenName', 'TokenSymbol', 18).send().deployed(); logger.info(`Deployed token contract at ${token.address}`); const secret = Fr.random(); @@ -84,9 +84,9 @@ describe('guides/writing_an_account_contract', () => { ); await pxe.addNote(extendedNote); - await token.methods.redeem_shield({ address }, mintAmount, secret).send().wait(); + await token.methods.redeem_shield(address, mintAmount, secret).send().wait(); - const balance = await token.methods.balance_of_private({ address }).simulate(); + const balance = await token.methods.balance_of_private(address).simulate(); logger.info(`Balance of wallet is now ${balance}`); // docs:end:account-contract-works expect(balance).toEqual(50n); diff --git a/yarn-project/key-store/src/test_key_store.ts b/yarn-project/key-store/src/test_key_store.ts index ecdcb1174c1..2d0e0672104 100644 --- a/yarn-project/key-store/src/test_key_store.ts +++ b/yarn-project/key-store/src/test_key_store.ts @@ -54,9 +54,10 @@ export class TestKeyStore implements KeyStore { const publicKeysHash = publicKeys.hash(); const accountAddress = computeAddress(publicKeysHash, partialAddress); - // We save the keys to db + // We save the keys to db associated with the account address await this.#keys.set(`${accountAddress.toString()}-public_keys_hash`, publicKeysHash.toBuffer()); + // Naming of keys is as follows ${from}-${to}_m await this.#keys.set(`${accountAddress.toString()}-nsk_m`, masterNullifierSecretKey.toBuffer()); await this.#keys.set(`${accountAddress.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer()); await this.#keys.set(`${accountAddress.toString()}-ovsk_m`, masterOutgoingViewingSecretKey.toBuffer()); @@ -83,16 +84,19 @@ export class TestKeyStore implements KeyStore { } /** - * Gets the master nullifier public key for a given account. + * Gets the master nullifier public key for a given account or master nullifier public key hash. * @throws If the account does not exist in the key store. - * @param account - The account address for which to retrieve the master nullifier public key. + * @param accountOrNpkMHash - account address or master nullifier public key hash. * @returns The master nullifier public key for the account. */ - public async getMasterNullifierPublicKey(account: AztecAddress): Promise { - const masterNullifierPublicKeyBuffer = this.#keys.get(`${account.toString()}-npk_m`); + public async getMasterNullifierPublicKey(accountOrNpkMHash: AztecAddress | Fr): Promise { + const masterNullifierPublicKeyBuffer = + this.#keys.get(`${accountOrNpkMHash.toString()}-npk_m`) ?? + this.#keys.get(`${this.#getAccountAddressForMasterNullifierPublicKeyHash(accountOrNpkMHash)?.toString()}-npk_m`); + if (!masterNullifierPublicKeyBuffer) { throw new Error( - `Account ${account.toString()} does not exist. Registered accounts: ${await this.getAccounts()}.`, + `Account or master nullifier public key hash ${accountOrNpkMHash} does not exist. Registered accounts: ${await this.getAccounts()}.`, ); } return Promise.resolve(Point.fromBuffer(masterNullifierPublicKeyBuffer)); @@ -147,17 +151,20 @@ export class TestKeyStore implements KeyStore { } /** - * Retrieves application nullifier secret key. + * Derives and returns the application nullifier secret key for a given account or master nullifier public key hash. * @throws If the account does not exist in the key store. - * @param account - The account to retrieve the application nullifier secret key for. + * @param accountOrNpkMHash - account address or master nullifier public key hash. * @param app - The application address to retrieve the nullifier secret key for. * @returns A Promise that resolves to the application nullifier secret key. */ - public async getAppNullifierSecretKey(account: AztecAddress, app: AztecAddress): Promise { - const masterNullifierSecretKeyBuffer = this.#keys.get(`${account.toString()}-nsk_m`); + public async getAppNullifierSecretKey(accountOrNpkMHash: AztecAddress | Fr, app: AztecAddress): Promise { + const masterNullifierSecretKeyBuffer = + this.#keys.get(`${accountOrNpkMHash.toString()}-nsk_m`) ?? + this.#keys.get(`${this.#getAccountAddressForMasterNullifierPublicKeyHash(accountOrNpkMHash)?.toString()}-nsk_m`); + if (!masterNullifierSecretKeyBuffer) { throw new Error( - `Account ${account.toString()} does not exist. Registered accounts: ${await this.getAccounts()}.`, + `Account or master nullifier public key hash ${accountOrNpkMHash} does not exist. Registered accounts: ${await this.getAccounts()}.`, ); } const masterNullifierSecretKey = GrumpkinScalar.fromBuffer(masterNullifierSecretKeyBuffer); @@ -228,7 +235,7 @@ export class TestKeyStore implements KeyStore { public getMasterNullifierSecretKeyForPublicKey(masterNullifierPublicKey: PublicKey): Promise { // We iterate over the map keys to find the account address that corresponds to the provided public key for (const [key, value] of this.#keys.entries()) { - if (value.equals(masterNullifierPublicKey.toBuffer())) { + if (value.equals(masterNullifierPublicKey.toBuffer()) && key.endsWith('-npk_m')) { // We extract the account address from the map key const accountAddress = key.split('-')[0]; // We fetch the secret key and return it @@ -288,4 +295,19 @@ export class TestKeyStore implements KeyStore { } return Promise.resolve(Fr.fromBuffer(publicKeysHashBuffer)); } + + #getAccountAddressForMasterNullifierPublicKeyHash(masterNullifierPublicKeyHash: Fr): AztecAddress | undefined { + for (const [key, value] of this.#keys.entries()) { + if (key.endsWith('-npk_m')) { + const computedMasterNullifierPublicKeyHash = poseidon2Hash(Point.fromBuffer(value).toFields()); + if (computedMasterNullifierPublicKeyHash.equals(masterNullifierPublicKeyHash)) { + // We extract the account address from the map key + const accountAddress = key.split('-')[0]; + return AztecAddress.fromString(accountAddress); + } + } + } + + return undefined; + } } diff --git a/yarn-project/p2p/package.json b/yarn-project/p2p/package.json index f7ad5cd7120..0cc79a46b28 100644 --- a/yarn-project/p2p/package.json +++ b/yarn-project/p2p/package.json @@ -97,4 +97,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 6d4ac0c8644..2de3eaff5b2 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -2,6 +2,7 @@ import { MerkleTreeId, type NoteFilter, NoteStatus, type PublicKey } from '@azte import { AztecAddress, CompleteAddress, Header } from '@aztec/circuits.js'; import { type ContractArtifact } from '@aztec/foundation/abi'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { poseidon2Hash } from '@aztec/foundation/crypto'; import { Fr, type Point } from '@aztec/foundation/fields'; import { type AztecArray, @@ -385,12 +386,27 @@ export class KVPxeDatabase implements PxeDatabase { return value ? CompleteAddress.fromBuffer(value) : undefined; } - getCompleteAddress(address: AztecAddress): Promise { - return Promise.resolve(this.#getCompleteAddress(address)); + getCompleteAddress(accountOrNpkMHash: AztecAddress | Fr): Promise { + return Promise.resolve( + this.#getCompleteAddress(accountOrNpkMHash) ?? this.#getCompleteAddressWithNpkMHash(accountOrNpkMHash), + ); + } + + #getCompleteAddressWithNpkMHash(npkMHash: Fr): Promise { + const completeAddresses = this.#getCompleteAddresses(); + + const completeAddress = completeAddresses.find(completeAddress => + poseidon2Hash(completeAddress.publicKeys.masterNullifierPublicKey.toFields()).equals(npkMHash), + ); + return Promise.resolve(completeAddress); + } + + #getCompleteAddresses(): CompleteAddress[] { + return Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v)); } getCompleteAddresses(): Promise { - return Promise.resolve(Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v))); + return Promise.resolve(this.#getCompleteAddresses()); } getSynchedBlockNumberForPublicKey(publicKey: Point): number | undefined { diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index b95d5e01d8c..13c930b2b92 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -133,11 +133,11 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD addCompleteAddress(address: CompleteAddress): Promise; /** - * Retrieves the complete address corresponding to the provided aztec address. - * @param address - The aztec address of the complete address contract. - * @returns A promise that resolves to a CompleteAddress instance if the address is found, or undefined if not found. + * Retrieve the complete address associated to a given address or master nullifier public key hash. + * @param accountOrNpkMHash - account address or master nullifier public key hash. + * @returns A promise that resolves to a CompleteAddress instance if found, or undefined if not found. */ - getCompleteAddress(address: AztecAddress): Promise; + getCompleteAddress(accountOrNpkMHash: AztecAddress | Fr): Promise; /** * Retrieves the list of complete address added to this database diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 7b604c40a2f..cababdd6772 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -207,14 +207,6 @@ export class PXEService implements PXE { return Promise.resolve(account); } - public async getRegisteredAccountPublicKeysHash(address: AztecAddress): Promise { - const accounts = await this.keyStore.getAccounts(); - if (!accounts.some(account => account.equals(address))) { - return undefined; - } - return this.keyStore.getPublicKeysHash(address); - } - public async registerRecipient(recipient: CompleteAddress): Promise { const wasAdded = await this.db.addCompleteAddress(recipient); diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index ac9ee3566ef..e598a1c49e9 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -37,17 +37,18 @@ export class SimulatorOracle implements DBOracle { private log = createDebugLogger('aztec:pxe:simulator_oracle'), ) {} - async getNullifierKeys(accountAddress: AztecAddress, contractAddress: AztecAddress): Promise { - const masterNullifierPublicKey = await this.keyStore.getMasterNullifierPublicKey(accountAddress); - const appNullifierSecretKey = await this.keyStore.getAppNullifierSecretKey(accountAddress, contractAddress); + async getNullifierKeys(accountOrNpkMHash: AztecAddress | Fr, contractAddress: AztecAddress): Promise { + const masterNullifierPublicKey = await this.keyStore.getMasterNullifierPublicKey(accountOrNpkMHash); + const appNullifierSecretKey = await this.keyStore.getAppNullifierSecretKey(accountOrNpkMHash, contractAddress); return { masterNullifierPublicKey, appNullifierSecretKey }; } - async getCompleteAddress(address: AztecAddress): Promise { - const completeAddress = await this.db.getCompleteAddress(address); + async getCompleteAddress(accountOrNpkMHash: AztecAddress | Fr): Promise { + const completeAddress = await this.db.getCompleteAddress(accountOrNpkMHash); if (!completeAddress) { throw new Error( - `No public key registered for address ${address.toString()}. Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/developers/debugging/aztecnr-errors#simulation-error-No-public-key-registered-for-address-0x0-Register-it-by-calling-pxeregisterRecipient-or-pxeregisterAccount`, + `No public key registered for address or master nullifier public key hash ${accountOrNpkMHash}. + Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/developers/debugging/aztecnr-errors#simulation-error-No-public-key-registered-for-address-0x0-Register-it-by-calling-pxeregisterRecipient-or-pxeregisterAccount`, ); } return completeAddress; diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index ed48931edf2..400203a085b 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -51,6 +51,19 @@ export class Oracle { ]; } + // Keeping this oracle separate from above because I don't want an implicit overload in noir code + async getNullifierKeysWithNpkMHash([masterNullifierPublicKeyHash]: ACVMField[]): Promise { + const { masterNullifierPublicKey, appNullifierSecretKey } = await this.typedOracle.getNullifierKeys( + fromACVMField(masterNullifierPublicKeyHash), + ); + + return [ + toACVMField(masterNullifierPublicKey.x), + toACVMField(masterNullifierPublicKey.y), + toACVMField(appNullifierSecretKey), + ]; + } + async getContractInstance([address]: ACVMField[]) { const instance = await this.typedOracle.getContractInstance(AztecAddress.fromField(fromACVMField(address))); @@ -169,6 +182,14 @@ export class Oracle { return [...publicKeys.toFields(), partialAddress].map(toACVMField); } + // Keeping this oracle separate from above because I don't want an implicit overload in noir code + async getPublicKeysAndPartialAddressWithNpkMHash([masterNullifierPublicKeyHash]: ACVMField[]) { + const parsedNpkMHash = fromACVMField(masterNullifierPublicKeyHash); + const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedNpkMHash); + + return [...publicKeys.toFields(), partialAddress].map(toACVMField); + } + async getNotes( [storageSlot]: ACVMField[], [numSelects]: ACVMField[], diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 59ae7ee9705..7da764e01ff 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -89,7 +89,7 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('unpackReturns'); } - getNullifierKeys(_accountAddress: AztecAddress): Promise { + getNullifierKeys(_accountOrNpkMHash: AztecAddress | Fr): Promise { throw new OracleMethodNotAvailableError('getNullifierKeys'); } @@ -124,7 +124,7 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('getHeader'); } - getCompleteAddress(_address: AztecAddress): Promise { + getCompleteAddress(_accountOrNpkMHash: AztecAddress | Fr): Promise { throw new OracleMethodNotAvailableError('getCompleteAddress'); } diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index 0fbdbd64364..13bc8a9c7d4 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -44,11 +44,12 @@ export interface DBOracle extends CommitmentsDB { getContractInstance(address: AztecAddress): Promise; /** - * Retrieve the complete address associated to a given address. - * @param address - Address to fetch the pubkey for. - * @returns A complete address associated with the input address. + * Retrieve the complete address associated to a given address or master nullifier public key hash. + * @param accountOrNpkMHash - account address or master nullifier public key hash. + * @returns A complete address associated with the input address or master nullifier public key hash + * @throws An error if the account is not registered in the database. */ - getCompleteAddress(address: AztecAddress): Promise; + getCompleteAddress(accountOrNpkMHash: AztecAddress | Fr): Promise; /** * Retrieve the auth witness for a given message hash. @@ -65,14 +66,12 @@ export interface DBOracle extends CommitmentsDB { popCapsule(): Promise; /** - * Retrieve nullifier keys associated with a specific account and app/contract address. - * - * @param accountAddress - The account address. - * @param contractAddress - The contract address. - * @returns A Promise that resolves to nullifier keys of a requested account and contract. - * @throws An error if the account is not registered in the database. + * Retrieve nullifier keys associated with a specific account or master nullifier public key and app address. + * @param accountOrNpkMHash - account address or master nullifier public key hash. + * @returns A Promise that resolves to nullifier keys. + * @throws If the nullifier keys are not registered in the key store. */ - getNullifierKeys(accountAddress: AztecAddress, contractAddress: AztecAddress): Promise; + getNullifierKeys(accountOrNpkMHash: AztecAddress | Fr, contractAddress: AztecAddress): Promise; /** * Retrieves a set of notes stored in the database for a given contract address and storage slot. diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index 502ccaad546..a106c825c6f 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -190,35 +190,37 @@ describe('Private Execution test suite', () => { beforeEach(async () => { trees = {}; oracle = mock(); - oracle.getNullifierKeys.mockImplementation((accountAddress: AztecAddress, contractAddress: AztecAddress) => { - if (accountAddress.equals(ownerCompleteAddress.address)) { - return Promise.resolve({ - masterNullifierPublicKey: ownerCompleteAddress.publicKeys.masterNullifierPublicKey, - appNullifierSecretKey: computeAppNullifierSecretKey(ownerMasterNullifierSecretKey, contractAddress), - }); - } - if (accountAddress.equals(recipientCompleteAddress.address)) { - return Promise.resolve({ - masterNullifierPublicKey: recipientCompleteAddress.publicKeys.masterNullifierPublicKey, - appNullifierSecretKey: computeAppNullifierSecretKey(recipientMasterNullifierSecretKey, contractAddress), - }); - } - throw new Error(`Unknown address ${accountAddress}`); - }); + oracle.getNullifierKeys.mockImplementation( + (accountOrNpkMHash: AztecAddress | Fr, contractAddress: AztecAddress) => { + if (accountOrNpkMHash.equals(ownerCompleteAddress.address)) { + return Promise.resolve({ + masterNullifierPublicKey: ownerCompleteAddress.publicKeys.masterNullifierPublicKey, + appNullifierSecretKey: computeAppNullifierSecretKey(ownerMasterNullifierSecretKey, contractAddress), + }); + } + if (accountOrNpkMHash.equals(recipientCompleteAddress.address)) { + return Promise.resolve({ + masterNullifierPublicKey: recipientCompleteAddress.publicKeys.masterNullifierPublicKey, + appNullifierSecretKey: computeAppNullifierSecretKey(recipientMasterNullifierSecretKey, contractAddress), + }); + } + throw new Error(`Unknown address ${accountOrNpkMHash}`); + }, + ); // We call insertLeaves here with no leaves to populate empty public data tree root --> this is necessary to be // able to get ivpk_m during execution await insertLeaves([], 'publicData'); oracle.getHeader.mockResolvedValue(header); - oracle.getCompleteAddress.mockImplementation((address: AztecAddress) => { - if (address.equals(owner)) { + oracle.getCompleteAddress.mockImplementation((accountOrNpkMHash: AztecAddress | Fr) => { + if (accountOrNpkMHash.equals(owner)) { return Promise.resolve(ownerCompleteAddress); } - if (address.equals(recipient)) { + if (accountOrNpkMHash.equals(recipient)) { return Promise.resolve(recipientCompleteAddress); } - throw new Error(`Unknown address ${address}`); + throw new Error(`Unknown address ${accountOrNpkMHash}`); }); // This oracle gets called when reading ivpk_m from key registry --> we return zero witness indicating that // the keys were not registered. This triggers non-registered keys flow in which getCompleteAddress oracle diff --git a/yarn-project/simulator/src/client/unconstrained_execution.test.ts b/yarn-project/simulator/src/client/unconstrained_execution.test.ts index fe099451a6f..3d1b69cb0ca 100644 --- a/yarn-project/simulator/src/client/unconstrained_execution.test.ts +++ b/yarn-project/simulator/src/client/unconstrained_execution.test.ts @@ -33,11 +33,11 @@ describe('Unconstrained Execution test suite', () => { const ownerCompleteAddress = CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; - oracle.getCompleteAddress.mockImplementation((address: AztecAddress) => { - if (address.equals(owner)) { + oracle.getCompleteAddress.mockImplementation((accountOrNpkMHash: AztecAddress | Fr) => { + if (accountOrNpkMHash.equals(owner)) { return Promise.resolve(ownerCompleteAddress); } - throw new Error(`Unknown address ${address}`); + throw new Error(`Unknown address ${accountOrNpkMHash}`); }); }); diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index d9ce5ee260e..bca1a2b0697 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -35,14 +35,13 @@ export class ViewDataOracle extends TypedOracle { } /** - * Retrieve nullifier keys associated with a specific account and app/contract address. - * - * @param accountAddress - The account address. - * @returns A Promise that resolves to nullifier keys of a requested account and contract. - * @throws An error if the account is not registered in the database. + * Retrieve nullifier keys associated with a specific account or master nullifier public key and app address. + * @param accountOrNpkMHash - account address or master nullifier public key hash. + * @returns A Promise that resolves to nullifier keys. + * @throws If the nullifier keys are not registered in the key store. */ - public override getNullifierKeys(account: AztecAddress): Promise { - return this.db.getNullifierKeys(account, this.contractAddress); + public override getNullifierKeys(accountOrNpkMHash: AztecAddress | Fr): Promise { + return this.db.getNullifierKeys(accountOrNpkMHash, this.contractAddress); } /** @@ -128,12 +127,13 @@ export class ViewDataOracle extends TypedOracle { } /** - * Retrieve the complete address associated to a given address. - * @param address - Address to fetch the complete address for. - * @returns A complete address associated with the input address. + * Retrieve the complete address associated to a given address or master nullifier public key hash. + * @param accountOrNpkMHash - account address or master nullifier public key hash. + * @returns A complete address associated with the input address or master nullifier public key hash + * @throws An error if the account is not registered in the database. */ - public override getCompleteAddress(address: AztecAddress): Promise { - return this.db.getCompleteAddress(address); + public override getCompleteAddress(accountOrNpkMHash: AztecAddress | Fr): Promise { + return this.db.getCompleteAddress(accountOrNpkMHash); } /**