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: added indexes and a way to store/retrieve tagged secrets #9468

Merged
merged 9 commits into from
Oct 29, 2024
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.
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
/// 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]]),
);
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
}
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) {
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
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>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good for this to mention whether this is the last index or the next index


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> {
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
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();
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
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
Loading