Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Note tagging oracle #9429

Merged
merged 11 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
nventuro marked this conversation as resolved.
Show resolved Hide resolved
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 {
);
}

/**
nventuro marked this conversation as resolved.
Show resolved Hide resolved
* 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
Loading