diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 90c3dcad862..642dcb4a0e0 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -198,3 +198,19 @@ pub unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool { #[oracle(checkNullifierExists)] 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. +/// 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) +} + +#[oracle(getAppTaggingSecret)] +unconstrained fn get_app_tagging_secret_oracle( + _sender: AztecAddress, + _recipient: AztecAddress, +) -> Field {} diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index 1caff8bff86..ae298c208b4 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -4,6 +4,7 @@ import { Fq, Fr, GrumpkinScalar, Point } from '@aztec/foundation/fields'; import { Grumpkin } from '../barretenberg/crypto/grumpkin/index.js'; import { GeneratorIndex } from '../constants.gen.js'; +import { type CompleteAddress } from '../index.js'; import { PublicKeys } from '../types/public_keys.js'; import { type KeyPrefix } from './key_types.js'; import { getKeyGenerator } from './utils.js'; @@ -125,3 +126,16 @@ export function deriveKeys(secretKey: Fr) { publicKeys, }; } + +export function computeTaggingSecret(senderCompleteAddress: CompleteAddress, senderIvsk: Fq, recipient: AztecAddress) { + const senderPreaddress = computePreaddress( + senderCompleteAddress.publicKeys.hash(), + senderCompleteAddress.partialAddress, + ); + // TODO: #8970 - Computation of address point from x coordinate might fail + const recipientAddressPoint = computePoint(recipient); + const curve = new Grumpkin(); + // Given A (sender) -> B (recipient) 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()))); +} diff --git a/yarn-project/key-store/src/key_store.test.ts b/yarn-project/key-store/src/key_store.test.ts index 7c1e4ce68b5..a816660a9a7 100644 --- a/yarn-project/key-store/src/key_store.test.ts +++ b/yarn-project/key-store/src/key_store.test.ts @@ -43,6 +43,11 @@ describe('KeyStore', () => { `"0x07cec19d32f1cbaaacf16edc081021b696c86dff14160779373ffc77b04568e7076f25b0e7f0d02fd6433d788483e2262c1e45c5962790b40d1cd7efbd5253d3"`, ); + const masterIncomingViewingSecretKey = await keyStore.getMasterIncomingViewingSecretKey(accountAddress); + expect(masterIncomingViewingSecretKey.toString()).toMatchInlineSnapshot( + `"0x1d1d920024dd64e019c23de36d27aefe4d9d4d05983b99cf85bea9e85fd60020"`, + ); + // Arbitrary app contract address const appAddress = AztecAddress.fromBigInt(624n); @@ -53,11 +58,6 @@ describe('KeyStore', () => { ); expect(obtainedMasterNullifierPublicKey).toEqual(masterNullifierPublicKey); - const appIncomingViewingSecretKey = await keyStore.getAppIncomingViewingSecretKey(accountAddress, appAddress); - expect(appIncomingViewingSecretKey.toString()).toMatchInlineSnapshot( - `"0x0247d73d16cf0939cc783b3cee140b37b294b6cbc1c0295d530f3f637c9b8034"`, - ); - const appOutgoingViewingSecretKey = await keyStore.getAppOutgoingViewingSecretKey(accountAddress, appAddress); expect(appOutgoingViewingSecretKey.toString()).toMatchInlineSnapshot( `"0x296c9931262d8b95b4cbbcc66ac4c97d2cc3fab4da5eedc08fcff80f1ce37e34"`, @@ -76,8 +76,10 @@ describe('KeyStore', () => { ); // Manages to find master incoming viewing secret key for pub key - const masterIncomingViewingSecretKey = await keyStore.getMasterSecretKey(masterIncomingViewingPublicKey); - expect(masterIncomingViewingSecretKey.toString()).toMatchInlineSnapshot( + const masterIncomingViewingSecretKeyFromPublicKey = await keyStore.getMasterSecretKey( + masterIncomingViewingPublicKey, + ); + expect(masterIncomingViewingSecretKeyFromPublicKey.toString()).toMatchInlineSnapshot( `"0x1d1d920024dd64e019c23de36d27aefe4d9d4d05983b99cf85bea9e85fd60020"`, ); }); diff --git a/yarn-project/key-store/src/key_store.ts b/yarn-project/key-store/src/key_store.ts index c6ee5e1b121..c0d41bd9318 100644 --- a/yarn-project/key-store/src/key_store.ts +++ b/yarn-project/key-store/src/key_store.ts @@ -205,13 +205,12 @@ export class KeyStore { } /** - * Retrieves application incoming viewing secret key. + * Retrieves master incoming viewing secret key. * @throws If the account does not exist in the key store. - * @param account - The account to retrieve the application incoming viewing secret key for. - * @param app - The application address to retrieve the incoming viewing secret key for. - * @returns A Promise that resolves to the application incoming viewing secret key. + * @param account - The account to retrieve the master incoming viewing secret key for. + * @returns A Promise that resolves to the master incoming viewing secret key. */ - public async getAppIncomingViewingSecretKey(account: AztecAddress, app: AztecAddress): Promise { + public async getMasterIncomingViewingSecretKey(account: AztecAddress): Promise { const masterIncomingViewingSecretKeyBuffer = this.#keys.get(`${account.toString()}-ivsk_m`); if (!masterIncomingViewingSecretKeyBuffer) { throw new Error( @@ -220,12 +219,7 @@ export class KeyStore { } const masterIncomingViewingSecretKey = GrumpkinScalar.fromBuffer(masterIncomingViewingSecretKeyBuffer); - return Promise.resolve( - poseidon2HashWithSeparator( - [masterIncomingViewingSecretKey.hi, masterIncomingViewingSecretKey.lo, app], - GeneratorIndex.IVSK_M, - ), - ); + return Promise.resolve(masterIncomingViewingSecretKey); } /** diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index fac1a6a43ec..1b18b32c318 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -16,8 +16,10 @@ import { type Header, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, + computeTaggingSecret, } from '@aztec/circuits.js'; import { type FunctionArtifact, getFunctionArtifact } from '@aztec/foundation/abi'; +import { poseidon2Hash } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; import { type KeyStore } from '@aztec/key-store'; import { type DBOracle, MessageLoadOracleInputs } from '@aztec/simulator'; @@ -226,4 +228,23 @@ export class SimulatorOracle implements DBOracle { public getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise { return this.contractDataOracle.getDebugFunctionName(contractAddress, selector); } + + /** + * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_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 + * @returns A tagging secret that can be used to tag notes. + */ + public async getAppTaggingSecret( + contractAddress: AztecAddress, + 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, contractAddress]); + } } diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index 103ac0a8f55..e3c315f0708 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -408,4 +408,12 @@ export class Oracle { notifySetMinRevertibleSideEffectCounter([minRevertibleSideEffectCounter]: ACVMField[]) { this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter))); } + + async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { + const taggingSecret = await this.typedOracle.getAppTaggingSecret( + AztecAddress.fromString(sender), + AztecAddress.fromString(recipient), + ); + return toACVMField(taggingSecret); + } } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index ed3eb107b0c..1e66f0f150c 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -252,4 +252,8 @@ export abstract class TypedOracle { debugLog(_message: string, _fields: Fr[]): void { throw new OracleMethodNotAvailableError('debugLog'); } + + getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise { + throw new OracleMethodNotAvailableError('getAppTaggingSecret'); + } } diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index bd729f1dccb..06c51c9b0bf 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -602,33 +602,6 @@ export class ClientExecutionContext extends ViewDataOracle { ); } - /** - * Read the public storage data. - * @param contractAddress - The address to read storage from. - * @param startStorageSlot - The starting storage slot. - * @param blockNumber - The block number to read storage at. - * @param numberOfElements - Number of elements to read from the starting storage slot. - */ - public override async storageRead( - contractAddress: Fr, - startStorageSlot: Fr, - blockNumber: number, - numberOfElements: number, - ): Promise { - const values = []; - for (let i = 0n; i < numberOfElements; i++) { - const storageSlot = new Fr(startStorageSlot.value + i); - - const value = await this.aztecNode.getPublicStorageAt(contractAddress, storageSlot, blockNumber); - this.log.debug( - `Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`, - ); - - values.push(value); - } - return values; - } - public override debugLog(message: string, fields: Fr[]) { this.log.verbose(`debug_log ${applyStringFormatting(message, fields)}`); } diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index 6d44cfe39c9..c80d4fed5df 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -193,4 +193,13 @@ export interface DBOracle extends CommitmentsDB { * @returns The block number. */ 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. + * @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; } diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index d4d1f7364e8..644c4b8b6bd 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -288,4 +288,15 @@ export class ViewDataOracle extends TypedOracle { const formattedStr = applyStringFormatting(message, fields); this.log.verbose(`debug_log ${formattedStr}`); } + + /** + * Returns the tagging secret for a given sender and recipient pair, siloed to the current contract address. + * 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 { + return await this.db.getAppTaggingSecret(this.contractAddress, sender, recipient); + } } diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 027f60b3630..3823d6d940d 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -30,6 +30,7 @@ import { type PublicDataTreeLeafPreimage, TxContext, computeContractClassId, + computeTaggingSecret, deriveKeys, getContractClassFromArtifact, } from '@aztec/circuits.js'; @@ -43,6 +44,7 @@ import { countArgumentsSize, } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { poseidon2Hash } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; @@ -747,6 +749,14 @@ export class TXE implements TypedOracle { return; } + 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]); + } + // AVM oracles async avmOpcodeCall( diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 13cc7637562..3469c9ad6aa 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -599,6 +599,14 @@ export class TXEService { return toForeignCallResult([]); } + async getAppTaggingSecret(sender: ForeignCallSingle, recipient: ForeignCallSingle) { + const secret = await this.typedOracle.getAppTaggingSecret( + AztecAddress.fromField(fromSingle(sender)), + AztecAddress.fromField(fromSingle(recipient)), + ); + return toForeignCallResult([toSingle(secret)]); + } + // AVM opcodes avmOpcodeEmitUnencryptedLog(_message: ForeignCallArray) {