diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index e505e00f6754..8be168fa5589 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -48,8 +48,7 @@ export { GlobalVariables, } from '@aztec/stdlib/tx'; export { Body, L2Block } from '@aztec/stdlib/block'; -export { LogId, type LogFilter, EncryptedLogPayload } from '@aztec/stdlib/logs'; -export { L1EventPayload, EventMetadata } from '@aztec/stdlib/event'; +export { LogId, type LogFilter } from '@aztec/stdlib/logs'; export { L1ToL2Message, L2Actor, L1Actor } from '@aztec/stdlib/messaging'; export { UniqueNote, ExtendedNote, Comparator, Note } from '@aztec/stdlib/note'; export { type PXE, EventType } from '@aztec/stdlib/interfaces/client'; diff --git a/yarn-project/builder/src/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts index 0887a89c1574..51b0a206ebed 100644 --- a/yarn-project/builder/src/contract-interface-gen/typescript.ts +++ b/yarn-project/builder/src/contract-interface-gen/typescript.ts @@ -344,7 +344,6 @@ import { type FieldLike, Fr, type FunctionSelectorLike, - L1EventPayload, loadContractArtifact, loadContractArtifactForPublic, type NoirCompiledContract, diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 8531bf0d61a5..3840c06eb70b 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -2,14 +2,12 @@ import { type InitialAccountData, deployFundedSchnorrAccount } from '@aztec/acco import type { AztecNodeService } from '@aztec/aztec-node'; import { type AccountWallet, - AccountWalletWithSecretKey, type AztecAddress, type AztecNode, ContractDeployer, ContractFunctionInteraction, Fr, type GlobalVariables, - L1EventPayload, type Logger, type PXE, TxStatus, @@ -420,13 +418,27 @@ describe('e2e_block_building', () => { afterAll(() => teardown()); it('calls a method with nested encrypted logs', async () => { - const thisWallet = new AccountWalletWithSecretKey(pxe, ownerWallet, owner.secret, owner.salt); const address = owner.address; + const values = { + value0: 5n, + value1: 4n, + value2: 3n, + value3: 2n, + value4: 1n, + }; + const nestedValues = { + value0: 0n, + value1: 0n, + value2: 0n, + value3: 0n, + value4: 0n, + }; + // call test contract - const values = [new Fr(5), new Fr(4), new Fr(3), new Fr(2), new Fr(1)]; - const nestedValues = [new Fr(0), new Fr(0), new Fr(0), new Fr(0), new Fr(0)]; - const action = testContract.methods.emit_array_as_encrypted_log(values, address, address, true); + const valuesAsArray = Object.values(values); + + const action = testContract.methods.emit_array_as_encrypted_log(valuesAsArray, address, address, true); const tx = await action.prove(); const rct = await tx.send().wait(); @@ -436,16 +448,23 @@ describe('e2e_block_building', () => { expect(privateLogs.length).toBe(3); // The first two logs are encrypted. - const event0 = (await L1EventPayload.decryptAsIncoming(privateLogs[0], await thisWallet.getEncryptionSecret()))!; - expect(event0.event.items).toEqual(values); - - const event1 = (await L1EventPayload.decryptAsIncoming(privateLogs[1], await thisWallet.getEncryptionSecret()))!; - expect(event1.event.items).toEqual(nestedValues); + const events = await pxe.getPrivateEvents( + testContract.address, + TestContract.events.ExampleEvent, + rct.blockNumber!, + 1, + [address], + ); + expect(events[0]).toEqual(values); + expect(events[1]).toEqual(nestedValues); // The last log is not encrypted. // The first field is the first value and is siloed with contract address by the kernel circuit. - const expectedFirstField = await poseidon2Hash([testContract.address, values[0]]); - expect(privateLogs[2].fields.slice(0, 5)).toEqual([expectedFirstField, ...values.slice(1)]); + const expectedFirstField = await poseidon2Hash([testContract.address, valuesAsArray[0]]); + expect(privateLogs[2].fields.slice(0, 5).map((f: Fr) => f.toBigInt())).toEqual([ + expectedFirstField.toBigInt(), + ...valuesAsArray.slice(1), + ]); }, 60_000); }); diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 541cad281d91..e4cdea174ee5 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -37,7 +37,6 @@ import { getContractClassFromArtifact, } from '@aztec/stdlib/contract'; import { SimulationError } from '@aztec/stdlib/errors'; -import { EventMetadata } from '@aztec/stdlib/event'; import type { GasFees } from '@aztec/stdlib/gas'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { @@ -915,7 +914,6 @@ export class PXEService implements PXE { } async getPublicEvents(eventMetadataDef: EventMetadataDefinition, from: number, limit: number): Promise { - const eventMetadata = new EventMetadata(eventMetadataDef); const { logs } = await this.node.getPublicLogs({ fromBlock: from, toBlock: from + limit, @@ -924,10 +922,10 @@ export class PXEService implements PXE { const decodedEvents = logs .map(log => { // +1 for the event selector - const expectedLength = eventMetadata.fieldNames.length + 1; + const expectedLength = eventMetadataDef.fieldNames.length + 1; const logFields = log.log.log.slice(0, expectedLength); // We are assuming here that event logs are the last 4 bytes of the event. This is not enshrined but is a function of aztec.nr raw log emission. - if (!EventSelector.fromField(logFields[logFields.length - 1]).equals(eventMetadata.eventSelector)) { + if (!EventSelector.fromField(logFields[logFields.length - 1]).equals(eventMetadataDef.eventSelector)) { return undefined; } // If any of the remaining fields, are non-zero, the payload does match expected: @@ -937,7 +935,7 @@ export class PXEService implements PXE { ); } - return eventMetadata.decode(log.log); + return decodeFromAbi([eventMetadataDef.abiType], log.log.log) as T; }) .filter(log => log !== undefined) as T[]; diff --git a/yarn-project/stdlib/package.json b/yarn-project/stdlib/package.json index 87714340cdc1..5a6abf4cb682 100644 --- a/yarn-project/stdlib/package.json +++ b/yarn-project/stdlib/package.json @@ -34,7 +34,6 @@ "./tx": "./dest/tx/index.js", "./fees": "./dest/fees/index.js", "./note": "./dest/note/index.js", - "./event": "./dest/event/index.js", "./p2p": "./dest/p2p/index.js", "./errors": "./dest/errors/index.js", "./stats": "./dest/stats/index.js", diff --git a/yarn-project/stdlib/src/event/event.test.ts b/yarn-project/stdlib/src/event/event.test.ts deleted file mode 100644 index 4a2b85af030e..000000000000 --- a/yarn-project/stdlib/src/event/event.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; - -import { Event } from './event.js'; - -describe('event', () => { - it('convert to and from buffer', () => { - const fields = Array.from({ length: 5 }).map(() => Fr.random()); - const event = new Event(fields); - const buf = event.toBuffer(); - expect(Event.fromBuffer(buf)).toEqual(event); - }); -}); diff --git a/yarn-project/stdlib/src/event/event.ts b/yarn-project/stdlib/src/event/event.ts deleted file mode 100644 index 3b0d94c31c2a..000000000000 --- a/yarn-project/stdlib/src/event/event.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; -import { BufferReader } from '@aztec/foundation/serialize'; - -import { Payload } from '../logs/l1_payload/payload.js'; -import { schemas } from '../schemas/schemas.js'; - -export class Event extends Payload { - static override get schema() { - return schemas.Buffer.transform(Event.fromBuffer); - } - - static override fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new Event(reader.readVector(Fr)); - } -} diff --git a/yarn-project/stdlib/src/event/event_metadata.ts b/yarn-project/stdlib/src/event/event_metadata.ts deleted file mode 100644 index bf23b9108642..000000000000 --- a/yarn-project/stdlib/src/event/event_metadata.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from 'zod'; - -import { type AbiType, AbiTypeSchema } from '../abi/abi.js'; -import { decodeFromAbi } from '../abi/decoder.js'; -import type { EventSelector } from '../abi/event_selector.js'; -import type { PublicLog } from '../logs/public_log.js'; -import { schemas } from '../schemas/index.js'; -import { L1EventPayload } from './l1_event_payload.js'; - -/** - * Represents metadata for an event decoder, including all information needed to reconstruct it. - */ -export class EventMetadata { - public readonly decode: (payload: L1EventPayload | PublicLog) => T | undefined; - - public readonly eventSelector: EventSelector; - public readonly abiType: AbiType; - public readonly fieldNames: string[]; - - constructor(event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] }) { - this.eventSelector = event.eventSelector; - this.abiType = event.abiType; - this.fieldNames = event.fieldNames; - this.decode = EventMetadata.decodeEvent(event.eventSelector, event.abiType); - } - - public static decodeEvent( - eventSelector: EventSelector, - abiType: AbiType, - ): (payload: L1EventPayload | PublicLog | undefined) => T | undefined { - return (payload: L1EventPayload | PublicLog | undefined): T | undefined => { - if (payload === undefined) { - return undefined; - } - - if (payload instanceof L1EventPayload) { - if (!eventSelector.equals(payload.eventTypeId)) { - return undefined; - } - return decodeFromAbi([abiType], payload.event.items) as T; - } else { - return decodeFromAbi([abiType], payload.log) as T; - } - }; - } - - static get schema() { - return z - .object({ - eventSelector: schemas.EventSelector, - abiType: AbiTypeSchema, - fieldNames: z.array(z.string()), - }) - .transform(obj => new EventMetadata(obj)); - } -} diff --git a/yarn-project/stdlib/src/event/index.ts b/yarn-project/stdlib/src/event/index.ts deleted file mode 100644 index b88d89e0669a..000000000000 --- a/yarn-project/stdlib/src/event/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './event.js'; -export * from './event_metadata.js'; -export * from './l1_event_payload.js'; diff --git a/yarn-project/stdlib/src/event/l1_event_payload.ts b/yarn-project/stdlib/src/event/l1_event_payload.ts deleted file mode 100644 index d8b3ac119863..000000000000 --- a/yarn-project/stdlib/src/event/l1_event_payload.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { type Fq, Fr } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { EventSelector } from '../abi/event_selector.js'; -import { AztecAddress } from '../aztec-address/index.js'; -import { EncryptedLogPayload } from '../logs/l1_payload/encrypted_log_payload.js'; -import type { PrivateLog } from '../logs/private_log.js'; -import { Event } from './event.js'; - -/** - * A class which wraps event data which is pushed on L1. - */ -export class L1EventPayload { - constructor( - /** - * A event as emitted from Noir contract. Can be used along with private key to compute nullifier. - */ - public event: Event, - /** - * Address of the contract this tx is interacting with. - */ - public contractAddress: AztecAddress, - /** - * Type identifier for the underlying event, required to determine how to compute its hash and nullifier. - */ - public eventTypeId: EventSelector, - ) {} - - static #fromIncomingBodyPlaintextAndContractAddress( - plaintext: Buffer, - contractAddress: AztecAddress, - ): L1EventPayload | undefined { - let payload: L1EventPayload; - try { - const reader = BufferReader.asReader(plaintext); - const fields = reader.readArray(plaintext.length / Fr.SIZE_IN_BYTES, Fr); - - const eventTypeId = EventSelector.fromField(fields[0]); - - const event = new Event(fields.slice(1)); - - payload = new L1EventPayload(event, contractAddress, eventTypeId); - } catch (e) { - return undefined; - } - - return payload; - } - - static async decryptAsIncoming(log: PrivateLog, sk: Fq): Promise { - const decryptedLog = await EncryptedLogPayload.decryptAsIncoming(log.fields, sk); - if (!decryptedLog) { - return undefined; - } - - return this.#fromIncomingBodyPlaintextAndContractAddress( - decryptedLog.incomingBodyPlaintext, - decryptedLog.contractAddress, - ); - } - - /** - * Serializes the L1EventPayload object into a Buffer. - * @returns Buffer representation of the L1EventPayload object. - */ - toIncomingBodyPlaintext() { - const fields = [this.eventTypeId.toField(), ...this.event.items]; - return serializeToBuffer(fields); - } - - /** - * Create a random L1EventPayload object (useful for testing purposes). - * @param contract - The address of a contract the event was emitted from. - * @returns A random L1EventPayload object. - */ - static async random(contract?: AztecAddress) { - return new L1EventPayload(Event.random(), contract ?? (await AztecAddress.random()), EventSelector.random()); - } - - public equals(other: L1EventPayload) { - return ( - this.event.equals(other.event) && - this.contractAddress.equals(other.contractAddress) && - this.eventTypeId.equals(other.eventTypeId) - ); - } -} diff --git a/yarn-project/stdlib/src/interfaces/pxe.ts b/yarn-project/stdlib/src/interfaces/pxe.ts index 7bdb765c95bc..eb590733b9dd 100644 --- a/yarn-project/stdlib/src/interfaces/pxe.ts +++ b/yarn-project/stdlib/src/interfaces/pxe.ts @@ -129,7 +129,7 @@ export interface PXE { /** * Proves the private portion of a simulated transaction, ready to send to the network - * (where valiators prove the public portion). + * (where validators prove the public portion). * * @param txRequest - An authenticated tx request ready for proving * @param privateExecutionResult - The result of the private execution of the transaction diff --git a/yarn-project/stdlib/src/logs/index.ts b/yarn-project/stdlib/src/logs/index.ts index 9a56f9043602..a44a9190502f 100644 --- a/yarn-project/stdlib/src/logs/index.ts +++ b/yarn-project/stdlib/src/logs/index.ts @@ -7,5 +7,5 @@ export * from './log_id.js'; export * from './log_filter.js'; export * from './extended_public_log.js'; export * from './extended_contract_class_log.js'; -export * from './l1_payload/index.js'; +export * from './shared_secret_derivation.js'; export * from './tx_scoped_l2_log.js'; diff --git a/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.test.ts b/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.test.ts deleted file mode 100644 index 3bf200bf701f..000000000000 --- a/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { randomBytes } from '@aztec/foundation/crypto'; -import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; -import { updateInlineTestData } from '@aztec/foundation/testing/files'; - -import { AztecAddress } from '../../aztec-address/index.js'; -import { CompleteAddress } from '../../contract/complete_address.js'; -import { computeAddressSecret, deriveKeys } from '../../keys/derivation.js'; -import { IndexedTaggingSecret, PrivateLog } from '../index.js'; -import { EncryptedLogPayload } from './encrypted_log_payload.js'; - -// placeholder value until tagging is implemented -const PLACEHOLDER_TAG = new Fr(33); - -describe('EncryptedLogPayload', () => { - describe('encrypt and decrypt a full log', () => { - let completeAddress: CompleteAddress; - let ivskM: GrumpkinScalar; - - let original: EncryptedLogPayload; - let payload: PrivateLog; - - beforeAll(async () => { - const incomingBodyPlaintext = randomBytes(128); - const contractAddress = await AztecAddress.random(); - - original = new EncryptedLogPayload(PLACEHOLDER_TAG, contractAddress, incomingBodyPlaintext); - - const secretKey = Fr.random(); - const partialAddress = Fr.random(); - ({ masterIncomingViewingSecretKey: ivskM } = await deriveKeys(secretKey)); - - completeAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(secretKey, partialAddress); - - const ephSk = GrumpkinScalar.random(); - - payload = await original.generatePayload(ephSk, completeAddress.address); - }); - - it('decrypt a log as incoming', async () => { - const addressSecret = await computeAddressSecret(await completeAddress.getPreaddress(), ivskM); - - const recreated = await EncryptedLogPayload.decryptAsIncoming(payload.fields, addressSecret); - - expect(recreated?.toBuffer()).toEqual(original.toBuffer()); - }); - }); - - it('encrypted tagged log matches Noir', async () => { - // All the values in this test were arbitrarily set and copied over to `payload.nr` - const contractAddress = AztecAddress.fromString( - '0x10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e04', - ); - - // This plaintext is taken from a MockNote, created in the corresponding noir test in aztec-packages/noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr. - // storage slot = 42 = 0x2a - // note_type_id = 76 = 0x4c - // value = 1234 = 0x04d2 - const plaintext = Buffer.from( - '000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000000000000000000000000000000000000000004d2', - 'hex', - ); - - // We set a random secret, as it is simply the result of an oracle call, and we are not actually computing this in nr. - const logTag = await new IndexedTaggingSecret(new Fr(69420), 1337).computeTag( - AztecAddress.fromBigInt(0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70cn), - ); - const log = new EncryptedLogPayload(logTag, contractAddress, plaintext); - - const ephSk = new GrumpkinScalar(0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538n); - - const recipientCompleteAddress = await CompleteAddress.fromString( - '0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c138af8799f2fba962549802469e12e3b7ba4c5f9c999c6421e05c73f45ec68481970dd8ce0250b677759dfc040f6edaf77c5827a7bcd425e66bcdec3fa7e59bc18dd22d6a4032eefe3a7a55703f583396596235f7c186e450c92981186ee74042e49e00996565114016a1a478309842ecbaf930fb716c3f498e7e10370631d7507f696b8b233de2c1935e43c793399586f532da5ff7c0356636a75acb862e964156e8a3e42bfca3663936ba98c7fd26386a14657c23b5f5146f1a94b6c4651542685ea16f17c580a7cc7c8ff2688dce9bde8bf1f50475f4c3281e1c33404ee0025f50db0733f719462b22eff03cec746bb9e3829ae3636c84fbccd2754b5a5a92087a5f41ccf94a03a2671cd341ba3264c45147e75d4ea96e3b1a58498550b89', - ); - - const fixedRand = (len: number) => { - // The random values in the noir test file after the overhead are filled with 1s. - return Buffer.from(Array(len).fill(1)); - }; - - const payload = await log.generatePayload(ephSk, recipientCompleteAddress.address, fixedRand); - - expect(payload.toBuffer().toString('hex')).toMatchInlineSnapshot( - `"0e9cffc3ddd746affb02410d8f0a823e89939785bcc8e88ee4f3cae05e737c360d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa7000194e6d7872db8f61e8e59f23580f4db45d13677f873ec473a409cf61fd04d00334e5fb6083721f3eb4eef500876af3c9acfab0a1cb1804b930606fdb0b28300af91db798fa320746831a59b74362dfd0cf9e7c239f6aad11a4b47d0d870ee00d25a054613a83be7be8512f2c09664bc4f7ab60a127b06584f476918581b8a003840d100d8c1d78d4b68b787ed353ebfb8cd2987503d3b472f614f25799a18003f38322629d4010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"`, - ); - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - const fieldArrayStr = `[${payload.fields.map(f => f.toString()).join(',')}]`; - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr', - 'private_log_payload_from_typescript', - fieldArrayStr, - ); - - const ivskM = new GrumpkinScalar(0x0d6e27b21c89a7632f7766e35cc280d43f75bea3898d7328400a5fefc804d462n); - - const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); - const recreated = await EncryptedLogPayload.decryptAsIncoming(payload.fields, addressSecret); - expect(recreated?.toBuffer()).toEqual(log.toBuffer()); - }); -}); diff --git a/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.ts b/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.ts deleted file mode 100644 index 0bd2e4ad3fe2..000000000000 --- a/yarn-project/stdlib/src/logs/l1_payload/encrypted_log_payload.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; -import { randomBytes } from '@aztec/foundation/crypto'; -import { Fr, type GrumpkinScalar, NotOnCurveError, Point } from '@aztec/foundation/fields'; -import { BufferReader, type Tuple, numToUInt16BE, serializeToBuffer } from '@aztec/foundation/serialize'; - -import { AztecAddress } from '../../aztec-address/index.js'; -import { derivePublicKeyFromSecretKey } from '../../keys/derivation.js'; -import { PrivateLog } from '../private_log.js'; -import { - aes128Decrypt, - aes128Encrypt, - deriveAesSymmetricKeyAndIvFromEcdhSharedSecretUsingSha256, -} from './encryption_util.js'; -import { deriveEcdhSharedSecret, deriveEcdhSharedSecretUsingAztecAddress } from './shared_secret_derivation.js'; - -// Below constants should match the values defined in aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr. -// Note: we will soon be 'abstracting' log processing: apps will process their own logs, instead of the PXE processing all apps' logs. Therefore, this file will imminently change considerably. - -const TAG_SIZE_IN_FIELDS = 1; -const EPK_SIZE_IN_FIELDS = 1; - -const USABLE_PRIVATE_LOG_SIZE_IN_FIELDS = PRIVATE_LOG_SIZE_IN_FIELDS - TAG_SIZE_IN_FIELDS - EPK_SIZE_IN_FIELDS; -const USABLE_PRIVATE_LOG_SIZE_IN_BYTES = ((USABLE_PRIVATE_LOG_SIZE_IN_FIELDS * 31) / 16) * 16; - -// The incoming header ciphertext is 48 bytes -// 32 bytes for the address, and 16 bytes padding to follow PKCS#7 -const HEADER_CIPHERTEXT_SIZE_IN_BYTES = 48; -const USABLE_PLAINTEXT_SIZE_IN_BYTES = USABLE_PRIVATE_LOG_SIZE_IN_BYTES - HEADER_CIPHERTEXT_SIZE_IN_BYTES; - -const CONTRACT_ADDRESS_SIZE_IN_BYTES = 32; - -const SIZE_OF_ENCODING_OF_CIPHERTEXT_SIZE_IN_BYTES = 2; - -function beBytes31ToFields(bytes: Buffer): Fr[] { - const fields = []; - const numFields = Math.ceil(bytes.length / 31); - for (let i = 0; i < numFields; i++) { - fields.push(new Fr(bytes.subarray(i * 31, (i + 1) * 31))); - } - return fields; -} - -function fieldsToBEBytes31(fields: Fr[]) { - return Buffer.concat(fields.map(f => f.toBuffer().subarray(1))); -} - -/** - * Encrypted log payload with a tag used for retrieval by clients. - */ -export class EncryptedLogPayload { - constructor( - /** - * Note discovery tag. - */ - public readonly tag: Fr, - /** - * Address of a contract that emitted the log. - */ - public readonly contractAddress: AztecAddress, - /** - * Decrypted incoming body. - */ - public readonly incomingBodyPlaintext: Buffer, - ) {} - - // NB: Only appears to be used in tests - // See noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr - public async generatePayload( - ephSk: GrumpkinScalar, - recipient: AztecAddress, - rand: (len: number) => Buffer = randomBytes, - ): Promise { - const ephPk = await derivePublicKeyFromSecretKey(ephSk); - const [ephPkX, ephPkSignBool] = ephPk.toXAndSign(); - const ephPkSignU8 = Buffer.from([Number(ephPkSignBool)]); - - const ciphertextSharedSecret = await deriveEcdhSharedSecretUsingAztecAddress(ephSk, recipient); // not to be confused with the tagging shared secret - - const [symKey, iv] = deriveAesSymmetricKeyAndIvFromEcdhSharedSecretUsingSha256(ciphertextSharedSecret); - - if (this.incomingBodyPlaintext.length > USABLE_PLAINTEXT_SIZE_IN_BYTES) { - throw new Error(`Incoming body plaintext cannot be more than ${USABLE_PLAINTEXT_SIZE_IN_BYTES} bytes.`); - } - - const finalPlaintext = this.incomingBodyPlaintext; - - const ciphertextBytes = await aes128Encrypt(finalPlaintext, iv, symKey); - - const headerPlaintext = serializeToBuffer(this.contractAddress.toBuffer(), numToUInt16BE(ciphertextBytes.length)); - - // TODO: it is unsafe to re-use the same iv and symKey. We'll need to do something cleverer. - const headerCiphertextBytes = await aes128Encrypt(headerPlaintext, iv, symKey); - - if (headerCiphertextBytes.length !== HEADER_CIPHERTEXT_SIZE_IN_BYTES) { - throw new Error(`Invalid header ciphertext size: ${headerCiphertextBytes.length}`); - } - - const properLogBytesLength = 1 /* ephPkSignU8 */ + HEADER_CIPHERTEXT_SIZE_IN_BYTES + ciphertextBytes.length; - - const logBytesPaddingToMult31 = rand(31 * Math.ceil(properLogBytesLength / 31) - properLogBytesLength); - - const logBytes = serializeToBuffer(ephPkSignU8, headerCiphertextBytes, ciphertextBytes, logBytesPaddingToMult31); - - if (logBytes.length % 31 !== 0) { - throw new Error(`logBytes.length should be divisible by 31, got: ${logBytes.length}`); - } - - const fieldsPadding = Array.from({ length: USABLE_PRIVATE_LOG_SIZE_IN_FIELDS - logBytes.length / 31 }, () => - Fr.fromBuffer(rand(32)), - ); // we use the randomBytes function instead of `Fr.random()`, so that we can use deterministic randomness in tests, through the rand() function. - - const logFields = [this.tag, ephPkX, ...beBytes31ToFields(logBytes), ...fieldsPadding] as Tuple< - Fr, - typeof PRIVATE_LOG_SIZE_IN_FIELDS - >; - - if (logFields.length !== PRIVATE_LOG_SIZE_IN_FIELDS) { - throw new Error( - `Expected private log payload to have ${PRIVATE_LOG_SIZE_IN_FIELDS} fields. Got ${logFields.length}.`, - ); - } - - return new PrivateLog(logFields); - } - - /** - * Decrypts a ciphertext as an incoming log. - * - * This is executable by the recipient of the note, and uses the addressSecret to decrypt the payload. - * - * @param payload - The payload for the log - * @param addressSecret - The address secret, used to decrypt the logs - * @param ciphertextLength - Optionally supply the ciphertext length (see trimCiphertext()) - * @returns The decrypted log payload - */ - public static async decryptAsIncoming( - payload: Fr[], - addressSecret: GrumpkinScalar, - ): Promise { - try { - const logFields = payload; - - const tag = logFields[0]; - const ephPkX = logFields[1]; - - const reader = BufferReader.asReader(fieldsToBEBytes31(logFields.slice(TAG_SIZE_IN_FIELDS + EPK_SIZE_IN_FIELDS))); - - const ephPkSigBuf = reader.readBytes(1); - const ephPkSignBool = !!ephPkSigBuf[0]; - const ephPk = await Point.fromXAndSign(ephPkX, ephPkSignBool); - - const headerCiphertextBytes = reader.readBytes(HEADER_CIPHERTEXT_SIZE_IN_BYTES); - - let contractAddress = AztecAddress.ZERO; - if (!addressSecret) { - throw new Error('Cannot decrypt without an address secret.'); - } - - const ciphertextSharedSecret = await deriveEcdhSharedSecret(addressSecret, ephPk); - - const [symKey, iv] = deriveAesSymmetricKeyAndIvFromEcdhSharedSecretUsingSha256(ciphertextSharedSecret); - - const headerPlaintextBytes = await aes128Decrypt(headerCiphertextBytes, iv, symKey); - - const headerReader = BufferReader.asReader(headerPlaintextBytes); - - const contractAddressBuf = headerReader.readBytes(CONTRACT_ADDRESS_SIZE_IN_BYTES); - contractAddress = AztecAddress.fromBuffer(contractAddressBuf); - - const ciphertextBytesLengthBuf = headerReader.readBytes(SIZE_OF_ENCODING_OF_CIPHERTEXT_SIZE_IN_BYTES); - const ciphertextBytesLength = (ciphertextBytesLengthBuf[0] << 8) + ciphertextBytesLengthBuf[1]; - - const ciphertextBytes = reader.readBytes(ciphertextBytesLength); - - const plaintextBytes = await aes128Decrypt(ciphertextBytes, iv, symKey); - - return new EncryptedLogPayload(tag, contractAddress, plaintextBytes); - } catch (e: any) { - // Following error messages are expected to occur when decryption fails - if (!this.isAcceptableError(e)) { - // If we encounter an unexpected error, we rethrow it - throw e; - } - return; - } - } - - private static isAcceptableError(e: any) { - return ( - e instanceof NotOnCurveError || - e.message.endsWith('is greater or equal to field modulus.') || - e.message.startsWith('Invalid AztecAddress length') || - e.message.startsWith('Selector must fit in') || - e.message.startsWith('Attempted to read beyond buffer length') || - e.message.startsWith('RangeError [ERR_BUFFER_OUT_OF_BOUNDS]:') - ); - } - - public toBuffer() { - return serializeToBuffer(this.tag, this.contractAddress.toBuffer(), this.incomingBodyPlaintext); - } -} diff --git a/yarn-project/stdlib/src/logs/l1_payload/encryption_util.ts b/yarn-project/stdlib/src/logs/l1_payload/encryption_util.ts deleted file mode 100644 index f2c134121dc3..000000000000 --- a/yarn-project/stdlib/src/logs/l1_payload/encryption_util.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { GeneratorIndex } from '@aztec/constants'; -import { Aes128, sha256 } from '@aztec/foundation/crypto'; -import type { Point } from '@aztec/foundation/fields'; -import { numToUInt8 } from '@aztec/foundation/serialize'; - -function extractCloseToUniformlyRandom256BitsFromEcdhSharedSecretUsingSha256(sharedSecret: Point): Buffer { - const secretBuffer = Buffer.concat([sharedSecret.toCompressedBuffer(), numToUInt8(GeneratorIndex.SYMMETRIC_KEY)]); - const hash = sha256(secretBuffer); - return hash; -} - -function deriveAesSymmetricKeyAndIvFromEcdhSharedSecret( - sharedSecret: Point, - randomnessExtractionFunction: (sharedSecret: Point) => Buffer, -): [Buffer, Buffer] { - const random256Bits = randomnessExtractionFunction(sharedSecret); - const symKey = random256Bits.subarray(0, 16); - const iv = random256Bits.subarray(16, 32); - return [symKey, iv]; -} - -export function deriveAesSymmetricKeyAndIvFromEcdhSharedSecretUsingSha256(sharedSecret: Point): [Buffer, Buffer] { - return deriveAesSymmetricKeyAndIvFromEcdhSharedSecret( - sharedSecret, - extractCloseToUniformlyRandom256BitsFromEcdhSharedSecretUsingSha256, - ); -} - -/** - * Encrypts the plaintext using the secret key and public key - * - * @param plaintext - The plaintext buffer - * @param secret - The secret key used to derive the AES secret - * @param publicKey - Public key used to derived the AES secret - * @param deriveSecret - Function to derive the AES secret from the ephemeral secret key and public key - * @returns The ciphertext - */ -export function aes128Encrypt(plaintext: Buffer, iv: Buffer, symKey: Buffer): Promise { - const aes128 = new Aes128(); - return aes128.encryptBufferCBC(plaintext, iv, symKey); -} - -/** - * Decrypts the ciphertext using the secret key and public key - * @param ciphertext - The ciphertext buffer - * @param secret - The secret key used to derive the AES secret - * @param publicKey - The public key used to derive the AES secret - * @param deriveSecret - Function to derive the AES secret from the ephemeral secret key and public key - * @returns - */ -export function aes128Decrypt(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise { - const aes128 = new Aes128(); - return aes128.decryptBufferCBC(ciphertext, iv, symKey); -} diff --git a/yarn-project/stdlib/src/logs/l1_payload/index.ts b/yarn-project/stdlib/src/logs/l1_payload/index.ts deleted file mode 100644 index 8506df7236f7..000000000000 --- a/yarn-project/stdlib/src/logs/l1_payload/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './encrypted_log_payload.js'; -export * from './shared_secret_derivation.js'; diff --git a/yarn-project/stdlib/src/logs/l1_payload/payload.ts b/yarn-project/stdlib/src/logs/l1_payload/payload.ts deleted file mode 100644 index ccfdca534fa2..000000000000 --- a/yarn-project/stdlib/src/logs/l1_payload/payload.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { randomInt } from '@aztec/foundation/crypto'; -import { Fr } from '@aztec/foundation/fields'; -import { BufferReader } from '@aztec/foundation/serialize'; -import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; - -import { schemas } from '../../schemas/index.js'; -import { Vector } from '../../types/index.js'; - -/** - * The Note class represents a Note emitted from a Noir contract as a vector of Fr (finite field) elements. - * This data also represents a preimage to a note hash. This class extends the Vector class, which allows for - * additional operations on the underlying field elements. - */ -export class Payload extends Vector { - toJSON() { - return this.toBuffer(); - } - - static get schema() { - return schemas.Buffer.transform(Payload.fromBuffer); - } - - /** - * Create a Note instance from a Buffer or BufferReader. - * The input 'buffer' can be either a Buffer containing the serialized Fr elements or a BufferReader instance. - * This function reads the Fr elements in the buffer and constructs a Note with them. - * - * @param buffer - The Buffer or BufferReader containing the serialized Fr elements. - * @returns A Note instance containing the deserialized Fr elements. - */ - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new Payload(reader.readVector(Fr)); - } - - /** - * Generates a random Note instance with a variable number of items. - * The number of items is determined by a random value between 1 and 10 (inclusive). - * Each item in the Note is generated using the Fr.random() method. - * - * @returns A randomly generated Note instance. - */ - static random() { - const numItems = randomInt(10) + 1; - const items = Array.from({ length: numItems }, () => Fr.random()); - return new Payload(items); - } - - /** - * Returns a hex representation of the note. - * @returns A hex string with the vector length as first element. - */ - override toString() { - return bufferToHex(this.toBuffer()); - } - - /** - * Creates a new Note instance from a hex string. - * @param str - Hex representation. - * @returns A Note instance. - */ - static fromString(str: string) { - return Payload.fromBuffer(hexToBuffer(str)); - } - - get length() { - return this.items.length; - } - - equals(other: Payload) { - return this.items.every((item, index) => item.equals(other.items[index])); - } -} diff --git a/yarn-project/stdlib/src/logs/l1_payload/shared_secret_derivation.ts b/yarn-project/stdlib/src/logs/shared_secret_derivation.ts similarity index 70% rename from yarn-project/stdlib/src/logs/l1_payload/shared_secret_derivation.ts rename to yarn-project/stdlib/src/logs/shared_secret_derivation.ts index b9a681766129..9b7bd9920ad8 100644 --- a/yarn-project/stdlib/src/logs/l1_payload/shared_secret_derivation.ts +++ b/yarn-project/stdlib/src/logs/shared_secret_derivation.ts @@ -1,8 +1,7 @@ import { Grumpkin } from '@aztec/foundation/crypto'; import type { GrumpkinScalar, Point } from '@aztec/foundation/fields'; -import type { AztecAddress } from '../../aztec-address/index.js'; -import type { PublicKey } from '../../keys/public_key.js'; +import type { PublicKey } from '../keys/public_key.js'; /** * Derive an Elliptic Curve Diffie-Hellman (ECDH) Shared Secret. @@ -13,6 +12,9 @@ import type { PublicKey } from '../../keys/public_key.js'; * @param publicKey - The public key used to derive shared secret. * @returns A derived shared secret. * @throws If the publicKey is zero. + * + * TODO(#12656): This function is kept around because of the getSharedSecret oracle. Nuke this once returning + * the app-siloed secret. */ export async function deriveEcdhSharedSecret(secretKey: GrumpkinScalar, publicKey: PublicKey): Promise { if (publicKey.isZero()) { @@ -24,12 +26,3 @@ export async function deriveEcdhSharedSecret(secretKey: GrumpkinScalar, publicKe const sharedSecret = await curve.mul(publicKey, secretKey); return sharedSecret; } - -export async function deriveEcdhSharedSecretUsingAztecAddress( - secretKey: GrumpkinScalar, - address: AztecAddress, -): Promise { - const addressPoint = await address.toAddressPoint(); - const sharedSecret = await deriveEcdhSharedSecret(secretKey, addressPoint); - return sharedSecret; -} diff --git a/yarn-project/stdlib/src/note/note.ts b/yarn-project/stdlib/src/note/note.ts index b8ef294b1555..db8abceab966 100644 --- a/yarn-project/stdlib/src/note/note.ts +++ b/yarn-project/stdlib/src/note/note.ts @@ -1,16 +1,72 @@ +import { randomInt } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; -import { schemas } from '@aztec/foundation/schemas'; import { BufferReader } from '@aztec/foundation/serialize'; +import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; -import { Payload } from '../logs/l1_payload/payload.js'; +import { schemas } from '../schemas/index.js'; +import { Vector } from '../types/shared.js'; -export class Note extends Payload { - static override get schema() { +/** + * The Note class represents a Note emitted from a Noir contract as a vector of Fr (finite field) elements. + * This data also represents a preimage to a note hash. + */ +export class Note extends Vector { + toJSON() { + return this.toBuffer(); + } + + static get schema() { return schemas.Buffer.transform(Note.fromBuffer); } - static override fromBuffer(buffer: Buffer | BufferReader) { + /** + * Create a Note instance from a Buffer or BufferReader. + * The input 'buffer' can be either a Buffer containing the serialized Fr elements or a BufferReader instance. + * This function reads the Fr elements in the buffer and constructs a Note with them. + * + * @param buffer - The Buffer or BufferReader containing the serialized Fr elements. + * @returns A Note instance containing the deserialized Fr elements. + */ + static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new Note(reader.readVector(Fr)); } + + /** + * Generates a random Note instance with a variable number of items. + * The number of items is determined by a random value between 1 and 10 (inclusive). + * Each item in the Note is generated using the Fr.random() method. + * + * @returns A randomly generated Note instance. + */ + static random() { + const numItems = randomInt(10) + 1; + const items = Array.from({ length: numItems }, () => Fr.random()); + return new Note(items); + } + + /** + * Returns a hex representation of the note. + * @returns A hex string with the vector length as first element. + */ + override toString() { + return bufferToHex(this.toBuffer()); + } + + /** + * Creates a new Note instance from a hex string. + * @param str - Hex representation. + * @returns A Note instance. + */ + static fromString(str: string) { + return Note.fromBuffer(hexToBuffer(str)); + } + + get length() { + return this.items.length; + } + + equals(other: Note) { + return this.items.every((item, index) => item.equals(other.items[index])); + } }