Skip to content

Commit

Permalink
feat: Note tagging oracle (#9429)
Browse files Browse the repository at this point in the history
Closes: #9378

Creates an exposes an oracle call that allows a sender to create a
tagging secret for a recipient, siloing it for the current app.
  • Loading branch information
Thunkar authored Oct 25, 2024
1 parent ad80169 commit cec6306
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 45 deletions.
16 changes: 16 additions & 0 deletions noir-projects/aztec-nr/aztec/src/oracle/notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
14 changes: 14 additions & 0 deletions yarn-project/circuits.js/src/keys/derivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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())));
}
16 changes: 9 additions & 7 deletions yarn-project/key-store/src/key_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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"`,
Expand All @@ -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"`,
);
});
Expand Down
16 changes: 5 additions & 11 deletions yarn-project/key-store/src/key_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fr> {
public async getMasterIncomingViewingSecretKey(account: AztecAddress): Promise<GrumpkinScalar> {
const masterIncomingViewingSecretKeyBuffer = this.#keys.get(`${account.toString()}-ivsk_m`);
if (!masterIncomingViewingSecretKeyBuffer) {
throw new Error(
Expand All @@ -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);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -226,4 +228,23 @@ export class SimulatorOracle implements DBOracle {
public getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise<string> {
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<Fr> {
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]);
}
}
8 changes: 8 additions & 0 deletions yarn-project/simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,12 @@ export class Oracle {
notifySetMinRevertibleSideEffectCounter([minRevertibleSideEffectCounter]: ACVMField[]) {
this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter)));
}

async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise<ACVMField> {
const taggingSecret = await this.typedOracle.getAppTaggingSecret(
AztecAddress.fromString(sender),
AztecAddress.fromString(recipient),
);
return toACVMField(taggingSecret);
}
}
4 changes: 4 additions & 0 deletions yarn-project/simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,8 @@ export abstract class TypedOracle {
debugLog(_message: string, _fields: Fr[]): void {
throw new OracleMethodNotAvailableError('debugLog');
}

getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise<Fr> {
throw new OracleMethodNotAvailableError('getAppTaggingSecret');
}
}
27 changes: 0 additions & 27 deletions yarn-project/simulator/src/client/client_execution_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fr[]> {
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)}`);
}
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,13 @@ export interface DBOracle extends CommitmentsDB {
* @returns The block number.
*/
getBlockNumber(): Promise<number>;

/**
* 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<Fr>;
}
11 changes: 11 additions & 0 deletions yarn-project/simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fr> {
return await this.db.getAppTaggingSecret(this.contractAddress, sender, recipient);
}
}
10 changes: 10 additions & 0 deletions yarn-project/txe/src/oracle/txe_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
type PublicDataTreeLeafPreimage,
TxContext,
computeContractClassId,
computeTaggingSecret,
deriveKeys,
getContractClassFromArtifact,
} from '@aztec/circuits.js';
Expand All @@ -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';
Expand Down Expand Up @@ -747,6 +749,14 @@ export class TXE implements TypedOracle {
return;
}

async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise<Fr> {
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(
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/txe/src/txe_service/txe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit cec6306

Please sign in to comment.