diff --git a/cspell.json b/cspell.json index 3059335afc2..0b8d2a27549 100644 --- a/cspell.json +++ b/cspell.json @@ -275,11 +275,12 @@ "unexcluded", "unfinalised", "unprefixed", + "unshift", + "unshifted", + "unsynched", "unzipit", "updateable", "upperfirst", - "unshift", - "unshifted", "usecase", "usecases", "utxo", @@ -323,5 +324,7 @@ "lib", "*.cmake" ], - "flagWords": ["anonymous"] + "flagWords": [ + "anonymous" + ] } diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index 751d3c2d718..1b396ef7b65 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -260,7 +260,7 @@ mod test { 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c, ); - let _ = OracleMock::mock("getAppTaggingSecretAsSender").returns([69420, 1337]); + let _ = OracleMock::mock("getIndexedTaggingSecretAsSender").returns([69420, 1337]); let _ = OracleMock::mock("incrementAppTaggingSecretIndexAsSender").returns(()); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 6d2cdab8f83..e7534b14fb9 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -205,25 +205,25 @@ pub unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool { #[oracle(checkNullifierExists)] unconstrained fn check_nullifier_exists_oracle(_inner_nullifier: Field) -> Field {} -/// Same as `get_app_tagging_secret_as_sender`, except it returns the derived tag, ready to be included in a log. +/// Same as `get_indexed_tagging_secret_as_sender`, except it returns the derived tag, ready to be included in a log. pub unconstrained fn get_app_tag_as_sender(sender: AztecAddress, recipient: AztecAddress) -> Field { - get_app_tagging_secret_as_sender(sender, recipient).compute_tag(recipient) + get_indexed_tagging_secret_as_sender(sender, recipient).compute_tag(recipient) } /// Returns the tagging secret for a given sender and recipient pair, siloed for the current contract address. /// Includes the last known index used to send a note tagged with this secret. -/// For this to work, PXE must know the ivpsk_m of the sender. +/// For this to work, PXE must know the ivsk_m of the sender. /// For the recipient's side, only the address is needed. -pub unconstrained fn get_app_tagging_secret_as_sender( +pub unconstrained fn get_indexed_tagging_secret_as_sender( sender: AztecAddress, recipient: AztecAddress, ) -> IndexedTaggingSecret { - let result = get_app_tagging_secret_as_sender_oracle(sender, recipient); + let result = get_indexed_tagging_secret_as_sender_oracle(sender, recipient); IndexedTaggingSecret::deserialize(result) } -#[oracle(getAppTaggingSecretAsSender)] -unconstrained fn get_app_tagging_secret_as_sender_oracle( +#[oracle(getIndexedTaggingSecretAsSender)] +unconstrained fn get_indexed_tagging_secret_as_sender_oracle( _sender: AztecAddress, _recipient: AztecAddress, ) -> [Field; INDEXED_TAGGING_SECRET_LENGTH] {} 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 index 89837964cff..4781bd87560 100644 --- 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 @@ -6,12 +6,14 @@ pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 2; #[derive(Serialize, Deserialize)] pub struct IndexedTaggingSecret { - secret: Field, + app_tagging_secret: Field, index: u32, } impl IndexedTaggingSecret { pub fn compute_tag(self, recipient: AztecAddress) -> Field { - poseidon2_hash([self.secret, recipient.to_field(), self.index as Field]) + poseidon2_hash( + [self.app_tagging_secret, recipient.to_field(), self.index as Field], + ) } } diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index 495ea964cf8..032141f9842 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -123,7 +123,8 @@ export function deriveKeys(secretKey: Fr) { }; } -export function computeTaggingSecret(knownAddress: CompleteAddress, ivsk: Fq, externalAddress: AztecAddress) { +// Returns shared tagging secret computed with Diffie-Hellman key exchange. +export function computeTaggingSecretPoint(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 externalAddressPoint = externalAddress.toAddressPoint(); diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 4959a0fb048..9160f7cfe06 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -15,7 +15,7 @@ export * from './gas_fees.js'; export * from './gas_settings.js'; export * from './global_variables.js'; export * from './block_header.js'; -export * from './tagging_secret.js'; +export * from './indexed_tagging_secret.js'; export * from './kernel/combined_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; export * from './kernel/private_kernel_empty_inputs.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..4a884180eb8 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts @@ -0,0 +1,40 @@ +import { type AztecAddress } from '@aztec/foundation/aztec-address'; +import { poseidon2Hash } from '@aztec/foundation/crypto'; +import { Fr } from '@aztec/foundation/fields'; + +export class IndexedTaggingSecret { + constructor(public appTaggingSecret: Fr, public index: number) {} + + toFields(): Fr[] { + return [this.appTaggingSecret, new Fr(this.index)]; + } + + static fromFields(serialized: Fr[]) { + return new this(serialized[0], serialized[1].toNumber()); + } + + /** + * Computes the tag based on the app tagging secret, recipient and index. + * @dev By including the recipient we achieve "directionality" of the tag (when sending a note in the other + * direction, the tag will be different). + * @param recipient The recipient of the note + * @returns The tag. + */ + computeTag(recipient: AztecAddress) { + return poseidon2Hash([this.appTaggingSecret, recipient, this.index]); + } + + /** + * Computes the tag as it is submitted on-chain. + * @dev We do this second layer of siloing (one was already done as the tagging secret is app-siloed) because kernels + * do that to protect against contract impersonation attacks. This extra layer of siloing in kernels ensures that + * a malicious contract cannot emit a note with a tag corresponding to another contract. + * @param recipient The recipient of the note + * @param app The app address + * @returns The tag as it is submitted on-chain in a log. + */ + computeSiloedTag(recipient: AztecAddress, app: AztecAddress) { + const tag = this.computeTag(recipient); + return poseidon2Hash([app, tag]); + } +} diff --git a/yarn-project/circuits.js/src/structs/tagging_secret.ts b/yarn-project/circuits.js/src/structs/tagging_secret.ts deleted file mode 100644 index 97371fe7a8b..00000000000 --- a/yarn-project/circuits.js/src/structs/tagging_secret.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { poseidon2Hash } from '@aztec/foundation/crypto'; -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)]; - } - - static fromFields(serialized: Fr[]) { - return new this(serialized[0], serialized[1].toNumber()); - } - - computeTag(recipient: AztecAddress) { - return poseidon2Hash([this.secret, recipient, this.index]); - } - - computeSiloedTag(recipient: AztecAddress, contractAddress: AztecAddress) { - const tag = this.computeTag(recipient); - return poseidon2Hash([contractAddress, tag]); - } -} diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 5439afa717b..7ff1f19062b 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -585,7 +585,9 @@ export class KVPxeDatabase implements PxeDatabase { async #setTaggingSecretsIndexes(indexedSecrets: IndexedTaggingSecret[], storageMap: AztecAsyncMap) { await Promise.all( - indexedSecrets.map(indexedSecret => storageMap.set(indexedSecret.secret.toString(), indexedSecret.index)), + indexedSecrets.map(indexedSecret => + storageMap.set(indexedSecret.appTaggingSecret.toString(), indexedSecret.index), + ), ); } diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 0d88946a5ea..160947e25c1 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -24,7 +24,7 @@ import { type L1_TO_L2_MSG_TREE_HEIGHT, PrivateLog, computeAddressSecret, - computeTaggingSecret, + computeTaggingSecretPoint, } from '@aztec/circuits.js'; import { type FunctionArtifact, getFunctionArtifact } from '@aztec/foundation/abi'; import { poseidon2Hash } from '@aztec/foundation/crypto'; @@ -38,6 +38,7 @@ import { type IncomingNoteDao } from '../database/incoming_note_dao.js'; import { type PxeDatabase } from '../database/index.js'; import { produceNoteDaos } from '../note_decryption_utils/produce_note_daos.js'; import { getAcirSimulator } from '../simulator/index.js'; +import { getInitialIndexes, getLeftMostIndexedTaggingSecrets, getRightMostIndexes } from './tagging_utils.js'; /** * A data oracle that provides information needed for simulating a transaction. @@ -257,28 +258,28 @@ 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. + * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known. * Includes the next index to be used 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 siloed tagging secret that can be used to tag notes. + * @returns An indexed tagging secret that can be used to tag notes. */ - public async getAppTaggingSecretAsSender( + public async getIndexedTaggingSecretAsSender( contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress, ): Promise { await this.syncTaggedLogsAsSender(contractAddress, sender, recipient); - const secret = await this.#calculateTaggingSecret(contractAddress, sender, recipient); - const [index] = await this.db.getTaggingSecretsIndexesAsSender([secret]); + const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient); + const [index] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]); - return new IndexedTaggingSecret(secret, index); + return new IndexedTaggingSecret(appTaggingSecret, index); } /** - * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. + * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note @@ -288,7 +289,7 @@ export class SimulatorOracle implements DBOracle { sender: AztecAddress, recipient: AztecAddress, ): Promise { - const secret = await this.#calculateTaggingSecret(contractAddress, sender, recipient); + const secret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient); const contractName = await this.contractDataOracle.getDebugContractName(contractAddress); this.log.debug(`Incrementing app tagging secret at ${contractName}(${contractAddress})`, { secret, @@ -302,25 +303,25 @@ export class SimulatorOracle implements DBOracle { await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(secret, index + 1)]); } - async #calculateTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) { + async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) { 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 - const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); - return siloedSecret; + const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient); + // Silo the secret so it can't be used to track other app's notes + const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]); + return appSecret; } /** - * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * Returns the indexed tagging secrets for a given recipient and all the senders in the address book * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, * so we're keeping it private for now. * @param contractAddress - The contract address to silo the secret for * @param recipient - The address receiving the notes - * @returns A list of siloed tagging secrets + * @returns A list of indexed tagging secrets */ - async #getAppTaggingSecretsForContacts( + async #getIndexedTaggingSecretsForContacts( contractAddress: AztecAddress, recipient: AztecAddress, ): Promise { @@ -332,7 +333,7 @@ export class SimulatorOracle implements DBOracle { (address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)), ); const appTaggingSecrets = contacts.map(contact => { - const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, contact); + const sharedSecret = computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact); return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); }); const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets); @@ -351,7 +352,7 @@ export class SimulatorOracle implements DBOracle { sender: AztecAddress, recipient: AztecAddress, ): Promise { - const appTaggingSecret = await this.#calculateTaggingSecret(contractAddress, sender, recipient); + const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient); let [currentIndex] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]); const INDEX_OFFSET = 10; @@ -409,7 +410,8 @@ export class SimulatorOracle implements DBOracle { /** * Synchronizes the logs tagged with scoped addresses and all the senders in the address book. - * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs to sync. + * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs + * to sync. * @param contractAddress - The address of the contract that the logs are tagged for * @param recipient - The address of the recipient * @returns A list of encrypted logs tagged with the recipient's address @@ -419,8 +421,12 @@ export class SimulatorOracle implements DBOracle { maxBlockNumber: number, scopes?: AztecAddress[], ): Promise> { + // Half the size of the window we slide over the tagging secret indexes. + const WINDOW_HALF_SIZE = 10; + const recipients = scopes ? scopes : await this.keyStore.getAccounts(); - const result = new Map(); + // A map of never-before-seen logs going from recipient address to logs + const newLogsMap = new Map(); const contractName = await this.contractDataOracle.getDebugContractName(contractAddress); for (const recipient of recipients) { const logs: TxScopedL2Log[] = []; @@ -431,56 +437,40 @@ export class SimulatorOracle implements DBOracle { // length, since we don't really know the note they correspond to until we decrypt them. // 1. Get all the secrets for the recipient and sender pairs (#9365) - const appTaggingSecrets = await this.#getAppTaggingSecretsForContacts(contractAddress, recipient); + const indexedTaggingSecrets = await this.#getIndexedTaggingSecretsForContacts(contractAddress, recipient); // 1.1 Set up a sliding window with an offset. Chances are the sender might have messed up - // and inadvertently incremented their index without use getting any logs (for example, in case - // of a revert). If we stopped looking for logs the first time - // we receive 0 for a tag, we might never receive anything from that sender again. - // Also there's a possibility that we have advanced our index, but the sender has reused it, so - // we might have missed some logs. For these reasons, we have to look both back and ahead of the - // stored index - const INDEX_OFFSET = 10; - type SearchState = { - currentTagggingSecrets: IndexedTaggingSecret[]; - maxIndexesToCheck: { [k: string]: number }; - initialSecretIndexes: { [k: string]: number }; - secretsToIncrement: { [k: string]: number }; - }; - const searchState = appTaggingSecrets.reduce( - (acc, appTaggingSecret) => ({ - // Start looking for logs before the stored index - currentTagggingSecrets: acc.currentTagggingSecrets.concat([ - new IndexedTaggingSecret(appTaggingSecret.secret, Math.max(0, appTaggingSecret.index - INDEX_OFFSET)), - ]), - // Keep looking for logs beyond the stored index - maxIndexesToCheck: { - ...acc.maxIndexesToCheck, - ...{ [appTaggingSecret.secret.toString()]: appTaggingSecret.index + INDEX_OFFSET }, - }, - // Keeps track of the secrets we have to increment in the database - secretsToIncrement: {}, - // Store the initial set of indexes for the secrets - initialSecretIndexes: { - ...acc.initialSecretIndexes, - ...{ [appTaggingSecret.secret.toString()]: appTaggingSecret.index }, - }, - }), - { currentTagggingSecrets: [], maxIndexesToCheck: {}, secretsToIncrement: {}, initialSecretIndexes: {} }, - ); - - let { currentTagggingSecrets } = searchState; - const { maxIndexesToCheck, secretsToIncrement, initialSecretIndexes } = searchState; - - while (currentTagggingSecrets.length > 0) { + // and inadvertently incremented their index without us getting any logs (for example, in case + // of a revert). If we stopped looking for logs the first time we don't receive any logs for a tag, + // we might never receive anything from that sender again. + // Also there's a possibility that we have advanced our index, but the sender has reused it, + // so we might have missed some logs. For these reasons, we have to look both back and ahead of + // the stored index. + + // App tagging secrets along with an index in a window to check in the current iteration. Called current because + // this value will be updated as we iterate through the window. + let currentSecrets = getLeftMostIndexedTaggingSecrets(indexedTaggingSecrets, WINDOW_HALF_SIZE); + // Right-most indexes in a window to check stored in a key-value map where key is the app tagging secret + // and value is the index to check (the right-most index in the window). + const rightMostIndexesMap = getRightMostIndexes(indexedTaggingSecrets, WINDOW_HALF_SIZE); + // The initial/unmodified indexes of the secrets stored in a key-value map where key is the app tagging secret. + const initialIndexesMap = getInitialIndexes(indexedTaggingSecrets); + // A map of indexes to increment for secrets for which we have found logs with an index higher than the one + // stored. + const indexesToIncrementMap: { [k: string]: number } = {}; + + while (currentSecrets.length > 0) { // 2. Compute tags using the secrets, recipient and index. Obtain logs for each tag (#9380) - const currentTags = currentTagggingSecrets.map(taggingSecret => - taggingSecret.computeSiloedTag(recipient, contractAddress), + const currentTags = currentSecrets.map(secret => + // We compute the siloed tags since we need the tags as they appear in the log. + secret.computeSiloedTag(recipient, contractAddress), ); + + // Fetch the logs for the tags and iterate over them const logsByTags = await this.aztecNode.getLogsByTags(currentTags); - const newTaggingSecrets: IndexedTaggingSecret[] = []; + const secretsWithNewIndex: IndexedTaggingSecret[] = []; logsByTags.forEach((logsByTag, logIndex) => { - const { secret: currentSecret, index: currentIndex } = currentTagggingSecrets[logIndex]; + const { appTaggingSecret: currentSecret, index: currentIndex } = currentSecrets[logIndex]; const currentSecretAsStr = currentSecret.toString(); this.log.debug(`Syncing logs for recipient ${recipient} at contract ${contractName}(${contractAddress})`, { recipient, @@ -504,40 +494,46 @@ export class SimulatorOracle implements DBOracle { ); logs.push(...logsByTag); - if (currentIndex >= initialSecretIndexes[currentSecretAsStr]) { - // 3.2. Increment the index for the tags that have logs, provided they're higher than the one - // we have stored in the db (#9380) - secretsToIncrement[currentSecretAsStr] = newIndex; - // 3.3. Slide the window forwards if we have found logs beyond the initial index - maxIndexesToCheck[currentSecretAsStr] = currentIndex + INDEX_OFFSET; + if (currentIndex >= initialIndexesMap[currentSecretAsStr]) { + // 3.2. We found an index higher than the stored/initial one so we update it in the db later on (#9380) + indexesToIncrementMap[currentSecretAsStr] = newIndex; + // 3.3. We found an index higher than the initial one so we slide the window. + rightMostIndexesMap[currentSecretAsStr] = currentIndex + WINDOW_HALF_SIZE; } } // 3.4 Keep increasing the index (inside the window) temporarily for the tags that have no logs // There's a chance the sender missed some and we want to catch up - if (currentIndex < maxIndexesToCheck[currentSecretAsStr]) { + if (currentIndex < rightMostIndexesMap[currentSecretAsStr]) { const newTaggingSecret = new IndexedTaggingSecret(currentSecret, currentIndex + 1); - newTaggingSecrets.push(newTaggingSecret); + secretsWithNewIndex.push(newTaggingSecret); } }); + + // We store the new indexes for the secrets that have logs with an index higher than the one stored. await this.db.setTaggingSecretsIndexesAsRecipient( - Object.keys(secretsToIncrement).map( - secret => new IndexedTaggingSecret(Fr.fromHexString(secret), secretsToIncrement[secret]), + Object.keys(indexesToIncrementMap).map( + secret => new IndexedTaggingSecret(Fr.fromHexString(secret), indexesToIncrementMap[secret]), ), ); - currentTagggingSecrets = newTaggingSecrets; + + // We've processed all the current secret-index pairs so we proceed to the next iteration. + currentSecrets = secretsWithNewIndex; } - result.set( + newLogsMap.set( recipient.toString(), // Remove logs with a block number higher than the max block number // Duplicates are likely to happen due to the sliding window, so we also filter them out logs.filter( (log, index, self) => + // The following condition is true if the log has small enough block number and is unique + // --> the right side of the && is true if the index of the current log is the first occurrence + // of the log in the array --> that way we ensure uniqueness. log.blockNumber <= maxBlockNumber && index === self.findIndex(otherLog => otherLog.equals(log)), ), ); } - return result; + return newLogsMap; } /** diff --git a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts index 930d586681d..1e88eaab363 100644 --- a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts +++ b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts @@ -18,7 +18,7 @@ import { IndexedTaggingSecret, MAX_NOTE_HASHES_PER_TX, computeAddress, - computeTaggingSecret, + computeTaggingSecretPoint, deriveKeys, } from '@aztec/circuits.js'; import { pedersenHash, poseidon2Hash } from '@aztec/foundation/crypto'; @@ -103,9 +103,9 @@ function computeSiloedTagForIndex( contractAddress: AztecAddress, index: number, ) { - const sharedSecret = computeTaggingSecret(sender.completeAddress, sender.ivsk, recipient); - const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); - const tag = poseidon2Hash([siloedSecret, recipient, index]); + const secretPoint = computeTaggingSecretPoint(sender.completeAddress, sender.ivsk, recipient); + const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]); + const tag = poseidon2Hash([appSecret, recipient, index]); return poseidon2Hash([contractAddress, tag]); } @@ -240,12 +240,12 @@ describe('Simulator oracle', () => { const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); // First sender should have 2 logs, but keep index 1 since they were built using the same tag - // Next 4 senders hould also have index 1 = offset + 1 + // Next 4 senders should also have index 1 = offset + 1 // Last 5 senders should have index 2 = offset + 2 const indexes = await database.getTaggingSecretsIndexesAsRecipient(secrets); @@ -269,8 +269,8 @@ describe('Simulator oracle', () => { // Recompute the secrets (as recipient) to ensure indexes are updated const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); const indexesAsSender = await database.getTaggingSecretsIndexesAsSender(secrets); @@ -320,12 +320,12 @@ describe('Simulator oracle', () => { // Recompute the secrets (as recipient) to ensure indexes are updated const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); // First sender should have 2 logs, but keep index 1 since they were built using the same tag - // Next 4 senders hould also have index 6 = offset + 1 + // Next 4 senders should also have index 6 = offset + 1 // Last 5 senders should have index 7 = offset + 2 const indexes = await database.getTaggingSecretsIndexesAsRecipient(secrets); @@ -344,8 +344,8 @@ describe('Simulator oracle', () => { // Recompute the secrets (as recipient) to update indexes const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); // Increase our indexes to 2 @@ -377,8 +377,8 @@ describe('Simulator oracle', () => { // Recompute the secrets (as recipient) to update indexes const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); await database.setTaggingSecretsIndexesAsRecipient( @@ -408,8 +408,8 @@ describe('Simulator oracle', () => { // Recompute the secrets (as recipient) to update indexes const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = senders.map(sender => { - const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); - return poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + const firstSenderSecretPoint = computeTaggingSecretPoint(recipient, ivsk, sender.completeAddress.address); + return poseidon2Hash([firstSenderSecretPoint.x, firstSenderSecretPoint.y, contractAddress]); }); await database.setTaggingSecretsIndexesAsRecipient( diff --git a/yarn-project/pxe/src/simulator_oracle/tagging_utils.ts b/yarn-project/pxe/src/simulator_oracle/tagging_utils.ts new file mode 100644 index 00000000000..52ed10a1cfc --- /dev/null +++ b/yarn-project/pxe/src/simulator_oracle/tagging_utils.ts @@ -0,0 +1,54 @@ +import { IndexedTaggingSecret } from '@aztec/circuits.js'; + +/** + * Gets indexed tagging secrets with leftmost indexes. + * @param indexedTaggingSecrets - The indexed tagging secrets to get the leftmost indexed tagging secrets from. + * @param windowHalfSize- The half size of the window to slide over the tagging secret indexes. + * @returns The leftmost indexed tagging secrets. + */ +export function getLeftMostIndexedTaggingSecrets( + indexedTaggingSecrets: IndexedTaggingSecret[], + windowHalfSize: number, +): IndexedTaggingSecret[] { + return indexedTaggingSecrets.map( + indexedTaggingSecret => + new IndexedTaggingSecret( + indexedTaggingSecret.appTaggingSecret, + Math.max(0, indexedTaggingSecret.index - windowHalfSize), + ), + ); +} + +/** + * Creates a map from app tagging secret to rightmost index. + * @param indexedTaggingSecrets - The indexed tagging secrets to get the rightmost indexes from. + * @param windowHalfSize- The half size of the window to slide over the tagging secret indexes. + * @returns The map from app tagging secret to rightmost index. + */ +export function getRightMostIndexes( + indexedTaggingSecrets: IndexedTaggingSecret[], + windowHalfSize: number, +): { [k: string]: number } { + const rightMostIndexes: { [k: string]: number } = {}; + + for (const indexedTaggingSecret of indexedTaggingSecrets) { + rightMostIndexes[indexedTaggingSecret.appTaggingSecret.toString()] = indexedTaggingSecret.index + windowHalfSize; + } + + return rightMostIndexes; +} + +/** + * Creates a map from app tagging secret to initial index. + * @param indexedTaggingSecrets - The indexed tagging secrets to get the initial indexes from. + * @returns The map from app tagging secret to initial index. + */ +export function getInitialIndexes(indexedTaggingSecrets: IndexedTaggingSecret[]): { [k: string]: number } { + const initialIndexes: { [k: string]: number } = {}; + + for (const indexedTaggingSecret of indexedTaggingSecrets) { + initialIndexes[indexedTaggingSecret.appTaggingSecret.toString()] = indexedTaggingSecret.index; + } + + return initialIndexes; +} diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index 99d5d5f29f2..254b91fb3ab 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -375,8 +375,8 @@ export class Oracle { this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter))); } - async getAppTaggingSecretAsSender([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { - const taggingSecret = await this.typedOracle.getAppTaggingSecretAsSender( + async getIndexedTaggingSecretAsSender([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { + const taggingSecret = await this.typedOracle.getIndexedTaggingSecretAsSender( AztecAddress.fromString(sender), AztecAddress.fromString(recipient), ); diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 2505a0478b0..d5c461bd3c2 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -237,8 +237,8 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('debugLog'); } - getAppTaggingSecretAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise { - throw new OracleMethodNotAvailableError('getAppTaggingSecretAsSender'); + getIndexedTaggingSecretAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise { + throw new OracleMethodNotAvailableError('getIndexedTaggingSecretAsSender'); } incrementAppTaggingSecretIndexAsSender(_sender: AztecAddress, _recipient: AztecAddress): Promise { diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index 6702810c86a..9472148257a 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -198,21 +198,21 @@ export interface DBOracle extends CommitmentsDB { getBlockNumber(): Promise; /** - * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. + * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known. * Includes the next index to be used 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. */ - getAppTaggingSecretAsSender( + getIndexedTaggingSecretAsSender( contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress, ): Promise; /** - * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. + * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index 4d225f58d57..19d96f8b043 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -233,7 +233,7 @@ describe('Private Execution test suite', () => { throw new Error(`Unknown address: ${address}. Recipient: ${recipient}, Owner: ${owner}`); }); - oracle.getAppTaggingSecretAsSender.mockImplementation( + oracle.getIndexedTaggingSecretAsSender.mockImplementation( (_contractAddress: AztecAddress, _sender: AztecAddress, _recipient: AztecAddress) => { const secret = Fr.random(); return Promise.resolve(new IndexedTaggingSecret(secret, 0)); diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index 358a3271186..bfd870760b1 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -296,16 +296,16 @@ export class ViewDataOracle extends TypedOracle { /** * Returns the tagging secret for a given sender and recipient pair, siloed to the current contract address. * Includes the next index to be used used for tagging with this secret. - * For this to work, the ivpsk_m of the sender must be known. + * For this to work, the ivsk_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 getAppTaggingSecretAsSender( + public override async getIndexedTaggingSecretAsSender( sender: AztecAddress, recipient: AztecAddress, ): Promise { - return await this.db.getAppTaggingSecretAsSender(this.contractAddress, sender, recipient); + return await this.db.getIndexedTaggingSecretAsSender(this.contractAddress, sender, recipient); } public override async syncNotes() { diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index a455373b83c..03928a58a30 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -36,7 +36,7 @@ import { type PublicDataTreeLeafPreimage, type PublicDataWrite, computeContractClassId, - computeTaggingSecret, + computeTaggingSecretPoint, deriveKeys, getContractClassFromArtifact, } from '@aztec/circuits.js'; @@ -890,24 +890,24 @@ export class TXE implements TypedOracle { } async incrementAppTaggingSecretIndexAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { - const appSecret = await this.#calculateTaggingSecret(this.contractAddress, sender, recipient); + const appSecret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient); const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([appSecret]); await this.txeDatabase.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appSecret, index + 1)]); } - async getAppTaggingSecretAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { - const secret = await this.#calculateTaggingSecret(this.contractAddress, sender, recipient); + async getIndexedTaggingSecretAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { + const secret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient); const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([secret]); return new IndexedTaggingSecret(secret, index); } - async #calculateTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) { + async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) { const senderCompleteAddress = await this.getCompleteAddress(sender); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); - const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); + const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); - return siloedSecret; + const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]); + return appSecret; } async syncNotes() { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index de5c041d1c1..9d87db3c1b4 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -584,8 +584,8 @@ export class TXEService { return toForeignCallResult([toArray(witness.toFields())]); } - async getAppTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) { - const secret = await this.typedOracle.getAppTaggingSecretAsSender( + async getIndexedTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) { + const secret = await this.typedOracle.getIndexedTaggingSecretAsSender( AztecAddress.fromField(fromSingle(sender)), AztecAddress.fromField(fromSingle(recipient)), );