Skip to content

Commit

Permalink
feat: encrypted log body (#6251)
Browse files Browse the repository at this point in the history
Fixes #5899.
  • Loading branch information
LHerskind authored May 8, 2024
1 parent e092514 commit ba618d5
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 11 deletions.
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs.nr
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod header;
mod body;
147 changes: 147 additions & 0 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs/body.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use crate::note::{note_interface::NoteInterface};
use dep::protocol_types::{grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint};

use crate::oracle::encryption::aes128_encrypt;
use crate::keys::point_to_symmetric_key::point_to_symmetric_key;

struct EncryptedLogBody<Note> {
storage_slot: Field,
note_type_id: Field,
note: Note,
}

impl<Note> EncryptedLogBody<Note> {
pub fn new<N>(
storage_slot: Field,
note_type_id: Field,
note: Note
) -> Self where Note: NoteInterface<N> {
Self { storage_slot, note_type_id, note }
}

pub fn compute_ciphertext<N, M>(
self,
secret: GrumpkinPrivateKey,
point: GrumpkinPoint
) -> [u8; M] where Note: NoteInterface<N> {
// We need 32 bytes for every field in the note, and then we have 2 extra fields (storage_slot and note_type_id)
let serialized_note: [Field; N] = Note::serialize_content(self.note);

// Work around not being able to use N directly beyond the size of the array above.
let N_ = serialized_note.len();

assert(N_ * 32 + 64 == M, "Invalid size of encrypted log body");

let mut buffer: [u8; M] = [0; M];

let storage_slot_bytes = self.storage_slot.to_be_bytes(32);
let note_type_id_bytes = self.note_type_id.to_be_bytes(32);
for i in 0..32 {
buffer[i] = storage_slot_bytes[i];
buffer[32 + i] = note_type_id_bytes[i];
}

for i in 0..N_ {
let bytes = serialized_note[i].to_be_bytes(32);
for j in 0..32 {
buffer[64 + i * 32 + j] = bytes[j];
}
}

let full_key = point_to_symmetric_key(secret, point);
let mut sym_key = [0; 16];
let mut iv = [0; 16];

for i in 0..16 {
sym_key[i] = full_key[i];
iv[i] = full_key[i + 16];
}

aes128_encrypt(buffer, iv, sym_key)
}
}

/*
// Test is semi broken, needs to be fixed along with #6172
mod test {
use crate::encrypted_logs::body::EncryptedLogBody;
use dep::protocol_types::{address::AztecAddress, traits::Empty, constants::GENERATOR_INDEX__NOTE_NULLIFIER};
use crate::{
note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption},
oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_app_nullifier_secret_key, get_public_key::get_public_key},
context::PrivateContext, hash::poseidon2_hash
};
use dep::protocol_types::{address::AztecAddress, grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint};
struct AddressNote {
address: AztecAddress,
owner: AztecAddress,
randomness: Field,
header: NoteHeader,
}
global BIB_BOB_ADDRESS_NOTE_LEN: Field = 3;
impl NoteInterface<BIB_BOB_ADDRESS_N3OTE_LEN> for AddressNote {
fn compute_note_content_hash(self) -> Field {1}
fn get_note_type_id() -> Field {2}
fn get_header(self) -> NoteHeader { self.header}
fn set_header(&mut self, header: NoteHeader) {self.header = header; }
fn compute_nullifier(self, context: &mut PrivateContext) -> Field {1}
fn compute_nullifier_without_context(self) -> Field {1}
fn broadcast(self, context: &mut PrivateContext, slot: Field) {}
fn serialize_content(self) -> [Field; BIB_BOB_ADDRESS_NOTE_LEN] { [self.address.to_field(), self.owner.to_field(), self.randomness]}
fn deserialize_content(fields: [Field; BIB_BOB_ADDRESS_NOTE_LEN]) -> Self {
AddressNote { address: AztecAddress::from_field(fields[0]), owner: AztecAddress::from_field(fields[1]), randomness: fields[2], header: NoteHeader::empty() }
}
}
impl AddressNote {
pub fn new(address: AztecAddress, owner: AztecAddress, randomness: Field) -> Self {
AddressNote { address, owner, randomness, header: NoteHeader::empty() }
}
// docs:end:address_note_def
}
// @todo Issue(#6172) This is to be run as a test. But it is currently using the AES oracle so will fail there.
fn test_encrypted_log_body() {
let note = AddressNote::new(
AztecAddress::from_field(0x1),
AztecAddress::from_field(0x2),
3
);
let note_type_id = 1;
let storage_slot = 2;
let body = EncryptedLogBody::new(storage_slot, note_type_id, note);
let secret = GrumpkinPrivateKey::new(
0x0000000000000000000000000000000023b3127c127b1f29a7adff5cccf8fb06,
0x00000000000000000000000000000000649e7ca01d9de27b21624098b897babd
);
let point = GrumpkinPoint::new(
0x2688431c705a5ff3e6c6f2573c9e3ba1c1026d2251d0dbbf2d810aa53fd1d186,
0x1e96887b117afca01c00468264f4f80b5bb16d94c1808a448595f115556e5c8e
);
let ciphertext = body.compute_ciphertext(secret, point);
let expected_body_ciphertext = [
131, 119, 105, 129, 244, 32, 151, 205, 12, 99, 93, 62, 10, 180, 72, 21, 36, 194, 14, 168, 0, 137, 126, 59, 151, 177, 136, 254, 153, 190, 92, 33, 40, 151, 178, 54, 34, 166, 124, 96, 117, 108, 168, 7, 147, 222, 81, 201, 254, 170, 244, 151, 60, 64, 226, 45, 156, 185, 53, 23, 121, 63, 243, 101, 134, 21, 167, 39, 226, 203, 162, 223, 28, 74, 244, 159, 54, 201, 192, 168, 19, 85, 103, 82, 148, 3, 153, 210, 89, 245, 171, 171, 12, 248, 40, 74, 199, 65, 96, 42, 84, 83, 48, 21, 188, 134, 45, 247, 134, 166, 109, 170, 68, 212, 99, 235, 74, 202, 162, 108, 130, 128, 122, 16, 79, 242, 30, 157, 26, 75, 57, 24, 18, 124, 217, 74, 155, 13, 171, 205, 194, 193, 103, 134, 224, 204, 46, 105, 135, 166, 192, 163, 186, 42, 71, 51, 156, 161, 8, 131
];
assert_eq(ciphertext, expected_body_ciphertext);
}
}
*/
13 changes: 13 additions & 0 deletions noir-projects/noir-contracts/contracts/test_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ contract Test {
};

use dep::aztec::encrypted_logs::header::EncryptedLogHeader;
use dep::aztec::encrypted_logs::body::EncryptedLogBody;

use dep::aztec::note::constants::MAX_NOTES_PER_PAGE;

Expand Down Expand Up @@ -350,6 +351,18 @@ contract Test {
EncryptedLogHeader::new(context.this_address()).compute_ciphertext(secret, point)
}

// 64 bytes + 32 * #fields = 96 bytes
#[aztec(private)]
fn compute_note_body_ciphertext(
secret: GrumpkinPrivateKey,
point: GrumpkinPoint,
storage_slot: Field,
value: Field
) -> [u8; 96] {
let note = TestNote::new(value);
EncryptedLogBody::new(storage_slot, TestNote::get_note_type_id(), note).compute_ciphertext(secret, point)
}

#[aztec(public)]
fn assert_public_global_vars(
chain_id: Field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ impl ScopedReadRequest {
pub fn counter(self) -> u32 {
self.read_request.counter
}
}
}
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export {
Comparator,
SiblingPath,
EncryptedLogHeader,
EncryptedLogBody,
} from '@aztec/circuit-types';
export { NodeInfo } from '@aztec/types/interfaces';

Expand Down
66 changes: 66 additions & 0 deletions yarn-project/circuit-types/src/logs/encrypted_log_body.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Fr, GrumpkinScalar } from '@aztec/circuits.js';
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { updateInlineTestData } from '@aztec/foundation/testing';

import { EncryptedLogBody } from './encrypted_log_body.js';
import { Note } from './l1_note_payload/note.js';

describe('encrypt log body', () => {
let grumpkin: Grumpkin;

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

it('encrypt and decrypt a log body', () => {
const ephSecretKey = GrumpkinScalar.random();
const viewingSecretKey = GrumpkinScalar.random();

const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey);
const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey);

const note = Note.random();
const noteTypeId = Fr.random();
const storageSlot = Fr.random();

const body = new EncryptedLogBody(noteTypeId, storageSlot, note);

const encrypted = body.computeCiphertext(ephSecretKey, viewingPubKey);

const recreated = EncryptedLogBody.fromCiphertext(encrypted, viewingSecretKey, ephPubKey);

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

it('encrypt a log body, generate input for noir test', () => {
// The following 2 are arbitrary fixed values - fixed in order to test a match with Noir
const viewingSecretKey: GrumpkinScalar = new GrumpkinScalar(
0x23b3127c127b1f29a7adff5cccf8fb06649e7ca01d9de27b21624098b897babdn,
);
const ephSecretKey: GrumpkinScalar = new GrumpkinScalar(
0x1fdd0dd8c99b21af8e00d2d130bdc263b36dadcbea84ac5ec9293a0660deca01n,
);

const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey);

const note = new Note([new Fr(1), new Fr(2), new Fr(3)]);
const noteTypeId = new Fr(1);
const storageSlot = new Fr(2);

const body = new EncryptedLogBody(noteTypeId, storageSlot, note);

const encrypted = body.computeCiphertext(ephSecretKey, viewingPubKey);

const byteArrayString = `[${encrypted
.toString('hex')
.match(/.{1,2}/g)!
.map(byte => parseInt(byte, 16))}]`;

// Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data
updateInlineTestData(
'noir-projects/aztec-nr/aztec/src/encrypted_logs/body.nr',
'expected_body_ciphertext',
byteArrayString,
);
});
});
81 changes: 81 additions & 0 deletions yarn-project/circuit-types/src/logs/encrypted_log_body.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Fr, type GrumpkinPrivateKey, type PublicKey } from '@aztec/circuits.js';
import { Aes128 } from '@aztec/circuits.js/barretenberg';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { Note, deriveAESSecret } from './l1_note_payload/index.js';

export class EncryptedLogBody {
constructor(public storageSlot: Fr, public noteTypeId: Fr, public note: Note) {}

/**
* Serializes the log body to a buffer WITHOUT the length of the note buffer
*
* @returns The serialized log body
*/
public toBuffer(): Buffer {
const noteBufferWithoutLength = this.note.toBuffer().subarray(4);
return serializeToBuffer(this.storageSlot, this.noteTypeId, noteBufferWithoutLength);
}

/**
* Deserialized the log body from a buffer WITHOUT the length of the note buffer
*
* @param buf - The buffer to deserialize
* @returns The deserialized log body
*/
public static fromBuffer(buf: Buffer): EncryptedLogBody {
const reader = BufferReader.asReader(buf);
const storageSlot = Fr.fromBuffer(reader);
const noteTypeId = Fr.fromBuffer(reader);

// 2 Fields (storage slot and note type id) are not included in the note buffer
const fieldsInNote = reader.getLength() / 32 - 2;
const note = new Note(reader.readArray(fieldsInNote, Fr));

return new EncryptedLogBody(storageSlot, noteTypeId, note);
}

/**
* Encrypts a log body
*
* @param secret - The ephemeral secret key
* @param publicKey - The incoming viewing key for the recipient of this log
*
* @returns The ciphertext of the encrypted log body
*/
public computeCiphertext(secret: GrumpkinPrivateKey, publicKey: PublicKey) {
const aesSecret = deriveAESSecret(secret, publicKey);
const key = aesSecret.subarray(0, 16);
const iv = aesSecret.subarray(16, 32);

const aes128 = new Aes128();
const buffer = this.toBuffer();

return aes128.encryptBufferCBC(buffer, iv, key);
}

/**
* Decrypts a log body
*
* @param ciphertext - The ciphertext buffer
* @param secret - The private key matching the public key used in encryption (the viewing key secret)
* @param publicKey - The public key generated with the ephemeral secret key used in encryption
*
* @returns The decrypted log body
*/
public static fromCiphertext(
ciphertext: Buffer | bigint[],
secret: GrumpkinPrivateKey,
publicKey: PublicKey,
): EncryptedLogBody {
const input = Buffer.isBuffer(ciphertext) ? ciphertext : Buffer.from(ciphertext.map((x: bigint) => Number(x)));

const aesSecret = deriveAESSecret(secret, publicKey);
const key = aesSecret.subarray(0, 16);
const iv = aesSecret.subarray(16, 32);

const aes128 = new Aes128();
const buffer = aes128.decryptBufferCBC(input, iv, key);
return EncryptedLogBody.fromBuffer(buffer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ describe('encrypt log header', () => {
const ephPubKey = grumpkin.mul(Grumpkin.generator, ephSecretKey);
const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey);

const addr = AztecAddress.random();
const header = new EncryptedLogHeader(addr);
const header = new EncryptedLogHeader(AztecAddress.random());

const encrypted = header.computeCiphertext(ephSecretKey, viewingPubKey);

const recreated = EncryptedLogHeader.fromCiphertext(encrypted, viewingSecretKey, ephPubKey);

expect(recreated.toBuffer()).toEqual(addr.toBuffer());
expect(recreated.toBuffer()).toEqual(header.toBuffer());
});

it('encrypt a log header, generate input for noir test', () => {
Expand All @@ -39,8 +38,7 @@ describe('encrypt log header', () => {

const viewingPubKey = grumpkin.mul(Grumpkin.generator, viewingSecretKey);

const addr = AztecAddress.fromBigInt(BigInt('0xdeadbeef'));
const header = new EncryptedLogHeader(addr);
const header = new EncryptedLogHeader(AztecAddress.fromBigInt(BigInt('0xdeadbeef')));

const encrypted = header.computeCiphertext(ephSecretKey, viewingPubKey);

Expand Down
6 changes: 2 additions & 4 deletions yarn-project/circuit-types/src/logs/encrypted_log_header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ export class EncryptedLogHeader {
const iv = aesSecret.subarray(16, 32);

const aes128 = new Aes128();
const buffer = this.address.toBuffer();

const buffer = this.toBuffer();
return aes128.encryptBufferCBC(buffer, iv, key);
}

Expand All @@ -66,7 +65,6 @@ export class EncryptedLogHeader {

const aes128 = new Aes128();
const buffer = aes128.decryptBufferCBC(input, iv, key);
const address = AztecAddress.fromBuffer(buffer);
return new EncryptedLogHeader(address);
return EncryptedLogHeader.fromBuffer(buffer);
}
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/logs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './tx_l2_logs.js';
export * from './unencrypted_l2_log.js';
export * from './extended_unencrypted_l2_log.js';
export * from './encrypted_log_header.js';
export * from './encrypted_log_body.js';
Loading

0 comments on commit ba618d5

Please sign in to comment.