diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 642dcb4a0e0..00bb2f14ab1 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -1,6 +1,10 @@ use crate::note::{note_header::NoteHeader, note_interface::NoteInterface}; -use dep::protocol_types::{address::AztecAddress, utils::arr_copy_slice}; +use dep::protocol_types::{ + address::AztecAddress, + indexed_tagging_secret::{INDEXED_TAGGING_SECRET_LENGTH, IndexedTaggingSecret}, + utils::arr_copy_slice, +}; /// Notifies the simulator that a note has been created, so that it can be returned in future read requests in the same /// transaction. This note should only be added to the non-volatile database if found in an actual block. @@ -200,17 +204,42 @@ pub unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool { unconstrained fn check_nullifier_exists_oracle(_inner_nullifier: Field) -> Field {} /// Returns the tagging secret for a given sender and recipient pair, siloed for the current contract address. +/// Includes the last known index used for tagging with this secret. /// For this to work, PXE must know the ivpsk_m of the sender. /// For the recipient's side, only the address is needed. pub unconstrained fn get_app_tagging_secret( sender: AztecAddress, recipient: AztecAddress, -) -> Field { - get_app_tagging_secret_oracle(sender, recipient) +) -> IndexedTaggingSecret { + let result = get_app_tagging_secret_oracle(sender, recipient); + IndexedTaggingSecret::deserialize(result) } #[oracle(getAppTaggingSecret)] unconstrained fn get_app_tagging_secret_oracle( _sender: AztecAddress, _recipient: AztecAddress, -) -> Field {} +) -> [Field; INDEXED_TAGGING_SECRET_LENGTH] {} + +/// Returns the tagging secrets for a given recipient and all the senders in PXE's address book, +// siloed for the current contract address. +/// Includes the last known index used for tagging with this secret. +/// For this to work, PXE must know the ivsk_m of the recipient. +pub unconstrained fn get_app_tagging_secrets_for_senders( + recipient: AztecAddress, +) -> [IndexedTaggingSecret] { + let results = get_app_tagging_secrets_for_senders_oracle(recipient); + let mut indexed_tagging_secrets = &[]; + for i in 0..results.len() { + if i % 2 != 0 { + continue; + } + indexed_tagging_secrets = indexed_tagging_secrets.push_back( + IndexedTaggingSecret::deserialize([results[i], results[i + 1]]), + ); + } + indexed_tagging_secrets +} + +#[oracle(getAppTaggingSecretsForSenders)] +unconstrained fn get_app_tagging_secrets_for_senders_oracle(_recipient: AztecAddress) -> [Field] {} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr new file mode 100644 index 00000000000..1685fdf38e8 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr @@ -0,0 +1,10 @@ +use crate::traits::Deserialize; +use std::meta::derive; + +pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 2; + +#[derive(Deserialize)] +pub struct IndexedTaggingSecret { + secret: Field, + index: u32, +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr index 75345c071af..785d31683c0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr @@ -30,6 +30,7 @@ mod data; mod storage; mod validate; mod meta; +mod indexed_tagging_secret; pub use abis::kernel_circuit_public_inputs::{ KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs, diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index b8f7c80634c..e7c79fda0ce 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -15,7 +15,14 @@ import type { AztecAddress } from '@aztec/foundation/aztec-address'; import type { Fr } from '@aztec/foundation/fields'; import type { L2Block } from '../l2_block.js'; -import type { FromLogType, GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js'; +import type { + EncryptedL2NoteLog, + FromLogType, + GetUnencryptedLogsResponse, + L2BlockL2Logs, + LogFilter, + LogType, +} from '../logs/index.js'; import type { MerkleTreeId } from '../merkle_tree_id.js'; import type { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import type { PublicDataWitness } from '../public_data_witness.js'; @@ -247,6 +254,14 @@ export interface AztecNode extends ProverCoordination { */ getUnencryptedLogs(filter: LogFilter): Promise; + /** + * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag). + * @param tags - The tags to filter the logs by. + * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match + * that tag. + */ + getLogsByTags(tags: Fr[]): Promise; + /** * Method to submit a transaction to the p2p pool. * @param tx - The transaction to be submitted. diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index ae298c208b4..1fa9276e3f3 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -127,15 +127,12 @@ export function deriveKeys(secretKey: Fr) { }; } -export function computeTaggingSecret(senderCompleteAddress: CompleteAddress, senderIvsk: Fq, recipient: AztecAddress) { - const senderPreaddress = computePreaddress( - senderCompleteAddress.publicKeys.hash(), - senderCompleteAddress.partialAddress, - ); +export function computeTaggingSecret(knownAddress: CompleteAddress, ivsk: Fq, externalAddress: AztecAddress) { + const knownPreaddress = computePreaddress(knownAddress.publicKeys.hash(), knownAddress.partialAddress); // TODO: #8970 - Computation of address point from x coordinate might fail - const recipientAddressPoint = computePoint(recipient); + const externalAddressPoint = computePoint(externalAddress); const curve = new Grumpkin(); - // Given A (sender) -> B (recipient) and h == preaddress + // Given A (known complete address) -> B (external address) and h == preaddress // Compute shared secret as S = (h_A + ivsk_A) * Addr_Point_B - return curve.mul(recipientAddressPoint, senderIvsk.add(new Fq(senderPreaddress.toBigInt()))); + return curve.mul(externalAddressPoint, ivsk.add(new Fq(knownPreaddress.toBigInt()))); } diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 022fa88bc44..4e047dfc520 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -13,6 +13,7 @@ export * from './gas_fees.js'; export * from './gas_settings.js'; export * from './global_variables.js'; export * from './header.js'; +export * from './indexed_tagging_secret.js'; export * from './kernel/combined_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; export * from './kernel/enqueued_call_data.js'; diff --git a/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts new file mode 100644 index 00000000000..ab218b5b7ee --- /dev/null +++ b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts @@ -0,0 +1,9 @@ +import { Fr } from '@aztec/foundation/fields'; + +export class IndexedTaggingSecret { + constructor(public secret: Fr, public index: number) {} + + toFields(): Fr[] { + return [this.secret, new Fr(this.index)]; + } +} diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index b0061e587ce..3d297c5ca51 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -66,6 +66,8 @@ export class KVPxeDatabase implements PxeDatabase { #notesByTxHashAndScope: Map>; #notesByIvpkMAndScope: Map>; + #taggingSecretIndexes: AztecMap; + constructor(private db: AztecKVStore) { this.#db = db; @@ -111,6 +113,8 @@ export class KVPxeDatabase implements PxeDatabase { this.#notesByTxHashAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_tx_hash`)); this.#notesByIvpkMAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_ivpk_m`)); } + + this.#taggingSecretIndexes = db.openMap('tagging_secret_indices'); } public async getContract( @@ -572,4 +576,20 @@ export class KVPxeDatabase implements PxeDatabase { return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize; } + + async incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { + const indexes = await this.getTaggingSecretsIndexes(appTaggingSecrets); + await this.db.transaction(() => { + indexes.forEach(index => { + const nextIndex = index ? index + 1 : 1; + void this.#taggingSecretIndexes.set(appTaggingSecrets.toString(), nextIndex); + }); + }); + } + + getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { + return this.db.transaction(() => + appTaggingSecrets.map(secret => this.#taggingSecretIndexes.get(secret.toString()) ?? 0), + ); + } } diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index 860b3d742a0..db845d06828 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -185,4 +185,8 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD * @returns The estimated size in bytes of this db. */ estimateSize(): Promise; + + getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; + + incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; } diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 1b18b32c318..50570db1ae5 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -14,6 +14,7 @@ import { type Fr, type FunctionSelector, type Header, + IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, computeTaggingSecret, @@ -231,20 +232,49 @@ export class SimulatorOracle implements DBOracle { /** * 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. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note - * @returns A tagging secret that can be used to tag notes. + * @returns A siloed tagging secret that can be used to tag notes. */ public async getAppTaggingSecret( contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress, - ): Promise { + ): Promise { const senderCompleteAddress = await this.getCompleteAddress(sender); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + const [index] = await this.db.getTaggingSecretsIndexes([secret]); + return new IndexedTaggingSecret(secret, index); + } + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + public async getAppTaggingSecretsForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise { + const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const completeAddresses = await this.db.getCompleteAddresses(); + // Filter out the addresses corresponding to accounts + const accounts = await this.keyStore.getAccounts(); + const senders = completeAddresses.filter( + completeAddress => !accounts.find(account => account.equals(completeAddress.address)), + ); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + const secrets = senders.map(({ address: sender }) => { + const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); + return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + }); + const indexes = await this.db.getTaggingSecretsIndexes(secrets); + return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); } } diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index e3c315f0708..c8fdb5a18b2 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -409,11 +409,16 @@ export class Oracle { this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter))); } - async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { + async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { const taggingSecret = await this.typedOracle.getAppTaggingSecret( AztecAddress.fromString(sender), AztecAddress.fromString(recipient), ); - return toACVMField(taggingSecret); + return taggingSecret.toFields().map(toACVMField); + } + + async getAppTaggingSecretsForSenders([recipient]: ACVMField[]): Promise { + const taggingSecrets = await this.typedOracle.getAppTaggingSecretsForSenders(AztecAddress.fromString(recipient)); + return taggingSecrets.flatMap(taggingSecret => taggingSecret.toFields().map(toACVMField)); } } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 1e66f0f150c..284b08f5e0f 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -11,6 +11,7 @@ import { import { type ContractInstance, type Header, + type IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, } from '@aztec/circuits.js'; @@ -253,7 +254,11 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('debugLog'); } - getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise { + getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise { throw new OracleMethodNotAvailableError('getAppTaggingSecret'); } + + getAppTaggingSecretsForSenders(_recipient: AztecAddress): Promise { + throw new OracleMethodNotAvailableError('getAppTaggingSecretsForSenders'); + } } diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index c80d4fed5df..c19a0b1636a 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -9,6 +9,7 @@ import { type CompleteAddress, type ContractInstance, type Header, + type IndexedTaggingSecret, type KeyValidationRequest, } from '@aztec/circuits.js'; import { type FunctionArtifact, type FunctionSelector } from '@aztec/foundation/abi'; @@ -196,10 +197,26 @@ export interface DBOracle extends CommitmentsDB { /** * 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. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note * @returns A tagging secret that can be used to tag notes. */ - getAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress): Promise; + getAppTaggingSecret( + contractAddress: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + ): Promise; + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + getAppTaggingSecretsForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise; } diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index 644c4b8b6bd..b8ca0266fa4 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -7,7 +7,12 @@ import { type NullifierMembershipWitness, type PublicDataWitness, } from '@aztec/circuit-types'; -import { type ContractInstance, type Header, type KeyValidationRequest } from '@aztec/circuits.js'; +import { + type ContractInstance, + type Header, + type IndexedTaggingSecret, + type KeyValidationRequest, +} from '@aztec/circuits.js'; import { siloNullifier } from '@aztec/circuits.js/hash'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; @@ -291,12 +296,26 @@ export class ViewDataOracle extends TypedOracle { /** * Returns the tagging secret for a given sender and recipient pair, siloed to the current contract address. + * Includes the last known index used for tagging with this secret. * For this to work, the ivpsk_m of the sender must be known. * @param sender - The address sending the note * @param recipient - The address receiving the note * @returns A tagging secret that can be used to tag notes. */ - public override async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { + public override async getAppTaggingSecret( + sender: AztecAddress, + recipient: AztecAddress, + ): Promise { return await this.db.getAppTaggingSecret(this.contractAddress, sender, recipient); } + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + public override async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise { + return await this.db.getAppTaggingSecretsForSenders(this.contractAddress, recipient); + } } diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 3823d6d940d..b16be91beb1 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -18,6 +18,7 @@ import { type ContractInstanceWithAddress, Gas, Header, + IndexedTaggingSecret, type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, @@ -749,12 +750,31 @@ export class TXE implements TypedOracle { return; } - async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { + async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { const senderCompleteAddress = await this.getCompleteAddress(sender); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - return poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + const [index] = await this.txeDatabase.getTaggingSecretsIndexes([secret]); + return new IndexedTaggingSecret(secret, index); + } + + async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise { + const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const completeAddresses = await this.txeDatabase.getCompleteAddresses(); + // Filter out the addresses corresponding to accounts + const accounts = await this.keyStore.getAccounts(); + const senders = completeAddresses.filter( + completeAddress => !accounts.find(account => account.equals(completeAddress.address)), + ); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + const secrets = senders.map(({ address: sender }) => { + const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); + return poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + }); + const indexes = await this.txeDatabase.getTaggingSecretsIndexes(secrets); + return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); } // AVM oracles diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index f0f41fa232c..7731f55b23f 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -604,7 +604,14 @@ export class TXEService { AztecAddress.fromField(fromSingle(sender)), AztecAddress.fromField(fromSingle(recipient)), ); - return toForeignCallResult([toSingle(secret)]); + return toForeignCallResult([toArray(secret.toFields())]); + } + + async getAppTaggingSecretsForSenders(recipient: ForeignCallSingle) { + const secrets = await this.typedOracle.getAppTaggingSecretsForSenders( + AztecAddress.fromField(fromSingle(recipient)), + ); + return toForeignCallResult([toArray(secrets.flatMap(secret => secret.toFields()))]); } // AVM opcodes