From e59f4d3cee4b27248d26111fc6fda2f0e55a7d54 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:03:45 +0100 Subject: [PATCH] feat: constrain note encryption (#6432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../aztec-nr/address-note/src/address_note.nr | 1 - .../aztec/src/context/private_context.nr | 29 +++-- .../aztec-nr/aztec/src/encrypted_logs.nr | 1 + .../aztec/src/encrypted_logs/header.nr | 1 - .../aztec/src/encrypted_logs/payload.nr | 101 ++++++++++++++++++ .../aztec-nr/value-note/src/value_note.nr | 1 - .../src/subscription_note.nr | 1 - .../src/types/card_note.nr | 1 - .../src/ecdsa_public_key_note.nr | 1 - .../pending_note_hashes_contract/src/main.nr | 2 - .../src/public_key_note.nr | 1 - .../src/types/token_note.nr | 1 - .../token_contract/src/types/token_note.nr | 1 - .../circuits.js/src/keys/derivation.ts | 6 ++ .../end-to-end/src/e2e_block_building.test.ts | 5 +- .../src/client/client_execution_context.ts | 3 + 16 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr diff --git a/noir-projects/aztec-nr/address-note/src/address_note.nr b/noir-projects/aztec-nr/address-note/src/address_note.nr index 32b13d77402..7478b59994c 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -49,7 +49,6 @@ impl NoteInterface for AddressNote { context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index b6c2c12271e..25570b7c9de 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -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, @@ -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(&mut self, log: T) where T: ToBytesForUnencryptedLog { + pub fn emit_unencrypted_log( + &mut self, + log: T + ) where T: ToBytesForUnencryptedLog { let event_selector = 5; // TODO: compute actual event selector. let contract_address = self.this_address(); let counter = self.next_counter(); @@ -317,7 +322,7 @@ impl PrivateContext { ) where [Field; N]: LensForEncryptedLog { 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, @@ -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 @@ -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) @@ -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 }; diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr index 61cb92154d4..d1df9c64e41 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr @@ -1,3 +1,4 @@ mod header; mod incoming_body; mod outgoing_body; +mod payload; \ No newline at end of file diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr index a96a1666e5e..98c2d42d5b9 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr @@ -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]; diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr new file mode 100644 index 00000000000..7606d6f010d --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -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( + contract_address: AztecAddress, + storage_slot: Field, + ovsk_app: Field, + ovpk: GrumpkinPoint, + ivpk: GrumpkinPoint, + note: Note +) -> [u8; M] where Note: NoteInterface { + // @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)*/ +} diff --git a/noir-projects/aztec-nr/value-note/src/value_note.nr b/noir-projects/aztec-nr/value-note/src/value_note.nr index 73fe20b3c7a..042144d76ae 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -51,7 +51,6 @@ impl NoteInterface for ValueNote { context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr index c3fc0feb7b1..aebe782e4c6 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr @@ -44,7 +44,6 @@ impl NoteInterface for Subsc context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index 16392f5b18d..ecef44ab355 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -55,7 +55,6 @@ impl NoteInterface for CardNote { context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr index 0a73ba51759..93583b8b67d 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/ecdsa_public_key_note.nr @@ -90,7 +90,6 @@ impl NoteInterface f context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 2168a93fd93..7d672df1460 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -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 @@ -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 diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr index 1282c27e800..819a3a3916c 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr @@ -44,7 +44,6 @@ impl NoteInterface for PublicKey context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr index c27d292625e..29f2112959d 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr @@ -56,7 +56,6 @@ impl NoteInterface for TokenNote { context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index c27d292625e..29f2112959d 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -56,7 +56,6 @@ impl NoteInterface for TokenNote { context.encrypt_and_emit_note( (*context).this_address(), slot, - Self::get_note_type_id(), ovpk_m, ivpk_m, self, diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index c867bebd61a..4d5e5ba3915 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -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. diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index f3a731eab2c..538bacf920f 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -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)), diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index 0e30f7fe9c0..1309dc26323 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -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);