diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index d6226a3fd889..afd861dd421b 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -88,6 +88,12 @@ export abstract class BaseWallet implements Wallet { getRegisteredAccount(address: AztecAddress): Promise { return this.pxe.getRegisteredAccount(address); } + registerContact(address: AztecAddress): Promise { + return this.pxe.registerContact(address); + } + getContacts(): Promise { + return this.pxe.getContacts(); + } registerContract(contract: { /** Instance */ instance: ContractInstanceWithAddress; /** Associated artifact */ artifact?: ContractArtifact; diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 12a5d873c872..beacd758cea9 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -93,6 +93,23 @@ export interface PXE { */ getRegisteredAccount(address: AztecAddress): Promise; + /** + * Registers a user contact in PXE. + * + * Once a new contact is registered, the PXE Service will be able to receive notes tagged from this contact. + * Will do nothing if the account is already registered. + * + * @param address - Address of the user to add to the address book + * @returns The address address of the account. + */ + registerContact(address: AztecAddress): Promise; + + /** + * Retrieves the addresses stored as contacts on this PXE Service. + * @returns An array of the contacts on this PXE Service. + */ + getContacts(): Promise; + /** * Registers a contract class in the PXE without registering any associated contract instance with it. * @@ -333,7 +350,7 @@ export interface PXE { getSyncStats(): Promise<{ [key: string]: NoteProcessorStats }>; /** - * Returns a Contact Instance given its address, which includes the contract class identifier, + * Returns a Contract Instance given its address, which includes the contract class identifier, * initialization hash, deployment salt, and public keys hash. * TODO(@spalladino): Should we return the public keys in plain as well here? * @param address - Deployment address of the contract. @@ -341,7 +358,7 @@ export interface PXE { getContractInstance(address: AztecAddress): Promise; /** - * Returns a Contact Class given its identifier. + * Returns a Contract Class given its identifier. * TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also, * should the pxe query the node for contract public info, and merge it with its own definitions? * @param id - Identifier of the class. diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 95a5da82e45b..76309c984dc0 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -36,8 +36,9 @@ import { type PxeDatabase } from './pxe_database.js'; */ export class KVPxeDatabase implements PxeDatabase { #synchronizedBlock: AztecSingleton; - #addresses: AztecArray; - #addressIndex: AztecMap; + #completeAddresses: AztecArray; + #completeAddressIndex: AztecMap; + #addressBook: AztecSet; #authWitnesses: AztecMap; #capsules: AztecArray; #notes: AztecMap; @@ -72,8 +73,10 @@ export class KVPxeDatabase implements PxeDatabase { constructor(private db: AztecKVStore) { this.#db = db; - this.#addresses = db.openArray('addresses'); - this.#addressIndex = db.openMap('address_index'); + this.#completeAddresses = db.openArray('addresses'); + this.#completeAddressIndex = db.openMap('address_index'); + + this.#addressBook = db.openSet('address_book'); this.#authWitnesses = db.openMap('auth_witnesses'); this.#capsules = db.openArray('capsules'); @@ -509,15 +512,15 @@ export class KVPxeDatabase implements PxeDatabase { return this.#db.transaction(() => { const addressString = completeAddress.address.toString(); const buffer = completeAddress.toBuffer(); - const existing = this.#addressIndex.get(addressString); + const existing = this.#completeAddressIndex.get(addressString); if (typeof existing === 'undefined') { - const index = this.#addresses.length; - void this.#addresses.push(buffer); - void this.#addressIndex.set(addressString, index); + const index = this.#completeAddresses.length; + void this.#completeAddresses.push(buffer); + void this.#completeAddressIndex.set(addressString, index); return true; } else { - const existingBuffer = this.#addresses.at(existing); + const existingBuffer = this.#completeAddresses.at(existing); if (existingBuffer?.equals(buffer)) { return false; @@ -531,12 +534,12 @@ export class KVPxeDatabase implements PxeDatabase { } #getCompleteAddress(address: AztecAddress): CompleteAddress | undefined { - const index = this.#addressIndex.get(address.toString()); + const index = this.#completeAddressIndex.get(address.toString()); if (typeof index === 'undefined') { return undefined; } - const value = this.#addresses.at(index); + const value = this.#completeAddresses.at(index); return value ? CompleteAddress.fromBuffer(value) : undefined; } @@ -545,7 +548,21 @@ export class KVPxeDatabase implements PxeDatabase { } getCompleteAddresses(): Promise { - return Promise.resolve(Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v))); + return Promise.resolve(Array.from(this.#completeAddresses).map(v => CompleteAddress.fromBuffer(v))); + } + + async addContactAddress(address: AztecAddress): Promise { + if (this.#addressBook.has(address.toString())) { + return false; + } + + await this.#addressBook.add(address.toString()); + + return true; + } + + getContactAddresses(): AztecAddress[] { + return [...this.#addressBook.entries()].map(AztecAddress.fromString); } getSynchedBlockNumberForAccount(account: AztecAddress): number | undefined { @@ -570,7 +587,7 @@ export class KVPxeDatabase implements PxeDatabase { (sum, value) => sum + value.length * Fr.SIZE_IN_BYTES, 0, ); - const addressesSize = this.#addresses.length * CompleteAddress.SIZE_IN_BYTES; + const addressesSize = this.#completeAddresses.length * CompleteAddress.SIZE_IN_BYTES; const treeRootsSize = Object.keys(MerkleTreeId).length * Fr.SIZE_IN_BYTES; return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize; diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index db845d06828b..22b0a3a46d97 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -145,6 +145,10 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD */ setHeader(header: Header): Promise; + addContactAddress(address: AztecAddress): Promise; + + getContactAddresses(): AztecAddress[]; + /** * Adds complete address to the database. * @param address - The complete address to add. diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index a06217d61044..d4342fcaf8a0 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -204,6 +204,30 @@ export class PXEService implements PXE { return accountCompleteAddress; } + public async registerContact(address: AztecAddress): Promise { + const accounts = await this.keyStore.getAccounts(); + if (accounts.includes(address)) { + this.log.info(`Account:\n "${address.toString()}"\n already registered.`); + return address; + } + + const wasAdded = await this.db.addContactAddress(address); + + if (wasAdded) { + this.log.info(`Added contact:\n ${address.toString()}`); + } else { + this.log.info(`Contact:\n "${address.toString()}"\n already registered.`); + } + + return address; + } + + public getContacts(): Promise { + const contacts = this.db.getContactAddresses(); + + return Promise.resolve(contacts); + } + public async getRegisteredAccounts(): Promise { // Get complete addresses of both the recipients and the accounts const completeAddresses = await this.db.getCompleteAddresses(); diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 4295bc1e555a..100233bd640c 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -230,6 +230,16 @@ export class SimulatorOracle implements DBOracle { return this.contractDataOracle.getDebugFunctionName(contractAddress, selector); } + /** + * Returns the full contents of your address book. + * This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and + * finally the index specified tag. We will then query the node with this tag for each address in the address book. + * @returns The full list of the users contact addresses. + */ + public getContacts(): AztecAddress[] { + return this.db.getContactAddresses(); + } + /** * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. * Includes the last known index used for tagging with this secret.