From e0add3723fa70367b3513a5850d82e328e191217 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 22 Jun 2026 10:11:57 -0300 Subject: [PATCH 1/8] feat: make node.getContract take an optional reference block (#24207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PXE needs the class id at the anchor block, but the node only exposed the latest one. This forced PXE to reimplement the class id detection by raw reads of the trees combined with delayed public mutable interpretation of said data, which unnecessarily coupled PXE and the registry contracts. With this PR we simply query the data from the node, which will always be available if there's an upgrade, making the original class a safe fallback. The new `referenceBlock` argument is optional and defaults to `'latest'`, with `address` kept as the first argument, so the change is backwards compatible: existing single-argument `getContract(address)` callers keep working unchanged, and only PXE's class-id resolution passes an explicit anchor block. Supersedes #24205, addressing the review feedback to make the new argument optional. Fixes #15170 --------- Co-authored-by: Nicolás Venturo --- .../webapp-tutorial/src/embedded-wallet.ts | 5 +- .../modules/contract_data_source_adapter.ts | 11 +---- .../archiver/src/modules/data_source_base.ts | 13 +---- .../aztec-node/src/aztec-node/server.test.ts | 47 +++++++++++++++++-- .../aztec-node/src/aztec-node/server.ts | 13 ++++- .../end-to-end/src/e2e_synching.test.ts | 9 ++-- .../contract_sync_service.test.ts | 5 +- yarn-project/pxe/src/contract_sync/helpers.ts | 29 +++++------- yarn-project/pxe/src/pxe.test.ts | 6 +-- .../interfaces/contract_data_source.ts | 6 +-- .../stdlib/src/interfaces/archiver.ts | 2 +- .../stdlib/src/interfaces/aztec-node.test.ts | 6 ++- .../stdlib/src/interfaces/aztec-node.ts | 14 ++++-- 13 files changed, 101 insertions(+), 65 deletions(-) diff --git a/docs/examples/webapp-tutorial/src/embedded-wallet.ts b/docs/examples/webapp-tutorial/src/embedded-wallet.ts index df8515c98d52..d5a11489dd8a 100644 --- a/docs/examples/webapp-tutorial/src/embedded-wallet.ts +++ b/docs/examples/webapp-tutorial/src/embedded-wallet.ts @@ -80,8 +80,9 @@ export class EmbeddedWallet extends BaseEmbeddedWallet { // docs:end:initialize static async #getSponsoredFPCContract() { - const { SponsoredFPCContractArtifact } = - await import("@aztec/noir-contracts.js/SponsoredFPC"); + const { SponsoredFPCContractArtifact } = await import( + "@aztec/noir-contracts.js/SponsoredFPC" + ); const instance = await getContractInstanceFromInstantiationParams( SponsoredFPCContractArtifact, { salt: new Fr(SPONSORED_FPC_SALT) }, diff --git a/yarn-project/archiver/src/modules/contract_data_source_adapter.ts b/yarn-project/archiver/src/modules/contract_data_source_adapter.ts index 9fc7a39bce20..10a603bd0628 100644 --- a/yarn-project/archiver/src/modules/contract_data_source_adapter.ts +++ b/yarn-project/archiver/src/modules/contract_data_source_adapter.ts @@ -28,16 +28,7 @@ export class ArchiverContractDataSourceAdapter implements ContractDataSource { return this.stores.contractClasses.getBytecodeCommitment(id); } - public async getContract( - address: AztecAddress, - maybeTimestamp?: UInt64, - ): Promise { - let timestamp = maybeTimestamp; - if (timestamp === undefined) { - const latest = await this.stores.blocks.getLatestL2BlockNumber(); - const blockData = latest > 0 ? await this.stores.blocks.getBlockData({ number: latest }) : undefined; - timestamp = blockData ? blockData.header.globalVariables.timestamp : 0n; - } + public getContract(address: AztecAddress, timestamp: UInt64): Promise { return this.stores.contractInstances.getContractInstance(address, timestamp); } diff --git a/yarn-project/archiver/src/modules/data_source_base.ts b/yarn-project/archiver/src/modules/data_source_base.ts index eb2beb5dedb4..73c9e64f91b0 100644 --- a/yarn-project/archiver/src/modules/data_source_base.ts +++ b/yarn-project/archiver/src/modules/data_source_base.ts @@ -316,18 +316,7 @@ export abstract class ArchiverDataSourceBase return this.stores.contractClasses.getBytecodeCommitment(id); } - public async getContract( - address: AztecAddress, - maybeTimestamp?: UInt64, - ): Promise { - let timestamp; - if (maybeTimestamp === undefined) { - const latestBlockData = await this.getBlockData({ tag: 'proposed' }); - timestamp = latestBlockData ? latestBlockData.header.globalVariables.timestamp : 0n; - } else { - timestamp = maybeTimestamp; - } - + public getContract(address: AztecAddress, timestamp: UInt64): Promise { return this.stores.contractInstances.getContractInstance(address, timestamp); } diff --git a/yarn-project/aztec-node/src/aztec-node/server.test.ts b/yarn-project/aztec-node/src/aztec-node/server.test.ts index 6b7ae27bf658..0b54dc133308 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.test.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.test.ts @@ -33,14 +33,14 @@ import { type L2Tips, } from '@aztec/stdlib/block'; import type { CheckpointData, ProposedCheckpointData } from '@aztec/stdlib/checkpoint'; -import type { ContractDataSource } from '@aztec/stdlib/contract'; +import type { ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { EmptyL1RollupConstants, type L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { GasFees } from '@aztec/stdlib/gas'; import type { L2LogsSource, MerkleTreeReadOperations, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server'; import { InboxLeaf } from '@aztec/stdlib/messaging'; import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; -import { mockTx } from '@aztec/stdlib/testing'; +import { mockTx, randomContractInstanceWithAddress } from '@aztec/stdlib/testing'; import { AppendOnlyTreeSnapshot, MerkleTreeId, @@ -116,6 +116,7 @@ describe('aztec node', () => { let merkleTreeOps: MockProxy; let worldState: MockProxy; let l2BlockSource: MockProxy; + let contractSource: MockProxy; let l1ToL2MessageSource: MockProxy; let lastBlockNumber: BlockNumber; let node: TestAztecNodeService; @@ -204,7 +205,7 @@ describe('aztec node', () => { l1ToL2MessageSource = mock(); // all txs use the same allowed FPC class - const contractSource = mock(); + contractSource = mock(); const nodeConfigFromEnvVars: AztecNodeConfig = getConfigEnvVars(); nodeConfig = { @@ -422,6 +423,46 @@ describe('aztec node', () => { }); }); + describe('getContract', () => { + let address: AztecAddress; + let instance: ContractInstanceWithAddress; + const referenceTimestamp = 4242n; + + beforeEach(async () => { + instance = await randomContractInstanceWithAddress(); + address = instance.address; + + l2BlockSource.getBlockData.mockResolvedValue({ + header: BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(1), timestamp: referenceTimestamp }), + }), + archive: L2Block.empty().archive, + blockHash: BlockHash.random(), + checkpointNumber: CheckpointNumber(1), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + }); + + contractSource.getContract.mockImplementation((_address, timestamp) => + Promise.resolve(timestamp === referenceTimestamp ? instance : undefined), + ); + }); + + it('resolves the reference block to its timestamp and reads the instance as of that block', async () => { + expect(await node.getContract(address, BlockNumber(1))).toEqual(instance); + }); + + it('defaults to the latest block when no reference block is given', async () => { + const getBlockData = jest.spyOn(node, 'getBlockData'); + expect(await node.getContract(address)).toEqual(instance); + expect(getBlockData).toHaveBeenCalledWith('latest'); + }); + + it('throws when the reference block is not part of the chain', async () => { + l2BlockSource.getBlockData.mockResolvedValue(undefined); + await expect(node.getContract(address, BlockHash.random())).rejects.toThrow(/not found/); + }); + }); + describe('getLowNullifierMembershipWitness', () => { beforeEach(() => { lastBlockNumber = BlockNumber(1); diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 045b2ed80dbb..ef829827c99e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -1219,8 +1219,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb return this.contractDataSource.getContractClass(id); } - public getContract(address: AztecAddress): Promise { - return this.contractDataSource.getContract(address); + public async getContract( + address: AztecAddress, + referenceBlock: BlockParameter = 'latest', + ): Promise { + const blockData = await this.getBlockData(referenceBlock); + if (!blockData) { + throw new Error( + `Reference block ${inspectBlockParameter(referenceBlock)} not found when querying contract ${address}. If the node API has been queried with an anchor block hash, possibly a reorg has occurred.`, + ); + } + return this.contractDataSource.getContract(address, blockData.header.globalVariables.timestamp); } public getPrivateLogsByTags(query: PrivateLogsQuery): Promise { diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 3616e1385f7e..d6de0fd3c591 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -620,11 +620,12 @@ describe('e2e_synching', () => { expect(await archiver.getCheckpointNumber()).toBeGreaterThan(provenThrough); const blockTip = (await archiver.getBlock({ number: await archiver.getBlockNumber() }))!; + const referenceTimestamp = blockTip.header.globalVariables.timestamp; const txHash = blockTip.body.txEffects[0].txHash; const contractClassIds = await archiver.getContractClassIds(); const contractInstances = await Promise.all( - contracts.map(async c => (await archiver.getContract(c.address))!), + contracts.map(async c => (await archiver.getContract(c.address, referenceTimestamp))!), ); for (let i = 0; i < contracts.length; i++) { expect(contractInstances[i]).not.toBeUndefined(); @@ -643,9 +644,9 @@ describe('e2e_synching', () => { expect(contractClassIdsAfter.some(id => id.equals(contractInstances[0].currentContractClassId))).toBeTrue(); expect(contractClassIdsAfter.some(id => id.equals(contractInstances[1].currentContractClassId))).toBeFalse(); - expect(await archiver.getContract(contracts[0].address)).not.toBeUndefined(); - expect(await archiver.getContract(contracts[1].address)).toBeUndefined(); - expect(await archiver.getContract(contracts[2].address)).toBeUndefined(); + expect(await archiver.getContract(contracts[0].address, referenceTimestamp)).not.toBeUndefined(); + expect(await archiver.getContract(contracts[1].address, referenceTimestamp)).toBeUndefined(); + expect(await archiver.getContract(contracts[2].address, referenceTimestamp)).toBeUndefined(); // Only the hardcoded schnorr is pruned since the contract class also existed before prune. expect(contractClassIdsAfter).toEqual( diff --git a/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts b/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts index ae81397c6454..ad59004f7f91 100644 --- a/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts +++ b/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts @@ -55,8 +55,9 @@ describe('ContractSyncService', () => { } as ContractInstanceWithAddress); aztecNode = mock(); - // readCurrentClassId reads from public storage; Fr.ZERO causes fallback to originalContractClassId - aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); + // verifyCurrentClassId reads the instance from the node at the anchor block; returning undefined causes + // readCurrentClassId to fall back to the local originalContractClassId, which matches so verification passes. + aztecNode.getContract.mockResolvedValue(undefined); noteStore = mock(); // syncNoteNullifiers returns early when no notes diff --git a/yarn-project/pxe/src/contract_sync/helpers.ts b/yarn-project/pxe/src/contract_sync/helpers.ts index c548188c76c8..b651d210280a 100644 --- a/yarn-project/pxe/src/contract_sync/helpers.ts +++ b/yarn-project/pxe/src/contract_sync/helpers.ts @@ -1,20 +1,20 @@ -import { ProtocolContractAddress, isProtocolContract } from '@aztec/protocol-contracts'; +import { isProtocolContract } from '@aztec/protocol-contracts'; import type { FunctionCall, FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractInstance } from '@aztec/stdlib/contract'; -import { DelayedPublicMutableValues, DelayedPublicMutableValuesWithHash } from '@aztec/stdlib/delayed-public-mutable'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import type { BlockHeader } from '@aztec/stdlib/tx'; import type { ContractStore } from '../storage/contract_store/contract_store.js'; /** - * Read the current class id of a contract from the execution data provider or AztecNode. If not found, class id - * from the instance is used. + * Read the current class id of a contract as of the given block, as seen by the AztecNode. The node resolves it from + * the same scheduled value change the AVM enforces against the public data tree. If the node has no record of the + * instance (e.g. it was never publicly deployed), the original class id from the local instance is used. * @param contractAddress - The address of the contract to read the class id for. - * @param instance - The instance of the contract. - * @param aztecNode - The Aztec node to query for storage. - * @param header - The header of the block at which to load the DelayedPublicMutable storing the class id. + * @param instance - The local instance of the contract, used as a fallback when the node doesn't know it. + * @param aztecNode - The Aztec node to query. + * @param header - The header of the block at which to resolve the current class id. * @returns The current class id. */ export async function readCurrentClassId( @@ -23,17 +23,10 @@ export async function readCurrentClassId( aztecNode: AztecNode, header: BlockHeader, ) { - const blockHash = await header.hash(); - const timestamp = header.globalVariables.timestamp; - const { delayedPublicMutableSlot } = await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress); - const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => - aztecNode.getPublicStorageAt(blockHash, ProtocolContractAddress.ContractInstanceRegistry, slot), - ); - let currentClassId = delayedPublicMutableValues.svc.getCurrentAt(timestamp)[0]; - if (currentClassId.isZero()) { - currentClassId = instance.originalContractClassId; - } - return currentClassId; + const nodeInstance = await aztecNode.getContract(contractAddress, await header.hash()); + // If the contract was upgraded then the node WILL know of that and return a non-undefined instance. An undefined + // result therefore means that no upgrade has happened, and therefore that the original class is the current one. + return nodeInstance?.currentContractClassId ?? instance.originalContractClassId; } export async function syncScope( diff --git a/yarn-project/pxe/src/pxe.test.ts b/yarn-project/pxe/src/pxe.test.ts index fad92c90669d..01b6b7e56fdc 100644 --- a/yarn-project/pxe/src/pxe.test.ts +++ b/yarn-project/pxe/src/pxe.test.ts @@ -293,9 +293,9 @@ describe('PXE', () => { finalized: tipId, }); - // This is read when PXE tries to resolve the - // class id of a contract instance - node.getPublicStorageAt.mockResolvedValue(Fr.ZERO); + // Read when PXE resolves the current class id of a contract instance at the anchor block. Returning undefined + // makes readCurrentClassId fall back to the local instance's originalContractClassId. + node.getContract.mockResolvedValue(undefined); // Used to sync private logs from the node - the return array needs to have the same length as the number of tags // on the input. diff --git a/yarn-project/stdlib/src/contract/interfaces/contract_data_source.ts b/yarn-project/stdlib/src/contract/interfaces/contract_data_source.ts index 27f00608787a..c7aba813415a 100644 --- a/yarn-project/stdlib/src/contract/interfaces/contract_data_source.ts +++ b/yarn-project/stdlib/src/contract/interfaces/contract_data_source.ts @@ -25,11 +25,9 @@ export interface ContractDataSource { /** * Returns a publicly deployed contract instance given its address. * @param address - Address of the deployed contract. - * @param timestamp - Timestamp at which to retrieve the contract instance. If not provided, the latest block should - * be used. - * TODO(#15170): Fix the implementations ignoring the timestamp param and make timestamp required. + * @param timestamp - Timestamp at which to resolve the contract instance's current class id. */ - getContract(address: AztecAddress, timestamp?: UInt64): Promise; + getContract(address: AztecAddress, timestamp: UInt64): Promise; /** * Returns the list of all class ids known. diff --git a/yarn-project/stdlib/src/interfaces/archiver.ts b/yarn-project/stdlib/src/interfaces/archiver.ts index 2b9ec63db52f..aec489278b8f 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.ts @@ -122,7 +122,7 @@ export const ArchiverApiSchema: ApiSchemaFor = { getContractClass: z.function({ input: z.tuple([schemas.Fr]), output: ContractClassPublicSchema.optional() }), getBytecodeCommitment: z.function({ input: z.tuple([schemas.Fr]), output: schemas.Fr }), getContract: z.function({ - input: z.tuple([schemas.AztecAddress, optional(schemas.BigInt)]), + input: z.tuple([schemas.AztecAddress, schemas.BigInt]), output: ContractInstanceWithAddressSchema.optional(), }), getContractClassIds: z.function({ input: z.tuple([]), output: z.array(schemas.Fr) }), diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index 7e00574f58bd..6ce64a363a04 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -916,7 +916,11 @@ class MockAztecNode implements AztecNode { const contractClass = await getContractClassFromArtifact(this.artifact); return contractClass; } - async getContract(address: AztecAddress): Promise { + async getContract( + address: AztecAddress, + referenceBlock?: BlockParameter, + ): Promise { + expect(referenceBlock).toBeUndefined(); expect(address).toBeInstanceOf(AztecAddress); const instance = { version: 2 as const, diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index 41328fc29545..644ba4de71ea 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -522,10 +522,18 @@ export interface AztecNode { getContractClass(id: Fr): Promise; /** - * Returns a publicly deployed contract instance given its address. + * Returns a publicly deployed contract instance given its address. Its current class id is resolved as of the given + * reference block. + * + * Returns `undefined` if the instance has not been published (i.e. `publish_for_public_execution` was never called + * on the `ContractInstanceRegistry`). A contract whose class has been updated will never return `undefined`: + * scheduling an update requires the contract's deployment nullifier, which is only emitted by publishing, so any + * updatable contract has necessarily been published. * @param address - Address of the deployed contract. + * @param referenceBlock - The block parameter (block number, block hash, or 'latest') at which to get the data. + * Defaults to 'latest'. */ - getContract(address: AztecAddress): Promise; + getContract(address: AztecAddress, referenceBlock?: BlockParameter): Promise; /** * Returns the ENR of this node for peer discovery, if available. @@ -758,7 +766,7 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getContractClass: z.function({ input: z.tuple([schemas.Fr]), output: ContractClassPublicSchema.optional() }), getContract: z.function({ - input: z.tuple([schemas.AztecAddress]), + input: z.tuple([schemas.AztecAddress, optional(BlockParameterSchema)]), output: ContractInstanceWithAddressSchema.optional(), }), From 518d851bc82a486bca2514443f0221d74c3b52af Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 22 Jun 2026 11:31:11 -0400 Subject: [PATCH 2/8] feat(aztec-nr): wire constrained message delivery (#23866) Fixes [F-669](https://linear.app/aztec-labs/issue/F-669/aztec-nr-calculate-secret-and-index-helper-for-constrained-delivery) Fixes [F-670](https://linear.app/aztec-labs/issue/F-670/aztec-nr-send-constrained-msg-helper-that-emits-a-constrained) ## The change - Adds constrained-delivery helpers that resolve or bootstrap the app-siloed handshake secret, seed same-tx index reuse, validate index `0` against the standard HandshakeRegistry, and constrain `index > 0` through the previous chain nullifier. - Wires constrained private message delivery to derive constrained log tags from the resolved `(secret, index)` pair and emit the current constrained-message nullifier. - Keeps constrained logs from being squashed with note hashes so recipient discovery can advance the per-secret index chain. - Authorizes the standard HandshakeRegistry utility reads needed by the flow. - Adds TXE, snapshot, and e2e coverage for bootstrap, reuse, index advancement, missing prior-nullifier failure, invalid API combinations, and standard-registry constrained delivery. Large portion of the diff is tests. [F-741](https://linear.app/aztec-labs/issue/F-741/getsharedsecrets-throws-no-public-key-during-recipient-discovery-for-a) follow-up for the test skipped in [fd07996](https://github.com/AztecProtocol/aztec-packages/pull/23866/commits/fd07996792ef2614fdc9f1befd5399df8bd683c0). ## Concurrency / batching constraint (worth a look) Pinning the e2e tests surfaced a sequencing constraint in constrained delivery that reviewers should sanity-check: - **Parallel sends on one `(sender, recipient, secret)` chain collide.** Each send is keyed on an incrementing index, so two sends fired as separate parallel txs read the same index and one tx is rejected. Distinct recipients are distinct chains and parallelize fine. Pinned as `it.failing` (documents the limit; flips green if parallel sends ever become supported). - **Same-chain sends _can_ be batched into one tx**, but only onto an already-committed handshake. A later send discharges its predecessor check against a same-tx pending nullifier. Works both via a single contract call (`emit_two_events`) and client-side `BatchCall`. - **Batching onto a brand-new chain does not reuse, it re-handshakes.** The reuse-vs-bootstrap decision in `get_or_create_app_siloed_handshake_secret` is a utility call reading _committed_ state, so a bootstrap earlier in the same tx is invisible and the later send mints a fresh secret on a separate chain (next index lands at 1, not 2). A new recipient therefore needs one landed tx to establish the chain before sends can be batched onto it. This is why the batched tests seed the handshake first. All four behaviors are pinned in `e2e_constrained_delivery.test.ts`; the rationale lives in the module doc and on `get_or_create_app_siloed_handshake_secret`. Follow-up: allowing utility functions to execute against combined committed + pending transaction state is tracked in [F-238](https://linear.app/aztec-labs/issue/F-238/allow-execution-of-utility-functions-touched-by-pending-transactions). --------- Co-authored-by: AztecBot Co-authored-by: Nicolas Chamo --- .../framework-description/state_variables.md | 2 +- .../webapp-tutorial/contracts/src/main.nr | 2 +- .../aztec/src/messages/delivery/builder.nr | 7 +- .../messages/delivery/constrained_delivery.nr | 219 ++++++++++++++++++ .../aztec/src/messages/delivery/handshake.nr | 71 +++++- .../aztec/src/messages/delivery/mod.nr | 185 +++++++++++---- .../aztec/src/messages/delivery/tag.nr | 173 ++++++++++++++ .../aztec-nr/aztec/src/messages/logs/mod.nr | 1 - .../aztec-nr/aztec/src/messages/logs/utils.nr | 86 ------- .../invalid_event/snapshots__stderr.snap | 2 +- .../snapshots__expanded.snap | 1 + .../snapshots__expanded.snap | 1 + .../snapshots__expanded.snap | 1 + .../snapshots__expanded.snap | 1 + noir-projects/noir-contracts/Nargo.toml | 1 + .../src/main.nr | 30 ++- .../src/main.nr | 30 ++- .../handshake_registry_contract/src/main.nr | 9 +- .../handshake_registry_contract/src/test.nr | 34 ++- .../Nargo.toml | 11 + .../src/main.nr | 87 +++++++ .../src/test.nr | 88 +++++++ .../test/note_getter_contract/src/main.nr | 6 +- .../test/state_vars_contract/src/main.nr | 10 +- .../test/stateful_test_contract/src/main.nr | 8 +- .../test/test_log_contract/src/main.nr | 8 +- .../crates/types/src/constants.nr | 2 + .../crates/types/src/constants_tests.nr | 15 +- yarn-project/constants/src/constants.gen.ts | 1 + .../end-to-end/src/e2e_2_pxes.test.ts | 5 +- .../src/e2e_constrained_delivery.test.ts | 157 +++++++++++++ .../e2e_pending_note_hashes_contract.test.ts | 35 ++- .../oracle/utility_execution.test.ts | 97 +++++++- .../oracle/utility_execution_oracle.ts | 29 ++- 34 files changed, 1229 insertions(+), 186 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr create mode 100644 noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr delete mode 100644 noir-projects/aztec-nr/aztec/src/messages/logs/utils.nr create mode 100644 noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr create mode 100644 noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr create mode 100644 yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts diff --git a/docs/docs-developers/docs/aztec-nr/framework-description/state_variables.md b/docs/docs-developers/docs/aztec-nr/framework-description/state_variables.md index a308805c88dd..6c15e1b2fedd 100644 --- a/docs/docs-developers/docs/aztec-nr/framework-description/state_variables.md +++ b/docs/docs-developers/docs/aztec-nr/framework-description/state_variables.md @@ -273,7 +273,7 @@ When working with private state variables, many operations return a `NoteMessage #### Delivery Methods Private notes need to be communicated to their recipients so they know the note exists and can use it. The [`NoteMessage`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/note/struct.NoteMessage) wrapper forces you to make an explicit choice about how this happens: - - [`MessageDelivery::onchain_constrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates). **Warning:** Currently [not fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565) - the log's tag is unconstrained. + - [`MessageDelivery::onchain_constrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates). - [`MessageDelivery::onchain_unconstrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Message stored onchain but no guarantees on content - Use when the sender is incentivized to deliver correctly but may not have an offchain channel to the recipient. - [`MessageDelivery::offchain()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Lowest cost, no onchain data - Use when the sender and recipient can communicate and the sender is incentivized to deliver correctly. diff --git a/docs/examples/webapp-tutorial/contracts/src/main.nr b/docs/examples/webapp-tutorial/contracts/src/main.nr index 5a313b45ebb1..9e010b50b503 100644 --- a/docs/examples/webapp-tutorial/contracts/src/main.nr +++ b/docs/examples/webapp-tutorial/contracts/src/main.nr @@ -95,7 +95,7 @@ pub contract PodRacing { .at(game_id) .at(player) .insert(GameRoundNote::new(track1, track2, track3, track4, track5, round, player)) - .deliver(MessageDelivery::onchain_constrained()); + .deliver(MessageDelivery::onchain_unconstrained()); self.enqueue(PodRacing::at(self.context.this_address()).validate_and_play_round( player, diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/builder.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/builder.nr index aacfbcd373e5..d67b3d96652f 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/builder.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/builder.nr @@ -19,8 +19,7 @@ use super::tag_secret_derivation::TagSecretDerivation; /// ## Construction /// /// The fields are private and there is no public constructor: a `MessageDelivery` can only be produced by a -/// [`MessageDeliveryBuilder`] that enforces valid configurations, so invalid field combinations cannot be -/// represented to the consumer. +/// [`MessageDeliveryBuilder`]. pub struct MessageDelivery { mode: DeliveryMode, tag_secret_derivation: TagSecretDerivation, @@ -144,10 +143,6 @@ impl MessageDelivery { /// Delivers the message on-chain, guaranteeing the recipient will receive the correct content. /// - /// >**WARNING**: this delivery mode is [currently NOT fully - /// constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained, - /// meaning a malicious sender could manipulate it to prevent the recipient from finding the message. - /// /// ## Use Cases /// /// This delivery method is suitable for all use cases, since it always works as expected. It is however the most diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr new file mode 100644 index 000000000000..8d82056ae1cd --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr @@ -0,0 +1,219 @@ +//! Sender-side helpers for constrained message delivery. +//! +//! Constrained messages form per-`(sender, recipient, secret)` sequences, each send anchored to the handshake +//! registry at an incrementing index. Two consequences shape the whole flow: sends on one sequence are strictly +//! ordered across transactions (parallel sends collide or fail the predecessor check, while distinct recipients are +//! distinct sequences and parallelize), and batching several sends onto one sequence within a transaction requires +//! an already-committed handshake. +//! +//! See [`constrain_secret`] for how a send is anchored to the registry, + +use crate::context::PrivateContext; +use crate::messages::delivery::OnchainDeliveryMode; +use crate::nullifier::utils::compute_nullifier_existence_request; + +use crate::protocol::{ + abis::function_selector::FunctionSelector, address::AztecAddress, constants::DOM_SEP__CONSTRAINED_MSG_NULLIFIER, + hash::poseidon2_hash_with_separator, traits::ToField, +}; + +// The helper cannot import the handshake registry interface because the registry contract depends on aztec-nr. The +// registry's test suite compares this against its macro-generated `HandshakeRegistry::at(...).method(...).selector` +// value so signature drift fails in tests. +pub global VALIDATE_HANDSHAKE_SELECTOR: FunctionSelector = + comptime { FunctionSelector::from_signature("validate_handshake((Field),(Field),(u8),Field)") }; + +pub(crate) fn constrain_secret_and_emit_nullifier( + context: &mut PrivateContext, + registry: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + secret: Field, + bootstrapped: bool, + index: u32, +) { + constrain_secret( + context, + registry, + sender, + recipient, + secret, + bootstrapped, + index, + ); + context.push_nullifier_unsafe(compute_constrained_msg_nullifier(sender, recipient, secret, index)); +} + +/// Anchors an untrusted `(secret, index)` to the registry before its constrained tag is emitted. +/// +/// - bootstrapped: the secret is the constrained `non_interactive_handshake` return (source of +/// truth), so only its `index == 0` start is asserted. +/// - reuse at index 0: `validate_handshake` binds the oracle-supplied secret to the stored handshake. +/// - reuse at index > 0: the prior sequence nullifier must exist, anchoring back to the index-0 check. +fn constrain_secret( + context: &mut PrivateContext, + registry: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + secret: Field, + bootstrapped: bool, + index: u32, +) { + let caller = context.this_address(); + let mode = OnchainDeliveryMode::onchain_constrained(); + let mode_field = mode.to_field(); + + if bootstrapped { + assert(index == 0, "freshly bootstrapped secret must start at index 0"); + } else if index == 0 { + let _ = context.call_private_function( + registry, + VALIDATE_HANDSHAKE_SELECTOR, + [sender.to_field(), recipient.to_field(), mode_field, secret], + ); + } else { + let prev_nullifier = compute_constrained_msg_nullifier(sender, recipient, secret, index - 1); + context.assert_nullifier_exists(compute_nullifier_existence_request(prev_nullifier, caller)); + } +} + +/// Computes a constrained send's sequence nullifier. +/// +/// Every constrained send emits this nullifier so the next send under the same `(sender, recipient, secret)` sequence +/// can prove its predecessor exists. +pub(crate) fn compute_constrained_msg_nullifier( + sender: AztecAddress, + recipient: AztecAddress, + secret: Field, + index: u32, +) -> Field { + poseidon2_hash_with_separator( + [sender.to_field(), recipient.to_field(), secret, index as Field], + DOM_SEP__CONSTRAINED_MSG_NULLIFIER, + ) +} + +mod test { + use crate::context::PrivateContext; + use crate::hash::hash_args; + use crate::messages::delivery::OnchainDeliveryMode; + use crate::protocol::{address::AztecAddress, hash::compute_siloed_nullifier, traits::{FromField, ToField}}; + use crate::test::helpers::test_environment::TestEnvironment; + use super::{compute_constrained_msg_nullifier, constrain_secret_and_emit_nullifier, VALIDATE_HANDSHAKE_SELECTOR}; + use std::test::OracleMock; + + fn assert_current_nullifier_emitted( + context: &mut PrivateContext, + sender: AztecAddress, + recipient: AztecAddress, + secret: Field, + index: u32, + ) { + assert_eq(context.nullifiers.len(), 1); + assert_eq( + context.nullifiers.get(0).inner.value, + compute_constrained_msg_nullifier(sender, recipient, secret, index), + ); + } + + #[test] + unconstrained fn constrained_helper_emits_current_nullifier() { + let env = TestEnvironment::new(); + let registry = AztecAddress::from_field(1); + let sender = AztecAddress::from_field(2); + let recipient = AztecAddress::from_field(4); + let secret: Field = 1234; + let index: u32 = 0; + + env.private_context(|context| { + constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, true, index); + + assert_current_nullifier_emitted(context, sender, recipient, secret, index); + assert_eq(context.private_call_requests.len(), 0); + assert_eq(context.nullifier_read_requests.len(), 0); + }); + } + + #[test(should_fail_with = "freshly bootstrapped secret must start at index 0")] + unconstrained fn bootstrapped_secret_must_start_at_index_zero() { + let env = TestEnvironment::new(); + let registry = AztecAddress::from_field(1); + let sender = AztecAddress::from_field(2); + let recipient = AztecAddress::from_field(4); + + env.private_context(|context| { + constrain_secret_and_emit_nullifier(context, registry, sender, recipient, 1234, true, 1); + }); + } + + #[test] + unconstrained fn reused_secret_at_index_zero_validates_registry_and_emits_nullifier() { + let env = TestEnvironment::new(); + let registry = AztecAddress::from_field(1); + let sender = AztecAddress::from_field(2); + let recipient = AztecAddress::from_field(4); + let secret: Field = 1234; + let index: u32 = 0; + + env.private_context(|context| { + // The real registry call is covered by integration tests; this unit test only needs a coherent child + // call result so `PrivateContext` records the request. + let child_call_end_counter = (context.side_effect_counter + 1) as Field; + let empty_returns_hash: Field = 0; + let _ = OracleMock::mock("aztec_prv_callPrivateFunction") + .returns([child_call_end_counter, empty_returns_hash]) + .times(1); + + constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, false, index); + + assert_current_nullifier_emitted(context, sender, recipient, secret, index); + assert_eq(context.nullifier_read_requests.len(), 0); + assert_eq(context.private_call_requests.len(), 1); + + let request = context.private_call_requests.get(0); + assert_eq(request.call_context.msg_sender, context.this_address()); + assert_eq(request.call_context.contract_address, registry); + assert_eq(request.call_context.function_selector, VALIDATE_HANDSHAKE_SELECTOR); + assert(!request.call_context.is_static_call); + assert_eq( + request.args_hash, + hash_args([ + sender.to_field(), + recipient.to_field(), + OnchainDeliveryMode::onchain_constrained().to_field(), + secret, + ]), + ); + }); + } + + #[test] + unconstrained fn reused_secret_above_index_zero_reads_previous_nullifier_and_emits_current_nullifier() { + let env = TestEnvironment::new(); + let registry = AztecAddress::from_field(1); + let sender = AztecAddress::from_field(2); + let recipient = AztecAddress::from_field(4); + let secret: Field = 1234; + let index: u32 = 3; + + env.private_context(|context| { + let _ = OracleMock::mock("aztec_prv_isNullifierPending").returns(false).times(1); + + constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, false, index); + + assert_current_nullifier_emitted(context, sender, recipient, secret, index); + assert_eq(context.private_call_requests.len(), 0); + assert_eq(context.nullifier_read_requests.len(), 1); + + let read_request = context.nullifier_read_requests.get(0); + assert_eq(read_request.contract_address, AztecAddress::zero()); + assert_eq( + read_request.inner.inner, + compute_siloed_nullifier( + context.this_address(), + compute_constrained_msg_nullifier(sender, recipient, secret, index - 1), + ), + ); + }); + } +} diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr index ae549f0cda77..d6c26fcd208a 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr @@ -3,6 +3,7 @@ use crate::protocol::point::EmbeddedCurvePoint; use crate::protocol::traits::{Deserialize, Serialize}; use crate::{ + context::PrivateContext, ephemeral::EphemeralArray, messages::processing::provided_secret::ProvidedSecret, oracle::{call_utility_function::call_utility_function, shared_secret::get_shared_secrets}, @@ -36,6 +37,69 @@ global HANDSHAKE_EPH_PKS_SLOT: Field = sha256_to_field("AZTEC_NR::HANDSHAKE_EPH_ global HANDSHAKE_MODES_SLOT: Field = sha256_to_field("AZTEC_NR::HANDSHAKE_MODES_SLOT".as_bytes()); +// The helper cannot import the handshake registry interface because the registry contract depends on aztec-nr. These +// selector constants pin the registry ABI surface this library calls. The registry's test suite compares them against +// its macro-generated `HandshakeRegistry::at(...).method(...).selector` values so signature drift fails in tests. +pub global GET_APP_SILOED_SECRET_SELECTOR: FunctionSelector = + comptime { FunctionSelector::from_signature("get_app_siloed_secret((Field),(Field),(u8))") }; +pub global NON_INTERACTIVE_HANDSHAKE_SELECTOR: FunctionSelector = + comptime { FunctionSelector::from_signature("non_interactive_handshake((Field),(Field),(u8))") }; +pub global GET_HANDSHAKES_SELECTOR: FunctionSelector = + comptime { FunctionSelector::from_signature("get_handshakes((Field),u32)") }; + +/// Resolves the app-siloed handshake secret, bootstrapping when none exists. +/// +/// Resolves the secret for `(sender, recipient, mode)`, creating it via the registry's `non_interactive_handshake` +/// when no handshake exists yet. +/// +/// Returns `(secret, bootstrapped)`, where `bootstrapped` is true when this call created the handshake. +/// +/// ## Batching +/// +/// The reuse-vs-bootstrap decision is a utility call that reads committed state. If no handshake exists yet, +/// multiple sends in the same transaction can't see each other's pending bootstrap, so each one creates its own +/// handshake (a fresh secret on a separate sequence) rather than reusing one. A brand-new recipient therefore needs +/// one landed transaction to establish the handshake before further sends can be batched onto it. +pub(crate) fn get_or_create_app_siloed_handshake_secret( + context: &mut PrivateContext, + registry: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + mode: OnchainDeliveryMode, +) -> (Field, bool) { + let mode_field = mode.to_field(); + + // Safety: the response only selects which path runs. On `None` we bootstrap via `non_interactive_handshake`, + // whose constrained return value is the secret, so a forged empty response cannot fabricate one; it can only + // trigger an unnecessary re-handshake that replaces the registry note. The caller must constrain the returned + // `(secret, bootstrapped)` pair against the selected tagging index before emitting a handshake-derived tag. + let maybe_secret: Option = unsafe { + let returns = call_utility_function( + registry, + GET_APP_SILOED_SECRET_SELECTOR, + [sender.to_field(), recipient.to_field(), mode_field], + ); + Deserialize::deserialize(returns) + }; + + maybe_secret.map(|secret| (secret, false)).unwrap_or_else(|| { + // Bootstrap: no handshake exists yet. The registry inserts a fresh note and returns the app-siloed + // secret to the caller. The constrained return is the source of truth for the secret, so no separate + // `validate_handshake` is needed by constrained delivery. + // TODO(F-660): dispatch to `perform_handshake(sender, recipient, handshake_type)` once interactive + // handshakes are supported. + let secret: Field = context + .call_private_function( + registry, + NON_INTERACTIVE_HANDSHAKE_SELECTOR, + [sender.to_field(), recipient.to_field(), mode_field], + ) + .get_preimage(); + + (secret, true) + }) +} + /// Fetches discovered handshakes from the HandshakeRegistry and derives app-siloed tagging secrets for each, /// returning them so that [`get_pending_tagged_logs`](crate::oracle::message_processing::get_pending_tagged_logs) /// searches for logs tagged with these secrets. @@ -75,9 +139,12 @@ pub(crate) unconstrained fn get_handshake_secrets( /// Calls the HandshakeRegistry's `get_handshakes` utility function and deserializes the response. unconstrained fn fetch_handshake_page(recipient: AztecAddress, page_offset: u32) -> HandshakePage { - let selector = comptime { FunctionSelector::from_signature("get_handshakes((Field),u32)") }; let args: [Field; 2] = [recipient.to_field(), page_offset as Field]; - let response = call_utility_function(STANDARD_HANDSHAKE_REGISTRY_ADDRESS, selector, args); + let response = call_utility_function( + STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + GET_HANDSHAKES_SELECTOR, + args, + ); HandshakePage::deserialize(response) } diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr index 57b47837846f..d009fb9d9402 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr @@ -1,20 +1,23 @@ mod builder; mod mode; +mod tag; mod tag_secret_derivation; +pub mod constrained_delivery; pub mod handshake; use crate::{ context::PrivateContext, messages::{ encryption::{aes128::AES128, message_encryption::MessageEncryption}, - logs::utils::compute_discovery_tag, offchain_messages::deliver_offchain_message, }, + oracle::notes::get_sender_for_tags, utils::remove_constraints::remove_constraints_if, }; -use crate::protocol::{address::AztecAddress, constants::DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG, hash::compute_log_tag}; +use crate::protocol::address::AztecAddress; use mode::DeliveryMode; +use tag::derive_log_tag; use tag_secret_derivation::TagSecretDerivation; pub use builder::{ @@ -32,7 +35,7 @@ pub use mode::OnchainDeliveryMode; /// note hash's side effect counter is passed and constrained tagging is not in use, then the log will be squashed /// alongside the note should its nullifier be emitted in the current transaction. Constrained-tagged logs are not tied /// to note squashing, because recipient discovery scans those tags sequentially and a removed log would break the -/// per-secret index chain. +/// per-secret index sequence. /// /// ## Privacy /// @@ -58,29 +61,52 @@ where mode.assert_is_constant(); let deliver_as_offchain_message = mode == DeliveryMode::offchain(); - let is_constrained = mode == DeliveryMode::onchain_constrained(); + assert_constant(deliver_as_offchain_message); let tag_secret_derivation = delivery.tag_secret_derivation(); tag_secret_derivation.assert_is_constant(); - if !deliver_as_offchain_message { - let resolved_tag_secret_derivation = resolve_tag_secret_derivation(mode, tag_secret_derivation); - resolved_tag_secret_derivation.assert_is_constant(); + let resolved_tag_secret_derivation = resolve_tag_secret_derivation(mode, tag_secret_derivation); + resolved_tag_secret_derivation.assert_is_constant(); - if is_constrained { - // Constrained tagging derives the tag from a handshake-registry secret and emits a chain nullifier. - std::static_assert( - resolved_tag_secret_derivation == TagSecretDerivation::non_interactive_handshake(), - "constrained delivery requires non-interactive handshake tag derivation", - ); - } else { - // Unconstrained handshake-origin delivery is not yet implemented; see F-698. - std::static_assert( - resolved_tag_secret_derivation == TagSecretDerivation::address_secret(), - "unconstrained handshake delivery not yet implemented (F-698)", - ); - } + let sender_override = delivery.sender_override(); + + if deliver_as_offchain_message { + let contract_address = context.this_address(); + let ciphertext = remove_constraints_if( + true, + || AES128::encrypt(encode_into_message_plaintext(), recipient, contract_address), + ); + + deliver_offchain_message(ciphertext, recipient); + } else { + do_onchain_private_message_delivery( + context, + encode_into_message_plaintext, + maybe_note_hash_counter, + recipient, + mode, + resolved_tag_secret_derivation, + sender_override, + ); } +} + +fn do_onchain_private_message_delivery( + context: &mut PrivateContext, + encode_into_message_plaintext: fn[Env]() -> [Field; MESSAGE_PLAINTEXT_LEN], + maybe_note_hash_counter: Option, + recipient: AztecAddress, + mode: DeliveryMode, + resolved_tag_secret_derivation: TagSecretDerivation, + sender_override: Option, +) { + let is_constrained = mode == DeliveryMode::onchain_constrained(); + assert_constant(is_constrained); + + assert_valid_tag_derivation_for_mode(mode, resolved_tag_secret_derivation); + let onchain_mode = to_onchain_delivery_mode(mode); + let sender = resolve_sender(sender_override); let contract_address = context.this_address(); @@ -89,33 +115,67 @@ where || AES128::encrypt(encode_into_message_plaintext(), recipient, contract_address), ); - if deliver_as_offchain_message { - deliver_offchain_message(ciphertext, recipient); + let log_tag = derive_log_tag( + context, + onchain_mode, + resolved_tag_secret_derivation, + sender, + recipient, + ); + + // This value must be constant to avoid predicating the context calls below, which might result in + // the context's arrays having unknown compile time write indices and hence dramatically increasing constraints + // when accessing them. In practice this restriction is not a problem as we always know at compile time whether + // we're emitting a note or non-note message. + assert_constant(maybe_note_hash_counter.is_some()); + + // Constrained-tagged logs must not be squashed alongside the note: the recipient discovers them by scanning + // the per-secret tag sequence, so removing a log would break the index sequence. + let squashable_note_log = maybe_note_hash_counter.is_some() & !is_constrained; + + let log = BoundedVec::from_array(ciphertext); + if squashable_note_log { + // We associate the log with the note's side effect counter, so that if the note ends up being squashed in + // the current transaction, the log will be removed as well. + context.emit_raw_note_log_unsafe(log_tag, log, maybe_note_hash_counter.unwrap()); } else { - // TODO(#14565): constrained tagging is not yet wired up. The tag-secret derivation is validated, but the tag is - // mocked with the wallet-driven unconstrained derivation so the builder API can land before the constrained - // helpers. - let discovery_tag = compute_discovery_tag(recipient, delivery.sender_override()); - let log_tag = compute_log_tag(discovery_tag, DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG); - - // We forbid this value not being constant to avoid predicating the context calls below, which might result in - // the context's arrays having unknown compile time write indices and hence dramatically increasing constraints - // when accessing them. In practice this restriction is not a problem as we always know at compile time whether - // we're emitting a note or non-note message. - assert_constant(maybe_note_hash_counter.is_some()); - - // Constrained-tagged logs must not be squashed alongside the note: the recipient discovers them by scanning - // the per-secret tag sequence, so removing a log would break the index chain. - let squashable_note_log = maybe_note_hash_counter.is_some() & !is_constrained; - - let log = BoundedVec::from_array(ciphertext); - if squashable_note_log { - // We associate the log with the note's side effect counter, so that if the note ends up being squashed in - // the current transaction, the log will be removed as well. - context.emit_raw_note_log_unsafe(log_tag, log, maybe_note_hash_counter.unwrap()); - } else { - context.emit_private_log_unsafe(log_tag, log); - } + context.emit_private_log_unsafe(log_tag, log); + } +} + +fn assert_valid_tag_derivation_for_mode(mode: DeliveryMode, tag_secret_derivation: TagSecretDerivation) { + if mode == DeliveryMode::onchain_constrained() { + std::static_assert( + tag_secret_derivation == TagSecretDerivation::non_interactive_handshake(), + "constrained delivery requires non-interactive handshake tag derivation", + ); + } else { + // Unconstrained handshake-origin delivery is not yet implemented; see F-698. + std::static_assert( + tag_secret_derivation == TagSecretDerivation::address_secret(), + "unconstrained handshake delivery not yet implemented (F-698)", + ); + } +} + +fn resolve_sender(sender_override: Option) -> AztecAddress { + // Safety: tag senders are unconstrained; the sender comes from the builder override or the wallet-provided + // default tag sender, which must be an account the PXE controls so it can own and recover the resulting + // tagging/handshake state. + unsafe { + sender_override.unwrap_or_else(|| { + get_sender_for_tags().expect( + f"Sender for tags is not set when emitting a private log and no override is set. Ensure the wallet provides a default sender.", + ) + }) + } +} + +fn to_onchain_delivery_mode(mode: DeliveryMode) -> OnchainDeliveryMode { + if mode == DeliveryMode::onchain_constrained() { + OnchainDeliveryMode::onchain_constrained() + } else { + OnchainDeliveryMode::onchain_unconstrained() } } @@ -124,6 +184,9 @@ fn resolve_tag_secret_derivation( tag_secret_derivation: TagSecretDerivation, ) -> TagSecretDerivation { if tag_secret_derivation == TagSecretDerivation::wallet_default() { + // TODO(F-699): Resolve wallet-default by first reusing an existing handshake for `(sender, recipient, mode)` + // without consulting wallet policy. If none exists, consult the wallet delivery privacy preference before + // choosing address-secret delivery or creating a non-interactive handshake. if mode == DeliveryMode::onchain_constrained() { TagSecretDerivation::non_interactive_handshake() } else { @@ -135,7 +198,10 @@ fn resolve_tag_secret_derivation( } mod test { - use super::{DeliveryMode, resolve_tag_secret_derivation, TagSecretDerivation}; + use crate::protocol::{address::AztecAddress, traits::FromField}; + use crate::test::helpers::test_environment::TestEnvironment; + use super::{DeliveryMode, resolve_sender, resolve_tag_secret_derivation, TagSecretDerivation}; + use std::test::OracleMock; #[test] fn wallet_default_resolves_for_delivery_mode() { @@ -172,4 +238,29 @@ mod test { == TagSecretDerivation::non_interactive_handshake(), ); } + + #[test(should_fail_with = "Sender for tags is not set")] + unconstrained fn sender_resolution_requires_wallet_default_sender() { + let env = TestEnvironment::new(); + let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::::none()); + + env.private_context(|_context| { let _ = resolve_sender(Option::none()); }); + } + + #[test] + unconstrained fn sender_resolution_uses_override() { + let env = TestEnvironment::new(); + let sender = AztecAddress::from_field(1); + + env.private_context(|_context| { assert_eq(resolve_sender(Option::some(sender)), sender); }); + } + + #[test] + unconstrained fn sender_resolution_uses_wallet_default_sender() { + let env = TestEnvironment::new(); + let sender = AztecAddress::from_field(1); + let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::some(sender)); + + env.private_context(|_context| { assert_eq(resolve_sender(Option::none()), sender); }); + } } diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr new file mode 100644 index 000000000000..69e32c293d5b --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr @@ -0,0 +1,173 @@ +//! Discovery tag derivation for on-chain message delivery. +//! +//! Constrained derivation also emits the sequence nullifier; see [`super::constrained_delivery`]. + +use crate::context::PrivateContext; +use crate::messages::delivery::{ + constrained_delivery::constrain_secret_and_emit_nullifier, handshake::get_or_create_app_siloed_handshake_secret, + OnchainDeliveryMode, +}; +use crate::oracle::notes::{get_app_tagging_secret, get_next_tagging_index}; +use crate::oracle::random::random; +use crate::protocol::{ + address::AztecAddress, + constants::{DOM_SEP__CONSTRAINED_MSG_LOG_TAG, DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG}, + hash::{compute_log_tag, poseidon2_hash}, +}; +use crate::standard_addresses::STANDARD_HANDSHAKE_REGISTRY_ADDRESS; +use super::tag_secret_derivation::TagSecretDerivation; + +/// Derives the discovery log tag for an on-chain delivery; the handshake path also emits the sequence +/// nullifier (see [`derive_handshake_log_tag`]). +pub(crate) fn derive_log_tag( + context: &mut PrivateContext, + mode: OnchainDeliveryMode, + resolved_tag_secret_derivation: TagSecretDerivation, + sender: AztecAddress, + recipient: AztecAddress, +) -> Field { + if resolved_tag_secret_derivation == TagSecretDerivation::address_secret() { + derive_address_secret_log_tag(sender, recipient, mode) + } else { + derive_handshake_log_tag(context, sender, recipient, mode) + } +} + +fn derive_address_secret_log_tag(sender: AztecAddress, recipient: AztecAddress, mode: OnchainDeliveryMode) -> Field { + // Safety: address-derived delivery is unconstrained. The oracle returns `None` for an invalid recipient (one + // whose address is not on the curve). To prevent king-of-the-hill attacks we emit a random tag instead of + // failing: the log keeps its expected shape, even though no recipient will ever be able to discover the note. + unsafe { + get_app_tagging_secret(sender, recipient).map_or_else( + || compute_log_tag(random(), tag_domain_separator(mode)), + |secret| { + let index = get_next_tagging_index(secret, mode); + tag_from_secret_and_index(secret, index, mode) + }, + ) + } +} + +fn derive_handshake_log_tag( + context: &mut PrivateContext, + sender: AztecAddress, + recipient: AztecAddress, + mode: OnchainDeliveryMode, +) -> Field { + let (secret, bootstrapped) = get_or_create_app_siloed_handshake_secret( + context, + STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + sender, + recipient, + mode, + ); + + // Safety: the returned index is untrusted and is constrained before the tag is emitted. + let index = unsafe { get_next_tagging_index(secret, mode) }; + constrain_secret_and_emit_nullifier( + context, + STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + sender, + recipient, + secret, + bootstrapped, + index, + ); + + tag_from_secret_and_index(secret, index, mode) +} + +fn tag_domain_separator(mode: OnchainDeliveryMode) -> u32 { + if mode == OnchainDeliveryMode::onchain_constrained() { + DOM_SEP__CONSTRAINED_MSG_LOG_TAG + } else { + DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG + } +} + +fn tag_from_secret_and_index(secret: Field, index: u32, mode: OnchainDeliveryMode) -> Field { + compute_log_tag( + poseidon2_hash([secret, index as Field]), + tag_domain_separator(mode), + ) +} + +mod test { + use crate::protocol::{ + address::AztecAddress, + constants::{DOM_SEP__CONSTRAINED_MSG_LOG_TAG, DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG}, + hash::{compute_log_tag, poseidon2_hash}, + traits::FromField, + }; + use crate::test::helpers::test_environment::TestEnvironment; + use super::{derive_log_tag, OnchainDeliveryMode, tag_from_secret_and_index, TagSecretDerivation}; + use std::test::OracleMock; + + #[test] + fn tag_from_secret_and_index_uses_mode_domain_separator() { + let secret: Field = 1234; + let index: u32 = 7; + let raw_tag = poseidon2_hash([secret, index as Field]); + + assert_eq( + tag_from_secret_and_index(secret, index, OnchainDeliveryMode::onchain_unconstrained()), + compute_log_tag(raw_tag, DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG), + ); + assert_eq( + tag_from_secret_and_index(secret, index, OnchainDeliveryMode::onchain_constrained()), + compute_log_tag(raw_tag, DOM_SEP__CONSTRAINED_MSG_LOG_TAG), + ); + } + + #[test] + unconstrained fn address_secret_tag_uses_secret_index_and_mode_domain() { + let env = TestEnvironment::new(); + let sender = AztecAddress::from_field(1); + let recipient = AztecAddress::from_field(2); + let secret: Field = 7; + let index: u32 = 3; + let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::some(sender)); + let _ = OracleMock::mock("aztec_prv_getAppTaggingSecret").returns(Option::some(secret)); + let _ = OracleMock::mock("aztec_prv_getNextTaggingIndex").returns(index); + + env.private_context(|context| { + assert_eq( + derive_log_tag( + context, + OnchainDeliveryMode::onchain_unconstrained(), + TagSecretDerivation::address_secret(), + sender, + recipient, + ), + compute_log_tag( + poseidon2_hash([secret, index as Field]), + DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG, + ), + ); + }); + } + + #[test] + unconstrained fn address_secret_tag_uses_random_tag_for_invalid_recipient() { + let env = TestEnvironment::new(); + let sender = AztecAddress::from_field(1); + let recipient = AztecAddress::from_field(2); + let random_tag = 42; + let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::some(sender)); + let _ = OracleMock::mock("aztec_prv_getAppTaggingSecret").returns(Option::::none()); + let _ = OracleMock::mock("aztec_misc_getRandomField").returns(random_tag); + + env.private_context(|context| { + assert_eq( + derive_log_tag( + context, + OnchainDeliveryMode::onchain_unconstrained(), + TagSecretDerivation::address_secret(), + sender, + recipient, + ), + compute_log_tag(random_tag, DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG), + ); + }); + } +} diff --git a/noir-projects/aztec-nr/aztec/src/messages/logs/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/logs/mod.nr index bbfd03bd051d..9b0b0f1c683b 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/logs/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/logs/mod.nr @@ -2,4 +2,3 @@ pub mod arithmetic_generics_utils; pub mod event; pub mod note; pub mod partial_note; -pub mod utils; diff --git a/noir-projects/aztec-nr/aztec/src/messages/logs/utils.nr b/noir-projects/aztec-nr/aztec/src/messages/logs/utils.nr deleted file mode 100644 index d5a74d37a219..000000000000 --- a/noir-projects/aztec-nr/aztec/src/messages/logs/utils.nr +++ /dev/null @@ -1,86 +0,0 @@ -use crate::messages::delivery::MessageDelivery; -use crate::oracle::{notes::{get_app_tagging_secret, get_next_tagging_index, get_sender_for_tags}, random::random}; -use crate::protocol::{address::AztecAddress, hash::poseidon2_hash}; - -// TODO(#14565): Add constrained tagging -/// Returns the next discovery tag for a private log sent to `recipient`. -/// -/// Private logs are encrypted, so the recipient cannot tell which logs are meant for it just by looking at them. -/// To solve this, sender and recipient derive a shared secret from their keys, and from that secret they produce a -/// sequence of one-time tags (tag_0, tag_1, ...). The recipient scans for these tags because it can compute the same -/// sequence. This function returns the next raw (not domain-separated) tag in the sequence. -pub(crate) fn compute_discovery_tag(recipient: AztecAddress, sender_override: Option) -> Field { - // Safety: we assume that the sender wants for the recipient to find the tagged note, and therefore that they will - // cooperate and use the correct tag. Usage of a bad tag will result in the recipient not being able to find the - // note automatically. - unsafe { - let sender = sender_override.unwrap_or_else(|| { - get_sender_for_tags().expect( - f"Sender for tags is not set when emitting a private log and no override is set. Ensure the wallet provides a default sender.", - ) - }); - get_app_tagging_secret(sender, recipient).map_or_else( - || { - // The oracle returns `None` for invalid recipients. To prevent king-of-the-hill attacks, emit a random - // tag instead of failing: the log shape is preserved, even though no recipient can discover the note. - random() - }, - |secret| { - let index = get_next_tagging_index(secret, MessageDelivery::onchain_unconstrained()); - poseidon2_hash([secret, index as Field]) - }, - ) - } -} - -mod test { - use crate::protocol::{address::AztecAddress, hash::poseidon2_hash, traits::FromField}; - use crate::test::helpers::test_environment::TestEnvironment; - use super::compute_discovery_tag; - use std::test::OracleMock; - - #[test(should_fail_with = "Sender for tags is not set")] - unconstrained fn no_tag_sender() { - let recipient = AztecAddress::from_field(2); - let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::::none()); - let _ = compute_discovery_tag(recipient, Option::none()); - } - - #[test] - unconstrained fn computes_tag_from_secret_and_index() { - let sender = AztecAddress::from_field(1); - let recipient = AztecAddress::from_field(2); - let secret: Field = 7; - let index: u32 = 3; - let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::some(sender)); - let _ = OracleMock::mock("aztec_prv_getAppTaggingSecret").returns(Option::some(secret)); - let _ = OracleMock::mock("aztec_prv_getNextTaggingIndex").returns(index); - assert_eq(compute_discovery_tag(recipient, Option::none()), poseidon2_hash([secret, index as Field])); - } - - #[test] - unconstrained fn uses_random_tag_for_invalid_recipient() { - let sender = AztecAddress::from_field(1); - let recipient = AztecAddress::from_field(2); - let random_tag = 42; - let _ = OracleMock::mock("aztec_prv_getSenderForTags").returns(Option::some(sender)); - let _ = OracleMock::mock("aztec_prv_getAppTaggingSecret").returns(Option::::none()); - let _ = OracleMock::mock("aztec_misc_getRandomField").returns(random_tag); - assert_eq(compute_discovery_tag(recipient, Option::none()), random_tag); - } - - #[test] - unconstrained fn does_not_fail_on_an_invalid_recipient() { - let mut env = TestEnvironment::new(); - - let sender = env.create_light_account(); - - env.private_context(|_context| { - // The recipient is an invalid address - let recipient = AztecAddress { inner: 3 }; - assert(!recipient.is_valid()); - - let _ = compute_discovery_tag(recipient, Option::some(sender)); - }); - } -} diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_event/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_event/snapshots__stderr.snap index 4a9d0dca64d0..73a27353c96e 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_event/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_event/snapshots__stderr.snap @@ -14,7 +14,7 @@ error: event's serialized length exceeds the maximum allowed for private events = Call stack: 1: remove_constraints at /noir-projects/aztec-nr/aztec/src/utils/remove_constraints.nr:: - 2: do_private_message_delivery + 2: do_onchain_private_message_delivery at /noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr:: 3: EventMessage::deliver_to at /noir-projects/aztec-nr/aztec/src/event/event_message.nr:: diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap index 32db6054f5b6..c219e2555419 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap index b04644838350..88315e0d0378 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap index 88d4e8960623..3df63f3bf259 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap index d2b1886ef0a6..5a84c91f96e3 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index c4309c321917..951b84cfa13e 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -46,6 +46,7 @@ members = [ "contracts/test/benchmarking_contract", "contracts/test/calldata_limit_test_contract", "contracts/test/child_contract", + "contracts/test/constrained_delivery_test_contract", "contracts/test/counter/counter_contract", "contracts/test/custom_message_contract", "contracts/test/custom_sync_state_contract", diff --git a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr index 0544820909ac..6a354765ba73 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_ecdsa_account_contract/src/main.nr @@ -27,9 +27,9 @@ pub contract SimulatedEcdsaAccount { // Does NOT use #[initializer] so that the macro does not inject // assert_initialization_matches_address_preimage_private, which would fail during kernelless // simulation because the stub instance has a different initialization hash than the real account. - // Emits the same shape of side effects as the real constructor (one nullifier for - // SinglePrivateImmutable initialization, one note hash for the key note, and one private - // log tied to that note hash) so that gas estimation produces accurate results. + // Emits side effects matching the *shape* of the real EcdsaKAccount / EcdsaRAccount constructor (the + // key-note storage plus the constrained-delivery handshake bootstrap it triggers) so that kernelless gas + // estimation produces accurate results. See the body for the per-effect breakdown and the coupling this creates. #[external("private")] fn constructor(_signing_pub_key_x: [u8; 32], _signing_pub_key_y: [u8; 32]) { // Safety: Random seeds are only used to produce dummy side effects that match the shape of @@ -56,6 +56,30 @@ pub contract SimulatedEcdsaAccount { BoundedVec::from_array(dummy_log), self.context.side_effect_counter, ); + + // The real constructor delivers the key note with `MessageDelivery::onchain_constrained()`, and a + // first send bootstraps a handshake: a nested call to `HandshakeRegistry::non_interactive_handshake` + // inserts a handshake note (note hash + initialization nullifier) and emits two logs (the note-delivery + // log and the recipient discovery log), and the constrained send emits a chain nullifier. We reproduce + // only the *shape* of those side effects so gas estimation stays accurate without running the real + // handshake. + // + // This couples the stub to constrained delivery's emitted shape: if the handshake bootstrap or + // chain-nullifier emission changes, update these dummies to match or the gas-parity assertion in + // e2e_kernelless_simulation.test.ts will fail. + + // Handshake note hash and its initialization nullifier. + self.context.push_note_hash(seed + 4); + self.context.push_nullifier_unsafe(seed + 5); + + // Chain nullifier emitted by the constrained send. + self.context.push_nullifier_unsafe(seed + 6); + + // Handshake note-delivery log and recipient discovery log. + let dummy_handshake_note_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 7; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_private_log_unsafe(seed + 8, BoundedVec::from_array(dummy_handshake_note_log)); + let dummy_handshake_discovery_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 9; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_private_log_unsafe(seed + 10, BoundedVec::from_array(dummy_handshake_discovery_log)); } // @dev: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts diff --git a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr index 9d3959ed947f..daa4403506c9 100644 --- a/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/account/simulated_schnorr_account_contract/src/main.nr @@ -29,9 +29,9 @@ pub contract SimulatedSchnorrAccount { // Does NOT use #[initializer] so that the macro does not inject // assert_initialization_matches_address_preimage_private, which would fail during kernelless // simulation because the stub instance has a different initialization hash than the real account. - // Emits the same shape of side effects as the real constructor (one nullifier for - // SinglePrivateImmutable initialization, one note hash for the key note, and one private - // log tied to that note hash) so that gas estimation produces accurate results. + // Emits side effects matching the *shape* of the real SchnorrAccount constructor (the key-note storage + // plus the constrained-delivery handshake bootstrap it triggers) so that kernelless gas estimation + // produces accurate results. See the body for the per-effect breakdown and the coupling this creates. #[external("private")] fn constructor(_signing_pub_key_x: Field, _signing_pub_key_y: Field) { // Safety: Random seeds are only used to produce dummy side effects that match the shape of @@ -58,6 +58,30 @@ pub contract SimulatedSchnorrAccount { BoundedVec::from_array(dummy_log), self.context.side_effect_counter, ); + + // The real constructor delivers the key note with `MessageDelivery::onchain_constrained()`, and a + // first send bootstraps a handshake: a nested call to `HandshakeRegistry::non_interactive_handshake` + // inserts a handshake note (note hash + initialization nullifier) and emits two logs (the note-delivery + // log and the recipient discovery log), and the constrained send emits a chain nullifier. We reproduce + // only the shape of those side effects so gas estimation stays accurate without running the real + // handshake. + // + // This couples the stub to constrained delivery's emitted shape: if the handshake bootstrap or + // chain-nullifier emission changes, update these dummies to match or the gas-parity assertion in + // e2e_kernelless_simulation.test.ts will fail. + + // Handshake note hash and its initialization nullifier. + self.context.push_note_hash(seed + 4); + self.context.push_nullifier_unsafe(seed + 5); + + // Chain nullifier emitted by the constrained send. + self.context.push_nullifier_unsafe(seed + 6); + + // Handshake note-delivery log and recipient discovery log. + let dummy_handshake_note_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 7; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_private_log_unsafe(seed + 8, BoundedVec::from_array(dummy_handshake_note_log)); + let dummy_handshake_discovery_log: [Field; MESSAGE_CIPHERTEXT_LEN] = [seed + 9; MESSAGE_CIPHERTEXT_LEN]; + self.context.emit_private_log_unsafe(seed + 10, BoundedVec::from_array(dummy_handshake_discovery_log)); } // @dev: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts diff --git a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr index 7157b985dded..08d000906eb9 100644 --- a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr @@ -17,6 +17,9 @@ global NON_INTERACTIVE_HANDSHAKE: u8 = 1; /// [`HandshakeRegistry::get_app_siloed_secret`] offchain for an existing handshake, and use /// [`HandshakeRegistry::validate_handshake`] to check an app-siloed secret against the current stored handshake. The /// private surfaces silo against `msg_sender()`, so a contract can only obtain or validate secrets siloed to itself. +/// Re-handshaking does not revoke already-started constrained-delivery sequences; it only replaces the registry note +/// used +/// for [validation][`HandshakeRegistry::validate_handshake`]. /// /// Currently only implements the non-interactive flow (see [`HandshakeRegistry::non_interactive_handshake`]). #[aztec(::aztec::macros::AztecConfig::new().custom_sync_state(crate::handshake_registry_sync))] @@ -39,7 +42,7 @@ pub contract HandshakeRegistry { #[storage] struct Storage { /// One current [`HandshakeNote`] per `(recipient, sender, mode)` tuple. Re-handshaking for the same tuple - /// replaces the prior sender-owned note, so only the latest handshake remains valid for that mode. + /// replaces the registry note; [`HandshakeRegistry::validate_handshake`] accepts only the latest note. handshakes: Map, Context>, Context>, Context>, } @@ -53,7 +56,7 @@ pub contract HandshakeRegistry { /// Generates a fresh ephemeral key pair `(eph_sk, eph_pk)`, computes the raw ECDH shared secret point /// `S = eph_sk * recipient_address_point`, and produces three effects: /// - /// 1. Inserts or replaces a [`HandshakeNote`] owned by `sender` for `mode`, holding the raw point `S`. + /// 1. Inserts or replaces the current [`HandshakeNote`] owned by `sender` for `mode`, holding the raw point `S`. /// 2. Emits an encrypted private log under a recipient-keyed tag with payload `[eph_pk.x, mode]`. The /// recipient discovers handshakes addressed to them by scanning their tag and recovers `S` from `eph_pk` /// via their own ECDH (`recipient_isk * eph_pk`). `eph_pk.y` is fixed positive by the @@ -103,7 +106,7 @@ pub contract HandshakeRegistry { /// the caller (`msg_sender()`). /// /// Apps that receive an `app_siloed_secret` from an untrusted source call this once to validate that secret - /// against the registry's stored handshake. + /// against the registry's latest stored handshake. /// /// # Panics /// If `mode` is not a recognized delivery mode. diff --git a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr index b87b85b422b5..092dc0c2bd5f 100644 --- a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr @@ -1,7 +1,15 @@ use crate::HandshakeRegistry; use aztec::{ - messages::delivery::{handshake::MAX_HANDSHAKES_PER_PAGE, MessageDelivery, OnchainDeliveryMode}, + messages::delivery::{ + constrained_delivery::VALIDATE_HANDSHAKE_SELECTOR, + handshake::{ + GET_APP_SILOED_SECRET_SELECTOR, GET_HANDSHAKES_SELECTOR, MAX_HANDSHAKES_PER_PAGE, + NON_INTERACTIVE_HANDSHAKE_SELECTOR, + }, + MessageDelivery, + OnchainDeliveryMode, + }, oracle::shared_secret::get_shared_secret, protocol::{ address::AztecAddress, @@ -41,6 +49,30 @@ unconstrained fn setup_with_two_recipients() -> (TestEnvironment, AztecAddress, (env, registry_address, sender, recipient_a, recipient_b) } +// The constrained-delivery helpers in aztec-nr cannot import this contract because the contract depends on aztec-nr. +// Assert their selector constants match this contract's macro-generated interface so a signature change on either side +// fails here instead of silently drifting. +#[test] +unconstrained fn selectors_match_the_constrained_delivery_helper() { + let registry = HandshakeRegistry::at(AztecAddress::from_field(1)); + let sender = AztecAddress::from_field(2); + let recipient = AztecAddress::from_field(3); + + assert_eq( + registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED).selector, + GET_APP_SILOED_SECRET_SELECTOR, + ); + assert_eq( + registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED).selector, + NON_INTERACTIVE_HANDSHAKE_SELECTOR, + ); + assert_eq( + registry.validate_handshake(sender, recipient, ONCHAIN_CONSTRAINED, 0).selector, + VALIDATE_HANDSHAKE_SELECTOR, + ); + assert_eq(registry.get_handshakes(recipient, 0).selector, GET_HANDSHAKES_SELECTOR); +} + #[test] unconstrained fn non_interactive_handshake_stores_handshake_for_sender_and_recipient() { let (env, registry_address, sender, _, recipient) = setup(); diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/Nargo.toml new file mode 100644 index 000000000000..6c29997b4b4b --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/Nargo.toml @@ -0,0 +1,11 @@ +[package] +name = "constrained_delivery_test_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } +balance_set = { path = "../../../../aztec-nr/balance-set" } +field_note = { path = "../../../../aztec-nr/field-note" } +handshake_registry_contract = { path = "../../standard/handshake_registry_contract" } diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr new file mode 100644 index 000000000000..b52c3e96d146 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr @@ -0,0 +1,87 @@ +//! Thin wrappers around constrained delivery for TXE tests. +use aztec::macros::aztec; + +mod test; + +#[aztec] +pub contract ConstrainedDeliveryTest { + use aztec::{ + macros::{events::event, functions::external, storage::storage}, + messages::delivery::{MessageDelivery, OnchainDeliveryMode}, + oracle::notes::get_next_tagging_index, + protocol::{ + address::AztecAddress, + constants::DOM_SEP__CONSTRAINED_MSG_LOG_TAG, + hash::{compute_log_tag, poseidon2_hash}, + }, + standard_addresses::STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + state_vars::{Owned, PrivateSet}, + }; + use balance_set::BalanceSet; + use field_note::FieldNote; + use handshake_registry_contract::HandshakeRegistry; + + #[event] + struct DeliveryEvent { + value: Field, + } + + #[storage] + struct Storage { + notes: Owned, Context>, + balances: Owned, Context>, + } + + #[external("private")] + fn next_index_for_secret(secret: Field) -> u32 { + // Safety: test-only observation of the index the PXE hands out next for this secret. + unsafe { + get_next_tagging_index(secret, MessageDelivery::onchain_constrained()) + } + } + + #[external("private")] + fn emit_note(recipient: AztecAddress) { + self.storage.notes.at(recipient).insert(FieldNote { value: 1 }).deliver(MessageDelivery::onchain_constrained()); + } + + #[external("private")] + fn emit_maybe_note(recipient: AztecAddress) { + self.storage.balances.at(recipient).add(1).deliver_to(recipient, MessageDelivery::onchain_constrained()); + } + + #[external("private")] + fn emit_event(recipient: AztecAddress) { + self.emit(DeliveryEvent { value: 1 }).deliver_to(recipient, MessageDelivery::onchain_constrained()); + } + + #[external("private")] + fn emit_two_events(recipient: AztecAddress) { + self.emit(DeliveryEvent { value: 1 }).deliver_to(recipient, MessageDelivery::onchain_constrained()); + self.emit(DeliveryEvent { value: 2 }).deliver_to(recipient, MessageDelivery::onchain_constrained()); + } + + /// Test reads go through this wrapper so the registry sees this contract as + /// [HandshakeRegistry::get_app_siloed_secret]'s msg_sender. + #[external("utility")] + unconstrained fn get_app_siloed_secret( + sender: AztecAddress, + recipient: AztecAddress, + mode: OnchainDeliveryMode, + ) -> Option { + let registry = HandshakeRegistry::at(STANDARD_HANDSHAKE_REGISTRY_ADDRESS); + self.call(registry.get_app_siloed_secret(sender, recipient, mode)) + } + + /// Test-only: emits a log under the constrained tag for `(secret, index)` WITHOUT the sequence nullifier. A landed + /// constrained-tagged log advances the PXE's per-secret index, so this lets tests reach the `index > 0` branch; + /// deliberately skipping the nullifier drives the negative test. + #[external("private")] + fn emit_constrained_log_without_nullifier(secret: Field, index: u32) { + let log_tag = compute_log_tag( + poseidon2_hash([secret, index as Field]), + DOM_SEP__CONSTRAINED_MSG_LOG_TAG, + ); + self.context.emit_private_log_unsafe(log_tag, BoundedVec::from_array([secret])); + } +} diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr new file mode 100644 index 000000000000..4001064ab170 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr @@ -0,0 +1,88 @@ +//! Tests for constrained delivery through the public message-delivery API. +//! +//! These tests exercise the sender-side helper flow indirectly via constrained note/event delivery: resolve or +//! bootstrap the app-siloed handshake secret, reserve the next per-secret index, constrain that index, emit the +//! sequence nullifier, then emit the tagged private log. The malformed-log test lands a constrained tag without its +//! sequence nullifier so the next real delivery must reject the broken sequence. +use crate::ConstrainedDeliveryTest; + +use aztec::{ + messages::delivery::{MessageDelivery, OnchainDeliveryMode}, + protocol::address::AztecAddress, + standard_addresses::STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + test::helpers::test_environment::{CallPrivateOptions, DeployOptions, ExecuteUtilityOptions, TestEnvironment}, +}; +use handshake_registry_contract::HandshakeRegistry; + +// Modes have no public constructors, so the tests derive them through the delivery builder API. +global ONCHAIN_CONSTRAINED: OnchainDeliveryMode = MessageDelivery::onchain_constrained().into(); + +unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, AztecAddress) { + let mut env = TestEnvironment::new(); + + let sender = env.create_light_account(); + let recipient = env.create_light_account(); + + let registry_address = env + .deploy_opts(DeployOptions::new().with_salt(1).with_secret(0), "@handshake_registry_contract/HandshakeRegistry") + .without_initializer(); + assert_eq(registry_address, STANDARD_HANDSHAKE_REGISTRY_ADDRESS); + + let test_address = env.deploy("ConstrainedDeliveryTest").without_initializer(); + + (env, registry_address, test_address, sender, recipient) +} + +/// Constrained delivery resolves the current app secret through `HandshakeRegistry::get_app_siloed_secret`, which TXE +/// denies by default. Authorize the registry as a utility-call target for calls that emit constrained messages. +unconstrained fn authorizing(registry_address: AztecAddress) -> CallPrivateOptions<0, 1> { + CallPrivateOptions::new().with_authorized_utility_call_targets([registry_address]) +} + +#[test] +unconstrained fn rehandshake_replaces_registry_secret_for_future_delivery() { + let (env, registry_address, test_address, sender, recipient) = setup(); + let test_contract = ConstrainedDeliveryTest::at(test_address); + let registry = HandshakeRegistry::at(registry_address); + + env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); + let first_secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + ) + .expect(f"first delivery should have stored a handshake"); + + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); + let second_secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + ) + .expect(f"re-handshake should have stored a replacement handshake"); + assert(first_secret != second_secret, "re-handshake should produce a distinct secret"); + + env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); + + let next_index = env.call_private(sender, test_contract.next_index_for_secret(second_secret)); + assert_eq(next_index, 1); +} + +#[test(should_fail_with = "reading an unknown nullifier")] +unconstrained fn fails_at_index_above_zero_without_prior_nullifier() { + let (env, registry_address, test_address, sender, recipient) = setup(); + let test_contract = ConstrainedDeliveryTest::at(test_address); + let registry = HandshakeRegistry::at(registry_address); + + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); + let secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + ) + .expect(f"handshake should be siloed for the test contract"); + + env.call_private(sender, test_contract.emit_constrained_log_without_nullifier(secret, 0)); + + let _ = env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); +} diff --git a/noir-projects/noir-contracts/contracts/test/note_getter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/note_getter_contract/src/main.nr index d25b0b6dbfde..193a21837008 100644 --- a/noir-projects/noir-contracts/contracts/test/note_getter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/note_getter_contract/src/main.nr @@ -25,12 +25,14 @@ pub contract NoteGetter { packed_set: Owned, Context>, } + // This function is called from tests that execute it across many parallel txs. + // Since constrained delivery doesn't support it, this function must use unconstrained delivery instead. #[external("private")] fn insert_note(value: Field) { let owner = self.msg_sender(); let note = FieldNote { value }; - self.storage.set.at(owner).insert(note).deliver(MessageDelivery::onchain_constrained()); + self.storage.set.at(owner).insert(note).deliver(MessageDelivery::onchain_unconstrained()); } #[external("utility")] @@ -48,7 +50,7 @@ pub contract NoteGetter { let owner = self.msg_sender(); let note = PackedNote { high, low }; - self.storage.packed_set.at(owner).insert(note).deliver(MessageDelivery::onchain_constrained()); + self.storage.packed_set.at(owner).insert(note).deliver(MessageDelivery::onchain_unconstrained()); } #[external("utility")] diff --git a/noir-projects/noir-contracts/contracts/test/state_vars_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/state_vars_contract/src/main.nr index 2b38bb6e63db..cff9cf2610b3 100644 --- a/noir-projects/noir-contracts/contracts/test/state_vars_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/state_vars_contract/src/main.nr @@ -93,12 +93,14 @@ pub contract StateVars { self.storage.public_immutable.read() } + // This function is called from tests that execute it across many parallel txs. + // Since constrained delivery doesn't support it, this function must use unconstrained delivery instead. #[external("private")] fn initialize_private_immutable(randomness: Field, value: Field) { let owner = self.msg_sender(); let new_note = FieldNote { value }; - self.storage.private_immutable.at(owner).initialize(new_note).deliver(MessageDelivery::onchain_constrained()); + self.storage.private_immutable.at(owner).initialize(new_note).deliver(MessageDelivery::onchain_unconstrained()); } #[external("private")] @@ -107,7 +109,7 @@ pub contract StateVars { let private_mutable = FieldNote { value }; self.storage.private_mutable.at(owner).initialize(private_mutable).deliver( - MessageDelivery::onchain_constrained(), + MessageDelivery::onchain_unconstrained(), ); } @@ -115,7 +117,7 @@ pub contract StateVars { fn update_private_mutable(randomness: Field, value: Field) { let owner = self.msg_sender(); self.storage.private_mutable.at(owner).replace(|_old_note| FieldNote { value }).deliver( - MessageDelivery::onchain_constrained(), + MessageDelivery::onchain_unconstrained(), ); } @@ -131,7 +133,7 @@ pub contract StateVars { let new_value = old_note.value + 1; FieldNote { value: new_value } }) - .deliver(MessageDelivery::onchain_constrained()); + .deliver(MessageDelivery::onchain_unconstrained()); } #[external("utility")] diff --git a/noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr index a2306c0e258b..4c2e1648c37b 100644 --- a/noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr @@ -41,7 +41,7 @@ pub contract StatefulTest { fn create_note(owner: AztecAddress, value: Field) { if (value != 0) { let note = FieldNote { value }; - self.storage.notes.at(owner).insert(note).deliver(MessageDelivery::onchain_constrained()); + self.storage.notes.at(owner).insert(note).deliver(MessageDelivery::onchain_unconstrained()); } } @@ -50,7 +50,7 @@ pub contract StatefulTest { fn create_note_no_init_check(owner: AztecAddress, value: Field) { if (value != 0) { let note = FieldNote { value }; - self.storage.notes.at(owner).insert(note).deliver(MessageDelivery::onchain_constrained()); + self.storage.notes.at(owner).insert(note).deliver(MessageDelivery::onchain_unconstrained()); } } @@ -61,7 +61,9 @@ pub contract StatefulTest { let _ = self.storage.notes.at(sender).pop_notes(NoteGetterOptions::new().set_limit(2)); - self.storage.notes.at(recipient).insert(FieldNote { value: 92 }).deliver(MessageDelivery::onchain_constrained()); + self.storage.notes.at(recipient).insert(FieldNote { value: 92 }).deliver( + MessageDelivery::onchain_unconstrained(), + ); } #[external("public")] diff --git a/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr index f3a3b20c3ecf..18ad00eac1aa 100644 --- a/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/test_log_contract/src/main.nr @@ -66,18 +66,20 @@ pub contract TestLog { self.context.emit_private_log_unsafe(tag3, BoundedVec::from_array(payload3)); } + // This function is called from tests that execute it across many parallel txs. + // Since constrained delivery doesn't support it, this function must use unconstrained delivery instead. #[external("private")] fn emit_encrypted_events(other: AztecAddress, preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; - self.emit(event0).deliver_to(self.msg_sender(), MessageDelivery::onchain_constrained()); + self.emit(event0).deliver_to(self.msg_sender(), MessageDelivery::onchain_unconstrained()); // We duplicate the emission, but swapping the sender and recipient: - self.emit(event0).deliver_to(other, MessageDelivery::onchain_constrained()); + self.emit(event0).deliver_to(other, MessageDelivery::onchain_unconstrained()); let event1 = ExampleEvent1 { value2: AztecAddress::from_field(preimages[2]), value3: preimages[3] as u8 }; - self.emit(event1).deliver_to(self.msg_sender(), MessageDelivery::onchain_constrained()); + self.emit(event1).deliver_to(self.msg_sender(), MessageDelivery::onchain_unconstrained()); } #[external("public")] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 42e699e8da82..d5143af64c42 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -730,6 +730,8 @@ pub global DOM_SEP__NOTE_COMPLETION_LOG_TAG: u32 = 3372669888; pub global DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG: u32 = 1485357192; /// Domain separator for constrained message delivery log tags. Used by [`crate::hash::compute_log_tag`]. pub global DOM_SEP__CONSTRAINED_MSG_LOG_TAG: u32 = 3715244738; +/// Domain separator for nullifiers used during constrained delivery. +pub global DOM_SEP__CONSTRAINED_MSG_NULLIFIER: u32 = 3723577546; /// Domain separator for non-interactive handshake log tags emitted by the handshake registry contract. Used by /// [`crate::hash::compute_log_tag`]. pub global DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG: u32 = 4046403018; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr index 96bc17853b0c..92e7295289d3 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr @@ -10,10 +10,11 @@ use crate::{ DOM_SEP__AUTHWIT_OUTER, DOM_SEP__BLOB_CHALLENGE_Z, DOM_SEP__BLOB_GAMMA_ACC, DOM_SEP__BLOB_GAMMA_FINAL, DOM_SEP__BLOB_HASHED_Y_LIMBS, DOM_SEP__BLOB_Z_ACC, DOM_SEP__BLOCK_HEADER_HASH, DOM_SEP__BLOCK_HEADERS_HASH, DOM_SEP__CONSTRAINED_MSG_LOG_TAG, - DOM_SEP__CONTRACT_ADDRESS_V2, DOM_SEP__CONTRACT_CLASS_ID, DOM_SEP__ECDH_FIELD_MASK, - DOM_SEP__ECDH_SUBKEY, DOM_SEP__EVENT_COMMITMENT, DOM_SEP__EVENT_LOG_TAG, - DOM_SEP__FUNCTION_ARGS, DOM_SEP__INITIALIZATION_NULLIFIER, DOM_SEP__INITIALIZER, - DOM_SEP__IVSK_M, DOM_SEP__MERKLE_HASH, DOM_SEP__MESSAGE_NULLIFIER, DOM_SEP__NHK_M, + DOM_SEP__CONSTRAINED_MSG_NULLIFIER, DOM_SEP__CONTRACT_ADDRESS_V2, + DOM_SEP__CONTRACT_CLASS_ID, DOM_SEP__ECDH_FIELD_MASK, DOM_SEP__ECDH_SUBKEY, + DOM_SEP__EVENT_COMMITMENT, DOM_SEP__EVENT_LOG_TAG, DOM_SEP__FUNCTION_ARGS, + DOM_SEP__INITIALIZATION_NULLIFIER, DOM_SEP__INITIALIZER, DOM_SEP__IVSK_M, + DOM_SEP__MERKLE_HASH, DOM_SEP__MESSAGE_NULLIFIER, DOM_SEP__NHK_M, DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, DOM_SEP__NOTE_COMPLETION_LOG_TAG, DOM_SEP__NOTE_HASH, DOM_SEP__NOTE_HASH_NONCE, DOM_SEP__NOTE_NULLIFIER, DOM_SEP__NULLIFIER_MERKLE, DOM_SEP__OVSK_M, DOM_SEP__PARTIAL_ADDRESS, @@ -143,7 +144,7 @@ impl HashedValueTester::new(); + let mut tester = HashedValueTester::<73, 66>::new(); // ----------------- // Domain separators @@ -180,6 +181,10 @@ fn hashed_values_match_derived() { DOM_SEP__CONSTRAINED_MSG_LOG_TAG, "constrained_msg_log_tag", ); + tester.assert_dom_sep_matches_derived( + DOM_SEP__CONSTRAINED_MSG_NULLIFIER, + "constrained_msg_nullifier", + ); tester.assert_dom_sep_matches_derived( DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, "non_interactive_handshake_log_tag", diff --git a/yarn-project/constants/src/constants.gen.ts b/yarn-project/constants/src/constants.gen.ts index e57889caba37..744acff17fb8 100644 --- a/yarn-project/constants/src/constants.gen.ts +++ b/yarn-project/constants/src/constants.gen.ts @@ -525,6 +525,7 @@ export enum DomainSeparator { NOTE_COMPLETION_LOG_TAG = 3372669888, UNCONSTRAINED_MSG_LOG_TAG = 1485357192, CONSTRAINED_MSG_LOG_TAG = 3715244738, + CONSTRAINED_MSG_NULLIFIER = 3723577546, NON_INTERACTIVE_HANDSHAKE_LOG_TAG = 4046403018, PRIVATE_LOG_FIRST_FIELD = 2769976252, PUBLIC_LEAF_SLOT = 1247650290, diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index f7e7a904a2f6..0bacafd279a0 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -143,7 +143,10 @@ describe('e2e_2_pxes', () => { expect(storedValueOnA).toEqual(newValueToSet); }); - it('private state is "zero" when PXE does not have the account secret key', async () => { + // TODO(F-741): `expectTokenBalance(walletB, token, accountAAddress, 0n)` throws + // "No public key registered". Handshake discovery (get_shared_secrets) needs the scope's + // keys, which this PXE lacks for a foreign account. + it.skip('private state is "zero" when PXE does not have the account secret key', async () => { const userABalance = 100n; const userBBalance = 150n; diff --git a/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts new file mode 100644 index 000000000000..021a88b8b4e7 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts @@ -0,0 +1,157 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { BatchCall } from '@aztec/aztec.js/contracts'; +import type { Wallet } from '@aztec/aztec.js/wallet'; +import { HandshakeRegistryContract } from '@aztec/noir-contracts.js/HandshakeRegistry'; +import { ConstrainedDeliveryTestContract } from '@aztec/noir-test-contracts.js/ConstrainedDeliveryTest'; +import { STANDARD_HANDSHAKE_REGISTRY_ADDRESS } from '@aztec/standard-contracts/handshake-registry/constants'; + +import { jest } from '@jest/globals'; + +import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; +import { ensureHandshakeRegistryPublished, setup } from './fixtures/setup.js'; + +const ONCHAIN_CONSTRAINED = { inner: 3 }; + +describe('constrained delivery', () => { + jest.setTimeout(300_000); + + let teardown: () => Promise; + let wallet: Wallet; + let sender: AztecAddress; + let recipient: AztecAddress; + let batchRecipient: AztecAddress; + let batchRecipient2: AztecAddress; + let batchRecipient3: AztecAddress; + let batchRecipient4: AztecAddress; + let contract: ConstrainedDeliveryTestContract; + let registry: HandshakeRegistryContract; + + beforeAll(async () => { + ({ + teardown, + wallet, + accounts: [sender, recipient, batchRecipient, batchRecipient2, batchRecipient3, batchRecipient4], + } = await setup(6, { ...AUTOMINE_E2E_OPTS })); + + await ensureHandshakeRegistryPublished(wallet, sender); + ({ contract } = await ConstrainedDeliveryTestContract.deploy(wallet).send({ from: sender })); + registry = HandshakeRegistryContract.at(STANDARD_HANDSHAKE_REGISTRY_ADDRESS, wallet); + }); + + afterAll(() => teardown()); + + it('reuses an existing standard-registry constrained handshake', async () => { + await contract.methods.emit_note(recipient).send({ from: sender }); + + const { result: secretAfterFirstSend } = await contract.methods + .get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + expect(secretAfterFirstSend).toBeDefined(); + + await contract.methods.emit_event(recipient).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + // The second send reuses the handshake rather than bootstrapping a new one: the secret is unchanged. + expect(secret).toEqual(secretAfterFirstSend); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + + expect(index).toEqual(2n); + }); + + // Constrained sends to one recipient form a strictly ordered sequence, so concurrent and batched sends behave + // differently: parallel txs collide on the shared index nullifier, same-tx batches work only once the handshake is + // committed, and batches that bootstrap a brand-new recipient re-handshake onto separate secrets. Each test uses + // its own recipient. + describe('concurrency and batching', () => { + // Constrained sends to one `(sender, recipient)` pair are strictly ordered: the first send bootstraps the + // handshake and every send emits a nullifier keyed only on `(sender, recipient, secret, index)`. Two sends fired + // in parallel read the same index and collide, so one tx is rejected. Marked `it.failing` because this is a + // protocol limitation, not a bug: it documents the constraint and will start failing (prompting its removal) if + // parallel sends to a single pair ever become supported. The working alternative is the batched test below. + it.failing('cannot fan out constrained sends on the same sequence in parallel', async () => { + await Promise.all([ + contract.methods.emit_note(recipient).send({ from: sender }), + contract.methods.emit_note(recipient).send({ from: sender }), + ]); + }); + + // CAN batch (1): a contract call may emit several constrained messages to one recipient in a single tx; each + // later emit proves the previous nullifier as a same-tx pending nullifier. The handshake must already be + // committed (see the re-handshake test below), so it is established first; a fresh recipient starts at index 0, + // so two emits land indices 0 and 1 and the next index is 2. + it('lands multiple constrained sends from a single contract call on an established handshake', async () => { + await registry.methods + .non_interactive_handshake(sender, batchRecipient, ONCHAIN_CONSTRAINED) + .send({ from: sender }); + + await contract.methods.emit_two_events(batchRecipient).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + expect(secret).toBeDefined(); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + expect(index).toEqual(2n); + }); + + // CAN batch (2): client-side BatchCall aggregates separate calls into one tx with the same effect. The two + // emit_note calls that fail as parallel txs (above) succeed batched, given an established handshake. + it('lands the same two sends when aggregated into one tx with BatchCall', async () => { + await registry.methods + .non_interactive_handshake(sender, batchRecipient2, ONCHAIN_CONSTRAINED) + .send({ from: sender }); + + await new BatchCall(wallet, [ + contract.methods.emit_note(batchRecipient2), + contract.methods.emit_note(batchRecipient2), + ]).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient2, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + expect(secret).toBeDefined(); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + expect(index).toEqual(2n); + }); + + // CANNOT batch onto a brand-new recipient, even within a single contract call. The registry lookup that decides + // reuse-vs-bootstrap is a utility call reading committed state, so the second emit cannot see the first emit's + // pending bootstrap and re-handshakes onto a fresh secret (each handshake mints a new shared secret). The registry + // keeps the second handshake, which holds a single log, so the next index is 1, not 2. This is why the + // established-handshake tests above seed the handshake first. + it('re-handshakes instead of reusing when sends bootstrap a new recipient in the same tx', async () => { + await contract.methods.emit_two_events(batchRecipient3).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient3, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + expect(secret).toBeDefined(); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + expect(index).toEqual(1n); + }); + + // The same new-recipient limitation holds via client-side BatchCall: the two aggregated emit_note calls each + // bootstrap and re-handshake onto separate secrets (the utility read can't see the first's pending bootstrap), + // so the next index is 1, not 2. Confirms the constraint is in the utility read, not the batching mechanism. + it('re-handshakes instead of reusing when BatchCall sends bootstrap a new recipient in the same tx', async () => { + await new BatchCall(wallet, [ + contract.methods.emit_note(batchRecipient4), + contract.methods.emit_note(batchRecipient4), + ]).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient4, ONCHAIN_CONSTRAINED) + .simulate({ from: sender }); + expect(secret).toBeDefined(); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + expect(index).toEqual(1n); + }); + }); +}); diff --git a/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts b/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts index 30d3e84d9c5b..61eb3a4fce6b 100644 --- a/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts @@ -127,24 +127,51 @@ describe('e2e_pending_note_hashes_contract', () => { it('Squash! Aztec.nr function can "create" and "nullify" note in the same TX but the constrained note log survives', async () => { // Kernel will squash the noteHash and its nullifier, but NOT the note log: constrained-delivery logs are not - // linked to the note for squashing, because a removed log would break the index chain. + // linked to the note for squashing, because a removed log would break the index sequence. const mintAmount = 65n; const deployedContract = await deployContract(); const sender = owner; + const insertConstrainedSelector = await deployedContract.methods.insert_note_constrained.selector(); + const getThenNullifySelector = await deployedContract.methods.get_then_nullify_note.selector(); + + // The first constrained send to a fresh sender/recipient sequence bootstraps the HandshakeRegistry. Assert that + // separately so the reused-sequence assertions below only observe the app note inserted and nullified in the + // measured tx. + await deployedContract.methods + .test_insert_then_get_then_nullify_all_in_nested_calls( + 1n, + owner, + sender, + insertConstrainedSelector, + getThenNullifySelector, + ) + .send({ from: owner }); + // Bootstrap emits a registry note hash, a registry initialization nullifier, and a constrained-delivery sequence + // nullifier. The app note hash and app note nullifier are still squashed. + await expectNoteHashesSquashedExcept(1); + await expectNullifiersSquashedExcept(2); + // It also emits three private logs: + // 1. registry HandshakeNote delivery log; + // 2. registry recipient-discovery log; + // 3. app constrained note-delivery log. + await expectNoteLogsSquashedExcept(3); + await deployedContract.methods .test_insert_then_get_then_nullify_all_in_nested_calls( mintAmount, owner, sender, - await deployedContract.methods.insert_note_constrained.selector(), - await deployedContract.methods.get_then_nullify_note.selector(), + insertConstrainedSelector, + getThenNullifySelector, ) .send({ from: owner }); await expectNoteHashesSquashedExcept(0); - await expectNullifiersSquashedExcept(0); + // Constrained delivery always emits its sequence nullifier. The one allowed nullifier below is that delivery + // nullifier, not the app note's nullifier, which remains squashed together with its note hash. + await expectNullifiersSquashedExcept(1); await expectNoteLogsSquashedExcept(1); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts index 3082b816e7f4..bc70e96e76d3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts @@ -4,8 +4,17 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import { StatefulTestContractArtifact } from '@aztec/noir-test-contracts.js/StatefulTest'; -import { WASMSimulator } from '@aztec/simulator/client'; -import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; +import { type CircuitSimulator, WASMSimulator } from '@aztec/simulator/client'; +import { HandshakeRegistryArtifact } from '@aztec/standard-contracts/handshake-registry'; +import { STANDARD_HANDSHAKE_REGISTRY_ADDRESS } from '@aztec/standard-contracts/handshake-registry/constants'; +import { + type ContractArtifact, + FunctionCall, + FunctionSelector, + FunctionType, + encodeArguments, + getFunctionArtifactByName, +} from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash, type L2TipsProvider } from '@aztec/stdlib/block'; import { @@ -17,7 +26,7 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { PublicKeys, deriveKeys, hashPublicKey } from '@aztec/stdlib/keys'; import { AppTaggingSecret, AppTaggingSecretKind, MessageContext, SiloedTag } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { makeL2Tips } from '@aztec/stdlib/testing'; +import { makeL2Tips, randomContractInstanceWithAddress } from '@aztec/stdlib/testing'; import { BlockHeader, CallContext, @@ -436,6 +445,88 @@ describe('Utility Execution test suite', () => { }); }); + // Pins the production oracle's default-authorization allowlist for cross-contract utility reads of the + // standard HandshakeRegistry: only get_handshakes and get_app_siloed_secret are allowed, everything else is + // denied. + describe('cross-contract utility authorization', () => { + const prepareNestedUtilityCall = async ( + targetContractAddress: AztecAddress, + contractArtifact: ContractArtifact, + functionName: string, + ) => { + const functionArtifact = { + ...getFunctionArtifactByName(contractArtifact, functionName), + contractName: contractArtifact.name, + }; + const selector = await FunctionSelector.fromNameAndParameters(functionName, functionArtifact.parameters); + const callerInstance = await randomContractInstanceWithAddress({}, contractAddress); + const targetInstance = await randomContractInstanceWithAddress({}, targetContractAddress); + + contractStore.getFunctionArtifactWithDebugMetadata.mockResolvedValue(functionArtifact); + contractStore.getContractInstance.mockImplementation(address => { + if (address.equals(contractAddress)) { + return Promise.resolve(callerInstance); + } + if (address.equals(targetContractAddress)) { + return Promise.resolve(targetInstance); + } + return Promise.reject(new Error(`Unexpected contract instance lookup for ${address}`)); + }); + + return selector; + }; + + const makeNestedSimulator = () => { + const nestedSimulator = mock(); + nestedSimulator.executeUserCircuit.mockResolvedValue({ partialWitness: new Map(), returnWitness: new Map() }); + return nestedSimulator; + }; + + let nestedSimulator: ReturnType; + // The standard HandshakeRegistry reads the oracle default-authorizes, mapped to the args each is called with. + let defaultAuthorizedHandshakeRegistryReads: Map; + + beforeEach(() => { + nestedSimulator = makeNestedSimulator(); + utilityExecutionOracle = makeOracle({ simulator: nestedSimulator }); + defaultAuthorizedHandshakeRegistryReads = new Map([ + ['get_handshakes', []], + ['get_app_siloed_secret', [Fr.random(), Fr.random(), new Fr(3)]], + ]); + }); + + afterEach(() => { + contractSyncService.ensureContractSynced.mockClear(); + nestedSimulator.executeUserCircuit.mockClear(); + }); + + it.each(HandshakeRegistryArtifact.functions.map(fn => fn.name))( + 'authorizes %s only if it is in the standard HandshakeRegistry read allowlist', + async name => { + const selector = await prepareNestedUtilityCall( + STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + HandshakeRegistryArtifact, + name, + ); + + if (defaultAuthorizedHandshakeRegistryReads.has(name)) { + const args = defaultAuthorizedHandshakeRegistryReads.get(name) ?? []; + await expect( + utilityExecutionOracle.callUtilityFunction(STANDARD_HANDSHAKE_REGISTRY_ADDRESS, selector, args), + ).resolves.toEqual([]); + expect(contractSyncService.ensureContractSynced).toHaveBeenCalled(); + expect(nestedSimulator.executeUserCircuit).toHaveBeenCalled(); + } else { + await expect( + utilityExecutionOracle.callUtilityFunction(STANDARD_HANDSHAKE_REGISTRY_ADDRESS, selector, []), + ).rejects.toThrow('Cross-contract utility call denied: No execution hooks configured'); + expect(contractSyncService.ensureContractSynced).not.toHaveBeenCalled(); + expect(nestedSimulator.executeUserCircuit).not.toHaveBeenCalled(); + } + }, + ); + }); + describe('getMessageContextsByTxHash', () => { const service = new EphemeralArrayService(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index 84a27e63724a..f398542c3654 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -847,12 +847,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra ); if (!targetContractAddress.equals(this.contractAddress)) { - // The HandshakeRegistry is called during every contract's sync to discover handshake secrets. - // It is a standard contract that only reads its own state, so it is always authorized. - const isHandshakeRegistryRead = - targetContractAddress.equals(STANDARD_HANDSHAKE_REGISTRY_ADDRESS) && - functionSelector.equals(await FunctionSelector.fromSignature('get_handshakes((Field),u32)')); - if (!isHandshakeRegistryRead) { + if (!(await isStandardHandshakeRegistryUtilityRead(targetContractAddress, functionSelector))) { const [callerInstance, targetInstance] = await Promise.all([ this.getContractInstance(this.contractAddress), this.getContractInstance(targetContractAddress), @@ -1017,3 +1012,25 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return this.callContext.contractAddress; } } + +const STANDARD_HANDSHAKE_REGISTRY_GET_HANDSHAKES_SIGNATURE = 'get_handshakes((Field),u32)'; +const STANDARD_HANDSHAKE_REGISTRY_GET_APP_SILOED_SECRET_SIGNATURE = 'get_app_siloed_secret((Field),(Field),(u8))'; + +async function doesSelectorHaveSignature(functionSelector: FunctionSelector, signature: string): Promise { + return functionSelector.equals(await FunctionSelector.fromSignature(signature)); +} + +async function isStandardHandshakeRegistryUtilityRead( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, +): Promise { + if (!targetContractAddress.equals(STANDARD_HANDSHAKE_REGISTRY_ADDRESS)) { + return false; + } + + const [isGetHandshakes, isGetAppSiloedSecret] = await Promise.all([ + doesSelectorHaveSignature(functionSelector, STANDARD_HANDSHAKE_REGISTRY_GET_HANDSHAKES_SIGNATURE), + doesSelectorHaveSignature(functionSelector, STANDARD_HANDSHAKE_REGISTRY_GET_APP_SILOED_SECRET_SIGNATURE), + ]); + return isGetHandshakes || isGetAppSiloedSecret; +} From 503b5c4490896bc648e9ee0c2a3c8ff917e50736 Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Mon, 22 Jun 2026 13:04:42 -0300 Subject: [PATCH 3/8] fix(pxe): respect slot boundaries in oracle (de)serialization (#24211) --- .../src/contract_function_simulator/index.ts | 1 + .../noir-structs/option.ts | 21 +- .../oracle/oracle_registry.test.ts | 128 ------- .../oracle/oracle_registry.ts | 21 +- .../oracle/oracle_type_mappings.test.ts | 326 ++++++++++++++++++ .../oracle/oracle_type_mappings.ts | 280 +++++++++++---- .../oracle/private_execution_oracle.ts | 6 +- .../oracle/utility_execution_oracle.ts | 17 +- yarn-project/pxe/src/oracle_version.ts | 2 +- .../src/oracle/test-resolver/resolver.test.ts | 2 +- .../txe/src/oracle/txe_oracle_registry.ts | 35 +- yarn-project/txe/src/rpc_translator.ts | 2 +- 12 files changed, 603 insertions(+), 238 deletions(-) delete mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.test.ts create mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.test.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/index.ts b/yarn-project/pxe/src/contract_function_simulator/index.ts index 15ec0a51e5a0..2d342eb420b5 100644 --- a/yarn-project/pxe/src/contract_function_simulator/index.ts +++ b/yarn-project/pxe/src/contract_function_simulator/index.ts @@ -39,6 +39,7 @@ export { type InputSlot, type MaybePromise, type OutputSlot, + type SlotShape, type TypeMapping, } from './oracle/oracle_type_mappings.js'; export { ExecutionNoteCache } from './execution_note_cache.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/option.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/option.ts index 14347d4d64a2..4d9ca790e899 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/option.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/option.ts @@ -8,7 +8,9 @@ export class Option { private constructor( public readonly value: T | undefined, - public readonly template: T | undefined, + // `unknown` (not the descriptor shape) keeps this generic Option decoupled from the serialization layer's + // `{ length }`/`{ maxLength }` size descriptors; only the None-serialization path inspects it. + public readonly size: unknown, ) {} /** @@ -26,25 +28,22 @@ export class Option { /** * Construct an absent Option. * - * When serialized back to ACVM, the `None` case must produce the same number of fields as `Some`. - * For types whose wire size varies per call site (`BoundedVec`, `FixedArray`), pass a `template` so the - * serializer knows how many zero fields to emit. Omit the template when the Option will not be - * re-serialized (e.g. deserialized input params). - * - * @param template - A representative empty `T` whose serialization determines the zero-filled wire format. + * When serialized back to ACVM, the `None` case must produce the same number of fields as `Some`. For an inner type whose + * wire size varies per call site (`BoundedVec`, an array), pass a `size` descriptor so the inner type's shape can + * resolve how many zero fields to emit; fixed-size inners take no argument. * * @example None for a fixed-size type: * ```ts - * return Option.none(AztecAddress.ZERO); + * return Option.none(); * ``` * * @example None for a dynamic-size type: * ```ts - * return Option.none(BoundedVec.empty({ maxLength: ciphertext.maxLength })); + * return Option.none({ maxLength: ciphertext.maxLength }); * ``` */ - static none(template?: T): Option { - return new Option(undefined, template); + static none(size?: unknown): Option { + return new Option(undefined, size); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.test.ts deleted file mode 100644 index 1de6f62f51dc..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { FieldReader } from '@aztec/foundation/serialize'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { AppTaggingSecretKind, Tag } from '@aztec/stdlib/logs'; - -import { EphemeralArrayService } from '../ephemeral_array_service.js'; -import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; -import { - BOUNDED_VEC, - BYTE, - DELIVERY_MODE, - EPHEMERAL_ARRAY, - FIELD, - LOG_RETRIEVAL_REQUEST, - type TypeMapping, - U32, -} from './oracle_registry.js'; - -function deserialize(mapping: TypeMapping, value: Fr): T { - const reader = new FieldReader([value]); - return mapping.deserialization!.fn([reader]); -} - -describe('oracle_registry type mappings', () => { - describe('U32', () => { - it('deserializes a valid u32', () => { - expect(deserialize(U32, new Fr(42))).toBe(42); - }); - - it('deserializes u32 max', () => { - expect(deserialize(U32, new Fr(0xffffffffn))).toBe(0xffffffff); - }); - - it('rejects values exceeding u32 max', () => { - expect(() => deserialize(U32, new Fr(0x100000000n))).toThrow('U32 overflow'); - }); - }); - - describe('BYTE', () => { - it('deserializes a valid byte', () => { - expect(deserialize(BYTE, new Fr(0))).toBe(0); - }); - - it('deserializes byte max', () => { - expect(deserialize(BYTE, new Fr(255))).toBe(255); - }); - - it('rejects values exceeding u8 max', () => { - expect(() => deserialize(BYTE, new Fr(256))).toThrow('BYTE overflow'); - }); - }); - - describe('DELIVERY_MODE', () => { - it('maps onchain unconstrained delivery to unconstrained tagging', () => { - expect(deserialize(DELIVERY_MODE, new Fr(2))).toBe(AppTaggingSecretKind.UNCONSTRAINED); - }); - - it('maps onchain constrained delivery to constrained tagging', () => { - expect(deserialize(DELIVERY_MODE, new Fr(3))).toBe(AppTaggingSecretKind.CONSTRAINED); - }); - - it('rejects offchain delivery', () => { - expect(() => deserialize(DELIVERY_MODE, new Fr(1))).toThrow('Unrecognized delivery mode for tagging'); - }); - }); - - describe('BOUNDED_VEC', () => { - it('deserializes when capacity equals length', () => { - const a = Fr.random(); - const b = Fr.random(); - const bv = deserializeBoundedVec(FIELD, [a, b], 2); - expect(bv.data).toEqual([a, b]); - expect(bv.maxLength).toBe(2); - }); - - it('deserializes when capacity exceeds length', () => { - const a = Fr.random(); - const bv = deserializeBoundedVec(FIELD, [a], 4); - expect(bv.data).toEqual([a]); - expect(bv.maxLength).toBe(4); - }); - - it('deserializes an empty vec with nonzero capacity', () => { - const bv = deserializeBoundedVec(FIELD, [], 3); - expect(bv.data).toEqual([]); - expect(bv.maxLength).toBe(3); - }); - }); - - describe('EPHEMERAL_ARRAY', () => { - it('deserializes well-formed rows', () => { - const a = Fr.random(); - const b = Fr.random(); - expect(readEphemeralArray(FIELD, [[a], [b]])).toEqual([a, b]); - }); - - it('rejects a row with too few fields', () => { - expect(() => readEphemeralArray(FIELD, [[]])).toThrow('Not enough fields to be consumed.'); - }); - - it('rejects a row with trailing fields', () => { - expect(() => readEphemeralArray(FIELD, [[Fr.random(), Fr.random()]])).toThrow('unexpected trailing field(s)'); - }); - - it('rejects a multi-field row with trailing fields', async () => { - const row = new LogRetrievalRequest(await AztecAddress.random(), new Tag(Fr.random())).toFields(); - expect(() => readEphemeralArray(LOG_RETRIEVAL_REQUEST, [[...row, Fr.random()]])).toThrow( - 'unexpected trailing field(s)', - ); - }); - }); -}); - -/** Deserializes a BoundedVec from `data` (padded to `capacity` with Fr.ZERO) and a length field. */ -function deserializeBoundedVec(element: TypeMapping, data: Fr[], capacity: number) { - const padded = [...data, ...Array(capacity - data.length).fill(Fr.ZERO)]; - const storageReader = new FieldReader(padded); - const lengthReader = new FieldReader([new Fr(data.length)]); - return BOUNDED_VEC(element).deserialization!.fn([storageReader, lengthReader]); -} - -/** Reads an input-mode EphemeralArray backed by `rows`, the way the registry does when deserializing an oracle param. */ -function readEphemeralArray(element: TypeMapping, rows: Fr[][]): T[] { - const service = new EphemeralArrayService(); - const slot = service.newArray(rows); - const array = EPHEMERAL_ARRAY(element).deserialization!.fn([new FieldReader([slot])]); - return array.readAll(service); -} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts index c0bb2dc1dbc1..f279eafa1697 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts @@ -48,6 +48,7 @@ import { U32, UTILITY_CONTEXT, assertReadersConsumed, + slotsOf, } from './oracle_type_mappings.js'; export { @@ -59,6 +60,7 @@ export { BOUNDED_VEC, BUFFER, BYTE, + CONTRACT_CLASS_LOG_INPUT, DELIVERY_MODE, EPHEMERAL_ARRAY, EVENT_VALIDATION_REQUEST, @@ -75,9 +77,11 @@ export { PROVIDED_SECRET, STR, U32, + slotsOf, type InputSlot, type MaybePromise, type OutputSlot, + type SlotShape, type TypeMapping, } from './oracle_type_mappings.js'; @@ -103,7 +107,10 @@ export const ORACLE_REGISTRY = { aztec_utl_getUtilityContext: makeEntry({ returnType: UTILITY_CONTEXT }), aztec_utl_getKeyValidationRequest: makeEntry({ - params: [{ name: 'pkMHash', type: FIELD }], + params: [ + { name: 'pkMHash', type: FIELD }, + { name: 'keyIndex', type: FIELD }, + ], returnType: KEY_VALIDATION_REQUEST, }), @@ -540,12 +547,12 @@ export function makeEntry { + const named = resolvedParams.map(param => { if (!param.type.deserialization) { throw new Error(`Param '${param.name}' has no deserialization defined`); } // Collect the slots for this param and wrap each in a FieldReader. - const slotCount = param.type.deserialization.slots; + const slotCount = slotsOf(param.type); const readers = inputs .slice(offset, offset + slotCount) .map(slot => new FieldReader(slot.map(hex => Fr.fromString(hex)))); @@ -555,7 +562,13 @@ export function makeEntry; + }); + // Every input slot must be specified by a param: oracles whose Noir decl passes an extra field must declare it + // (the handler can ignore it). Otherwise an under-declared shape would silently drop a field into nothing. + if (offset !== inputs.length) { + throw new Error(`Oracle received ${inputs.length} input slot(s) but the registry specifies ${offset}`); + } + return named as unknown as InferDeserializedParams; }, serializeReturn(result: TReturnValue): OutputSlot[] { if (returnType?.serialization === undefined) { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.test.ts new file mode 100644 index 000000000000..453e3076a077 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.test.ts @@ -0,0 +1,326 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; +import { Point } from '@aztec/foundation/curves/grumpkin'; +import { FieldReader } from '@aztec/foundation/serialize'; +import { MembershipWitness } from '@aztec/foundation/trees'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { AppTaggingSecretKind, Tag } from '@aztec/stdlib/logs'; + +import { EphemeralArrayService } from '../ephemeral_array_service.js'; +import { BoundedVec } from '../noir-structs/bounded_vec.js'; +import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; +import { Option } from '../noir-structs/option.js'; +import { + ARRAY, + AZTEC_ADDRESS, + BOUNDED_VEC, + BYTE, + DELIVERY_MODE, + EPHEMERAL_ARRAY, + FIELD, + LOG_RETRIEVAL_REQUEST, + MEMBERSHIP_WITNESS, + OPTION, + POINT, + type SlotShape, + type TypeMapping, + U32, + makeEntry, + slotsOf, +} from './oracle_registry.js'; + +/** + * Tests for the oracle type mappings: how the PXE encodes values to, and decodes them from, the flat field arrays that + * Noir oracles exchange over the ACVM foreign-call interface. + * + * A mapping's wire form is a list of *slots*. Each slot is either a single field (a scalar) or a run of fields (e.g. an + * array's contents). `serialization.fn` produces the slots; `deserialization.fn` reads them back, one `FieldReader` per + * slot. A mapping's `shape` declares each slot's width up front, so the reader knows where one slot ends and the next + * begins. + * + * Most tests *round-trip*: serialize a value, deserialize the result, and assert it comes back unchanged (see the + * `roundTrip` helper at the bottom). The rest pin a specific encoding, or check that malformed wire input is rejected. + */ +describe('oracle type mappings', () => { + describe('FIELD', () => { + it('serializes to its declared shape', () => { + expect(shapeOf(FIELD.serialization!.fn(Fr.random()))).toEqual(FIELD.shape); + }); + + it('reads one input slot', () => { + expect(slotsOf(FIELD)).toBe(1); + }); + }); + + describe('U32', () => { + it('deserializes a value in range', () => { + expect(deserialize(U32, new Fr(42))).toBe(42); + }); + + it('deserializes the maximum value', () => { + expect(deserialize(U32, new Fr(0xffffffffn))).toBe(0xffffffff); + }); + + it('rejects a value above the maximum', () => { + expect(() => deserialize(U32, new Fr(0x100000000n))).toThrow('U32 overflow'); + }); + }); + + describe('BYTE', () => { + it('deserializes a value in range', () => { + expect(deserialize(BYTE, new Fr(0))).toBe(0); + }); + + it('deserializes the maximum value', () => { + expect(deserialize(BYTE, new Fr(255))).toBe(255); + }); + + it('rejects a value above the maximum', () => { + expect(() => deserialize(BYTE, new Fr(256))).toThrow('BYTE overflow'); + }); + }); + + // DELIVERY_MODE maps Noir's on-chain MessageDelivery variants (2 = unconstrained, 3 = constrained) to a tagging kind, + // and rejects any other value. + describe('DELIVERY_MODE', () => { + it('deserializes unconstrained delivery as unconstrained tagging', () => { + expect(deserialize(DELIVERY_MODE, new Fr(2))).toBe(AppTaggingSecretKind.UNCONSTRAINED); + }); + + it('deserializes constrained delivery as constrained tagging', () => { + expect(deserialize(DELIVERY_MODE, new Fr(3))).toBe(AppTaggingSecretKind.CONSTRAINED); + }); + + it('rejects an invalid value', () => { + expect(() => deserialize(DELIVERY_MODE, new Fr(1))).toThrow('Unrecognized delivery mode for tagging'); + }); + }); + + describe('AZTEC_ADDRESS', () => { + it('serializes to its declared shape', async () => { + expect(shapeOf(AZTEC_ADDRESS.serialization!.fn(await AztecAddress.random()))).toEqual(AZTEC_ADDRESS.shape); + }); + }); + + // An Option serializes to a leading discriminant slot followed by the inner's slots. + describe('OPTION', () => { + it('round-trips a Some', () => { + const value = Fr.random(); + const out = roundTrip(OPTION(FIELD), Option.some(value)); + expect(out.isSome()).toBe(true); + expect(out.value).toEqual(value); + }); + + it('round-trips a None, skipping the inner instead of parsing it', () => { + // A None still occupies the inner's slots as zero padding. REJECTS_ZERO_FIELD_TYPE throws on a zero, so a None + // whose inner were parsed would throw here; the test passing proves the inner was skipped. + const out = roundTrip(OPTION(REJECTS_ZERO_FIELD_TYPE), Option.none()); + expect(out.isNone()).toBe(true); + }); + + it('serializes a fixed None as zero-padding', () => { + // A None occupies the same slots as a Some, zero-filled. Here: [discriminant, the FIELD slot zeroed]. + expect(OPTION(FIELD).serialization!.fn(Option.none())).toEqual([Fr.ZERO, Fr.ZERO]); + }); + + it('serializes a variable None from its size descriptor', () => { + // A variable inner (array, bounded vec) has no fixed width, so the None's size comes from the descriptor passed + // to Option.none: { length } for an array, { maxLength } for a bounded vec. + expect(OPTION(ARRAY(FIELD)).serialization!.fn(Option.none({ length: 3 }))).toEqual([ + Fr.ZERO, + [Fr.ZERO, Fr.ZERO, Fr.ZERO], + ]); + expect(OPTION(BOUNDED_VEC(BYTE)).serialization!.fn(Option.none>({ maxLength: 2 }))).toEqual([ + Fr.ZERO, + [Fr.ZERO, Fr.ZERO], + Fr.ZERO, + ]); + }); + + it('rejects a variable None with no size descriptor', () => { + expect(() => OPTION(ARRAY(FIELD)).serialization!.fn(Option.none())).toThrow('needs a size'); + }); + + it('reads two input slots', () => { + expect(slotsOf(OPTION(AZTEC_ADDRESS))).toBe(2); // discriminant + inner + }); + }); + + // A BoundedVec serializes to two slots: the element storage (padded to maxLength), then the actual length. + // E.g. BoundedVec.from({ data: [0x41, 0x42], maxLength: 4 }) → storage [0x41, 0x42, 0, 0], length 2. + describe('BOUNDED_VEC', () => { + it('round-trips a full vec', () => { + const data = [Fr.random(), Fr.random()]; + const out = roundTrip(BOUNDED_VEC(FIELD), BoundedVec.from({ data, maxLength: 2 })); + expect(out.data).toEqual(data); + expect(out.maxLength).toBe(2); + }); + + it('round-trips a partially-full vec', () => { + const data = [Fr.random()]; + const out = roundTrip(BOUNDED_VEC(FIELD), BoundedVec.from({ data, maxLength: 4 })); + expect(out.data).toEqual(data); + expect(out.maxLength).toBe(4); + }); + + it('round-trips an empty vec', () => { + const out = roundTrip(BOUNDED_VEC(FIELD), BoundedVec.from({ data: [], maxLength: 3 })); + expect(out.data).toEqual([]); + expect(out.maxLength).toBe(3); + }); + + it('round-trips a vec of multi-field elements', async () => { + // A point spans two fields, so its elementSize is 2. + const data = [await Point.random(), await Point.random()]; + const out = roundTrip(BOUNDED_VEC(POINT), BoundedVec.from({ data, maxLength: 3, elementSize: 2 })); + expect(out.data).toEqual(data); + expect(out.maxLength).toBe(3); + }); + + it('round-trips a vec of multi-slot elements', () => { + const present = Fr.random(); + const data = [Option.some(present), Option.none()]; + const vec = BoundedVec.from({ data, maxLength: 3, elementSize: 2 }); + const out = roundTrip(BOUNDED_VEC(OPTION(FIELD)), vec); + expect(out.data.map(o => o.isSome())).toEqual([true, false]); + expect(out.data[0].value).toEqual(present); + expect(out.maxLength).toBe(3); + }); + + it('fully consumes a partially-full vec as an oracle param', () => { + // The registry rejects a param whose slots aren't fully read. A partially-full vec leaves zero padding in its + // storage slot, so this checks the deserializer drains that padding instead of tripping the consumption check. + const entry = makeEntry({ params: [{ name: 'ciphertext', type: BOUNDED_VEC(BYTE) }] }); + const inputs = toInputSlots(BOUNDED_VEC(BYTE).serialization!.fn(BoundedVec.from({ data: [1, 2], maxLength: 4 }))); + const [{ value }] = entry.deserializeParams(inputs); + expect(value.data).toEqual([1, 2]); + expect(value.maxLength).toBe(4); + }); + + it('rejects an element that under-reads its slot', () => { + const storage = new FieldReader([new Fr(1), new Fr(2)]); + const length = new FieldReader([new Fr(1)]); + expect(() => BOUNDED_VEC(UNDER_READS_SLOT_TYPE).deserialization!.fn([storage, length])).toThrow( + 'unexpected trailing field(s)', + ); + }); + + it('reads two input slots', () => { + expect(slotsOf(BOUNDED_VEC(FIELD))).toBe(2); // storage + length + }); + }); + + // An array serializes to a single slot holding every element's fields back to back. + describe('ARRAY', () => { + it('round-trips a mix of Some and None elements', () => { + const data = [Option.some(new Fr(7)), Option.none(), Option.some(new Fr(9))]; + const out = roundTrip(ARRAY(OPTION(FIELD)), data); + expect(out.map(o => o.isSome())).toEqual([true, false, true]); + expect(out[0].value).toEqual(new Fr(7)); + expect(out[2].value).toEqual(new Fr(9)); + }); + + it('rejects an element that under-reads its slot', () => { + expect(() => ARRAY(UNDER_READS_SLOT_TYPE).deserialization!.fn([new FieldReader([new Fr(1), new Fr(2)])])).toThrow( + 'unexpected trailing field(s)', + ); + }); + }); + + // An EphemeralArray param is a handle to a list of rows, each row being one flat slot of fields. + describe('EPHEMERAL_ARRAY', () => { + it('deserializes single-field rows', () => { + const a = Fr.random(); + const b = Fr.random(); + expect(readEphemeralArray(FIELD, [[a], [b]])).toEqual([a, b]); + }); + + it('rejects a row with too few fields', () => { + expect(() => readEphemeralArray(FIELD, [[]])).toThrow('Not enough fields to reconstruct shape'); + }); + + it('rejects a row with trailing fields', () => { + expect(() => readEphemeralArray(FIELD, [[Fr.random(), Fr.random()]])).toThrow('unexpected trailing field(s)'); + }); + + it('rejects a multi-field row with trailing fields', async () => { + const row = new LogRetrievalRequest(await AztecAddress.random(), new Tag(Fr.random())).toFields(); + expect(() => readEphemeralArray(LOG_RETRIEVAL_REQUEST, [[...row, Fr.random()]])).toThrow( + 'unexpected trailing field(s)', + ); + }); + + it('deserializes multi-slot rows with a None', () => { + // A row is one flat slot, but an Option element spans two; the deserializer rebuilds the element's slots from the + // row's fields. The None keeps that reconstruction honest (OPTION owns the skip behavior). + const present = Fr.random(); + const rows = [Option.some(present), Option.none()].map(opt => toRow(OPTION(FIELD), opt)); + const result = readEphemeralArray(OPTION(FIELD), rows); + expect(result[0].isSome()).toBe(true); + expect(result[0].value).toEqual(present); + expect(result[1].isNone()).toBe(true); + }); + + /** Reads an input-mode EphemeralArray backed by `rows`, as the registry does when deserializing a param. */ + function readEphemeralArray(element: TypeMapping, rows: Fr[][]): T[] { + const service = new EphemeralArrayService(); + const slot = service.newArray(rows); + const array = EPHEMERAL_ARRAY(element).deserialization!.fn([new FieldReader([slot])]); + return array.readAll(service); + } + + /** Serializes a value to a single flat field row, the way an EphemeralArray stores each element. */ + function toRow(element: TypeMapping, value: T): Fr[] { + return element.serialization!.fn(value).flatMap(slot => (Array.isArray(slot) ? slot : [slot])); + } + }); + + describe('MEMBERSHIP_WITNESS', () => { + it('serializes to its declared shape', () => { + const witness = MEMBERSHIP_WITNESS(4); + expect(shapeOf(witness.serialization!.fn(MembershipWitness.random(4)))).toEqual(witness.shape); + }); + }); +}); + +/** A field mapping that throws on a zero, proving a None skips its zero-padded inner instead of parsing it. */ +const REJECTS_ZERO_FIELD_TYPE: TypeMapping = { + serialization: { fn: v => [v] }, + deserialization: { + fn: ([reader]) => { + const field = reader.readField(); + if (field.isZero()) { + throw new Error('REJECTS_ZERO_FIELD_TYPE read a zero value'); + } + return field; + }, + }, + shape: ['scalar'], +}; + +/** A mapping whose shape claims two fields but whose fn reads only one, leaving a trailing field unread. */ +const UNDER_READS_SLOT_TYPE: TypeMapping = { + deserialization: { fn: ([reader]) => reader.readField() }, + shape: [{ len: 2 }], +}; + +/** Round-trips a value through a bidirectional mapping: serialize to wire slots, then deserialize back. */ +function roundTrip(mapping: TypeMapping, value: T): T { + const slots = mapping.serialization!.fn(value); + const readers = slots.map(slot => new FieldReader(Array.isArray(slot) ? slot : [slot])); + return mapping.deserialization!.fn(readers); +} + +/** Converts serialized wire slots into the hex `InputSlot[]` form the registry's `deserializeParams` expects. */ +function toInputSlots(slots: (Fr | Fr[])[]): string[][] { + return slots.map(slot => (Array.isArray(slot) ? slot : [slot]).map(f => f.toString())); +} + +/** Deserializes a single-slot scalar mapping (U32, BYTE, ...) from one field. */ +function deserialize(mapping: TypeMapping, value: Fr): T { + return mapping.deserialization!.fn([new FieldReader([value])]); +} + +/** The wire shape of an already-serialized value, for comparing against a type's declared `shape`. */ +function shapeOf(slots: (Fr | Fr[])[]): SlotShape[] { + return slots.map(slot => (Array.isArray(slot) ? { len: slot.length } : 'scalar')); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts index 931eb11c5302..7a277d059164 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts @@ -1,6 +1,6 @@ import { BLOCK_HEADER_LENGTH, - KEY_VALIDATION_REQUEST_LENGTH, + CONTRACT_INSTANCE_LENGTH, L1_TO_L2_MSG_TREE_HEIGHT, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, @@ -58,6 +58,18 @@ export type InputSlot = ACVMField[]; /** One ACVM output slot: a scalar hex string or an array of hex strings. */ export type OutputSlot = ACVMField | ACVMField[]; +/** + * Wire layout of a single slot: + * - `'scalar'` — a bare field (one `Fr`); on the ACVM/Noir wire a `Field`. + * - `{ len }` — an array of exactly `len` fields (`Fr[]`); a `[Field; len]`. + * - `'variable'` — a slot whose field count is not statically known and cannot be sized on demand (a string, a + * length-prefixed struct). It contributes to the slot count but can never be zero-filled. + * - `{ lenFrom: fn }` — like `'variable'`, but its field count can be resolved from a runtime size descriptor. The + * descriptor's shape is type-specific (e.g. `{ length }`, `{ maxLength }`), so `size` is typed `any`: a single union + * can't type each mapping's distinct `lenFrom` under parameter contravariance. + */ +export type SlotShape = 'scalar' | { len: number } | 'variable' | { lenFrom: (size: any) => number }; + /** * Describes how to serialize and/or deserialize a single typed value to/from ACVM wire format. * Either side is optional — output-only types omit `deserialization`, input-only types omit `serialization`. @@ -68,18 +80,18 @@ export interface TypeMapping { fn: (value: T) => (Fr | Fr[])[]; }; deserialization?: { - /** Read a typed value from one FieldReader per consumed slot. */ + /** Read a typed value from its slots — one {@link FieldReader} per slot, as laid out by {@link shape}. */ fn: (readers: FieldReader[]) => T; - /** - * Number of InputSlots this type reads from. `deserialization.fn` receives one FieldReader per slot in `readers`. - * - * Examples: - * - `FIELD`, `U32`, `AZTEC_ADDRESS` — single slot → `slots: 1` - * - `OPTION(T)` — discriminant + inner slots → `slots: T.slots + 1` - * - `CONTRACT_CLASS_LOG_INPUT` — [addr], [fields], [len] → `slots: 3` - */ - slots: number; }; + /** + * The type's wire layout, one entry per slot. + * + * Examples: + * - `FIELD` → `['scalar']` // single slot + * - `OPTION(T)` → `['scalar', ...T.shape]` // [discriminant], [...inner.shape] + * - `CONTRACT_CLASS_LOG_INPUT` → `['scalar', 'variable', 'scalar']` // [addr], [fields], [len] + */ + shape: SlotShape[]; } export type MaybePromise = T | Promise; @@ -101,12 +113,14 @@ export function assertReadersConsumed(readers: FieldReader[]): void { export const FIELD: TypeMapping = { serialization: { fn: v => [v] }, - deserialization: { fn: ([reader]) => reader.readField(), slots: 1 }, + deserialization: { fn: ([reader]) => reader.readField() }, + shape: ['scalar'], }; export const BOOL: TypeMapping = { serialization: { fn: v => [new Fr(v ? 1n : 0n)] }, - deserialization: { fn: ([reader]) => !reader.readField().isZero(), slots: 1 }, + deserialization: { fn: ([reader]) => !reader.readField().isZero() }, + shape: ['scalar'], }; export const U32: TypeMapping = { @@ -119,13 +133,14 @@ export const U32: TypeMapping = { } return Number(value); }, - slots: 1, }, + shape: ['scalar'], }; export const BLOCK_NUMBER: TypeMapping = { serialization: { fn: v => [new Fr(v)] }, - deserialization: { fn: ([reader]) => BlockNumber(reader.readField().toNumber()), slots: 1 }, + deserialization: { fn: ([reader]) => BlockNumber(reader.readField().toNumber()) }, + shape: ['scalar'], }; /** A u8 byte: serializes to a single Fr; deserializes from a single Fr to a number in [0, 255]. */ @@ -139,21 +154,22 @@ export const BYTE: TypeMapping = { } return Number(value); }, - slots: 1, }, + shape: ['scalar'], }; // Noir passes `MessageDelivery` onchain variants here. export const DELIVERY_MODE: TypeMapping = { deserialization: { fn: readers => appTaggingSecretKindFromDeliveryMode(BYTE.deserialization!.fn(readers)), - slots: BYTE.deserialization!.slots, }, + shape: BYTE.shape, }; export const BIGINT: TypeMapping = { serialization: { fn: v => [new Fr(v)] }, - deserialization: { fn: ([reader]) => reader.readField().toBigInt(), slots: 1 }, + deserialization: { fn: ([reader]) => reader.readField().toBigInt() }, + shape: ['scalar'], }; /** Reads every field in the slot as a UTF-8 character code. */ @@ -167,61 +183,62 @@ export const STR: TypeMapping = { } return chars.join(''); }, - slots: 1, }, + shape: ['variable'], }; export const AZTEC_ADDRESS: TypeMapping = { serialization: { fn: v => [v.toField()] }, - deserialization: { fn: ([reader]) => AztecAddress.fromField(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => AztecAddress.fromField(reader.readField()) }, + shape: ['scalar'], }; export const BLOCK_HASH: TypeMapping = { serialization: { fn: v => [new Fr(v.toBuffer())] }, - deserialization: { fn: ([reader]) => new BlockHash(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => new BlockHash(reader.readField()) }, + shape: ['scalar'], }; export const FUNCTION_SELECTOR: TypeMapping = { serialization: { fn: v => [v.toField()] }, - deserialization: { fn: ([reader]) => FunctionSelector.fromField(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => FunctionSelector.fromField(reader.readField()) }, + shape: ['scalar'], }; export const NOTE_SELECTOR: TypeMapping = { serialization: { fn: v => [v.toField()] }, - deserialization: { fn: ([reader]) => NoteSelector.fromField(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => NoteSelector.fromField(reader.readField()) }, + shape: ['scalar'], }; export const TX_HASH: TypeMapping = { serialization: { fn: v => [v.hash] }, - deserialization: { fn: ([reader]) => TxHash.fromField(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => TxHash.fromField(reader.readField()) }, + shape: ['scalar'], }; export const TAG: TypeMapping = { serialization: { fn: v => [v.value] }, - deserialization: { fn: ([reader]) => new Tag(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => new Tag(reader.readField()) }, + shape: ['scalar'], }; export const POINT: TypeMapping = { serialization: { fn: p => [p.toFields()] }, - deserialization: { - fn: ([reader]) => Point.fromFields([reader.readField(), reader.readField()]), - slots: 1, - }, + deserialization: { fn: ([reader]) => Point.fromFields([reader.readField(), reader.readField()]) }, + shape: [{ len: 2 }], }; // ─── Struct Type Mappings ──────────────────────────────────────────────────── export const BLOCK_HEADER: TypeMapping = { serialization: { fn: v => v.toFields() }, - deserialization: { fn: ([reader]) => BlockHeader.fromFields(reader.readFieldArray(BLOCK_HEADER_LENGTH)), slots: 1 }, + shape: Array(BLOCK_HEADER_LENGTH).fill('scalar'), }; export const KEY_VALIDATION_REQUEST: TypeMapping = { serialization: { fn: v => v.toFields() }, - deserialization: { - fn: ([reader]) => KeyValidationRequest.fromFields(reader.readFieldArray(KEY_VALIDATION_REQUEST_LENGTH)), - slots: 1, - }, + shape: ['scalar', 'scalar'], }; export const CONTRACT_INSTANCE: TypeMapping = { @@ -236,6 +253,7 @@ export const CONTRACT_INSTANCE: TypeMapping = { ...v.publicKeys.toFields(), ], }, + shape: Array(CONTRACT_INSTANCE_LENGTH).fill('scalar'), }; export const NULLIFIER_MEMBERSHIP_WITNESS: TypeMapping = { @@ -245,6 +263,7 @@ export const NULLIFIER_MEMBERSHIP_WITNESS: TypeMapping (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), }, + shape: ['scalar', 'scalar', 'scalar', 'scalar', { len: 42 }], }; export const PUBLIC_DATA_WITNESS: TypeMapping = { @@ -254,6 +273,7 @@ export const PUBLIC_DATA_WITNESS: TypeMapping = { .toNoirRepresentation() .map(slot => (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), }, + shape: ['scalar', 'scalar', 'scalar', 'scalar', 'scalar', { len: 40 }], }; export const MESSAGE_LOAD_ORACLE_INPUTS: TypeMapping> = { @@ -263,6 +283,7 @@ export const MESSAGE_LOAD_ORACLE_INPUTS: TypeMapping (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), }, + shape: ['scalar', { len: L1_TO_L2_MSG_TREE_HEIGHT }], // leaf index + sibling path }; export const UTILITY_CONTEXT: TypeMapping = { @@ -273,10 +294,12 @@ export const UTILITY_CONTEXT: TypeMapping = { ctx.msgSender.toField(), ], }, + shape: Array(BLOCK_HEADER_LENGTH + 2).fill('scalar'), // block header + contract address + msg sender }; export const CALL_PRIVATE_RESULT: TypeMapping<{ endSideEffectCounter: Fr; returnsHash: Fr }> = { serialization: { fn: v => [[v.endSideEffectCounter, v.returnsHash]] }, + shape: [{ len: 2 }], }; export const PUBLIC_KEYS_AND_PARTIAL_ADDRESS: TypeMapping<{ @@ -286,19 +309,20 @@ export const PUBLIC_KEYS_AND_PARTIAL_ADDRESS: TypeMapping<{ serialization: { fn: v => [[...v.publicKeys.toFields(), v.partialAddress]], }, + shape: [{ len: 8 }], // a single slot of 7 public-key fields + partial address }; export const CONTRACT_CLASS_LOG_INPUT: TypeMapping = { deserialization: { fn: ([addrReader, fieldsReader, lengthReader]) => { const addr = AztecAddress.fromField(addrReader.readField()); - const fields = new ContractClassLogFields([...fieldsReader.readFieldArray(fieldsReader.remainingFields())]); + const fields = new ContractClassLogFields(fieldsReader.readFieldArray(fieldsReader.remainingFields())); const length = lengthReader.readField().toNumber(); return new ContractClassLog(addr, fields, length); }, - // ContractClassLog input occupies 3 ACVM slots: [contractAddress], [message fields...], [length]. - slots: 3, }, + // ContractClassLog input occupies 3 slots: [contractAddress], [message fields...], [length]. + shape: ['scalar', 'variable', 'scalar'], }; export const TX_EFFECT: TypeMapping = { @@ -328,6 +352,21 @@ export const TX_EFFECT: TypeMapping = { ] as (Fr | Fr[])[]; }, }, + // Mirrors the output above: revertCode, txHash, fee, then padded note hashes / nullifiers / l2-to-l1 msgs / + // public-data writes / private logs, the public-logs length, the public-logs payload, and the contract-class logs. + shape: [ + 'scalar', + 'scalar', + 'scalar', + { len: 64 }, + { len: 64 }, + { len: 8 }, + { len: 128 }, + { len: 1088 }, + 'scalar', + { len: 4096 }, + { len: 3025 }, + ], }; export const NOTE: TypeMapping = { @@ -343,57 +382,64 @@ export const NOTE: TypeMapping = { note: noteData.note, }), }, + // A packed note is the note's (variable-count) field items followed by 6 metadata scalars, emitted as one field + // output per element. Its length depends on the note, so it is described as a single variable-width run. + shape: ['variable'], }; export const PENDING_TAGGED_LOG: TypeMapping = { serialization: { fn: log => [log.toFields()] }, + shape: [{ len: 84 }], }; export const NOTE_VALIDATION_REQUEST: TypeMapping = { deserialization: { fn: ([reader]) => NoteValidationRequest.fromFields(reader), - slots: 1, }, + shape: ['variable'], }; export const EVENT_VALIDATION_REQUEST: TypeMapping = { deserialization: { fn: ([reader]) => EventValidationRequest.fromFields(reader), - slots: 1, }, + shape: ['variable'], }; export const LOG_RETRIEVAL_REQUEST: TypeMapping = { serialization: { fn: req => [req.toFields()] }, deserialization: { fn: ([reader]) => LogRetrievalRequest.fromFields(reader), - slots: 1, }, + // address, tag, source, then (isSome, value) for each of fromBlock and toBlock. + shape: [{ len: 7 }], }; export const LOG_RETRIEVAL_RESPONSE: TypeMapping = { serialization: { fn: resp => [resp.toFields()] }, + shape: [{ len: 83 }], }; export const MESSAGE_CONTEXT: TypeMapping = { serialization: { fn: mc => [mc.toFields()] }, + shape: [{ len: 67 }], }; export const PROVIDED_SECRET: TypeMapping = { deserialization: { fn: ([reader]) => ProvidedSecret.fromFields(reader), - slots: 1, }, + shape: [{ len: 2 }], }; // ─── Combinator Type Mappings ──────────────────────────────────────────────── -/** `_height` is unused at runtime but lets TypeScript infer the exact `N` for `MembershipWitness`. */ -export function MEMBERSHIP_WITNESS(_height: N): TypeMapping> { +export function MEMBERSHIP_WITNESS(height: N): TypeMapping> { return { serialization: { fn: (witness: MembershipWitness) => [new Fr(witness.leafIndex), [...witness.siblingPath]], }, + shape: ['scalar', { len: height }], }; } @@ -407,15 +453,23 @@ export function ARRAY(inner: TypeMapping): TypeMapping & { kind: 'arr deserialization: inner.deserialization ? { fn: ([reader]) => { + // All elements are flattened into one slot; read them out one (fixed-width) element at a time, giving each + // its own per-slot readers reconstructed from the element's shape. + const elementWidth = fieldWidth(inner.shape); const result: T[] = []; while (!reader.isFinished()) { - result.push(inner.deserialization!.fn([reader])); + const fields = reader.readFieldArray(elementWidth); + const elementReader = splitByShape(fields, inner.shape); + const value = inner.deserialization!.fn(elementReader); + assertReadersConsumed(elementReader); + result.push(value); } return result; }, - slots: 1, } : undefined, + // One slot of variable length (all elements flattened into it). + shape: [{ lenFrom: (size: { length: number }) => size.length * fieldWidth(inner.shape) }], }; } @@ -452,26 +506,36 @@ export function BOUNDED_VEC( deserialization: inner.deserialization ? { fn: ([storageReader, lengthReader]) => { - const maxLength = storageReader.remainingFields(); + // slot 0 is the padded storage, slot 1 the actual length. Parse only the first `length` elements out of + // storage; the trailing zero-padding is left untouched. + const elementWidth = fieldWidth(inner.shape); + const maxLength = storageReader.remainingFields() / elementWidth; const length = lengthReader.readField().toNumber(); const elements: T[] = []; for (let i = 0; i < length; i++) { - elements.push(inner.deserialization!.fn([storageReader])); + const fields = storageReader.readFieldArray(elementWidth); + const elementReader = splitByShape(fields, inner.shape); + const value = inner.deserialization!.fn(elementReader); + assertReadersConsumed(elementReader); + elements.push(value); } // Drain the trailing zero-padding (maxLength - length unused element slots) so the storage reader is // fully consumed. storageReader.skip(storageReader.remainingFields()); return BoundedVec.from({ data: elements, maxLength }); }, - slots: 2, } : undefined, + // slot 0: variable-length storage; slot 1: the length scalar. + shape: [{ lenFrom: (size: { maxLength: number }) => size.maxLength * fieldWidth(inner.shape) }, 'scalar'], }; } /** - * Wraps an inner TypeMapping in Noir-style `Option`. Adds a discriminant slot and uses the handler-provided - * `Option.none(shape)` template to produce a correctly-sized zero-filled output for the None case. + * Wraps an inner TypeMapping in Noir-style `Option`, adding a leading discriminant slot. + * + * For the `None` case, the inner's slots must still be present on the wire as zero-padding (so `Some` and `None` have + * identical size). That padding is derived entirely from `inner.shape`. * * @example Serializing `Option.some(AztecAddress.fromField(Fr(42)))` with `OPTION(AZTEC_ADDRESS)`: * ``` @@ -479,10 +543,10 @@ export function BOUNDED_VEC( * slot 1: Fr(42) // inner value * ``` * - * @example Serializing `Option.empty(AztecAddress.ZERO)` with `OPTION(AZTEC_ADDRESS)`: + * @example Serializing `Option.none()` with `OPTION(AZTEC_ADDRESS)`: * ``` * slot 0: Fr(0) // discriminant: None - * slot 1: Fr(0) // zero-filled using shape + * slot 1: Fr(0) // zero-filled from AZTEC_ADDRESS.shape * ``` */ export function OPTION(inner: TypeMapping): TypeMapping> & { kind: 'option'; inner: TypeMapping } { @@ -491,36 +555,27 @@ export function OPTION(inner: TypeMapping): TypeMapping> & { kin inner, serialization: inner.serialization ? { - fn: opt => { - if (opt.isSome()) { - return [Fr.ONE, ...inner.serialization!.fn(opt.value)]; - } - if (opt.template === undefined) { - throw new Error( - 'Cannot serialize Option.empty() without an emptyTemplate — provide one via Option.empty(emptyTemplate)', - ); - } - const zeroSlots = inner - .serialization!.fn(opt.template) - .map(s => (Array.isArray(s) ? Array(s.length).fill(Fr.ZERO) : Fr.ZERO)); - return [Fr.ZERO, ...zeroSlots]; - }, + fn: opt => + opt.isSome() + ? [Fr.ONE, ...inner.serialization!.fn(opt.value)] + : [Fr.ZERO, ...zeroSlotsFromShape(inner.shape, opt.size)], } : undefined, deserialization: inner.deserialization ? { fn: ([discriminant, ...innerReaders]) => { if (discriminant.readField().isZero()) { - // None still carries zero-filled inner slots on the wire; drain them so the inner readers are fully - // consumed. + // None still carries the inner's zero-padded slots; consume them without parsing, since an inner that + // validates its fields would reject the zeros. innerReaders.forEach(reader => reader.skip(reader.remainingFields())); - return Option.none(undefined as unknown as T); + return Option.none(); } return Option.some(inner.deserialization!.fn(innerReaders)); }, - slots: inner.deserialization.slots + 1, } : undefined, + // A leading discriminant slot followed by the inner's slots. + shape: ['scalar', ...inner.shape], }; } @@ -535,24 +590,29 @@ export function BUFFER(bitSize: number): TypeMapping { const fields = reader.readFieldArray(reader.remainingFields()).map(f => f.toString()); return fromUintArray(fields, bitSize); }, - slots: 1, }, + shape: ['variable'], }; } export function EPHEMERAL_ARRAY(element: TypeMapping): TypeMapping> { - // An EphemeralArray param is a single slot; the per-param assert covers that slot but never sees the - // per-row readers materialized in readAll(). Assert full consumption per row here. + // EphemeralArray.readAll hands each row's flat fields in as a single reader; reconstruct the element's per-slot + // readers from its shape, deserialize, and assert the row was fully consumed so a row with trailing fields is + // rejected. const rowElement: TypeMapping | undefined = element.deserialization ? { deserialization: { - fn: readers => { + fn: ([rowReader]) => { + const fields = rowReader.readFieldArray(rowReader.remainingFields()); + const readers = splitByShape(fields, element.shape); const value = element.deserialization!.fn(readers); assertReadersConsumed(readers); return value; }, - slots: element.deserialization.slots, }, + // `fn` reads the whole row from one reader, so this is one variable-width slot, not the element's multi-slot + // shape. + shape: ['variable'], } : undefined; return { @@ -560,7 +620,75 @@ export function EPHEMERAL_ARRAY(element: TypeMapping): TypeMapping [ea.materializeSlot(v => element.serialization!.fn(v).flat() as Fr[])] } : undefined, deserialization: rowElement - ? { fn: ([reader]) => EphemeralArray.fromSlot(reader.readField(), rowElement), slots: 1 } + ? { fn: ([reader]) => EphemeralArray.fromSlot(reader.readField(), rowElement) } : undefined, + // A single slot carrying the array's service-slot id. + shape: ['scalar'], }; } + +/** Number of InputSlots a deserializable type spans, derived from its {@link TypeMapping.shape}. */ +export function slotsOf(mapping: TypeMapping): number { + return mapping.shape.length; +} + +/** Number of fields a fully-static shape occupies. Throws on a variable-width shape, whose field count isn't known. */ +function fieldWidth(shape: SlotShape[]): number { + return shape.reduce((acc, slot) => { + if (slot === 'scalar') { + return acc + 1; + } + if (typeof slot === 'object' && 'len' in slot) { + return acc + slot.len; + } + throw new Error('Cannot compute a fixed field width for a variable-width shape'); + }, 0); +} + +/** Reconstructs a value's per-slot readers from a flat run of fields, using its shape (inverse of slot-flattening). */ +function splitByShape(fields: Fr[], shape: SlotShape[]): FieldReader[] { + const readers: FieldReader[] = []; + let cursor = 0; + shape.forEach((slot, i) => { + if (slot === 'scalar' || (typeof slot === 'object' && 'len' in slot)) { + const width = slot === 'scalar' ? 1 : slot.len; + if (cursor + width > fields.length) { + throw new Error(`Not enough fields to reconstruct shape: needed ${width}, had ${fields.length - cursor}`); + } + readers.push(new FieldReader(fields.slice(cursor, cursor + width))); + cursor += width; + } else { + // A variable slot (sized or not) takes whatever remains, so it must be last. + if (i !== shape.length - 1) { + throw new Error('A variable-width slot must be last to be reconstructed from a flat field array'); + } + readers.push(new FieldReader(fields.slice(cursor))); + cursor = fields.length; + } + }); + if (cursor !== fields.length) { + throw new Error(`Malformed flattened value: ${fields.length - cursor} unexpected trailing field(s)`); + } + return readers; +} + +/** Builds the zero-filled slots for a `None`, matching a `Some`'s wire shape (variable slots sized from `size`). */ +function zeroSlotsFromShape(shape: SlotShape[], size: unknown): (Fr | Fr[])[] { + return shape.map(slot => { + if (slot === 'scalar') { + return Fr.ZERO; + } + if (slot === 'variable') { + throw new Error('Cannot zero-fill an unsized variable-width slot'); + } + if ('len' in slot) { + return Array(slot.len).fill(Fr.ZERO); + } + if (size === undefined) { + throw new Error( + 'Serializing Option.none() over a variable-size inner needs a size, e.g. Option.none({ length: n })', + ); + } + return Array(slot.lenFrom(size)).fill(Fr.ZERO); + }); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 7fae6481e3e4..cf691842f04d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -177,9 +177,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * Returns the wallet-supplied default sender for tags, or `None` if no default was provided. */ public getSenderForTags(): Promise> { - return Promise.resolve( - this.defaultSenderForTags ? Option.some(this.defaultSenderForTags) : Option.none(AztecAddress.ZERO), - ); + return Promise.resolve(this.defaultSenderForTags ? Option.some(this.defaultSenderForTags) : Option.none()); } /** @@ -199,7 +197,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.logger.warn(`Computing a tagging secret for invalid recipient ${recipient} - returning no secret`, { contractAddress: this.contractAddress, }); - return Option.none(Fr.ZERO); + return Option.none(); } return Option.some(extendedSecret.secret); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index f398542c3654..d4972acf33c0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -201,11 +201,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra /** * Retrieve keys associated with a specific master public key and app address. * @param pkMHash - The master public key hash. + * @param _keyIndex - Sent by the Noir oracle caller but unused here; kept to match the oracle signature. * @returns A Promise that resolves to nullifier keys. * @throws If the keys are not registered in the key store. * @throws If scopes are defined and the account is not in the scopes. */ - public async getKeyValidationRequest(pkMHash: Fr): Promise { + public async getKeyValidationRequest(pkMHash: Fr, _keyIndex: Fr): Promise { let hasAccess = false; for (let i = 0; i < this.scopes.length && !hasAccess; i++) { if (await this.keyStore.accountHasKey(this.scopes[i], pkMHash)) { @@ -259,7 +260,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const witness = await this.#queryWithBlockHashNotAfterAnchor(referenceBlockHash, () => this.aztecNode.getBlockHashMembershipWitness(referenceBlockHash, blockHash), ); - return witness ? Option.some(witness) : Option.none(MembershipWitness.empty(ARCHIVE_HEIGHT)); + return witness ? Option.some(witness) : Option.none(); } /** @@ -351,7 +352,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra ): Promise> { const completeAddress = await this.addressStore.getCompleteAddress(account); if (!completeAddress) { - return Option.none({ publicKeys: PublicKeys.default(), partialAddress: Fr.ZERO }); + return Option.none(); } return Option.some({ publicKeys: completeAddress.publicKeys, partialAddress: completeAddress.partialAddress }); } @@ -650,9 +651,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.anchorBlockHeader.getBlockNumber(), ); - const options = maybeMessageContexts.map(mc => - mc ? Option.some(mc) : Option.none(MessageContext.empty()), - ); + const options = maybeMessageContexts.map(mc => (mc ? Option.some(mc) : Option.none())); return EphemeralArray.fromValues(this.ephemeralArrayService, options); } @@ -667,7 +666,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const receipt = await this.aztecNode.getTxReceipt(txHash, { includeTxEffect: true }); if (!receipt.isMined() || !receipt.txEffect || receipt.blockNumber > this.anchorBlockHeader.getBlockNumber()) { - return Option.none(TxEffect.empty()); + return Option.none(); } return Option.some(receipt.txEffect); @@ -692,7 +691,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`); } const values = await this.capsuleService.getCapsule(contractAddress, slot, this.jobId, scope, this.capsules); - return values ? Option.some(values) : Option.none(new Array(tSize).fill(Fr.ZERO)); + return values ? Option.some(values) : Option.none({ length: tSize }); } public deleteCapsule(contractAddress: AztecAddress, slot: Fr, scope: AztecAddress): void { @@ -740,7 +739,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const plaintext = await aes128.decryptBufferCBC(Buffer.from(ciphertext.data), iv, symKey); return Option.some(BoundedVec.from({ data: [...plaintext], maxLength: capacity })); } catch { - return Option.none(BoundedVec.empty({ maxLength: capacity })); + return Option.none({ maxLength: capacity }); } } diff --git a/yarn-project/pxe/src/oracle_version.ts b/yarn-project/pxe/src/oracle_version.ts index 9b9a096ea9f8..813a9281c170 100644 --- a/yarn-project/pxe/src/oracle_version.ts +++ b/yarn-project/pxe/src/oracle_version.ts @@ -19,4 +19,4 @@ export const ORACLE_VERSION_MINOR = 0; /// - increment only `ORACLE_VERSION_MINOR` if the change is additive (a new oracle was added). /// /// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`. -export const ORACLE_INTERFACE_HASH = 'f5cd3321b32371186f30dfd11b246946fb425cadffb8e6564b897d5184e43fe9'; +export const ORACLE_INTERFACE_HASH = '04e973e69ac73ab958cec7908abcfc40fdac5615a10c890808928c962d91c652'; diff --git a/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts b/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts index 4230904a4ac1..844b2c1d7057 100644 --- a/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts +++ b/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts @@ -40,7 +40,7 @@ const TEST_FIXTURES: Record = { test_single: [{ inputs: { slot: new Fr(10), addr: AztecAddress.fromNumber(1) }, output: new Fr(42) }], test_multi: [ { scenario: 'some', inputs: {}, output: Option.some(AztecAddress.fromNumber(7)) }, - { scenario: 'none', inputs: {}, output: Option.none(AztecAddress.ZERO) }, + { scenario: 'none', inputs: {}, output: Option.none() }, ], test_labeled: [ { scenario: 'some', inputs: { slot: new Fr(10) }, output: Option.some(new Fr(1)) }, diff --git a/yarn-project/txe/src/oracle/txe_oracle_registry.ts b/yarn-project/txe/src/oracle/txe_oracle_registry.ts index 3d3fc4aa098e..d17a1935fced 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_registry.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_registry.ts @@ -4,6 +4,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + PRIVATE_CONTEXT_INPUTS_LENGTH, PRIVATE_LOG_SIZE_IN_FIELDS, } from '@aztec/constants'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -23,6 +24,7 @@ import { type OracleRegistryEntry, type ParamTypes, STR, + type SlotShape, type TypeMapping, U32, makeEntry, @@ -43,19 +45,20 @@ import { import type { ForeignCallArgs, ForeignCallResult } from '../utils/encoding.js'; const GAS_SETTINGS: TypeMapping = { - serialization: { fn: v => v.toFields() }, deserialization: { fn: ([reader]) => GasSettings.fromFields(reader.readFieldArray(GAS_SETTINGS_LENGTH)), - slots: 1, }, + shape: [{ len: GAS_SETTINGS_LENGTH }], }; const PRIVATE_CONTEXT_INPUTS: TypeMapping = { serialization: { fn: v => v.toFields() }, + shape: Array(PRIVATE_CONTEXT_INPUTS_LENGTH).fill('scalar'), }; const COMPLETE_ADDRESS: TypeMapping = { serialization: { fn: v => [v.address.toField(), ...v.publicKeys.toFields()] }, + shape: Array(8).fill('scalar'), // address + 7 public-key fields }; const TXE_TX_EFFECTS: TypeMapping<{ @@ -91,6 +94,17 @@ const TXE_TX_EFFECTS: TypeMapping<{ ] as (Fr | Fr[])[]; }, }, + // txHash, padded note hashes + count, padded nullifiers + count, flattened private-log storage + lengths + count. + shape: [ + 'scalar', + { len: MAX_NOTE_HASHES_PER_TX }, + 'scalar', + { len: MAX_NULLIFIERS_PER_TX }, + 'scalar', + { len: MAX_PRIVATE_LOGS_PER_TX * PRIVATE_LOG_SIZE_IN_FIELDS }, + { len: MAX_PRIVATE_LOGS_PER_TX }, + 'scalar', + ], }; const TXE_OFFCHAIN_EFFECTS: TypeMapping<{ effects: Fr[][] }> = { @@ -109,6 +123,12 @@ const TXE_OFFCHAIN_EFFECTS: TypeMapping<{ effects: Fr[][] }> = { return [rawArrayStorage, effectLengths, new Fr(effects.length)] as (Fr | Fr[])[]; }, }, + // Flattened effect storage, per-effect lengths, then the effect count. + shape: [ + { len: MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY * MAX_OFFCHAIN_EFFECT_LEN }, + { len: MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY }, + 'scalar', + ], }; const TXE_CALL_CONTEXT: TypeMapping<{ txHash: Fr; anchorBlockTimestamp: bigint }> = { @@ -118,15 +138,18 @@ const TXE_CALL_CONTEXT: TypeMapping<{ txHash: Fr; anchorBlockTimestamp: bigint } return [new Fr(isSome), txHash, new Fr(anchorBlockTimestamp)]; }, }, + shape: ['scalar', 'scalar', 'scalar'], // discriminant, txHash, anchor block timestamp }; const CONTRACT_INSTANCE_MEMBER: TypeMapping<{ member: Fr; exists: boolean }> = { serialization: { fn: ({ member, exists }) => [member, new Fr(exists)] }, + shape: ['scalar', 'scalar'], }; const EVENT_SELECTOR: TypeMapping = { serialization: { fn: v => [v.toField()] }, - deserialization: { fn: ([reader]) => EventSelector.fromField(reader.readField()), slots: 1 }, + deserialization: { fn: ([reader]) => EventSelector.fromField(reader.readField()) }, + shape: ['scalar'], }; const TXE_PRIVATE_EVENTS: TypeMapping = { @@ -144,6 +167,12 @@ const TXE_PRIVATE_EVENTS: TypeMapping = { return [rawArrayStorage, eventLengths, new Fr(events.length)] as (Fr | Fr[])[]; }, }, + // Flattened event storage, per-event lengths, then the event count. + shape: [ + { len: MAX_PRIVATE_EVENTS_PER_TXE_QUERY * MAX_PRIVATE_EVENT_LEN }, + { len: MAX_PRIVATE_EVENTS_PER_TXE_QUERY }, + 'scalar', + ], }; export const TXE_ORACLE_REGISTRY = { diff --git a/yarn-project/txe/src/rpc_translator.ts b/yarn-project/txe/src/rpc_translator.ts index ae56166087a7..f99c66c8dfc5 100644 --- a/yarn-project/txe/src/rpc_translator.ts +++ b/yarn-project/txe/src/rpc_translator.ts @@ -439,7 +439,7 @@ export class RPCTranslator { return callTxeHandler({ oracle: 'aztec_utl_getKeyValidationRequest', inputs, - handler: ([pkMHash]) => this.handlerAsUtility().getKeyValidationRequest(pkMHash), + handler: ([pkMHash, keyIndex]) => this.handlerAsUtility().getKeyValidationRequest(pkMHash, keyIndex), }); } From b84449319b8581db2d709cd0e21fa75b342207cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 22 Jun 2026 17:18:21 -0300 Subject: [PATCH 4/8] feat: allow registration of raw shared secrets (#23708) --- .../contract_function_simulator.ts | 12 +- .../oracle/oracle_version_is_checked.test.ts | 10 +- .../oracle/private_execution.test.ts | 11 +- .../oracle/private_execution_oracle.test.ts | 4 +- .../oracle/private_execution_oracle.ts | 2 +- .../oracle/utility_execution.test.ts | 13 ++- .../oracle/utility_execution_oracle.ts | 12 +- yarn-project/pxe/src/logs/log_service.test.ts | 8 +- yarn-project/pxe/src/logs/log_service.ts | 80 ++++++++----- yarn-project/pxe/src/pxe.ts | 61 ++++++++-- .../__snapshots__/SenderAddressBookStore.json | 16 --- .../TaggingSecretSourcesStore.json | 30 +++++ .../__snapshots__/opened_stores.json | 12 +- .../schema_tests.ts | 23 ++-- .../pxe/src/storage/open_pxe_stores.ts | 6 +- .../pxe/src/storage/tagging_store/index.ts | 2 +- .../sender_address_book_store.ts | 48 -------- .../tagging_secret_sources_store.test.ts | 88 +++++++++++++++ .../tagging_secret_sources_store.ts | 106 ++++++++++++++++++ .../src/logs/app_tagging_secret.test.ts | 58 +++++++++- .../stdlib/src/logs/app_tagging_secret.ts | 58 ++++++---- .../oracle/txe_oracle_top_level_context.ts | 8 +- yarn-project/txe/src/txe_session.ts | 18 +-- 23 files changed, 502 insertions(+), 184 deletions(-) delete mode 100644 yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/SenderAddressBookStore.json create mode 100644 yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/TaggingSecretSourcesStore.json delete mode 100644 yarn-project/pxe/src/storage/tagging_store/sender_address_book_store.ts create mode 100644 yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.test.ts create mode 100644 yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index aafe16558774..56de5e8b8778 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -100,8 +100,8 @@ import type { ContractStore } from '../storage/contract_store/contract_store.js' import type { NoteStore } from '../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../storage/tagging_store/sender_address_book_store.js'; import type { SenderTaggingStore } from '../storage/tagging_store/sender_tagging_store.js'; +import type { TaggingSecretSourcesStore } from '../storage/tagging_store/tagging_secret_sources_store.js'; import type { BenchmarkedNode } from './benchmarked_node.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; @@ -136,7 +136,7 @@ export type ContractFunctionSimulatorArgs = { l2TipsStore: L2TipsProvider; senderTaggingStore: SenderTaggingStore; recipientTaggingStore: RecipientTaggingStore; - senderAddressBookStore: SenderAddressBookStore; + taggingSecretSourcesStore: TaggingSecretSourcesStore; capsuleStore: CapsuleStore; privateEventStore: PrivateEventStore; simulator: CircuitSimulator; @@ -158,7 +158,7 @@ export class ContractFunctionSimulator { private readonly l2TipsStore: L2TipsProvider; private readonly senderTaggingStore: SenderTaggingStore; private readonly recipientTaggingStore: RecipientTaggingStore; - private readonly senderAddressBookStore: SenderAddressBookStore; + private readonly taggingSecretSourcesStore: TaggingSecretSourcesStore; private readonly capsuleStore: CapsuleStore; private readonly privateEventStore: PrivateEventStore; private readonly simulator: CircuitSimulator; @@ -175,7 +175,7 @@ export class ContractFunctionSimulator { this.l2TipsStore = args.l2TipsStore; this.senderTaggingStore = args.senderTaggingStore; this.recipientTaggingStore = args.recipientTaggingStore; - this.senderAddressBookStore = args.senderAddressBookStore; + this.taggingSecretSourcesStore = args.taggingSecretSourcesStore; this.capsuleStore = args.capsuleStore; this.privateEventStore = args.privateEventStore; this.simulator = args.simulator; @@ -251,7 +251,7 @@ export class ContractFunctionSimulator { aztecNode: this.aztecNode, senderTaggingStore: this.senderTaggingStore, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, scopes), privateEventStore: this.privateEventStore, messageContextService: this.messageContextService, @@ -353,7 +353,7 @@ export class ContractFunctionSimulator { addressStore: this.addressStore, aztecNode: this.aztecNode, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, scopes), privateEventStore: this.privateEventStore, messageContextService: this.messageContextService, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 6ccecd7a1d6f..1d805f7e78cc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -24,8 +24,8 @@ import type { ContractStore } from '../../storage/contract_store/contract_store. import type { NoteStore } from '../../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_address_book_store.js'; import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js'; +import type { TaggingSecretSourcesStore } from '../../storage/tagging_store/tagging_secret_sources_store.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import { TransientArrayService } from '../transient_array_service.js'; import { buildACIRCallback } from './acir_callback.js'; @@ -41,7 +41,7 @@ describe('Oracle Version Check test suite', () => { let aztecNode: ReturnType>; let senderTaggingStore: ReturnType>; let recipientTaggingStore: ReturnType>; - let senderAddressBookStore: ReturnType>; + let taggingSecretSourcesStore: ReturnType>; let capsuleStore: ReturnType>; let privateEventStore: ReturnType>; let contractSyncService: ReturnType>; @@ -62,7 +62,7 @@ describe('Oracle Version Check test suite', () => { aztecNode = mock(); senderTaggingStore = mock(); recipientTaggingStore = mock(); - senderAddressBookStore = mock(); + taggingSecretSourcesStore = mock(); capsuleStore = mock(); privateEventStore = mock(); contractSyncService = mock(); @@ -107,7 +107,7 @@ describe('Oracle Version Check test suite', () => { l2TipsStore: mock(), senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, simulator, @@ -208,7 +208,7 @@ describe('Oracle Version Check test suite', () => { addressStore, aztecNode, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleService: new CapsuleService(capsuleStore, []), privateEventStore, messageContextService, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index d019ec71a001..de0e017b0046 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -61,8 +61,8 @@ import type { ContractStore } from '../../storage/contract_store/contract_store. import type { NoteStore } from '../../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_address_book_store.js'; import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js'; +import type { TaggingSecretSourcesStore } from '../../storage/tagging_store/tagging_secret_sources_store.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; jest.setTimeout(60_000); @@ -107,7 +107,7 @@ describe('Private Execution test suite', () => { let keyStore: MockProxy; let senderTaggingStore: MockProxy; let recipientTaggingStore: MockProxy; - let senderAddressBookStore: MockProxy; + let taggingSecretSourcesStore: MockProxy; let aztecNode: MockProxy; let capsuleStore: MockProxy; let privateEventStore: MockProxy; @@ -288,7 +288,7 @@ describe('Private Execution test suite', () => { capsuleStore = mock(); l2TipsStore = mock(); privateEventStore = mock(); - senderAddressBookStore = mock(); + taggingSecretSourcesStore = mock(); contractSyncService = mock(); messageContextService = mock(); messageContextService.getMessageContextsByTxHash.mockResolvedValue([]); @@ -311,7 +311,8 @@ describe('Private Execution test suite', () => { senderTaggingStore.getTxHashesOfPendingIndexes.mockResolvedValue([]); senderTaggingStore.storePendingIndexes.mockResolvedValue(); - senderAddressBookStore.getSenders.mockResolvedValue([]); + taggingSecretSourcesStore.getSenders.mockResolvedValue([]); + taggingSecretSourcesStore.getSharedSecrets.mockResolvedValue([]); // Mock aztec node methods - the return array needs to have the same length as the number of tags // on the input. @@ -455,7 +456,7 @@ describe('Private Execution test suite', () => { l2TipsStore, senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, simulator, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts index b5705dad2af8..300e03b25d6e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts @@ -21,8 +21,8 @@ import type { ContractStore } from '../../storage/contract_store/contract_store. import type { NoteStore } from '../../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_address_book_store.js'; import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js'; +import type { TaggingSecretSourcesStore } from '../../storage/tagging_store/tagging_secret_sources_store.js'; import { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import { HashedValuesCache } from '../hashed_values_cache.js'; @@ -88,7 +88,7 @@ describe('PrivateExecutionOracle', () => { aztecNode: mock(), senderTaggingStore: mock(), recipientTaggingStore: mock(), - senderAddressBookStore: mock(), + taggingSecretSourcesStore: mock(), capsuleService: new CapsuleService(mock(), []), privateEventStore: mock(), messageContextService: mock(), diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index cf691842f04d..bf666fe23e8c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -557,7 +557,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP aztecNode: this.aztecNode, senderTaggingStore: this.senderTaggingStore, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: this.capsuleService, privateEventStore: this.privateEventStore, messageContextService: this.messageContextService, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts index bc70e96e76d3..b7d36f6f0264 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts @@ -51,8 +51,8 @@ import type { ContractStore } from '../../storage/contract_store/contract_store. import type { NoteStore } from '../../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_address_book_store.js'; import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js'; +import type { TaggingSecretSourcesStore } from '../../storage/tagging_store/tagging_secret_sources_store.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import { EphemeralArrayService } from '../ephemeral_array_service.js'; import { BoundedVec } from '../noir-structs/bounded_vec.js'; @@ -71,7 +71,7 @@ describe('Utility Execution test suite', () => { let aztecNode: ReturnType>; let senderTaggingStore: ReturnType>; let recipientTaggingStore: ReturnType>; - let senderAddressBookStore: ReturnType>; + let taggingSecretSourcesStore: ReturnType>; let capsuleStore: ReturnType>; let privateEventStore: ReturnType>; let contractSyncService: ReturnType>; @@ -95,7 +95,7 @@ describe('Utility Execution test suite', () => { aztecNode = mock(); senderTaggingStore = mock(); recipientTaggingStore = mock(); - senderAddressBookStore = mock(); + taggingSecretSourcesStore = mock(); capsuleStore = mock(); privateEventStore = mock(); contractSyncService = mock(); @@ -107,7 +107,8 @@ describe('Utility Execution test suite', () => { senderTaggingStore.getLastUsedIndex.mockResolvedValue(undefined); senderTaggingStore.getTxHashesOfPendingIndexes.mockResolvedValue([]); senderTaggingStore.storePendingIndexes.mockResolvedValue(); - senderAddressBookStore.getSenders.mockResolvedValue([]); + taggingSecretSourcesStore.getSenders.mockResolvedValue([]); + taggingSecretSourcesStore.getSharedSecrets.mockResolvedValue([]); l2TipsStore.getL2Tips.mockResolvedValue(makeL2Tips(anchorBlockHeader.globalVariables.blockNumber)); aztecNode.getPrivateLogsByTags.mockImplementation(query => Promise.resolve(query.tags.map(() => []))); @@ -128,7 +129,7 @@ describe('Utility Execution test suite', () => { l2TipsStore, senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, simulator, @@ -702,7 +703,7 @@ describe('Utility Execution test suite', () => { addressStore, aztecNode, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleService: new CapsuleService(capsuleStore, scopes), privateEventStore, messageContextService, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index d4972acf33c0..e372dff03ed6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -59,7 +59,7 @@ import type { ContractStore } from '../../storage/contract_store/contract_store. import type { NoteStore } from '../../storage/note_store/note_store.js'; import type { PrivateEventStore } from '../../storage/private_event_store/private_event_store.js'; import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_address_book_store.js'; +import type { TaggingSecretSourcesStore } from '../../storage/tagging_store/tagging_secret_sources_store.js'; import { EphemeralArrayService } from '../ephemeral_array_service.js'; import { BoundedVec } from '../noir-structs/bounded_vec.js'; import { EphemeralArray } from '../noir-structs/ephemeral_array.js'; @@ -90,7 +90,7 @@ export type UtilityExecutionOracleArgs = { addressStore: AddressStore; aztecNode: AztecNode; recipientTaggingStore: RecipientTaggingStore; - senderAddressBookStore: SenderAddressBookStore; + taggingSecretSourcesStore: TaggingSecretSourcesStore; capsuleService: CapsuleService; privateEventStore: PrivateEventStore; messageContextService: MessageContextService; @@ -132,7 +132,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly addressStore: AddressStore; protected readonly aztecNode: AztecNode; protected readonly recipientTaggingStore: RecipientTaggingStore; - protected readonly senderAddressBookStore: SenderAddressBookStore; + protected readonly taggingSecretSourcesStore: TaggingSecretSourcesStore; protected readonly capsuleService: CapsuleService; protected readonly privateEventStore: PrivateEventStore; protected readonly messageContextService: MessageContextService; @@ -156,7 +156,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.addressStore = args.addressStore; this.aztecNode = args.aztecNode; this.recipientTaggingStore = args.recipientTaggingStore; - this.senderAddressBookStore = args.senderAddressBookStore; + this.taggingSecretSourcesStore = args.taggingSecretSourcesStore; this.capsuleService = args.capsuleService; this.privateEventStore = args.privateEventStore; this.messageContextService = args.messageContextService; @@ -586,7 +586,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.l2TipsStore, this.keyStore, this.recipientTaggingStore, - this.senderAddressBookStore, + this.taggingSecretSourcesStore, this.addressStore, this.jobId, this.logger.getBindings(), @@ -906,7 +906,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra addressStore: this.addressStore, aztecNode: this.aztecNode, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: this.capsuleService, privateEventStore: this.privateEventStore, messageContextService: this.messageContextService, diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index f3b8385328aa..591b3a68b6b9 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -14,7 +14,7 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { LogRetrievalRequest, LogSource } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; import { AddressStore } from '../storage/address_store/address_store.js'; import { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js'; -import { SenderAddressBookStore } from '../storage/tagging_store/sender_address_book_store.js'; +import { TaggingSecretSourcesStore } from '../storage/tagging_store/tagging_secret_sources_store.js'; import { LogService } from './log_service.js'; describe('LogService', () => { @@ -23,7 +23,7 @@ describe('LogService', () => { let keyStore: KeyStore; let recipientTaggingStore: RecipientTaggingStore; let addressStore: AddressStore; - let senderAddressBookStore: SenderAddressBookStore; + let taggingSecretSourcesStore: TaggingSecretSourcesStore; let logService: LogService; describe('fetchLogsByTag', () => { @@ -34,7 +34,7 @@ describe('LogService', () => { contractAddress = await AztecAddress.random(); keyStore = new KeyStore(await openTmpStore('test')); recipientTaggingStore = new RecipientTaggingStore(await openTmpStore('test')); - senderAddressBookStore = new SenderAddressBookStore(await openTmpStore('test')); + taggingSecretSourcesStore = new TaggingSecretSourcesStore(await openTmpStore('test')); addressStore = new AddressStore(await openTmpStore('test')); aztecNode = mock(); @@ -52,7 +52,7 @@ describe('LogService', () => { mock(), keyStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, addressStore, 'test', ); diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 0b9ed0dbe282..a1ea74309805 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -1,10 +1,18 @@ import type { BlockNumber } from '@aztec/foundation/branded-types'; +import type { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockHash, L2TipsProvider } from '@aztec/stdlib/block'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { AppTaggingSecret, type LogResult, PendingTaggedLog, SiloedTag } from '@aztec/stdlib/logs'; +import { + AppTaggingSecret, + type LogResult, + PendingTaggedLog, + SiloedTag, + computeSharedTaggingSecret, +} from '@aztec/stdlib/logs'; import type { BlockHeader } from '@aztec/stdlib/tx'; import { @@ -14,7 +22,7 @@ import { import { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js'; import { AddressStore } from '../storage/address_store/address_store.js'; import type { RecipientTaggingStore } from '../storage/tagging_store/recipient_tagging_store.js'; -import type { SenderAddressBookStore } from '../storage/tagging_store/sender_address_book_store.js'; +import type { TaggingSecretSourcesStore } from '../storage/tagging_store/tagging_secret_sources_store.js'; import { getAllPrivateLogsByTags, getAllPublicLogsByTagsFromContract, @@ -34,7 +42,7 @@ export class LogService { private readonly l2TipsStore: L2TipsProvider, private readonly keyStore: KeyStore, private readonly recipientTaggingStore: RecipientTaggingStore, - private readonly senderAddressBookStore: SenderAddressBookStore, + private readonly taggingSecretSourcesStore: TaggingSecretSourcesStore, private readonly addressStore: AddressStore, private readonly jobId: string, bindings?: LoggerBindings, @@ -171,12 +179,20 @@ export class LogService { recipient: AztecAddress, providedSecrets: AppTaggingSecret[], ): Promise { - this.log.verbose(`Fetching tagged logs for ${contractAddress.toString()}`); + this.log.verbose( + `Fetching tagged logs for contract ${contractAddress.toString()} and recipient ${recipient.toString()}`, + ); const l2Tips = await this.l2TipsStore.getL2Tips(); - // The secrets PXE derives or stores internally, plus any the app supplies explicitly for secrets PXE cannot - // enumerate itself (e.g. handshake-derived ones). - const secrets = [...(await this.#getSecretsForSenders(contractAddress, recipient)), ...providedSecrets]; + + // Secrets PXE can enumerate for this recipient (senders via ECDH + pre-shared store secrets), plus any the app + // supplies explicitly for secrets PXE cannot enumerate itself (e.g. handshake-derived ones). The latter arrive + // already computed and are searched even when the recipient's account is unknown locally, since they need no ECDH. + const combinedSecrets = [...(await this.#getPointDerivedSecrets(contractAddress, recipient)), ...providedSecrets]; + + // These sources can overlap (a sender that is also a PXE account, or a pre-shared secret that coincides with a + // sender-derived one), so we deduplicate the combined set. + const secrets = Array.from(new Map(combinedSecrets.map(secret => [secret.toString(), secret])).values()); const logs = await syncTaggedPrivateLogs( secrets, @@ -197,40 +213,50 @@ export class LogService { }); } - async #getSecretsForSenders(contractAddress: AztecAddress, recipient: AztecAddress): Promise { + /** + * Computes the tagging secrets PXE can enumerate for a recipient: one per known sender (via ECDH) plus any + * pre-shared secret points registered directly for the recipient, each siloed to `contractAddress` and directed to + * `recipient`. These require knowing the recipient's address preimage and keys, so returns an empty array when those + * are unavailable. App-supplied secrets (e.g. handshake-derived) are handled separately by the caller and do not go + * through here. + */ + async #getPointDerivedSecrets(contractAddress: AztecAddress, recipient: AztecAddress): Promise { const recipientCompleteAddress = await this.addressStore.getCompleteAddress(recipient); - if (!recipientCompleteAddress) { + if (!recipientCompleteAddress || !(await this.keyStore.hasAccount(recipient))) { + this.log.warn( + `Skipping sender-derived tag retrieval for ${recipient.toString()} due to unknown address preimage`, + ); return []; } const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); - // We implicitly add all PXE accounts as senders, this helps us find tagged logs with messages that are sent to a - // local account (recipient = us, sender = us) - const allSenders = [...(await this.senderAddressBookStore.getSenders()), ...(await this.keyStore.getAccounts())]; + const points = [ + ...(await this.#getSecretsForSenders(recipientCompleteAddress, recipientIvsk)), + ...(await this.taggingSecretSourcesStore.getSharedSecrets(recipient)), + ]; + return Promise.all(points.map(secret => AppTaggingSecret.compute(secret, contractAddress, recipient))); + } - // We deduplicate the senders by adding them to a set and then converting the set back to an array - const deduplicatedSenders = Array.from(new Set(allSenders.map(sender => sender.toString()))).map(sender => - AztecAddress.fromString(sender), - ); + async #getSecretsForSenders( + recipientCompleteAddress: CompleteAddress, + recipientIvsk: GrumpkinScalar, + ): Promise { + // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves + // (recipient = us, sender = us). + const allSenders = [...(await this.taggingSecretSourcesStore.getSenders()), ...(await this.keyStore.getAccounts())]; return Promise.all( - deduplicatedSenders.map(async sender => { - const secret = await AppTaggingSecret.computeUnconstrained( - recipientCompleteAddress, - recipientIvsk, - sender, - contractAddress, - recipient, - ); + allSenders.map(async sender => { + const taggingSecretPoint = await computeSharedTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); - if (!secret) { - // Note that all senders originate from either the SenderAddressBookStore or the KeyStore. + if (!taggingSecretPoint) { + // Note that all senders originate from either the TaggingSecretSourcesStore or the KeyStore. throw new Error( `Failed to compute a tagging secret for sender ${sender} - this implies this is an invalid address, which should not happen as they have been previously registered in PXE.`, ); } - return secret; + return taggingSecretPoint; }), ); } diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index d4a08a45abe8..8ee37ca591b0 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -1,6 +1,7 @@ import type { PrivateEventFilter } from '@aztec/aztec.js/wallet'; import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { Point } from '@aztec/foundation/curves/grumpkin'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; import { Timer } from '@aztec/foundation/timer'; @@ -82,8 +83,8 @@ import { NoteStore } from './storage/note_store/note_store.js'; import { openPxeStores } from './storage/open_pxe_stores.js'; import { PrivateEventStore } from './storage/private_event_store/private_event_store.js'; import { RecipientTaggingStore } from './storage/tagging_store/recipient_tagging_store.js'; -import { SenderAddressBookStore } from './storage/tagging_store/sender_address_book_store.js'; import { SenderTaggingStore } from './storage/tagging_store/sender_tagging_store.js'; +import { TaggingSecretSourcesStore } from './storage/tagging_store/tagging_secret_sources_store.js'; import { persistSenderTaggingIndexRangesForTx } from './tagging/index.js'; export type PackedPrivateEvent = InTx & { @@ -203,7 +204,7 @@ export class PXE { private capsuleStore: CapsuleStore, private anchorBlockStore: AnchorBlockStore, private senderTaggingStore: SenderTaggingStore, - private senderAddressBookStore: SenderAddressBookStore, + private taggingSecretSourcesStore: TaggingSecretSourcesStore, private recipientTaggingStore: RecipientTaggingStore, private addressStore: AddressStore, private privateEventStore: PrivateEventStore, @@ -269,7 +270,7 @@ export class PXE { noteStore, anchorBlockStore, senderTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, recipientTaggingStore, capsuleStore, keyStore, @@ -320,7 +321,7 @@ export class PXE { capsuleStore, anchorBlockStore, senderTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, recipientTaggingStore, addressStore, privateEventStore, @@ -367,7 +368,7 @@ export class PXE { l2TipsStore: this.l2TipsStore, senderTaggingStore: this.senderTaggingStore, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleStore: this.capsuleStore, privateEventStore: this.privateEventStore, simulator: this.simulator, @@ -681,7 +682,7 @@ export class PXE { return sender; } - const wasAdded = await this.senderAddressBookStore.addSender(sender); + const wasAdded = await this.taggingSecretSourcesStore.addSender(sender); if (wasAdded) { this.log.info(`Added sender:\n ${sender.toString()}`); @@ -700,7 +701,7 @@ export class PXE { * @returns Senders registered in this PXE. */ public getSenders(): Promise { - return this.senderAddressBookStore.getSenders(); + return this.taggingSecretSourcesStore.getSenders(); } /** @@ -708,7 +709,7 @@ export class PXE { * @param sender - The address of the sender to remove. */ public async removeSender(sender: AztecAddress): Promise { - const wasRemoved = await this.senderAddressBookStore.removeSender(sender); + const wasRemoved = await this.taggingSecretSourcesStore.removeSender(sender); if (wasRemoved) { this.log.info(`Removed sender:\n ${sender.toString()}`); @@ -717,6 +718,50 @@ export class PXE { } } + /** + * Registers a pre-shared tagging secret scoped to a recipient, used to discover private logs tagged with it. + * + * Unlike a registered sender (whose shared secret is derived via ECDH), this is the shared secret point itself, + * provided directly. It is scoped to a recipient because the per-app derivation of the final tagging secret requires + * no further private information i.e. there is no ECDH step - reuse of such secrets by multiple recipients could lead + * to a privacy loss as they'd all be able to find each others' tags. + */ + public async registerSharedSecret(recipient: AztecAddress, secret: Point): Promise { + if (!(await recipient.isValid())) { + throw new Error( + `Recipient ${recipient} is not valid: it does not correspond to a point on the Grumpkin curve. Cannot register a shared secret for it.`, + ); + } + + if (secret.isZero() || !secret.isOnCurve()) { + throw new Error(`Shared secret ${secret} is not a valid non-zero point on the Grumpkin curve.`); + } + + const wasAdded = await this.taggingSecretSourcesStore.addSharedSecret(recipient, secret); + + if (wasAdded) { + this.log.info(`Added shared secret for recipient:\n ${recipient.toString()}`); + // Wipe the entire sync cache: the new secret's tagged logs could contain notes/events for any contract, so + // all contracts must re-sync to discover them. Queued to avoid wiping while a job is in flight. + await this.#putInJobQueue(() => Promise.resolve(this.contractSyncService.wipe())); + } else { + this.log.info(`Shared secret already registered for recipient:\n ${recipient.toString()}`); + } + } + + /** + * Removes a pre-shared tagging secret registered in this PXE. + */ + public async removeSharedSecret(recipient: AztecAddress, secret: Point): Promise { + const wasRemoved = await this.taggingSecretSourcesStore.removeSharedSecret(recipient, secret); + + if (wasRemoved) { + this.log.info(`Removed shared secret for recipient:\n ${recipient.toString()}`); + } else { + this.log.info(`Shared secret not registered for recipient:\n ${recipient.toString()}`); + } + } + /** * Retrieves the user accounts registered on this PXE. * @returns An array of the accounts registered on this PXE. diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/SenderAddressBookStore.json b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/SenderAddressBookStore.json deleted file mode 100644 index e601b6d1ccc1..000000000000 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/SenderAddressBookStore.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "address_book": [ - { - "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000002", - "value": "true" - }, - { - "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000003", - "value": "true" - }, - { - "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000005", - "value": "true" - } - ] -} diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/TaggingSecretSourcesStore.json b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/TaggingSecretSourcesStore.json new file mode 100644 index 000000000000..1c612022458a --- /dev/null +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/TaggingSecretSourcesStore.json @@ -0,0 +1,30 @@ +{ + "senders": [ + { + "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000002", + "value": "true" + }, + { + "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000003", + "value": "true" + }, + { + "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000005", + "value": "true" + } + ], + "recipient_shared_secrets": [ + { + "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000007", + "value": "utf8:0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" + }, + { + "key": "utf8:0x0000000000000000000000000000000000000000000000000000000000000007", + "value": "utf8:0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000007" + }, + { + "key": "utf8:0x000000000000000000000000000000000000000000000000000000000000000b", + "value": "utf8:0x000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000011" + } + ] +} diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/opened_stores.json b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/opened_stores.json index c2754970b4d6..e1dc76547144 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/opened_stores.json +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/opened_stores.json @@ -1,10 +1,6 @@ { "schemaVersion": 9, "stores": [ - { - "name": "address_book", - "kind": "map" - }, { "name": "capsules", "kind": "map" @@ -96,6 +92,14 @@ { "name": "pxe_l2_tips", "kind": "map" + }, + { + "name": "recipient_shared_secrets", + "kind": "multimap" + }, + { + "name": "senders", + "kind": "map" } ] } diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts index 593591b994c0..a872ab5a90d0 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts @@ -2,6 +2,7 @@ import { CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { Point } from '@aztec/foundation/curves/grumpkin'; import { EthAddress } from '@aztec/foundation/eth-address'; import type { Tuple } from '@aztec/foundation/serialize'; import { KeyStore } from '@aztec/key-store'; @@ -41,7 +42,7 @@ import { CapsuleStore } from '../capsule_store/capsule_store.js'; import { ContractStore } from '../contract_store/contract_store.js'; import { NoteStore } from '../note_store/note_store.js'; import { PrivateEventStore } from '../private_event_store/private_event_store.js'; -import { RecipientTaggingStore, SenderAddressBookStore, SenderTaggingStore } from '../tagging_store/index.js'; +import { RecipientTaggingStore, SenderTaggingStore, TaggingSecretSourcesStore } from '../tagging_store/index.js'; import { snapshotArray, snapshotMap, snapshotSingleton } from './kv_store_snapshot.js'; /** @@ -506,16 +507,24 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ }, { - name: 'SenderAddressBookStore', + name: 'TaggingSecretSourcesStore', writeToStore: async kvStore => { - const senderAddressBookStore = new SenderAddressBookStore(kvStore); + const taggingSecretSourcesStore = new TaggingSecretSourcesStore(kvStore); - await senderAddressBookStore.addSender(AztecAddress.fromBigInt(2n)); - await senderAddressBookStore.addSender(AztecAddress.fromBigInt(3n)); - await senderAddressBookStore.addSender(AztecAddress.fromBigInt(5n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(2n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(3n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(5n)); + + await taggingSecretSourcesStore.addSharedSecret(AztecAddress.fromBigInt(7n), new Point(new Fr(2n), new Fr(3n))); + await taggingSecretSourcesStore.addSharedSecret(AztecAddress.fromBigInt(7n), new Point(new Fr(5n), new Fr(7n))); + await taggingSecretSourcesStore.addSharedSecret( + AztecAddress.fromBigInt(11n), + new Point(new Fr(13n), new Fr(17n)), + ); }, snapshotStore: async kvStore => ({ - address_book: await snapshotMap(kvStore.openMap('address_book')), + senders: await snapshotMap(kvStore.openMap('senders')), + recipient_shared_secrets: await snapshotMap(kvStore.openMultiMap('recipient_shared_secrets')), }), }, diff --git a/yarn-project/pxe/src/storage/open_pxe_stores.ts b/yarn-project/pxe/src/storage/open_pxe_stores.ts index 573b85a40984..65341a871ca7 100644 --- a/yarn-project/pxe/src/storage/open_pxe_stores.ts +++ b/yarn-project/pxe/src/storage/open_pxe_stores.ts @@ -9,7 +9,7 @@ import { CapsuleStore } from './capsule_store/capsule_store.js'; import { ContractStore } from './contract_store/contract_store.js'; import { NoteStore } from './note_store/note_store.js'; import { PrivateEventStore } from './private_event_store/private_event_store.js'; -import { RecipientTaggingStore, SenderAddressBookStore, SenderTaggingStore } from './tagging_store/index.js'; +import { RecipientTaggingStore, SenderTaggingStore, TaggingSecretSourcesStore } from './tagging_store/index.js'; /** * The set of sub-stores opened against a single `AztecAsyncKVStore` to back PXE state. @@ -21,7 +21,7 @@ export type PxeStores = { noteStore: NoteStore; anchorBlockStore: AnchorBlockStore; senderTaggingStore: SenderTaggingStore; - senderAddressBookStore: SenderAddressBookStore; + taggingSecretSourcesStore: TaggingSecretSourcesStore; recipientTaggingStore: RecipientTaggingStore; capsuleStore: CapsuleStore; keyStore: KeyStore; @@ -40,7 +40,7 @@ export function openPxeStores(store: AztecAsyncKVStore, initialBlockHash: BlockH noteStore: new NoteStore(store), anchorBlockStore: new AnchorBlockStore(store), senderTaggingStore: new SenderTaggingStore(store), - senderAddressBookStore: new SenderAddressBookStore(store), + taggingSecretSourcesStore: new TaggingSecretSourcesStore(store), recipientTaggingStore: new RecipientTaggingStore(store), capsuleStore: new CapsuleStore(store), keyStore: new KeyStore(store), diff --git a/yarn-project/pxe/src/storage/tagging_store/index.ts b/yarn-project/pxe/src/storage/tagging_store/index.ts index 21cc2bd0f463..170c68823791 100644 --- a/yarn-project/pxe/src/storage/tagging_store/index.ts +++ b/yarn-project/pxe/src/storage/tagging_store/index.ts @@ -1,3 +1,3 @@ export { SenderTaggingStore } from './sender_tagging_store.js'; -export { SenderAddressBookStore } from './sender_address_book_store.js'; +export { TaggingSecretSourcesStore } from './tagging_secret_sources_store.js'; export { RecipientTaggingStore } from './recipient_tagging_store.js'; diff --git a/yarn-project/pxe/src/storage/tagging_store/sender_address_book_store.ts b/yarn-project/pxe/src/storage/tagging_store/sender_address_book_store.ts deleted file mode 100644 index 540a7334b457..000000000000 --- a/yarn-project/pxe/src/storage/tagging_store/sender_address_book_store.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { toArray } from '@aztec/foundation/iterable'; -import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; - -/** - * Stores sender addresses. During recipient log synchronization, these senders are used, along with a given recipient, - * to derive directional app tagging secrets that are then used to sync the logs. - */ -export class SenderAddressBookStore { - #store: AztecAsyncKVStore; - #addressBook: AztecAsyncMap; - - constructor(store: AztecAsyncKVStore) { - this.#store = store; - - this.#addressBook = this.#store.openMap('address_book'); - } - - addSender(address: AztecAddress): Promise { - return this.#store.transactionAsync(async () => { - if (await this.#addressBook.hasAsync(address.toString())) { - return false; - } - - await this.#addressBook.set(address.toString(), true); - - return true; - }); - } - - getSenders(): Promise { - return this.#store.transactionAsync(async () => { - return (await toArray(this.#addressBook.keysAsync())).map(AztecAddress.fromString); - }); - } - - removeSender(address: AztecAddress): Promise { - return this.#store.transactionAsync(async () => { - if (!(await this.#addressBook.hasAsync(address.toString()))) { - return false; - } - - await this.#addressBook.delete(address.toString()); - - return true; - }); - } -} diff --git a/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.test.ts b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.test.ts new file mode 100644 index 000000000000..90ab14cb72f2 --- /dev/null +++ b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.test.ts @@ -0,0 +1,88 @@ +import { Point } from '@aztec/foundation/curves/grumpkin'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; + +import { TaggingSecretSourcesStore } from './tagging_secret_sources_store.js'; + +describe('TaggingSecretSourcesStore', () => { + let store: TaggingSecretSourcesStore; + + beforeEach(async () => { + store = new TaggingSecretSourcesStore(await openTmpStore('test')); + }); + + describe('senders', () => { + it('adds, lists and removes senders', async () => { + const sender = await AztecAddress.random(); + + expect(await store.addSender(sender)).toBe(true); + expect(await store.addSender(sender)).toBe(false); + expect(await store.getSenders()).toEqual([sender]); + + expect(await store.removeSender(sender)).toBe(true); + expect(await store.removeSender(sender)).toBe(false); + expect(await store.getSenders()).toEqual([]); + }); + }); + + describe('shared secrets', () => { + it('adds and retrieves shared secrets scoped to a recipient', async () => { + const recipient = await AztecAddress.random(); + const secret = await Point.random(); + + expect(await store.addSharedSecret(recipient, secret)).toBe(true); + expect(await store.getSharedSecrets(recipient)).toEqual([secret]); + }); + + it('returns false when adding a duplicate secret for the same recipient', async () => { + const recipient = await AztecAddress.random(); + const secret = await Point.random(); + + expect(await store.addSharedSecret(recipient, secret)).toBe(true); + expect(await store.addSharedSecret(recipient, secret)).toBe(false); + expect(await store.getSharedSecrets(recipient)).toEqual([secret]); + }); + + it('scopes secrets per recipient', async () => { + const recipientA = await AztecAddress.random(); + const recipientB = await AztecAddress.random(); + const secretA = await Point.random(); + const secretB = await Point.random(); + + await store.addSharedSecret(recipientA, secretA); + await store.addSharedSecret(recipientB, secretB); + + expect(await store.getSharedSecrets(recipientA)).toEqual([secretA]); + expect(await store.getSharedSecrets(recipientB)).toEqual([secretB]); + }); + + it('removes a shared secret', async () => { + const recipient = await AztecAddress.random(); + const secret = await Point.random(); + + await store.addSharedSecret(recipient, secret); + + expect(await store.removeSharedSecret(recipient, secret)).toBe(true); + expect(await store.getSharedSecrets(recipient)).toEqual([]); + }); + + it('returns false when removing a secret that is not registered', async () => { + const recipient = await AztecAddress.random(); + const secret = await Point.random(); + + expect(await store.removeSharedSecret(recipient, secret)).toBe(false); + }); + + it('keeps senders and shared secrets separate', async () => { + const recipient = await AztecAddress.random(); + const sender = await AztecAddress.random(); + const secret = await Point.random(); + + await store.addSender(sender); + await store.addSharedSecret(recipient, secret); + + expect(await store.getSenders()).toEqual([sender]); + expect(await store.getSharedSecrets(recipient)).toEqual([secret]); + }); + }); +}); diff --git a/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts new file mode 100644 index 000000000000..aff377255fd7 --- /dev/null +++ b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts @@ -0,0 +1,106 @@ +import { Point } from '@aztec/foundation/curves/grumpkin'; +import { toArray } from '@aztec/foundation/iterable'; +import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; + +/** + * Stores the sources from which directional app tagging secrets are derived during recipient log synchronization. + * + * Two kinds of source are held: + * - Sender addresses: combined with a recipient to derive a shared tagging secret via ECDH. These are global (not + * scoped to a recipient) because the per-recipient binding comes from re-mixing the recipient's keys during + * derivation, so each account only ever derives secrets meant for it. + * - Pre-shared tagging secrets: shared secret points registered directly, bypassing ECDH. These are scoped to a + * specific recipient, since the derivation of the directional app tagging secret does not require any secret + * recipient data: given the original secret anyone can derive a recipient's app-siloed directional tagging secret, + * and so these must not be reused to preserve privacy. + */ +export class TaggingSecretSourcesStore { + #store: AztecAsyncKVStore; + #senders: AztecAsyncMap; + #sharedSecretsByRecipient: AztecAsyncMultiMap; + + constructor(store: AztecAsyncKVStore) { + this.#store = store; + + this.#senders = this.#store.openMap('senders'); + this.#sharedSecretsByRecipient = this.#store.openMultiMap('recipient_shared_secrets'); + } + + addSender(address: AztecAddress): Promise { + return this.#store.transactionAsync(async () => { + if (await this.#senders.hasAsync(address.toString())) { + return false; + } + + await this.#senders.set(address.toString(), true); + + return true; + }); + } + + getSenders(): Promise { + return this.#store.transactionAsync(async () => { + return (await toArray(this.#senders.keysAsync())).map(AztecAddress.fromString); + }); + } + + removeSender(address: AztecAddress): Promise { + return this.#store.transactionAsync(async () => { + if (!(await this.#senders.hasAsync(address.toString()))) { + return false; + } + + await this.#senders.delete(address.toString()); + + return true; + }); + } + + /** + * Registers a pre-shared tagging secret scoped to a recipient. + * @returns true if the secret was newly added, false if it was already registered for that recipient. + */ + addSharedSecret(recipient: AztecAddress, secret: Point): Promise { + return this.#store.transactionAsync(async () => { + const secretStr = secret.toString(); + // MultiMap.set silently ignores an identical (key, value), so we scan to report whether this is a new secret. + for await (const existing of this.#sharedSecretsByRecipient.getValuesAsync(recipient.toString())) { + if (existing === secretStr) { + return false; + } + } + + await this.#sharedSecretsByRecipient.set(recipient.toString(), secretStr); + + return true; + }); + } + + /** Returns the pre-shared tagging secrets registered for a given recipient. */ + getSharedSecrets(recipient: AztecAddress): Promise { + return this.#store.transactionAsync(async () => { + return (await toArray(this.#sharedSecretsByRecipient.getValuesAsync(recipient.toString()))).map(secret => + Point.fromString(secret), + ); + }); + } + + /** + * Removes a pre-shared tagging secret scoped to a recipient. + * @returns true if the secret was registered and removed, false if it was not registered for that recipient. + */ + removeSharedSecret(recipient: AztecAddress, secret: Point): Promise { + return this.#store.transactionAsync(async () => { + const secretStr = secret.toString(); + for await (const existing of this.#sharedSecretsByRecipient.getValuesAsync(recipient.toString())) { + if (existing === secretStr) { + await this.#sharedSecretsByRecipient.deleteValue(recipient.toString(), secretStr); + return true; + } + } + + return false; + }); + } +} diff --git a/yarn-project/stdlib/src/logs/app_tagging_secret.test.ts b/yarn-project/stdlib/src/logs/app_tagging_secret.test.ts index b9fe23ba7e72..3f60ad75a7b7 100644 --- a/yarn-project/stdlib/src/logs/app_tagging_secret.test.ts +++ b/yarn-project/stdlib/src/logs/app_tagging_secret.test.ts @@ -1,10 +1,14 @@ import { DomainSeparator } from '@aztec/constants'; import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { Point } from '@aztec/foundation/curves/grumpkin'; +import { AztecAddress } from '../aztec-address/index.js'; +import { CompleteAddress } from '../contract/complete_address.js'; import { computeLogTag, computeSiloedPrivateLogFirstField } from '../hash/hash.js'; +import { deriveMasterIncomingViewingSecretKey } from '../keys/derivation.js'; import { randomAppTaggingSecret } from '../tests/factories.js'; -import { AppTaggingSecret } from './app_tagging_secret.js'; +import { AppTaggingSecret, computeSharedTaggingSecret } from './app_tagging_secret.js'; import { AppTaggingSecretKind } from './app_tagging_secret_kind.js'; import { SiloedTag } from './siloed_tag.js'; import { TaggingIndexRangeSchema } from './tagging_index_range.js'; @@ -45,6 +49,58 @@ describe('AppTaggingSecret', () => { }); }); + describe('compute', () => { + it('is a deterministic function of the point, app and recipient', async () => { + const point = await Point.random(); + const app = await AztecAddress.random(); + const recipient = await AztecAddress.random(); + + const a = await AppTaggingSecret.compute(point, app, recipient); + const b = await AppTaggingSecret.compute(point, app, recipient); + expect(b.secret).toEqual(a.secret); + + const otherApp = await AppTaggingSecret.compute(point, await AztecAddress.random(), recipient); + expect(otherApp.secret).not.toEqual(a.secret); + + const otherRecipient = await AppTaggingSecret.compute(point, app, await AztecAddress.random()); + expect(otherRecipient.secret).not.toEqual(a.secret); + }); + + // Registering a pre-shared tagging secret point directly must yield the same directional secret as the ECDH-derived + // sender path. Both sides of the Diffie-Hellman exchange compute the same shared point, so a recipient that + // registers that point discovers exactly the tags a sender would emit. + it('a directly registered shared point matches the ECDH-derived sender secret', async () => { + const recipientSecretKey = Fr.random(); + const recipientComplete = await CompleteAddress.fromSecretKeyAndPartialAddress(recipientSecretKey, Fr.random()); + const recipientIvsk = deriveMasterIncomingViewingSecretKey(recipientSecretKey); + + const senderSecretKey = Fr.random(); + const senderComplete = await CompleteAddress.fromSecretKeyAndPartialAddress(senderSecretKey, Fr.random()); + const senderIvsk = deriveMasterIncomingViewingSecretKey(senderSecretKey); + + const app = await AztecAddress.random(); + + // The recipient derives the shared point against the sender. + const pointFromRecipient = await computeSharedTaggingSecret( + recipientComplete, + recipientIvsk, + senderComplete.address, + ); + // The sender derives the same point against the recipient (Diffie-Hellman symmetry). + const pointFromSender = await computeSharedTaggingSecret(senderComplete, senderIvsk, recipientComplete.address); + + expect(pointFromRecipient).toBeDefined(); + expect(pointFromSender).toEqual(pointFromRecipient); + + const secretViaEcdh = await AppTaggingSecret.compute(pointFromRecipient!, app, recipientComplete.address); + // Registering the shared point directly (bypassing ECDH) derives the identical secret. + const secretViaRegistration = await AppTaggingSecret.compute(pointFromSender!, app, recipientComplete.address); + + expect(secretViaRegistration.secret).toEqual(secretViaEcdh.secret); + expect(secretViaRegistration.app).toEqual(app); + }); + }); + describe('fromString', () => { it('round-trips an unconstrained secret', async () => { const original = await randomAppTaggingSecret(AppTaggingSecretKind.UNCONSTRAINED); diff --git a/yarn-project/stdlib/src/logs/app_tagging_secret.ts b/yarn-project/stdlib/src/logs/app_tagging_secret.ts index f0d441ee170a..a6a3f0599382 100644 --- a/yarn-project/stdlib/src/logs/app_tagging_secret.ts +++ b/yarn-project/stdlib/src/logs/app_tagging_secret.ts @@ -30,16 +30,33 @@ export class AppTaggingSecret { ) {} /** - * Derives shared tagging secret and from that, the app address and recipient derives the directional app tagging - * secret. Returns undefined if `externalAddress` is an invalid address. + * Derives an app-siloed, recipient-directional tagging secret from a shared tagging secret point. * - * @param localAddress - The complete address of entity A in the shared tagging secret derivation scheme - * @param localIvsk - The incoming viewing secret key of entity A - * @param externalAddress - The address of entity B in the shared tagging secret derivation scheme + * The point is obtained either via {@link computeSharedTaggingSecret} (an ECDH key exchange against a sender) or + * registered directly as a pre-shared secret. Each secret point yields a distinct tagging secret per (app, recipient) + * pair. + * + * @param taggingSecretPoint - The shared tagging secret point (ECDH output, or a directly registered pre-shared secret) * @param app - Contract address to silo the secret to * @param recipient - Recipient of the log. Defines the "direction of the secret". * @returns The secret that can be used along with an index to compute a tag to be included in a log. */ + static async compute( + taggingSecretPoint: Point, + app: AztecAddress, + recipient: AztecAddress, + ): Promise { + const appTaggingSecret = await poseidon2Hash([taggingSecretPoint.x, taggingSecretPoint.y, app]); + const directionalAppTaggingSecret = await poseidon2Hash([appTaggingSecret, recipient]); + + return new AppTaggingSecret(directionalAppTaggingSecret, app); + } + + /** + * Derives the unconstrained tagging secret for `(externalAddress, recipient, app)` by performing the ECDH key + * exchange against `externalAddress` and then siloing and directing the result. Returns undefined if + * `externalAddress` is not a valid address. + */ static async computeUnconstrained( localAddress: CompleteAddress, localIvsk: Fq, @@ -52,10 +69,7 @@ export class AppTaggingSecret { return undefined; } - const appTaggingSecret = await poseidon2Hash([taggingSecretPoint.x, taggingSecretPoint.y, app]); - const directionalAppTaggingSecret = await poseidon2Hash([appTaggingSecret, recipient]); - - return new AppTaggingSecret(directionalAppTaggingSecret, app); + return AppTaggingSecret.compute(taggingSecretPoint, app, recipient); } toString(): string { @@ -111,18 +125,17 @@ function appTaggingSecretKindFromString(kind: string): AppTaggingSecretKind { } } -// Returns shared tagging secret computed with Diffie-Hellman key exchange, or undefined if `externalAddress` is an -// invalid address. -async function computeSharedTaggingSecret( +/** + * Computes the shared tagging secret point between a local address (i.e. one for which the privacy keys are known) and + * an external one via a Diffie-Hellman key exchange. + * + * Returns undefined if `externalAddress` is an invalid address. + */ +export async function computeSharedTaggingSecret( localAddress: CompleteAddress, localIvsk: Fq, externalAddress: AztecAddress, ): Promise { - // Given A (local complete address) -> B (external address) and h == preaddress - // Compute shared secret as S = (h_A + local_ivsk_A) * Addr_Point_B - - const knownPreaddress = await computePreaddress(await localAddress.publicKeys.hash(), localAddress.partialAddress); - // An invalid address has no corresponding address point if (!(await externalAddress.isValid())) { return undefined; @@ -130,8 +143,11 @@ async function computeSharedTaggingSecret( const externalAddressPoint = await externalAddress.toAddressPoint(); - // Beware! h_a + local_ivsk_a (also known as the address secret) can lead to an address point with a negative - // y-coordinate, since there's two possible candidates computeAddressSecret takes care of selecting the one that - // leads to a positive y-coordinate, which is the only valid address point - return Grumpkin.mul(externalAddressPoint, await computeAddressSecret(knownPreaddress, localIvsk)); + const localPreaddress = await computePreaddress(await localAddress.publicKeys.hash(), localAddress.partialAddress); + const localAddressSecret = await computeAddressSecret(localPreaddress, localIvsk); + + // For a given local address A and external address B, the shared tagging secret S is (h_A + ivsk_A) * Addr_Point_B + // (conceptually, in reality we don't use h_A + ivsk_A directly but rather the actual address secret, which is the + // same but with an optional sign adjustment to prevent A's address point from having a negative y-coordinate). + return Grumpkin.mul(externalAddressPoint, localAddressSecret); } diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 17139ca29023..6755a14b7a3c 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -19,8 +19,8 @@ import { ORACLE_VERSION_MAJOR, PrivateEventStore, RecipientTaggingStore, - SenderAddressBookStore, SenderTaggingStore, + TaggingSecretSourcesStore, composeHooks, enrichPublicSimulationError, } from '@aztec/pxe/server'; @@ -107,7 +107,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private accountStore: TXEAccountStore, private senderTaggingStore: SenderTaggingStore, private recipientTaggingStore: RecipientTaggingStore, - private senderAddressBookStore: SenderAddressBookStore, + private taggingSecretSourcesStore: TaggingSecretSourcesStore, private capsuleStore: CapsuleStore, private privateEventStore: PrivateEventStore, private nextBlockTimestamp: bigint, @@ -449,7 +449,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl aztecNode: this.stateMachine.node, senderTaggingStore: this.senderTaggingStore, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, scopes), privateEventStore: this.privateEventStore, contractSyncService: this.stateMachine.contractSyncService, @@ -848,7 +848,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl addressStore: this.addressStore, aztecNode: this.stateMachine.node, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, scopes), privateEventStore: this.privateEventStore, messageContextService: this.stateMachine.messageContextService, diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 903d81f45c6d..54edc95bea7f 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -15,8 +15,8 @@ import { NoteStore, PrivateEventStore, RecipientTaggingStore, - SenderAddressBookStore, SenderTaggingStore, + TaggingSecretSourcesStore, } from '@aztec/pxe/server'; import { ExecutionNoteCache, @@ -252,7 +252,7 @@ export class TXESession implements TXESessionStateHandler { private accountStore: TXEAccountStore, private senderTaggingStore: SenderTaggingStore, private recipientTaggingStore: RecipientTaggingStore, - private senderAddressBookStore: SenderAddressBookStore, + private taggingSecretSourcesStore: TaggingSecretSourcesStore, private capsuleStore: CapsuleStore, private privateEventStore: PrivateEventStore, private jobCoordinator: JobCoordinator, @@ -303,7 +303,7 @@ export class TXESession implements TXESessionStateHandler { const noteStore = new NoteStore(store); const senderTaggingStore = new SenderTaggingStore(store); const recipientTaggingStore = new RecipientTaggingStore(store); - const senderAddressBookStore = new SenderAddressBookStore(store); + const taggingSecretSourcesStore = new TaggingSecretSourcesStore(store); const capsuleStore = new CapsuleStore(store); const keyStore = new KeyStore(store); const accountStore = new TXEAccountStore(store); @@ -338,7 +338,7 @@ export class TXESession implements TXESessionStateHandler { accountStore, senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, nextBlockTimestamp, @@ -364,7 +364,7 @@ export class TXESession implements TXESessionStateHandler { accountStore, senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, jobCoordinator, @@ -653,7 +653,7 @@ export class TXESession implements TXESessionStateHandler { this.accountStore, this.senderTaggingStore, this.recipientTaggingStore, - this.senderAddressBookStore, + this.taggingSecretSourcesStore, this.capsuleStore, this.privateEventStore, this.nextBlockTimestamp, @@ -722,7 +722,7 @@ export class TXESession implements TXESessionStateHandler { aztecNode: this.stateMachine.node, senderTaggingStore: this.senderTaggingStore, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()), privateEventStore: this.privateEventStore, contractSyncService: this.stateMachine.contractSyncService, @@ -815,7 +815,7 @@ export class TXESession implements TXESessionStateHandler { addressStore: this.addressStore, aztecNode: this.stateMachine.node, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()), privateEventStore: this.privateEventStore, messageContextService: this.stateMachine.messageContextService, @@ -922,7 +922,7 @@ export class TXESession implements TXESessionStateHandler { addressStore: this.addressStore, aztecNode: this.stateMachine.node, recipientTaggingStore: this.recipientTaggingStore, - senderAddressBookStore: this.senderAddressBookStore, + taggingSecretSourcesStore: this.taggingSecretSourcesStore, capsuleService: new CapsuleService(this.capsuleStore, scopes), privateEventStore: this.privateEventStore, messageContextService: this.stateMachine.messageContextService, From bc092c573271dbac10058f52ca1a037e5d1c3073 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 22 Jun 2026 17:25:08 -0400 Subject: [PATCH 5/8] feat(e2e): constrained delivery and discovery with two PXEs (#24228) Fixes [F-664](https://linear.app/aztec-labs/issue/F-664/e2e-test-constrained-delivery-via-non-interactive-handshake) Add e2e tests for discovering events and notes sent with constrained delivery. --------- Co-authored-by: Nicolas Chamo --- .../src/main.nr | 16 ++- .../src/test.nr | 6 +- .../src/e2e_constrained_delivery.test.ts | 135 ++++++++++++++++-- 3 files changed, 140 insertions(+), 17 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr index b52c3e96d146..70ff711f9dd5 100644 --- a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr @@ -8,6 +8,7 @@ pub contract ConstrainedDeliveryTest { use aztec::{ macros::{events::event, functions::external, storage::storage}, messages::delivery::{MessageDelivery, OnchainDeliveryMode}, + note::note_viewer_options::NoteViewerOptions, oracle::notes::get_next_tagging_index, protocol::{ address::AztecAddress, @@ -41,8 +42,8 @@ pub contract ConstrainedDeliveryTest { } #[external("private")] - fn emit_note(recipient: AztecAddress) { - self.storage.notes.at(recipient).insert(FieldNote { value: 1 }).deliver(MessageDelivery::onchain_constrained()); + fn emit_note(recipient: AztecAddress, value: Field) { + self.storage.notes.at(recipient).insert(FieldNote { value }).deliver(MessageDelivery::onchain_constrained()); } #[external("private")] @@ -51,8 +52,8 @@ pub contract ConstrainedDeliveryTest { } #[external("private")] - fn emit_event(recipient: AztecAddress) { - self.emit(DeliveryEvent { value: 1 }).deliver_to(recipient, MessageDelivery::onchain_constrained()); + fn emit_event(recipient: AztecAddress, value: Field) { + self.emit(DeliveryEvent { value }).deliver_to(recipient, MessageDelivery::onchain_constrained()); } #[external("private")] @@ -73,6 +74,13 @@ pub contract ConstrainedDeliveryTest { self.call(registry.get_app_siloed_secret(sender, recipient, mode)) } + /// Returns the values of all `FieldNote`s discovered for `owner`. The e2e test derives both the count and the + /// sum from this, confirming each delivered note was discovered and decrypted. + #[external("utility")] + unconstrained fn get_note_values(owner: AztecAddress) -> BoundedVec { + self.storage.notes.at(owner).view_notes(NoteViewerOptions::new()).map(|note| note.value) + } + /// Test-only: emits a log under the constrained tag for `(secret, index)` WITHOUT the sequence nullifier. A landed /// constrained-tagged log advances the PXE's per-secret index, so this lets tests reach the `index > 0` branch; /// deliberately skipping the nullifier drives the negative test. diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr index 4001064ab170..2cc740b4fb19 100644 --- a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr @@ -45,7 +45,7 @@ unconstrained fn rehandshake_replaces_registry_secret_for_future_delivery() { let test_contract = ConstrainedDeliveryTest::at(test_address); let registry = HandshakeRegistry::at(registry_address); - env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); + env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient, 1)); let first_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(test_address), @@ -62,7 +62,7 @@ unconstrained fn rehandshake_replaces_registry_secret_for_future_delivery() { .expect(f"re-handshake should have stored a replacement handshake"); assert(first_secret != second_secret, "re-handshake should produce a distinct secret"); - env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); + env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient, 1)); let next_index = env.call_private(sender, test_contract.next_index_for_secret(second_secret)); assert_eq(next_index, 1); @@ -84,5 +84,5 @@ unconstrained fn fails_at_index_above_zero_without_prior_nullifier() { env.call_private(sender, test_contract.emit_constrained_log_without_nullifier(secret, 0)); - let _ = env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient)); + let _ = env.call_private_opts(sender, authorizing(registry_address), test_contract.emit_event(recipient, 1)); } diff --git a/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts index 021a88b8b4e7..fa06a85dd969 100644 --- a/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts +++ b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts @@ -1,14 +1,23 @@ +import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing'; +import { NO_FROM } from '@aztec/aztec.js/account'; import type { AztecAddress } from '@aztec/aztec.js/addresses'; import { BatchCall } from '@aztec/aztec.js/contracts'; +import type { AztecNode } from '@aztec/aztec.js/node'; import type { Wallet } from '@aztec/aztec.js/wallet'; +import { BlockNumber } from '@aztec/foundation/branded-types'; import { HandshakeRegistryContract } from '@aztec/noir-contracts.js/HandshakeRegistry'; -import { ConstrainedDeliveryTestContract } from '@aztec/noir-test-contracts.js/ConstrainedDeliveryTest'; +import { + ConstrainedDeliveryTestContract, + type DeliveryEvent, +} from '@aztec/noir-test-contracts.js/ConstrainedDeliveryTest'; import { STANDARD_HANDSHAKE_REGISTRY_ADDRESS } from '@aztec/standard-contracts/handshake-registry/constants'; +import type { AztecNodeDebug } from '@aztec/stdlib/interfaces/client'; import { jest } from '@jest/globals'; import { AUTOMINE_E2E_OPTS } from './fixtures/fixtures.js'; -import { ensureHandshakeRegistryPublished, setup } from './fixtures/setup.js'; +import { ensureHandshakeRegistryPublished, setup, setupPXEAndGetWallet } from './fixtures/setup.js'; +import { TestWallet } from './test-wallet/test_wallet.js'; const ONCHAIN_CONSTRAINED = { inner: 3 }; @@ -41,14 +50,14 @@ describe('constrained delivery', () => { afterAll(() => teardown()); it('reuses an existing standard-registry constrained handshake', async () => { - await contract.methods.emit_note(recipient).send({ from: sender }); + await contract.methods.emit_note(recipient, 1).send({ from: sender }); const { result: secretAfterFirstSend } = await contract.methods .get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED) .simulate({ from: sender }); expect(secretAfterFirstSend).toBeDefined(); - await contract.methods.emit_event(recipient).send({ from: sender }); + await contract.methods.emit_event(recipient, 1).send({ from: sender }); const { result: secret } = await contract.methods .get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED) @@ -73,8 +82,8 @@ describe('constrained delivery', () => { // parallel sends to a single pair ever become supported. The working alternative is the batched test below. it.failing('cannot fan out constrained sends on the same sequence in parallel', async () => { await Promise.all([ - contract.methods.emit_note(recipient).send({ from: sender }), - contract.methods.emit_note(recipient).send({ from: sender }), + contract.methods.emit_note(recipient, 1).send({ from: sender }), + contract.methods.emit_note(recipient, 1).send({ from: sender }), ]); }); @@ -106,8 +115,8 @@ describe('constrained delivery', () => { .send({ from: sender }); await new BatchCall(wallet, [ - contract.methods.emit_note(batchRecipient2), - contract.methods.emit_note(batchRecipient2), + contract.methods.emit_note(batchRecipient2, 1), + contract.methods.emit_note(batchRecipient2, 1), ]).send({ from: sender }); const { result: secret } = await contract.methods @@ -141,8 +150,8 @@ describe('constrained delivery', () => { // so the next index is 1, not 2. Confirms the constraint is in the utility read, not the batching mechanism. it('re-handshakes instead of reusing when BatchCall sends bootstrap a new recipient in the same tx', async () => { await new BatchCall(wallet, [ - contract.methods.emit_note(batchRecipient4), - contract.methods.emit_note(batchRecipient4), + contract.methods.emit_note(batchRecipient4, 1), + contract.methods.emit_note(batchRecipient4, 1), ]).send({ from: sender }); const { result: secret } = await contract.methods @@ -155,3 +164,109 @@ describe('constrained delivery', () => { }); }); }); + +// A constrained message sent by an account on one PXE is discovered and read by a recipient whose account lives on a +// separate PXE, with no shared in-memory state: PXE B finds the message purely from on-chain logs plus the +// HandshakeRegistry. The two PXEs share one node, following the e2e_2_pxes pattern. +describe('cross-PXE constrained delivery', () => { + jest.setTimeout(300_000); + + let aztecNode: AztecNode & AztecNodeDebug; + let walletSender: TestWallet; + let walletRecipient: TestWallet; + let sender: AztecAddress; + let recipient: AztecAddress; + let additionallyFundedAccounts: InitialAccountData[]; + let contractSender: ConstrainedDeliveryTestContract; + let teardownSender: () => Promise; + let teardownRecipient: () => Promise; + + beforeAll(async () => { + // PXE A holds the sender. The recipient is funded at genesis here but created and deployed on PXE B below. + ({ + aztecNode, + additionallyFundedAccounts, + wallet: walletSender, + accounts: [sender], + teardown: teardownSender, + } = await setup(1, { + ...AUTOMINE_E2E_OPTS, + additionallyFundedAccounts: await generateSchnorrAccounts(1, 'schnorr'), + })); + + // PXE B holds the recipient on the same node; the recipient account's keys live only here. + ({ wallet: walletRecipient, teardown: teardownRecipient } = await setupPXEAndGetWallet( + aztecNode, + aztecNode, + {}, + undefined, + 'pxe-b', + )); + const recipientAccount = await walletRecipient.createSchnorrAccount( + additionallyFundedAccounts[0].secret, + additionallyFundedAccounts[0].salt, + ); + await (await recipientAccount.getDeployMethod()).send({ from: NO_FROM }); + recipient = recipientAccount.address; + + await ensureHandshakeRegistryPublished(walletSender, sender); + const { contract: deployed, instance } = await ConstrainedDeliveryTestContract.deploy(walletSender).send({ + from: sender, + }); + contractSender = deployed; + + await ensureHandshakeRegistryPublished(walletRecipient, recipient); + await walletRecipient.registerContract(instance, ConstrainedDeliveryTestContract.artifact); + }); + + afterAll(async () => { + await teardownRecipient(); + await teardownSender(); + }); + + it('delivers multiple constrained events from PXE A that PXE B discovers', async () => { + // Distinct values prove PXE B decrypts each message's content, not merely that a tagged log arrived; delivering + // several on one sequence proves both the bootstrap send (index 0) and the reused-handshake sends are + // discovered. Constrained sends to one pair are strictly ordered, so they go one tx at a time. + const eventValues = [10n, 20n, 30n]; + const blockNumbers: number[] = []; + for (const value of eventValues) { + const { receipt } = await contractSender.methods.emit_event(recipient, value).send({ from: sender }); + blockNumbers.push(receipt.blockNumber!); + } + + await walletRecipient.sync(); + + const events = await walletRecipient.getPrivateEvents( + ConstrainedDeliveryTestContract.events.DeliveryEvent, + { + contractAddress: contractSender.address, + fromBlock: BlockNumber(Math.min(...blockNumbers)), + toBlock: BlockNumber(Math.max(...blockNumbers) + 1), + scopes: [recipient], + }, + ); + + const discovered = events.map(e => e.event.value); + expect(discovered.length).toBe(eventValues.length); + for (const value of eventValues) { + expect(discovered).toContainEqual(value); + } + }); + + it('delivers multiple constrained notes from PXE A that PXE B reads back', async () => { + // Same recipient and handshake as the events above, so these notes land at the following sequence indices. + const noteValues = [40n, 50n, 60n]; + for (const value of noteValues) { + await contractSender.methods.emit_note(recipient, value).send({ from: sender }); + } + + await walletRecipient.sync(); + + const contractRecipient = ConstrainedDeliveryTestContract.at(contractSender.address, walletRecipient); + // Count proves every delivered note was discovered; the sum of distinct values proves each was decrypted. + const { result } = await contractRecipient.methods.get_note_values(recipient).simulate({ from: recipient }); + const values: bigint[] = result.storage.slice(0, Number(result.len)); + expect(values).toEqual(noteValues); + }); +}); From 1657cd6b183267c32a710620d740ce8a0ea8dd26 Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Tue, 23 Jun 2026 04:53:17 -0300 Subject: [PATCH 6/8] refactor(stdlib)!: rename unsafe AztecAddress constructors to *Unsafe (#24230) --- boxes/boxes/vanilla/app/embedded-wallet.ts | 2 +- boxes/boxes/vanilla/app/main.ts | 8 +-- .../docs/resources/migration_notes.md | 20 ++++++ .../wallet-extension/05-transactions.md | 6 +- .../src/components/GameLobby.tsx | 2 +- .../protocol-fuzzer/wallet-bridge.mjs | 6 +- .../src/components/common/FnParameter.tsx | 2 +- .../components/CreateAuthwitDialog.tsx | 2 +- .../components/CreateContractDialog.tsx | 2 +- .../components/home/components/Landing.tsx | 2 +- .../navbar/components/AccountSelector.tsx | 2 +- .../navbar/components/ContractSelector.tsx | 2 +- playground/src/utils/contracts.ts | 2 +- .../src/wallet/components/AddSenderDialog.tsx | 2 +- .../archiver/src/store/log_store.test.ts | 4 +- .../src/store/log_store_codec.test.ts | 6 +- .../archiver/src/test/mock_structs.ts | 7 +- .../call_authorization_request.ts | 4 +- .../src/contract/interaction_options.test.ts | 2 +- .../src/contract/interaction_options.ts | 2 +- yarn-project/aztec/src/cli/cmds/standby.ts | 2 +- .../aztec/src/local-network/local-network.ts | 2 +- .../avm_check_circuit1.test.ts | 2 +- .../avm_check_circuit2.test.ts | 4 +- .../avm_check_circuit3.test.ts | 10 +-- .../avm_contract_class_limits.test.ts | 2 +- .../avm_contract_updates.test.ts | 2 +- .../avm_proven_gadgets.test.ts | 4 +- .../avm_proving_tests/avm_proving_tester.ts | 2 +- .../avm_public_fee_payment.test.ts | 4 +- .../cli-wallet/src/utils/options/fees.ts | 2 +- yarn-project/cli-wallet/src/utils/wallet.ts | 2 +- yarn-project/cli/src/utils/commands.ts | 4 +- ...e2e_multi_validator_node_key_store.test.ts | 4 +- .../end-to-end/src/e2e_avm_simulator.test.ts | 4 +- .../e2e_l1_publisher/e2e_l1_publisher.test.ts | 2 +- .../src/e2e_publisher_funding_multi.test.ts | 2 +- .../src/e2e_sequencer/reload_keystore.test.ts | 4 +- .../src/simulators/lending_simulator.ts | 2 +- yarn-project/key-store/src/key_store.test.ts | 2 +- yarn-project/key-store/src/key_store.ts | 4 +- .../src/keystore_manager.test.ts | 6 +- .../node-keystore/src/schemas.test.ts | 6 +- .../node-keystore/src/validation.test.ts | 4 +- .../src/conversion/common.ts | 2 +- yarn-project/p2p/src/config.ts | 2 +- .../fee_payer_balance_eviction_rule.ts | 2 +- .../mem_pools/tx_pool_v2/tx_pool_v2.test.ts | 26 +++---- .../tx_pool_v2/tx_pool_v2_bench.test.ts | 6 +- .../mem_pools/tx_pool_v2/tx_pool_v2_impl.ts | 2 +- .../tx_validator/phases_validator.test.ts | 2 +- .../tx_validator/phases_validator.ts | 4 +- .../tx_validator/tx_validator_bench.test.ts | 4 +- .../src/scripts/generate_data.ts | 10 +-- ...ghtweight_checkpoint_builder.bench.test.ts | 4 +- .../lightweight_checkpoint_builder.test.ts | 2 +- .../prover-client/src/mocks/test_context.ts | 2 +- .../event_validation_request.test.ts | 2 +- .../noir-structs/event_validation_request.ts | 2 +- .../log_retrieval_request.test.ts | 8 +-- .../noir-structs/log_retrieval_request.ts | 2 +- .../note_validation_request.test.ts | 4 +- .../noir-structs/note_validation_request.ts | 4 +- .../oracle/note_packing_utils.test.ts | 4 +- .../oracle/oracle_type_mappings.ts | 6 +- .../oracle/private_execution.test.ts | 2 +- .../oracle/private_execution_oracle.test.ts | 2 +- .../transient_array_service.test.ts | 4 +- .../contract_sync_service.test.ts | 16 ++--- yarn-project/pxe/src/error_enriching.ts | 2 +- .../src/private_kernel/batch_planner.test.ts | 2 +- .../src/private_kernel/hints/test_utils.ts | 2 +- .../private_kernel_execution_prover.test.ts | 4 +- .../schema_tests.ts | 68 ++++++++++--------- .../storage/contract_store/contract_store.ts | 2 +- .../src/storage/note_store/note_store.test.ts | 16 +++-- .../tagging_secret_sources_store.ts | 2 +- yarn-project/sequencer-client/src/config.ts | 2 +- .../public/avm/apps_tests/avm_test.test.ts | 4 +- .../avm/apps_tests/storage_proofs.test.ts | 4 +- .../src/public/avm/apps_tests/token.test.ts | 6 +- .../avm/avm_execution_environment.test.ts | 2 +- .../src/public/avm/avm_simulator.test.ts | 14 ++-- .../fixtures/base_avm_simulation_tester.ts | 2 +- .../src/public/avm/fixtures/utils.ts | 2 +- .../avm/opcodes/accrued_substate.test.ts | 4 +- .../simulator/src/public/fixtures/amm_test.ts | 4 +- .../src/public/fixtures/bulk_test.ts | 4 +- .../public/fixtures/custom_bytecode_tester.ts | 4 +- .../src/public/fixtures/token_test.ts | 6 +- .../simulator/src/public/fixtures/utils.ts | 2 +- .../apps_tests/deployments.test.ts | 6 +- .../apps_tests/timeout_race.test.ts | 2 +- .../public_processor/apps_tests/token.test.ts | 6 +- .../public_processor/public_processor.test.ts | 2 +- .../apps_tests/avm_gadgets.test.ts | 2 +- .../apps_tests/bench.test.ts | 8 +-- .../apps_tests/cpp_exception_handling.test.ts | 4 +- .../contract_provider_for_cpp.ts | 4 +- .../public_tx_simulator.test.ts | 12 ++-- .../src/public/side_effect_trace.test.ts | 28 ++++---- .../state_manager/public_storage.test.ts | 14 ++-- .../state_manager/state_manager.test.ts | 14 ++-- .../src/public/state_manager/state_manager.ts | 2 +- yarn-project/standard-contracts/src/drift.ts | 2 +- .../src/standard_contract_data.ts | 12 ++-- yarn-project/stdlib/src/abi/decoder.test.ts | 2 +- yarn-project/stdlib/src/avm/avm.ts | 2 +- .../stdlib/src/aztec-address/index.ts | 42 ++++++++++-- .../stdlib/src/checkpoint/validate.test.ts | 4 +- .../src/contract/contract_address.test.ts | 4 +- yarn-project/stdlib/src/hash/hash.test.ts | 6 +- yarn-project/stdlib/src/hash/map_slot.test.ts | 2 +- ...build_note_hash_read_request_hints.test.ts | 2 +- ...build_nullifier_read_request_hints.test.ts | 2 +- .../hints/build_transient_data_hints.test.ts | 6 +- .../stdlib/src/kernel/hints/read_request.ts | 2 +- yarn-project/stdlib/src/kernel/log_hash.ts | 7 +- yarn-project/stdlib/src/kernel/note_hash.ts | 2 +- yarn-project/stdlib/src/kernel/nullifier.ts | 2 +- .../stdlib/src/kernel/private_log_data.ts | 5 +- .../stdlib/src/logs/app_tagging_secret.ts | 4 +- .../src/logs/contract_class_log.test.ts | 4 +- .../src/rollup/checkpoint_header.test.ts | 4 +- yarn-project/stdlib/src/tests/factories.ts | 4 +- .../stdlib/src/tx/execution_payload.ts | 3 +- .../stdlib/src/tx/global_variables.ts | 4 +- yarn-project/stdlib/src/tx/tx.test.ts | 2 +- yarn-project/stdlib/src/tx/tx_request.test.ts | 2 +- yarn-project/txe/src/constants.ts | 2 +- .../oracle/test-resolver/default_fixtures.ts | 2 +- .../src/oracle/test-resolver/resolver.test.ts | 14 ++-- .../oracle/txe_oracle_top_level_context.ts | 2 +- .../src/checkpoint_builder.test.ts | 2 +- .../key_store/node_keystore_adapter.test.ts | 6 +- .../wallet-sdk/src/base-wallet/base_wallet.ts | 2 +- .../wallets/src/embedded/wallet_db.ts | 4 +- 137 files changed, 399 insertions(+), 319 deletions(-) diff --git a/boxes/boxes/vanilla/app/embedded-wallet.ts b/boxes/boxes/vanilla/app/embedded-wallet.ts index 0231e3516c32..0cfa283da83b 100644 --- a/boxes/boxes/vanilla/app/embedded-wallet.ts +++ b/boxes/boxes/vanilla/app/embedded-wallet.ts @@ -163,7 +163,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase { if (!address) { return null; } - const parsed = AztecAddress.fromString(address); + const parsed = AztecAddress.fromStringUnsafe(address); this.connectedAccount = parsed; return this.connectedAccount; } diff --git a/boxes/boxes/vanilla/app/main.ts b/boxes/boxes/vanilla/app/main.ts index 4c35c1045b97..36902af49dcf 100644 --- a/boxes/boxes/vanilla/app/main.ts +++ b/boxes/boxes/vanilla/app/main.ts @@ -53,9 +53,9 @@ document.addEventListener('DOMContentLoaded', async () => { const instance = await getContractInstanceFromInstantiationParams( PrivateVotingContract.artifact, { - deployer: AztecAddress.fromString(deployerAddress), + deployer: AztecAddress.fromStringUnsafe(deployerAddress), salt: Fr.fromString(deploymentSalt), - constructorArgs: [AztecAddress.fromString(deployerAddress)], + constructorArgs: [AztecAddress.fromStringUnsafe(deployerAddress)], } ); await wallet.registerContract(instance, PrivateVotingContract.artifact); @@ -155,7 +155,7 @@ voteButton.addEventListener('click', async (e) => { // Prepare contract interaction const votingContract = PrivateVotingContract.at( - AztecAddress.fromString(contractAddress), + AztecAddress.fromStringUnsafe(contractAddress), wallet ); @@ -188,7 +188,7 @@ async function updateVoteTally(wallet: Wallet, from: AztecAddress) { // Prepare contract interaction const votingContract = PrivateVotingContract.at( - AztecAddress.fromString(contractAddress), + AztecAddress.fromStringUnsafe(contractAddress), wallet ); diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 07b9a16eac4a..4a46a11478ce 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,26 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [Aztec.js] Unchecked `AztecAddress` constructors renamed with an `Unsafe` suffix + +The synchronous `AztecAddress` constructors that build an address from a raw value do not verify that the value is a valid address (the x-coordinate of a point on the Grumpkin curve, which is what allows it to be encrypted to). An invalid value is accepted silently and only fails later, when a transaction is sent. To make this obvious at the call site, they now carry an `Unsafe` suffix: + +| Before | After | +| --- | --- | +| `AztecAddress.fromField` | `AztecAddress.fromFieldUnsafe` | +| `AztecAddress.fromBigInt` | `AztecAddress.fromBigIntUnsafe` | +| `AztecAddress.fromNumber` | `AztecAddress.fromNumberUnsafe` | +| `AztecAddress.fromString` | `AztecAddress.fromStringUnsafe` | + +**Migration:** + +```diff +- const address = AztecAddress.fromBigInt(123n); ++ const address = AztecAddress.fromBigIntUnsafe(123n); +``` + +For a random, genuinely valid address in tests use `AztecAddress.random()`, and to check an untrusted value use `address.isValid()`. The serialization constructors `fromBuffer` and `fromFields` keep their names (they are part of the (de)serialization interface and read addresses from already-validated data), but their docs now note that they perform no validation either. + ### Cross-contract utility calls now have a `msg_sender` A utility function called by another contract (utility to utility, or private to utility) can read the calling contract's address via `self.msg_sender()`, mirroring private and public functions. A top-level utility call (e.g. invoked directly by a wallet or dapp) has no caller: `self.msg_sender()` panics, and `self.context.maybe_msg_sender()` returns `Option::none()`. diff --git a/docs/docs-developers/docs/tutorials/js_tutorials/wallet-extension/05-transactions.md b/docs/docs-developers/docs/tutorials/js_tutorials/wallet-extension/05-transactions.md index feda079c013d..ad65ec7b02d0 100644 --- a/docs/docs-developers/docs/tutorials/js_tutorials/wallet-extension/05-transactions.md +++ b/docs/docs-developers/docs/tutorials/js_tutorials/wallet-extension/05-transactions.md @@ -173,7 +173,7 @@ case 'sendTx': { // 1. Deserialize the payload const payload = deserializeExecutionPayload(executionPayload); - const fromAddress = AztecAddress.fromString(from || options.from); + const fromAddress = AztecAddress.fromStringUnsafe(from || options.from); // 2. Call wallet.sendTx (inherited from BaseWallet) const result = await wallet.sendTx(payload, { @@ -297,7 +297,7 @@ For gas estimation or validation, dApps use `simulateTx`: case 'simulateTx': { const { executionPayload, options } = args; const payload = deserializeExecutionPayload(executionPayload); - const fromAddress = AztecAddress.fromString(from || options.from); + const fromAddress = AztecAddress.fromStringUnsafe(from || options.from); const result = await wallet.simulateTx(payload, { ...options, @@ -342,7 +342,7 @@ For delegated actions (like approving token spending), the wallet creates auth w ```typescript case 'createAuthWit': { const { from: authFrom, messageHashOrIntent } = args; - const fromAddress = AztecAddress.fromString(authFrom); + const fromAddress = AztecAddress.fromStringUnsafe(authFrom); const authWit = await wallet.createAuthWit(fromAddress, messageHashOrIntent); diff --git a/docs/examples/webapp-tutorial/src/components/GameLobby.tsx b/docs/examples/webapp-tutorial/src/components/GameLobby.tsx index 05af34336f25..511614dd9827 100644 --- a/docs/examples/webapp-tutorial/src/components/GameLobby.tsx +++ b/docs/examples/webapp-tutorial/src/components/GameLobby.tsx @@ -79,7 +79,7 @@ export function GameLobby({ wallet, account, onGameJoined }: GameLobbyProps) { return; } - const contractAddr = AztecAddress.fromString(joinContractAddress); + const contractAddr = AztecAddress.fromStringUnsafe(joinContractAddress); const contract = await attachToContract( wallet, contractAddr diff --git a/noir-projects/protocol-fuzzer/wallet-bridge.mjs b/noir-projects/protocol-fuzzer/wallet-bridge.mjs index b37cc1cb6ea4..2f4d41e489bc 100644 --- a/noir-projects/protocol-fuzzer/wallet-bridge.mjs +++ b/noir-projects/protocol-fuzzer/wallet-bridge.mjs @@ -85,7 +85,7 @@ const handlers = { const w = await ensureWallet(); const { result: address, stdout } = await capturing(log => deploy( - w, node, AztecAddress.fromString(from), artifact, + w, node, AztecAddress.fromStringUnsafe(from), artifact, false, /* json */ undefined, /* publicKeys */ Array.isArray(args) ? args : [], /* args */ @@ -106,8 +106,8 @@ const handlers = { '/execute': async ({ verb, method, contract, from, args, artifact }) => { const w = await ensureWallet(); - const sender = AztecAddress.fromString(from); - const target = AztecAddress.fromString(contract); + const sender = AztecAddress.fromStringUnsafe(from); + const target = AztecAddress.fromStringUnsafe(contract); const callArgs = args || []; const { stdout } = await capturing(log => verb === 'send' diff --git a/playground/src/components/common/FnParameter.tsx b/playground/src/components/common/FnParameter.tsx index 4c5b7cf75a21..9cb5f1e93703 100644 --- a/playground/src/components/common/FnParameter.tsx +++ b/playground/src/components/common/FnParameter.tsx @@ -74,7 +74,7 @@ export function FunctionParameter({ parameter, required, onParameterChange, defa const contacts = await wallet.getAddressBook(); const contracts = (await playgroundDB.listAliases('contracts')).map( - ({ alias, item }) => ({ alias, item: AztecAddress.fromString(item) }), + ({ alias, item }) => ({ alias, item: AztecAddress.fromStringUnsafe(item) }), ); setAliasedAddresses([...accounts, ...contacts, ...contracts]); setLoading(false); diff --git a/playground/src/components/contract/components/CreateAuthwitDialog.tsx b/playground/src/components/contract/components/CreateAuthwitDialog.tsx index 0332f4ab3850..a166e48da015 100644 --- a/playground/src/components/contract/components/CreateAuthwitDialog.tsx +++ b/playground/src/components/contract/components/CreateAuthwitDialog.tsx @@ -62,7 +62,7 @@ export function CreateAuthwitDialog({ open, contract, fnName, args, isPrivate, o setCreating(true); const call = await contract.methods[fnName](...args).getFunctionCall(); const intent = { - caller: AztecAddress.fromString(caller), + caller: AztecAddress.fromStringUnsafe(caller), call, }; try { diff --git a/playground/src/components/contract/components/CreateContractDialog.tsx b/playground/src/components/contract/components/CreateContractDialog.tsx index b98a5b2e4912..2dea9d80ae5d 100644 --- a/playground/src/components/contract/components/CreateContractDialog.tsx +++ b/playground/src/components/contract/components/CreateContractDialog.tsx @@ -141,7 +141,7 @@ export function CreateContractDialog({ const registerExistingContract = async () => { setIsRegistering(true); try { - const contract = await node.getContract(AztecAddress.fromString(address)); + const contract = await node.getContract(AztecAddress.fromStringUnsafe(address)); if (!contract) { throw new Error('Contract with this address was not found in node'); } diff --git a/playground/src/components/home/components/Landing.tsx b/playground/src/components/home/components/Landing.tsx index 1b007362104c..1709737852d9 100644 --- a/playground/src/components/home/components/Landing.tsx +++ b/playground/src/components/home/components/Landing.tsx @@ -352,7 +352,7 @@ export function Landing() { const artifactAsString = await playgroundDB.retrieveAlias(`artifacts:${contract.item}`); const contractArtifact = loadContractArtifact(parse(artifactAsString)); if (contractArtifact.name === contractArtifactJSON.name) { - deployedContractAddress = AztecAddress.fromString(contract.item); + deployedContractAddress = AztecAddress.fromStringUnsafe(contract.item); break; } } diff --git a/playground/src/components/navbar/components/AccountSelector.tsx b/playground/src/components/navbar/components/AccountSelector.tsx index 80e70e05f1c2..9db2cf993b5e 100644 --- a/playground/src/components/navbar/components/AccountSelector.tsx +++ b/playground/src/components/navbar/components/AccountSelector.tsx @@ -81,7 +81,7 @@ export function AccountSelector() { onClose={() => setIsOpen(false)} onChange={e => { if (e.target.value !== '') { - handleAccountChange(AztecAddress.fromString(e.target.value)); + handleAccountChange(AztecAddress.fromStringUnsafe(e.target.value)); } }} disabled={areAccountsLoading} diff --git a/playground/src/components/navbar/components/ContractSelector.tsx b/playground/src/components/navbar/components/ContractSelector.tsx index 7ab2a0f83030..3700cc58ae69 100644 --- a/playground/src/components/navbar/components/ContractSelector.tsx +++ b/playground/src/components/navbar/components/ContractSelector.tsx @@ -108,7 +108,7 @@ export function ContractSelector() { } else { const artifactAsString = await playgroundDB.retrieveAlias(`artifacts:${contractValue}`); const contractArtifact = loadContractArtifact(parse(artifactAsString)); - setCurrentContractAddress(AztecAddress.fromString(contractValue)); + setCurrentContractAddress(AztecAddress.fromStringUnsafe(contractValue)); setCurrentContractArtifact(contractArtifact); setSelectedPredefinedContract(undefined); setShowContractInterface(true); diff --git a/playground/src/utils/contracts.ts b/playground/src/utils/contracts.ts index 3216b8b4a4d2..1ddbbd9c67d0 100644 --- a/playground/src/utils/contracts.ts +++ b/playground/src/utils/contracts.ts @@ -8,7 +8,7 @@ export async function filterDeployedAliasedContracts( const deployed = ( await Promise.all( aliasedContracts.map(async contract => { - const { isContractPublished } = await wallet.getContractMetadata(AztecAddress.fromString(contract.item)); + const { isContractPublished } = await wallet.getContractMetadata(AztecAddress.fromStringUnsafe(contract.item)); return { ...contract, deployed: isContractPublished }; }), ) diff --git a/playground/src/wallet/components/AddSenderDialog.tsx b/playground/src/wallet/components/AddSenderDialog.tsx index fc2335162fc7..fef434ad85fa 100644 --- a/playground/src/wallet/components/AddSenderDialog.tsx +++ b/playground/src/wallet/components/AddSenderDialog.tsx @@ -24,7 +24,7 @@ export function AddSendersDialog({ const addSender = async () => { try { - const parsed = AztecAddress.fromString(sender); + const parsed = AztecAddress.fromStringUnsafe(sender); onClose(parsed, alias); } catch (e) { setError('Invalid Aztec address'); diff --git a/yarn-project/archiver/src/store/log_store.test.ts b/yarn-project/archiver/src/store/log_store.test.ts index 1b684535f063..54b899569a04 100644 --- a/yarn-project/archiver/src/store/log_store.test.ts +++ b/yarn-project/archiver/src/store/log_store.test.ts @@ -40,7 +40,7 @@ async function buildChainedCheckpointsWithLogs( return checkpoints; } -const CONTRACT = AztecAddress.fromNumber(543254); +const CONTRACT = AztecAddress.fromNumberUnsafe(543254); describe('LogStore', () => { let blockStore: BlockStore; @@ -468,7 +468,7 @@ describe('LogStore', () => { await logStore.addLogs([ckpt.checkpoint.blocks[0]]); // Same tag, different contract → no hits. - const otherContract = AztecAddress.fromNumber(99); + const otherContract = AztecAddress.fromNumberUnsafe(99); const [missing] = await logStore.getPublicLogsByTags({ contractAddress: otherContract, tags: [tag] }); expect(missing).toEqual([]); diff --git a/yarn-project/archiver/src/store/log_store_codec.test.ts b/yarn-project/archiver/src/store/log_store_codec.test.ts index b087312746e1..25ee762ee106 100644 --- a/yarn-project/archiver/src/store/log_store_codec.test.ts +++ b/yarn-project/archiver/src/store/log_store_codec.test.ts @@ -45,7 +45,7 @@ describe('log_store_codec', () => { }); it('strips 0x prefix for AztecAddress', () => { - const addr = AztecAddress.fromNumber(12345); + const addr = AztecAddress.fromNumberUnsafe(12345); const hex = fieldHex(addr); expect(hex).toHaveLength(64); expect(hex).not.toMatch(/^0x/); @@ -69,7 +69,7 @@ describe('log_store_codec', () => { }); it('round-trips a public-style prefix (contract-tag)', () => { - const contractHex = fieldHex(AztecAddress.fromNumber(99)); + const contractHex = fieldHex(AztecAddress.fromNumberUnsafe(99)); const tagHex = fieldHex(new Fr(0x5678n)); const prefix = encodePublicPrefix(contractHex, tagHex); const key = encodeKey(prefix, 10, 0, 2); @@ -242,7 +242,7 @@ describe('log_store_codec', () => { describe('encodePublicPrefix', () => { it('produces contractHex-tagHex', () => { - const contractHex = fieldHex(AztecAddress.fromNumber(1)); + const contractHex = fieldHex(AztecAddress.fromNumberUnsafe(1)); const tagHex = fieldHex(new Fr(2n)); expect(encodePublicPrefix(contractHex, tagHex)).toBe(`${contractHex}-${tagHex}`); }); diff --git a/yarn-project/archiver/src/test/mock_structs.ts b/yarn-project/archiver/src/test/mock_structs.ts index 0888d717218c..91145fcfcb11 100644 --- a/yarn-project/archiver/src/test/mock_structs.ts +++ b/yarn-project/archiver/src/test/mock_structs.ts @@ -269,7 +269,10 @@ export function makePublicLogTag(blockNumber: number, txIndex: number, logIndex: } /** Creates a PublicLog with fields derived from the tag. */ -export function makePublicLog(tag: Tag, contractAddress: AztecAddress = AztecAddress.fromNumber(543254)): PublicLog { +export function makePublicLog( + tag: Tag, + contractAddress: AztecAddress = AztecAddress.fromNumberUnsafe(543254), +): PublicLog { return PublicLog.from({ contractAddress, fields: new Array(10).fill(null).map((_, i) => (!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i)))), @@ -281,7 +284,7 @@ export function makePublicLogs( blockNumber: number, txIndex: number, numLogsPerTx: number, - contractAddress: AztecAddress = AztecAddress.fromNumber(543254), + contractAddress: AztecAddress = AztecAddress.fromNumberUnsafe(543254), ): PublicLog[] { return times(numLogsPerTx, logIndex => { const tag = makePublicLogTag(blockNumber, txIndex, logIndex); diff --git a/yarn-project/aztec.js/src/authorization/call_authorization_request.ts b/yarn-project/aztec.js/src/authorization/call_authorization_request.ts index 0b15b70fdde9..c2238e5eb843 100644 --- a/yarn-project/aztec.js/src/authorization/call_authorization_request.ts +++ b/yarn-project/aztec.js/src/authorization/call_authorization_request.ts @@ -81,8 +81,8 @@ export class CallAuthorizationRequest { const request = new CallAuthorizationRequest( selector, reader.readField(), // inner_hash - AztecAddress.fromField(reader.readField()), // on_behalf_of - AztecAddress.fromField(reader.readField()), // msg_sender + AztecAddress.fromFieldUnsafe(reader.readField()), // on_behalf_of + AztecAddress.fromFieldUnsafe(reader.readField()), // msg_sender FunctionSelector.fromField(reader.readField()), // fn_selector reader.readField(), // args_hash reader.readFieldArray(reader.remainingFields()), // args diff --git a/yarn-project/aztec.js/src/contract/interaction_options.test.ts b/yarn-project/aztec.js/src/contract/interaction_options.test.ts index 71bb49634ac9..2bcc2852378f 100644 --- a/yarn-project/aztec.js/src/contract/interaction_options.test.ts +++ b/yarn-project/aztec.js/src/contract/interaction_options.test.ts @@ -9,7 +9,7 @@ describe('extractOffchainOutput', () => { const makeEffect = (data: Fr[], contractAddress?: AztecAddress): OffchainEffect => ({ data, - contractAddress: contractAddress ?? AztecAddress.fromField(Fr.random()), + contractAddress: contractAddress ?? AztecAddress.fromFieldUnsafe(Fr.random()), }); const makeMessageEffect = async (recipient?: AztecAddress, payload?: Fr[], contractAddress?: AztecAddress) => diff --git a/yarn-project/aztec.js/src/contract/interaction_options.ts b/yarn-project/aztec.js/src/contract/interaction_options.ts index 92d48488c1c2..5a7ae4d0ba06 100644 --- a/yarn-project/aztec.js/src/contract/interaction_options.ts +++ b/yarn-project/aztec.js/src/contract/interaction_options.ts @@ -191,7 +191,7 @@ export function extractOffchainOutput(effects: OffchainEffect[], anchorBlockTime for (const effect of effects) { if (effect.data.length >= 2 && effect.data[0].equals(OFFCHAIN_MESSAGE_IDENTIFIER)) { offchainMessages.push({ - recipient: AztecAddress.fromField(effect.data[1]), + recipient: AztecAddress.fromFieldUnsafe(effect.data[1]), payload: effect.data.slice(2), contractAddress: effect.contractAddress, anchorBlockTimestamp, diff --git a/yarn-project/aztec/src/cli/cmds/standby.ts b/yarn-project/aztec/src/cli/cmds/standby.ts index d2769d36788d..720c72596bd2 100644 --- a/yarn-project/aztec/src/cli/cmds/standby.ts +++ b/yarn-project/aztec/src/cli/cmds/standby.ts @@ -23,7 +23,7 @@ const ROLLUP_POLL_INTERVAL_S = 60; export async function computeExpectedGenesisRoot(config: GenesisStateConfig, userLog: LogFn) { const testAccounts = config.testAccounts ? (await getInitialTestAccountsData()).map(a => a.address) : []; const sponsoredFPCAccounts = config.sponsoredFPC ? [await getSponsoredFPCAddress()] : []; - const prefundAddresses = (config.prefundAddresses ?? []).map(a => AztecAddress.fromString(a)); + const prefundAddresses = (config.prefundAddresses ?? []).map(a => AztecAddress.fromStringUnsafe(a)); const initialFundedAccounts = testAccounts.concat(sponsoredFPCAccounts).concat(prefundAddresses); userLog(`Initial funded accounts: ${initialFundedAccounts.map(a => a.toString()).join(', ')}`); diff --git a/yarn-project/aztec/src/local-network/local-network.ts b/yarn-project/aztec/src/local-network/local-network.ts index a285c029b2cf..d0a06b77cf0b 100644 --- a/yarn-project/aztec/src/local-network/local-network.ts +++ b/yarn-project/aztec/src/local-network/local-network.ts @@ -166,7 +166,7 @@ export async function createLocalNetwork(config: Partial = { const bananaFPC = await getBananaFPCAddress(initialAccounts); const sponsoredFPC = await getSponsoredFPCAddress(); - const prefundAddresses = (aztecNodeConfig.prefundAddresses ?? []).map(a => AztecAddress.fromString(a)); + const prefundAddresses = (aztecNodeConfig.prefundAddresses ?? []).map(a => AztecAddress.fromStringUnsafe(a)); const fundedAddresses = [ ...initialAccounts.map(a => a.address), ...(initialAccounts.length ? [bananaFPC, sponsoredFPC] : []), diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts index b74cb0f18dcc..fc961b0f733f 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts @@ -26,7 +26,7 @@ describe('AVM check-circuit – unhappy paths 1', () => { tester = await AvmProvingTester.new(worldStateService, /*checkCircuitOnly*/ true); avmTestContractInstance = await tester.registerAndDeployContract( /*constructorArgs=*/ [], - /*deployer=*/ AztecAddress.fromNumber(420), + /*deployer=*/ AztecAddress.fromNumberUnsafe(420), AvmTestContractArtifact, ); }); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts index 555c6bfb4bc7..4f174bac0b92 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts @@ -10,7 +10,7 @@ import { AvmProvingTester } from './avm_proving_tester.js'; const TIMEOUT = 30_000; describe('AVM check-circuit – unhappy paths 2', () => { - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); let avmTestContractInstance: ContractInstanceWithAddress; let tester: AvmProvingTester; let worldStateService: NativeWorldStateService; @@ -20,7 +20,7 @@ describe('AVM check-circuit – unhappy paths 2', () => { tester = await AvmProvingTester.new(worldStateService, /*checkCircuitOnly*/ true); avmTestContractInstance = await tester.registerAndDeployContract( /*constructorArgs=*/ [], - /*deployer=*/ AztecAddress.fromNumber(420), + /*deployer=*/ AztecAddress.fromNumberUnsafe(420), AvmTestContractArtifact, ); }); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts index b5648bcea5de..e8a2e46287da 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts @@ -13,7 +13,7 @@ import { AvmProvingTester } from './avm_proving_tester.js'; const TIMEOUT = 100_000; describe('AVM check-circuit – unhappy paths 3', () => { - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); let avmTestContractInstance: ContractInstanceWithAddress; let tester: AvmProvingTester; let worldStateService: NativeWorldStateService; @@ -23,7 +23,7 @@ describe('AVM check-circuit – unhappy paths 3', () => { tester = await AvmProvingTester.new(worldStateService, /*checkCircuitOnly*/ true); avmTestContractInstance = await tester.registerAndDeployContract( /*constructorArgs=*/ [], - /*deployer=*/ AztecAddress.fromNumber(420), + /*deployer=*/ AztecAddress.fromNumberUnsafe(420), AvmTestContractArtifact, ); }); @@ -137,15 +137,15 @@ describe('AVM check-circuit – unhappy paths 3', () => { l2ToL1Msgs: [ new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x1111), new Fr(0xdddd)), - AztecAddress.fromNumber(0x1111), + AztecAddress.fromNumberUnsafe(0x1111), ), new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x2222), new Fr(0xeeee)), - AztecAddress.fromNumber(0x2222), + AztecAddress.fromNumberUnsafe(0x2222), ), new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x3333), new Fr(0xffff)), - AztecAddress.fromNumber(0x3333), + AztecAddress.fromNumberUnsafe(0x3333), ), ], }, diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts index 961cd3655e23..fbb93782c534 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts @@ -9,7 +9,7 @@ import { AvmProvingTester } from './avm_proving_tester.js'; const TIMEOUT = 300_000; describe('AVM check-circuit - contract class limits', () => { - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); let instances: ContractInstanceWithAddress[]; let tester: AvmProvingTester; let avmTestContractAddress: AztecAddress; diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_updates.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_updates.test.ts index b043f8793b8b..33515961f285 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_updates.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_updates.test.ts @@ -17,7 +17,7 @@ import { AvmProvingTester } from './avm_proving_tester.js'; const TIMEOUT = 60_000; describe('AVM check-circuit - contract updates', () => { - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); const avmTestContractClassSeed = 0; let avmTestContractInstance: ContractInstanceWithAddress; diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_proven_gadgets.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_proven_gadgets.test.ts index 1765487d6383..23f49bb1d25e 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_proven_gadgets.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_proven_gadgets.test.ts @@ -18,7 +18,7 @@ describe.skip('AVM proven gadgets test', () => { const metrics = new TestExecutorMetrics(); let worldStateService: NativeWorldStateService; - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); let avmGadgetsTestContract: ContractInstanceWithAddress; beforeEach(async () => { @@ -107,7 +107,7 @@ describe('AVM proven gadgets test: test vectors', () => { let tester: AvmProvingTester; let worldStateService: NativeWorldStateService; - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); let avmGadgetsTestContract: ContractInstanceWithAddress; beforeEach(async () => { diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_tester.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_tester.ts index 53d2a0fc5e71..4b4638c4ea42 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_tester.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_tester.ts @@ -192,7 +192,7 @@ export class AvmProvingTester extends PublicTxSimulationTester { gasLimits?: Gas, ) { await this.simProveVerify( - /*sender=*/ AztecAddress.fromNumber(42), + /*sender=*/ AztecAddress.fromNumberUnsafe(42), /*setupCalls=*/ [], [appCall], undefined, diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts index ff6145055b89..56444bb2ec5f 100644 --- a/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts @@ -9,7 +9,7 @@ import { AvmProvingTester } from './avm_proving_tester.js'; const TIMEOUT = 60_000; describe('AVM check-circuit – public fee payment', () => { - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); const feePayer = sender; const initialFeeJuiceBalance = new Fr(20000); @@ -25,7 +25,7 @@ describe('AVM check-circuit – public fee payment', () => { avmTestContractInstance = await tester.registerAndDeployContract( /*constructorArgs=*/ [], - /*deployer=*/ AztecAddress.fromNumber(420), + /*deployer=*/ AztecAddress.fromNumberUnsafe(420), AvmTestContractArtifact, ); }); diff --git a/yarn-project/cli-wallet/src/utils/options/fees.ts b/yarn-project/cli-wallet/src/utils/options/fees.ts index 5628955fc5a2..867e8b833735 100644 --- a/yarn-project/cli-wallet/src/utils/options/fees.ts +++ b/yarn-project/cli-wallet/src/utils/options/fees.ts @@ -137,7 +137,7 @@ export function parsePaymentMethod( if (!parsed.asset) { throw new Error('Missing "asset" in payment option'); } - return AztecAddress.fromString(parsed.asset); + return AztecAddress.fromStringUnsafe(parsed.asset); }; return async (wallet: Wallet, from: AztecAddress, gasSettings: GasSettings) => { diff --git a/yarn-project/cli-wallet/src/utils/wallet.ts b/yarn-project/cli-wallet/src/utils/wallet.ts index f4cb9f60a738..ffb0832a1110 100644 --- a/yarn-project/cli-wallet/src/utils/wallet.ts +++ b/yarn-project/cli-wallet/src/utils/wallet.ts @@ -95,7 +95,7 @@ export class CLIWallet extends BaseWallet { return Promise.resolve( accounts.map(({ key, value }) => { const alias = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key; - return { alias, item: AztecAddress.fromString(value) }; + return { alias, item: AztecAddress.fromStringUnsafe(value) }; }), ); } diff --git a/yarn-project/cli/src/utils/commands.ts b/yarn-project/cli/src/utils/commands.ts index 1a3c7e1d6cb1..f4c35cc816a3 100644 --- a/yarn-project/cli/src/utils/commands.ts +++ b/yarn-project/cli/src/utils/commands.ts @@ -101,7 +101,7 @@ export async function getTxSender(pxe: PXE, _from?: string) { let from: AztecAddress; if (_from) { try { - from = AztecAddress.fromString(_from); + from = AztecAddress.fromStringUnsafe(_from); } catch { throw new InvalidArgumentError(`Invalid option 'from' passed: ${_from}`); } @@ -195,7 +195,7 @@ export function parseFieldFromHexString(str: string): Fr { */ export function parseAztecAddress(address: string): AztecAddress { try { - return AztecAddress.fromString(address); + return AztecAddress.fromStringUnsafe(address); } catch { throw new InvalidArgumentError(`Invalid Aztec address: ${address}`); } diff --git a/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts b/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts index af674e9dcdad..ba87f9540a02 100644 --- a/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts +++ b/yarn-project/end-to-end/src/composed/web3signer/e2e_multi_validator_node_key_store.test.ts @@ -80,7 +80,7 @@ async function createKeyFiles() { }); const feeRecipientAddresses = Array.from({ length: VALIDATOR_COUNT }, (_, i) => { - return AztecAddress.fromNumber(i + 1); + return AztecAddress.fromNumberUnsafe(i + 1); }); await createKeyFile1( @@ -202,7 +202,7 @@ describe('e2e_multi_validator_node', () => { .toString() .toLowerCase(); expectedCoinbaseAddresses.set(validatorAddress.toLowerCase(), coinbase); - const feeRecipient = AztecAddress.fromNumber(i + 1) + const feeRecipient = AztecAddress.fromNumberUnsafe(i + 1) .toString() .toLowerCase(); expectedFeeRecipientAddresses.set(validatorAddress.toLowerCase(), feeRecipient); diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 554e336ea56a..ee3c6add14f1 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -193,7 +193,7 @@ describe('e2e_avm_simulator', () => { }); it('Modifies storage (Map)', async () => { - const address = AztecAddress.fromBigInt(9090n); + const address = AztecAddress.fromBigIntUnsafe(9090n); await avmContract.methods.set_storage_map(address, 100).send({ from: defaultAccountAddress }); await avmContract.methods.add_storage_map(address, 100).send({ from: defaultAccountAddress }); expect( @@ -202,7 +202,7 @@ describe('e2e_avm_simulator', () => { }); it('Preserves storage across enqueued public calls', async () => { - const address = AztecAddress.fromBigInt(9090n); + const address = AztecAddress.fromBigIntUnsafe(9090n); // This will create 1 tx with 2 public calls in it. await new BatchCall(wallet, [ avmContract.methods.set_storage_map(address, 100), diff --git a/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts b/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts index 53ac2043cfbb..7837570a3794 100644 --- a/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts @@ -520,7 +520,7 @@ describe('L1Publisher integration', () => { const l1BlockNumber = await l1Client.getBlockNumber(); // random recipient address, just kept consistent for easy testing ts/sol. - const recipientAddress = AztecAddress.fromString( + const recipientAddress = AztecAddress.fromStringUnsafe( '0x1647b194c649f5dd01d7c832f89b0f496043c9150797923ea89e93d5ac619a93', ); diff --git a/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts index 940174e33e24..b8f5f09965ac 100644 --- a/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts +++ b/yarn-project/end-to-end/src/e2e_publisher_funding_multi.test.ts @@ -66,7 +66,7 @@ describe('e2e_publisher_funding_multi', () => { attester: attesterKey, publisher: [publisherKey1, publisherKey2], coinbase: EthAddress.fromNumber(42).toChecksumString(), - feeRecipient: AztecAddress.fromNumber(42).toString(), + feeRecipient: AztecAddress.fromNumberUnsafe(42).toString(), }, ], fundingAccount: funderKey, diff --git a/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts b/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts index 601785e53a36..2b9966eac169 100644 --- a/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts +++ b/yarn-project/end-to-end/src/e2e_sequencer/reload_keystore.test.ts @@ -101,7 +101,7 @@ describe('e2e_reload_keystore', () => { let publisherKey: EthPrivateKey; const initialCoinbase = EthAddress.fromNumber(42); - const initialFeeRecipient = AztecAddress.fromNumber(42); + const initialFeeRecipient = AztecAddress.fromNumberUnsafe(42); const artifact = StatefulTestContractArtifact; @@ -200,7 +200,7 @@ describe('e2e_reload_keystore', () => { // Write updated keystore and reload // Each validator gets its own new coinbase so we can verify per-validator updates. const newCoinbases = VALIDATOR_KEY_INDICES.map((_, i) => EthAddress.fromNumber(100 + i)); - const newFeeRecipients = VALIDATOR_KEY_INDICES.map((_, i) => AztecAddress.fromNumber(100 + i)); + const newFeeRecipients = VALIDATOR_KEY_INDICES.map((_, i) => AztecAddress.fromNumberUnsafe(100 + i)); // Build updated keystore: all 4 validators (including the previously-excluded validator 4) const updatedKeystore = { diff --git a/yarn-project/end-to-end/src/simulators/lending_simulator.ts b/yarn-project/end-to-end/src/simulators/lending_simulator.ts index defcc7965273..5d29f144481c 100644 --- a/yarn-project/end-to-end/src/simulators/lending_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/lending_simulator.ts @@ -208,7 +208,7 @@ export class LendingSimulator { expect(interestAccumulator).toEqual(this.accumulator); expect(asset['last_updated_ts']).toEqual(BigInt(this.time)); - for (const key of [this.account.address, AztecAddress.fromField(await this.account.key())]) { + for (const key of [this.account.address, AztecAddress.fromFieldUnsafe(await this.account.key())]) { const { result: privatePos } = await this.lendingContract.methods .get_position(key) .simulate({ from: this.account.address }); diff --git a/yarn-project/key-store/src/key_store.test.ts b/yarn-project/key-store/src/key_store.test.ts index e061279b1fee..74df521e1a0d 100644 --- a/yarn-project/key-store/src/key_store.test.ts +++ b/yarn-project/key-store/src/key_store.test.ts @@ -42,7 +42,7 @@ describe('KeyStore', () => { expect(masterIncomingViewingSecretKey.equals(keys.masterIncomingViewingSecretKey)).toBe(true); // Arbitrary app contract address - const appAddress = AztecAddress.fromBigInt(624n); + const appAddress = AztecAddress.fromBigIntUnsafe(624n); const { pkMHash: obtainedNpkMHash, skApp: appNullifierHidingKey } = await keyStore.getKeyValidationRequest( computedMasterNullifierPublicKeyHash, diff --git a/yarn-project/key-store/src/key_store.ts b/yarn-project/key-store/src/key_store.ts index 3c3c05ada9c2..d347c4f863b7 100644 --- a/yarn-project/key-store/src/key_store.ts +++ b/yarn-project/key-store/src/key_store.ts @@ -109,7 +109,7 @@ export class KeyStore { const allMapKeys = await toArray(this.#keys.keysAsync()); // We return account addresses based on the map keys that end with '-ivsk_m' const accounts = allMapKeys.filter(key => key.endsWith('-ivsk_m')).map(key => key.split('-')[0]); - return accounts.map(account => AztecAddress.fromString(account)); + return accounts.map(account => AztecAddress.fromStringUnsafe(account)); } /** Checks whether an account is registered in the key store. */ @@ -340,7 +340,7 @@ export class KeyStore { if (Buffer.from(val).equals(valueBuffer)) { for (const prefix of KEY_PREFIXES) { if (key.includes(`-${prefix}`)) { - const account = AztecAddress.fromString(key.split('-')[0]); + const account = AztecAddress.fromStringUnsafe(key.split('-')[0]); return [prefix, account]; } } diff --git a/yarn-project/node-keystore/src/keystore_manager.test.ts b/yarn-project/node-keystore/src/keystore_manager.test.ts index 85f355b5fcc2..a0bcf268d5cf 100644 --- a/yarn-project/node-keystore/src/keystore_manager.test.ts +++ b/yarn-project/node-keystore/src/keystore_manager.test.ts @@ -95,7 +95,9 @@ describe('KeystoreManager', () => { validators: [ { attester: EthAddress.random(), - feeRecipient: AztecAddress.fromString('0x1111111111111111111111111111111111111111111111111111111111111111'), + feeRecipient: AztecAddress.fromStringUnsafe( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ), }, ], }; @@ -105,7 +107,7 @@ describe('KeystoreManager', () => { expect( feeRecipient.equals( - AztecAddress.fromString('0x1111111111111111111111111111111111111111111111111111111111111111'), + AztecAddress.fromStringUnsafe('0x1111111111111111111111111111111111111111111111111111111111111111'), ), ).toBeTruthy(); }); diff --git a/yarn-project/node-keystore/src/schemas.test.ts b/yarn-project/node-keystore/src/schemas.test.ts index 8f56315d535d..a0c53a330568 100644 --- a/yarn-project/node-keystore/src/schemas.test.ts +++ b/yarn-project/node-keystore/src/schemas.test.ts @@ -30,7 +30,7 @@ describe('Keystore Schema Validation', () => { expect(parsed.validators![0].attester).toBe('0x1234567890123456789012345678901234567890123456789012345678901234'); expect( parsed.validators![0].feeRecipient?.equals( - AztecAddress.fromString('0x1234567890123456789012345678901234567890123456789012345678901234'), + AztecAddress.fromStringUnsafe('0x1234567890123456789012345678901234567890123456789012345678901234'), ), ).toBeTruthy(); }); @@ -190,7 +190,7 @@ describe('Keystore Schema Validation', () => { expect(parsed.coinbase?.equals(EthAddress.fromString('0x1111111111111111111111111111111111111111'))).toBeTruthy(); expect( parsed.feeRecipient?.equals( - AztecAddress.fromString('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), + AztecAddress.fromStringUnsafe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), ), ).toBeTruthy(); } @@ -210,7 +210,7 @@ describe('Keystore Schema Validation', () => { ).toBeTruthy(); expect( parsed.validators![2].feeRecipient?.equals( - AztecAddress.fromString('0x0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcd'), + AztecAddress.fromStringUnsafe('0x0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcd'), ), ).toBeTruthy(); }); diff --git a/yarn-project/node-keystore/src/validation.test.ts b/yarn-project/node-keystore/src/validation.test.ts index 02aefa06de84..e04b6fd01f47 100644 --- a/yarn-project/node-keystore/src/validation.test.ts +++ b/yarn-project/node-keystore/src/validation.test.ts @@ -371,7 +371,7 @@ describe('Keystore Duplication Validation', () => { expect(v0.publisher).toBeDefined(); // array including private key, address, remote signer account, json v3 dir expect( (v0.feeRecipient as AztecAddress).equals( - AztecAddress.fromString('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), + AztecAddress.fromStringUnsafe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), ), ).toBeTruthy(); expect(typeof v0.remoteSigner === 'string').toBe(true); @@ -382,7 +382,7 @@ describe('Keystore Duplication Validation', () => { expect(v1.publisher).toBeDefined(); // mnemonic config expect( (v1.feeRecipient as AztecAddress).equals( - AztecAddress.fromString('0x0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcd'), + AztecAddress.fromStringUnsafe('0x0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcdef0bcd'), ), ).toBeTruthy(); expect(v1.fundingAccount).toBeDefined(); diff --git a/yarn-project/noir-protocol-circuits-types/src/conversion/common.ts b/yarn-project/noir-protocol-circuits-types/src/conversion/common.ts index 5868f473ca49..92f3dcd6d62f 100644 --- a/yarn-project/noir-protocol-circuits-types/src/conversion/common.ts +++ b/yarn-project/noir-protocol-circuits-types/src/conversion/common.ts @@ -225,7 +225,7 @@ export function mapAztecAddressToNoir(address: AztecAddress): NoirAztecAddress { * @returns The aztec address. */ export function mapAztecAddressFromNoir(address: NoirAztecAddress): AztecAddress { - return AztecAddress.fromField(mapFieldFromNoir(address.inner)); + return AztecAddress.fromFieldUnsafe(mapFieldFromNoir(address.inner)); } /** diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index e05747c8b1e3..cd34de88bade 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -740,7 +740,7 @@ export function parseAllowList(value: string): AllowedElement[] { if (typeString === 'I') { entries.push({ - address: AztecAddress.fromString(identifierString), + address: AztecAddress.fromStringUnsafe(identifierString), selector, ...flags, }); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts index 6badaf736d0a..4f116dfb8d56 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts @@ -86,7 +86,7 @@ export class FeePayerBalanceEvictionRule implements EvictionRule { publicStateSource: DatabasePublicStateSource, pool: PoolOperations, ): Promise { - const feePayer = AztecAddress.fromString(feePayerStr); + const feePayer = AztecAddress.fromStringUnsafe(feePayerStr); const initialBalance = ( await publicStateSource.storageRead( ProtocolContractAddress.FeeJuice, diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2.test.ts index 53f0ec8d9be5..6d6adbd073d1 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2.test.ts @@ -1036,7 +1036,7 @@ describe('TxPoolV2', () => { }); it('pre-protected tx bypasses insufficient balance pre-add rule', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Set balance to 0 - normally tx would be ignored setFeePayerBalanceForPreProtect(0n); @@ -1129,7 +1129,7 @@ describe('TxPoolV2', () => { }); it('pre-protected tx does not trigger post-add eviction rules', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Balance covers only one tx setFeePayerBalanceForPreProtect(DEFAULT_TX_FEE_LIMIT + DEFAULT_TX_FEE_LIMIT / 2n); @@ -1294,7 +1294,7 @@ describe('TxPoolV2', () => { }); it('tx ignored due to insufficient balance succeeds after pre-protection', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Set balance to 0 setFeePayerBalanceForPreProtect(0n); @@ -3441,7 +3441,7 @@ describe('TxPoolV2', () => { }); it('high priority tx evicts lower priority tx from same fee payer', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Set balance to cover only one tx setFeePayerBalance(DEFAULT_TX_FEE_LIMIT + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3470,7 +3470,7 @@ describe('TxPoolV2', () => { }); it('low priority tx ignored when fee payer balance exhausted by existing tx', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Balance covers only one tx setFeePayerBalance(DEFAULT_TX_FEE_LIMIT + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3498,7 +3498,7 @@ describe('TxPoolV2', () => { }); it('batch from same fee payer - only top N by priority accepted', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Balance covers exactly 2 tx fee limits setFeePayerBalance(DEFAULT_TX_FEE_LIMIT * 2n + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3546,7 +3546,7 @@ describe('TxPoolV2', () => { }; it('evicts low-priority txs after BLOCK_MINED when balance is insufficient', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Initial balance covers all 3 txs setFeePayerBalance(DEFAULT_TX_FEE_LIMIT * 3n + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3587,7 +3587,7 @@ describe('TxPoolV2', () => { }); it('evicts low-priority txs after CHAIN_PRUNED when balance is insufficient', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Initial balance covers both txs setFeePayerBalance(DEFAULT_TX_FEE_LIMIT * 2n + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3621,7 +3621,7 @@ describe('TxPoolV2', () => { }); it('priority ordering is correct - highest priority funded first', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Initial balance covers all 3 txs setFeePayerBalance(DEFAULT_TX_FEE_LIMIT * 3n + DEFAULT_TX_FEE_LIMIT / 2n); @@ -3663,7 +3663,7 @@ describe('TxPoolV2', () => { }); it('does not evict when balance is sufficient', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Balance covers all txs setFeePayerBalance(BigInt(1e18)); @@ -4737,7 +4737,7 @@ describe('TxPoolV2', () => { }); it('fee payer balance + nullifier conflict - higher priority wins both', async () => { - const sharedFeePayer = AztecAddress.fromBigInt(999n); + const sharedFeePayer = AztecAddress.fromBigIntUnsafe(999n); // Set balance to only cover 1 tx db.getLeafPreimage.mockImplementation((tree, index) => { if (tree === MerkleTreeId.PUBLIC_DATA_TREE) { @@ -4781,8 +4781,8 @@ describe('TxPoolV2', () => { }); it('batch with nullifier conflicts across different fee payers', async () => { - const feePayerA = AztecAddress.fromBigInt(111n); - const feePayerB = AztecAddress.fromBigInt(222n); + const feePayerA = AztecAddress.fromBigIntUnsafe(111n); + const feePayerB = AztecAddress.fromBigIntUnsafe(222n); // tx1 (fee payer A, low priority) and tx2 (fee payer B, high priority) share nullifier const tx1 = await mockTx(1, { diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_bench.test.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_bench.test.ts index b018afcecbfb..c2399cf02084 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_bench.test.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_bench.test.ts @@ -37,7 +37,11 @@ describe('TxPoolV2: benchmarks', () => { const metrics = new TxPoolBenchMetrics(); // Use a fixed set of fee payers to test fee payer index with multiple txs per payer - const feePayers = [AztecAddress.fromBigInt(1n), AztecAddress.fromBigInt(2n), AztecAddress.fromBigInt(3n)]; + const feePayers = [ + AztecAddress.fromBigIntUnsafe(1n), + AztecAddress.fromBigIntUnsafe(2n), + AztecAddress.fromBigIntUnsafe(3n), + ]; // Pre-created transaction pools for different sizes const POOL_SIZES = [10, 100, 1000] as const; diff --git a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts index 8cfb4a7c24ab..13397cc4476f 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts @@ -1139,7 +1139,7 @@ export class TxPoolV2Impl { const publicStateSource = new DatabasePublicStateSource(db); const balance = await publicStateSource.storageRead( ProtocolContractAddress.FeeJuice, - await computeFeePayerBalanceStorageSlot(AztecAddress.fromString(feePayer)), + await computeFeePayerBalanceStorageSlot(AztecAddress.fromStringUnsafe(feePayer)), ); return balance.toBigInt(); }, diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts index 2d94c2f4e65d..1e374af572e7 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts @@ -427,7 +427,7 @@ describe('PhasesTxValidator', () => { }); describe('rejectNullMsgSender validation', () => { - const nullMsgSender = AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS); + const nullMsgSender = AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS); let rejectNullContract: AztecAddress; let rejectNullSelector: FunctionSelector; let noRejectNullContract: AztecAddress; diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts index 39362bd39dd2..4156b3b91770 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts @@ -97,7 +97,7 @@ export class PhasesTxValidator implements TxValidator { } if ( entry.rejectNullMsgSender && - publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS)) + publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS)) ) { return TX_ERROR_SETUP_NULL_MSG_SENDER; } @@ -130,7 +130,7 @@ export class PhasesTxValidator implements TxValidator { } if ( entry.rejectNullMsgSender && - publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS)) + publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS)) ) { return TX_ERROR_SETUP_NULL_MSG_SENDER; } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/tx_validator_bench.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/tx_validator_bench.test.ts index c4bc38f7103f..967fac31ab46 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/tx_validator_bench.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/tx_validator_bench.test.ts @@ -105,7 +105,7 @@ describe('TxValidator: Benchmarks', () => { ccLogFields = ContractClassLogFields.random(); const logHash = await ccLogFields.hash(); const scopedLogHash = LogHash.from({ value: logHash, length: CONTRACT_CLASS_LOG_SIZE_IN_FIELDS }).scope( - AztecAddress.fromNumber(1), + AztecAddress.fromNumberUnsafe(1), ); ccLogTx.contractClassLogFields.push(ccLogFields); ccLogTx.data.forPublic!.nonRevertibleAccumulatedData.contractClassLogsHashes[0] = scopedLogHash; @@ -172,7 +172,7 @@ describe('TxValidator: Benchmarks', () => { phasesPrivateTx = await mockTxForRollup(10); // PhasesTxValidator - public tx with allowed setup - const allowedAddress = AztecAddress.fromNumber(999); + const allowedAddress = AztecAddress.fromNumberUnsafe(999); const allowedSelector = makeSelector(1); phasesPublicTx = await mockTx(11, { numberOfNonRevertiblePublicCallRequests: 1 }); await patchNonRevertibleFn(phasesPublicTx, 0, { address: allowedAddress, selector: allowedSelector }); diff --git a/yarn-project/protocol-contracts/src/scripts/generate_data.ts b/yarn-project/protocol-contracts/src/scripts/generate_data.ts index e97b2909b948..c734ba42c641 100644 --- a/yarn-project/protocol-contracts/src/scripts/generate_data.ts +++ b/yarn-project/protocol-contracts/src/scripts/generate_data.ts @@ -132,7 +132,9 @@ function generateSalts(names: string[]) { } function generateContractAddresses(names: string[]) { - const addresses = names.map(name => `${name}: AztecAddress.fromBigInt(${contractAddressMapping[name]}n)`).join(',\n'); + const addresses = names + .map(name => `${name}: AztecAddress.fromBigIntUnsafe(${contractAddressMapping[name]}n)`) + .join(',\n'); return ` export const ProtocolContractAddress: Record = { ${addresses} @@ -143,7 +145,7 @@ function generateContractAddresses(names: string[]) { function generateDerivedAddresses(names: string[], contractData: ContractData[]) { return ` export const ProtocolContractDerivedAddress = { - ${contractData.map((d, i) => `${names[i]}: AztecAddress.fromString('${d.address.toString()}')`).join(',\n')} + ${contractData.map((d, i) => `${names[i]}: AztecAddress.fromStringUnsafe('${d.address.toString()}')`).join(',\n')} }; `; } @@ -200,7 +202,7 @@ async function generateProtocolContractsList(names: string[], contractData: Cont return ` export const ProtocolContractsList = new ProtocolContracts([ - ${list.map(address => `AztecAddress.fromString('${address.toString()}')`).join(',\n')} + ${list.map(address => `AztecAddress.fromStringUnsafe('${address.toString()}')`).join(',\n')} ]); export const protocolContractsHash = Fr.fromString('${(await new ProtocolContracts(list).hash()).toString()}'); @@ -257,7 +259,7 @@ async function main() { const artifact = await copyArtifact(srcName, destName); await generateDeclarationFile(destName); contractDataList.push( - await computeContractData(artifact, AztecAddress.fromBigInt(BigInt(contractAddressMapping[destName]))), + await computeContractData(artifact, AztecAddress.fromBigIntUnsafe(BigInt(contractAddressMapping[destName]))), ); } diff --git a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.bench.test.ts b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.bench.test.ts index 907368ea83ab..a44f01610f14 100644 --- a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.bench.test.ts +++ b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.bench.test.ts @@ -41,7 +41,7 @@ describe('LightweightCheckpointBuilder benchmarks', () => { const toGithubActionBenchmarkJSON = (indent = 2) => JSON.stringify(results, null, indent); beforeEach(async () => { - feePayer = AztecAddress.fromNumber(42222); + feePayer = AztecAddress.fromNumberUnsafe(42222); feePayerBalance = new Fr(10n ** 20n); const feePayerSlot = await computeFeePayerBalanceLeafSlot(feePayer); const genesis: GenesisData = { @@ -120,7 +120,7 @@ describe('LightweightCheckpointBuilder benchmarks', () => { // Add a full contract class log (CONTRACT_CLASS_LOG_SIZE_IN_FIELDS = 3,023 blob fields). tx.txEffect.contractClassLogs = [ new ContractClassLog( - AztecAddress.fromNumber(seed), + AztecAddress.fromNumberUnsafe(seed), ContractClassLogFields.random(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS), CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, ), diff --git a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.test.ts b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.test.ts index 7ed3e2623b23..2ab1c908f2b2 100644 --- a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.test.ts +++ b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.test.ts @@ -28,7 +28,7 @@ describe('LightweightCheckpointBuilder', () => { beforeEach(async () => { // Set up fee payer with balance - feePayer = AztecAddress.fromNumber(42222); + feePayer = AztecAddress.fromNumberUnsafe(42222); feePayerBalance = new Fr(10n ** 20n); const feePayerSlot = await computeFeePayerBalanceLeafSlot(feePayer); const genesis: GenesisData = { diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 065e19142844..24096fe0aa99 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -86,7 +86,7 @@ export class TestContext { ) { const directoriesToCleanup: string[] = []; - const feePayer = AztecAddress.fromNumber(42222); + const feePayer = AztecAddress.fromNumberUnsafe(42222); const initialFeePayerBalance = new Fr(10n ** 20n); const feePayerSlot = await computeFeePayerBalanceLeafSlot(feePayer); const genesis: GenesisData = { diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.test.ts index b7a6eaf9ce96..dc614c8f8354 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.test.ts @@ -29,7 +29,7 @@ describe('EventValidationRequest', () => { const request = EventValidationRequest.fromFields(serialized); - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); + expect(request.contractAddress).toEqual(AztecAddress.fromBigIntUnsafe(1n)); expect(request.eventTypeId).toEqual(new EventSelector(2)); expect(request.randomness).toEqual(new Fr(3)); expect(request.serializedEvent).toEqual([new Fr(4), new Fr(5)]); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.ts index 4a48bf4805fd..21b3823f4164 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.ts @@ -21,7 +21,7 @@ export class EventValidationRequest { static fromFields(fields: Fr[] | FieldReader): EventValidationRequest { const reader = FieldReader.asReader(fields); - const contractAddress = AztecAddress.fromField(reader.readField()); + const contractAddress = AztecAddress.fromFieldUnsafe(reader.readField()); const eventTypeId = EventSelector.fromField(reader.readField()); const randomness = reader.readField(); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts index 175f7f906bcb..4ce60ac76e72 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts @@ -19,7 +19,7 @@ describe('LogRetrievalRequest', () => { const request = LogRetrievalRequest.fromFields(serialized); - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); + expect(request.contractAddress).toEqual(AztecAddress.fromBigIntUnsafe(1n)); expect(request.tag).toEqual(new Tag(new Fr(2))); expect(request.source).toEqual(LogSource.PUBLIC_AND_PRIVATE); expect(request.fromBlock).toBeUndefined(); @@ -39,7 +39,7 @@ describe('LogRetrievalRequest', () => { const request = LogRetrievalRequest.fromFields(serialized); - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); + expect(request.contractAddress).toEqual(AztecAddress.fromBigIntUnsafe(1n)); expect(request.tag).toEqual(new Tag(new Fr(2))); expect(request.source).toEqual(LogSource.PUBLIC); expect(request.fromBlock).toEqual(BlockNumber(10)); @@ -62,7 +62,7 @@ describe('LogRetrievalRequest', () => { it('accepts all valid LogSource values', () => { for (const source of [LogSource.PRIVATE, LogSource.PUBLIC, LogSource.PUBLIC_AND_PRIVATE]) { - const fields = new LogRetrievalRequest(AztecAddress.fromBigInt(1n), new Tag(new Fr(2)), source).toFields(); + const fields = new LogRetrievalRequest(AztecAddress.fromBigIntUnsafe(1n), new Tag(new Fr(2)), source).toFields(); const restored = LogRetrievalRequest.fromFields(fields); expect(restored.source).toEqual(source); } @@ -70,7 +70,7 @@ describe('LogRetrievalRequest', () => { it('round-trips through toFields and fromFields', () => { const original = new LogRetrievalRequest( - AztecAddress.fromBigInt(42n), + AztecAddress.fromBigIntUnsafe(42n), new Tag(new Fr(99)), LogSource.PRIVATE, BlockNumber(5), diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts index 549b452d2d39..d2a6a189c1f9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.ts @@ -39,7 +39,7 @@ export class LogRetrievalRequest { static fromFields(fields: Fr[] | FieldReader): LogRetrievalRequest { const reader = FieldReader.asReader(fields); - const contractAddress = AztecAddress.fromField(reader.readField()); + const contractAddress = AztecAddress.fromFieldUnsafe(reader.readField()); const tag = new Tag(reader.readField()); const sourceNum = reader.readField().toNumber(); if (!(sourceNum in LogSource)) { diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.test.ts index 6c1b4384b3e6..4912c772f1b5 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.test.ts @@ -29,8 +29,8 @@ describe('NoteValidationRequest', () => { const request = NoteValidationRequest.fromFields(serialized); - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); - expect(request.owner).toEqual(AztecAddress.fromBigInt(50n)); + expect(request.contractAddress).toEqual(AztecAddress.fromBigIntUnsafe(1n)); + expect(request.owner).toEqual(AztecAddress.fromBigIntUnsafe(50n)); expect(request.storageSlot).toEqual(new Fr(2)); expect(request.randomness).toEqual(new Fr(42)); expect(request.noteNonce).toEqual(new Fr(3)); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.ts index 82099f6e65ef..29217123838a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.ts @@ -23,8 +23,8 @@ export class NoteValidationRequest { static fromFields(fields: Fr[] | FieldReader): NoteValidationRequest { const reader = FieldReader.asReader(fields); - const contractAddress = AztecAddress.fromField(reader.readField()); - const owner = AztecAddress.fromField(reader.readField()); + const contractAddress = AztecAddress.fromFieldUnsafe(reader.readField()); + const owner = AztecAddress.fromFieldUnsafe(reader.readField()); const storageSlot = reader.readField(); const randomness = reader.readField(); const noteNonce = reader.readField(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/note_packing_utils.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/note_packing_utils.test.ts index 45aa99434acb..d881fb9d6215 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/note_packing_utils.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/note_packing_utils.test.ts @@ -7,8 +7,8 @@ import { packAsHintedNote } from './note_packing_utils.js'; it('packs hinted note', () => { const noteInfo = { - contractAddress: AztecAddress.fromField(new Fr(1n)), - owner: AztecAddress.fromField(new Fr(5n)), + contractAddress: AztecAddress.fromFieldUnsafe(new Fr(1n)), + owner: AztecAddress.fromFieldUnsafe(new Fr(5n)), randomness: new Fr(42n), storageSlot: new Fr(100n), noteNonce: new Fr(2n), diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts index 7a277d059164..9eb32083939b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.ts @@ -189,7 +189,7 @@ export const STR: TypeMapping = { export const AZTEC_ADDRESS: TypeMapping = { serialization: { fn: v => [v.toField()] }, - deserialization: { fn: ([reader]) => AztecAddress.fromField(reader.readField()) }, + deserialization: { fn: ([reader]) => AztecAddress.fromFieldUnsafe(reader.readField()) }, shape: ['scalar'], }; @@ -315,7 +315,7 @@ export const PUBLIC_KEYS_AND_PARTIAL_ADDRESS: TypeMapping<{ export const CONTRACT_CLASS_LOG_INPUT: TypeMapping = { deserialization: { fn: ([addrReader, fieldsReader, lengthReader]) => { - const addr = AztecAddress.fromField(addrReader.readField()); + const addr = AztecAddress.fromFieldUnsafe(addrReader.readField()); const fields = new ContractClassLogFields(fieldsReader.readFieldArray(fieldsReader.remainingFields())); const length = lengthReader.readField().toNumber(); return new ContractClassLog(addr, fields, length); @@ -537,7 +537,7 @@ export function BOUNDED_VEC( * For the `None` case, the inner's slots must still be present on the wire as zero-padding (so `Some` and `None` have * identical size). That padding is derived entirely from `inner.shape`. * - * @example Serializing `Option.some(AztecAddress.fromField(Fr(42)))` with `OPTION(AZTEC_ADDRESS)`: + * @example Serializing `Option.some(AztecAddress.fromFieldUnsafe(Fr(42)))` with `OPTION(AZTEC_ADDRESS)`: * ``` * slot 0: Fr(1) // discriminant: Some * slot 1: Fr(42) // inner value diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index de0e017b0046..7546ea8dbb3e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -535,7 +535,7 @@ describe('Private Execution test suite', () => { anchorBlockHeader, functionName: 'constructor', contractAddress: instance.address, - msgSender: AztecAddress.fromNumber(1234), + msgSender: AztecAddress.fromNumberUnsafe(1234), }); const result = executionResult.entrypoint.nestedExecutionResults[0]; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts index 300e03b25d6e..f1fd82f3815e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.test.ts @@ -37,7 +37,7 @@ describe('PrivateExecutionOracle', () => { beforeAll(async () => { contractAddress = await AztecAddress.random(); callContext = new CallContext( - AztecAddress.fromField(Fr.MAX_FIELD_VALUE), + AztecAddress.fromFieldUnsafe(Fr.MAX_FIELD_VALUE), contractAddress, FunctionSelector.empty(), false, diff --git a/yarn-project/pxe/src/contract_function_simulator/transient_array_service.test.ts b/yarn-project/pxe/src/contract_function_simulator/transient_array_service.test.ts index beed9990f050..84b877132d91 100644 --- a/yarn-project/pxe/src/contract_function_simulator/transient_array_service.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/transient_array_service.test.ts @@ -5,8 +5,8 @@ import { TransientArrayService } from './transient_array_service.js'; describe('TransientArrayService', () => { let service: TransientArrayService; - const contractA = AztecAddress.fromField(new Fr(0xaa)); - const contractB = AztecAddress.fromField(new Fr(0xbb)); + const contractA = AztecAddress.fromFieldUnsafe(new Fr(0xaa)); + const contractB = AztecAddress.fromFieldUnsafe(new Fr(0xbb)); const slot = Fr.fromString('0x01'); const otherSlot = Fr.fromString('0x02'); diff --git a/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts b/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts index ad59004f7f91..97f4f4177f6e 100644 --- a/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts +++ b/yarn-project/pxe/src/contract_sync/contract_sync_service.test.ts @@ -21,9 +21,9 @@ describe('ContractSyncService', () => { let service: ContractSyncService; let utilityExecutor: jest.Mock<(call: FunctionCall, scopes: AztecAddress[]) => Promise>; - const contractAddress = AztecAddress.fromBigInt(100n); - const scopeA = AztecAddress.fromBigInt(200n); - const scopeB = AztecAddress.fromBigInt(201n); + const contractAddress = AztecAddress.fromBigIntUnsafe(100n); + const scopeA = AztecAddress.fromBigIntUnsafe(200n); + const scopeB = AztecAddress.fromBigIntUnsafe(201n); const jobId = 'job-1'; const anchorBlockHeader = makeBlockHeader(0); const classId = Fr.fromHexString('0xdeadbeef'); @@ -145,10 +145,10 @@ describe('ContractSyncService', () => { // acquire one. Per-call limiters give the nested syncs their own slots, so the batch completes. it('does not deadlock when concurrent syncs each trigger a nested sync', async () => { const outerContracts = Array.from({ length: MAX_CONCURRENT_SCOPE_SYNCS }, (_, i) => - AztecAddress.fromBigInt(1000n + BigInt(i)), + AztecAddress.fromBigIntUnsafe(1000n + BigInt(i)), ); const nestedContracts = Array.from({ length: MAX_CONCURRENT_SCOPE_SYNCS }, (_, i) => - AztecAddress.fromBigInt(2000n + BigInt(i)), + AztecAddress.fromBigIntUnsafe(2000n + BigInt(i)), ); const nestedByOuter = new Map(outerContracts.map((outer, i) => [outer.toString(), nestedContracts[i]])); @@ -170,7 +170,7 @@ describe('ContractSyncService', () => { it('bounds the number of concurrently syncing scopes within a single call', async () => { const scopes = Array.from({ length: MAX_CONCURRENT_SCOPE_SYNCS + 3 }, (_, i) => - AztecAddress.fromBigInt(500n + BigInt(i)), + AztecAddress.fromBigIntUnsafe(500n + BigInt(i)), ); let inFlight = 0; @@ -252,7 +252,7 @@ describe('ContractSyncService', () => { }); describe('class ID verification deduplication', () => { - const contract2 = AztecAddress.fromBigInt(300n); + const contract2 = AztecAddress.fromBigIntUnsafe(300n); it('verifies class ID only once per contract across scope batches', async () => { await service.ensureContractSynced(contractAddress, null, utilityExecutor, anchorBlockHeader, jobId, [scopeA]); @@ -354,7 +354,7 @@ describe('ContractSyncService', () => { }); describe('invalidateContractForScopes', () => { - const contract2 = AztecAddress.fromBigInt(300n); + const contract2 = AztecAddress.fromBigIntUnsafe(300n); it('only invalidates the targeted scope', async () => { await service.ensureContractSynced(contractAddress, null, utilityExecutor, anchorBlockHeader, jobId, [ diff --git a/yarn-project/pxe/src/error_enriching.ts b/yarn-project/pxe/src/error_enriching.ts index 0bd889147cb2..78343df6a213 100644 --- a/yarn-project/pxe/src/error_enriching.ts +++ b/yarn-project/pxe/src/error_enriching.ts @@ -29,7 +29,7 @@ export async function enrichSimulationError(err: SimulationError, contractStore: await Promise.all( [...mentionedFunctions.entries()].map(async ([contractAddress, fnSelectors]) => { - const parsedContractAddress = AztecAddress.fromString(contractAddress); + const parsedContractAddress = AztecAddress.fromStringUnsafe(contractAddress); const contract = await contractStore.getContract(parsedContractAddress); if (contract) { err.enrichWithContractName(parsedContractAddress, contract.name); diff --git a/yarn-project/pxe/src/private_kernel/batch_planner.test.ts b/yarn-project/pxe/src/private_kernel/batch_planner.test.ts index 9999e6202864..d150b76044d2 100644 --- a/yarn-project/pxe/src/private_kernel/batch_planner.test.ts +++ b/yarn-project/pxe/src/private_kernel/batch_planner.test.ts @@ -12,7 +12,7 @@ import times from 'lodash.times'; import { BatchPlanner } from './batch_planner.js'; import { PrivateCircuitPublicInputsBuilder, PrivateKernelCircuitPublicInputsBuilder } from './hints/test_utils.js'; -const contractAddress = AztecAddress.fromBigInt(987654n); +const contractAddress = AztecAddress.fromBigIntUnsafe(987654n); /** * Wraps a `PrivateCircuitPublicInputs` in a `PrivateCallExecutionResult` with the given children. diff --git a/yarn-project/pxe/src/private_kernel/hints/test_utils.ts b/yarn-project/pxe/src/private_kernel/hints/test_utils.ts index 9a1fba6009ed..ff7e98bf4130 100644 --- a/yarn-project/pxe/src/private_kernel/hints/test_utils.ts +++ b/yarn-project/pxe/src/private_kernel/hints/test_utils.ts @@ -36,7 +36,7 @@ import { PrivateLog } from '@aztec/stdlib/logs'; import { PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import { VerificationKeyData } from '@aztec/stdlib/vks'; -const DEFAULT_CONTRACT_ADDRESS = AztecAddress.fromBigInt(987654n); +const DEFAULT_CONTRACT_ADDRESS = AztecAddress.fromBigIntUnsafe(987654n); /** * Builds a ClaimedLengthArray from a list of items, padding to the required size. diff --git a/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts b/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts index a44bab2ad2fc..3c3a0f466d0a 100644 --- a/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts +++ b/yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.test.ts @@ -39,7 +39,7 @@ describe('Private Kernel Sequencer', () => { let prover: PrivateKernelExecutionProver; let dependencies: { [name: string]: string[] } = {}; - const contractAddress = AztecAddress.fromBigInt(987654n); + const contractAddress = AztecAddress.fromBigIntUnsafe(987654n); const blockTimestamp = 12345n; const expirationTimestamp = blockTimestamp + BigInt(MAX_TX_LIFETIME); @@ -414,7 +414,7 @@ describe('Private Kernel Sequencer', () => { }); it('fetches updated class id hints once per unique contract address', async () => { - const contractAddressB = AztecAddress.fromBigInt(111111n); + const contractAddressB = AztecAddress.fromBigIntUnsafe(111111n); // a { b {} c {} } // a and c use contractAddress, b uses contractAddressB → 2 unique contracts, 3 executions. diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts index a872ab5a90d0..587c540ed1c3 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts @@ -107,7 +107,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ SlotNumber(47), 53n, EthAddress.fromField(new Fr(59n)), - AztecAddress.fromBigInt(61n), + AztecAddress.fromBigIntUnsafe(61n), new GasFees(67n, 71n), ), new Fr(73n), @@ -126,8 +126,8 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ const capsuleStore = new CapsuleStore(kvStore); const jobId = 'fixture-job'; - const contractAddress = AztecAddress.fromBigInt(2n); - const scope = AztecAddress.fromBigInt(3n); + const contractAddress = AztecAddress.fromBigIntUnsafe(2n); + const scope = AztecAddress.fromBigIntUnsafe(3n); // Three setCapsule calls (2-element, 1-element, 0-element value vector) pin every value-encoding length case. capsuleStore.setCapsule(contractAddress, new Fr(5n), [new Fr(7n), new Fr(11n)], jobId, scope); @@ -189,7 +189,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ new SerializableContractInstance({ version: 2, salt: new Fr(73n), - deployer: AztecAddress.fromBigInt(79n), + deployer: AztecAddress.fromBigIntUnsafe(79n), currentContractClassId: new Fr(83n), originalContractClassId: new Fr(89n), initializationHash: new Fr(97n), @@ -205,7 +205,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ new Fr(71n), new Fr(73n), ), - }).withAddress(AztecAddress.fromBigInt(101n)), + }).withAddress(AztecAddress.fromBigIntUnsafe(101n)), ); }, snapshotStore: async kvStore => ({ @@ -244,7 +244,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ SlotNumber(19), 23n, EthAddress.fromField(new Fr(29n)), - AztecAddress.fromBigInt(31n), + AztecAddress.fromBigIntUnsafe(31n), new GasFees(37n, 41n), new Fr(43n), new Fr(47n), @@ -294,17 +294,17 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ // Two contracts so `note_nullifiers_by_contract` exhibits both a multi-value row (contractA → {n1, n2}) and a // single-value row (contractB → {n3}). - const contractA = AztecAddress.fromBigInt(2n); - const contractB = AztecAddress.fromBigInt(3n); - const scopeX = AztecAddress.fromBigInt(5n); - const scopeY = AztecAddress.fromBigInt(7n); + const contractA = AztecAddress.fromBigIntUnsafe(2n); + const contractB = AztecAddress.fromBigIntUnsafe(3n); + const scopeX = AztecAddress.fromBigIntUnsafe(5n); + const scopeY = AztecAddress.fromBigIntUnsafe(7n); // note1: active, will be added under two scopes to exercise the multi-element scopes vector encoding in // `StoredNote.toBuffer`. const note1 = new NoteDao( new Note([new Fr(13n), new Fr(17n), new Fr(19n)]), contractA, - AztecAddress.fromBigInt(23n), + AztecAddress.fromBigIntUnsafe(23n), new Fr(29n), new Fr(31n), new Fr(37n), @@ -321,7 +321,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ const note2 = new NoteDao( new Note([new Fr(71n), new Fr(73n), new Fr(79n)]), contractA, - AztecAddress.fromBigInt(83n), + AztecAddress.fromBigIntUnsafe(83n), new Fr(89n), new Fr(97n), new Fr(101n), @@ -339,7 +339,7 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ const note3 = new NoteDao( new Note([new Fr(139n), new Fr(149n), new Fr(151n)]), contractB, - AztecAddress.fromBigInt(157n), + AztecAddress.fromBigIntUnsafe(157n), new Fr(163n), new Fr(167n), new Fr(173n), @@ -392,12 +392,12 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ // Two (contract, selector) pairs and two block numbers so each multimap exhibits both a multi-value row // (contractA/selectorA → {e1, e2} and blockN1 → {e1, e2}) and a contrasting single-value row. - const contractA = AztecAddress.fromBigInt(2n); - const contractB = AztecAddress.fromBigInt(3n); + const contractA = AztecAddress.fromBigIntUnsafe(2n); + const contractB = AztecAddress.fromBigIntUnsafe(3n); const selectorA = EventSelector.fromField(new Fr(5n)); const selectorB = EventSelector.fromField(new Fr(7n)); - const scopeX = AztecAddress.fromBigInt(11n); - const scopeY = AztecAddress.fromBigInt(13n); + const scopeX = AztecAddress.fromBigIntUnsafe(11n); + const scopeY = AztecAddress.fromBigIntUnsafe(13n); const blockN1 = BlockNumber(17); const blockN2 = BlockNumber(19); @@ -492,8 +492,8 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ const recipientTaggingStore = new RecipientTaggingStore(kvStore); const jobId = 'fixture-job'; - const secretA = new AppTaggingSecret(new Fr(2n), AztecAddress.fromBigInt(3n)); - const secretB = new AppTaggingSecret(new Fr(5n), AztecAddress.fromBigInt(7n)); + const secretA = new AppTaggingSecret(new Fr(2n), AztecAddress.fromBigIntUnsafe(3n)); + const secretB = new AppTaggingSecret(new Fr(5n), AztecAddress.fromBigIntUnsafe(7n)); await recipientTaggingStore.updateHighestFinalizedIndex(secretA, 11, jobId); await recipientTaggingStore.updateHighestAgedIndex(secretA, 13, jobId); @@ -511,14 +511,20 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ writeToStore: async kvStore => { const taggingSecretSourcesStore = new TaggingSecretSourcesStore(kvStore); - await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(2n)); - await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(3n)); - await taggingSecretSourcesStore.addSender(AztecAddress.fromBigInt(5n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigIntUnsafe(2n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigIntUnsafe(3n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigIntUnsafe(5n)); - await taggingSecretSourcesStore.addSharedSecret(AztecAddress.fromBigInt(7n), new Point(new Fr(2n), new Fr(3n))); - await taggingSecretSourcesStore.addSharedSecret(AztecAddress.fromBigInt(7n), new Point(new Fr(5n), new Fr(7n))); await taggingSecretSourcesStore.addSharedSecret( - AztecAddress.fromBigInt(11n), + AztecAddress.fromBigIntUnsafe(7n), + new Point(new Fr(2n), new Fr(3n)), + ); + await taggingSecretSourcesStore.addSharedSecret( + AztecAddress.fromBigIntUnsafe(7n), + new Point(new Fr(5n), new Fr(7n)), + ); + await taggingSecretSourcesStore.addSharedSecret( + AztecAddress.fromBigIntUnsafe(11n), new Point(new Fr(13n), new Fr(17n)), ); }, @@ -534,9 +540,9 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ const senderTaggingStore = new SenderTaggingStore(kvStore); const jobId = 'fixture-job'; - const secretA = new AppTaggingSecret(new Fr(2n), AztecAddress.fromBigInt(3n)); - const secretB = new AppTaggingSecret(new Fr(5n), AztecAddress.fromBigInt(7n)); - const secretC = new AppTaggingSecret(new Fr(11n), AztecAddress.fromBigInt(13n)); + const secretA = new AppTaggingSecret(new Fr(2n), AztecAddress.fromBigIntUnsafe(3n)); + const secretB = new AppTaggingSecret(new Fr(5n), AztecAddress.fromBigIntUnsafe(7n)); + const secretC = new AppTaggingSecret(new Fr(11n), AztecAddress.fromBigIntUnsafe(13n)); const txHashA = TxHash.fromBigInt(17n); const txHashB = TxHash.fromBigInt(19n); const txHashC = TxHash.fromBigInt(23n); @@ -630,7 +636,7 @@ function buildL2Block(): L2Block { SlotNumber(181), 191n, EthAddress.fromField(new Fr(193n)), - AztecAddress.fromBigInt(197n), + AztecAddress.fromBigIntUnsafe(197n), new GasFees(199n, 211n), ), new Fr(223n), @@ -651,10 +657,10 @@ function buildL2Block(): L2Block { 3, ), ], - [new PublicLog(AztecAddress.fromBigInt(281n), [new Fr(283n), new Fr(293n)])], + [new PublicLog(AztecAddress.fromBigIntUnsafe(281n), [new Fr(283n), new Fr(293n)])], [ new ContractClassLog( - AztecAddress.fromBigInt(307n), + AztecAddress.fromBigIntUnsafe(307n), new ContractClassLogFields(paddedFrs([311n, 313n, 317n], CONTRACT_CLASS_LOG_SIZE_IN_FIELDS)), 3, ), diff --git a/yarn-project/pxe/src/storage/contract_store/contract_store.ts b/yarn-project/pxe/src/storage/contract_store/contract_store.ts index 57cd75b229a6..5b3e9efdf97f 100644 --- a/yarn-project/pxe/src/storage/contract_store/contract_store.ts +++ b/yarn-project/pxe/src/storage/contract_store/contract_store.ts @@ -215,7 +215,7 @@ export class ContractStore { getContractsAddresses(): Promise { return this.#store.transactionAsync(async () => { const keys = await toArray(this.#contractInstances.keysAsync()); - return keys.map(AztecAddress.fromString); + return keys.map(AztecAddress.fromStringUnsafe); }); } diff --git a/yarn-project/pxe/src/storage/note_store/note_store.test.ts b/yarn-project/pxe/src/storage/note_store/note_store.test.ts index bf1bbd8b388d..6c372345b902 100644 --- a/yarn-project/pxe/src/storage/note_store/note_store.test.ts +++ b/yarn-project/pxe/src/storage/note_store/note_store.test.ts @@ -10,11 +10,13 @@ import { NoteStore } from './note_store.js'; // ----------------------------------------------------------------------------- // Shared constants for deterministic fixtures // ----------------------------------------------------------------------------- -const CONTRACT_A = AztecAddress.fromString('0x0eadbeef00000000000000000000000000000000000000000000000000000000'); -const CONTRACT_B = AztecAddress.fromString('0x0eedface00000000000000000000000000000000000000000000000000000000'); -const SCOPE_1 = AztecAddress.fromString('0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a'); -const SCOPE_2 = AztecAddress.fromString('0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'); -const FAKE_ADDRESS = AztecAddress.fromString('0x1111111111111111111111111111111111111111111111111111111111111111'); +const CONTRACT_A = AztecAddress.fromStringUnsafe('0x0eadbeef00000000000000000000000000000000000000000000000000000000'); +const CONTRACT_B = AztecAddress.fromStringUnsafe('0x0eedface00000000000000000000000000000000000000000000000000000000'); +const SCOPE_1 = AztecAddress.fromStringUnsafe('0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a'); +const SCOPE_2 = AztecAddress.fromStringUnsafe('0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'); +const FAKE_ADDRESS = AztecAddress.fromStringUnsafe( + '0x1111111111111111111111111111111111111111111111111111111111111111', +); const SLOT_X = Fr.fromString('0x01'); const SLOT_Y = Fr.fromString('0x02'); const NON_EXISTING_SLOT = Fr.fromString('0xabad1dea'); @@ -753,8 +755,8 @@ describe('NoteStore', () => { describe('NoteStore.rollback', () => { const JOB = 'note-store-test-job'; - const scope = AztecAddress.fromBigInt(1n); - const contract = AztecAddress.fromBigInt(100n); + const scope = AztecAddress.fromBigIntUnsafe(1n); + const contract = AztecAddress.fromBigIntUnsafe(100n); let kv: Awaited>; let store: NoteStore; diff --git a/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts index aff377255fd7..5dc475e79880 100644 --- a/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts +++ b/yarn-project/pxe/src/storage/tagging_store/tagging_secret_sources_store.ts @@ -41,7 +41,7 @@ export class TaggingSecretSourcesStore { getSenders(): Promise { return this.#store.transactionAsync(async () => { - return (await toArray(this.#senders.keysAsync())).map(AztecAddress.fromString); + return (await toArray(this.#senders.keysAsync())).map(AztecAddress.fromStringUnsafe); }); } diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index 0d1d24e52b92..3e88d931f4e3 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -144,7 +144,7 @@ export const sequencerConfigMappings: ConfigMappingsType = { }, feeRecipient: { env: 'FEE_RECIPIENT', - parseEnv: (val: string) => AztecAddress.fromString(val), + parseEnv: (val: string) => AztecAddress.fromStringUnsafe(val), description: 'Address to receive fees.', }, acvmWorkingDirectory: { diff --git a/yarn-project/simulator/src/public/avm/apps_tests/avm_test.test.ts b/yarn-project/simulator/src/public/avm/apps_tests/avm_test.test.ts index d61fba0c8f46..004facc6e5ce 100644 --- a/yarn-project/simulator/src/public/avm/apps_tests/avm_test.test.ts +++ b/yarn-project/simulator/src/public/avm/apps_tests/avm_test.test.ts @@ -9,8 +9,8 @@ import { NativeWorldStateService } from '@aztec/world-state'; import { AvmSimulationTester } from '../fixtures/avm_simulation_tester.js'; describe('AVM simulator apps tests: AvmTestContract', () => { - const deployer = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(4200); + const deployer = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(4200); let testContractAddress: AztecAddress; let instances: ContractInstanceWithAddress[]; let worldStateService: NativeWorldStateService; diff --git a/yarn-project/simulator/src/public/avm/apps_tests/storage_proofs.test.ts b/yarn-project/simulator/src/public/avm/apps_tests/storage_proofs.test.ts index 15373f14ee83..a46098fa3bab 100644 --- a/yarn-project/simulator/src/public/avm/apps_tests/storage_proofs.test.ts +++ b/yarn-project/simulator/src/public/avm/apps_tests/storage_proofs.test.ts @@ -16,8 +16,8 @@ import { AvmSimulationTester } from '../fixtures/avm_simulation_tester.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); describe('AVM simulator apps tests: StorageProof', () => { - const deployer = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(4200); + const deployer = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(4200); let storageProver: ContractInstanceWithAddress; let worldStateService: NativeWorldStateService; let simTester: AvmSimulationTester; diff --git a/yarn-project/simulator/src/public/avm/apps_tests/token.test.ts b/yarn-project/simulator/src/public/avm/apps_tests/token.test.ts index c4c71ce8e844..6f5da061cf36 100644 --- a/yarn-project/simulator/src/public/avm/apps_tests/token.test.ts +++ b/yarn-project/simulator/src/public/avm/apps_tests/token.test.ts @@ -7,9 +7,9 @@ import { NativeWorldStateService } from '@aztec/world-state'; import { AvmSimulationTester } from '../fixtures/avm_simulation_tester.js'; describe('AVM simulator apps tests: TokenContract', () => { - const admin = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(111); - const receiver = AztecAddress.fromNumber(222); + const admin = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(111); + const receiver = AztecAddress.fromNumberUnsafe(222); let token: ContractInstanceWithAddress; let worldStateService: NativeWorldStateService; diff --git a/yarn-project/simulator/src/public/avm/avm_execution_environment.test.ts b/yarn-project/simulator/src/public/avm/avm_execution_environment.test.ts index a98e7f0dad2f..87f21a0d67df 100644 --- a/yarn-project/simulator/src/public/avm/avm_execution_environment.test.ts +++ b/yarn-project/simulator/src/public/avm/avm_execution_environment.test.ts @@ -6,7 +6,7 @@ import { initExecutionEnvironment } from './fixtures/initializers.js'; import { allSameExcept } from './fixtures/utils.js'; describe('Execution Environment', () => { - const newAddress = AztecAddress.fromNumber(123456); + const newAddress = AztecAddress.fromNumberUnsafe(123456); const calldata = new CallDataArray([new Fr(1n), new Fr(2n), new Fr(3n)]); it('New call should fork execution environment correctly', () => { diff --git a/yarn-project/simulator/src/public/avm/avm_simulator.test.ts b/yarn-project/simulator/src/public/avm/avm_simulator.test.ts index b6285108b102..03209fcaeb6a 100644 --- a/yarn-project/simulator/src/public/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/public/avm/avm_simulator.test.ts @@ -89,7 +89,7 @@ import { const siloAddress = (contractAddress: AztecAddress) => { const contractAddressNullifier = siloNullifier( - AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), + AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), contractAddress.toField(), ); return contractAddressNullifier; @@ -159,7 +159,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { it('execution of a non-existent contract immediately reverts and consumes all allocated gas', async () => { const treesDB = mock(); const persistableState = initPersistableStateManager({ treesDB }); - const address = AztecAddress.fromNumber(1234); + const address = AztecAddress.fromNumberUnsafe(1234); const env = initExecutionEnvironment({ address }); const context = initContext({ env, persistableState }); mockCheckNullifierExists(treesDB, false); @@ -581,8 +581,8 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); describe('Side effects, world state, nested calls', () => { - const address = AztecAddress.fromNumber(1); - const sender = AztecAddress.fromNumber(42); + const address = AztecAddress.fromNumberUnsafe(1); + const sender = AztecAddress.fromNumberUnsafe(42); const leafIndex = 7n; const slotNumber = 1; // must update Noir contract if changing this const slot = new Fr(slotNumber); @@ -909,7 +909,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const contractInstance = new SerializableContractInstance({ version: 2 as const, salt: new Fr(0x123), - deployer: AztecAddress.fromBigInt(0x456n), + deployer: AztecAddress.fromBigIntUnsafe(0x456n), currentContractClassId: new Fr(0x789), originalContractClassId: new Fr(0x789), initializationHash: new Fr(0x101112), @@ -1175,8 +1175,8 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); describe('Side effects including merkle checks', () => { - const address = AztecAddress.fromNumber(1); - const sender = AztecAddress.fromNumber(42); + const address = AztecAddress.fromNumberUnsafe(1); + const sender = AztecAddress.fromNumberUnsafe(42); const value0 = new Fr(420); diff --git a/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts b/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts index d239c9ad1e52..467fef56f186 100644 --- a/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts +++ b/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts @@ -149,7 +149,7 @@ export abstract class BaseAvmSimulationTester { private async insertContractAddressNullifier(contractAddress: AztecAddress) { const contractAddressNullifier = await siloNullifier( - AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), + AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), contractAddress.toField(), ); await this.merkleTrees.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()]); diff --git a/yarn-project/simulator/src/public/avm/fixtures/utils.ts b/yarn-project/simulator/src/public/avm/fixtures/utils.ts index 382fdd2a9764..b4aa338b078e 100644 --- a/yarn-project/simulator/src/public/avm/fixtures/utils.ts +++ b/yarn-project/simulator/src/public/avm/fixtures/utils.ts @@ -146,7 +146,7 @@ export async function createContractClassAndInstance( }); const contractAddressNullifier = await siloNullifier( - AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), + AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), contractInstance.address.toField(), ); diff --git a/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.test.ts index 6958a5c15bc3..be3a59468e9c 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.test.ts @@ -35,8 +35,8 @@ describe('Accrued Substate', () => { let persistableState: PublicPersistableStateManager; let context: AvmContext; - const address = AztecAddress.fromNumber(1); - const sender = AztecAddress.fromNumber(42); + const address = AztecAddress.fromNumberUnsafe(1); + const sender = AztecAddress.fromNumberUnsafe(42); const value0 = new Fr(69); // noteHash or nullifier... const value0Offset = 100; const value1 = new Fr(420); diff --git a/yarn-project/simulator/src/public/fixtures/amm_test.ts b/yarn-project/simulator/src/public/fixtures/amm_test.ts index 5b78faf274c8..e359e7c71a23 100644 --- a/yarn-project/simulator/src/public/fixtures/amm_test.ts +++ b/yarn-project/simulator/src/public/fixtures/amm_test.ts @@ -25,8 +25,8 @@ export async function ammTest( ) { const timer = new Timer(); - const admin = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(111); + const admin = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(111); logger.debug(`Deploying tokens`); const token0 = await setUpToken(tester, tokenArtifact, admin, expectToBeTrue, /*seed=*/ 0); diff --git a/yarn-project/simulator/src/public/fixtures/bulk_test.ts b/yarn-project/simulator/src/public/fixtures/bulk_test.ts index 650b142b0633..b0f755ed57be 100644 --- a/yarn-project/simulator/src/public/fixtures/bulk_test.ts +++ b/yarn-project/simulator/src/public/fixtures/bulk_test.ts @@ -13,7 +13,7 @@ export async function bulkTest( ) { const timer = new Timer(); - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); const avmTestContract = await tester.registerAndDeployContract( /*constructorArgs=*/ [], deployer, @@ -114,7 +114,7 @@ export async function megaBulkTest( ) { const timer = new Timer(); - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); const avmTestContract = await tester.registerAndDeployContract( /*constructorArgs=*/ [], deployer, diff --git a/yarn-project/simulator/src/public/fixtures/custom_bytecode_tester.ts b/yarn-project/simulator/src/public/fixtures/custom_bytecode_tester.ts index 53e0975fd337..07f7dde5e574 100644 --- a/yarn-project/simulator/src/public/fixtures/custom_bytecode_tester.ts +++ b/yarn-project/simulator/src/public/fixtures/custom_bytecode_tester.ts @@ -17,7 +17,7 @@ export async function deployCustomBytecode( bytecode: Buffer, tester: PublicTxSimulationTester, contractName: string = 'CustomBytecodeContract', - deployer: AztecAddress = AztecAddress.fromNumber(42), + deployer: AztecAddress = AztecAddress.fromNumberUnsafe(42), ): Promise { const contractArtifact = emptyContractArtifact(); contractArtifact.name = contractName; @@ -75,7 +75,7 @@ export async function deployAndExecuteCustomBytecode( tester: PublicTxSimulationTester, txLabel: string = 'CustomBytecodeTest', contractName: string = 'CustomBytecodeContract', - deployer: AztecAddress = AztecAddress.fromNumber(42), + deployer: AztecAddress = AztecAddress.fromNumberUnsafe(42), calldata: any[] = [], ): Promise { const testContract = await deployCustomBytecode(bytecode, tester, contractName, deployer); diff --git a/yarn-project/simulator/src/public/fixtures/token_test.ts b/yarn-project/simulator/src/public/fixtures/token_test.ts index eb1d3ee2dc18..213a0b870629 100644 --- a/yarn-project/simulator/src/public/fixtures/token_test.ts +++ b/yarn-project/simulator/src/public/fixtures/token_test.ts @@ -21,9 +21,9 @@ export async function tokenTest( ) { const timer = new Timer(); - const admin = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(111); - const receiver = AztecAddress.fromNumber(222); + const admin = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(111); + const receiver = AztecAddress.fromNumberUnsafe(222); const token = await setUpToken(tester, tokenArtifact, admin, expectToBeTrue); diff --git a/yarn-project/simulator/src/public/fixtures/utils.ts b/yarn-project/simulator/src/public/fixtures/utils.ts index ae94a9b49f34..ea1525a824b3 100644 --- a/yarn-project/simulator/src/public/fixtures/utils.ts +++ b/yarn-project/simulator/src/public/fixtures/utils.ts @@ -255,7 +255,7 @@ export async function addNewContractInstanceToTx( ); const contractAddressNullifier = await siloNullifier( - AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), + AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), contractInstance.address.toField(), ); diff --git a/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts b/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts index d8a6353581f3..532f5b348ed8 100644 --- a/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts +++ b/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts @@ -23,8 +23,8 @@ describe.each([ { useCppSimulator: false, simulatorName: 'TS Simulator' }, { useCppSimulator: true, simulatorName: 'Cpp Simulator' }, ])('Public processor contract registration/deployment tests ($simulatorName)', ({ useCppSimulator }) => { - const admin = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(111); + const admin = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(111); let worldStateService: NativeWorldStateService; let contractsDB: PublicContractsDB; @@ -196,7 +196,7 @@ describe.each([ // the contract data source. // Second transaction - deploys second token but fails during transfer - const receiver = AztecAddress.fromNumber(222); + const receiver = AztecAddress.fromNumberUnsafe(222); const transferAmount = 10n; const authwitNonce = new Fr(0); const failingConstructorTx = await tester.createTx( diff --git a/yarn-project/simulator/src/public/public_processor/apps_tests/timeout_race.test.ts b/yarn-project/simulator/src/public/public_processor/apps_tests/timeout_race.test.ts index 2d16e26e602f..b9df2d33cfef 100644 --- a/yarn-project/simulator/src/public/public_processor/apps_tests/timeout_race.test.ts +++ b/yarn-project/simulator/src/public/public_processor/apps_tests/timeout_race.test.ts @@ -50,7 +50,7 @@ describe('PublicProcessor C++ Timeout Race Condition', () => { const logger = createLogger('public-processor-timeout-race'); - const admin = AztecAddress.fromNumber(42); + const admin = AztecAddress.fromNumberUnsafe(42); let worldStateService: NativeWorldStateService; diff --git a/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts b/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts index 2e10ec87d23e..0c971b76ea96 100644 --- a/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts +++ b/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts @@ -24,8 +24,8 @@ describe.each([ const logger = createLogger('public-processor-apps-tests-token'); const NUM_TRANSFERS = 10; - const admin = AztecAddress.fromNumber(42); - const sender = AztecAddress.fromNumber(111); + const admin = AztecAddress.fromNumberUnsafe(42); + const sender = AztecAddress.fromNumberUnsafe(111); let token: ContractInstanceWithAddress; let worldStateService: NativeWorldStateService; @@ -113,7 +113,7 @@ describe.each([ const transferTxs = []; for (let i = 0; i < NUM_TRANSFERS; i++) { - const receiver = AztecAddress.fromNumber(200 + i); // different receiver each time + const receiver = AztecAddress.fromNumberUnsafe(200 + i); // different receiver each time transferTxs.push( await tester.createTx( /*sender=*/ sender, diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts index a4c33e92a576..1fdd96ebfbb4 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts @@ -315,7 +315,7 @@ describe('public_processor', () => { }); describe('with fee payer', () => { - const feePayer = AztecAddress.fromBigInt(123123n); + const feePayer = AztecAddress.fromBigIntUnsafe(123123n); const initialBalance = new Fr(1000); beforeEach(async () => { diff --git a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/avm_gadgets.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/avm_gadgets.test.ts index 0af13f737b10..d6d8461e7714 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/avm_gadgets.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/avm_gadgets.test.ts @@ -12,7 +12,7 @@ describe('Public TX simulator apps tests: gadgets', () => { { useCppSimulator: false, simulatorName: 'TS Simulator' }, { useCppSimulator: true, simulatorName: 'Cpp Simulator' }, ])('Public TX simulator apps tests: gadgets (via $simulatorName)', ({ useCppSimulator }) => { - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); let worldStateService: NativeWorldStateService; let tester: PublicTxSimulationTester; diff --git a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/bench.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/bench.test.ts index dd3fd8d1ddce..a731c6378864 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/bench.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/bench.test.ts @@ -114,7 +114,7 @@ describe('Public TX simulator apps tests: benchmarks', () => { it('AVM large calldata test', async () => { tester.setMetricsPrefix(`${metricsPrefixPrefix}AvmTest contract tests`); - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); const avmTestContract = await tester.registerAndDeployContract( /*constructorArgs=*/ [], @@ -140,7 +140,7 @@ describe('Public TX simulator apps tests: benchmarks', () => { it('PublicFnsWithEmitRepro contract test', async () => { // See comments on the contract source for motivation as to including this contract in our benchmarks. tester.setMetricsPrefix(`${metricsPrefixPrefix}PublicFnsWithEmitRepro contract tests`); - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); const reproContract = await tester.registerAndDeployContract( /*constructorArgs=*/ [], @@ -165,7 +165,7 @@ describe('Public TX simulator apps tests: benchmarks', () => { it('Storage proof test', async () => { tester.setMetricsPrefix(`${metricsPrefixPrefix}StorageProof contract tests`); - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); const storageProofContract = await tester.registerAndDeployContract( /*constructorArgs=*/ [], @@ -200,7 +200,7 @@ describe('Public TX simulator apps tests: benchmarks', () => { }); describe('AVM gadgets tests', () => { - const deployer = AztecAddress.fromNumber(42); + const deployer = AztecAddress.fromNumberUnsafe(42); let worldStateService: NativeWorldStateService; let tester: PublicTxSimulationTester; diff --git a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/cpp_exception_handling.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/cpp_exception_handling.test.ts index 12cd90a8b4ac..d14526dcc087 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/cpp_exception_handling.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/apps_tests/cpp_exception_handling.test.ts @@ -6,7 +6,7 @@ import { NativeWorldStateService } from '@aztec/world-state/native'; import { PublicTxSimulationTester } from '../../fixtures/public_tx_simulation_tester.js'; describe('C++ Exception Handling during Public Tx Simulation', () => { - const sender = AztecAddress.fromNumber(42); + const sender = AztecAddress.fromNumberUnsafe(42); let avmTestContractInstance: ContractInstanceWithAddress; let tester: PublicTxSimulationTester; let worldStateService: NativeWorldStateService; @@ -21,7 +21,7 @@ describe('C++ Exception Handling during Public Tx Simulation', () => { ); avmTestContractInstance = await tester.registerAndDeployContract( /*constructorArgs=*/ [], - /*deployer=*/ AztecAddress.fromNumber(420), + /*deployer=*/ AztecAddress.fromNumberUnsafe(420), AvmTestContractArtifact, ); }); diff --git a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts index 21b195484d66..952031b8c3da 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts @@ -23,7 +23,7 @@ export class ContractProviderForCpp implements ContractProvider { public getContractInstance = async (address: string): Promise => { this.log.trace(`Contract provider callback: getContractInstance(${address})`); - const aztecAddr = AztecAddress.fromString(address); + const aztecAddr = AztecAddress.fromStringUnsafe(address); const instance = await this.contractsDB.getContractInstance(aztecAddr, this.globalVariables.timestamp); @@ -88,7 +88,7 @@ export class ContractProviderForCpp implements ContractProvider { this.log.trace(`Contract provider callback: getDebugFunctionName(${address}, ${selector})`); // Parse address and selector strings - const aztecAddr = AztecAddress.fromString(address); + const aztecAddr = AztecAddress.fromStringUnsafe(address); const selectorFr = Fr.fromString(selector); const functionSelector = FunctionSelector.fromFieldOrUndefined(selectorFr); diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts index 64a6cf8b585b..72081df63bda 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts @@ -104,18 +104,18 @@ describe('public_tx_simulator', () => { tx.data.forPublic!.nonRevertibleAccumulatedData.noteHashes[1] = new Fr(0xaaaa); tx.data.forPublic!.nonRevertibleAccumulatedData.l2ToL1Msgs[0] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x5555), new Fr(0xbbbb)), - AztecAddress.fromField(new Fr(0x6666)), + AztecAddress.fromFieldUnsafe(new Fr(0x6666)), ); tx.data.forPublic!.nonRevertibleAccumulatedData.l2ToL1Msgs[1] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x6666), new Fr(0xcccc)), - AztecAddress.fromField(new Fr(0x7777)), + AztecAddress.fromFieldUnsafe(new Fr(0x7777)), ); tx.data.forPublic!.revertibleAccumulatedData.nullifiers[0] = new Fr(0x9999); tx.data.forPublic!.revertibleAccumulatedData.noteHashes[0] = new Fr(0xbbbb); tx.data.forPublic!.revertibleAccumulatedData.l2ToL1Msgs[0] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(0x7777), new Fr(0xdddd)), - AztecAddress.fromField(new Fr(0x8888)), + AztecAddress.fromFieldUnsafe(new Fr(0x8888)), ); tx.data.gasUsed = privateGasUsed; @@ -1203,7 +1203,7 @@ describe('public_tx_simulator', () => { tx.data.forPublic!.revertibleAccumulatedData.l2ToL1Msgs[0] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(123), new Fr(456)), - AztecAddress.fromNumber(789), + AztecAddress.fromNumberUnsafe(789), ); mockPublicExecutor([ @@ -1283,7 +1283,7 @@ describe('public_tx_simulator', () => { tx.data.forPublic!.revertibleAccumulatedData.l2ToL1Msgs[0] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(123), new Fr(456)), - AztecAddress.fromNumber(789), + AztecAddress.fromNumberUnsafe(789), ); mockPublicExecutor([ @@ -1379,7 +1379,7 @@ describe('public_tx_simulator', () => { tx.data.forPublic!.revertibleAccumulatedData.l2ToL1Msgs[0] = new ScopedL2ToL1Message( new L2ToL1Message(EthAddress.fromNumber(123), new Fr(456)), - AztecAddress.fromNumber(789), + AztecAddress.fromNumberUnsafe(789), ); mockPublicExecutor([ diff --git a/yarn-project/simulator/src/public/side_effect_trace.test.ts b/yarn-project/simulator/src/public/side_effect_trace.test.ts index 0f55ea827cb5..befe67e4ff7d 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.test.ts @@ -106,11 +106,11 @@ describe('Public Side Effect Trace', () => { await trace.tracePublicStorageWrite(address, slot.add(new Fr(i)), value.add(new Fr(1)), false); } await expect( - trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), value, false), + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), value, false), ).rejects.toThrow(SideEffectLimitReachedError); // Still allows protocol writes await expect( - trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), value, true), + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), value, true), ).resolves.not.toThrow(); }); @@ -118,12 +118,12 @@ describe('Public Side Effect Trace', () => { for (let i = 0; i < PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) { await trace.tracePublicStorageWrite(address, slot, value, true); } - await expect(trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), value, true)).rejects.toThrow( - SideEffectLimitReachedError, - ); + await expect( + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), value, true), + ).rejects.toThrow(SideEffectLimitReachedError); // Still allows user writes await expect( - trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), value, false), + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), value, false), ).resolves.not.toThrow(); }); @@ -143,9 +143,9 @@ describe('Public Side Effect Trace', () => { it('Should enforce maximum number of new l2 to l1 messages', () => { for (let i = 0; i < MAX_L2_TO_L1_MSGS_PER_TX; i++) { - trace.traceNewL2ToL1Message(AztecAddress.fromNumber(i), new Fr(i), new Fr(i)); + trace.traceNewL2ToL1Message(AztecAddress.fromNumberUnsafe(i), new Fr(i), new Fr(i)); } - expect(() => trace.traceNewL2ToL1Message(AztecAddress.fromNumber(42), new Fr(42), new Fr(42))).toThrow( + expect(() => trace.traceNewL2ToL1Message(AztecAddress.fromNumberUnsafe(42), new Fr(42), new Fr(42))).toThrow( SideEffectLimitReachedError, ); }); @@ -153,10 +153,10 @@ describe('Public Side Effect Trace', () => { it('Should enforce maximum number of log fields', () => { // Fill the payload with one super large log trace.tracePublicLog( - AztecAddress.fromNumber(42), + AztecAddress.fromNumberUnsafe(42), new Array(FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH - PUBLIC_LOG_HEADER_LENGTH).fill(new Fr(42)), ); - expect(() => trace.tracePublicLog(AztecAddress.fromNumber(42), [])).toThrow(SideEffectLimitReachedError); + expect(() => trace.tracePublicLog(AztecAddress.fromNumberUnsafe(42), [])).toThrow(SideEffectLimitReachedError); }); it('Should enforce maximum number of unique contract class IDs', async () => { @@ -209,17 +209,17 @@ describe('Public Side Effect Trace', () => { ), ); await expect( - trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), new Fr(42), false), + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), new Fr(42), false), ).rejects.toThrow(SideEffectLimitReachedError); await expect( - trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), new Fr(42), true), + trace.tracePublicStorageWrite(AztecAddress.fromNumberUnsafe(42), new Fr(42), new Fr(42), true), ).rejects.toThrow(SideEffectLimitReachedError); expect(() => trace.traceNewNoteHash(new Fr(42))).toThrow(SideEffectLimitReachedError); expect(() => trace.traceNewNullifier(new Fr(42))).toThrow(SideEffectLimitReachedError); - expect(() => trace.traceNewL2ToL1Message(AztecAddress.fromNumber(42), new Fr(42), new Fr(42))).toThrow( + expect(() => trace.traceNewL2ToL1Message(AztecAddress.fromNumberUnsafe(42), new Fr(42), new Fr(42))).toThrow( SideEffectLimitReachedError, ); - expect(() => trace.tracePublicLog(AztecAddress.fromNumber(42), [])).toThrow(SideEffectLimitReachedError); + expect(() => trace.tracePublicLog(AztecAddress.fromNumberUnsafe(42), [])).toThrow(SideEffectLimitReachedError); }); }); diff --git a/yarn-project/simulator/src/public/state_manager/public_storage.test.ts b/yarn-project/simulator/src/public/state_manager/public_storage.test.ts index 5530eaa5f1a2..6adbddfbc1fe 100644 --- a/yarn-project/simulator/src/public/state_manager/public_storage.test.ts +++ b/yarn-project/simulator/src/public/state_manager/public_storage.test.ts @@ -17,7 +17,7 @@ describe('avm public storage', () => { describe('AVM Public Storage', () => { it('Reading an unwritten slot works (gets zero & DNE)', async () => { - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); // never written! publicDb.storageRead.mockResolvedValue(Fr.ZERO); @@ -28,7 +28,7 @@ describe('avm public storage', () => { }); it('Should cache storage write, reading works after write', async () => { - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); const value = new Fr(3); // Write to cache @@ -40,7 +40,7 @@ describe('avm public storage', () => { }); it('Reading works on fallback to host (gets value & exists)', async () => { - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); const storedValue = new Fr(420); // ensure that fallback to host gets a value @@ -53,7 +53,7 @@ describe('avm public storage', () => { }); it('Reading works on fallback to parent (gets value & exists)', async () => { - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); const value = new Fr(3); const childStorage = new PublicStorage(publicDb, publicStorage); @@ -66,7 +66,7 @@ describe('avm public storage', () => { }); it('Reading works on fallback to grandparent (gets value & exists)', async () => { - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); const value = new Fr(3); const childStorage = new PublicStorage(publicDb, publicStorage); @@ -81,7 +81,7 @@ describe('avm public storage', () => { it('When reading from storage, should check cache, then parent, then host', async () => { // Store a different value in storage vs the cache, and make sure the cache is returned - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); const storedValue = new Fr(420); const parentValue = new Fr(69); @@ -114,7 +114,7 @@ describe('avm public storage', () => { it('Should be able to merge two public storages together', async () => { // Checking that child's writes take precedence on marge - const contractAddress = AztecAddress.fromNumber(1); + const contractAddress = AztecAddress.fromNumberUnsafe(1); const slot = new Fr(2); // value written initially in parent const value = new Fr(1); diff --git a/yarn-project/simulator/src/public/state_manager/state_manager.test.ts b/yarn-project/simulator/src/public/state_manager/state_manager.test.ts index 6c695e57c72a..cf9cdadb7bca 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.test.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.test.ts @@ -195,8 +195,8 @@ describe('state_manager', () => { // // merge journals // // t2 -> journal0 -> read | 2 - // const contractAddress = AztecAddress.fromNumber(1); - // const aztecContractAddress = AztecAddress.fromField(contractAddress); + // const contractAddress = AztecAddress.fromNumberUnsafe(1); + // const aztecContractAddress = AztecAddress.fromFieldUnsafe(contractAddress); // const key = new Fr(2); // const value = new Fr(1); // const valueT1 = new Fr(2); @@ -283,12 +283,12 @@ describe('state_manager', () => { // ]); // expect(journalUpdates.newLogs).toEqual([ // new UnencryptedL2Log( - // AztecAddress.fromBigInt(log.address), + // AztecAddress.fromBigIntUnsafe(log.address), // new EventSelector(log.selector), // Buffer.concat(log.data.map(f => f.toBuffer())), // ), // new UnencryptedL2Log( - // AztecAddress.fromBigInt(logT1.address), + // AztecAddress.fromBigIntUnsafe(logT1.address), // new EventSelector(logT1.selector), // Buffer.concat(logT1.data.map(f => f.toBuffer())), // ), @@ -328,8 +328,8 @@ describe('state_manager', () => { // // merge journals // // t2 -> journal0 -> read | 1 - // const contractAddress = AztecAddress.fromNumber(1); - // const aztecContractAddress = AztecAddress.fromField(contractAddress); + // const contractAddress = AztecAddress.fromNumberUnsafe(1); + // const aztecContractAddress = AztecAddress.fromFieldUnsafe(contractAddress); // const key = new Fr(2); // const value = new Fr(1); // const valueT1 = new Fr(2); @@ -437,7 +437,7 @@ describe('state_manager', () => { // // Check that rejected Accrued Substate is absent // expect(journalUpdates.newLogs).toEqual([ // new UnencryptedL2Log( - // AztecAddress.fromBigInt(log.address), + // AztecAddress.fromBigIntUnsafe(log.address), // new EventSelector(log.selector), // Buffer.concat(log.data.map(f => f.toBuffer())), // ), diff --git a/yarn-project/simulator/src/public/state_manager/state_manager.ts b/yarn-project/simulator/src/public/state_manager/state_manager.ts index ae2323bddca5..67eddb5db19d 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.ts @@ -402,7 +402,7 @@ export class PublicPersistableStateManager { // This will internally decide whether to check the nullifier tree or not depending on doMerkleOperations. const nullifierExistsInTree = await this.checkNullifierExists( - AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), + AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), contractAddress.toField(), ); assert( diff --git a/yarn-project/standard-contracts/src/drift.ts b/yarn-project/standard-contracts/src/drift.ts index d41c67c97470..778305e4546a 100644 --- a/yarn-project/standard-contracts/src/drift.ts +++ b/yarn-project/standard-contracts/src/drift.ts @@ -37,7 +37,7 @@ function generateSalts(names: string[]) { function generateAddresses(names: string[], contractData: ContractData[]) { return ` export const StandardContractAddress: Record = { - ${contractData.map((d, i) => `${names[i]}: AztecAddress.fromString('${d.address.toString()}')`).join(',\n')} + ${contractData.map((d, i) => `${names[i]}: AztecAddress.fromStringUnsafe('${d.address.toString()}')`).join(',\n')} }; `; } diff --git a/yarn-project/standard-contracts/src/standard_contract_data.ts b/yarn-project/standard-contracts/src/standard_contract_data.ts index 5a66ea3ccc5c..ae3b0697cc5a 100644 --- a/yarn-project/standard-contracts/src/standard_contract_data.ts +++ b/yarn-project/standard-contracts/src/standard_contract_data.ts @@ -20,10 +20,14 @@ export const StandardContractSalt: Record = { }; export const StandardContractAddress: Record = { - AuthRegistry: AztecAddress.fromString('0x2df3bf0052304b37c59cfdb79eeeab7f05f8b1e197293e456dc9c7716e6fc654'), - MultiCallEntrypoint: AztecAddress.fromString('0x099e0fdbd90bed29103c75ae755dc43dc06e53c845dd25cf81ec05570a68c2fb'), - PublicChecks: AztecAddress.fromString('0x2da605de400a83f4c1750fdd1dba3a4b2977884a95549efd06f7a62ef6ae69c3'), - HandshakeRegistry: AztecAddress.fromString('0x19fca351f28a726da8bd3c66c10f22314bbe6359a61440efbbc0721e019167ef'), + AuthRegistry: AztecAddress.fromStringUnsafe('0x2df3bf0052304b37c59cfdb79eeeab7f05f8b1e197293e456dc9c7716e6fc654'), + MultiCallEntrypoint: AztecAddress.fromStringUnsafe( + '0x099e0fdbd90bed29103c75ae755dc43dc06e53c845dd25cf81ec05570a68c2fb', + ), + PublicChecks: AztecAddress.fromStringUnsafe('0x2da605de400a83f4c1750fdd1dba3a4b2977884a95549efd06f7a62ef6ae69c3'), + HandshakeRegistry: AztecAddress.fromStringUnsafe( + '0x19fca351f28a726da8bd3c66c10f22314bbe6359a61440efbbc0721e019167ef', + ), }; export const StandardContractClassId: Record = { diff --git a/yarn-project/stdlib/src/abi/decoder.test.ts b/yarn-project/stdlib/src/abi/decoder.test.ts index 2992dd4aeb30..8c8e3dd667f1 100644 --- a/yarn-project/stdlib/src/abi/decoder.test.ts +++ b/yarn-project/stdlib/src/abi/decoder.test.ts @@ -254,7 +254,7 @@ describe('decoder', () => { ], ); - expect(decoded).toEqual([1n, 2n, false, 'xyz', AztecAddress.fromBigInt(1n), { x: 1n, y: 2n }]); + expect(decoded).toEqual([1n, 2n, false, 'xyz', AztecAddress.fromBigIntUnsafe(1n), { x: 1n, y: 2n }]); }); it('decodes Option::Some as the wrapped value', () => { diff --git a/yarn-project/stdlib/src/avm/avm.ts b/yarn-project/stdlib/src/avm/avm.ts index a5f2a4963bf3..5ef89c02d0f5 100644 --- a/yarn-project/stdlib/src/avm/avm.ts +++ b/yarn-project/stdlib/src/avm/avm.ts @@ -1152,7 +1152,7 @@ export class CallStackMetadata { const { stack, leaf } = failingCall; const aztecCallStack = stack.map(call => ({ - contractAddress: AztecAddress.fromField(call.contractAddress), + contractAddress: AztecAddress.fromFieldUnsafe(call.contractAddress), functionSelector: call.calldata.length > 0 ? FunctionSelector.fromFieldOrUndefined(call.calldata[0]) : undefined, })); diff --git a/yarn-project/stdlib/src/aztec-address/index.ts b/yarn-project/stdlib/src/aztec-address/index.ts index e821d3997020..b147012430da 100644 --- a/yarn-project/stdlib/src/aztec-address/index.ts +++ b/yarn-project/stdlib/src/aztec-address/index.ts @@ -43,34 +43,64 @@ export class AztecAddress { static ZERO = new AztecAddress(Buffer.alloc(32, 0)); /** Null msg sender address. Not part of the protocol contracts tree. */ - static NULL_MSG_SENDER = AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS); + static NULL_MSG_SENDER = AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS); static zero(): AztecAddress { return AztecAddress.ZERO; } - static fromField(fr: Fr) { + /** + * Builds an `AztecAddress` from a field **without checking it is a valid address** (the x-coordinate of a point on + * the Grumpkin curve, which is what lets it be encrypted to). Use {@link AztecAddress.isValid} to validate an + * untrusted one, or {@link AztecAddress.random} for valid test addresses. + */ + static fromFieldUnsafe(fr: Fr) { return new AztecAddress(fr); } + /** + * Deserializes an `AztecAddress` from a buffer. It does **not** check the value is a valid Grumpkin-curve address + * (see {@link AztecAddress.isValid}); it is meant for reading addresses from already-validated serialized data. Use + * {@link AztecAddress.random} for valid test addresses. + */ static fromBuffer(buffer: Buffer | BufferReader) { return new AztecAddress(fromBuffer(buffer, Fr)); } + /** + * Deserializes an `AztecAddress` from a field reader. It does **not** check the value is a valid Grumpkin-curve + * address (see {@link AztecAddress.isValid}); it is meant for reading addresses from already-validated serialized + * data. Use {@link AztecAddress.random} for valid test addresses. + */ static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); return new AztecAddress(reader.readField()); } - static fromBigInt(value: bigint) { + /** + * Builds an `AztecAddress` from a bigint **without checking it is a valid address** (the x-coordinate of a point on + * the Grumpkin curve, which is what lets it be encrypted to). Use {@link AztecAddress.isValid} to validate an + * untrusted one, or {@link AztecAddress.random} for valid test addresses. + */ + static fromBigIntUnsafe(value: bigint) { return new AztecAddress(new Fr(value)); } - static fromNumber(value: number) { + /** + * Builds an `AztecAddress` from a number **without checking it is a valid address** (the x-coordinate of a point on + * the Grumpkin curve, which is what lets it be encrypted to). Use {@link AztecAddress.isValid} to validate an + * untrusted one, or {@link AztecAddress.random} for valid test addresses. + */ + static fromNumberUnsafe(value: number) { return new AztecAddress(new Fr(value)); } - static fromString(buf: string) { + /** + * Builds an `AztecAddress` from a hex string **without checking it is a valid address** (the x-coordinate of a + * point on the Grumpkin curve, which is what lets it be encrypted to). Use {@link AztecAddress.isValid} to + * validate an untrusted one, or {@link AztecAddress.random} for valid test addresses. + */ + static fromStringUnsafe(buf: string) { return new AztecAddress(hexToBuffer(buf)); } @@ -89,7 +119,7 @@ export class AztecAddress { if (obj instanceof Buffer || Buffer.isBuffer(obj)) { return new AztecAddress(obj); } - return AztecAddress.fromString(obj); + return AztecAddress.fromStringUnsafe(obj); } /** diff --git a/yarn-project/stdlib/src/checkpoint/validate.test.ts b/yarn-project/stdlib/src/checkpoint/validate.test.ts index 271fe4f87779..2c81ba4a359a 100644 --- a/yarn-project/stdlib/src/checkpoint/validate.test.ts +++ b/yarn-project/stdlib/src/checkpoint/validate.test.ts @@ -18,7 +18,7 @@ describe('validateCheckpointStructure', () => { const fixedSlot = SlotNumber(42); const fixedCoinbase = EthAddress.random(); - const fixedFeeRecipient = AztecAddress.fromField(Fr.random()); + const fixedFeeRecipient = AztecAddress.fromFieldUnsafe(Fr.random()); const fixedGasFees = GasFees.random(); const fixedTimestamp = BigInt(Math.floor(Date.now() / 1000)); @@ -158,7 +158,7 @@ describe('validateCheckpoint — limits', () => { const checkpointNumber = CheckpointNumber(1); const fixedSlot = SlotNumber(42); const fixedCoinbase = EthAddress.random(); - const fixedFeeRecipient = AztecAddress.fromField(Fr.random()); + const fixedFeeRecipient = AztecAddress.fromFieldUnsafe(Fr.random()); const fixedGasFees = GasFees.random(); const fixedTimestamp = BigInt(Math.floor(Date.now() / 1000)); diff --git a/yarn-project/stdlib/src/contract/contract_address.test.ts b/yarn-project/stdlib/src/contract/contract_address.test.ts index f378473ba4ab..4a13cacfc2a0 100644 --- a/yarn-project/stdlib/src/contract/contract_address.test.ts +++ b/yarn-project/stdlib/src/contract/contract_address.test.ts @@ -27,7 +27,7 @@ describe('ContractAddress', () => { const mockInstance = { initializationHash: new Fr(1), salt: new Fr(2), - deployer: AztecAddress.fromField(new Fr(4)), + deployer: AztecAddress.fromFieldUnsafe(new Fr(4)), immutablesHash: new Fr(3), }; const result = await computeSaltedInitializationHash(mockInstance); @@ -62,7 +62,7 @@ describe('ContractAddress', () => { const contractClassId = new Fr(4n); const initializationHash = new Fr(5n); const immutablesHash = new Fr(6n); - const deployer = AztecAddress.fromField(new Fr(7)); + const deployer = AztecAddress.fromFieldUnsafe(new Fr(7)); const publicKeys = (await deriveKeys(secretKey)).publicKeys; const instance = { publicKeys, diff --git a/yarn-project/stdlib/src/hash/hash.test.ts b/yarn-project/stdlib/src/hash/hash.test.ts index 93320c17c973..85833838a098 100644 --- a/yarn-project/stdlib/src/hash/hash.test.ts +++ b/yarn-project/stdlib/src/hash/hash.test.ts @@ -99,7 +99,7 @@ describe('hash', () => { }); it('computes public data tree leaf slot', async () => { - const contractAddress = AztecAddress.fromField(new Fr(987)); + const contractAddress = AztecAddress.fromFieldUnsafe(new Fr(987)); const storageSlot = new Fr(123); const res = await computePublicDataTreeLeafSlot(contractAddress, storageSlot); expect(res.toString()).toMatchInlineSnapshot( @@ -172,7 +172,7 @@ describe('hash', () => { it('empty L2ToL1Message siloing matches Noir', () => { const nonEmptyHash = computeL2ToL1MessageHash({ - l2Sender: AztecAddress.fromField(new Fr(0)), + l2Sender: AztecAddress.fromFieldUnsafe(new Fr(0)), l1Recipient: EthAddress.fromField(new Fr(0)), content: new Fr(0), rollupVersion: new Fr(0), @@ -193,7 +193,7 @@ describe('hash', () => { it('L2ToL1Message siloing matches Noir', () => { const nonEmptyHash = computeL2ToL1MessageHash({ - l2Sender: AztecAddress.fromField(new Fr(3)), + l2Sender: AztecAddress.fromFieldUnsafe(new Fr(3)), l1Recipient: EthAddress.fromField(new Fr(1)), content: new Fr(2), rollupVersion: new Fr(4), diff --git a/yarn-project/stdlib/src/hash/map_slot.test.ts b/yarn-project/stdlib/src/hash/map_slot.test.ts index 71de81cb15de..e251a9457c91 100644 --- a/yarn-project/stdlib/src/hash/map_slot.test.ts +++ b/yarn-project/stdlib/src/hash/map_slot.test.ts @@ -7,7 +7,7 @@ import { deriveStorageSlotInMap } from './index.js'; describe('Map slot', () => { it('derived map slot matches Noir', async () => { const mapSlot = new Fr(0x132258fb6962c4387ba659d9556521102d227549a386d39f0b22d1890d59c2b5n); - const key = AztecAddress.fromString('0x302dbc2f9b50a73283d5fb2f35bc01eae8935615817a0b4219a057b2ba8a5a3f'); + const key = AztecAddress.fromStringUnsafe('0x302dbc2f9b50a73283d5fb2f35bc01eae8935615817a0b4219a057b2ba8a5a3f'); const slot = await deriveStorageSlotInMap(mapSlot, key); diff --git a/yarn-project/stdlib/src/kernel/hints/build_note_hash_read_request_hints.test.ts b/yarn-project/stdlib/src/kernel/hints/build_note_hash_read_request_hints.test.ts index 45d25ecd985a..162cc5bb73ec 100644 --- a/yarn-project/stdlib/src/kernel/hints/build_note_hash_read_request_hints.test.ts +++ b/yarn-project/stdlib/src/kernel/hints/build_note_hash_read_request_hints.test.ts @@ -13,7 +13,7 @@ import { ReadRequest, ScopedReadRequest } from './read_request.js'; import { PendingReadHint, ReadRequestAction, SettledReadHint } from './read_request_hints.js'; describe('buildNoteHashReadRequestHints', () => { - const contractAddress = AztecAddress.fromBigInt(112233n); + const contractAddress = AztecAddress.fromBigIntUnsafe(112233n); const getNoteHashValue = (index: number) => index + 9999; diff --git a/yarn-project/stdlib/src/kernel/hints/build_nullifier_read_request_hints.test.ts b/yarn-project/stdlib/src/kernel/hints/build_nullifier_read_request_hints.test.ts index 7244472f602d..9c30c30a2c16 100644 --- a/yarn-project/stdlib/src/kernel/hints/build_nullifier_read_request_hints.test.ts +++ b/yarn-project/stdlib/src/kernel/hints/build_nullifier_read_request_hints.test.ts @@ -17,7 +17,7 @@ describe('buildNullifierReadRequestHints', () => { /** * Create fixtures. */ - const contractAddress = AztecAddress.fromBigInt(112233n); + const contractAddress = AztecAddress.fromBigIntUnsafe(112233n); const innerNullifier = (index: number) => index + 1; diff --git a/yarn-project/stdlib/src/kernel/hints/build_transient_data_hints.test.ts b/yarn-project/stdlib/src/kernel/hints/build_transient_data_hints.test.ts index 91a2cbd22bdc..18112d827f08 100644 --- a/yarn-project/stdlib/src/kernel/hints/build_transient_data_hints.test.ts +++ b/yarn-project/stdlib/src/kernel/hints/build_transient_data_hints.test.ts @@ -10,7 +10,7 @@ import { ReadRequest, ScopedReadRequest } from './read_request.js'; import { TransientDataSquashingHint } from './transient_data_squashing_hint.js'; describe('buildTransientDataHints', () => { - const contractAddress = AztecAddress.fromBigInt(987654n); + const contractAddress = AztecAddress.fromBigIntUnsafe(987654n); let noteHashes: ScopedNoteHash[]; let nullifiers: ScopedNullifier[]; @@ -90,7 +90,7 @@ describe('buildTransientDataHints', () => { }); it('throws if contract address does not match', () => { - nullifiers[3].contractAddress = AztecAddress.fromBigInt(123456n); + nullifiers[3].contractAddress = AztecAddress.fromBigIntUnsafe(123456n); expect(buildHints).toThrow('Contract address of hinted note hash does not match.'); }); @@ -134,7 +134,7 @@ describe('buildTransientDataHints', () => { }); describe('countSquashedLogs', () => { - const contractAddress = AztecAddress.fromBigInt(987654n); + const contractAddress = AztecAddress.fromBigIntUnsafe(987654n); const makeLog = (noteHashCounter: number, counter: number): ScopedPrivateLogData => { const log = PrivateLogData.empty(); diff --git a/yarn-project/stdlib/src/kernel/hints/read_request.ts b/yarn-project/stdlib/src/kernel/hints/read_request.ts index 5ad51b7b85a1..fdd895270277 100644 --- a/yarn-project/stdlib/src/kernel/hints/read_request.ts +++ b/yarn-project/stdlib/src/kernel/hints/read_request.ts @@ -112,7 +112,7 @@ export class ScopedReadRequest { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedReadRequest(reader.readObject(ReadRequest), AztecAddress.fromField(reader.readField())); + return new ScopedReadRequest(reader.readObject(ReadRequest), AztecAddress.fromFieldUnsafe(reader.readField())); } /** diff --git a/yarn-project/stdlib/src/kernel/log_hash.ts b/yarn-project/stdlib/src/kernel/log_hash.ts index aafa1ceded48..ad57699ffb5d 100644 --- a/yarn-project/stdlib/src/kernel/log_hash.ts +++ b/yarn-project/stdlib/src/kernel/log_hash.ts @@ -114,7 +114,7 @@ export class ScopedLogHash { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedLogHash(reader.readObject(LogHash), AztecAddress.fromField(reader.readField())); + return new ScopedLogHash(reader.readObject(LogHash), AztecAddress.fromFieldUnsafe(reader.readField())); } isEmpty() { @@ -156,7 +156,10 @@ export class ScopedCountedLogHash { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedCountedLogHash(reader.readObject(CountedLogHash), AztecAddress.fromField(reader.readField())); + return new ScopedCountedLogHash( + reader.readObject(CountedLogHash), + AztecAddress.fromFieldUnsafe(reader.readField()), + ); } toBuffer(): Buffer; diff --git a/yarn-project/stdlib/src/kernel/note_hash.ts b/yarn-project/stdlib/src/kernel/note_hash.ts index acab10025047..b2978cb867dd 100644 --- a/yarn-project/stdlib/src/kernel/note_hash.ts +++ b/yarn-project/stdlib/src/kernel/note_hash.ts @@ -65,7 +65,7 @@ export class ScopedNoteHash implements Ordered { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedNoteHash(reader.readObject(NoteHash), AztecAddress.fromField(reader.readField())); + return new ScopedNoteHash(reader.readObject(NoteHash), AztecAddress.fromFieldUnsafe(reader.readField())); } isEmpty() { diff --git a/yarn-project/stdlib/src/kernel/nullifier.ts b/yarn-project/stdlib/src/kernel/nullifier.ts index fb0453e2836e..cf61936d501a 100644 --- a/yarn-project/stdlib/src/kernel/nullifier.ts +++ b/yarn-project/stdlib/src/kernel/nullifier.ts @@ -70,7 +70,7 @@ export class ScopedNullifier implements Ordered { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedNullifier(reader.readObject(Nullifier), AztecAddress.fromField(reader.readField())); + return new ScopedNullifier(reader.readObject(Nullifier), AztecAddress.fromFieldUnsafe(reader.readField())); } isEmpty() { diff --git a/yarn-project/stdlib/src/kernel/private_log_data.ts b/yarn-project/stdlib/src/kernel/private_log_data.ts index 9e68d6d240dd..d5055b847cc9 100644 --- a/yarn-project/stdlib/src/kernel/private_log_data.ts +++ b/yarn-project/stdlib/src/kernel/private_log_data.ts @@ -85,7 +85,10 @@ export class ScopedPrivateLogData { static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); - return new ScopedPrivateLogData(reader.readObject(PrivateLogData), AztecAddress.fromField(reader.readField())); + return new ScopedPrivateLogData( + reader.readObject(PrivateLogData), + AztecAddress.fromFieldUnsafe(reader.readField()), + ); } isEmpty() { diff --git a/yarn-project/stdlib/src/logs/app_tagging_secret.ts b/yarn-project/stdlib/src/logs/app_tagging_secret.ts index a6a3f0599382..55742a6f3177 100644 --- a/yarn-project/stdlib/src/logs/app_tagging_secret.ts +++ b/yarn-project/stdlib/src/logs/app_tagging_secret.ts @@ -85,13 +85,13 @@ export class AppTaggingSecret { if (parts.length === 2) { // TODO(F-680): Remove legacy two-part parsing after stored tagging keys are migrated. const [secretStr, appStr] = parts; - return new AppTaggingSecret(Fr.fromString(secretStr), AztecAddress.fromString(appStr)); + return new AppTaggingSecret(Fr.fromString(secretStr), AztecAddress.fromStringUnsafe(appStr)); } if (parts.length === 3) { const [kindStr, secretStr, appStr] = parts; return new AppTaggingSecret( Fr.fromString(secretStr), - AztecAddress.fromString(appStr), + AztecAddress.fromStringUnsafe(appStr), appTaggingSecretKindFromString(kindStr), ); } diff --git a/yarn-project/stdlib/src/logs/contract_class_log.test.ts b/yarn-project/stdlib/src/logs/contract_class_log.test.ts index 0ef4d69f33b2..833a160b45e2 100644 --- a/yarn-project/stdlib/src/logs/contract_class_log.test.ts +++ b/yarn-project/stdlib/src/logs/contract_class_log.test.ts @@ -37,7 +37,7 @@ describe('ContractClassLog', () => { it('number of emitted blob fields is correct', () => { const smallLogFields = [new Fr(1), new Fr(2), new Fr(3)]; const smallLog = new ContractClassLog( - AztecAddress.fromField(Fr.ONE), + AztecAddress.fromFieldUnsafe(Fr.ONE), ContractClassLogFields.fromEmittedFields(smallLogFields), smallLogFields.length, ); @@ -47,7 +47,7 @@ describe('ContractClassLog', () => { Fr.random(), ); const largeLog = new ContractClassLog( - AztecAddress.fromField(Fr.ONE), + AztecAddress.fromFieldUnsafe(Fr.ONE), ContractClassLogFields.fromEmittedFields(largeLogFields), largeLogFields.length, ); diff --git a/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts b/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts index 14eaef5d7f53..2748572da831 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts @@ -42,7 +42,7 @@ describe('CheckpointHeader', () => { slotNumber: SlotNumber(1234), timestamp: BigInt(5678), coinbase: EthAddress.fromField(new Fr(9090)), - feeRecipient: AztecAddress.fromField(new Fr(101010)), + feeRecipient: AztecAddress.fromFieldUnsafe(new Fr(101010)), gasFees: new GasFees(100, 200), totalManaUsed: new Fr(151617), accumulatedFees: new Fr(181920), @@ -69,7 +69,7 @@ describe('CheckpointHeader', () => { slotNumber: SlotNumber(1234), timestamp: 2n ** 64n - 1n - 5678n, coinbase: EthAddress.fromField(new Fr(2n ** 160n - 1n - 9090n)), - feeRecipient: AztecAddress.fromField(new Fr(MAX_FIELD_VALUE - 101010n)), + feeRecipient: AztecAddress.fromFieldUnsafe(new Fr(MAX_FIELD_VALUE - 101010n)), gasFees: new GasFees(2n ** 128n - 1n - 100n, 2n ** 128n - 1n - 200n), totalManaUsed: new Fr(MAX_FIELD_VALUE - 151617n), accumulatedFees: new Fr(MAX_FIELD_VALUE - 181920n), diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index a4708c7578e9..9962e7f2615c 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -703,7 +703,7 @@ export function makeGlobalVariables(seed = 1, overrides: Partial { }); } - const someAddress = AztecAddress.fromField(new Fr(27)); + const someAddress = AztecAddress.fromFieldUnsafe(new Fr(27)); it('returns overhead only for tx with just a nullifier', () => { const tx = makePrivateOnlyTx(); diff --git a/yarn-project/stdlib/src/tx/tx_request.test.ts b/yarn-project/stdlib/src/tx/tx_request.test.ts index bb1e8f254108..ef6329aa1576 100644 --- a/yarn-project/stdlib/src/tx/tx_request.test.ts +++ b/yarn-project/stdlib/src/tx/tx_request.test.ts @@ -35,7 +35,7 @@ describe('TxRequest', () => { it('compute hash', async () => { const gasSettings = new GasSettings(new Gas(2, 2), new Gas(1, 1), new GasFees(4, 4), new GasFees(3, 3)); const txRequest = TxRequest.from({ - origin: AztecAddress.fromBigInt(1122n), + origin: AztecAddress.fromBigIntUnsafe(1122n), argsHash: new Fr(33), txContext: new TxContext(new Fr(44), new Fr(55), gasSettings), functionData: new FunctionData(FunctionSelector.fromField(new Fr(66n)), /*isPrivate=*/ true), diff --git a/yarn-project/txe/src/constants.ts b/yarn-project/txe/src/constants.ts index bbe9cd289d52..dec902fd9a83 100644 --- a/yarn-project/txe/src/constants.ts +++ b/yarn-project/txe/src/constants.ts @@ -1,7 +1,7 @@ import { PRIVATE_LOG_CIPHERTEXT_LEN } from '@aztec/constants'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -export const DEFAULT_ADDRESS = AztecAddress.fromNumber(42); +export const DEFAULT_ADDRESS = AztecAddress.fromNumberUnsafe(42); // This is MAX_MESSAGE_CONTENT_LEN - PRIVATE_EVENT_MSG_PLAINTEXT_RESERVED_FIELDS_LEN export const MAX_PRIVATE_EVENT_LEN = 10; diff --git a/yarn-project/txe/src/oracle/test-resolver/default_fixtures.ts b/yarn-project/txe/src/oracle/test-resolver/default_fixtures.ts index 924837b7c095..db0654fa1bb5 100644 --- a/yarn-project/txe/src/oracle/test-resolver/default_fixtures.ts +++ b/yarn-project/txe/src/oracle/test-resolver/default_fixtures.ts @@ -53,7 +53,7 @@ const TEST_VALUE_IMPLS: TestValueImpl[] = [ scalar(BIGINT, seed => BigInt(seed)), scalar(BYTE, seed => seed), scalar(BOOL, seed => seed % 2 !== 0), - scalar(AZTEC_ADDRESS, seed => AztecAddress.fromNumber(seed)), + scalar(AZTEC_ADDRESS, seed => AztecAddress.fromNumberUnsafe(seed)), scalar(FUNCTION_SELECTOR, seed => FunctionSelector.fromField(new Fr(seed))), scalar(NOTE_SELECTOR, seed => NoteSelector.fromField(new Fr(seed))), scalar(BLOCK_HASH, seed => new BlockHash(new Fr(seed))), diff --git a/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts b/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts index 844b2c1d7057..1df33596320c 100644 --- a/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts +++ b/yarn-project/txe/src/oracle/test-resolver/resolver.test.ts @@ -37,9 +37,9 @@ const TEST_REGISTRY: Record = { }; const TEST_FIXTURES: Record = { - test_single: [{ inputs: { slot: new Fr(10), addr: AztecAddress.fromNumber(1) }, output: new Fr(42) }], + test_single: [{ inputs: { slot: new Fr(10), addr: AztecAddress.fromNumberUnsafe(1) }, output: new Fr(42) }], test_multi: [ - { scenario: 'some', inputs: {}, output: Option.some(AztecAddress.fromNumber(7)) }, + { scenario: 'some', inputs: {}, output: Option.some(AztecAddress.fromNumberUnsafe(7)) }, { scenario: 'none', inputs: {}, output: Option.none() }, ], test_labeled: [ @@ -60,7 +60,7 @@ describe('OracleTestResolver', () => { }); it('resolves an oracle with a single scenario', async () => { - const result = await callOracle('test_single', [toHex(new Fr(10)), toHex(AztecAddress.fromNumber(1))]); + const result = await callOracle('test_single', [toHex(new Fr(10)), toHex(AztecAddress.fromNumberUnsafe(1))]); expect(result.values).toHaveLength(1); expect(result.values[0]).toBe(toHex(new Fr(42))); }); @@ -90,9 +90,9 @@ describe('OracleTestResolver', () => { }); it('throws when inputs do not match the fixture', async () => { - await expect(callOracle('test_single', [toHex(new Fr(777)), toHex(AztecAddress.fromNumber(1))])).rejects.toThrow( - 'Input mismatch', - ); + await expect( + callOracle('test_single', [toHex(new Fr(777)), toHex(AztecAddress.fromNumberUnsafe(1))]), + ).rejects.toThrow('Input mismatch'); }); it('labels the input-mismatch error with the scenario name', async () => { @@ -126,7 +126,7 @@ describe('OracleTestResolver', () => { it('tracks uncalled fixtures', async () => { expect(resolver.getUncalledFixtures()).toContain('test_single'); - await callOracle('test_single', [toHex(new Fr(10)), toHex(AztecAddress.fromNumber(1))]); + await callOracle('test_single', [toHex(new Fr(10)), toHex(AztecAddress.fromNumberUnsafe(1))]); expect(resolver.getUncalledFixtures()).not.toContain('test_single'); }); diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 6755a14b7a3c..584e0fe3b011 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -252,7 +252,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl } private deploymentNullifier(address: AztecAddress): Promise { - return siloNullifier(AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), address.toField()); + return siloNullifier(AztecAddress.fromNumberUnsafe(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), address.toField()); } async deploy( diff --git a/yarn-project/validator-client/src/checkpoint_builder.test.ts b/yarn-project/validator-client/src/checkpoint_builder.test.ts index 6f5d48bdacff..01a07bdd46e1 100644 --- a/yarn-project/validator-client/src/checkpoint_builder.test.ts +++ b/yarn-project/validator-client/src/checkpoint_builder.test.ts @@ -60,7 +60,7 @@ describe('CheckpointBuilder', () => { slotNumber, timestamp: BigInt(Date.now()), coinbase: EthAddress.random(), - feeRecipient: AztecAddress.fromField(Fr.random()), + feeRecipient: AztecAddress.fromFieldUnsafe(Fr.random()), gasFees: GasFees.empty(), }; diff --git a/yarn-project/validator-client/src/key_store/node_keystore_adapter.test.ts b/yarn-project/validator-client/src/key_store/node_keystore_adapter.test.ts index bead4a92fdc4..2f50546dd024 100644 --- a/yarn-project/validator-client/src/key_store/node_keystore_adapter.test.ts +++ b/yarn-project/validator-client/src/key_store/node_keystore_adapter.test.ts @@ -40,9 +40,9 @@ const A = { COINBASE_1: EthAddress.fromString('0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc'), COINBASE_2: EthAddress.fromString('0x90f79bf6eb2c4f870365e785982e1f101e93b906'), COINBASE_3: EthAddress.fromString('0x71be63f3384f5fb98995898a86b02fb2426c5788'), - FEE_1: AztecAddress.fromString('0x0bcd4042de499d14e55001ccbb24a551f3b95409600000000000000000000000'), - FEE_2: AztecAddress.fromString('0x071be63f3384f5fb98995898a86b02fb2426c578800000000000000000000000'), - FEE_3: AztecAddress.fromString('0x0f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000'), + FEE_1: AztecAddress.fromStringUnsafe('0x0bcd4042de499d14e55001ccbb24a551f3b95409600000000000000000000000'), + FEE_2: AztecAddress.fromStringUnsafe('0x071be63f3384f5fb98995898a86b02fb2426c578800000000000000000000000'), + FEE_3: AztecAddress.fromStringUnsafe('0x0f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000'), UNKNOWN: '0x9999999999999999999999999999999999999999', } as const; diff --git a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts index 29f220531915..7e9179b7ee72 100644 --- a/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts +++ b/yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts @@ -129,7 +129,7 @@ export abstract class BaseWallet implements Wallet { protected scopesFrom(from: AztecAddress | NoFrom, additionalScopes: AztecAddress[] = []): AztecAddress[] { const allScopes = from === NO_FROM ? additionalScopes : [from, ...additionalScopes]; const scopeSet = new Set(allScopes.map(address => address.toString())); - return [...scopeSet].map(AztecAddress.fromString); + return [...scopeSet].map(AztecAddress.fromStringUnsafe); } /** diff --git a/yarn-project/wallets/src/embedded/wallet_db.ts b/yarn-project/wallets/src/embedded/wallet_db.ts index e6d8e1a47491..6671cdee0171 100644 --- a/yarn-project/wallets/src/embedded/wallet_db.ts +++ b/yarn-project/wallets/src/embedded/wallet_db.ts @@ -83,7 +83,7 @@ export class WalletDB { return accountAddresses.map(addressStr => ({ alias: aliasesByAddress.get(addressStr) ?? '', - item: AztecAddress.fromString(addressStr), + item: AztecAddress.fromStringUnsafe(addressStr), })); } @@ -92,7 +92,7 @@ export class WalletDB { for await (const [alias, item] of this.aliases.entriesAsync({ start: 'senders:', end: 'senders:\uffff' })) { result.push({ alias: alias.slice('senders:'.length), - item: AztecAddress.fromString(item.toString()), + item: AztecAddress.fromStringUnsafe(item.toString()), }); } return result; From 9184d47ef725d551e459bc9b86e80677e1ae3c64 Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Tue, 23 Jun 2026 14:59:02 -0300 Subject: [PATCH 7/8] fix(txe): correct AVM oracle registry types for call and success_copy (#24203) --- noir-projects/aztec-nr/aztec/src/oracle/mod.nr | 4 ---- yarn-project/txe/src/oracle/interfaces.ts | 6 +++--- yarn-project/txe/src/oracle/txe_oracle_public_context.ts | 6 +++--- yarn-project/txe/src/oracle/txe_oracle_registry.ts | 4 +--- yarn-project/txe/src/oracle/txe_oracle_version.ts | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/oracle/mod.nr b/noir-projects/aztec-nr/aztec/src/oracle/mod.nr index 3b2f1ad42c58..445af0acd0b6 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/mod.nr @@ -19,10 +19,6 @@ use crate::macros::oracle_testing::{generate_oracle_tests, generate_oracle_tests // TODO: implement once we support more complex types quote { emit_public_log_opcode }, quote { returndata_copy_opcode }, - // TODO: requires fix on registry - quote { call_opcode }, - quote { call_static_opcode }, - quote { success_copy_opcode }, ], )] pub mod avm; diff --git a/yarn-project/txe/src/oracle/interfaces.ts b/yarn-project/txe/src/oracle/interfaces.ts index 8f569d928349..c1cfe38ab8cb 100644 --- a/yarn-project/txe/src/oracle/interfaces.ts +++ b/yarn-project/txe/src/oracle/interfaces.ts @@ -42,9 +42,9 @@ export interface IAvmExecutionOracle { getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }>; returndataSize(): Promise; returndataCopy(rdOffset: number, copySize: number): Promise; - call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; - staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; - successCopy(): Promise; + call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + successCopy(): Promise; } /** diff --git a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts index 73af8b5f4329..67fc7ecf6efc 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts @@ -164,19 +164,19 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle { ); } - call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { throw new Error( 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', ); } - staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { throw new Error( 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', ); } - successCopy(): Promise { + successCopy(): Promise { throw new Error( 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', ); diff --git a/yarn-project/txe/src/oracle/txe_oracle_registry.ts b/yarn-project/txe/src/oracle/txe_oracle_registry.ts index d17a1935fced..2573cec9c991 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_registry.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_registry.ts @@ -366,7 +366,6 @@ export const TXE_ORACLE_REGISTRY = { { name: 'argsLength', type: U32 }, { name: 'args', type: ARRAY(FIELD) }, ], - returnType: ARRAY(FIELD), }), aztec_avm_staticCall: makeEntry({ @@ -377,10 +376,9 @@ export const TXE_ORACLE_REGISTRY = { { name: 'argsLength', type: U32 }, { name: 'args', type: ARRAY(FIELD) }, ], - returnType: ARRAY(FIELD), }), - aztec_avm_successCopy: makeEntry({ returnType: FIELD }), + aztec_avm_successCopy: makeEntry({ returnType: BOOL }), aztec_avm_getContractInstanceDeployer: makeEntry({ params: [{ name: 'address', type: AZTEC_ADDRESS }], diff --git a/yarn-project/txe/src/oracle/txe_oracle_version.ts b/yarn-project/txe/src/oracle/txe_oracle_version.ts index c59d3746f9d5..2b3df86678a3 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_version.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_version.ts @@ -14,4 +14,4 @@ export const TXE_ORACLE_VERSION_MINOR = 0; * - TXE_ORACLE_VERSION_MAJOR (and reset MINOR to 0) for breaking changes, or * - TXE_ORACLE_VERSION_MINOR for additive changes (new oracle method added). */ -export const TXE_ORACLE_INTERFACE_HASH = '408fdcb7d24ff0570366b3bc1514678b48890cd70f2621e4cee12dc3153e3923'; +export const TXE_ORACLE_INTERFACE_HASH = 'd42928ac8954f859850333e153c368f1a98402d0e3a1341bfd0c316ea8378357'; From 255eab5d0be940324aa0bc8f749aa87e0a4b9a7f Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 23 Jun 2026 16:15:52 -0400 Subject: [PATCH 8/8] feat(aztec-nr): make handshake tagging secrets mode agnostic (#24241) ## Summary Switches handshake-derived tagging secrets to be mode agnostic across constrained and unconstrained delivery. - Removes delivery mode specialization from handshake secrets and registry handling - Updates provided secret processing so PXE scans handshake secrets across both on-chain tag domains - Adds/updates Noir, PXE, and e2e coverage for mode-agnostic handshake behavior. The meaningful tests of multiple mode scanning functionality is contained in the TS. --------- Co-authored-by: Nicolas Chamo --- .../messages/delivery/constrained_delivery.nr | 18 +- .../aztec/src/messages/delivery/handshake.nr | 84 +++--- .../aztec/src/messages/delivery/mod.nr | 5 + .../aztec/src/messages/delivery/tag.nr | 1 - .../messages/processing/provided_secret.nr | 3 +- .../aztec-nr/aztec/src/standard_addresses.nr | 8 +- .../aztec_sublib/src/standard_addresses.nr | 8 +- .../handshake_registry_contract/src/main.nr | 61 ++--- .../handshake_registry_contract/src/sync.nr | 10 +- .../handshake_registry_contract/src/test.nr | 243 ++++-------------- .../src/main.nr | 10 +- .../src/test.nr | 14 +- .../pinned-standard-contracts.tar.gz | Bin 1119752 -> 1107643 bytes .../src/e2e_constrained_delivery.test.ts | 22 +- .../noir-structs/provided_secret.ts | 2 +- .../oracle/oracle_type_mappings.test.ts | 13 + .../oracle/utility_execution.test.ts | 78 ++++-- .../oracle/utility_execution_oracle.ts | 2 +- .../recipient_tagging_store.test.ts | 23 +- .../src/standard_contract_data.ts | 34 +-- 20 files changed, 267 insertions(+), 372 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr index 8d82056ae1cd..c1f2ffae9496 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr @@ -9,7 +9,6 @@ //! See [`constrain_secret`] for how a send is anchored to the registry, use crate::context::PrivateContext; -use crate::messages::delivery::OnchainDeliveryMode; use crate::nullifier::utils::compute_nullifier_existence_request; use crate::protocol::{ @@ -21,7 +20,7 @@ use crate::protocol::{ // registry's test suite compares this against its macro-generated `HandshakeRegistry::at(...).method(...).selector` // value so signature drift fails in tests. pub global VALIDATE_HANDSHAKE_SELECTOR: FunctionSelector = - comptime { FunctionSelector::from_signature("validate_handshake((Field),(Field),(u8),Field)") }; + comptime { FunctionSelector::from_signature("validate_handshake((Field),(Field),Field)") }; pub(crate) fn constrain_secret_and_emit_nullifier( context: &mut PrivateContext, @@ -60,8 +59,6 @@ fn constrain_secret( index: u32, ) { let caller = context.this_address(); - let mode = OnchainDeliveryMode::onchain_constrained(); - let mode_field = mode.to_field(); if bootstrapped { assert(index == 0, "freshly bootstrapped secret must start at index 0"); @@ -69,7 +66,7 @@ fn constrain_secret( let _ = context.call_private_function( registry, VALIDATE_HANDSHAKE_SELECTOR, - [sender.to_field(), recipient.to_field(), mode_field, secret], + [sender.to_field(), recipient.to_field(), secret], ); } else { let prev_nullifier = compute_constrained_msg_nullifier(sender, recipient, secret, index - 1); @@ -96,7 +93,6 @@ pub(crate) fn compute_constrained_msg_nullifier( mod test { use crate::context::PrivateContext; use crate::hash::hash_args; - use crate::messages::delivery::OnchainDeliveryMode; use crate::protocol::{address::AztecAddress, hash::compute_siloed_nullifier, traits::{FromField, ToField}}; use crate::test::helpers::test_environment::TestEnvironment; use super::{compute_constrained_msg_nullifier, constrain_secret_and_emit_nullifier, VALIDATE_HANDSHAKE_SELECTOR}; @@ -175,15 +171,7 @@ mod test { assert_eq(request.call_context.contract_address, registry); assert_eq(request.call_context.function_selector, VALIDATE_HANDSHAKE_SELECTOR); assert(!request.call_context.is_static_call); - assert_eq( - request.args_hash, - hash_args([ - sender.to_field(), - recipient.to_field(), - OnchainDeliveryMode::onchain_constrained().to_field(), - secret, - ]), - ); + assert_eq(request.args_hash, hash_args([sender.to_field(), recipient.to_field(), secret])); }); } diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr index d6c26fcd208a..380d77b0d6b7 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/handshake.nr @@ -1,11 +1,10 @@ -use crate::messages::delivery::OnchainDeliveryMode; use crate::protocol::point::EmbeddedCurvePoint; use crate::protocol::traits::{Deserialize, Serialize}; use crate::{ context::PrivateContext, ephemeral::EphemeralArray, - messages::processing::provided_secret::ProvidedSecret, + messages::{delivery::OnchainDeliveryMode, processing::provided_secret::ProvidedSecret}, oracle::{call_utility_function::call_utility_function, shared_secret::get_shared_secrets}, protocol::{ abis::function_selector::FunctionSelector, address::AztecAddress, hash::sha256_to_field, traits::ToField, @@ -16,11 +15,10 @@ use crate::{ /// Page size for handshake discovery pagination. pub global MAX_HANDSHAKES_PER_PAGE: u32 = 32; -/// A handshake discovered during sync: the sender's ephemeral public key and the delivery mode. +/// A handshake discovered during sync: the sender's ephemeral public key. #[derive(Deserialize, Eq, Serialize)] pub struct DiscoveredHandshake { pub eph_pk: EmbeddedCurvePoint, - pub mode: OnchainDeliveryMode, } /// A paginated response of discovered handshakes. @@ -35,21 +33,19 @@ pub(crate) global PROVIDED_SECRETS_ARRAY_BASE_SLOT: Field = global HANDSHAKE_EPH_PKS_SLOT: Field = sha256_to_field("AZTEC_NR::HANDSHAKE_EPH_PKS_SLOT".as_bytes()); -global HANDSHAKE_MODES_SLOT: Field = sha256_to_field("AZTEC_NR::HANDSHAKE_MODES_SLOT".as_bytes()); - // The helper cannot import the handshake registry interface because the registry contract depends on aztec-nr. These // selector constants pin the registry ABI surface this library calls. The registry's test suite compares them against // its macro-generated `HandshakeRegistry::at(...).method(...).selector` values so signature drift fails in tests. pub global GET_APP_SILOED_SECRET_SELECTOR: FunctionSelector = - comptime { FunctionSelector::from_signature("get_app_siloed_secret((Field),(Field),(u8))") }; + comptime { FunctionSelector::from_signature("get_app_siloed_secret((Field),(Field))") }; pub global NON_INTERACTIVE_HANDSHAKE_SELECTOR: FunctionSelector = - comptime { FunctionSelector::from_signature("non_interactive_handshake((Field),(Field),(u8))") }; + comptime { FunctionSelector::from_signature("non_interactive_handshake((Field),(Field))") }; pub global GET_HANDSHAKES_SELECTOR: FunctionSelector = comptime { FunctionSelector::from_signature("get_handshakes((Field),u32)") }; /// Resolves the app-siloed handshake secret, bootstrapping when none exists. /// -/// Resolves the secret for `(sender, recipient, mode)`, creating it via the registry's `non_interactive_handshake` +/// Resolves the secret for `(sender, recipient)`, creating it via the registry's `non_interactive_handshake` /// when no handshake exists yet. /// /// Returns `(secret, bootstrapped)`, where `bootstrapped` is true when this call created the handshake. @@ -65,10 +61,7 @@ pub(crate) fn get_or_create_app_siloed_handshake_secret( registry: AztecAddress, sender: AztecAddress, recipient: AztecAddress, - mode: OnchainDeliveryMode, ) -> (Field, bool) { - let mode_field = mode.to_field(); - // Safety: the response only selects which path runs. On `None` we bootstrap via `non_interactive_handshake`, // whose constrained return value is the secret, so a forged empty response cannot fabricate one; it can only // trigger an unnecessary re-handshake that replaces the registry note. The caller must constrain the returned @@ -77,7 +70,7 @@ pub(crate) fn get_or_create_app_siloed_handshake_secret( let returns = call_utility_function( registry, GET_APP_SILOED_SECRET_SELECTOR, - [sender.to_field(), recipient.to_field(), mode_field], + [sender.to_field(), recipient.to_field()], ); Deserialize::deserialize(returns) }; @@ -92,7 +85,7 @@ pub(crate) fn get_or_create_app_siloed_handshake_secret( .call_private_function( registry, NON_INTERACTIVE_HANDSHAKE_SELECTOR, - [sender.to_field(), recipient.to_field(), mode_field], + [sender.to_field(), recipient.to_field()], ) .get_preimage(); @@ -110,7 +103,6 @@ pub(crate) unconstrained fn get_handshake_secrets( let provided_secrets = EphemeralArray::::empty_at(PROVIDED_SECRETS_ARRAY_BASE_SLOT); let eph_pks: EphemeralArray = EphemeralArray::empty_at(HANDSHAKE_EPH_PKS_SLOT); - let modes: EphemeralArray = EphemeralArray::empty_at(HANDSHAKE_MODES_SLOT); let mut page_offset: u32 = 0; let mut has_more = true; @@ -118,9 +110,7 @@ pub(crate) unconstrained fn get_handshake_secrets( let page = fetch_handshake_page(scope, page_offset); for j in 0..page.items.len() { - let handshake = page.items.get(j); - eph_pks.push(handshake.eph_pk); - modes.push(handshake.mode); + eph_pks.push(page.items.get(j).eph_pk); } page_offset += page.items.len(); @@ -130,7 +120,10 @@ pub(crate) unconstrained fn get_handshake_secrets( if eph_pks.len() > 0 { let secrets = get_shared_secrets(scope, eph_pks, contract_address); for j in 0..secrets.len() { - provided_secrets.push(ProvidedSecret { secret: secrets.get(j), mode: modes.get(j) }); + let secret = secrets.get(j); + // Handshakes are mode-agnostic; emit one entry per on-chain tag domain for PXE to scan. + provided_secrets.push(ProvidedSecret { secret, mode: OnchainDeliveryMode::onchain_unconstrained() }); + provided_secrets.push(ProvidedSecret { secret, mode: OnchainDeliveryMode::onchain_constrained() }); } } @@ -154,18 +147,15 @@ mod test { DiscoveredHandshake, get_handshake_secrets, HandshakePage, MAX_HANDSHAKES_PER_PAGE, }; use crate::messages::delivery::OnchainDeliveryMode; - use crate::protocol::{address::AztecAddress, traits::Serialize}; + use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}}; use crate::test::helpers::test_environment::TestEnvironment; use crate::utils::point::point_from_x_coord; use std::test::OracleMock; - global UNCONSTRAINED: OnchainDeliveryMode = OnchainDeliveryMode::onchain_unconstrained(); - global CONSTRAINED: OnchainDeliveryMode = OnchainDeliveryMode::onchain_constrained(); - #[test] unconstrained fn get_handshake_secrets_returns_secrets_from_single_page() { - let mut env = TestEnvironment::new(); - let scope = env.create_light_account(); + let env = TestEnvironment::new(); + let scope = AztecAddress::from_field(1); let contract_address = AztecAddress { inner: 0xdeadbeef }; env.utility_context_at(contract_address, |_| { @@ -173,8 +163,8 @@ mod test { let pk_b = point_from_x_coord(2).unwrap(); let mut items: BoundedVec = BoundedVec::new(); - items.push(DiscoveredHandshake { eph_pk: pk_a, mode: UNCONSTRAINED }); - items.push(DiscoveredHandshake { eph_pk: pk_b, mode: CONSTRAINED }); + items.push(DiscoveredHandshake { eph_pk: pk_a }); + items.push(DiscoveredHandshake { eph_pk: pk_b }); let page = HandshakePage { items, total_count: 2 }; let _ = OracleMock::mock("aztec_utl_callUtilityFunction").returns(page.serialize()); @@ -184,18 +174,22 @@ mod test { let secrets = get_handshake_secrets(contract_address, scope); - assert_eq(secrets.len(), 2); + assert_eq(secrets.len(), 4); assert_eq(secrets.get(0).secret, secret_a); - assert_eq(secrets.get(0).mode, UNCONSTRAINED); - assert_eq(secrets.get(1).secret, secret_b); - assert_eq(secrets.get(1).mode, CONSTRAINED); + assert(secrets.get(0).mode == OnchainDeliveryMode::onchain_unconstrained()); + assert_eq(secrets.get(1).secret, secret_a); + assert(secrets.get(1).mode == OnchainDeliveryMode::onchain_constrained()); + assert_eq(secrets.get(2).secret, secret_b); + assert(secrets.get(2).mode == OnchainDeliveryMode::onchain_unconstrained()); + assert_eq(secrets.get(3).secret, secret_b); + assert(secrets.get(3).mode == OnchainDeliveryMode::onchain_constrained()); }); } #[test] unconstrained fn get_handshake_secrets_fetches_multiple_pages() { - let mut env = TestEnvironment::new(); - let scope = env.create_light_account(); + let env = TestEnvironment::new(); + let scope = AztecAddress::from_field(1); let contract_address = AztecAddress { inner: 0xdeadbeef }; env.utility_context_at(contract_address, |_| { @@ -204,12 +198,12 @@ mod test { let pk_c = point_from_x_coord(8).unwrap(); let mut page_1_items: BoundedVec = BoundedVec::new(); - page_1_items.push(DiscoveredHandshake { eph_pk: pk_a, mode: UNCONSTRAINED }); - page_1_items.push(DiscoveredHandshake { eph_pk: pk_b, mode: CONSTRAINED }); + page_1_items.push(DiscoveredHandshake { eph_pk: pk_a }); + page_1_items.push(DiscoveredHandshake { eph_pk: pk_b }); let page_1 = HandshakePage { items: page_1_items, total_count: 3 }; let mut page_2_items: BoundedVec = BoundedVec::new(); - page_2_items.push(DiscoveredHandshake { eph_pk: pk_c, mode: UNCONSTRAINED }); + page_2_items.push(DiscoveredHandshake { eph_pk: pk_c }); let page_2 = HandshakePage { items: page_2_items, total_count: 3 }; let _ = OracleMock::mock("aztec_utl_callUtilityFunction").returns(page_1.serialize()).times(1); @@ -218,13 +212,19 @@ mod test { mock_get_shared_secrets([111, 222, 333]); let secrets = get_handshake_secrets(contract_address, scope); - assert_eq(secrets.len(), 3); + assert_eq(secrets.len(), 6); assert_eq(secrets.get(0).secret, 111); - assert_eq(secrets.get(0).mode, UNCONSTRAINED); - assert_eq(secrets.get(1).secret, 222); - assert_eq(secrets.get(1).mode, CONSTRAINED); - assert_eq(secrets.get(2).secret, 333); - assert_eq(secrets.get(2).mode, UNCONSTRAINED); + assert(secrets.get(0).mode == OnchainDeliveryMode::onchain_unconstrained()); + assert_eq(secrets.get(1).secret, 111); + assert(secrets.get(1).mode == OnchainDeliveryMode::onchain_constrained()); + assert_eq(secrets.get(2).secret, 222); + assert(secrets.get(2).mode == OnchainDeliveryMode::onchain_unconstrained()); + assert_eq(secrets.get(3).secret, 222); + assert(secrets.get(3).mode == OnchainDeliveryMode::onchain_constrained()); + assert_eq(secrets.get(4).secret, 333); + assert(secrets.get(4).mode == OnchainDeliveryMode::onchain_unconstrained()); + assert_eq(secrets.get(5).secret, 333); + assert(secrets.get(5).mode == OnchainDeliveryMode::onchain_constrained()); }); } diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr index d009fb9d9402..7a9aed0dd7ab 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/mod.nr @@ -187,6 +187,11 @@ fn resolve_tag_secret_derivation( // TODO(F-699): Resolve wallet-default by first reusing an existing handshake for `(sender, recipient, mode)` // without consulting wallet policy. If none exists, consult the wallet delivery privacy preference before // choosing address-secret delivery or creating a non-interactive handshake. + // + // Once the privacy preference is oracle-driven, add integration coverage that reuses one mode-agnostic + // handshake secret for both constrained and unconstrained onchain delivery tags. Today unconstrained + // handshake-derived delivery is rejected, so PXE unit tests cover provided-secret expansion across both tag + // domains. if mode == DeliveryMode::onchain_constrained() { TagSecretDerivation::non_interactive_handshake() } else { diff --git a/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr b/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr index 69e32c293d5b..33185be9f38c 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr @@ -59,7 +59,6 @@ fn derive_handshake_log_tag( STANDARD_HANDSHAKE_REGISTRY_ADDRESS, sender, recipient, - mode, ); // Safety: the returned index is untrusted and is constrained before the tag is emitted. diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/provided_secret.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/provided_secret.nr index baea36896314..e250c678914c 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/provided_secret.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/provided_secret.nr @@ -1,5 +1,4 @@ -use crate::messages::delivery::OnchainDeliveryMode; -use crate::protocol::traits::{Deserialize, Serialize}; +use crate::{messages::delivery::OnchainDeliveryMode, protocol::traits::{Deserialize, Serialize}}; /// A tagging secret the app supplies explicitly to `get_pending_tagged_logs`. /// diff --git a/noir-projects/aztec-nr/aztec/src/standard_addresses.nr b/noir-projects/aztec-nr/aztec/src/standard_addresses.nr index 54cdd9db9047..5665acefb197 100644 --- a/noir-projects/aztec-nr/aztec/src/standard_addresses.nr +++ b/noir-projects/aztec-nr/aztec/src/standard_addresses.nr @@ -2,17 +2,17 @@ use protocol_types::{address::AztecAddress, traits::FromField}; pub global STANDARD_AUTH_REGISTRY_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x2df3bf0052304b37c59cfdb79eeeab7f05f8b1e197293e456dc9c7716e6fc654, + 0x04fc897b6deff5e3c18900a6a1c8ad601772027a500b8f329083810b7bffaf26, ); pub global STANDARD_MULTI_CALL_ENTRYPOINT_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x099e0fdbd90bed29103c75ae755dc43dc06e53c845dd25cf81ec05570a68c2fb, + 0x1aaa6153c0d5780188c64dd4d2c28372d42e5bcee5210c4184a312db0fe709d0, ); pub global STANDARD_PUBLIC_CHECKS_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x2da605de400a83f4c1750fdd1dba3a4b2977884a95549efd06f7a62ef6ae69c3, + 0x1da099c6dff135d8e519a4e973f27c0e25cf0897d7cf968c7d38329f46df1056, ); pub global STANDARD_HANDSHAKE_REGISTRY_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x19fca351f28a726da8bd3c66c10f22314bbe6359a61440efbbc0721e019167ef, + 0x215f91f8907b8d6406a9b209b88b3d8d01c764c81d3704af257a2de6f0cd908d, ); diff --git a/noir-projects/noir-contracts/contracts/protocol/aztec_sublib/src/standard_addresses.nr b/noir-projects/noir-contracts/contracts/protocol/aztec_sublib/src/standard_addresses.nr index 54cdd9db9047..5665acefb197 100644 --- a/noir-projects/noir-contracts/contracts/protocol/aztec_sublib/src/standard_addresses.nr +++ b/noir-projects/noir-contracts/contracts/protocol/aztec_sublib/src/standard_addresses.nr @@ -2,17 +2,17 @@ use protocol_types::{address::AztecAddress, traits::FromField}; pub global STANDARD_AUTH_REGISTRY_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x2df3bf0052304b37c59cfdb79eeeab7f05f8b1e197293e456dc9c7716e6fc654, + 0x04fc897b6deff5e3c18900a6a1c8ad601772027a500b8f329083810b7bffaf26, ); pub global STANDARD_MULTI_CALL_ENTRYPOINT_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x099e0fdbd90bed29103c75ae755dc43dc06e53c845dd25cf81ec05570a68c2fb, + 0x1aaa6153c0d5780188c64dd4d2c28372d42e5bcee5210c4184a312db0fe709d0, ); pub global STANDARD_PUBLIC_CHECKS_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x2da605de400a83f4c1750fdd1dba3a4b2977884a95549efd06f7a62ef6ae69c3, + 0x1da099c6dff135d8e519a4e973f27c0e25cf0897d7cf968c7d38329f46df1056, ); pub global STANDARD_HANDSHAKE_REGISTRY_ADDRESS: AztecAddress = AztecAddress::from_field( - 0x19fca351f28a726da8bd3c66c10f22314bbe6359a61440efbbc0721e019167ef, + 0x215f91f8907b8d6406a9b209b88b3d8d01c764c81d3704af257a2de6f0cd908d, ); diff --git a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr index 08d000906eb9..78ca61ea0e2b 100644 --- a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/main.nr @@ -12,7 +12,8 @@ global NON_INTERACTIVE_HANDSHAKE: u8 = 1; /// Registry for the constrained-delivery shared-secret handshake protocol. /// /// The registry establishes a master shared-secret point `S` between a sender and a recipient and stores one current -/// note for each `(recipient, sender, mode)` tuple. The raw `S` never leaves the registry: app contracts call +/// note for each `(recipient, sender)` pair. `S` is independent of delivery mode: the recipient should derive per-mode tags +/// from it when scanning. The raw `S` never leaves the registry: app contracts call /// [`HandshakeRegistry::non_interactive_handshake`] to receive the secret already siloed to the caller, call /// [`HandshakeRegistry::get_app_siloed_secret`] offchain for an existing handshake, and use /// [`HandshakeRegistry::validate_handshake`] to check an app-siloed secret against the current stored handshake. The @@ -29,7 +30,7 @@ pub contract HandshakeRegistry { keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_positive_ephemeral_key_pair}, macros::{functions::external, storage::storage}, messages::{ - delivery::{handshake::{HandshakePage, MAX_HANDSHAKES_PER_PAGE}, MessageDelivery, OnchainDeliveryMode}, + delivery::{handshake::{HandshakePage, MAX_HANDSHAKES_PER_PAGE}, MessageDelivery}, encryption::{aes128::AES128, message_encryption::MessageEncryption}, }, protocol::{ @@ -41,38 +42,34 @@ pub contract HandshakeRegistry { #[storage] struct Storage { - /// One current [`HandshakeNote`] per `(recipient, sender, mode)` tuple. Re-handshaking for the same tuple + /// One current [`HandshakeNote`] per `(recipient, sender)` pair. Re-handshaking for the same pair /// replaces the registry note; [`HandshakeRegistry::validate_handshake`] accepts only the latest note. - handshakes: Map, Context>, Context>, Context>, + handshakes: Map, Context>, Context>, } /// Performs a non-interactive handshake from `sender` to `recipient` and returns the app-siloed shared secret /// for the calling contract. /// - /// `mode` sets the delivery mode for messages tagged with this handshake - /// ([`MessageDelivery::onchain_unconstrained`] or [`MessageDelivery::onchain_constrained`]); the handshake note - /// itself is always stored onchain. + /// The handshake establishes a single shared secret per `(recipient, sender)` pair, independent of delivery + /// mode: the recipient should derive per-mode tags from it when scanning. The handshake note is always stored onchain. /// /// Generates a fresh ephemeral key pair `(eph_sk, eph_pk)`, computes the raw ECDH shared secret point /// `S = eph_sk * recipient_address_point`, and produces three effects: /// - /// 1. Inserts or replaces the current [`HandshakeNote`] owned by `sender` for `mode`, holding the raw point `S`. - /// 2. Emits an encrypted private log under a recipient-keyed tag with payload `[eph_pk.x, mode]`. The + /// 1. Inserts or replaces the current [`HandshakeNote`] owned by `sender`, holding the raw point `S`. + /// 2. Emits an encrypted private log under a recipient-keyed tag with payload `[eph_pk.x]`. The /// recipient discovers handshakes addressed to them by scanning their tag and recovers `S` from `eph_pk` /// via their own ECDH (`recipient_isk * eph_pk`). `eph_pk.y` is fixed positive by the - /// [`generate_positive_ephemeral_key_pair`] convention, so only `eph_pk.x` is transmitted. The mode is - /// included so the recipient knows which delivery domain to search for tagged logs. + /// [`generate_positive_ephemeral_key_pair`] convention, so only `eph_pk.x` is transmitted. /// 3. Returns the app-siloed shared secret for `msg_sender()`, allowing the caller to fold "handshake + first /// tag" into one call without a second hop into the registry. /// /// # Panics - /// If `mode` is not a recognized delivery mode. - /// /// If `recipient` is not a valid curve point. There are no upstream side effects in this call frame to /// protect, and a fallback would insert a permanent note recording a handshake with an invalid recipient, /// polluting registry state. #[external("private")] - fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress, mode: OnchainDeliveryMode) -> Field { + fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress) -> Field { let recipient_point = recipient.to_address_point().expect(f"recipient address is not on the curve"); let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair(); @@ -83,7 +80,7 @@ pub contract HandshakeRegistry { // The recipient is not involved in this note's delivery: they discover the handshake via the encrypted log // emitted below, not via the sender's note. We deliver onchain unconstrained rather than offchain so the // note is discoverable via normal PXE sync. - self.storage.handshakes.at(recipient).at(mode).at(sender).initialize_or_replace(|_| note).deliver( + self.storage.handshakes.at(recipient).at(sender).initialize_or_replace(|_| note).deliver( MessageDelivery::onchain_unconstrained().with_sender(sender), ); @@ -92,36 +89,25 @@ pub contract HandshakeRegistry { DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, ); - let ciphertext = AES128::encrypt( - [eph_pk.x, mode.to_field()], - recipient, - self.context.this_address(), - ); + let ciphertext = AES128::encrypt([eph_pk.x], recipient, self.context.this_address()); self.context.emit_private_log_unsafe(log_tag, BoundedVec::from_array(ciphertext)); note.siloed_for(self.msg_sender()) } - /// Asserts that `app_siloed_secret` is the silo of a stored handshake from `sender` to `recipient` in `mode`, for + /// Asserts that `app_siloed_secret` is the silo of the stored handshake from `sender` to `recipient`, for /// the caller (`msg_sender()`). /// /// Apps that receive an `app_siloed_secret` from an untrusted source call this once to validate that secret /// against the registry's latest stored handshake. /// /// # Panics - /// If `mode` is not a recognized delivery mode. - /// - /// If no stored handshake for `(sender, recipient, mode)` silos to `app_siloed_secret` under the calling + /// If no stored handshake for `(sender, recipient)` silos to `app_siloed_secret` under the calling /// contract's address. #[external("private")] - fn validate_handshake( - sender: AztecAddress, - recipient: AztecAddress, - mode: OnchainDeliveryMode, - app_siloed_secret: Field, - ) { + fn validate_handshake(sender: AztecAddress, recipient: AztecAddress, app_siloed_secret: Field) { let caller = self.msg_sender(); - let replacement_note_message = self.storage.handshakes.at(recipient).at(mode).at(sender).get_note(); + let replacement_note_message = self.storage.handshakes.at(recipient).at(sender).get_note(); let note = replacement_note_message.get_note(); assert(note.siloed_for(caller) == app_siloed_secret, "no matching handshake"); @@ -131,23 +117,16 @@ pub contract HandshakeRegistry { replacement_note_message.deliver(MessageDelivery::onchain_unconstrained()); } - /// Returns the app-siloed shared secret for an existing handshake in `mode`. + /// Returns the app-siloed shared secret for an existing handshake. /// /// This is the existing-handshake retrieval surface. It returns `silo(S, msg_sender)`, never raw `S`; a different /// caller receives a different app-siloed value for the same registry note. Siloing by `self.msg_sender()` means /// a hostile contract cannot read another app's siloed secret: whatever arguments a caller passes, it only ever /// learns values siloed to its own address. Contracts should still call [HandshakeRegistry::validate_handshake] /// when they need a constrained proof that a supplied app-siloed secret matches the current handshake. - /// - /// # Panics - /// If `mode` is not a recognized delivery mode. #[external("utility")] - unconstrained fn get_app_siloed_secret( - sender: AztecAddress, - recipient: AztecAddress, - mode: OnchainDeliveryMode, - ) -> Option { - let handshake = self.storage.handshakes.at(recipient).at(mode).at(sender); + unconstrained fn get_app_siloed_secret(sender: AztecAddress, recipient: AztecAddress) -> Option { + let handshake = self.storage.handshakes.at(recipient).at(sender); if handshake.is_initialized() { Option::some(handshake.view_note().siloed_for(self.msg_sender())) diff --git a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/sync.nr b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/sync.nr index d4296582c399..412150813095 100644 --- a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/sync.nr +++ b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/sync.nr @@ -3,7 +3,7 @@ use aztec::{ context::UtilityContext, ephemeral::EphemeralArray, messages::{ - delivery::{handshake::DiscoveredHandshake, OnchainDeliveryMode}, + delivery::handshake::DiscoveredHandshake, discovery::{ ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler, do_sync_state_without_handshake_discovery, }, @@ -19,7 +19,7 @@ use aztec::{ address::AztecAddress, constants::DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, hash::{compute_log_tag, sha256_to_field}, - traits::{Deserialize, ToField}, + traits::ToField, }, utils::point::point_from_x_coord_and_sign, }; @@ -63,7 +63,7 @@ pub(crate) unconstrained fn handshake_registry_sync( let tag = compute_log_tag(scope.to_field(), DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG); let logs = get_handshake_logs_in_range(contract_address, tag, cursor, Option::some(next_block)); - // Store all valid handshakes with their delivery mode + // Store all valid handshakes let handshakes: CapsuleArray = CapsuleArray::at(contract_address, HANDSHAKES_CAPSULE_SLOT, scope); logs.for_each(|_i, log| { @@ -71,9 +71,7 @@ pub(crate) unconstrained fn handshake_registry_sync( let _ = AES128::decrypt(ciphertext, scope, contract_address) .and_then(|pt| { - point_from_x_coord_and_sign(pt.get(0), true).map(|pk| { - DiscoveredHandshake { eph_pk: pk, mode: OnchainDeliveryMode::deserialize([pt.get(1)]) } - }) + point_from_x_coord_and_sign(pt.get(0), true).map(|pk| DiscoveredHandshake { eph_pk: pk }) }) .map(|h| handshakes.push(h)); }); diff --git a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr index 092dc0c2bd5f..2aa6de1aaeef 100644 --- a/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/standard/handshake_registry_contract/src/test.nr @@ -7,8 +7,6 @@ use aztec::{ GET_APP_SILOED_SECRET_SELECTOR, GET_HANDSHAKES_SELECTOR, MAX_HANDSHAKES_PER_PAGE, NON_INTERACTIVE_HANDSHAKE_SELECTOR, }, - MessageDelivery, - OnchainDeliveryMode, }, oracle::shared_secret::get_shared_secret, protocol::{ @@ -22,9 +20,6 @@ use aztec::{ use std::test::OracleMock; -global ONCHAIN_UNCONSTRAINED: OnchainDeliveryMode = MessageDelivery::onchain_unconstrained().into(); -global ONCHAIN_CONSTRAINED: OnchainDeliveryMode = MessageDelivery::onchain_constrained().into(); - unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); @@ -58,18 +53,9 @@ unconstrained fn selectors_match_the_constrained_delivery_helper() { let sender = AztecAddress::from_field(2); let recipient = AztecAddress::from_field(3); - assert_eq( - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED).selector, - GET_APP_SILOED_SECRET_SELECTOR, - ); - assert_eq( - registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED).selector, - NON_INTERACTIVE_HANDSHAKE_SELECTOR, - ); - assert_eq( - registry.validate_handshake(sender, recipient, ONCHAIN_CONSTRAINED, 0).selector, - VALIDATE_HANDSHAKE_SELECTOR, - ); + assert_eq(registry.get_app_siloed_secret(sender, recipient).selector, GET_APP_SILOED_SECRET_SELECTOR); + assert_eq(registry.non_interactive_handshake(sender, recipient).selector, NON_INTERACTIVE_HANDSHAKE_SELECTOR); + assert_eq(registry.validate_handshake(sender, recipient, 0).selector, VALIDATE_HANDSHAKE_SELECTOR); assert_eq(registry.get_handshakes(recipient, 0).selector, GET_HANDSHAKES_SELECTOR); } @@ -78,19 +64,18 @@ unconstrained fn non_interactive_handshake_stores_handshake_for_sender_and_recip let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let returned_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let returned_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let utility_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake secret should be present"); assert_eq(utility_secret, returned_secret); assert(returned_secret != 0, "returned app-siloed secret should be non-zero"); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, returned_secret)); + env.call_private(sender, registry.validate_handshake(sender, recipient, returned_secret)); } // The DH-direct flow lifts `recipient` to a curve point and fails loud if the address has no @@ -105,8 +90,7 @@ unconstrained fn handshake_to_invalid_recipient_panics() { let invalid_recipient = AztecAddress::from_field(3); assert(!invalid_recipient.is_valid()); - let _ = - env.call_private(sender, registry.non_interactive_handshake(sender, invalid_recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, invalid_recipient)); } // The handshake tag depends only on the recipient, so multiple senders posting to @@ -116,14 +100,11 @@ unconstrained fn two_senders_to_same_recipient_share_tag() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_first = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_first.len(), 2); - let _ = env.call_private( - other_sender, - registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED), - ); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); let logs_second = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_second.len(), 2); @@ -143,20 +124,18 @@ unconstrained fn two_senders_to_same_recipient_share_tag() { assert(logs_first.get(1).get(1) != logs_second.get(1).get(1), "ciphertexts should differ across senders"); } -// Re-handshaking for the same `(sender, recipient, mode)` replaces the prior current handshake and produces +// Re-handshaking for the same `(sender, recipient)` replaces the prior current handshake and produces // distinct `S` (hence distinct app-siloed secrets when siloed against the same caller). #[test] unconstrained fn rehandshake_replaces_previous_secret_and_returns_latest() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let secret_first = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let secret_first = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_first = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_first.len(), 2); - let secret_second = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let secret_second = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_second = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_second.len(), 2); @@ -181,12 +160,12 @@ unconstrained fn rehandshake_replaces_previous_secret_and_returns_latest() { let retrieved = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake secret should be present"); assert_eq(retrieved, secret_second); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, secret_second)); + env.call_private(sender, registry.validate_handshake(sender, recipient, secret_second)); } #[test(should_fail_with = "no matching handshake")] @@ -194,11 +173,10 @@ unconstrained fn rehandshake_revokes_previous_secret() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let secret_first = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let secret_first = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, secret_first)); + env.call_private(sender, registry.validate_handshake(sender, recipient, secret_first)); } #[test] @@ -206,11 +184,11 @@ unconstrained fn different_recipients_have_different_tags() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); let logs_a = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_a.len(), 2); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); let logs_b = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_b.len(), 2); @@ -232,31 +210,29 @@ unconstrained fn one_sender_can_have_active_handshakes_with_many_recipients() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - let secret_a = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED)); - let secret_b = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b, ONCHAIN_UNCONSTRAINED)); + let secret_a = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let secret_b = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); assert(secret_a != secret_b, "separate recipient handshakes should produce distinct secrets"); let retrieved_a = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient_a, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient_a), ) .expect(f"handshake secret should be present for recipient A"); let retrieved_b = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient_b, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient_b), ) .expect(f"handshake secret should be present for recipient B"); assert_eq(retrieved_a, secret_a); assert_eq(retrieved_b, secret_b); - env.call_private(sender, registry.validate_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED, secret_a)); - env.call_private(sender, registry.validate_handshake(sender, recipient_b, ONCHAIN_UNCONSTRAINED, secret_b)); + env.call_private(sender, registry.validate_handshake(sender, recipient_a, secret_a)); + env.call_private(sender, registry.validate_handshake(sender, recipient_b, secret_b)); } #[test] @@ -264,12 +240,12 @@ unconstrained fn get_app_siloed_secret_returns_none_for_wrong_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); assert(env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(other_sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(other_sender, recipient), ) .is_none()); } @@ -279,12 +255,12 @@ unconstrained fn get_app_siloed_secret_returns_none_for_wrong_recipient() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); assert(env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient_b, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient_b), ) .is_none()); } @@ -294,16 +270,13 @@ unconstrained fn validate_handshake_rejects_wrong_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let _ = env.call_private( - other_sender, - registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED), - ); + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); env.call_private_opts( sender, CallPrivateOptions::new().with_additional_scopes([other_sender]), - registry.validate_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED, secret), + registry.validate_handshake(other_sender, recipient, secret), ); } @@ -312,11 +285,10 @@ unconstrained fn validate_handshake_rejects_wrong_recipient() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - let secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED)); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b, ONCHAIN_UNCONSTRAINED)); + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); - env.call_private(sender, registry.validate_handshake(sender, recipient_b, ONCHAIN_UNCONSTRAINED, secret)); + env.call_private(sender, registry.validate_handshake(sender, recipient_b, secret)); } // `PrivateMutable`'s init nullifier is derived from the owner's nullifier @@ -327,8 +299,7 @@ unconstrained fn non_interactive_handshake_rejects_other_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = - env.call_private(sender, registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(other_sender, recipient)); } // Counterpart with `non_interactive_handshake_rejects_other_sender`: with `other_sender` added to additional scopes, @@ -342,7 +313,7 @@ unconstrained fn non_interactive_handshake_accepts_other_sender_in_additional_sc let _ = env.call_private_opts( sender, CallPrivateOptions::new().with_additional_scopes([other_sender]), - registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.non_interactive_handshake(other_sender, recipient), ); } @@ -351,12 +322,11 @@ unconstrained fn get_app_siloed_secret_differs_per_msg_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let sender_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let sender_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let other_sender_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(other_sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake secret should be present for other caller"); @@ -368,11 +338,11 @@ unconstrained fn validate_handshake_accepts_secret_siloed_for_msg_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let other_sender_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(other_sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake secret should be present for other caller"); @@ -381,7 +351,7 @@ unconstrained fn validate_handshake_accepts_secret_siloed_for_msg_sender() { env.call_private_opts( other_sender, CallPrivateOptions::new().with_additional_scopes([sender]), - registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, other_sender_secret), + registry.validate_handshake(sender, recipient, other_sender_secret), ); } @@ -390,13 +360,12 @@ unconstrained fn validate_handshake_rejects_secret_siloed_for_different_msg_send let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let sender_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let sender_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); env.call_private_opts( other_sender, CallPrivateOptions::new().with_additional_scopes([sender]), - registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, sender_secret), + registry.validate_handshake(sender, recipient, sender_secret), ); } @@ -407,9 +376,9 @@ unconstrained fn validate_handshake_rejects_wrong_secret() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, secret + 1)); + env.call_private(sender, registry.validate_handshake(sender, recipient, secret + 1)); } #[test(should_fail_with = "Failed to get a note")] @@ -417,95 +386,7 @@ unconstrained fn validate_handshake_panics_when_no_handshake() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, 1)); -} - -// A handshake can be set to constrained mode, and `get_app_siloed_secret` returns the matching secret for that mode. -#[test] -unconstrained fn non_interactive_handshake_sets_constrained_mode() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); - - let app_siloed = env - .execute_utility_opts( - ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), - ) - .expect(f"handshake secret should be present"); - assert_eq(app_siloed, secret); -} - -#[test] -unconstrained fn get_app_siloed_secret_returns_none_for_uninitialized_mode() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - - assert(env - .execute_utility_opts( - ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), - ) - .is_none()); -} - -#[test] -unconstrained fn same_pair_can_have_active_handshakes_in_both_modes() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - let unconstrained_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let constrained_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); - - assert(unconstrained_secret != constrained_secret, "separate mode handshakes should produce distinct secrets"); - - let unconstrained_app_siloed = env - .execute_utility_opts( - ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), - ) - .expect(f"unconstrained handshake secret should be present"); - let constrained_app_siloed = env - .execute_utility_opts( - ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), - ) - .expect(f"constrained handshake secret should be present"); - - assert_eq(unconstrained_app_siloed, unconstrained_secret); - assert_eq(constrained_app_siloed, constrained_secret); - - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, unconstrained_secret)); - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_CONSTRAINED, constrained_secret)); -} - -#[test(should_fail_with = "no matching handshake")] -unconstrained fn validate_handshake_rejects_unconstrained_secret_for_constrained_mode() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - let unconstrained_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); - - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_CONSTRAINED, unconstrained_secret)); -} - -#[test(should_fail_with = "no matching handshake")] -unconstrained fn validate_handshake_rejects_constrained_secret_for_unconstrained_mode() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let constrained_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); - - env.call_private(sender, registry.validate_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED, constrained_secret)); + env.call_private(sender, registry.validate_handshake(sender, recipient, 1)); } #[test] @@ -513,8 +394,7 @@ unconstrained fn non_interactive_handshake_is_discovered_by_recipient() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let returned_secret = - env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let returned_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let discovered = env.execute_utility(registry.get_handshakes(recipient, 0)); @@ -523,7 +403,6 @@ unconstrained fn non_interactive_handshake_is_discovered_by_recipient() { // The recipient derives the same app-siloed secret from the discovered eph_pk via ECDH. let handshake = discovered.items.get(0); - assert_eq(handshake.mode, ONCHAIN_UNCONSTRAINED); let recipient_secret = env.utility_context_at(sender, |_| get_shared_secret(recipient, handshake.eph_pk, sender)); assert_eq(recipient_secret, returned_secret); @@ -531,7 +410,7 @@ unconstrained fn non_interactive_handshake_is_discovered_by_recipient() { let utility_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(sender), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_UNCONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake secret should be present"); assert_eq(utility_secret, returned_secret); @@ -542,7 +421,7 @@ unconstrained fn handshake_to_one_recipient_not_discovered_by_another() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); let discovered_a = env.execute_utility(registry.get_handshakes(recipient_a, 0)); let discovered_b = env.execute_utility(registry.get_handshakes(recipient_b, 0)); @@ -556,7 +435,7 @@ unconstrained fn repeated_sync_does_not_refetch_logs() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let after_first = env.execute_utility(registry.get_handshakes(recipient, 0)); assert_eq(after_first.items.len(), 1); @@ -573,15 +452,12 @@ unconstrained fn later_handshake_appends_after_cursor_advances() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let after_first = env.execute_utility(registry.get_handshakes(recipient, 0)); assert_eq(after_first.items.len(), 1); - let _ = env.call_private( - other_sender, - registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_UNCONSTRAINED), - ); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); let after_second = env.execute_utility(registry.get_handshakes(recipient, 0)); assert_eq(after_second.items.len(), 2); } @@ -592,11 +468,8 @@ unconstrained fn multiple_senders_discovered_for_same_recipient() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); - let _ = env.call_private( - other_sender, - registry.non_interactive_handshake(other_sender, recipient, ONCHAIN_CONSTRAINED), - ); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); let discovered = env.execute_utility(registry.get_handshakes(recipient, 0)); @@ -605,8 +478,6 @@ unconstrained fn multiple_senders_discovered_for_same_recipient() { discovered.items.get(0).eph_pk.x != discovered.items.get(1).eph_pk.x, "distinct senders should produce distinct ephemeral public keys", ); - assert_eq(discovered.items.get(0).mode, ONCHAIN_CONSTRAINED); - assert_eq(discovered.items.get(1).mode, ONCHAIN_UNCONSTRAINED); } #[test] @@ -616,7 +487,7 @@ unconstrained fn get_handshakes_paginates_across_full_pages() { let total_handshakes = MAX_HANDSHAKES_PER_PAGE + 3; for _ in 0..total_handshakes { - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); } let page_0 = env.execute_utility(registry.get_handshakes(recipient, 0)); @@ -633,7 +504,7 @@ unconstrained fn get_handshakes_out_of_bounds_offset_returns_empty() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_UNCONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let page = env.execute_utility(registry.get_handshakes(recipient, 999)); assert_eq(page.items.len(), 0); diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr index 70ff711f9dd5..d17950c89343 100644 --- a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr @@ -7,7 +7,7 @@ mod test; pub contract ConstrainedDeliveryTest { use aztec::{ macros::{events::event, functions::external, storage::storage}, - messages::delivery::{MessageDelivery, OnchainDeliveryMode}, + messages::delivery::MessageDelivery, note::note_viewer_options::NoteViewerOptions, oracle::notes::get_next_tagging_index, protocol::{ @@ -65,13 +65,9 @@ pub contract ConstrainedDeliveryTest { /// Test reads go through this wrapper so the registry sees this contract as /// [HandshakeRegistry::get_app_siloed_secret]'s msg_sender. #[external("utility")] - unconstrained fn get_app_siloed_secret( - sender: AztecAddress, - recipient: AztecAddress, - mode: OnchainDeliveryMode, - ) -> Option { + unconstrained fn get_app_siloed_secret(sender: AztecAddress, recipient: AztecAddress) -> Option { let registry = HandshakeRegistry::at(STANDARD_HANDSHAKE_REGISTRY_ADDRESS); - self.call(registry.get_app_siloed_secret(sender, recipient, mode)) + self.call(registry.get_app_siloed_secret(sender, recipient)) } /// Returns the values of all `FieldNote`s discovered for `owner`. The e2e test derives both the count and the diff --git a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr index 2cc740b4fb19..b423f116d042 100644 --- a/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr @@ -7,16 +7,12 @@ use crate::ConstrainedDeliveryTest; use aztec::{ - messages::delivery::{MessageDelivery, OnchainDeliveryMode}, protocol::address::AztecAddress, standard_addresses::STANDARD_HANDSHAKE_REGISTRY_ADDRESS, test::helpers::test_environment::{CallPrivateOptions, DeployOptions, ExecuteUtilityOptions, TestEnvironment}, }; use handshake_registry_contract::HandshakeRegistry; -// Modes have no public constructors, so the tests derive them through the delivery builder API. -global ONCHAIN_CONSTRAINED: OnchainDeliveryMode = MessageDelivery::onchain_constrained().into(); - unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, AztecAddress) { let mut env = TestEnvironment::new(); @@ -49,15 +45,15 @@ unconstrained fn rehandshake_replaces_registry_secret_for_future_delivery() { let first_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(test_address), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"first delivery should have stored a handshake"); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let second_secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(test_address), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"re-handshake should have stored a replacement handshake"); assert(first_secret != second_secret, "re-handshake should produce a distinct secret"); @@ -74,11 +70,11 @@ unconstrained fn fails_at_index_above_zero_without_prior_nullifier() { let test_contract = ConstrainedDeliveryTest::at(test_address); let registry = HandshakeRegistry::at(registry_address); - let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient, ONCHAIN_CONSTRAINED)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let secret = env .execute_utility_opts( ExecuteUtilityOptions::new().with_from(test_address), - registry.get_app_siloed_secret(sender, recipient, ONCHAIN_CONSTRAINED), + registry.get_app_siloed_secret(sender, recipient), ) .expect(f"handshake should be siloed for the test contract"); diff --git a/noir-projects/noir-contracts/pinned-standard-contracts.tar.gz b/noir-projects/noir-contracts/pinned-standard-contracts.tar.gz index d8cd0e2ce1884649791da11a418480eeb3c8e9ca..29e560a371b17b0ad37f40fa2e6e306b67cf8586 100644 GIT binary patch literal 1107643 zcmV)3K+C@$iwFQ)usUi01MGciciTp`;C!B6fjFKvnWkt--sxqYVk;e;wq=i{B;C*U zAt4f!&?W&k07_On`tNg>+N%J9vYa@1=6SDU0fky`-F=Cd)yc3(j?=O#&WGbXtBQDB zZS26~*WzjaUuB;CuTS`GZf@SaeLKSc-n)B;{=2!&e{bHtwSDW}z3pi0&i2;bJGVBs zw(muo+qbqh@BXi7^HTV^K3ZF$1(n+gtu?R+eo#ewplz+;O(_IDYv%o z50h+MoG+?tJ+lq&cm<7a-Pux)FM8|rH(bP%NirFx*qa| zHYO|n^-i|Dwuq|}?5V6KH+ic4tWX_YEAwSBrlD@$j3)WG98k9dZu&O636KAlo+eoo zM~hjUW>xZ`isCYg;Ym@%=TSb5j?Q78y{Jg4Ws#Lpb&^EmyeN`#k!O=MJC3-wXq+xi zl7f2ZJpKM@G|wkVw;%1{UPZWZ*fuSrMN&-jVxCN*Wr>eo{r%~|xA%HcnM2=kMFZr^ z3cs2q*>QD(AB||s`|zOCjs6;Sw(dq7k@@WZL|b>eqyAfXbQDdq$UeA7v!sgl2hsA@ z_LI)r3^#{926$TDe;Dn*>xqY_;>p%s`RILeZawQp8&AYnFcF%^K8^k^`p944SYo|H zv4Y29#@w=tTMRwu=5w({61E04BRr1YZ2FHyN3g3=3hRnC`~7`68T^?&ze%wd`A_lX zh10=4c3_Rs+?~BzyHi}AIL^mW!*%MIfAruadbssn(HZyfHiJ&zX5YSnnI`FR*0KBR zzI&T_M{f;c!GQw-_Tk&-ZX7xsGfhI?s3e=H`}8&gppWQU%MDwWNmMQY<_80NUJtQ3 zw)pl9e(+8lK^BSk0Mo{v&NAgE@?;Zmipq9nr}M4 zrcYqmYk#AT_s#b0+r4ND|9|@~{Lj5@{GY8}bbGTGZExO(zwXoXTlg<%woT18@4&xq z;oI$PYzKdCJ=l6@j1re6pq63sZ|B524z%>dT)G+t;s37vZvz`&+uHnODs!AYRiq!)9b-M*=yZI9weR(41dp1^?q_S&X6MagmERH&+~64ROUgKE{0+3}*T9 zFXG*f(duhe$q!G5Bme{cvxUFknE4>6kx-Wvc%IH z4Aggn!BZZ5AG-TdFRSGj`)ZC+b-qZ-E^`VUI-zca3#-z3g8287FL#Hi#s6Ev+lk2& zc%K2rT>ly??CW)@Ca`wD#?%#A`ZvzsBth4`snoqw-O0Z2hsXR=hWFB7FM;FCf%ha3 zv6)JEKBCU>xQ6M3j~jp_lZ!5+6v!-nN}Jiq@@%6_XE}l{Hcm6xgGld+-JmR&a~cTX zPIeLM((&w|%W;Bir?E_X&4(wzzDgp4tZclj@;Oi% zyj8dZh?!upqjSCj-@YM`em9B;Z5UmD`=;hF-3BkR3lo^a zE!6JH0WUX|0=6)pXVgLML%Zk4M?l4ZU$NSy0 zwytMwQ_tIyp0T=aA4K=gud4H3aeR9Q_(L?sm(D_b-qzyt!9Y&g{i5T~M7=ZFQFGK~ zmi~L0tTU?nMMs}i&s#)j*ziOM5n1TD4*j|l=tWn0Vh=+Hv@V>vU}nKJO*;JE*@Zx> z3;WSUSO0xs^7C)Ab?9}7g!%+Jokql#33bBb$I+&_P(b8QMfWA+|175!mv9>CY!)H! zmLN|GSlmSTNZ(HLbOPk#6v<7wNXBVA+Ze~F{OA$RSKpHSVGUa1JEROneg*_@K2Iho ztaWy7&;^fD^S6TZD2{QGCqynuBwZBgDfGij5V{w0+T}^UoK2!E0XP7vC|ST5i7u=k z7EHusl45HjWYajE^}RXNOzKMLA^zIXMEtcNeH|Ylho70^(HNgX_Nar^w2zvtCVI7`Q!>Dy~<;rZ>gu7gQ@L=q*lGO4}cq==m4(IlAyz=(?u z8!%ap6Xagg=>~m2J^_FNp(g=>$6;4tssPpF6TCIY(5fWOrZwIOML+YaPIsiQ2FOH7 zJU(IO6C3cX&XVYTmY>1v9;)i{Vgff5uuM7u1d>kAHN%SE^rM$Ryx=2iVCo>HzVOy* zoi1$aXh}@^NJy5^w8-at6JMcBeKbozghFu@2hEQv+yH!yccYk{15&ewQZwlhPcCkq z!&RG{dEXWqqF<5-z$>4f@<(vPP^yHjrKy(@?Y*fxhStkjg}7E=_|tf{Oi)735R;XQ zSvoeEdqSVqkVmt8{2tl@BW<7?q~vFDkqIs5aGsWQz(CH0)DJfUFGN-hxm|udor(|Y2bD$g_H!lWJ@?pVv@#EG-G{w6DX!{tT|Ks*WKRS4yE*7|C z{ILJ_n#oc=3@l-pAqZp|_*yOE+E4tMH$8s>Hmf03-09Q;_aQA9@E6=%Qs)2;FHw*9 zUvEt1pQYC}XtaYWa1iJiKIcYoxjL!O@?bOf-jTo@(82^r0(=p`bbrnpEdNhZ6ukAZPRVJ=$A} zMAHLQup9%iC7zlWE_n$AXC#DFhG2iFNp{ege?k$0bm`9_)H3DWJv{N{UMnE zb^A{ulwQEuBIy#!r^a!`t=)X~}R(IbB`T*GZKs$n^78U>ki@~5=9?`!{Elpp5Qm5OCHoIW~J~Z<(pC=t( zW7}!aBN%#urX`181s*q%unU(vScQXHqp5%8Y1R?==)&HwS~>Z1s-^wW9A?*f9TJ!U zqov1LTp?N`ONl8#!$@#<&xNp2Uc!z-+_^t4E)4kUw33`NS#)3nNMw-Xmr_6gY&!?} zXolz)O+aNdn&iXsJR9>F(N6iJi~t>tVVXlB!SNZ-%j40XO_}f;=n_6vmC#O;@Y^dA zu11e=eH8}rKWRem*Ta&OO_pj&%_(Mg1S5MU@y}XjAnsP^Vfm)2|xVuNu~`8rH8GR{!_q zX;_6uPRJ~VW3y0X0W@Y4kb;w{nhEBl?D1Vmh1XgsIne@L#FLnPO;-*JJUY;D0I95u zh3*Ym!kDfi^o2n&Q9OeVCor41sH9u9?Y`Ql`PV2Y1H%$0#fX~&B#<>fN${F7--^

a;!C2WMt@e$-YV`}(@FNdlH_h5W)_61DM z0+F%t^C*#@Va@ud-&7q{o&z1U+vZ$fFS*Y!NK9n*E@gP9X{Xr=gXfCd1&2CNP!>I68EHr zz3N5$S^|{(Tkf{}8>97mg3H9KA^8tEFZtgf@B8s7q4*d4?nfpgy@;P40PYiyu#*dY z%bIVa3A~q#Sykc2Op2BaN+kOs%ld{H`q4YZu@WP!m|xOjgSb>8dtQ_guC_=gC>~Fz zAev?s38bZzNGHiOUe2mWA}DQ@I!e$moTGOVFz`GCaPd;yCc;ZeM^G+6G7%xgI4SZh z{SVEV#9NLnj^fE_oS~Uwnvn1!1n(mG_cBG<8`?~lGaiKOFVG|V`G|SS*@(X}m&17p zY*%USz_;HJo^-jEPPZRsG&=Kh`Ohv!bYh=s%U+yI z!&~$*>i7G|V~H7XIuyoXkujQN4#T4jBvTp!R>{J9yo&Rc0nGRb3D&5E4o!mqk8l== zy2)8IGFpw;!9c0H^OQQ>+^BX}AWI)7ohUVmjfLg@U{JjkMbs$>#Fc5Z1;KrA% zC(!^bZjv6Zlk|`K&`}FUarCZB@+&!tnL;eQKTfPGAwf&Yi`OW?9`a!n~_Yrpb#(!@)2;4f$aL=LAHsk03Y6NC}`t_H(Ik8R&kiERuy z@H_lG02D6;vM((-4kBkXkEY`=j3kPun?xg1W226O{A@_?qfZPS1Cn?G2)gZ|*!rM% ze9h%92qp!997YjDpag_q;Dw&nN9iGpr93fSqCgJQ1b9G6t|kfPtieBZ)3OWjhML=6 z6i+MU1<+2b7A5T%2C&-m{~h4j!0c-Rvz~VY92?!Q1Y+Ic+VFcJIi6jCm=rIfn1vkk zqKzcN8ksbI2f)K)UP|dE1{geHEh!ThqC5cBwEfAvC;Y}zzA=Dq`BVfp6woJotVVmPShsn+6#^!Z87(4ILm+ZI7b0M7;ZqJ2?uv8ZLY zc~yf=jPvHFTjr>k^@~Rw>a$x09*NkGC>HZx9mp62;kc{@&a~X>$~rI4XS-Y`yvw@ zi7v?1P%0O)4QenI2)0i7)R}k9$^D{QLM4(6Y+979&+xs&J=nCIKrJ6G3%F{vU_kcXY0*T zLZp#BYbPms)1ei;LqhUvcc1VVBq=n;o47_E6onf9WVs-Nj zrkvue^^k;Hjman+9L&O`-tx5MM@nTSHCE=wZ8ThVcOey)HJ!lwhJD)kelHSAB-#h* z@~G&G-i?MsvXoIUo1l`aVHTm(X1)5nW>H5RYot?}seV_dGLy;AxZd^?^ zvnzl}CW;e__Ke0x1H4H8LWYbJOhpNx+jBPlngG-~jfp*DMyTJ+`x^4QeUP0xzfOO3 z{B-xp@lZI!GQvW{LuVUIAm=|fHp+COCFdYwe253Y3}D8%8$vroTxpV`(}EEvz)*$x zwa9fdlf0z(_!+uPlJ6>d6rUwGpm~wZbIED3gA$%O5^;-$8y!visJ`cb2I=m#a%ZI^ zY`INcL7KQa5+%I^G_{eiA+ejo66D1BG6XPrtzjDaDPWA8Ho!?Ps>Q(O4we~S49gKo z?xP)hD0!25k8$_A#*9}%Rg|ZzP`HEAl1j~UnjPgISXNLw<@sD#v-f7AdAY$r_6u#X zOX}U@4B#ZfS1|;Eh8SZ588a)kn*8lGG!bpEYyBj;p+CMM)ggj$=ts~p3aO_IL9A;t zxcJ7afwL5-PMH3 zq^8DZ8YkCuQ$uDK4@iwhflU@~yO#qv*C=xduyeccfe-a6u2;;4>kOl~a5gM}w4X#L zRkbJwH*aEo<-V{~Ce>M9yuX z_Ha~SjnBIlJ6GG>X5t)6qS=M>sxwC=O8pd@l6oqJ)OhA`qsF@s>u9`I0BAn5I6Skj z%W*nJ56(uIYiPKM`Gq zjQ2oLSHV2iGv7|b00XYv9k}5WSsKPC-^fJ>naJ;^l{ggmtSz~dl28rGs_|U*IIxwC zn)3QyZz~L`4_Zk@@&^#&`I0#?9rT|%Lgk>11|@`Ldi}bi?MS=9iEG1Oh{n^Xm+-fD zFf4H69VLcz->sSxH1{T>ra1`DzF~0NnzzbR6D5bZs*3bzNtHqHZ-E(`^7b1*xYnix zqrF8-ogEzs2S^x$!B0Gsr}A$$N20b>Mn`x7iPvqB$;nk?(}XySMy^OzM;OSrkW51W zJ21F*5)%0bWJii}5S*Ll>mTw0B%1>50acu$J-kdpkxF4zpPXME9r9HOVPc@YUtz-= zd}Ea=i{=+ySegL^h(Z9D;?eZ&wU2dfx)%ogzP;9`?kB_~psmvq+z<-a=@{DA^w@&& zuUSk0KU{C{GaiVJT)*N=;D+n&@*A*2&Ft$YfMEDqdKkG7Tgqe_Mv|5rMl%9vveA^w zqcZvTlDuk79|L0;d(2(TgQ6}*85O{`u>&IK5y6fLk;S!`+2sZ{UeO-8oew~d40}?N}R+IdugGQq3tCnfXIj= zk-E9jyIx~E33sD+WL`xBtV>+#ojkO{P(T2pg()tDUf`=uA=N z_f2ESgw;ka$a;v&!UtY(Gn)2=?YUeYA19RF368v8LZ)jk&gv)`$7tqN_Up4)`gm6= z%T$gAW^ddvR&!ZYE>8Ci9<;4*uSx%&=p%00g>tE!$HjZj04FTjA$K3OC*p~A^05pU z_^Z7ZZmi_3PmPrnju&OMJQ^OwWnD1b=Kc272;DA+=(RH>XStdf$vazXMA~uhGBz7W zsR@j{b-#UJq}MBXE7f;?d+Wj7wL53aLVc%qHn%R{>52*8xxd{$R8sUUC|g6XckkYA zA441HbtP!r+uXc7Xc(5Ox&_?dzQ6VY+aSU6nyK(#NPB{U}-UBZ87g}q62)-L2hVP z*P#=#Xy?@SOND8jVn80|bt;bYET5-i!&i~q8)0tchVmh1$HwTJXpf^2sN``z1;Upt ziR*^FO5<7jABt~~PP5v{Tsm_aUg_wxPSZv*9#OqBm(@gllhK=pmeu%pmLI|P{P6na zi{TG@yU(8uB20WY+5NCUt+1g~Ae2gC8mD{H{!R7y=zf52$i^96U}a}c6Q_ij%2EiJ zj;FK(LrxJ`I|v9#2Dhs|gHgq!1Mx%*jtUPsU~sU$oyxA`u2|cD`@;PW{xwZ!02iHK z=avKHJm3$byVK5o%7+|(a+VV`^poqJuF(6FtFcnx&(Kc6eju4jw^@AZiZv+SrnAHq z035ceGaV>@^JnOYL+OS|PV5MKMO63S<*jY!4?2Ctnxl(l5*M5ac6^Spvv8l61%@PH zm?8$1#2BV@o-c2lQcN;ng2`XUa3ghW3yT!Jx zO0|ZDRdX6gF` z#)vaaV^m3hct?pK-ik@Z9G`QX=M$lw(?KM0LHV^XvZqKvtU+xh%cgo%Dv_*%cH-#m zH8oxBj&n@}U&q=bSe94DXv*mn?gk(joICo`SJk=TCAPKjI{|3|=LkGN zIgoADH=`Hldvo+_(Qzb*S_z{n{5n5x#I6s$UVp;avuc@9-fqtLy(G;P5f?) zQgL*YQXVt-#|#top;9+G%tg{USiJz8M3_Al_z<9~F~<~_CFANW*hR<P`y2%s{3&Onw;gzb2n+FwGeJGJCf1~{l3`*!H5z#^XRw|eK^$f} z!b!s0!u70g5WG54%u(Oji!z6rod(jCdOA?CX z7iGsX25si_=(qi`~kZ4m$8UJzeD9&&kJab!Td zx`W7`(65U8UFXG69?Ofam8!0H;rK#Ih6YF>Mnw%Np`u_Mmg44x+sqQd0M#|XSpZqq zRJVbL>R4+95XbN$JEW#&4g=g$*etS~^5G%SiuECKflj9_=Xv_f7OHc(q@G$A4Iy47V9H%0!_ zvBk8#J4$`@^$PudSLlF_;ZJ#y^wR#!*Gbz6zFwo>;~LTa|Hmo zPf3NGmHmnDl4+%FeT$kcytO*0o(rHWlu~B@E%Z{DS4eekE2%D}3aVS)r)h&kD`To` z;i(TL_yglB3o2IV9X=0|lKyXhD*R+D>JN>YD7oZpx=~~0fB-=L zi_vp0+GV2yzeGc{@qpXNoIm~OrwmO$G_X`DmsoM=G+~oOY0ak835i6E66d*L`?f+Z z3@D^{mhQxC!Qt8!m{F}DdmxXSd!6-_8>C8y%H~3}&kKW1tg1CCQ&|VSf_Z`7yA*I9 z1{u+7N{b2;12jj8l|*ZuJ`8;+JLWDwqD*~KtHCXv(B|5^A8gm{`>DkmU3S8Bhf*)| z0tV4hp3i<4^f;9N_hqwa4#N7q@D=$YWB*imNO>buxsd?}^~2jbojaKV!Wb|I{54C^ z(KZ<`MezIh1Rh{mV$#%MPfsW`Ti!G^Va_wjfZQhll_0A;6wulWu?aCgtXBl z=VaNOCk!pBp@B;*1S{$<@hW7gSi?zsOdb^Sz4?hwqU**`h(j0d6f(ICS3yH}n<70v z!Lo0%y6m3=Z7wq`xEPAhzqFbMe=87?Xi|raXakygz=k~v@PYX25>oA$Xm)kk=^890 zSi3r8MP3>0F_vIxJ^^oiiQ1^I=<6%``cl!?YW*7izDOj7xaD&oG35f?jK+ktXW=nV z>LuBX2XqCrR#+_%Ebt{Ewg&CZ&_;QmplSc~TlzZ+VtCoG+$VhY#!D3-J;D!j@)d#y zIr~C)RG04%xW{p|j<{xyTQ3Bv7F?KD{&_jsPk!CA2X;FPNOlbDwqR%VvA%F;eI9s! z^E)NR(NK@SyGfdvVAGRyLiI@Dl(9|`w&jG{^%CH^f}NLmL3ngi`3vPWY%xkg`PiW4 z2?qjwNyy{(RO+9&6!zjeZd(RTpCx-2) zKRRM=({p%72*9GF< zZXbuMDn1S0C+9Ws=lAa3Y9H4t+|!pChSQ>^dbXU@RTkdfY~OL3VZi{=Z%st_{jK(u zijS7*Y$B(5lFSx$WuUhxvXLk* z8aW8eC<;ggX)>;Ipv4rCBxp2u!q3bigQ^SJD-0#o!W40ij0FX@DvB;LX2lBtIAG!= z4@X04jo+R~6Etd(^IaL_dlc?CD|32H