Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: constrain note encryption #6432

Merged
merged 8 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we link issue here?

// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we link issue here?

// 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
Loading