diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 4b089fa49b9..aee54d0da7c 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -387,8 +387,10 @@ describe('Private Execution test suite', () => { beforeAll(async () => { // These args should match the ones hardcoded in importer contract - const dummyNote = { amount: 1, secretHash: 2 }; - const deepStruct = { aField: 1, aBool: true, aNote: dummyNote, manyNotes: [dummyNote, dummyNote, dummyNote] }; + // eslint-disable-next-line camelcase + const dummyNote = { amount: 1, secret_hash: 2 }; + // eslint-disable-next-line camelcase + const deepStruct = { a_field: 1, a_bool: true, a_note: dummyNote, many_notes: [dummyNote, dummyNote, dummyNote] }; args = [1, true, 1, [1, 2], dummyNote, deepStruct]; testCodeGenArtifact = getFunctionArtifact(TestContractArtifact, 'test_code_gen'); const serializedArgs = encodeArguments(testCodeGenArtifact, args); diff --git a/yarn-project/aztec-nr/field-note/Nargo.toml b/yarn-project/aztec-nr/field-note/Nargo.toml new file mode 100644 index 00000000000..a00c3f2c582 --- /dev/null +++ b/yarn-project/aztec-nr/field-note/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "field_note" +authors = ["aztec-labs"] +compiler_version = "0.7.1" +type = "lib" + +[dependencies] +aztec = { path = "../aztec" } \ No newline at end of file diff --git a/yarn-project/aztec-nr/field-note/src/field_note.nr b/yarn-project/aztec-nr/field-note/src/field_note.nr new file mode 100644 index 00000000000..845dd0cea59 --- /dev/null +++ b/yarn-project/aztec-nr/field-note/src/field_note.nr @@ -0,0 +1,90 @@ +use dep::aztec::{ + note::{ + note_header::NoteHeader, + note_interface::NoteInterface, + }, + hash::pedersen_hash, + context::PrivateContext, +}; + +global FIELD_NOTE_LEN: Field = 1; + +// A note which stores a field and is expected to be passed around using the `addNote` function. +// WARNING: This Note is not private as it does not contain randomness and hence it can be easy to perform preimage +// attack on it. +struct FieldNote { + value: Field, + header: NoteHeader, +} + +impl FieldNote { + pub fn new(value: Field) -> Self { + FieldNote { + value, + header: NoteHeader::empty(), + } + } + + pub fn serialize(self) -> [Field; FIELD_NOTE_LEN]{ + [self.value] + } + + pub fn deserialize(preimage: [Field; FIELD_NOTE_LEN]) -> Self { + FieldNote { + value: preimage[0], + header: NoteHeader::empty(), + } + } + + pub fn compute_note_hash(self) -> Field { + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash(self.serialize(), 0) + } + + pub fn compute_nullifier(self) -> Field { + // This note is expected to be shared between users and for this reason can't be nullified using a secret. + 0 + } + + pub fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } +} + +fn deserialize(preimage: [Field; FIELD_NOTE_LEN]) -> FieldNote { + FieldNote::deserialize(preimage) +} + +fn serialize(note: FieldNote) -> [Field; FIELD_NOTE_LEN]{ + note.serialize() +} + +fn compute_note_hash(note: FieldNote) -> Field { + note.compute_note_hash() +} + +fn compute_nullifier(note: FieldNote) -> Field { + note.compute_nullifier() +} + +fn get_header(note: FieldNote) -> NoteHeader { + note.header +} + +fn set_header(note: &mut FieldNote, header: NoteHeader) { + note.set_header(header); +} + +fn broadcast(context: &mut PrivateContext, slot: Field, note: FieldNote) { + assert(false, "FieldNote does not support broadcast. Add it to PXE directly using the `.addNote` function."); +} + +global FieldNoteMethods = NoteInterface { + deserialize, + serialize, + compute_note_hash, + compute_nullifier, + get_header, + set_header, + broadcast, +}; diff --git a/yarn-project/aztec-nr/field-note/src/lib.nr b/yarn-project/aztec-nr/field-note/src/lib.nr new file mode 100644 index 00000000000..2625d3a2277 --- /dev/null +++ b/yarn-project/aztec-nr/field-note/src/lib.nr @@ -0,0 +1 @@ +mod field_note; \ No newline at end of file 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 a5b5fe1d0c0..895646a9e95 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 @@ -4,7 +4,7 @@ import { siloNullifier } from '@aztec/circuits.js/abis'; import { DebugLogger } from '@aztec/foundation/log'; import { toBigInt } from '@aztec/foundation/serialize'; import { TestContract } from '@aztec/noir-contracts/types'; -import { AztecNode, PXE, TxStatus } from '@aztec/types'; +import { AztecNode, NotePreimage, PXE, TxStatus } from '@aztec/types'; import { setup } from './fixtures/utils.js'; @@ -17,9 +17,9 @@ describe('e2e_non_contract_account', () => { let logger: DebugLogger; let contract: TestContract; + let wallet: Wallet; beforeEach(async () => { - let wallet: Wallet; ({ teardown, aztecNode, pxe, wallet, logger } = await setup(1)); nonContractAccountWallet = new SignerlessWallet(pxe); @@ -59,4 +59,24 @@ describe('e2e_non_contract_account', () => { const msgSender = toBigInt(logs[0].log.data); expect(msgSender).toBe(0n); }, 120_000); + + // 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 FieldNote 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. + it('can set and get a constant', async () => { + const value = 123n; + + const receipt = await contract.methods.set_constant(value).send().wait({ interval: 0.1 }); + + // check that 1 commitment was created + const tx = await pxe.getTx(receipt.txHash); + const nonZeroCommitments = tx?.newCommitments.filter(c => c.value > 0); + expect(nonZeroCommitments?.length).toBe(1); + + // Add the note + const preimage = new NotePreimage([new Fr(value)]); + await wallet.addNote(wallet.getCompleteAddress().address, contract.address, new Fr(1), preimage, receipt.txHash); + + expect(await contract.methods.get_constant().view()).toEqual(value); + }); }); diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr index 11247b403b6..599a28218fb 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -32,15 +32,15 @@ contract ImportTest { true, 1 as u32, [1, 2], - AStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + AStructTestCodeGenStruct { amount: 1, secret_hash: 2 }, ADeepStructTestCodeGenStruct { - aField: 1, - aBool: true, - aNote: ANoteADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, - manyNotes: [ - ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, - ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, - ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + a_field: 1, + a_bool: true, + a_note: ANoteADeepStructTestCodeGenStruct { amount: 1, secret_hash: 2 }, + many_notes: [ + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secret_hash: 2 }, + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secret_hash: 2 }, + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secret_hash: 2 }, ] } ); diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/test_contract/Nargo.toml index afa5e911ac9..be62c146701 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/Nargo.toml +++ b/yarn-project/noir-contracts/src/contracts/test_contract/Nargo.toml @@ -6,4 +6,5 @@ type = "contract" [dependencies] aztec = { path = "../../../../aztec-nr/aztec" } +field_note = { path = "../../../../aztec-nr/field-note" } token_portal_content_hash_lib = { path = "../token_portal_content_hash_lib" } diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr index 66b6bd143d4..1bb62e9f3f6 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr @@ -6,24 +6,24 @@ use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; struct AStructTestCodeGenStruct { amount: Field, - secretHash: Field, + secret_hash: Field, } struct ADeepStructTestCodeGenStruct { - aField: Field, - aBool: bool, - aNote: ANoteADeepStructTestCodeGenStruct, - manyNotes: [ManyNotesADeepStructTestCodeGenStruct;3], + a_field: Field, + a_bool: bool, + a_note: ANoteADeepStructTestCodeGenStruct, + many_notes: [ManyNotesADeepStructTestCodeGenStruct;3], } struct ANoteADeepStructTestCodeGenStruct { amount: Field, - secretHash: Field, + secret_hash: Field, } struct ManyNotesADeepStructTestCodeGenStruct { amount: Field, - secretHash: Field, + secret_hash: Field, } @@ -83,11 +83,11 @@ impl TestPrivateContextInterface { self, context: &mut PrivateContext, amount: Field, - secretHash: Field + secret_hash: Field ) { let mut serialized_args = [0; 2]; serialized_args[0] = amount; - serialized_args[1] = secretHash; + serialized_args[1] = secret_hash; context.call_public_function(self.address, 0x9749ca06, serialized_args) } @@ -97,11 +97,11 @@ impl TestPrivateContextInterface { self, context: &mut PrivateContext, amount: Field, - secretHash: Field + secret_hash: Field ) { let mut serialized_args = [0; 2]; serialized_args[0] = amount; - serialized_args[1] = secretHash; + serialized_args[1] = secret_hash; context.call_public_function(self.address, 0xdf02db8d, serialized_args) } @@ -197,34 +197,46 @@ impl TestPrivateContextInterface { } + pub fn set_constant( + self, + context: &mut PrivateContext, + value: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialized_args = [0; 1]; + serialized_args[0] = value; + + context.call_private_function(self.address, 0x1b3b9e18, serialized_args) + } + + pub fn test_code_gen( self, context: &mut PrivateContext, - aField: Field, - aBool: bool, - aNumber: u32, - anArray: [Field;2], - aStruct: AStructTestCodeGenStruct, - aDeepStruct: ADeepStructTestCodeGenStruct + a_field: Field, + a_bool: bool, + a_number: u32, + an_array: [Field;2], + a_struct: AStructTestCodeGenStruct, + a_deep_struct: ADeepStructTestCodeGenStruct ) -> [Field; RETURN_VALUES_LENGTH] { let mut serialized_args = [0; 17]; - serialized_args[0] = aField; - serialized_args[1] = aBool as Field; - serialized_args[2] = aNumber as Field; - serialized_args[3] = anArray[0]; - serialized_args[4] = anArray[1]; - serialized_args[5] = aStruct.amount; - serialized_args[6] = aStruct.secretHash; - serialized_args[7] = aDeepStruct.aField; - serialized_args[8] = aDeepStruct.aBool as Field; - serialized_args[9] = aDeepStruct.aNote.amount; - serialized_args[10] = aDeepStruct.aNote.secretHash; - serialized_args[11] = aDeepStruct.manyNotes[0].amount; - serialized_args[12] = aDeepStruct.manyNotes[0].secretHash; - serialized_args[13] = aDeepStruct.manyNotes[1].amount; - serialized_args[14] = aDeepStruct.manyNotes[1].secretHash; - serialized_args[15] = aDeepStruct.manyNotes[2].amount; - serialized_args[16] = aDeepStruct.manyNotes[2].secretHash; + serialized_args[0] = a_field; + serialized_args[1] = a_bool as Field; + serialized_args[2] = a_number as Field; + serialized_args[3] = an_array[0]; + serialized_args[4] = an_array[1]; + serialized_args[5] = a_struct.amount; + serialized_args[6] = a_struct.secret_hash; + serialized_args[7] = a_deep_struct.a_field; + serialized_args[8] = a_deep_struct.a_bool as Field; + serialized_args[9] = a_deep_struct.a_note.amount; + serialized_args[10] = a_deep_struct.a_note.secret_hash; + serialized_args[11] = a_deep_struct.many_notes[0].amount; + serialized_args[12] = a_deep_struct.many_notes[0].secret_hash; + serialized_args[13] = a_deep_struct.many_notes[1].amount; + serialized_args[14] = a_deep_struct.many_notes[1].secret_hash; + serialized_args[15] = a_deep_struct.many_notes[2].amount; + serialized_args[16] = a_deep_struct.many_notes[2].secret_hash; context.call_private_function(self.address, 0x0f054f9b, serialized_args) } @@ -270,11 +282,11 @@ impl TestPublicContextInterface { self, context: PublicContext, amount: Field, - secretHash: Field + secret_hash: Field ) -> [Field; RETURN_VALUES_LENGTH] { let mut serialized_args = [0; 2]; serialized_args[0] = amount; - serialized_args[1] = secretHash; + serialized_args[1] = secret_hash; context.call_public_function(self.address, 0x9749ca06, serialized_args) } @@ -284,11 +296,11 @@ impl TestPublicContextInterface { self, context: PublicContext, amount: Field, - secretHash: Field + secret_hash: Field ) -> [Field; RETURN_VALUES_LENGTH] { let mut serialized_args = [0; 2]; serialized_args[0] = amount; - serialized_args[1] = secretHash; + serialized_args[1] = secret_hash; context.call_public_function(self.address, 0xdf02db8d, serialized_args) } diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index 329b67abf99..b22de0d2894 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -1,30 +1,51 @@ // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. contract Test { + use dep::std::option::Option; + // docs:start:unencrypted_import use dep::aztec::log::emit_unencrypted_log; // docs:end:unencrypted_import use dep::aztec::{ + context::Context, abi, abi::PrivateContextInputs, hash::pedersen_hash, context::PrivateContext, + note::{ + note_header::NoteHeader, + utils as note_utils, + }, oracle::{ get_public_key::get_public_key as get_public_key_oracle, context::get_portal_address, rand::rand }, + state_vars::immutable_singleton::ImmutableSingleton, log::emit_unencrypted_log_from_private, types::vec::BoundedVec, constants_gen::EMPTY_NULLIFIED_COMMITMENT, }; use dep::token_portal_content_hash_lib::{get_mint_private_content_hash, get_mint_public_content_hash}; + use dep::field_note::field_note::{FieldNote, FieldNoteMethods, FIELD_NOTE_LEN}; #[event] struct ExampleEvent { value: Field, } + struct Storage { + example_constant: ImmutableSingleton, + } + + impl Storage { + fn init(context: Context) -> pub Self { + Storage { + example_constant: ImmutableSingleton::new(context, 1, FieldNoteMethods), + } + } + } + #[aztec(private)] // docs:start:empty-constructor fn constructor() {} @@ -64,27 +85,27 @@ contract Test { // Note; this function is deliberately NOT annotated with #[aztec(private)] due to its use in tests fn test_code_gen( inputs: PrivateContextInputs, - aField: Field, - aBool: bool, - aNumber: u32, - anArray: [Field; 2], - aStruct: DummyNote, - aDeepStruct: DeepStruct, + a_field: Field, + a_bool: bool, + a_number: u32, + an_array: [Field; 2], + a_struct: DummyNote, + a_deep_struct: DeepStruct, ) -> distinct pub abi::PrivateCircuitPublicInputs { let mut args: BoundedVec = BoundedVec::new(0); - args.push(aField); - args.push(aBool as Field); - args.push(aNumber as Field); - args.push_array(anArray); - args.push(aStruct.amount); - args.push(aStruct.secretHash); - args.push(aDeepStruct.aField); - args.push(aDeepStruct.aBool as Field); - args.push(aDeepStruct.aNote.amount); - args.push(aDeepStruct.aNote.secretHash); - for note in aDeepStruct.manyNotes { + args.push(a_field); + args.push(a_bool as Field); + args.push(a_number as Field); + args.push_array(an_array); + args.push(a_struct.amount); + args.push(a_struct.secret_hash); + args.push(a_deep_struct.a_field); + args.push(a_deep_struct.a_bool as Field); + args.push(a_deep_struct.a_note.amount); + args.push(a_deep_struct.a_note.secret_hash); + for note in a_deep_struct.many_notes { args.push(note.amount); - args.push(note.secretHash); + args.push(note.secret_hash); } let args_hash = abi::hash_args(args.storage); let mut context = PrivateContext::new(inputs, args_hash); @@ -96,10 +117,10 @@ contract Test { #[aztec(public)] fn create_l2_to_l1_message_public( amount: Field, - secretHash: Field, + secret_hash: Field, ) { // Create a commitment to the amount - let note = DummyNote::new(amount, secretHash); + let note = DummyNote::new(amount, secret_hash); // Public oracle call to emit new commitment. context.message_portal(note.get_commitment()); @@ -110,10 +131,10 @@ contract Test { #[aztec(public)] fn create_nullifier_public( amount: Field, - secretHash: Field, + secret_hash: Field, ) { // Create a commitment to the amount - let note = DummyNote::new(amount, secretHash); + let note = DummyNote::new(amount, secret_hash); // Public oracle call to emit new commitment. context.push_new_nullifier(note.get_commitment(), EMPTY_NULLIFIED_COMMITMENT); @@ -177,6 +198,19 @@ contract Test { context.consume_l1_to_l2_message(msg_key, content_hash, secret_for_L1_to_L2_message_consumption); } + #[aztec(private)] + fn set_constant( + value: Field, + ) { + let mut note = FieldNote::new(value); + storage.example_constant.initialize(&mut note, Option::none(), false); + } + + unconstrained fn get_constant() -> Field { + let constant = storage.example_constant.view_note(); + constant.value + } + // Purely exists for testing unconstrained fn get_random( kindaSeed: Field @@ -186,26 +220,35 @@ contract Test { struct DummyNote { amount: Field, - secretHash: Field + secret_hash: Field } impl DummyNote { - fn new(amount: Field, secretHash: Field) -> pub Self { + fn new(amount: Field, secret_hash: Field) -> pub Self { Self { amount: amount, - secretHash: secretHash + secret_hash: secret_hash } } fn get_commitment(self) -> pub Field { - pedersen_hash([self.amount, self.secretHash],0) + pedersen_hash([self.amount, self.secret_hash],0) } } struct DeepStruct { - aField: Field, - aBool: bool, - aNote: DummyNote, - manyNotes: [DummyNote; 3], + a_field: Field, + a_bool: bool, + a_note: DummyNote, + many_notes: [DummyNote; 3], + } + + // Computes note hash and nullifier. + // Note 1: Needs to be defined by every contract producing logs. + // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. + unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, preimage: [Field; FIELD_NOTE_LEN]) -> [Field; 4] { + assert(storage_slot == 1); + let note_header = NoteHeader::new(contract_address, nonce, storage_slot); + note_utils::compute_note_hash_and_nullifier(FieldNoteMethods, note_header, preimage) } } diff --git a/yarn-project/noir-contracts/src/contracts/token_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/token_contract/Nargo.toml index 1da51baf184..860f56f4b40 100644 --- a/yarn-project/noir-contracts/src/contracts/token_contract/Nargo.toml +++ b/yarn-project/noir-contracts/src/contracts/token_contract/Nargo.toml @@ -6,6 +6,5 @@ type = "contract" [dependencies] aztec = { path = "../../../../aztec-nr/aztec" } -value_note = { path = "../../../../aztec-nr/value-note"} safe_math = { path = "../../../../aztec-nr/safe-math" } authwit = { path = "../../../../aztec-nr/authwit" } \ No newline at end of file