Skip to content

Commit

Permalink
feat: constrain note encryption (#6432)
Browse files Browse the repository at this point in the history
Fixes #6408. 

Chans the noir implementation of incoming body slightly to easily fit in
with the broadcast format currently in use.

Have some todo's in the payload.nr file, mainly to use meaningful
outgoing keys #6410 and to use tags when we get to it.

For the `eph_sk` also pulling only a Fr while we use it for Fq so we are
slightly biased. @iAmMichaelConnor if you got some news on if this is
acceptable would be cool 👀. Using the unsafe random atm.
  • Loading branch information
LHerskind authored Jun 6, 2024
1 parent 46dcb98 commit e59f4d3
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 29 deletions.
1 change: 0 additions & 1 deletion noir-projects/aztec-nr/address-note/src/address_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ impl NoteInterface<ADDRESS_NOTE_LEN, ADDRESS_NOTE_BYTES_LEN> for AddressNote {
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
29 changes: 12 additions & 17 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::encrypted_logs::{payload::compute_encrypted_note_log};

use crate::{
context::{inputs::PrivateContextInputs, packed_returns::PackedReturns},
messaging::process_l1_to_l2_message,
Expand Down Expand Up @@ -272,7 +274,10 @@ impl PrivateContext {
// --> might be a better approach to force devs to make a public function call that emits the log if needed then
// it would be less easy to accidentally leak information.
// If we decide to keep this function around would make sense to wait for traits and then merge it with emit_unencrypted_log.
pub fn emit_unencrypted_log<T, N, M>(&mut self, log: T) where T: ToBytesForUnencryptedLog<N, M> {
pub fn emit_unencrypted_log<T, N, M>(
&mut self,
log: T
) where T: ToBytesForUnencryptedLog<N, M> {
let event_selector = 5; // TODO: compute actual event selector.
let contract_address = self.this_address();
let counter = self.next_counter();
Expand Down Expand Up @@ -317,7 +322,7 @@ impl PrivateContext {
) where [Field; N]: LensForEncryptedLog<N, M> {
let ovsk_app = self.request_ovsk_app(ovpk_m.hash());

// We are currently just encrypting it EXACTLY the same way as if it was a note.
// We are currently just encrypting it unconstrained, but otherwise the same way as if it was a note.
let counter = self.next_counter();
let encrypted_log: [u8; M] = compute_encrypted_log(
contract_address,
Expand All @@ -339,7 +344,6 @@ impl PrivateContext {
&mut self,
contract_address: AztecAddress,
storage_slot: Field,
note_type_id: Field,
ovpk_m: GrumpkinPoint,
ivpk_m: GrumpkinPoint,
note: Note
Expand All @@ -352,23 +356,11 @@ impl PrivateContext {
assert(
note_exists_index != MAX_NEW_NOTE_HASHES_PER_CALL, "Can only emit a note log for an existing note."
);
let preimage = note.serialize_content();

let counter = self.next_counter();

let ovsk_app = self.request_ovsk_app(ovpk_m.hash());

// TODO(#1139 | #6408): perform encryption in the circuit
let encrypted_log: [u8; M] = compute_encrypted_log(
contract_address,
storage_slot,
note_type_id,
ovsk_app,
ovpk_m,
ivpk_m,
preimage
);
emit_encrypted_note_log(note_hash_counter, encrypted_log, counter);

// Current unoptimized size of the encrypted log
// incoming_tag (32 bytes)
// outgoing_tag (32 bytes)
Expand All @@ -378,8 +370,11 @@ impl PrivateContext {
// outgoing_body (176 bytes)
// incoming_body_fixed (64 bytes)
// incoming_body_variable (N * 32 bytes + 16 bytes padding)
let encrypted_log: [u8; M] = compute_encrypted_note_log(contract_address, storage_slot, ovsk_app, ovpk_m, ivpk_m, note);
emit_encrypted_note_log(note_hash_counter, encrypted_log, counter);

// len of processed log (4 bytes)
let len = 32 + 32 + 64 + 48 + 48 + 176 + 64 + (preimage.len() as Field * 32) + 16 + 4;
let len = encrypted_log.len() as Field + 4;

let log_hash = sha256_to_field(encrypted_log);
let side_effect = NoteLogHash { value: log_hash, counter, length: len, note_hash_counter };
Expand Down
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,3 +1,4 @@
mod header;
mod incoming_body;
mod outgoing_body;
mod payload;
1 change: 0 additions & 1 deletion noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ impl EncryptedLogHeader {
EncryptedLogHeader { address }
}

// @todo Issue(#5901) Figure out if we return the bytes or fields for the log
fn compute_ciphertext(self, secret: GrumpkinPrivateKey, point: GrumpkinPoint) -> [u8; 48] {
let full_key = point_to_symmetric_key(secret, point);
let mut sym_key = [0; 16];
Expand Down
101 changes: 101 additions & 0 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use dep::protocol_types::{
address::AztecAddress, grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint,
constants::{GENERATOR_INDEX__IVSK_M, GENERATOR_INDEX__OVSK_M}, hash::poseidon2_hash
};

use dep::std::{embedded_curve_ops::{embedded_curve_add, EmbeddedCurvePoint}, field::bytes32_to_field};

use crate::oracle::unsafe_rand::unsafe_rand;

use crate::note::note_interface::NoteInterface;

use crate::encrypted_logs::{
header::EncryptedLogHeader, incoming_body::EncryptedLogIncomingBody,
outgoing_body::EncryptedLogOutgoingBody
};

pub fn compute_encrypted_note_log<Note, N, NB, M>(
contract_address: AztecAddress,
storage_slot: Field,
ovsk_app: Field,
ovpk: GrumpkinPoint,
ivpk: GrumpkinPoint,
note: Note
) -> [u8; M] where Note: NoteInterface<N, NB> {
// @todo Need to draw randomness from the full domain of Fq not only Fr
let eph_sk: GrumpkinPrivateKey = fr_to_private_key(unsafe_rand());
let eph_pk = eph_sk.derive_public_key();

// @todo This value needs to be populated!
let recipient = AztecAddress::from_field(0);

let ivpk_app = compute_ivpk_app(ivpk, contract_address);

let header = EncryptedLogHeader::new(contract_address);

let incoming_header_ciphertext: [u8; 48] = header.compute_ciphertext(eph_sk, ivpk);
let outgoing_Header_ciphertext: [u8; 48] = header.compute_ciphertext(eph_sk, ovpk);
let incoming_body_ciphertext = EncryptedLogIncomingBody::from_note(note, storage_slot).compute_ciphertext(eph_sk, ivpk_app);
let outgoing_body_ciphertext: [u8; 176] = EncryptedLogOutgoingBody::new(eph_sk, recipient, ivpk_app).compute_ciphertext(fr_to_private_key(ovsk_app), eph_pk);

let mut encrypted_bytes: [u8; M] = [0; M];
// @todo We ignore the tags for now

let eph_pk_bytes = eph_pk.to_be_bytes();
for i in 0..64 {
encrypted_bytes[64 + i] = eph_pk_bytes[i];
}
for i in 0..48 {
encrypted_bytes[128 + i] = incoming_header_ciphertext[i];
encrypted_bytes[176 + i] = outgoing_Header_ciphertext[i];
}
for i in 0..176 {
encrypted_bytes[224 + i] = outgoing_body_ciphertext[i];
}
// Then we fill in the rest as the incoming body ciphertext
let size = M - 400;
assert_eq(size, incoming_body_ciphertext.len(), "ciphertext length mismatch");
for i in 0..size {
encrypted_bytes[400 + i] = incoming_body_ciphertext[i];
}

encrypted_bytes
}

fn fr_to_private_key(r: Field) -> GrumpkinPrivateKey {
let r_bytes = r.to_be_bytes(32);

let mut high_bytes = [0; 32];
let mut low_bytes = [0; 32];

for i in 0..16 {
high_bytes[16 + i] = r_bytes[i];
low_bytes[16 + i] = r_bytes[i + 16];
}

let low = bytes32_to_field(low_bytes);
let high = bytes32_to_field(high_bytes);

GrumpkinPrivateKey::new(high, low)
}

fn compute_ivpk_app(ivpk: GrumpkinPoint, contract_address: AztecAddress) -> GrumpkinPoint {
// It is useless to compute this, it brings no value to derive fully.
// Issue(#6955)
ivpk

/*
// @todo Just setting infinite to false, but it should be checked.
// for example user could define ivpk = infinity using the registry
assert((ivpk.x != 0) & (ivpk.y != 0), "ivpk is infinite");
let i = fr_to_private_key(poseidon2_hash([contract_address.to_field(), ivpk.x, ivpk.y, GENERATOR_INDEX__IVSK_M]));
let I = i.derive_public_key();
let embed_I = EmbeddedCurvePoint { x: I.x, y: I.y, is_infinite: false };
let embed_ivpk = EmbeddedCurvePoint { x: ivpk.x, y: ivpk.y, is_infinite: false };
let embed_result = embedded_curve_add(embed_I, embed_ivpk);
GrumpkinPoint::new(embed_result.x, embed_result.y)*/
}
1 change: 0 additions & 1 deletion noir-projects/aztec-nr/value-note/src/value_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ impl NoteInterface<VALUE_NOTE_LEN, VALUE_NOTE_BYTES_LEN> for ValueNote {
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ impl NoteInterface<SUBSCRIPTION_NOTE_LEN, SUBSCRIPTION_NOTE_BYTES_LEN> for Subsc
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ impl NoteInterface<CARD_NOTE_LEN, CARD_NOTE_BYTES_LEN> for CardNote {
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ impl NoteInterface<ECDSA_PUBLIC_KEY_NOTE_LEN, ECDSA_PUBLIC_KEY_NOTE_BYTES_LEN> f
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ contract PendingNoteHashes {
context.encrypt_and_emit_note(
context.this_address(),
note.get_header().storage_slot,
ValueNote::get_note_type_id(),
outgoing_viewer_ovpk_m,
owner_ivpk_m,
note
Expand Down Expand Up @@ -372,7 +371,6 @@ contract PendingNoteHashes {
context.encrypt_and_emit_note(
context.this_address(),
existing_note_header.storage_slot,
ValueNote::get_note_type_id(),
outgoing_viewer_ovpk_m,
owner_ivpk_m,
bad_note
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ impl NoteInterface<PUBLIC_KEY_NOTE_LEN, PUBLIC_KEY_NOTE_BYTES_LEN> for PublicKey
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ impl NoteInterface<TOKEN_NOTE_LEN, TOKEN_NOTE_BYTES_LEN> for TokenNote {
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ impl NoteInterface<TOKEN_NOTE_LEN, TOKEN_NOTE_BYTES_LEN> for TokenNote {
context.encrypt_and_emit_note(
(*context).this_address(),
slot,
Self::get_note_type_id(),
ovpk_m,
ivpk_m,
self,
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/circuits.js/src/keys/derivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ export function computeAppSecretKey(skM: GrumpkinPrivateKey, app: AztecAddress,
}

export function computeIvpkApp(ivpk: PublicKey, address: AztecAddress) {
return ivpk;
// Computing the siloed key is actually useless because we can derive the master key from it
// Issue(#6955)
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) {
return ivsk;
// Computing the siloed key is actually useless because we can derive the master key from it
// Issue(#6955)
const ivpk = curve.mul(Grumpkin.generator, ivsk);
// Here we are intentionally converting Fr (output of poseidon) to Fq. This is fine even though a distribution of
// P = s * G will not be uniform because 2 * (q - r) / q is small.
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ describe('e2e_block_building', () => {

const ownerAddress = owner.getCompleteAddress().address;
const outgoingViewer = ownerAddress;
const methods = times(TX_COUNT, i => deployer.deploy(ownerAddress, outgoingViewer, i));
// Need to have value > 0, so adding + 1
// We need to do so, because noir currently will fail if the multiscalarmul is in an `if`
// that we DO NOT enter. This should be fixed by https://github.com/noir-lang/noir/issues/5045.
const methods = times(TX_COUNT, i => deployer.deploy(ownerAddress, outgoingViewer, i + 1));
for (let i = 0; i < TX_COUNT; i++) {
await methods[i].create({
contractAddressSalt: new Fr(BigInt(i + 1)),
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/simulator/src/client/client_execution_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ export class ClientExecutionContext extends ViewDataOracle {

const ephSk = GrumpkinScalar.random();

// @todo This should be populated properly.
// Note that this encryption function SHOULD not be used, but is currently used
// as oracle for encrypted event logs.
const recipient = AztecAddress.random();

return taggedNote.encrypt(ephSk, recipient, ivpkM, ovKeys);
Expand Down

0 comments on commit e59f4d3

Please sign in to comment.