Skip to content

Commit

Permalink
feat: added indexes and a way to store/retrieve tagged secrets (#9468)
Browse files Browse the repository at this point in the history
Closes: #9365

- Allows storing the current known index for a given tagging secret.
- Returns the current index when calling `get_app_tagging_secret` so
notes can be sent with the correct one.
- Adds an oracle that returns the app tagging secrets for the whole
addressbook, including their indexes.

---------

Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com>
  • Loading branch information
Thunkar and sklppy88 authored Oct 29, 2024
1 parent 7c2d67a commit 1c685b1
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 25 deletions.
37 changes: 33 additions & 4 deletions noir-projects/aztec-nr/aztec/src/oracle/notes.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::note::{note_header::NoteHeader, note_interface::NoteInterface};

use dep::protocol_types::{address::AztecAddress, utils::arr_copy_slice};
use dep::protocol_types::{
address::AztecAddress,
indexed_tagging_secret::{INDEXED_TAGGING_SECRET_LENGTH, IndexedTaggingSecret},
utils::arr_copy_slice,
};

/// Notifies the simulator that a note has been created, so that it can be returned in future read requests in the same
/// transaction. This note should only be added to the non-volatile database if found in an actual block.
Expand Down Expand Up @@ -200,17 +204,42 @@ pub unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool {
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.
/// Includes the last known index used for tagging with this secret.
/// 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)
) -> IndexedTaggingSecret {
let result = get_app_tagging_secret_oracle(sender, recipient);
IndexedTaggingSecret::deserialize(result)
}

#[oracle(getAppTaggingSecret)]
unconstrained fn get_app_tagging_secret_oracle(
_sender: AztecAddress,
_recipient: AztecAddress,
) -> Field {}
) -> [Field; INDEXED_TAGGING_SECRET_LENGTH] {}

/// Returns the tagging secrets for a given recipient and all the senders in PXE's address book,
// siloed for the current contract address.
/// Includes the last known index used for tagging with this secret.
/// For this to work, PXE must know the ivsk_m of the recipient.
pub unconstrained fn get_app_tagging_secrets_for_senders(
recipient: AztecAddress,
) -> [IndexedTaggingSecret] {
let results = get_app_tagging_secrets_for_senders_oracle(recipient);
let mut indexed_tagging_secrets = &[];
for i in 0..results.len() {
if i % 2 != 0 {
continue;
}
indexed_tagging_secrets = indexed_tagging_secrets.push_back(
IndexedTaggingSecret::deserialize([results[i], results[i + 1]]),
);
}
indexed_tagging_secrets
}

#[oracle(getAppTaggingSecretsForSenders)]
unconstrained fn get_app_tagging_secrets_for_senders_oracle(_recipient: AztecAddress) -> [Field] {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::traits::Deserialize;
use std::meta::derive;

pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 2;

#[derive(Deserialize)]
pub struct IndexedTaggingSecret {
secret: Field,
index: u32,
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mod data;
mod storage;
mod validate;
mod meta;
mod indexed_tagging_secret;

pub use abis::kernel_circuit_public_inputs::{
KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs,
Expand Down
17 changes: 16 additions & 1 deletion yarn-project/circuit-types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import type { AztecAddress } from '@aztec/foundation/aztec-address';
import type { Fr } from '@aztec/foundation/fields';

import type { L2Block } from '../l2_block.js';
import type { FromLogType, GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js';
import type {
EncryptedL2NoteLog,
FromLogType,
GetUnencryptedLogsResponse,
L2BlockL2Logs,
LogFilter,
LogType,
} from '../logs/index.js';
import type { MerkleTreeId } from '../merkle_tree_id.js';
import type { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js';
import type { PublicDataWitness } from '../public_data_witness.js';
Expand Down Expand Up @@ -247,6 +254,14 @@ export interface AztecNode extends ProverCoordination {
*/
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse>;

/**
* Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
* @param tags - The tags to filter the logs by.
* @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
* that tag.
*/
getLogsByTags(tags: Fr[]): Promise<EncryptedL2NoteLog[][]>;

/**
* Method to submit a transaction to the p2p pool.
* @param tx - The transaction to be submitted.
Expand Down
13 changes: 5 additions & 8 deletions yarn-project/circuits.js/src/keys/derivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,12 @@ export function deriveKeys(secretKey: Fr) {
};
}

export function computeTaggingSecret(senderCompleteAddress: CompleteAddress, senderIvsk: Fq, recipient: AztecAddress) {
const senderPreaddress = computePreaddress(
senderCompleteAddress.publicKeys.hash(),
senderCompleteAddress.partialAddress,
);
export function computeTaggingSecret(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 recipientAddressPoint = computePoint(recipient);
const externalAddressPoint = computePoint(externalAddress);
const curve = new Grumpkin();
// Given A (sender) -> B (recipient) and h == preaddress
// Given A (known complete address) -> B (external address) 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())));
return curve.mul(externalAddressPoint, ivsk.add(new Fq(knownPreaddress.toBigInt())));
}
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/structs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './gas_fees.js';
export * from './gas_settings.js';
export * from './global_variables.js';
export * from './header.js';
export * from './indexed_tagging_secret.js';
export * from './kernel/combined_accumulated_data.js';
export * from './kernel/combined_constant_data.js';
export * from './kernel/enqueued_call_data.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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)];
}
}
20 changes: 20 additions & 0 deletions yarn-project/pxe/src/database/kv_pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export class KVPxeDatabase implements PxeDatabase {
#notesByTxHashAndScope: Map<string, AztecMultiMap<string, string>>;
#notesByIvpkMAndScope: Map<string, AztecMultiMap<string, string>>;

#taggingSecretIndexes: AztecMap<string, number>;

constructor(private db: AztecKVStore) {
this.#db = db;

Expand Down Expand Up @@ -111,6 +113,8 @@ export class KVPxeDatabase implements PxeDatabase {
this.#notesByTxHashAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_tx_hash`));
this.#notesByIvpkMAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_ivpk_m`));
}

this.#taggingSecretIndexes = db.openMap('tagging_secret_indices');
}

public async getContract(
Expand Down Expand Up @@ -572,4 +576,20 @@ export class KVPxeDatabase implements PxeDatabase {

return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize;
}

async incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise<void> {
const indexes = await this.getTaggingSecretsIndexes(appTaggingSecrets);
await this.db.transaction(() => {
indexes.forEach(index => {
const nextIndex = index ? index + 1 : 1;
void this.#taggingSecretIndexes.set(appTaggingSecrets.toString(), nextIndex);
});
});
}

getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise<number[]> {
return this.db.transaction(() =>
appTaggingSecrets.map(secret => this.#taggingSecretIndexes.get(secret.toString()) ?? 0),
);
}
}
4 changes: 4 additions & 0 deletions yarn-project/pxe/src/database/pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,8 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD
* @returns The estimated size in bytes of this db.
*/
estimateSize(): Promise<number>;

getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise<number[]>;

incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise<void>;
}
36 changes: 33 additions & 3 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type Fr,
type FunctionSelector,
type Header,
IndexedTaggingSecret,
type KeyValidationRequest,
type L1_TO_L2_MSG_TREE_HEIGHT,
computeTaggingSecret,
Expand Down Expand Up @@ -231,20 +232,49 @@ 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.
* Includes the last known index 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.
* @returns A siloed tagging secret that can be used to tag notes.
*/
public async getAppTaggingSecret(
contractAddress: AztecAddress,
sender: AztecAddress,
recipient: AztecAddress,
): Promise<Fr> {
): Promise<IndexedTaggingSecret> {
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]);
const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
const [index] = await this.db.getTaggingSecretsIndexes([secret]);
return new IndexedTaggingSecret(secret, index);
}

/**
* Returns the siloed tagging secrets for a given recipient and all the senders in the address book
* @param contractAddress - The contract address to silo the secret for
* @param recipient - The address receiving the notes
* @returns A list of siloed tagging secrets
*/
public async getAppTaggingSecretsForSenders(
contractAddress: AztecAddress,
recipient: AztecAddress,
): Promise<IndexedTaggingSecret[]> {
const recipientCompleteAddress = await this.getCompleteAddress(recipient);
const completeAddresses = await this.db.getCompleteAddresses();
// Filter out the addresses corresponding to accounts
const accounts = await this.keyStore.getAccounts();
const senders = completeAddresses.filter(
completeAddress => !accounts.find(account => account.equals(completeAddress.address)),
);
const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
const secrets = senders.map(({ address: sender }) => {
const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender);
return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
});
const indexes = await this.db.getTaggingSecretsIndexes(secrets);
return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i]));
}
}
9 changes: 7 additions & 2 deletions yarn-project/simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,16 @@ export class Oracle {
this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter)));
}

async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise<ACVMField> {
async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise<ACVMField[]> {
const taggingSecret = await this.typedOracle.getAppTaggingSecret(
AztecAddress.fromString(sender),
AztecAddress.fromString(recipient),
);
return toACVMField(taggingSecret);
return taggingSecret.toFields().map(toACVMField);
}

async getAppTaggingSecretsForSenders([recipient]: ACVMField[]): Promise<ACVMField[]> {
const taggingSecrets = await this.typedOracle.getAppTaggingSecretsForSenders(AztecAddress.fromString(recipient));
return taggingSecrets.flatMap(taggingSecret => taggingSecret.toFields().map(toACVMField));
}
}
7 changes: 6 additions & 1 deletion yarn-project/simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
type ContractInstance,
type Header,
type IndexedTaggingSecret,
type KeyValidationRequest,
type L1_TO_L2_MSG_TREE_HEIGHT,
} from '@aztec/circuits.js';
Expand Down Expand Up @@ -253,7 +254,11 @@ export abstract class TypedOracle {
throw new OracleMethodNotAvailableError('debugLog');
}

getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise<Fr> {
getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise<IndexedTaggingSecret> {
throw new OracleMethodNotAvailableError('getAppTaggingSecret');
}

getAppTaggingSecretsForSenders(_recipient: AztecAddress): Promise<IndexedTaggingSecret[]> {
throw new OracleMethodNotAvailableError('getAppTaggingSecretsForSenders');
}
}
19 changes: 18 additions & 1 deletion yarn-project/simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type CompleteAddress,
type ContractInstance,
type Header,
type IndexedTaggingSecret,
type KeyValidationRequest,
} from '@aztec/circuits.js';
import { type FunctionArtifact, type FunctionSelector } from '@aztec/foundation/abi';
Expand Down Expand Up @@ -196,10 +197,26 @@ export interface DBOracle extends CommitmentsDB {

/**
* Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known.
* Includes the last known index 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.
*/
getAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress): Promise<Fr>;
getAppTaggingSecret(
contractAddress: AztecAddress,
sender: AztecAddress,
recipient: AztecAddress,
): Promise<IndexedTaggingSecret>;

/**
* Returns the siloed tagging secrets for a given recipient and all the senders in the address book
* @param contractAddress - The contract address to silo the secret for
* @param recipient - The address receiving the notes
* @returns A list of siloed tagging secrets
*/
getAppTaggingSecretsForSenders(
contractAddress: AztecAddress,
recipient: AztecAddress,
): Promise<IndexedTaggingSecret[]>;
}
23 changes: 21 additions & 2 deletions yarn-project/simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {
type NullifierMembershipWitness,
type PublicDataWitness,
} from '@aztec/circuit-types';
import { type ContractInstance, type Header, type KeyValidationRequest } from '@aztec/circuits.js';
import {
type ContractInstance,
type Header,
type IndexedTaggingSecret,
type KeyValidationRequest,
} from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { type AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
Expand Down Expand Up @@ -291,12 +296,26 @@ export class ViewDataOracle extends TypedOracle {

/**
* Returns the tagging secret for a given sender and recipient pair, siloed to the current contract address.
* Includes the last known index used for tagging with this secret.
* 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> {
public override async getAppTaggingSecret(
sender: AztecAddress,
recipient: AztecAddress,
): Promise<IndexedTaggingSecret> {
return await this.db.getAppTaggingSecret(this.contractAddress, sender, recipient);
}

/**
* Returns the siloed tagging secrets for a given recipient and all the senders in the address book
* @param contractAddress - The contract address to silo the secret for
* @param recipient - The address receiving the notes
* @returns A list of siloed tagging secrets
*/
public override async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise<IndexedTaggingSecret[]> {
return await this.db.getAppTaggingSecretsForSenders(this.contractAddress, recipient);
}
}
Loading

0 comments on commit 1c685b1

Please sign in to comment.