From 699e357626614e94703ff9aae25c511e8c8e98a0 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Wed, 15 May 2024 11:39:32 +0000 Subject: [PATCH] chore: address comments --- .../src/logs/encrypted_log_payload.ts | 94 ++++++++++++------- yarn-project/circuits.js/src/keys/index.ts | 24 ++++- 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/yarn-project/circuit-types/src/logs/encrypted_log_payload.ts b/yarn-project/circuit-types/src/logs/encrypted_log_payload.ts index 9f37c47eb65..6ef1cc82add 100644 --- a/yarn-project/circuit-types/src/logs/encrypted_log_payload.ts +++ b/yarn-project/circuit-types/src/logs/encrypted_log_payload.ts @@ -1,6 +1,14 @@ -import { AztecAddress, Fq, Fr, GeneratorIndex, GrumpkinPrivateKey, Point, type PublicKey } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { poseidon2Hash } from '@aztec/foundation/crypto'; +import { + AztecAddress, + Fr, + type GrumpkinPrivateKey, + Point, + type PublicKey, + computeIvpkApp, + computeIvskApp, + computeOvskApp, + derivePublicKeyFromSecretKey, +} from '@aztec/circuits.js'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { EncryptedLogHeader } from './encrypted_log_header.js'; @@ -9,12 +17,16 @@ import { EncryptedLogOutgoingBody } from './encrypted_log_outgoing_body.js'; import { type L1NotePayload } from './l1_note_payload/l1_note_payload.js'; import { Note } from './l1_note_payload/note.js'; +// A placeholder tag until we have a proper tag system in place. const PLACEHOLDER_TAG = new Fr(33); -const grumpkin = new Grumpkin(); +// Both the incoming and the outgoing header are 48 bytes. +// 32 bytes for the address, and 16 bytes padding to follow PKCS#7 +const HEADER_SIZE = 48; -const HEADER_SIZE = 48; // 32 bytes + 16 bytes padding. (address) -const OUTGOING_BODY_SIZE = 176; // 160 bytes + 16 bytes padding. (secret key | address | public key) +// The outgoing body is constant size of 176 bytes. +// 160 bytes for the secret key, address, and public key, and 16 bytes padding to follow PKCS#7 +const OUTGOING_BODY_SIZE = 176; export class EncryptedLogPayload { constructor( @@ -27,7 +39,7 @@ export class EncryptedLogPayload { */ public contractAddress: AztecAddress, /** - * Storage slot of the contract this tx is interacting with. + * Storage slot of the underlying note. */ public storageSlot: Fr, /** @@ -59,16 +71,27 @@ export class EncryptedLogPayload { ); } + /** + * Encrypts a note payload for a given recipient and sender. + * Creates an incoming log the the recipient using the recipient's ivsk, and + * an outgoing log for the sender using the sender's ovsk. + * + * @param ephSk - An ephemeral secret key used for the encryption + * @param recipient - The recipient address, retrievable by the sender for his logs + * @param ivpk - The incoming viewing public key of the recipient + * @param ovsk - The outgoing viewing secret key of the sender + * @returns A buffer containing the encrypted log payload + */ public encrypt(ephSk: GrumpkinPrivateKey, recipient: AztecAddress, ivpk: PublicKey, ovsk: GrumpkinPrivateKey) { - const ephPk = grumpkin.mul(Grumpkin.generator, ephSk); - const ovpk = grumpkin.mul(Grumpkin.generator, ovsk); + const ephPk = derivePublicKeyFromSecretKey(ephSk); + const ovpk = derivePublicKeyFromSecretKey(ovsk); const header = new EncryptedLogHeader(this.contractAddress); const incomingHeaderCiphertext = header.computeCiphertext(ephSk, ivpk); const outgoingHeaderCiphertext = header.computeCiphertext(ephSk, ovpk); - const ivpkApp = EncryptedLogPayload.computeIvpkApp(ivpk, this.contractAddress); + const ivpkApp = computeIvpkApp(ivpk, this.contractAddress); const incomingBodyCiphertext = new EncryptedLogIncomingBody( this.storageSlot, @@ -76,7 +99,7 @@ export class EncryptedLogPayload { this.note, ).computeCiphertext(ephSk, ivpkApp); - const ovskApp = EncryptedLogPayload.computeOvskApp(ovsk, this.contractAddress); + const ovskApp = computeOvskApp(ovsk, this.contractAddress); const outgoingBodyCiphertext = new EncryptedLogOutgoingBody(ephSk, recipient, ivpkApp).computeCiphertext( ovskApp, @@ -94,6 +117,18 @@ export class EncryptedLogPayload { ]); } + /** + * Decrypts a ciphertext as an incoming log. + * + * This is executable by the recipient of the note, and uses the ivsk to decrypt the payload. + * The outgoing parts of the log are ignored entirely. + * + * Produces the same output as `decryptAsOutgoing`. + * + * @param ciphertext - The ciphertext for the log + * @param ivsk - The incoming viewing secret key, used to decrypt the logs + * @returns The decrypted log payload + */ public static decryptAsIncoming(ciphertext: Buffer | bigint[], ivsk: GrumpkinPrivateKey) { const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); const reader = BufferReader.asReader(input); @@ -106,14 +141,14 @@ export class EncryptedLogPayload { const incomingHeader = EncryptedLogHeader.fromCiphertext(reader.readBytes(HEADER_SIZE), ivsk, ephPk); - // Skipping outgoing + // Skipping the outgoing header and body reader.readBytes(HEADER_SIZE); reader.readBytes(OUTGOING_BODY_SIZE); // The incoming can be of variable size, so we read until the end const incomingBodySlice = reader.readToEnd(); - const ivskApp = EncryptedLogPayload.computeIvskApp(ivsk, incomingHeader.address); + const ivskApp = computeIvskApp(ivsk, incomingHeader.address); const incomingBody = EncryptedLogIncomingBody.fromCiphertext(incomingBodySlice, ivskApp, ephPk); return new EncryptedLogPayload( @@ -124,6 +159,19 @@ export class EncryptedLogPayload { ); } + /** + * Decrypts a ciphertext as an outgoing log. + * + * This is executable by the sender of the note, and uses the ovsk to decrypt the payload. + * The outgoing parts are decrypted to retrieve information that allows the sender to + * decrypt the incoming log, and learn about the note contents. + * + * Produces the same output as `decryptAsIncoming`. + * + * @param ciphertext - The ciphertext for the log + * @param ovsk - The outgoing viewing secret key, used to decrypt the logs + * @returns The decrypted log payload + */ public static decryptAsOutgoing(ciphertext: Buffer | bigint[], ovsk: GrumpkinPrivateKey) { const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x))); const reader = BufferReader.asReader(input); @@ -137,10 +185,9 @@ export class EncryptedLogPayload { // Skip the incoming header reader.readBytes(HEADER_SIZE); - // Skipping outgoing const outgoingHeader = EncryptedLogHeader.fromCiphertext(reader.readBytes(HEADER_SIZE), ovsk, ephPk); - const ovskApp = EncryptedLogPayload.computeOvskApp(ovsk, outgoingHeader.address); + const ovskApp = computeOvskApp(ovsk, outgoingHeader.address); const outgoingBody = EncryptedLogOutgoingBody.fromCiphertext(reader.readBytes(OUTGOING_BODY_SIZE), ovskApp, ephPk); // The incoming can be of variable size, so we read until the end @@ -159,21 +206,4 @@ export class EncryptedLogPayload { incomingBody.noteTypeId, ); } - - static computeIvpkApp(ivpk: PublicKey, address: AztecAddress) { - const I = Fq.fromBuffer(poseidon2Hash([address.toField(), ivpk.x, ivpk.y, GeneratorIndex.IVSK_M]).toBuffer()); - return grumpkin.add(grumpkin.mul(Grumpkin.generator, I), ivpk); - } - - static computeIvskApp(ivsk: GrumpkinPrivateKey, address: AztecAddress) { - const ivpk = grumpkin.mul(Grumpkin.generator, ivsk); - const I = Fq.fromBuffer(poseidon2Hash([address.toField(), ivpk.x, ivpk.y, GeneratorIndex.IVSK_M]).toBuffer()); - return new Fq((I.toBigInt() + ivsk.toBigInt()) % Fq.MODULUS); - } - - static computeOvskApp(ovsk: GrumpkinPrivateKey, address: AztecAddress) { - return GrumpkinPrivateKey.fromBuffer( - poseidon2Hash([address.toField(), ovsk.high, ovsk.low, GeneratorIndex.OVSK_M]).toBuffer(), - ); - } } diff --git a/yarn-project/circuits.js/src/keys/index.ts b/yarn-project/circuits.js/src/keys/index.ts index a994d96e5ae..2dc73210e08 100644 --- a/yarn-project/circuits.js/src/keys/index.ts +++ b/yarn-project/circuits.js/src/keys/index.ts @@ -1,16 +1,36 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { poseidon2Hash, sha512ToGrumpkinScalar } from '@aztec/foundation/crypto'; -import { type Fq, type Fr, type GrumpkinScalar } from '@aztec/foundation/fields'; +import { Fq, type Fr, type GrumpkinScalar } from '@aztec/foundation/fields'; import { Grumpkin } from '../barretenberg/crypto/grumpkin/index.js'; import { GeneratorIndex } from '../constants.gen.js'; -import { type GrumpkinPrivateKey } from '../types/grumpkin_private_key.js'; +import { GrumpkinPrivateKey } from '../types/grumpkin_private_key.js'; +import { type PublicKey } from '../types/public_key.js'; import { PublicKeys } from '../types/public_keys.js'; +const curve = new Grumpkin(); + export function computeAppNullifierSecretKey(masterNullifierSecretKey: GrumpkinPrivateKey, app: AztecAddress): Fr { return poseidon2Hash([masterNullifierSecretKey.high, masterNullifierSecretKey.low, app, GeneratorIndex.NSK_M]); } +export function computeIvpkApp(ivpk: PublicKey, address: AztecAddress) { + const I = Fq.fromBuffer(poseidon2Hash([address.toField(), ivpk.x, ivpk.y, GeneratorIndex.IVSK_M]).toBuffer()); + return curve.add(curve.mul(Grumpkin.generator, I), ivpk); +} + +export function computeIvskApp(ivsk: GrumpkinPrivateKey, address: AztecAddress) { + const ivpk = curve.mul(Grumpkin.generator, ivsk); + const I = Fq.fromBuffer(poseidon2Hash([address.toField(), ivpk.x, ivpk.y, GeneratorIndex.IVSK_M]).toBuffer()); + return new Fq((I.toBigInt() + ivsk.toBigInt()) % Fq.MODULUS); +} + +export function computeOvskApp(ovsk: GrumpkinPrivateKey, address: AztecAddress) { + return GrumpkinPrivateKey.fromBuffer( + poseidon2Hash([address.toField(), ovsk.high, ovsk.low, GeneratorIndex.OVSK_M]).toBuffer(), + ); +} + export function deriveMasterNullifierSecretKey(secretKey: Fr): GrumpkinScalar { return sha512ToGrumpkinScalar([secretKey, GeneratorIndex.NSK_M]); }