Skip to content

Commit

Permalink
feat: full encryption and decryption of log in ts
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed May 15, 2024
1 parent 1bea038 commit 1770b04
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 0 deletions.
11 changes: 11 additions & 0 deletions barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/c_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ WASM_EXPORT void ecc_grumpkin__mul(uint8_t const* point_buf, uint8_t const* scal
write(result, r);
}

// Silencing warnings about reserved identifiers. Fixing would break downstream code that calls our WASM API.
// NOLINTBEGIN(cert-dcl37-c, cert-dcl51-cpp, bugprone-reserved-identifier)
WASM_EXPORT void ecc_grumpkin__add(uint8_t const* point_a_buf, uint8_t const* point_b_buf, uint8_t* result)
{
using serialize::write;
auto point_a = from_buffer<grumpkin::g1::affine_element>(point_a_buf);
auto point_b = from_buffer<grumpkin::g1::affine_element>(point_b_buf);
grumpkin::g1::affine_element r = point_a + point_b;
write(result, r);
}

// multiplies a vector of points by a single scalar. Returns a vector of points (this is NOT a multi-exponentiation)
WASM_EXPORT void ecc_grumpkin__batch_mul(uint8_t const* point_buf,
uint8_t const* scalar_buf,
Expand Down
42 changes: 42 additions & 0 deletions yarn-project/circuit-types/src/logs/encrypted_log_payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AztecAddress, GrumpkinScalar } from '@aztec/circuits.js';
import { Grumpkin } from '@aztec/circuits.js/barretenberg';

import { EncryptedLogPayload } from './encrypted_log_payload.js';
import { L1NotePayload } from './l1_note_payload/l1_note_payload.js';

describe('encrypt and decrypt a full log', () => {
let grumpkin: Grumpkin;

let ovsk: GrumpkinScalar;
let ivsk: GrumpkinScalar;

let payload: EncryptedLogPayload;
let encrypted: Buffer;

beforeAll(() => {
grumpkin = new Grumpkin();

ovsk = GrumpkinScalar.random();
ivsk = GrumpkinScalar.random();

const ephSk = GrumpkinScalar.random();

const recipientAddress = AztecAddress.random();
const ivpk = grumpkin.mul(Grumpkin.generator, ivsk);

payload = EncryptedLogPayload.fromL1NotePayload(L1NotePayload.random());
encrypted = payload.encrypt(ephSk, recipientAddress, ivpk, ovsk);
});

it('decrypt a log as incoming', () => {
const recreated = EncryptedLogPayload.decryptAsIncoming(encrypted, ivsk);

expect(recreated.toBuffer()).toEqual(payload.toBuffer());
});

it('decrypt a log as outgoing', () => {
const recreated = EncryptedLogPayload.decryptAsOutgoing(encrypted, ovsk);

expect(recreated.toBuffer()).toEqual(payload.toBuffer());
});
});
179 changes: 179 additions & 0 deletions yarn-project/circuit-types/src/logs/encrypted_log_payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
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 { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { EncryptedLogHeader } from './encrypted_log_header.js';
import { EncryptedLogIncomingBody } from './encrypted_log_incoming_body.js';
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';

const PLACEHOLDER_TAG = new Fr(33);

const grumpkin = new Grumpkin();

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)

export class EncryptedLogPayload {
constructor(
/**
* A note as emitted from Noir contract. Can be used along with private key to compute nullifier.
*/
public note: Note,
/**
* Address of the contract this tx is interacting with.
*/
public contractAddress: AztecAddress,
/**
* Storage slot of the contract this tx is interacting with.
*/
public storageSlot: Fr,
/**
* Type identifier for the underlying note, required to determine how to compute its hash and nullifier.
*/
public noteTypeId: Fr,
) {}

toBuffer() {
return serializeToBuffer([this.note, this.contractAddress, this.storageSlot, this.noteTypeId]);
}

static fromBuffer(buffer: Buffer | BufferReader): EncryptedLogPayload {
const reader = BufferReader.asReader(buffer);
return new EncryptedLogPayload(
reader.readObject(Note),
reader.readObject(AztecAddress),
Fr.fromBuffer(reader),
Fr.fromBuffer(reader),
);
}

static fromL1NotePayload(l1NotePayload: L1NotePayload) {
return new EncryptedLogPayload(
l1NotePayload.note,
l1NotePayload.contractAddress,
l1NotePayload.storageSlot,
l1NotePayload.noteTypeId,
);
}

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 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 incomingBodyCiphertext = new EncryptedLogIncomingBody(
this.storageSlot,
this.noteTypeId,
this.note,
).computeCiphertext(ephSk, ivpkApp);

const ovskApp = EncryptedLogPayload.computeOvskApp(ovsk, this.contractAddress);

const outgoingBodyCiphertext = new EncryptedLogOutgoingBody(ephSk, recipient, ivpkApp).computeCiphertext(
ovskApp,
ephPk,
);

return Buffer.concat([
PLACEHOLDER_TAG.toBuffer(),
PLACEHOLDER_TAG.toBuffer(),
ephPk.toBuffer(),
incomingHeaderCiphertext,
outgoingHeaderCiphertext,
outgoingBodyCiphertext,
incomingBodyCiphertext,
]);
}

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);

// We don't use the tags as part of the decryption here, we just gotta read to skip them.
reader.readObject(Fr); // incoming tag
reader.readObject(Fr); // outgoing tag

const ephPk = reader.readObject(Point);

const incomingHeader = EncryptedLogHeader.fromCiphertext(reader.readBytes(HEADER_SIZE), ivsk, ephPk);

// Skipping outgoing
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 incomingBody = EncryptedLogIncomingBody.fromCiphertext(incomingBodySlice, ivskApp, ephPk);

return new EncryptedLogPayload(
incomingBody.note,
incomingHeader.address,
incomingBody.storageSlot,
incomingBody.noteTypeId,
);
}

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);

// We don't use the tags as part of the decryption here, we just gotta read to skip them.
reader.readObject(Fr); // incoming tag
reader.readObject(Fr); // outgoing tag

const ephPk = reader.readObject(Point);

// 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 outgoingBody = EncryptedLogOutgoingBody.fromCiphertext(reader.readBytes(OUTGOING_BODY_SIZE), ovskApp, ephPk);

// The incoming can be of variable size, so we read until the end
const incomingBodySlice = reader.readToEnd();

const incomingBody = EncryptedLogIncomingBody.fromCiphertext(
incomingBodySlice,
outgoingBody.ephSk,
outgoingBody.recipientIvpkApp,
);

return new EncryptedLogPayload(
incomingBody.note,
outgoingHeader.address,
incomingBody.storageSlot,
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(),
);
}
}
13 changes: 13 additions & 0 deletions yarn-project/circuits.js/src/barretenberg/crypto/grumpkin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ export class Grumpkin {
return Point.fromBuffer(Buffer.from(this.wasm.getMemorySlice(96, 160)));
}

/**
* Add two points.
* @param a - Point a in the addition
* @param b - Point b to add to a
* @returns Result of the addition.
*/
public add(a: Point, b: Point): Point {
this.wasm.writeMemory(0, a.toBuffer());
this.wasm.writeMemory(64, b.toBuffer());
this.wasm.call('ecc_grumpkin__add', 0, 64, 128);
return Point.fromBuffer(Buffer.from(this.wasm.getMemorySlice(128, 192)));
}

/**
* Multiplies a set of points by a scalar.
* @param points - Points to multiply.
Expand Down

0 comments on commit 1770b04

Please sign in to comment.