diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index a34e62a1e8c..e4c94df16e5 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -24,10 +24,16 @@ pub fn compute_encrypted_log( let header = EncryptedLogHeader::new(contract_address); - let incoming_header_ciphertext: [u8; 48] = header.compute_ciphertext(eph_sk, ivpk); + let incoming_header_ciphertext: [u8; 48] = header.compute_ciphertext(eph_sk, recipient); let outgoing_header_ciphertext: [u8; 48] = header.compute_ciphertext(eph_sk, ovpk); - let incoming_body_ciphertext = compute_incoming_body_ciphertext(plaintext, eph_sk, ivpk); - let outgoing_body_ciphertext: [u8; 144] = compute_outgoing_body_ciphertext(recipient, ivpk, fr_to_fq(ovsk_app), eph_sk, eph_pk); + let incoming_body_ciphertext = compute_incoming_body_ciphertext(plaintext, eph_sk, IvpkM { inner: recipient.to_point() }); + let outgoing_body_ciphertext: [u8; 144] = compute_outgoing_body_ciphertext( + recipient, + IvpkM { inner: recipient.to_point() }, + fr_to_fq(ovsk_app), + eph_sk, + eph_pk + ); let mut encrypted_bytes: [u8; M] = [0; M]; // @todo We ignore the tags for now diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters/mod.nr b/noir-projects/aztec-nr/aztec/src/keys/getters/mod.nr index 4e599139665..0257d3d2c47 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters/mod.nr @@ -25,7 +25,7 @@ pub fn get_public_keys(account: AztecAddress) -> PublicKeys { get_public_keys_and_partial_address(account) }; assert_eq( - account, AztecAddress::compute(hinted_canonical_public_keys.hash(), partial_address), "Invalid public keys hint for address" + account, AztecAddress::compute_from_public_keys(hinted_canonical_public_keys, partial_address), "Invalid public keys hint for address" ); hinted_canonical_public_keys diff --git a/noir-projects/aztec-nr/aztec/src/utils/point.nr b/noir-projects/aztec-nr/aztec/src/utils/point.nr index 40b555c5655..75a3f07179d 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/point.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/point.nr @@ -1,6 +1,6 @@ use dep::protocol_types::point::Point; -// I am storing the modulus divided by 2 plus 1 here because full modulus would throw "String literal too large" error +// I am storing the modulus minus 1 divided by 2 here because full modulus would throw "String literal too large" error // Full modulus is 21888242871839275222246405745257275088548364400416034343698204186575808495617 global BN254_FR_MODULUS_DIV_2: Field = 10944121435919637611123202872628637544274182200208017171849102093287904247808; 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 2ab6c10a034..d7d09b6ecc8 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 @@ -94,7 +94,7 @@ contract ContractInstanceDeployer { let partial_address = PartialAddress::compute(contract_class_id, salt, initialization_hash, deployer); - let address = AztecAddress::compute(public_keys.hash(), partial_address); + let address = AztecAddress::compute_from_public_keys(public_keys, partial_address); // Emit the address as a nullifier to be able to prove that this instance has been (not) deployed context.push_nullifier(address.to_field()); diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/util.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/util.nr index 824847e2c0c..3df705deca8 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/util.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/util.nr @@ -13,5 +13,5 @@ pub fn recover_address(message_hash: Field, witness: AuthWitness) -> AztecAddres ); assert(verification == true); - AztecAddress::compute(witness.keys.hash(), witness.partial_address) + AztecAddress::compute_from_public_keys(witness.keys, witness.partial_address) } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_call_data_validator/validate_contract_address.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_call_data_validator/validate_contract_address.nr index e3ccbc1aab8..1d109f9c274 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_call_data_validator/validate_contract_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/private_call_data_validator/validate_contract_address.nr @@ -18,7 +18,7 @@ pub fn validate_contract_address(private_call_data: PrivateCallData, protocol_co private_call_data.contract_class_artifact_hash, private_call_data.contract_class_public_bytecode_commitment, private_call_data.salted_initialization_hash, - private_call_data.public_keys.hash() + private_call_data.public_keys ); let protocol_contract_index = contract_address.to_field(); 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 4fe04afcb26..e8c4c222502 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 @@ -1,16 +1,29 @@ use crate::{ - abis::function_selector::FunctionSelector, + abis::function_selector::FunctionSelector, public_keys::{ToPoint, PublicKeys}, address::{ partial_address::PartialAddress, public_keys_hash::PublicKeysHash, salted_initialization_hash::SaltedInitializationHash }, - constants::{AZTEC_ADDRESS_LENGTH, FUNCTION_TREE_HEIGHT, GENERATOR_INDEX__CONTRACT_ADDRESS_V1}, + constants::{ + AZTEC_ADDRESS_LENGTH, FUNCTION_TREE_HEIGHT, GENERATOR_INDEX__PUBLIC_KEYS_HASH, + GENERATOR_INDEX__CONTRACT_ADDRESS_V1 +}, contract_class_id::ContractClassId, hash::{poseidon2_hash_with_separator, private_functions_root_from_siblings}, merkle_tree::membership::MembershipWitness, traits::{Empty, FromField, ToField, Serialize, Deserialize}, utils }; +global BN254_FR_MODULUS_DIV_2: Field = 10944121435919637611123202872628637544274182200208017171849102093287904247808; + +// We do below because `use crate::point::Point;` does not work +use dep::std::embedded_curve_ops::EmbeddedCurvePoint as Point; + +use std::{ + ec::{sqrt, pow}, + embedded_curve_ops::{fixed_base_scalar_mul as derive_public_key, EmbeddedCurveScalar} +}; + // Aztec address pub struct AztecAddress { inner : Field @@ -52,6 +65,31 @@ impl Deserialize for AztecAddress { } } +impl ToPoint for AztecAddress { + fn to_point(self) -> Point { + // Calculate y^2 = x^3 - 17 + let y_squared = pow(self.inner, 3) - 17; + + // We can see if y is square first, or we can soft fail with just sqrt(y_squared); + // If y is not square, the x-coordinate is not on the curve + // Do we throw here or soft continue ? + // let y_is_square = is_square(y_squared); + // assert(y_is_square); + + let mut y = sqrt(y_squared); + + // We can NOT do a check like the below. We do not have access to the sign, and this derivation produces "both" points + // assert(y.lt(BN254_FR_MODULUS_DIV_2) | y.eq(BN254_FR_MODULUS_DIV_2)); + + // If we get a negative y coordinate, we pin it to the positive one by subtracting it from the Field modulus + if (!(y.lt(BN254_FR_MODULUS_DIV_2) | y.eq(BN254_FR_MODULUS_DIV_2))) { + y = (BN254_FR_MODULUS_DIV_2 + BN254_FR_MODULUS_DIV_2 + 1) - y; + } + + Point { x: self.inner, y, is_infinite: false } + } +} + impl AztecAddress { pub fn zero() -> Self { Self { inner: 0 } @@ -66,6 +104,18 @@ impl AztecAddress { ) } + pub fn compute_from_public_keys(public_keys: PublicKeys, partial_address: PartialAddress) -> AztecAddress { + let public_keys_hash = public_keys.hash(); + + let pre_address = poseidon2_hash_with_separator( + [public_keys_hash.to_field(), partial_address.to_field()], + GENERATOR_INDEX__CONTRACT_ADDRESS_V1 + ); + + let address_point = derive_public_key(EmbeddedCurveScalar::from_field(pre_address)).add(public_keys.ivpk_m.to_point()); + AztecAddress::from_field(address_point.x) + } + pub fn compute_from_private_function( function_selector: FunctionSelector, functino_vk_hash: Field, @@ -73,7 +123,7 @@ impl AztecAddress { contract_class_artifact_hash: Field, contract_class_public_bytecode_commitment: Field, salted_initialization_hash: SaltedInitializationHash, - public_keys_hash: PublicKeysHash + public_keys: PublicKeys ) -> Self { let private_functions_root = private_functions_root_from_siblings( function_selector, @@ -91,7 +141,7 @@ impl AztecAddress { // Compute contract address using the preimage which includes the class_id. let partial_address = PartialAddress::compute_from_salted_initialization_hash(contract_class_id, salted_initialization_hash); - AztecAddress::compute(public_keys_hash, partial_address) + AztecAddress::compute_from_public_keys(public_keys, partial_address) } pub fn is_zero(self) -> bool { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/contract_instance.nr b/noir-projects/noir-protocol-circuits/crates/types/src/contract_instance.nr index 95f113ec1c3..6c05cdc605e 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/contract_instance.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/contract_instance.nr @@ -80,8 +80,8 @@ impl Hash for ContractInstance { impl ContractInstance { fn to_address(self) -> AztecAddress { - AztecAddress::compute( - self.public_keys.hash(), + AztecAddress::compute_from_public_keys( + self.public_keys, PartialAddress::compute( self.contract_class_id, self.salt, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contracts.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contracts.nr index ea84c6c5ec0..6db26f2740d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contracts.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contracts.nr @@ -68,7 +68,7 @@ pub fn get_protocol_contract(index: u32) -> ContractData { let partial_address = PartialAddress::compute_from_salted_initialization_hash(contract_class_id, salted_initialization_hash); - let address = AztecAddress::compute(public_keys.hash(), partial_address); + let address = AztecAddress::compute_from_public_keys(public_keys, partial_address); ContractData { contract_address_salt: 1, diff --git a/yarn-project/aztec.js/src/account_manager/index.ts b/yarn-project/aztec.js/src/account_manager/index.ts index 1448f6d7212..3f0d22f42ce 100644 --- a/yarn-project/aztec.js/src/account_manager/index.ts +++ b/yarn-project/aztec.js/src/account_manager/index.ts @@ -35,26 +35,29 @@ export class AccountManager { public readonly salt: Fr; // TODO(@spalladino): Does it make sense to have both completeAddress and instance? - private completeAddress?: CompleteAddress; - private instance?: ContractInstanceWithAddress; - private publicKeys?: PublicKeys; + private completeAddress: CompleteAddress; + private instance: ContractInstanceWithAddress; constructor(private pxe: PXE, private secretKey: Fr, private accountContract: AccountContract, salt?: Salt) { this.salt = salt !== undefined ? new Fr(salt) : Fr.random(); - } - protected getPublicKeysHash() { - if (!this.publicKeys) { - this.publicKeys = deriveKeys(this.secretKey).publicKeys; - } - return this.publicKeys.hash(); + const { publicKeys } = deriveKeys(secretKey); + + this.instance = getContractInstanceFromDeployParams(this.accountContract.getContractArtifact(), { + constructorArgs: this.accountContract.getDeploymentArgs(), + salt: this.salt, + publicKeys, + }); + + this.completeAddress = CompleteAddress.fromSecretKeyAndInstance(this.secretKey, this.instance); } protected getPublicKeys() { - if (!this.publicKeys) { - this.publicKeys = deriveKeys(this.secretKey).publicKeys; - } - return this.publicKeys; + return this.instance.publicKeys; + } + + protected getPublicKeysHash() { + return this.getPublicKeys().hash(); } /** @@ -73,10 +76,6 @@ export class AccountManager { * @returns The address, partial address, and encryption public key. */ public getCompleteAddress(): CompleteAddress { - if (!this.completeAddress) { - const instance = this.getInstance(); - this.completeAddress = CompleteAddress.fromSecretKeyAndInstance(this.secretKey, instance); - } return this.completeAddress; } @@ -95,13 +94,6 @@ export class AccountManager { * @returns ContractInstance instance. */ public getInstance(): ContractInstanceWithAddress { - if (!this.instance) { - this.instance = getContractInstanceFromDeployParams(this.accountContract.getContractArtifact(), { - constructorArgs: this.accountContract.getDeploymentArgs(), - salt: this.salt, - publicKeys: this.getPublicKeys(), - }); - } return this.instance; } diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index f3c3f50aff6..e7641b22199 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -19,6 +19,7 @@ import { ScopedLogHash, SerializableContractInstance, computeContractAddressFromInstance, + computeContractAddressFromInstanceNew, computeContractClassId, getContractClassFromArtifact, } from '@aztec/circuits.js'; @@ -329,7 +330,7 @@ export const randomContractInstanceWithAddress = ( address?: AztecAddress, ): ContractInstanceWithAddress => { const instance = SerializableContractInstance.random(opts); - return instance.withAddress(address ?? computeContractAddressFromInstance(instance)); + return instance.withAddress(address ?? computeContractAddressFromInstanceNew(instance)); }; export const randomDeployedContract = () => { diff --git a/yarn-project/circuits.js/src/contract/contract_address.ts b/yarn-project/circuits.js/src/contract/contract_address.ts index 571d8a33f74..2334ed5947b 100644 --- a/yarn-project/circuits.js/src/contract/contract_address.ts +++ b/yarn-project/circuits.js/src/contract/contract_address.ts @@ -5,7 +5,7 @@ import { Fr } from '@aztec/foundation/fields'; import { GeneratorIndex } from '../constants.gen.js'; import { computeVarArgsHash } from '../hash/hash.js'; -import { computeAddress } from '../keys/index.js'; +import { computeAddress, computeNewAddress } from '../keys/index.js'; import { type ContractInstance } from './interfaces/contract_instance.js'; // TODO(@spalladino): Review all generator indices in this file @@ -29,6 +29,15 @@ export function computeContractAddressFromInstance( return computeAddress(publicKeysHash, partialAddress); } +export function computeContractAddressFromInstanceNew( + instance: + | ContractInstance + | ({ contractClassId: Fr; saltedInitializationHash: Fr } & Pick), +): AztecAddress { + const partialAddress = computePartialAddress(instance); + return computeNewAddress(instance.publicKeys, partialAddress); +} + /** * Computes the partial address defined as the hash of the contract class id and salted initialization hash. * @param instance - Contract instance for which to calculate the partial address. diff --git a/yarn-project/circuits.js/src/contract/contract_instance.ts b/yarn-project/circuits.js/src/contract/contract_instance.ts index 6d574838c15..52b293e660c 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance.ts @@ -14,6 +14,7 @@ import { computeContractClassId } from '../contract/contract_class_id.js'; import { PublicKeys } from '../types/public_keys.js'; import { computeContractAddressFromInstance, + computeContractAddressFromInstanceNew, computeInitializationHash, computeInitializationHashFromEncodedArgs, } from './contract_address.js'; @@ -133,7 +134,7 @@ export function getContractInstanceFromDeployParams( version: 1, }; - return { ...instance, address: computeContractAddressFromInstance(instance) }; + return { ...instance, address: computeContractAddressFromInstanceNew(instance) }; } function getConstructorArtifact( diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index c8eb037c167..61af48f7595 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -1,6 +1,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { poseidon2HashWithSeparator, sha512ToGrumpkinScalar } from '@aztec/foundation/crypto'; -import { type Fq, type Fr, GrumpkinScalar } from '@aztec/foundation/fields'; +import { Fq, Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { Grumpkin } from '../barretenberg/crypto/grumpkin/index.js'; import { GeneratorIndex } from '../constants.gen.js'; @@ -46,6 +46,40 @@ export function computeAddress(publicKeysHash: Fr, partialAddress: Fr) { return AztecAddress.fromField(addressFr); } +export function computeNewAddress(publicKeys: PublicKeys, partialAddress: Fr) { + const preaddress = poseidon2HashWithSeparator( + [publicKeys.hash(), partialAddress], + GeneratorIndex.CONTRACT_ADDRESS_V1, + ); + const address = computeAddressFromPreaddressAndIvpkM(preaddress, publicKeys.masterIncomingViewingPublicKey); + + return address; +} + +export function computeAddressFromPreaddressAndIvpkM(preaddress: Fr, ivpkM: Point) { + const addressPoint = computeAddressPointFromPreaddressAndIvpkM(preaddress, ivpkM); + + return AztecAddress.fromField(addressPoint.x); +} + +export function computeAddressPointFromPreaddressAndIvpkM(preaddress: Fr, ivpkM: Point) { + const preaddressPoint = derivePublicKeyFromSecretKey(new Fq(preaddress.toBigInt())); + const addressPoint = new Grumpkin().add(preaddressPoint, ivpkM); + + return addressPoint; +} + +export function computeAddressSecret(preaddress: Fr, ivsk: Fq) { + const addressSecretCandidate = ivsk.add(new Fq(preaddress.toBigInt())); + const addressPointCandidate = derivePublicKeyFromSecretKey(addressSecretCandidate); + + if (!addressPointCandidate.y.lt(new Fr((Fr.MODULUS - 1n) / 2n))) { + return new Fq(Fq.MODULUS - addressSecretCandidate.toBigInt()); + } + + return addressSecretCandidate; +} + export function derivePublicKeyFromSecretKey(secretKey: Fq) { const curve = new Grumpkin(); return curve.mul(curve.generator(), secretKey); diff --git a/yarn-project/circuits.js/src/structs/complete_address.ts b/yarn-project/circuits.js/src/structs/complete_address.ts index 31c36f15698..76fc19cf984 100644 --- a/yarn-project/circuits.js/src/structs/complete_address.ts +++ b/yarn-project/circuits.js/src/structs/complete_address.ts @@ -1,9 +1,10 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr } from '@aztec/foundation/fields'; +import { Fq, Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { Grumpkin } from '../barretenberg/index.js'; import { computePartialAddress } from '../contract/contract_address.js'; -import { computeAddress, deriveKeys } from '../keys/index.js'; +import { computeAddress, deriveKeys, derivePublicKeyFromSecretKey } from '../keys/index.js'; import { type PartialAddress } from '../types/partial_address.js'; import { PublicKeys } from '../types/public_keys.js'; @@ -35,9 +36,18 @@ export class CompleteAddress { } static fromSecretKeyAndPartialAddress(secretKey: Fr, partialAddress: Fr): CompleteAddress { - const { publicKeys } = deriveKeys(secretKey); - const address = computeAddress(publicKeys.hash(), partialAddress); - return new CompleteAddress(address, publicKeys, partialAddress); + const { publicKeys, masterIncomingViewingSecretKey } = deriveKeys(secretKey); + const preaddress = computeAddress(publicKeys.hash(), partialAddress); + + const combined = masterIncomingViewingSecretKey.add(new Fq(preaddress.toBigInt())); + + const addressPoint = derivePublicKeyFromSecretKey(combined); + + return new CompleteAddress(AztecAddress.fromField(addressPoint.x), publicKeys, partialAddress); + } + + getPreAddress() { + return computeAddress(this.publicKeys.hash(), this.partialAddress); } static fromSecretKeyAndInstance( @@ -50,8 +60,12 @@ export class CompleteAddress { /** Throws if the address is not correctly derived from the public key and partial address.*/ public validate() { - const expectedAddress = computeAddress(this.publicKeys.hash(), this.partialAddress); - if (!expectedAddress.equals(this.address)) { + const expectedPreAddress = computeAddress(this.publicKeys.hash(), this.partialAddress); + const expectedPreAddressPoint = derivePublicKeyFromSecretKey(new Fq(expectedPreAddress.toBigInt())); + + const expectedAddress = new Grumpkin().add(expectedPreAddressPoint, this.publicKeys.masterIncomingViewingPublicKey); + + if (!AztecAddress.fromField(expectedAddress.x).equals(this.address)) { throw new Error( `Address cannot be derived from public keys and partial address (received ${this.address.toString()}, derived ${expectedAddress.toString()})`, ); diff --git a/yarn-project/circuits.js/src/types/public_keys.ts b/yarn-project/circuits.js/src/types/public_keys.ts index a641a7fdfde..3d891a00a8b 100644 --- a/yarn-project/circuits.js/src/types/public_keys.ts +++ b/yarn-project/circuits.js/src/types/public_keys.ts @@ -3,8 +3,8 @@ import { Fq, Fr, Point } from '@aztec/foundation/fields'; import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { GeneratorIndex } from '../constants.gen.js'; -import { type PublicKey } from './public_key.js'; import { derivePublicKeyFromSecretKey } from '../keys/derivation.js'; +import { type PublicKey } from './public_key.js'; export class PublicKeys { public constructor( @@ -48,7 +48,12 @@ export class PublicKeys { static default(): PublicKeys { // We use this because empty will produce a point not on the curve. We may want to replace this with some sort of hash to curve func. - return new PublicKeys(derivePublicKeyFromSecretKey(new Fq(1)), derivePublicKeyFromSecretKey(new Fq(2)), derivePublicKeyFromSecretKey(new Fq(3)), derivePublicKeyFromSecretKey(new Fq(4))); + return new PublicKeys( + derivePublicKeyFromSecretKey(new Fq(1)), + derivePublicKeyFromSecretKey(new Fq(2)), + derivePublicKeyFromSecretKey(new Fq(3)), + derivePublicKeyFromSecretKey(new Fq(4)), + ); } static random(): PublicKeys { 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 17e19850e80..081232a1ad8 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 @@ -14,6 +14,7 @@ import { retryUntil, sleep, } from '@aztec/aztec.js'; +import { computeAddressSecret, deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js'; import { times } from '@aztec/foundation/collection'; import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js'; @@ -295,7 +296,13 @@ describe('e2e_block_building', () => { // compare logs expect(rct.status).toEqual('success'); const noteValues = tx.noteEncryptedLogs.unrollLogs().map(l => { - const notePayload = L1NotePayload.decryptAsIncoming(l, keys.masterIncomingViewingSecretKey); + const notePayload = L1NotePayload.decryptAsIncoming( + l, + computeAddressSecret( + thisWallet.getCompleteAddress().getPreAddress(), + deriveMasterIncomingViewingSecretKey(thisWallet.getSecretKey()), + ), + ); return notePayload?.note.items[0]; }); expect(noteValues[0]).toEqual(new Fr(10)); diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index d15b19d740c..b53eb2f09ce 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -6,7 +6,7 @@ import { L1EventPayload, type PXE, } from '@aztec/aztec.js'; -import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js'; +import { computeAddressSecret, deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js'; import { EventSelector } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { type Tuple } from '@aztec/foundation/serialize'; @@ -57,7 +57,10 @@ describe('Logs', () => { const decryptedEvent0 = L1EventPayload.decryptAsIncoming( encryptedLogs[0], - deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), + computeAddressSecret( + wallets[0].getCompleteAddress().getPreAddress(), + deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), + ), )!; expect(decryptedEvent0.contractAddress).toStrictEqual(testLogContract.address); @@ -77,7 +80,10 @@ describe('Logs', () => { const decryptedEvent1 = L1EventPayload.decryptAsIncoming( encryptedLogs[2], - deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), + computeAddressSecret( + wallets[0].getCompleteAddress().getPreAddress(), + deriveMasterIncomingViewingSecretKey(wallets[0].getSecretKey()), + ), )!; expect(decryptedEvent1.contractAddress).toStrictEqual(testLogContract.address); diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index 65d6a150aef..5f2ee0a153e 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -145,14 +145,14 @@ export const browserTestSuite = ( it('Can access CompleteAddress class in browser', async () => { const result: string = await page.evaluate(() => { const completeAddress = window.AztecJs.CompleteAddress.fromString( - '0x05eb10b0bda78b5d8dc91de3d62bed7b55d73b5509521a9f8ef7269133e58a8c2c93b9572b35f9c9e07e9003ae1ca444442a165f927bce00e347dab57cc19391148730d0deec722eb6c54747df7345bc2ab3bd8e81f438b17b81ccabd9e6a3ac0708920251ccaf6664d769cbc47c8d767f64912639e13d9f9e441b225066161900c48a65eea83f1dbf217c43daf1be6ba9cefd2754f07e3cc13e81e5432e47f30dfb47c8b1e11368bec638fd9d22c696bf9c323a0fd09050745f4b7cf150bfa529a9f3062ee5f9d0a099ac53b4e1130653fb797ed2b59914a8915951d13ad8252521211957a854707af85ad40e9ab4d474a4fcbdcbe7a47866cae0db4fd86ed2261669d85a9cfbd09365a6db5d7acfe5560104a0cb893a375d6c08ffb9cbb8270be446a16361f271ac11899ee19f990c68035da18703ba00c8e9773dfe6a784a', + '0x2401bfdad7ac9282bd612e8a6bb0f6ce125b08e317e24dc04ddbba24694ac2e7261249d8b3ad8ad9ed075257eede1dcd8356bfd55e1518f07717c47609194b6500c926582f07fda6a53e3600251f2aa1401c0cd377cef064f3f59045222194541acc5f62d8907a6dc98b85e32f8097a152c3c795bb3981c64e576b014f23005e0891d109aa087560cf8720ae94098827aa009a0bcee09f98fd2a05a7cbc6185402a53516a379d7856d26e3bb5542f1fe57f1ee5a0e4c60f7a463205aa19e2f8e00bce110b9a89857b79e3f70777e38a262b04cf80c56bd833a3c4b58dde7dbdc25c807c4012229e08651fd0d48cf9d966d9ab18826692f48a4cf934bef78614023e9cb95711f532786c7c78e72c3752f03f2a4cafc1846ad9df47324e2b7683f0faaa2e6fe44f3ff68646ce7d8538cb6935ce25472c4c75a244ab0c5d2e3b74d', ); // NOTE: browser does not know how to serialize CompleteAddress for return, so return a string // otherwise returning a CompleteAddress makes result undefined. return completeAddress.toString(); }); expect(result).toBe( - '0x05eb10b0bda78b5d8dc91de3d62bed7b55d73b5509521a9f8ef7269133e58a8c2c93b9572b35f9c9e07e9003ae1ca444442a165f927bce00e347dab57cc19391148730d0deec722eb6c54747df7345bc2ab3bd8e81f438b17b81ccabd9e6a3ac0708920251ccaf6664d769cbc47c8d767f64912639e13d9f9e441b225066161900c48a65eea83f1dbf217c43daf1be6ba9cefd2754f07e3cc13e81e5432e47f30dfb47c8b1e11368bec638fd9d22c696bf9c323a0fd09050745f4b7cf150bfa529a9f3062ee5f9d0a099ac53b4e1130653fb797ed2b59914a8915951d13ad8252521211957a854707af85ad40e9ab4d474a4fcbdcbe7a47866cae0db4fd86ed2261669d85a9cfbd09365a6db5d7acfe5560104a0cb893a375d6c08ffb9cbb8270be446a16361f271ac11899ee19f990c68035da18703ba00c8e9773dfe6a784a', + '0x2401bfdad7ac9282bd612e8a6bb0f6ce125b08e317e24dc04ddbba24694ac2e7261249d8b3ad8ad9ed075257eede1dcd8356bfd55e1518f07717c47609194b6500c926582f07fda6a53e3600251f2aa1401c0cd377cef064f3f59045222194541acc5f62d8907a6dc98b85e32f8097a152c3c795bb3981c64e576b014f23005e0891d109aa087560cf8720ae94098827aa009a0bcee09f98fd2a05a7cbc6185402a53516a379d7856d26e3bb5542f1fe57f1ee5a0e4c60f7a463205aa19e2f8e00bce110b9a89857b79e3f70777e38a262b04cf80c56bd833a3c4b58dde7dbdc25c807c4012229e08651fd0d48cf9d966d9ab18826692f48a4cf934bef78614023e9cb95711f532786c7c78e72c3752f03f2a4cafc1846ad9df47324e2b7683f0faaa2e6fe44f3ff68646ce7d8538cb6935ce25472c4c75a244ab0c5d2e3b74d', ); }); diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 8939b7adb7c..08443b00468 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -381,6 +381,10 @@ export class Fq extends BaseField { return new Fq((high.toBigInt() << Fq.HIGH_SHIFT) + low.toBigInt()); } + add(rhs: Fq) { + return new Fq((this.toBigInt() + rhs.toBigInt()) % Fq.MODULUS); + } + toJSON() { return { type: 'Fq', diff --git a/yarn-project/key-store/src/key_store.ts b/yarn-project/key-store/src/key_store.ts index 3c6904ee805..a48f4aaa01b 100644 --- a/yarn-project/key-store/src/key_store.ts +++ b/yarn-project/key-store/src/key_store.ts @@ -54,8 +54,8 @@ export class KeyStore { publicKeys, } = deriveKeys(sk); - const publicKeysHash = publicKeys.hash(); - const account = computeAddress(publicKeysHash, partialAddress); + const completeAddress = CompleteAddress.fromSecretKeyAndPartialAddress(sk, partialAddress); + const { address: account } = completeAddress; // Naming of keys is as follows ${account}-${n/iv/ov/t}${sk/pk}_m await this.#keys.set(`${account.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer()); @@ -82,7 +82,7 @@ export class KeyStore { await this.#keys.set(`${account.toString()}-tpk_m_hash`, publicKeys.masterTaggingPublicKey.hash().toBuffer()); // At last, we return the newly derived account address - return Promise.resolve(new CompleteAddress(account, publicKeys, partialAddress)); + return Promise.resolve(completeAddress); } /** @@ -104,7 +104,7 @@ export class KeyStore { * @returns The key validation request. */ public getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise { - const [keyPrefix, account] = this.#getKeyPrefixAndAccount(pkMHash); + const [keyPrefix, account] = this.getKeyPrefixAndAccount(pkMHash); // Now we find the master public key for the account const pkMBuffer = this.#keys.get(`${account.toString()}-${keyPrefix}pk_m`); @@ -141,6 +141,22 @@ export class KeyStore { return Promise.resolve(new KeyValidationRequest(pkM, skApp)); } + /** + * Gets the master nullifier public key for a given account. + * @throws If the account does not exist in the key store. + * @param account - The account address for which to retrieve the master nullifier public key. + * @returns The master nullifier public key for the account. + */ + public async getMasterNullifierPublicKey(account: AztecAddress): Promise { + const masterNullifierPublicKeyBuffer = this.#keys.get(`${account.toString()}-npk_m`); + if (!masterNullifierPublicKeyBuffer) { + throw new Error( + `Account ${account.toString()} does not exist. Registered accounts: ${await this.getAccounts()}.`, + ); + } + return Promise.resolve(Point.fromBuffer(masterNullifierPublicKeyBuffer)); + } + /** * Gets the master incoming viewing public key for a given account. * @throws If the account does not exist in the key store. @@ -245,7 +261,7 @@ export class KeyStore { * @dev Used when feeding the sk_m to the kernel circuit for keys verification. */ public getMasterSecretKey(pkM: PublicKey): Promise { - const [keyPrefix, account] = this.#getKeyPrefixAndAccount(pkM); + const [keyPrefix, account] = this.getKeyPrefixAndAccount(pkM); const secretKeyBuffer = this.#keys.get(`${account.toString()}-${keyPrefix}sk_m`); if (!secretKeyBuffer) { @@ -268,7 +284,7 @@ export class KeyStore { * @dev Note that this is quite inefficient but it should not matter because there should never be too many keys * in the key store. */ - #getKeyPrefixAndAccount(value: Bufferable): [KeyPrefix, AztecAddress] { + getKeyPrefixAndAccount(value: Bufferable): [KeyPrefix, AztecAddress] { const valueBuffer = serializeToBuffer(value); for (const [key, val] of this.#keys.entries()) { if (val.equals(valueBuffer)) { diff --git a/yarn-project/protocol-contracts/src/protocol_contract_data.ts b/yarn-project/protocol-contracts/src/protocol_contract_data.ts index 7cc5bfad540..48ce95ad950 100644 --- a/yarn-project/protocol-contracts/src/protocol_contract_data.ts +++ b/yarn-project/protocol-contracts/src/protocol_contract_data.ts @@ -50,14 +50,14 @@ export const ProtocolContractAddress: Record }; export const ProtocolContractLeaf = { - AuthRegistry: Fr.fromString('0x2ace300b02ca5ab0a25052b1e852913a47292096997ca09f758c0e3624e84560'), - ContractInstanceDeployer: Fr.fromString('0x21e432f60f69ac5eb7582c26c03c6c7e4a3eb577720774bc8e1521561ca752a1'), - ContractClassRegisterer: Fr.fromString('0x1b6d5873cef5a35f681ab9468527f356c96e09b3c64603aef404ec2ad80aa3a9'), - MultiCallEntrypoint: Fr.fromString('0x0966ead8d11933bb3c72547bb997898971715f2275acd4c7d5d86fdf614ba1a2'), - FeeJuice: Fr.fromString('0x24388f7ef1b9c9e661721f3331a989a2f10cba300539471e4401c38629b27816'), - Router: Fr.fromString('0x1d8f25db3e8faa6a96cb1ecf57876a2ee04581deb3c4f181488ccd817abcbdb0'), + AuthRegistry: Fr.fromString('0x087d102766af335cf7654eb5d946dccf114d0eb1e86dc788cbc10d87e1c84fd0'), + ContractInstanceDeployer: Fr.fromString('0x10141a7093a1050d38fc0467c6c81ca7beb158ef47095145ce4edd52be7ef661'), + ContractClassRegisterer: Fr.fromString('0x074de3b4ee658fd695a1be6c4f9f10fe3042b948955436e0e20c31c7027cfaef'), + MultiCallEntrypoint: Fr.fromString('0x19bc0ab26e84d8b2551cb3c83000a5da0f6958056fcaa91f63c14c35e266ef0d'), + FeeJuice: Fr.fromString('0x033a034d778d077970ae6108253a4971e37af6bd163d80b3be184c5838045eda'), + Router: Fr.fromString('0x24eed1db8f33a2db1611412c16157a28a57c7bef4794844c4ef228d538438ac8'), }; export const protocolContractTreeRoot = Fr.fromString( - '0x0ce9f44ae6605f375e5f5267ceb769861703ce8e4235f16f7afc137ec34dcf06', + '0x1299197d756a2e86830d877cb2c5c71ab20aa4f97726fc7ccd2d468ce20d81a6', ); diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index b5bd723dc6d..3e7beedcacf 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -1,6 +1,13 @@ import { type AztecNode, L1NotePayload, type L2Block } from '@aztec/circuit-types'; import { type NoteProcessorStats } from '@aztec/circuit-types/stats'; -import { type CompleteAddress, INITIAL_L2_BLOCK_NUM, MAX_NOTE_HASHES_PER_TX, type PublicKey } from '@aztec/circuits.js'; +import { + type CompleteAddress, + INITIAL_L2_BLOCK_NUM, + MAX_NOTE_HASHES_PER_TX, + type PublicKey, + computeAddressSecret, + derivePublicKeyFromSecretKey, +} from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; import { type Logger, createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; @@ -115,6 +122,8 @@ export class NoteProcessor { const deferredOutgoingNotes: DeferredNoteDao[] = []; const ivskM = await this.keyStore.getMasterSecretKey(this.ivpkM); + const addressSecret = computeAddressSecret(this.account.getPreAddress(), ivskM); + const ovskM = await this.keyStore.getMasterSecretKey(this.ovpkM); // Iterate over both blocks and encrypted logs. @@ -142,7 +151,7 @@ export class NoteProcessor { for (const functionLogs of txFunctionLogs) { for (const log of functionLogs.logs) { this.stats.seen++; - const incomingNotePayload = L1NotePayload.decryptAsIncoming(log, ivskM); + const incomingNotePayload = L1NotePayload.decryptAsIncoming(log, addressSecret); const outgoingNotePayload = L1NotePayload.decryptAsOutgoing(log, ovskM); if (incomingNotePayload || outgoingNotePayload) { diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 3b13593cb29..4350af8078e 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -43,8 +43,13 @@ import { PUBLIC_DISPATCH_SELECTOR, type PartialAddress, type PrivateKernelTailCircuitPublicInputs, + computeAddress, + computeAddressSecret, computeContractAddressFromInstance, + computeContractAddressFromInstanceNew, computeContractClassId, + computeNewAddress, + computePartialAddress, getContractClassFromArtifact, } from '@aztec/circuits.js'; import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash'; @@ -267,7 +272,7 @@ export class PXEService implements PXE { } if ( // Computed address from the instance does not match address inside instance - !computeContractAddressFromInstance(instance).equals(instance.address) + !computeContractAddressFromInstanceNew(instance).equals(instance.address) ) { throw new Error('Added a contract in which the address does not match the contract instance.'); } @@ -926,6 +931,7 @@ export class PXEService implements PXE { from: number, limit: number, eventMetadata: EventMetadata, + // TODO: Fix this by allow it to pass only an address vpks: Point[], ): Promise { if (vpks.length === 0) { @@ -939,7 +945,24 @@ export class PXEService implements PXE { const encryptedLogs = encryptedTxLogs.flatMap(encryptedTxLog => encryptedTxLog.unrollLogs()); - const vsks = await Promise.all(vpks.map(vpk => this.keyStore.getMasterSecretKey(vpk))); + const vsks = await Promise.all( + vpks.map(async vpk => { + const [keyPrefix, account] = this.keyStore.getKeyPrefixAndAccount(vpk); + let secretKey = await this.keyStore.getMasterSecretKey(vpk); + if (keyPrefix === 'iv') { + const registeredAccount = await this.getRegisteredAccount(account); + if (!registeredAccount) { + throw new Error('No registered account'); + } + + const preaddress = registeredAccount.getPreAddress(); + + secretKey = computeAddressSecret(preaddress, secretKey); + } + + return secretKey; + }), + ); const visibleEvents = encryptedLogs.flatMap(encryptedLog => { for (const sk of vsks) {