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 25bb33ba663..6a5a7048371 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -1,17 +1,11 @@ use crate::{ context::{inputs::PrivateContextInputs, packed_returns::PackedReturns}, - messaging::process_l1_to_l2_message, - hash::{hash_args_array, ArgsHasher, compute_unencrypted_log_hash}, + messaging::process_l1_to_l2_message, hash::{hash_args_array, ArgsHasher}, keys::constants::{NULLIFIER_INDEX, OUTGOING_INDEX, NUM_KEY_TYPES, sk_generators}, - note::note_interface::NoteInterface, oracle::{ key_validation_request::get_key_validation_request, arguments, returns::pack_returns, call_private_function::call_private_function_internal, header::get_header_at, - logs::{ - emit_encrypted_note_log, emit_encrypted_event_log, - emit_contract_class_unencrypted_log_private_internal, emit_unencrypted_log_private_internal -}, - logs_traits::{LensForEncryptedLog, ToBytesForUnencryptedLog}, + logs::{emit_encrypted_note_log, emit_encrypted_event_log}, enqueue_public_function_call::{ enqueue_public_function_call_internal, set_public_teardown_function_call_internal, parse_public_call_stack_item_from_oracle @@ -36,10 +30,7 @@ use dep::protocol_types::{ MAX_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_ENCRYPTED_LOGS_PER_CALL, MAX_UNENCRYPTED_LOGS_PER_CALL, MAX_NOTE_ENCRYPTED_LOGS_PER_CALL }, - contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, - grumpkin_private_key::GrumpkinPrivateKey, grumpkin_point::GrumpkinPoint, header::Header, - messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader, traits::{is_empty, Empty}, - utils::arrays::find_index + header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader, traits::Empty }; // When finished, one can call .finish() to convert back to the abi @@ -270,42 +261,6 @@ impl PrivateContext { } // docs:end:consume_l1_to_l2_message - // TODO: We might want to remove this since emitting unencrypted logs from private functions is violating privacy. - // --> 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 { - let event_selector = 5; // TODO: compute actual event selector. - let contract_address = self.this_address(); - let counter = self.next_counter(); - let log_slice = log.to_be_bytes_arr(); - let log_hash = compute_unencrypted_log_hash(contract_address, event_selector, log); - // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) - let len = 44 + log_slice.len().to_field(); - let side_effect = LogHash { value: log_hash, counter, length: len }; - self.unencrypted_logs_hashes.push(side_effect); - // call oracle - let _void = emit_unencrypted_log_private_internal(contract_address, event_selector, log, counter); - } - - // This fn exists separately from emit_unencrypted_log because sha hashing the preimage - // is too large to compile (16,200 fields, 518,400 bytes) => the oracle hashes it - // It is ONLY used with contract_class_registerer_contract since we already assert correctness: - // - Contract class -> we will commit to the packed bytecode (currently a TODO) - // - Private function -> we provide a membership proof - // - Unconstrained function -> we provide a membership proof - // Ordinary logs are not protected by the above so this fn shouldn't be called by anything else - pub fn emit_contract_class_unencrypted_log(&mut self, log: [Field; N]) { - let event_selector = 5; // TODO: compute actual event selector. - let contract_address = self.this_address(); - let counter = self.next_counter(); - let log_hash = emit_contract_class_unencrypted_log_private_internal(contract_address, event_selector, log, counter); - // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) - let len = 44 + N * 32; - let side_effect = LogHash { value: log_hash, counter, length: len }; - self.unencrypted_logs_hashes.push(side_effect); - } - // NB: A randomness value of 0 signals that the kernels should not mask the contract address // used in siloing later on e.g. 'handshaking' contract w/ known address. pub fn emit_raw_event_log_with_masked_address(&mut self, randomness: Field, encrypted_log: [u8; M]) { diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index aa5e3bf242d..16d6f1ceaf6 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -9,9 +9,11 @@ contract ContractClassRegisterer { ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, FUNCTION_TREE_HEIGHT, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE }, - traits::Serialize + traits::Serialize, abis::log_hash::LogHash }; + use dep::aztec::{context::PrivateContext, oracle::logs::emit_contract_class_unencrypted_log_private_internal}; + use crate::events::{ class_registered::ContractClassRegistered, private_function_broadcasted::{ClassPrivateFunctionBroadcasted, PrivateFunction}, @@ -52,7 +54,7 @@ contract ContractClassRegisterer { public_bytecode_commitment ] ); - context.emit_contract_class_unencrypted_log(event.serialize()); + emit_contract_class_unencrypted_log(&mut context, event.serialize()); } #[aztec(private)] @@ -87,7 +89,7 @@ contract ContractClassRegisterer { function_data.metadata_hash ] ); - context.emit_contract_class_unencrypted_log(event.serialize()); + emit_contract_class_unencrypted_log(&mut context, event.serialize()); } #[aztec(private)] @@ -117,6 +119,25 @@ contract ContractClassRegisterer { function_data.metadata_hash ] ); - context.emit_contract_class_unencrypted_log(event.serialize()); + emit_contract_class_unencrypted_log(&mut context, event.serialize()); + } + + // This fn exists separately from emit_unencrypted_log because sha hashing the preimage + // is too large to compile (16,200 fields, 518,400 bytes) => the oracle hashes it + // It is ONLY used with contract_class_registerer_contract since we already assert correctness: + // - Contract class -> we will commit to the packed bytecode (currently a TODO) + // - Private function -> we provide a membership proof + // - Unconstrained function -> we provide a membership proof + // Ordinary logs are not protected by the above so this fn shouldn't be called by anything else + #[contract_library_method] + pub fn emit_contract_class_unencrypted_log(context: &mut PrivateContext, log: [Field; N]) { + let event_selector = 5; // TODO: compute actual event selector. + let contract_address = context.this_address(); + let counter = context.next_counter(); + let log_hash = emit_contract_class_unencrypted_log_private_internal(contract_address, event_selector, log, counter); + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) + let len = 44 + N * 32; + let side_effect = LogHash { value: log_hash, counter, length: len }; + context.unencrypted_logs_hashes.push(side_effect); } } diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events.nr b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events.nr deleted file mode 100644 index d2b6ed6033f..00000000000 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events.nr +++ /dev/null @@ -1 +0,0 @@ -mod instance_deployed; diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events/instance_deployed.nr b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events/instance_deployed.nr deleted file mode 100644 index 638a08db001..00000000000 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/events/instance_deployed.nr +++ /dev/null @@ -1,33 +0,0 @@ -use dep::aztec::protocol_types::{ - contract_class_id::ContractClassId, - address::{AztecAddress, EthAddress, PublicKeysHash, PartialAddress}, - constants::DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, traits::Serialize -}; - -// #[aztec(event)] -struct ContractInstanceDeployed { - address: AztecAddress, - version: u8, - salt: Field, - contract_class_id: ContractClassId, - initialization_hash: Field, - public_keys_hash: PublicKeysHash, - deployer: AztecAddress, -} - -global CONTRACT_INSTANCE_DEPLOYED_SERIALIZED_SIZE: Field = 8; - -impl Serialize for ContractInstanceDeployed { - fn serialize(self: Self) -> [Field; CONTRACT_INSTANCE_DEPLOYED_SERIALIZED_SIZE] { - [ - DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, - self.address.to_field(), - self.version as Field, - self.salt, - self.contract_class_id.to_field(), - self.initialization_hash, - self.public_keys_hash.to_field(), - self.deployer.to_field(), - ] - } -} diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr index 43e7d45c655..56f5e4827f5 100644 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr @@ -1,13 +1,42 @@ -mod events; - contract ContractInstanceDeployer { use dep::aztec::protocol_types::{ address::{AztecAddress, EthAddress, PublicKeysHash, PartialAddress}, contract_class_id::ContractClassId, constants::DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, - traits::Serialize + traits::Serialize, abis::log_hash::LogHash + }; + use dep::aztec::{ + context::PrivateContext, hash::compute_unencrypted_log_hash, + oracle::logs::emit_unencrypted_log_private_internal }; - use crate::events::{instance_deployed::ContractInstanceDeployed}; + // @todo This should be using an event, but currently we only support fields in the struct. + // #[aztec(event)] + struct ContractInstanceDeployed { + address: AztecAddress, + version: u8, + salt: Field, + contract_class_id: ContractClassId, + initialization_hash: Field, + public_keys_hash: PublicKeysHash, + deployer: AztecAddress, + } + + global CONTRACT_INSTANCE_DEPLOYED_SERIALIZED_SIZE: Field = 8; + + impl Serialize for ContractInstanceDeployed { + fn serialize(self: Self) -> [Field; CONTRACT_INSTANCE_DEPLOYED_SERIALIZED_SIZE] { + [ + DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, + self.address.to_field(), + self.version as Field, + self.salt, + self.contract_class_id.to_field(), + self.initialization_hash, + self.public_keys_hash.to_field(), + self.deployer.to_field(), + ] + } + } #[aztec(private)] fn deploy( @@ -34,8 +63,25 @@ contract ContractInstanceDeployer { // Broadcast the event let event = ContractInstanceDeployed { contract_class_id, address, public_keys_hash, initialization_hash, salt, deployer, version: 1 }; - let event_payload = event.serialize(); - dep::aztec::oracle::debug_log::debug_log_format("ContractInstanceDeployed: {}", event_payload); - context.emit_unencrypted_log(event_payload); + + let payload = event.serialize(); + dep::aztec::oracle::debug_log::debug_log_format("ContractInstanceDeployed: {}", payload); + + let contract_address = context.this_address(); + let counter = context.next_counter(); + + // The event_type_id is not strictly needed. So i'm setting it to 0 here, and we can then purge it + // later on. + let event_type_id = 0; + + // @todo This is very inefficient, we are doing a lot of back and forth conversions. + let log_slice = payload.to_be_bytes_arr(); + let log_hash = compute_unencrypted_log_hash(contract_address, event_type_id, payload); + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4) + let len = 44 + log_slice.len().to_field(); + let side_effect = LogHash { value: log_hash, counter, length: len }; + context.unencrypted_logs_hashes.push(side_effect); + + let _void = emit_unencrypted_log_private_internal(contract_address, event_type_id, payload, counter); } } diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index 9e43661a329..4eb7657462e 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -4,13 +4,11 @@ contract Crowdfunding { // docs:start:all-deps use dep::aztec::{ - protocol_types::{ - abis::function_selector::FunctionSelector, address::AztecAddress, traits::Serialize, - grumpkin_point::GrumpkinPoint - }, + protocol_types::address::AztecAddress, encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, state_vars::{PrivateSet, PublicImmutable, SharedImmutable} }; + use dep::aztec::unencrypted_logs::unencrypted_event_emission::encode_event; use dep::value_note::value_note::ValueNote; use dep::token::Token; // docs:end:all-deps @@ -95,10 +93,14 @@ contract Crowdfunding { // 2) Transfer the donation tokens from this contract to the operator Token::at(storage.donation_token.read_private()).transfer(operator_address, amount as Field).call(&mut context); - // 3) Emit an unencrypted event so that anyone can audit how much the operator has withdrawn - let event = WithdrawalProcessed { amount: amount as Field, who: operator_address.to_field() }; - context.emit_unencrypted_log(event.serialize()); + Crowdfunding::at(context.this_address())._publish_donation_receipts(amount, operator_address).enqueue(&mut context); } // docs:end:operator-withdrawals + + #[aztec(public)] + #[aztec(internal)] + fn _publish_donation_receipts(amount: u64, to: AztecAddress) { + WithdrawalProcessed { amount: amount as Field, who: to.to_field() }.emit(encode_event(&mut context)); + } } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 1db5e91ff24..2f8e598aa64 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -299,25 +299,6 @@ contract Test { } } - #[aztec(private)] - fn emit_msg_sender() { - context.emit_unencrypted_log(context.msg_sender()); - } - - #[aztec(private)] - fn emit_unencrypted_logs(fields: [Field; 5], nest: bool) { - // Merged two fns to avoid hitting max #functions limit: - // nest -> emit_unencrypted_logs_nested - // else -> emit_array_as_unencrypted_log - if nest { - Test::at(context.this_address()).emit_msg_sender().call(&mut context); - Test::at(context.this_address()).emit_unencrypted_logs(fields, false).call(&mut context); - context.emit_unencrypted_log("test"); - } else { - context.emit_unencrypted_log(fields); - } - } - #[aztec(private)] fn emit_encrypted_logs_nested(value: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let mut storage_slot = storage.example_constant.get_storage_slot() + 1; diff --git a/yarn-project/circuits.js/package.json b/yarn-project/circuits.js/package.json index 524b4a62210..1444de37a2d 100644 --- a/yarn-project/circuits.js/package.json +++ b/yarn-project/circuits.js/package.json @@ -97,4 +97,4 @@ ] ] } -} +} \ No newline at end of file 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 9a308a2e523..206e19e00b4 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 @@ -246,24 +246,6 @@ describe('e2e_block_building', () => { testContract = await TestContract.deploy(owner).send().deployed(); }, 60_000); - it('calls a method with nested unencrypted logs', async () => { - const tx = await testContract.methods.emit_unencrypted_logs([1, 2, 3, 4, 5], true).send().wait(); - const logs = (await pxe.getUnencryptedLogs({ txHash: tx.txHash })).logs.map(l => l.log); - - // First log should be contract address - expect(logs[0].data).toEqual(testContract.address.toBuffer()); - - // Second log should be array of fields - let expectedBuffer = Buffer.concat([1, 2, 3, 4, 5].map(num => new Fr(num).toBuffer())); - expect(logs[1].data.subarray(-32 * 5)).toEqual(expectedBuffer); - - // Third log should be string "test" - expectedBuffer = Buffer.concat( - ['t', 'e', 's', 't'].map(num => Buffer.concat([Buffer.alloc(31), Buffer.from(num)])), - ); - expect(logs[2].data.subarray(-32 * 5)).toEqual(expectedBuffer); - }, 60_000); - it('calls a method with nested note encrypted logs', async () => { // account setup const privateKey = new Fr(7n); diff --git a/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts b/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts index 1728f637e1e..54e377ad4e9 100644 --- a/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts +++ b/yarn-project/end-to-end/src/e2e_non_contract_account.test.ts @@ -1,13 +1,4 @@ -import { - type DebugLogger, - ExtendedNote, - Fr, - Note, - type PXE, - SignerlessWallet, - type Wallet, - toBigInt, -} from '@aztec/aztec.js'; +import { type DebugLogger, ExtendedNote, Fr, Note, type PXE, SignerlessWallet, type Wallet } from '@aztec/aztec.js'; import { siloNullifier } from '@aztec/circuits.js/hash'; import { TestContract } from '@aztec/noir-contracts.js/Test'; @@ -50,20 +41,6 @@ describe('e2e_non_contract_account', () => { expect(siloedNullifier.equals(expectedSiloedNullifier)).toBeTruthy(); }); - it('msg.sender is 0 when a non-contract account calls a private function on a contract', async () => { - const contractWithNoContractWallet = await TestContract.at(contract.address, nonContractAccountWallet); - - // Send transaction as arbitrary non-contract account - const tx = contractWithNoContractWallet.methods.emit_msg_sender().send(); - await tx.wait({ interval: 0.1 }); - - const logs = (await tx.getUnencryptedLogs()).logs; - expect(logs.length).toBe(1); - - const msgSender = toBigInt(logs[0].log.data); - expect(msgSender).toBe(0n); - }); - // Note: This test doesn't really belong here as it doesn't have anything to do with non-contract accounts. I needed // to test the TestNote functionality and it doesn't really fit anywhere else. Creating a separate e2e test for this // seems wasteful. Move this test if a better place is found. diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index c46153495fb..e5e1e22ac18 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -67,7 +67,7 @@ import { MessageLoadOracleInputs } from '../acvm/index.js'; import { buildL1ToL2Message } from '../test/utils.js'; import { computeSlotForMapping } from '../utils.js'; import { type DBOracle } from './db_oracle.js'; -import { type ExecutionResult, collectSortedEncryptedLogs, collectSortedUnencryptedLogs } from './execution_result.js'; +import { type ExecutionResult, collectSortedEncryptedLogs } from './execution_result.js'; import { AcirSimulator } from './simulator.js'; jest.setTimeout(60_000); @@ -266,41 +266,6 @@ describe('Private Execution test suite', () => { }); describe('no constructor', () => { - it('emits a field as an unencrypted log', async () => { - const artifact = getFunctionArtifact(TestContractArtifact, 'emit_msg_sender'); - const result = await runSimulator({ artifact, msgSender: owner }); - - const newUnencryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.unencryptedLogsHashes); - expect(newUnencryptedLogs).toHaveLength(1); - - const functionLogs = collectSortedUnencryptedLogs(result); - expect(functionLogs.logs).toHaveLength(1); - - const [unencryptedLog] = newUnencryptedLogs; - expect(unencryptedLog.value).toEqual(Fr.fromBuffer(functionLogs.logs[0].hash())); - expect(unencryptedLog.length).toEqual(new Fr(functionLogs.getKernelLength())); - // Test that the log payload (ie ignoring address, selector, and header) matches what we emitted - expect(functionLogs.logs[0].data.subarray(-32).toString('hex')).toEqual(owner.toBuffer().toString('hex')); - }); - - it('emits a field array as an unencrypted log', async () => { - const artifact = getFunctionArtifact(TestContractArtifact, 'emit_unencrypted_logs'); - const args = times(5, () => Fr.random()); - const result = await runSimulator({ artifact, msgSender: owner, args: [args, false] }); - - const newUnencryptedLogs = getNonEmptyItems(result.callStackItem.publicInputs.unencryptedLogsHashes); - expect(newUnencryptedLogs).toHaveLength(1); - const functionLogs = collectSortedUnencryptedLogs(result); - expect(functionLogs.logs).toHaveLength(1); - - const [unencryptedLog] = newUnencryptedLogs; - expect(unencryptedLog.value).toEqual(Fr.fromBuffer(functionLogs.logs[0].hash())); - expect(unencryptedLog.length).toEqual(new Fr(functionLogs.getKernelLength())); - // Test that the log payload (ie ignoring address, selector, and header) matches what we emitted - const expected = Buffer.concat(args.map(arg => arg.toBuffer())).toString('hex'); - expect(functionLogs.logs[0].data.subarray(-32 * 5).toString('hex')).toEqual(expected); - }); - it('emits a field array as an encrypted log', async () => { // NB: this test does NOT cover correct enc/dec of values, just whether // the kernels correctly populate non-note encrypted logs @@ -952,7 +917,7 @@ describe('Private Execution test suite', () => { describe('setting fee payer', () => { it('should default to not being a fee payer', async () => { // arbitrary random function that doesn't set a fee payer - const entrypoint = getFunctionArtifact(TestContractArtifact, 'emit_msg_sender'); + const entrypoint = getFunctionArtifact(TestContractArtifact, 'get_this_address'); const contractAddress = AztecAddress.random(); const result = await runSimulator({ artifact: entrypoint, contractAddress }); expect(result.callStackItem.publicInputs.isFeePayer).toBe(false);