diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index 7f7eabc5949c..dbdcca8a8e91 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -311,7 +311,7 @@ export class CLIWallet extends BaseWallet { opts: SimulateViaEntrypointOptions, ): Promise { const { from, feeOptions, additionalScopes, sendMessagesAs } = opts; - const scopes = this.scopesFrom(from, additionalScopes); + const scopes = this.scopesFrom(from, additionalScopes, sendMessagesAs); const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); const finalExecutionPayload = feeExecutionPayload ? mergeExecutionPayloads([feeExecutionPayload, executionPayload]) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts index 5d2de605b03d..c5d49775c8d4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts @@ -80,6 +80,19 @@ describe('PrivateExecutionOracle', () => { }); }); + describe('getAppTaggingSecret', () => { + it('rejects a sender outside the execution scopes', async () => { + const inScopeSender = await AztecAddress.random(); + const outOfScopeSender = await AztecAddress.random(); + const recipient = await AztecAddress.random(); + const oracle = makeOracle({ scopes: [inScopeSender] }); + + await expect(oracle.getAppTaggingSecret(outOfScopeSender, recipient)).rejects.toThrow( + 'is not in the allowed scopes list', + ); + }); + }); + describe('resolveTaggingStrategy', () => { let sender: AztecAddress; let recipient: AztecAddress; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 481d28bc24c5..43642589f3f9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -36,6 +36,7 @@ import { type TaggingSecretStrategy, } from '../../hooks/resolve_tagging_secret_strategy.js'; import { NoteService } from '../../notes/note_service.js'; +import { assertAllowedScope } from '../../storage/allowed_scopes.js'; import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js'; import { syncSenderTaggingIndexes } from '../../tagging/index.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; @@ -321,7 +322,17 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * @returns The app tagging secret, or `None` if the recipient is invalid. */ public async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise> { - const extendedSecret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient); + assertAllowedScope(sender, this.scopes); + + const senderCompleteAddress = await this.getCompleteAddressOrFail(sender); + const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); + const extendedSecret = await AppTaggingSecret.computeViaEcdh( + senderCompleteAddress, + senderIvsk, + recipient, + this.contractAddress, + recipient, + ); if (!extendedSecret) { this.logger.warn(`Computing a tagging secret for invalid recipient ${recipient} - returning no secret`, { @@ -354,12 +365,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP return index; } - async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) { - const senderCompleteAddress = await this.getCompleteAddressOrFail(sender); - const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); - return AppTaggingSecret.computeViaEcdh(senderCompleteAddress, senderIvsk, recipient, contractAddress, recipient); - } - async #getIndexToUseForSecret(secret: AppTaggingSecret): Promise { // If we have the tagging index in the cache, we use it. If not we obtain it from the execution data provider. const lastUsedIndexInTx = this.taggingIndexCache.getLastUsedIndex(secret); diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 8ddf690bbb3e..afef9c61238b 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -125,8 +125,15 @@ export abstract class BaseWallet implements Wallet { protected log = createLogger('wallet-sdk:base_wallet'), ) {} - protected scopesFrom(from: AztecAddress | NoFrom, additionalScopes: AztecAddress[] = []): AztecAddress[] { - const allScopes = from === NO_FROM ? additionalScopes : [from, ...additionalScopes]; + protected scopesFrom( + from: AztecAddress | NoFrom, + additionalScopes: AztecAddress[] = [], + sendMessagesAs?: AztecAddress, + ): AztecAddress[] { + // The sendMessageAs account must be in scope so that its tagging secrets can be accessed. + const tagSenderScopes = sendMessagesAs ? [sendMessagesAs] : []; + const baseScopes = from === NO_FROM ? [] : [from]; + const allScopes = [...baseScopes, ...additionalScopes, ...tagSenderScopes]; const scopeSet = new Set(allScopes.map(address => address.toString())); return [...scopeSet].map(AztecAddress.fromStringUnsafe); } @@ -402,7 +409,7 @@ export abstract class BaseWallet implements Wallet { simulatePublic: true, skipTxValidation: opts.skipTxValidation, skipFeeEnforcement: opts.skipFeeEnforcement, - scopes: this.scopesFrom(opts.from, opts.additionalScopes), + scopes: this.scopesFrom(opts.from, opts.additionalScopes, opts.sendMessagesAs), senderForTags: this.senderForTagsFrom(opts.from, opts.sendMessagesAs), overrides: opts.overrides, }); @@ -498,7 +505,7 @@ export abstract class BaseWallet implements Wallet { return this.pxe.profileTx(txRequest, { profileMode: opts.profileMode, skipProofGeneration: opts.skipProofGeneration ?? true, - scopes: this.scopesFrom(opts.from, opts.additionalScopes), + scopes: this.scopesFrom(opts.from, opts.additionalScopes, opts.sendMessagesAs), senderForTags: this.senderForTagsFrom(opts.from, opts.sendMessagesAs), }); } @@ -515,7 +522,7 @@ export abstract class BaseWallet implements Wallet { }); const txRequest = await this.createTxExecutionRequestFromPayloadAndFee(executionPayload, opts.from, feeOptions); const provenTx = await this.pxe.proveTx(txRequest, { - scopes: this.scopesFrom(opts.from, opts.additionalScopes), + scopes: this.scopesFrom(opts.from, opts.additionalScopes, opts.sendMessagesAs), senderForTags: this.senderForTagsFrom(opts.from, opts.sendMessagesAs), }); const offchainOutput = extractOffchainOutput( diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.test.ts b/yarn-project/wallets/src/embedded/embedded_wallet.test.ts index e91818763460..cea4feec1f14 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.test.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.test.ts @@ -42,7 +42,7 @@ describe('EmbeddedWallet', () => { }); describe('sendTx', () => { - it('passes sendMessagesAs as senderForTags to PXE simulation', async () => { + it('passes sendMessagesAs as senderForTags and includes it in scopes for PXE simulation', async () => { getPredictedMinFees.mockResolvedValue([new GasFees(2, 2)]); getNodeInfo.mockResolvedValue({ l1ChainId: 1, @@ -69,7 +69,10 @@ describe('EmbeddedWallet', () => { expect(simulateTx).toHaveBeenCalledWith( expect.anything(), - expect.objectContaining({ senderForTags: sendMessagesAs }), + expect.objectContaining({ + senderForTags: sendMessagesAs, + scopes: expect.arrayContaining([sendMessagesAs]), + }), ); }); diff --git a/yarn-project/wallets/src/embedded/embedded_wallet.ts b/yarn-project/wallets/src/embedded/embedded_wallet.ts index 84843021e08c..42b2080e628a 100644 --- a/yarn-project/wallets/src/embedded/embedded_wallet.ts +++ b/yarn-project/wallets/src/embedded/embedded_wallet.ts @@ -329,7 +329,7 @@ export class EmbeddedWallet extends BaseWallet { opts: SimulateViaEntrypointOptions, ): Promise { const { from, feeOptions, additionalScopes, skipTxValidation, skipFeeEnforcement, sendMessagesAs } = opts; - const scopes = this.scopesFrom(from, additionalScopes); + const scopes = this.scopesFrom(from, additionalScopes, sendMessagesAs); const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload(); const finalExecutionPayload = feeExecutionPayload