diff --git a/noir-projects/aztec-nr/aztec/src/keys.nr b/noir-projects/aztec-nr/aztec/src/keys.nr index 20d48201aca..0a79c4a024a 100644 --- a/noir-projects/aztec-nr/aztec/src/keys.nr +++ b/noir-projects/aztec-nr/aztec/src/keys.nr @@ -1 +1,4 @@ -mod point_to_symmetric_key; \ No newline at end of file +mod getters; +mod point_to_symmetric_key; + +use crate::keys::getters::get_fresh_nullifier_public_key_hash; diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters.nr b/noir-projects/aztec-nr/aztec/src/keys/getters.nr new file mode 100644 index 00000000000..bbc0eb2dda8 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/keys/getters.nr @@ -0,0 +1,79 @@ +use dep::protocol_types::{ + address::{ + AztecAddress, + PartialAddress + }, + constants::{ + GENERATOR_INDEX__PUBLIC_KEYS_HASH, + GENERATOR_INDEX__CONTRACT_ADDRESS_V1, + CANONICAL_KEY_REGISTRY_ADDRESS + }, + grumpkin_point::GrumpkinPoint, +}; + +use crate::context::PrivateContext; +use crate::hash::{ + pedersen_hash, + poseidon2_hash, +}; +use crate::oracle::keys::get_public_keys_and_partial_address; +use crate::state_vars::{ + map::derive_storage_slot_in_map, + shared_mutable::shared_mutable_private_getter::SharedMutablePrivateGetter, +}; + +struct PublicKeyTypeEnum { + NULLIFIER: u8, +} + +global PublicKeyType = PublicKeyTypeEnum { + NULLIFIER: 0, +}; + +pub fn get_fresh_nullifier_public_key_hash( + context: &mut PrivateContext, + address: AztecAddress, +) -> Field { + // This is the storage slot of the nullifier_public_key inside the key registry contract + // TODO: (#6133) We should have this be directly imported from the other contract if possible, or at least this should not be this brittle + let storage_slot_of_nullifier_public_key = 1; + + let derived_slot = derive_storage_slot_in_map(storage_slot_of_nullifier_public_key, address); + + // We read from the canonical Key Registry + // TODO: (#6134) It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly. + // We should allow for this usecase without needing to hard code it here. + let registry_private_getter: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new(*context, AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS), derived_slot); + let nullifier_public_key_hash_in_registry = registry_private_getter.get_current_value_in_private(); + + let nullifier_public_key_hash = if nullifier_public_key_hash_in_registry == 0 { + let keys = get_original_public_keys_internal(address); + poseidon2_hash(keys[PublicKeyType.NULLIFIER].serialize()) + } else { + nullifier_public_key_hash_in_registry + }; + + nullifier_public_key_hash +} + +// This constraint only works on keys that have not been rotated, otherwise this call will fail as the public keys are not constrained +fn get_original_public_keys_internal(address: AztecAddress) -> [GrumpkinPoint; 4] { + let (public_keys, partial_address) = get_public_keys_and_partial_address(address); + + let nullifier_pub_key = public_keys[0]; + let incoming_pub_key = public_keys[1]; + let outgoing_pub_key = public_keys[2]; + let tagging_pub_key = public_keys[3]; + + let computed_address = AztecAddress::compute_from_public_keys_and_partial_address( + nullifier_pub_key, + incoming_pub_key, + outgoing_pub_key, + tagging_pub_key, + partial_address, + ); + + assert(computed_address.eq(address)); + + [nullifier_pub_key, incoming_pub_key, outgoing_pub_key, tagging_pub_key] +} diff --git a/noir-projects/aztec-nr/aztec/src/oracle.nr b/noir-projects/aztec-nr/aztec/src/oracle.nr index 753ef6e930a..97811426bab 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle.nr @@ -11,6 +11,7 @@ mod get_nullifier_membership_witness; mod get_public_data_witness; mod get_membership_witness; mod get_public_key; +mod keys; mod nullifier_key; mod get_sibling_path; mod unsafe_rand; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/keys.nr b/noir-projects/aztec-nr/aztec/src/oracle/keys.nr new file mode 100644 index 00000000000..d8737a0dd06 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/oracle/keys.nr @@ -0,0 +1,28 @@ +use dep::protocol_types::{ + address::{ + AztecAddress, + PartialAddress, + }, + grumpkin_point::GrumpkinPoint, +}; + +use crate::hash::poseidon2_hash; + +#[oracle(getPublicKeysAndPartialAddress)] +fn get_public_keys_and_partial_address_oracle(_address: AztecAddress) -> [Field; 9] {} + +unconstrained fn get_public_keys_and_partial_address_oracle_wrapper(address: AztecAddress) -> [Field; 9] { + get_public_keys_and_partial_address_oracle(address) +} + +fn get_public_keys_and_partial_address(address: AztecAddress) -> ([GrumpkinPoint; 4], PartialAddress) { + let result = get_public_keys_and_partial_address_oracle_wrapper(address); + + let nullifier_pub_key = GrumpkinPoint::new(result[0], result[1]); + let incoming_pub_key = GrumpkinPoint::new(result[2], result[3]); + let outgoing_pub_key = GrumpkinPoint::new(result[4], result[5]); + let tagging_pub_key = GrumpkinPoint::new(result[6], result[7]); + let partial_address = PartialAddress::from_field(result[8]); + + ([nullifier_pub_key, incoming_pub_key, outgoing_pub_key, tagging_pub_key], partial_address) +} \ No newline at end of file diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/map.nr b/noir-projects/aztec-nr/aztec/src/state_vars/map.nr index a138a8deb9c..c075578db3f 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/map.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/map.nr @@ -27,10 +27,14 @@ impl Map { // docs:start:at pub fn at(self, key: K) -> V where K: ToField { // TODO(#1204): use a generator index for the storage slot - let derived_storage_slot = pedersen_hash([self.storage_slot, key.to_field()], 0); + let derived_storage_slot = derive_storage_slot_in_map(self.storage_slot, key); let state_var_constructor = self.state_var_constructor; state_var_constructor(self.context, derived_storage_slot) } // docs:end:at } + +pub fn derive_storage_slot_in_map(storage_slot: Field, key: K) -> Field where K: ToField { + pedersen_hash([storage_slot, key.to_field()], 0) +} diff --git a/noir-projects/noir-contracts/contracts/key_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/key_registry_contract/src/main.nr index 1d07a431e24..2d2111d07dd 100644 --- a/noir-projects/noir-contracts/contracts/key_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/key_registry_contract/src/main.nr @@ -7,6 +7,7 @@ contract KeyRegistry { Map }, protocol_types::{ + grumpkin_point::GrumpkinPoint, address::{ AztecAddress, PublicKeysHash, @@ -24,9 +25,12 @@ contract KeyRegistry { #[aztec(storage)] struct Storage { + //! This should stay at storage slot 1. If you change this, make sure you change the hardcoded value in keys/assert_public_key_freshness. + //! We use this hardcoded storage slot with derive_storage_slot_in_map and the SharedMutablePrivateGetter to directly read the value at an address in this contract. + nullifier_public_key_hash_registry: Map>, + // We are not supporting rotating / changing keys other than the nullifier public in the registry at the moment, but will in the future. // Uncomment lines below to enable that functionality - nullifier_public_key_registry: Map>, // incoming_public_key_registry: Map>, // outgoing_public_key_registry: Map>, // tagging_public_key_registry: Map>, @@ -35,71 +39,63 @@ contract KeyRegistry { #[aztec(public)] fn rotate_nullifier_public_key( address: AztecAddress, - new_nullifier_public_key: Field, + new_nullifier_public_key: GrumpkinPoint, + nonce: Field, ) { assert( - new_nullifier_public_key != 0, + !new_nullifier_public_key.is_zero(), "New nullifier public key must be non-zero" ); + // TODO: (#6137) if (!address.eq(context.msg_sender())) { assert_current_call_valid_authwit_public(&mut context, address); + } else { + assert(nonce == 0, "invalid nonce"); } - let nullifier_key_registry = storage.nullifier_public_key_registry.at(address); + let nullifier_key_registry = storage.nullifier_public_key_hash_registry.at(address); - nullifier_key_registry.schedule_value_change(new_nullifier_public_key); + nullifier_key_registry.schedule_value_change(poseidon2_hash(new_nullifier_public_key.serialize())); } #[aztec(public)] fn register( address: AztecAddress, partial_address: PartialAddress, - nullifier_public_key: Field, - incoming_public_key: Field, - outgoing_public_key: Field, - tagging_public_key: Field, + nullifier_public_key: GrumpkinPoint, + incoming_public_key: GrumpkinPoint, + outgoing_public_key: GrumpkinPoint, + tagging_public_key: GrumpkinPoint, ) { assert( - (nullifier_public_key != 0) & - (incoming_public_key != 0) & - (outgoing_public_key != 0) & - (tagging_public_key != 0), + !partial_address.is_zero() & + !nullifier_public_key.is_zero() & + !incoming_public_key.is_zero() & + !outgoing_public_key.is_zero() & + !tagging_public_key.is_zero(), "All public keys must be non-zero" ); - // TODO (ek): Do it below after refactoring all public_keys_hash_elemtns - // let public_keys_hash = PublicKeysHash::compute(nullifier_public_key, tagging_public_key, incoming_public_key, outgoing_public_key); - // let address = AztecAddress::compute(public_keys_hash, partial_address); // We could also pass in original_public_keys_hash instead of computing it here, if all we need the original one is for being able to prove ownership of address - let public_keys_hash = poseidon2_hash([ - nullifier_public_key, - incoming_public_key, - outgoing_public_key, - tagging_public_key, - GENERATOR_INDEX__PUBLIC_KEYS_HASH, - ] - ); - - let computed_address = AztecAddress::from_field( - poseidon2_hash([ - partial_address.to_field(), - public_keys_hash.to_field(), - GENERATOR_INDEX__CONTRACT_ADDRESS_V1 as Field, - ] - ) + let computed_address = AztecAddress::compute_from_public_keys_and_partial_address( + nullifier_public_key, + incoming_public_key, + outgoing_public_key, + tagging_public_key, + partial_address, ); assert(computed_address.eq(address), "Computed address does not match supplied address"); - let nullifier_key_registry = storage.nullifier_public_key_registry.at(address); + let nullifier_key_hash_registry = storage.nullifier_public_key_hash_registry.at(address); // We are not supporting rotating / changing keys other than the nullifier public in the registry at the moment, but will in the future. // Uncomment lines below to enable that functionality // let incoming_key_registry = storage.incoming_public_key_registry.at(address); // let outgoing_key_registry = storage.outgoing_public_key_registry.at(address); // let tagging_key_registry = storage.taggin_public_key_registry.at(address); - nullifier_key_registry.schedule_value_change(nullifier_public_key); + nullifier_key_hash_registry.schedule_value_change(poseidon2_hash(nullifier_public_key.serialize())); // We are not supporting rotating / changing keys other than the nullifier public in the registry at the moment, but will in the future. // Uncomment lines below to enable that functionality // incoming_key_registry.schedule_value_change(new_incoming_public_key); // outgoing_key_registry.schedule_value_change(new_outgoing_public_key); 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 6d0f3fca2e0..74f0e049512 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -10,16 +10,18 @@ contract Test { use dep::aztec::protocol_types::{ abis::private_circuit_public_inputs::PrivateCircuitPublicInputs, - constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::Serialize + constants::{MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, CANONICAL_KEY_REGISTRY_ADDRESS}, traits::{Serialize, ToField, FromField}, + grumpkin_point::GrumpkinPoint }; use dep::aztec::note::constants::MAX_NOTES_PER_PAGE; - use dep::aztec::state_vars::shared_mutable::SharedMutablePrivateGetter; + use dep::aztec::state_vars::{shared_mutable::SharedMutablePrivateGetter, map::derive_storage_slot_in_map}; use dep::aztec::{ + keys::getters::get_fresh_nullifier_public_key_hash, context::{Context, inputs::private_context_inputs::PrivateContextInputs}, - hash::{pedersen_hash, compute_secret_hash, ArgsHasher}, + hash::{pedersen_hash, poseidon2_hash, compute_secret_hash, ArgsHasher}, note::{ lifecycle::{create_note, destroy_note}, note_getter::{get_notes, view_notes}, note_getter_options::NoteStatus @@ -375,38 +377,47 @@ contract Test { constant.value } + // This function is used in the e2e_state_vars to test the SharedMutablePrivateGetter in isolation #[aztec(private)] - fn test_shared_mutable_private_getter_for_registry_contract( + fn test_shared_mutable_private_getter( contract_address_to_read: AztecAddress, + storage_slot_of_shared_mutable: Field + ) -> Field where T: FromField, T: ToField { + // It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly + let test: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new( + context, + contract_address_to_read, + storage_slot_of_shared_mutable + ); + + let ret = test.get_current_value_in_private(); + + ret.to_field() + } + + // This function is used for testing the registry contract and fresh public key getters. If nothing exists in the registry, but we have added public + // keys to the pxe, this function will return nothing, but the public key getters will return the correct value + #[aztec(private)] + fn test_shared_mutable_private_getter_for_registry_contract( storage_slot_of_shared_mutable: Field, address_to_get_in_registry: AztecAddress - ) { + ) -> Field { // We have to derive this slot to get the location of the shared mutable inside the Map - let derived_slot = dep::aztec::hash::pedersen_hash( - [storage_slot_of_shared_mutable, address_to_get_in_registry.to_field()], - 0 - ); + let derived_slot = derive_storage_slot_in_map(storage_slot_of_shared_mutable, address_to_get_in_registry); + // It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly - let registry_private_getter: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new(context, contract_address_to_read, derived_slot); + let registry_private_getter: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new(context, AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS), derived_slot); let nullifier_public_key = registry_private_getter.get_current_value_in_private(); - context.emit_unencrypted_log(nullifier_public_key); + nullifier_public_key } #[aztec(private)] - fn test_shared_mutable_private_getter( - contract_address_to_read: AztecAddress, - storage_slot_of_shared_mutable: Field + fn test_nullifier_key_freshness( + address: AztecAddress, + public_nullifying_key: GrumpkinPoint, ) { - // It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly - let test: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new( - context, - contract_address_to_read, - storage_slot_of_shared_mutable - ); - let authorized = test.get_current_value_in_private(); - - context.emit_unencrypted_log(authorized); + assert_eq(get_fresh_nullifier_public_key_hash(&mut context, address), poseidon2_hash(public_nullifying_key.serialize())); } #[aztec(public)] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr index fcbae4871d9..06463ce268c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr @@ -61,6 +61,25 @@ impl AztecAddress { ) } + pub fn compute_from_public_keys_and_partial_address( + nullifier_public_key: GrumpkinPoint, + incoming_public_key: GrumpkinPoint, + outgoing_public_key: GrumpkinPoint, + tagging_public_key: GrumpkinPoint, + partial_address: PartialAddress, + ) -> AztecAddress { + let public_keys_hash = PublicKeysHash::compute_new( + nullifier_public_key, + incoming_public_key, + outgoing_public_key, + tagging_public_key, + ); + + let computed_address = AztecAddress::compute(public_keys_hash, partial_address); + + computed_address + } + pub fn is_zero(self) -> bool { self.inner == 0 } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/partial_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/partial_address.nr index 3829d951a8d..3a802d9f9eb 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/partial_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/partial_address.nr @@ -68,6 +68,10 @@ impl PartialAddress { self.inner } + pub fn is_zero(self) -> bool { + self.to_field() == 0 + } + pub fn assert_is_zero(self) { assert(self.to_field() == 0); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/public_keys_hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/public_keys_hash.nr index dfe3023abb2..bff82cc1644 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/public_keys_hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/public_keys_hash.nr @@ -1,6 +1,7 @@ use crate::{ - constants::GENERATOR_INDEX__PARTIAL_ADDRESS, hash::pedersen_hash, grumpkin_point::GrumpkinPoint, - traits::{ToField, Serialize, Deserialize} + constants::{GENERATOR_INDEX__PARTIAL_ADDRESS, GENERATOR_INDEX__PUBLIC_KEYS_HASH}, hash::pedersen_hash, grumpkin_point::GrumpkinPoint, + traits::{ToField, Serialize, Deserialize}, + hash::poseidon2_hash, }; // Public keys hash. Used in the computation of an address. @@ -37,7 +38,7 @@ impl PublicKeysHash { Self { inner: field } } - // TODO(#5830): update this + // TODO(#5830): When we do this refactor, rename compute_new -> compute pub fn compute(public_key: GrumpkinPoint) -> Self { PublicKeysHash::from_field( pedersen_hash( @@ -50,6 +51,28 @@ impl PublicKeysHash { ) } + // TODO(#5830): When we do this refactor, rename compute_new -> compute + pub fn compute_new( + nullifier_public_key: GrumpkinPoint, + incoming_public_key: GrumpkinPoint, + outgoing_public_key: GrumpkinPoint, + tagging_public_key: GrumpkinPoint + ) -> Self { + PublicKeysHash::from_field( + poseidon2_hash([ + nullifier_public_key.x, + nullifier_public_key.y, + incoming_public_key.x, + incoming_public_key.y, + outgoing_public_key.x, + outgoing_public_key.y, + tagging_public_key.x, + tagging_public_key.y, + GENERATOR_INDEX__PUBLIC_KEYS_HASH, + ]) + ) + } + pub fn to_field(self) -> Field { self.inner } diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 87081769ed8..0e95d024727 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -1,4 +1,4 @@ -import { type AztecAddress, type CompleteAddress, type Fr, type PartialAddress } from '@aztec/circuits.js'; +import { type AztecAddress, type CompleteAddress, type Fr, type PartialAddress, type Point } from '@aztec/circuits.js'; import { type ContractArtifact } from '@aztec/foundation/abi'; import { type ContractClassWithId, type ContractInstanceWithAddress } from '@aztec/types/contracts'; import { type NodeInfo } from '@aztec/types/interfaces'; @@ -73,7 +73,8 @@ export interface PXE { * the recipient's notes. We can send notes to this account because we can encrypt them with the recipient's * public key. */ - registerRecipient(recipient: CompleteAddress): Promise; + // TODO: #5834: Nuke publicKeys optional parameter after `CompleteAddress` refactor. + registerRecipient(recipient: CompleteAddress, publicKeys?: Point[]): Promise; /** * Retrieves the user accounts registered on this PXE Service. diff --git a/yarn-project/circuit-types/src/keys/key_store.ts b/yarn-project/circuit-types/src/keys/key_store.ts index 55c69f0facb..168ec8d5f04 100644 --- a/yarn-project/circuit-types/src/keys/key_store.ts +++ b/yarn-project/circuit-types/src/keys/key_store.ts @@ -3,6 +3,7 @@ import { type Fr, type GrumpkinPrivateKey, type PartialAddress, + type Point, type PublicKey, } from '@aztec/circuits.js'; @@ -116,4 +117,21 @@ export interface KeyStore { * @returns A Promise that resolves to the public keys hash. */ getPublicKeysHash(account: AztecAddress): Promise; + + /** + * This is used to register a recipient / for storing public keys of an address + * @param accountAddress - The account address to store keys for. + * @param masterNullifierPublicKey - The stored master nullifier public key + * @param masterIncomingViewingPublicKey - The stored incoming viewing public key + * @param masterOutgoingViewingPublicKey - The stored outgoing viewing public key + * @param masterTaggingPublicKey - The stored master tagging public key + */ + // TODO(#5834): Move this function out of here. Key store should only be used for accounts, not recipients + addPublicKeysForAccount( + accountAddress: AztecAddress, + masterNullifierPublicKey: Point, + masterIncomingViewingPublicKey: Point, + masterOutgoingViewingPublicKey: Point, + masterTaggingPublicKey: Point, + ): Promise; } diff --git a/yarn-project/end-to-end/src/e2e_key_registry.test.ts b/yarn-project/end-to-end/src/e2e_key_registry.test.ts index 97157db0657..b801beed9e4 100644 --- a/yarn-project/end-to-end/src/e2e_key_registry.test.ts +++ b/yarn-project/end-to-end/src/e2e_key_registry.test.ts @@ -1,5 +1,5 @@ import { type AccountWallet, AztecAddress, Fr, type PXE } from '@aztec/aztec.js'; -import { GeneratorIndex } from '@aztec/circuits.js'; +import { CompleteAddress, GeneratorIndex, type PartialAddress, Point } from '@aztec/circuits.js'; import { poseidon2Hash } from '@aztec/foundation/crypto'; import { KeyRegistryContract, TestContract } from '@aztec/noir-contracts.js'; import { getCanonicalKeyRegistryAddress } from '@aztec/protocol-contracts/key-registry'; @@ -10,11 +10,11 @@ import { publicDeployAccounts, setup } from './fixtures/utils.js'; const TIMEOUT = 100_000; -describe('SharedMutablePrivateGetter', () => { +describe('Key Registry', () => { let keyRegistry: KeyRegistryContract; - let testContract: TestContract; let pxe: PXE; + let testContract: TestContract; jest.setTimeout(TIMEOUT); let wallets: AccountWallet[]; @@ -22,7 +22,7 @@ describe('SharedMutablePrivateGetter', () => { let teardown: () => Promise; beforeAll(async () => { - ({ teardown, pxe, wallets } = await setup(2)); + ({ teardown, pxe, wallets } = await setup(3)); keyRegistry = await KeyRegistryContract.at(getCanonicalKeyRegistryAddress(), wallets[0]); testContract = await TestContract.deploy(wallets[0]).send().deployed(); @@ -41,13 +41,12 @@ describe('SharedMutablePrivateGetter', () => { describe('failure cases', () => { let accountAddedToRegistry: AztecAddress; - describe('should fail registering with bad input', () => { - const partialAddress = new Fr(69); - - const masterNullifierPublicKey = new Fr(12); - const masterIncomingViewingPublicKey = new Fr(34); - const masterOutgoingViewingPublicKey = new Fr(56); - const masterTaggingPublicKey = new Fr(78); + describe('should fail when registering with different types of invalid input', () => { + const masterNullifierPublicKey = Point.random(); + const masterIncomingViewingPublicKey = Point.random(); + const masterOutgoingViewingPublicKey = Point.random(); + const masterTaggingPublicKey = Point.random(); + const partialAddress: PartialAddress = Fr.random(); // TODO(#5726): use computePublicKeysHash function const publicKeysHash = poseidon2Hash([ @@ -58,18 +57,19 @@ describe('SharedMutablePrivateGetter', () => { GeneratorIndex.PUBLIC_KEYS_HASH, ]); - // We hash the partial address and the public keys hash to get the account address // TODO(#5726): Move the following line to AztecAddress class? - accountAddedToRegistry = poseidon2Hash([partialAddress, publicKeysHash, GeneratorIndex.CONTRACT_ADDRESS_V1]); + accountAddedToRegistry = AztecAddress.fromField( + poseidon2Hash([publicKeysHash, partialAddress, GeneratorIndex.CONTRACT_ADDRESS_V1]), + ); - it('should fail registering with mismatched address', async () => { - const mismatchedAddress = Fr.random(); + it('should fail when we register with a mismatched address', async () => { + const mismatchedAddress = AztecAddress.random(); await expect( keyRegistry .withWallet(wallets[0]) .methods.register( - AztecAddress.fromField(mismatchedAddress), + mismatchedAddress, partialAddress, masterNullifierPublicKey, masterIncomingViewingPublicKey, @@ -81,8 +81,8 @@ describe('SharedMutablePrivateGetter', () => { ).rejects.toThrow('Computed address does not match supplied address'); }); - it('should fail registering with mismatched nullifier public key', async () => { - const mismatchedMasterNullifierPublicKey = Fr.random(); + it('should fail when we register with mismatched nullifier public key', async () => { + const mismatchedMasterNullifierPublicKey = Point.random(); await expect( keyRegistry @@ -101,22 +101,22 @@ describe('SharedMutablePrivateGetter', () => { }); }); - describe('should fail when rotating keys with bad input', () => { - it('should fail when trying to rotate setting a 0 key', async () => { + describe('should fail when rotating keys with different types of bad input', () => { + it('should fail when we try to rotate keys, while setting a 0 key', async () => { await expect( keyRegistry .withWallet(wallets[0]) - .methods.rotate_nullifier_public_key(wallets[0].getAddress(), new Fr(0)) + .methods.rotate_nullifier_public_key(wallets[0].getAddress(), Point.ZERO, Fr.ZERO) .send() .wait(), ).rejects.toThrow('New nullifier public key must be non-zero'); }); - it('should fail when trying to rotate for another address without authwit', async () => { + it('should fail when we try to rotate keys for another address without authwit', async () => { await expect( keyRegistry .withWallet(wallets[0]) - .methods.rotate_nullifier_public_key(wallets[1].getAddress(), new Fr(2)) + .methods.rotate_nullifier_public_key(wallets[1].getAddress(), Point.random(), Fr.ZERO) .send() .wait(), ).rejects.toThrow('Assertion failed: Message not authorized by account'); @@ -126,14 +126,13 @@ describe('SharedMutablePrivateGetter', () => { describe('key registration flow', () => { let accountAddedToRegistry: AztecAddress; + const masterNullifierPublicKey = Point.random(); - it('should generate and register with original keys', async () => { - const partialAddress = new Fr(69); - - const masterNullifierPublicKey = new Fr(12); - const masterIncomingViewingPublicKey = new Fr(34); - const masterOutgoingViewingPublicKey = new Fr(56); - const masterTaggingPublicKey = new Fr(78); + it('should generate master public keys, a partial address, and register with the key registry', async () => { + const masterIncomingViewingPublicKey = Point.random(); + const masterOutgoingViewingPublicKey = Point.random(); + const masterTaggingPublicKey = Point.random(); + const partialAddress: PartialAddress = new Fr(420); const publicKeysHash = poseidon2Hash([ masterNullifierPublicKey, @@ -143,9 +142,10 @@ describe('SharedMutablePrivateGetter', () => { GeneratorIndex.PUBLIC_KEYS_HASH, ]); - // We hash the partial address and the public keys hash to get the account address // TODO(#5726): Move the following line to AztecAddress class? - accountAddedToRegistry = poseidon2Hash([partialAddress, publicKeysHash, GeneratorIndex.CONTRACT_ADDRESS_V1]); + accountAddedToRegistry = AztecAddress.fromField( + poseidon2Hash([publicKeysHash, partialAddress, GeneratorIndex.CONTRACT_ADDRESS_V1]), + ); await keyRegistry .withWallet(wallets[0]) @@ -159,106 +159,291 @@ describe('SharedMutablePrivateGetter', () => { ) .send() .wait(); + + // We check if our registered nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this to fail because the change has not been applied yet + const emptyNullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, accountAddedToRegistry) + .simulate(); + + expect(new Fr(emptyNullifierPublicKey)).toEqual(Fr.ZERO); + + // We check it again after a delay and expect that the change has been applied and consequently the assert is true + await delay(5); + + const nullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, accountAddedToRegistry) + .simulate(); + + expect(new Fr(nullifierPublicKey)).toEqual(poseidon2Hash(masterNullifierPublicKey.toFields())); }); + }); - it('checks our registry contract from test contract and fails because the address has not been registered yet', async () => { - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, accountAddedToRegistry) - .send() - .wait(); + describe('key rotation flows', () => { + const firstNewMasterNullifierPublicKey = Point.random(); + + describe('key rotation flow without authwit', () => { + it('we call the key registry to rotate our nullifier key', async () => { + await keyRegistry + .withWallet(wallets[0]) + .methods.rotate_nullifier_public_key(wallets[0].getAddress(), firstNewMasterNullifierPublicKey, Fr.ZERO) + .send() + .wait(); + + // We check if our rotated nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this to fail because the change has not been applied yet + const emptyNullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, wallets[0].getAddress()) + .simulate(); + + expect(new Fr(emptyNullifierPublicKey)).toEqual(Fr.ZERO); + + // We check it again after a delay and expect that the change has been applied and consequently the assert is true + await delay(5); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(Fr.ZERO); + const nullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, wallets[0].getAddress()) + .simulate(); + + expect(new Fr(nullifierPublicKey)).toEqual(poseidon2Hash(firstNewMasterNullifierPublicKey.toFields())); + }); }); - it('checks our registry contract from test contract and finds the address and associated nullifier public key after a delay', async () => { - await delay(5); + describe('key rotation flow with authwit', () => { + const secondNewMasterNullifierPublicKey = Point.random(); - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, accountAddedToRegistry) - .send() - .wait(); + it(`wallet 1 rotates wallet 0's nullifying public key with an authwit`, async () => { + const action = keyRegistry + .withWallet(wallets[1]) + .methods.rotate_nullifier_public_key(wallets[0].getAddress(), secondNewMasterNullifierPublicKey, Fr.ZERO); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); + await wallets[0] + .setPublicAuthWit({ caller: wallets[1].getCompleteAddress().address, action }, true) + .send() + .wait(); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(12)); + await action.send().wait(); + + // We check if our rotated nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this value to be the old one, because the new one hasn't been applied + const oldNullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, wallets[0].getAddress()) + .simulate(); + + expect(new Fr(oldNullifierPublicKey)).toEqual(poseidon2Hash(firstNewMasterNullifierPublicKey.toFields())); + + // We check it again after a delay and expect that the change has been applied and consequently the assert is true + await delay(5); + + const newNullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, wallets[0].getAddress()) + .simulate(); + + expect(new Fr(newNullifierPublicKey)).toEqual(poseidon2Hash(secondNewMasterNullifierPublicKey.toFields())); + }); }); }); - describe('key rotation flow', () => { - it('we rotate the nullifier key', async () => { - // This changes - const newMasterNullifierPublicKey = new Fr(910); + describe('testing get_fresh_nullifier_public_key_hash: key registration flow, no PXE', () => { + const masterNullifierPublicKey = Point.random(); + const masterIncomingViewingPublicKey = Point.random(); + const masterOutgoingViewingPublicKey = Point.random(); + const masterTaggingPublicKey = Point.random(); + const partialAddress: PartialAddress = new Fr(420); + + const publicKeysHash = poseidon2Hash([ + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + GeneratorIndex.PUBLIC_KEYS_HASH, + ]); + + // TODO(#5726): Move the following line to AztecAddress class? + const accountAddedToRegistry = AztecAddress.fromField( + poseidon2Hash([publicKeysHash, partialAddress, GeneratorIndex.CONTRACT_ADDRESS_V1]), + ); + + it('should fail as we have not registered anything to the registry nor have we registered a recipient', async () => { + await expect( + testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) + .send() + .wait(), + ).rejects.toThrow(`Cannot satisfy constraint 'computed_address.eq(address)'`); + }); + it('adds an entry to the key registry, and checks the key freshness without and with conflicting information from our pxe', async () => { await keyRegistry .withWallet(wallets[0]) - .methods.rotate_nullifier_public_key(wallets[0].getAddress(), newMasterNullifierPublicKey) - .send() - .wait(); - }); - - it("checks our registry contract from test contract and finds our old public key because the key rotation hasn't been applied yet", async () => { - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, wallets[0].getAddress()) + .methods.register( + AztecAddress.fromField(accountAddedToRegistry), + partialAddress, + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + ) .send() .wait(); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(0)); - }); + // We check if our registered nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this to fail because the change has not been applied yet + await expect( + testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) + .send() + .wait(), + ).rejects.toThrow(`Cannot satisfy constraint 'computed_address.eq(address)'`); - it('checks our registry contract from test contract and finds the new nullifier public key that has been rotated', async () => { + // We check it again after a delay and expect that the change has been applied and consequently the assert is true await delay(5); - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, wallets[0].getAddress()) + await testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) .send() .wait(); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); + // TODO: (#5834) Refactor complete address to move the public keys + await pxe.registerRecipient(CompleteAddress.create(accountAddedToRegistry, Point.ZERO, partialAddress), [ + new Point(Fr.random(), Fr.random()), + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + ]); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(910)); + // Our check should still succeed even if our pxe gives conflicting information, taking the registry as the source of truth. + await testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) + .send() + .wait(); }); }); - describe('key rotation flow with authwit', () => { - it('wallet 0 lets wallet 1 call rotate_nullifier_public_key on his behalf with a pre-defined new public key', async () => { - // This changes - const newMasterNullifierPublicKey = new Fr(420); + describe('testing assert_nullifier_key_is_fresh: key registration flow, with PXE', () => { + const masterNullifierPublicKey = Point.random(); + const masterIncomingViewingPublicKey = Point.random(); + const masterOutgoingViewingPublicKey = Point.random(); + const masterTaggingPublicKey = Point.random(); + const partialAddress: PartialAddress = new Fr(69420); + + const publicKeysHash = poseidon2Hash([ + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + GeneratorIndex.PUBLIC_KEYS_HASH, + ]); + + // TODO(#5726): Move the following line to AztecAddress class? + const accountAddedToRegistry = AztecAddress.fromField( + poseidon2Hash([publicKeysHash, partialAddress, GeneratorIndex.CONTRACT_ADDRESS_V1]), + ); + + it('should fail as we have not registered anything to the registry nor have we registered a recipient', async () => { + await expect( + testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) + .send() + .wait(), + ).rejects.toThrow(`Cannot satisfy constraint 'computed_address.eq(address)'`); + }); + + it('should fail when we try to check the public keys for a invalid address', async () => { + const randAddress = AztecAddress.random(); + // TODO: (#5834) Refactor complete address to move the public keys + await pxe.registerRecipient(CompleteAddress.create(randAddress, Point.ZERO, partialAddress), [ + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + ]); + + await expect( + testContract.methods.test_nullifier_key_freshness(randAddress, masterNullifierPublicKey).send().wait(), + ).rejects.toThrow(`Cannot satisfy constraint 'computed_address.eq(address)'`); + }); - const action = keyRegistry - .withWallet(wallets[1]) - .methods.rotate_nullifier_public_key(wallets[0].getAddress(), newMasterNullifierPublicKey); + it('adds a recipient to our pxe, and checks the key freshness with and without adding an entry to our key registry', async () => { + // TODO: (#5834) Refactor complete address to move the public keys + await pxe.registerRecipient(CompleteAddress.create(accountAddedToRegistry, Point.ZERO, partialAddress), [ + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + ]); - await wallets[0] - .setPublicAuthWit({ caller: wallets[1].getCompleteAddress().address, action }, true) + // The check should succeed because we register our recipient manually and the lib checks our pxe + await testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) .send() .wait(); - await action.send().wait(); - }); + // Now we add the keys to registry + await keyRegistry + .withWallet(wallets[0]) + .methods.register( + AztecAddress.fromField(accountAddedToRegistry), + partialAddress, + masterNullifierPublicKey, + masterIncomingViewingPublicKey, + masterOutgoingViewingPublicKey, + masterTaggingPublicKey, + ) + .send() + .wait(); + + // We check if our rotated nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this to be 0 because the change has not been applied yet + const emptyNullifierPublicKey = await testContract.methods + .test_shared_mutable_private_getter_for_registry_contract(1, accountAddedToRegistry) + .simulate(); - it("checks our registry contract from test contract and finds our old public key because the key rotation hasn't been applied yet", async () => { - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, wallets[0].getAddress()) + expect(new Fr(emptyNullifierPublicKey)).toEqual(Fr.ZERO); + + // We check if our rotated nullifier key is equal to the key obtained from the getter. We expect this to succeed because even though the change + // has not been applied yet to the registry, we have manually the keys to our pxe + await testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) .send() .wait(); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(910)); + // In the case where the key exists both in the pxe and our registry, we know that our assert will still remain true + await testContract.methods + .test_nullifier_key_freshness(accountAddedToRegistry, masterNullifierPublicKey) + .send() + .wait(); }); + }); - it('checks our registry contract from test contract and finds the new nullifier public key that has been rotated', async () => { - await delay(5); + describe('testing assert_nullifier_key_is_fresh: key rotation flow', () => { + const newMasterNullifierPublicKey = Point.random(); - const { txHash } = await testContract.methods - .test_shared_mutable_private_getter_for_registry_contract(keyRegistry.address, 1, wallets[0].getAddress()) + it('we rotate the nullifier key and check that the key is fresh', async () => { + await keyRegistry + .withWallet(wallets[0]) + .methods.rotate_nullifier_public_key(wallets[0].getAddress(), newMasterNullifierPublicKey, Fr.ZERO) .send() .wait(); - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); + // We check if our rotated nullifier key is equal to the key obtained from the getter by + // reading our registry contract from the test contract. We expect this to fail because the change has not been applied yet + await expect( + testContract.methods + .test_nullifier_key_freshness(wallets[0].getAddress(), newMasterNullifierPublicKey) + .send() + .wait(), + ).rejects.toThrow( + `Cannot satisfy constraint 'assert_eq(get_fresh_nullifier_public_key_hash(&mut context, address), poseidon2_hash(public_nullifying_key.serialize()))'`, + ); + + // We check it again after a delay and expect that the change has been applied and consequently the assert is true + await delay(5); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(420)); + await testContract.methods + .test_nullifier_key_freshness(wallets[0].getAddress(), newMasterNullifierPublicKey) + .send() + .wait(); }); }); }); diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index ae6a7281aee..dcdbdfd9454 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -1,4 +1,4 @@ -import { AztecAddress, Fr, type PXE, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress, Fr, type Wallet } from '@aztec/aztec.js'; import { AuthContract, DocsExampleContract, TestContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -8,7 +8,6 @@ import { setup } from './fixtures/utils.js'; const TIMEOUT = 100_000; describe('e2e_state_vars', () => { - let pxe: PXE; jest.setTimeout(TIMEOUT); let wallet: Wallet; @@ -20,7 +19,7 @@ describe('e2e_state_vars', () => { const RANDOMNESS = 2n; beforeAll(async () => { - ({ teardown, wallet, pxe } = await setup(2)); + ({ teardown, wallet } = await setup(2)); contract = await DocsExampleContract.deploy(wallet).send().deployed(); }); @@ -249,27 +248,21 @@ describe('e2e_state_vars', () => { }); it("checks authorized in auth contract from test contract and finds the old value because the change hasn't been applied yet", async () => { - const { txHash } = await testContract.methods + const authorized = await testContract.methods .test_shared_mutable_private_getter(authContract.address, 2) - .send() - .wait(); + .simulate(); - // The function above emits an unencrypted log as a means of returning the data - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(0)); + expect(AztecAddress.fromBigInt(authorized)).toEqual(AztecAddress.ZERO); }); it('checks authorized in auth contract from test contract and finds the correctly set value', async () => { await delay(5); - const { txHash } = await testContract.methods + const authorized = await testContract.methods .test_shared_mutable_private_getter(authContract.address, 2) - .send() - .wait(); + .simulate(); - // The function above emits an unencrypted log as a means of returning the data - const rawLogs = await pxe.getUnencryptedLogs({ txHash }); - expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(6969696969)); + expect(AztecAddress.fromBigInt(authorized)).toEqual(AztecAddress.fromBigInt(6969696969n)); }); }); }); diff --git a/yarn-project/key-store/src/test_key_store.ts b/yarn-project/key-store/src/test_key_store.ts index c2755aecf0e..a3c0d4b239b 100644 --- a/yarn-project/key-store/src/test_key_store.ts +++ b/yarn-project/key-store/src/test_key_store.ts @@ -292,4 +292,18 @@ export class TestKeyStore implements KeyStore { } return Promise.resolve(Fr.fromBuffer(publicKeysHashBuffer)); } + + // TODO(#5834): Re-add separation between recipients and accounts in keystore. + public async addPublicKeysForAccount( + accountAddress: AztecAddress, + masterNullifierPublicKey: Point, + masterIncomingViewingPublicKey: Point, + masterOutgoingViewingPublicKey: Point, + masterTaggingPublicKey: Point, + ): Promise { + await this.#keys.set(`${accountAddress.toString()}-npk_m`, masterNullifierPublicKey.toBuffer()); + await this.#keys.set(`${accountAddress.toString()}-ivpk_m`, masterIncomingViewingPublicKey.toBuffer()); + await this.#keys.set(`${accountAddress.toString()}-ovpk_m`, masterOutgoingViewingPublicKey.toBuffer()); + await this.#keys.set(`${accountAddress.toString()}-tpk_m`, masterTaggingPublicKey.toBuffer()); + } } diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index e87a447f81b..c1747a2d6c4 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -42,7 +42,7 @@ import { import { computeCommitmentNonce, siloNullifier } from '@aztec/circuits.js/hash'; import { type ContractArtifact, type DecodedReturn, FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; import { arrayNonEmptyLength, padArrayEnd } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/fields'; +import { Fr, type Point } from '@aztec/foundation/fields'; import { SerialQueue } from '@aztec/foundation/fifo'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; @@ -216,8 +216,20 @@ export class PXEService implements PXE { return this.keyStore.getPublicKeysHash(address); } - public async registerRecipient(recipient: CompleteAddress): Promise { + public async registerRecipient(recipient: CompleteAddress, publicKeys: Point[] = []): Promise { const wasAdded = await this.db.addCompleteAddress(recipient); + + // TODO #5834: This should be refactored to be okay with only adding complete address + if (publicKeys.length !== 0) { + await this.keyStore.addPublicKeysForAccount( + recipient.address, + publicKeys[0], + publicKeys[1], + publicKeys[2], + publicKeys[3], + ); + } + if (wasAdded) { this.log.info(`Added recipient:\n ${recipient.toReadableString()}`); } else { diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 827f37111f6..0a81cc5b1d6 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -15,6 +15,7 @@ import { type FunctionSelector, type Header, type L1_TO_L2_MSG_TREE_HEIGHT, + type Point, } from '@aztec/circuits.js'; import { computeL1ToL2MessageNullifier } from '@aztec/circuits.js/hash'; import { type FunctionArtifactWithDebugMetadata, getFunctionArtifactWithDebugMetadata } from '@aztec/foundation/abi'; @@ -43,6 +44,7 @@ export class SimulatorOracle implements DBOracle { return { masterNullifierPublicKey, appNullifierSecretKey }; } + // TODO: #5834 async getCompleteAddress(address: AztecAddress): Promise { const completeAddress = await this.db.getCompleteAddress(address); if (!completeAddress) { @@ -77,6 +79,16 @@ export class SimulatorOracle implements DBOracle { return capsule; } + // TODO: #5834 + async getPublicKeysForAddress(address: AztecAddress): Promise { + const nullifierPublicKey = await this.keyStore.getMasterNullifierPublicKey(address); + const incomingViewingPublicKey = await this.keyStore.getMasterIncomingViewingPublicKey(address); + const outgoingViewingPublicKey = await this.keyStore.getMasterOutgoingViewingPublicKey(address); + const taggingPublicKey = await this.keyStore.getMasterTaggingPublicKey(address); + + return [nullifierPublicKey, incomingViewingPublicKey, outgoingViewingPublicKey, taggingPublicKey]; + } + async getNotes(contractAddress: AztecAddress, storageSlot: Fr, status: NoteStatus) { const noteDaos = await this.db.getNotes({ contractAddress, diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index a5787a406d5..f46990d5ca9 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -1,5 +1,5 @@ import { MerkleTreeId, UnencryptedL2Log } from '@aztec/circuit-types'; -import { acvmFieldMessageToString, oracleDebugCallToFormattedStr } from '@aztec/circuits.js'; +import { type PartialAddress, acvmFieldMessageToString, oracleDebugCallToFormattedStr } from '@aztec/circuits.js'; import { EventSelector, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; @@ -53,6 +53,7 @@ export class Oracle { ]; } + // TODO: #5834 Nuke this async getPublicKeyAndPartialAddress([address]: ACVMField[]) { const { publicKey, partialAddress } = await this.typedOracle.getCompleteAddress( AztecAddress.fromField(fromACVMField(address)), @@ -171,6 +172,28 @@ export class Oracle { return capsule.map(toACVMField); } + async getPublicKeysAndPartialAddress([address]: ACVMField[]): Promise { + let publicKeys: Point[] | undefined; + let partialAddress: PartialAddress; + + // TODO #5834: This should be reworked to return the public keys as well + try { + ({ partialAddress } = await this.typedOracle.getCompleteAddress(AztecAddress.fromField(fromACVMField(address)))); + } catch (err) { + partialAddress = Fr.ZERO; + } + + try { + publicKeys = await this.typedOracle.getPublicKeysForAddress(AztecAddress.fromField(fromACVMField(address))); + } catch (err) { + publicKeys = Array(4).fill(Point.ZERO); + } + + const acvmPublicKeys = publicKeys.flatMap(key => key.toFields()); + + return [...acvmPublicKeys, partialAddress].map(toACVMField); + } + async getNotes( [storageSlot]: ACVMField[], [numSelects]: ACVMField[], diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 360607ee5f6..af2be1de22c 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -17,7 +17,7 @@ import { } from '@aztec/circuits.js'; import { type FunctionSelector } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr } from '@aztec/foundation/fields'; +import { Fr, type Point } from '@aztec/foundation/fields'; import { type ContractInstance } from '@aztec/types/contracts'; /** Nullifier keys which both correspond to the same master nullifier secret key. */ @@ -140,6 +140,10 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('popCapsule'); } + getPublicKeysForAddress(_address: AztecAddress): Promise { + throw new OracleMethodNotAvailableError('getPublicKeysForAddress'); + } + getNotes( _storageSlot: Fr, _numSelects: number, diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index d03e152bde9..eb545df6e11 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -8,7 +8,7 @@ import { import { type CompleteAddress, type Header } from '@aztec/circuits.js'; import { type FunctionArtifactWithDebugMetadata, type FunctionSelector } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { type Fr } from '@aztec/foundation/fields'; +import { type Fr, type Point } from '@aztec/foundation/fields'; import { type ContractInstance } from '@aztec/types/contracts'; import { type NoteData, type NullifierKeys } from '../acvm/index.js'; @@ -64,6 +64,14 @@ export interface DBOracle extends CommitmentsDB { */ popCapsule(): Promise; + /** + * Gets public keys for an address. + * @param The address to look up + * @returns The public keys for a specific address + * TODO(#5834): Replace with `getCompleteAddress`. + */ + getPublicKeysForAddress(address: AztecAddress): Promise; + /** * Retrieve nullifier keys associated with a specific account and app/contract address. * diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index b4c02039175..50dc2552c25 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -166,6 +166,16 @@ export class ViewDataOracle extends TypedOracle { return this.db.popCapsule(); } + /** + * Gets public keys for an address. + * @param The address to look up + * @returns The public keys for a specific address + * TODO(#5834): Replace with `getCompleteAddress`. + */ + public override getPublicKeysForAddress(address: AztecAddress) { + return this.db.getPublicKeysForAddress(address); + } + /** * Gets some notes for a contract address and storage slot. * Returns a flattened array containing filtered notes.