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/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/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/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/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/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/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..c1f2ffae9496 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/constrained_delivery.nr @@ -0,0 +1,207 @@ +//! 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::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),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(); + + 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(), 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::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(), 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..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,10 +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, @@ -15,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. @@ -34,7 +33,65 @@ 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))") }; +pub global NON_INTERACTIVE_HANDSHAKE_SELECTOR: FunctionSelector = + 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)`, 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, +) -> (Field, bool) { + // 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()], + ); + 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()], + ) + .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) @@ -46,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; @@ -54,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(); @@ -66,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() }); } } @@ -75,9 +132,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) } @@ -87,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, |_| { @@ -106,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()); @@ -117,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, |_| { @@ -137,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); @@ -151,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 57b47837846f..7a9aed0dd7ab 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,14 @@ 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. + // + // 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 { @@ -135,7 +203,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 +243,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..33185be9f38c --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr @@ -0,0 +1,172 @@ +//! 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, + ); + + // 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/aztec-nr/aztec/src/messages/processing/event_validation_request.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/event_validation_request.nr index bcdcd9afc900..b25374638e23 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/event_validation_request.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/event_validation_request.nr @@ -35,49 +35,3 @@ impl EventValidationRequest { } } } - -mod test { - use crate::event::EventSelector; - use crate::messages::logs::event::MAX_EVENT_SERIALIZED_LEN; - use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}}; - use super::EventValidationRequest; - - #[test] - unconstrained fn serialization_matches_typescript() { - let request = EventValidationRequest { - contract_address: AztecAddress::from_field(1), - event_type_id: EventSelector::from_field(2), - randomness: 3, - max_event_serialized_len: MAX_EVENT_SERIALIZED_LEN as Field, - serialized_event: BoundedVec::from_array([4, 5]), - event_commitment: 6, - tx_hash: 7, - }; - - // We define the serialization in Noir and the deserialization in TS. If the deserialization changes from the - // snapshot value below, then event_validation_request.test.ts must be updated with the same value. Ideally - // we'd autogenerate this, but for now we only have single-sided snapshot generation, from TS to Noir, which is - // not what we need here. - let expected_serialization = [ - 1, // contract_address - 2, // event_type_id - 3, // randomness - MAX_EVENT_SERIALIZED_LEN as Field, - 4, // serialized_event[0] - 5, // serialized_event[1] - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, // serialized_event padding - 2, // bounded_vec_len - 6, // event_commitment - 7, // tx_hash - ]; - - assert_eq(request.serialize(), expected_serialization); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr index ad54692b8087..b5c71dc3f567 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_request.nr @@ -33,58 +33,3 @@ impl LogRetrievalRequest { } } } - -mod test { - use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}}; - use super::{LogRetrievalRequest, LogSource}; - - #[test] - fn serialization_of_defaults_matches_typescript() { - let request = LogRetrievalRequest { - contract_address: AztecAddress::from_field(1), - unsiloed_tag: 2, - source: LogSource.PUBLIC_AND_PRIVATE, - from_block: Option::none(), - to_block: Option::none(), - }; - - // We define the serialization in Noir and the deserialization in TS. If the deserialization changes from - // the snapshot value below, then log_retrieval_request.test.ts must be updated with the same value. - // Ideally we'd autogenerate this, but for now we only have single-sided snapshot generation, from TS to - // Noir, which is not what we need here. - let expected_serialization = [ - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - ]; - - assert_eq(request.serialize(), expected_serialization); - } - - #[test] - fn serialization_with_values_matches_typescript() { - let request = LogRetrievalRequest { - contract_address: AztecAddress::from_field(1), - unsiloed_tag: 2, - source: LogSource.PUBLIC, - from_block: Option::some(10), - to_block: Option::some(20), - }; - - let expected_serialization = [ - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x000000000000000000000000000000000000000000000000000000000000000a, - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000014, - ]; - - assert_eq(request.serialize(), expected_serialization); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr index 256a2c81cbbe..335804c3ef19 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr @@ -20,213 +20,3 @@ pub struct LogRetrievalResponse { /// The first nullifier created by `tx_hash` pub first_nullifier_in_tx: Field, } - -mod test { - use crate::protocol::traits::Deserialize; - use super::LogRetrievalResponse; - - #[test] - fn deserialization_of_some_matches_typescript() { - let some_response = Option::some( - LogRetrievalResponse { - log_payload: BoundedVec::from_array([1, 2, 3]), - tx_hash: 4, - unique_note_hashes_in_tx: BoundedVec::from_array([5, 6]), - first_nullifier_in_tx: 7, - }, - ); - - // The following value was generated by `log_retrieval_response.test.ts` - // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. - let serialized_some_log_retrieval_response_from_typescript = [ - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000003, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000003, - 0x0000000000000000000000000000000000000000000000000000000000000004, - 0x0000000000000000000000000000000000000000000000000000000000000005, - 0x0000000000000000000000000000000000000000000000000000000000000006, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000007, - ]; - - assert_eq( - Option::::deserialize(serialized_some_log_retrieval_response_from_typescript), - some_response, - ); - } - - #[test] - fn deserialization_of_none_matches_typescript() { - let none_response: Option = Option::none(); - - // The following value was generated by `log_retrieval_response.test.ts` - // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. - let serialized_none_log_retrieval_response_from_typescript = [ - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - ]; - - assert_eq( - Option::::deserialize(serialized_none_log_retrieval_response_from_typescript), - none_response, - ); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/message_context.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/message_context.nr index 4f456bf6003b..0ab9fc9d17e9 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/message_context.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/message_context.nr @@ -11,98 +11,3 @@ pub struct MessageContext { pub unique_note_hashes_in_tx: BoundedVec, pub first_nullifier_in_tx: Field, } - -mod test { - use crate::messages::processing::MessageContext; - use crate::protocol::traits::Deserialize; - - #[test] - unconstrained fn message_context_serialization_matches_typescript() { - // Setup test data - let tx_hash = 123; - let unique_note_hashes = BoundedVec::from_array([4, 5]); - let first_nullifier = 6; - - // Create a MessageContext instance - let message_context = MessageContext { - tx_hash, - unique_note_hashes_in_tx: unique_note_hashes, - first_nullifier_in_tx: first_nullifier, - }; - - // Expected output generated from TypeScript's `MessageContext.toFields()` - let serialized_message_context_from_typescript = [ - 0x000000000000000000000000000000000000000000000000000000000000007b, - 0x0000000000000000000000000000000000000000000000000000000000000004, - 0x0000000000000000000000000000000000000000000000000000000000000005, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000006, - ]; - - let deserialized = MessageContext::deserialize(serialized_message_context_from_typescript); - - assert_eq(deserialized, message_context); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr index cf932da857be..ec2e98a4b4b7 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr @@ -44,52 +44,3 @@ impl NoteValidationRequest { } } } - -mod test { - use crate::messages::logs::note::MAX_NOTE_PACKED_LEN; - use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}}; - use super::NoteValidationRequest; - - #[test] - fn serialization_matches_typescript() { - let request = NoteValidationRequest { - contract_address: AztecAddress::from_field(1), - owner: AztecAddress::from_field(50), - storage_slot: 2, - randomness: 42, - note_nonce: 3, - max_note_packed_len: MAX_NOTE_PACKED_LEN as Field, - packed_note: BoundedVec::from_array([4, 5]), - note_hash: 6, - nullifier: 7, - tx_hash: 8, - }; - - // We define the serialization in Noir and the deserialization in TS. If the deserialization changes from the - // snapshot value below, then note_validation_request.test.ts must be updated with the same value. Ideally we'd - // autogenerate this, but for now we only have single-sided snapshot generation, from TS to Noir, which is not - // what we need here. - let expected_serialization = [ - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000032, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x000000000000000000000000000000000000000000000000000000000000002a, - 0x0000000000000000000000000000000000000000000000000000000000000003, - MAX_NOTE_PACKED_LEN as Field, - 0x0000000000000000000000000000000000000000000000000000000000000004, - 0x0000000000000000000000000000000000000000000000000000000000000005, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000006, - 0x0000000000000000000000000000000000000000000000000000000000000007, - 0x0000000000000000000000000000000000000000000000000000000000000008, - ]; - - assert_eq(request.serialize(), expected_serialization); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/pending_tagged_log.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/pending_tagged_log.nr index 7a85decfb504..3829895d2bc2 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/pending_tagged_log.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/pending_tagged_log.nr @@ -9,118 +9,3 @@ pub(crate) struct PendingTaggedLog { // Context of the message included in the log. pub context: MessageContext, } - -mod test { - use crate::messages::processing::MessageContext; - use crate::protocol::traits::Deserialize; - use super::PendingTaggedLog; - - #[test] - unconstrained fn pending_tagged_log_serialization_matches_typescript() { - let log = BoundedVec::from_array([1, 2, 3]); - let tx_hash = 123; - let unique_note_hashes = BoundedVec::from_array([4, 5]); - let first_nullifier = 6; - let pending_log = PendingTaggedLog { - log, - context: MessageContext { - tx_hash, - unique_note_hashes_in_tx: unique_note_hashes, - first_nullifier_in_tx: first_nullifier, - }, - }; - - // The following value was generated by `pending_tagged_log.test.ts` - // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. - let serialized_pending_tagged_log_from_typescript = [ - 0x0000000000000000000000000000000000000000000000000000000000000001, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000003, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000003, - 0x000000000000000000000000000000000000000000000000000000000000007b, - 0x0000000000000000000000000000000000000000000000000000000000000004, - 0x0000000000000000000000000000000000000000000000000000000000000005, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000000, - 0x0000000000000000000000000000000000000000000000000000000000000002, - 0x0000000000000000000000000000000000000000000000000000000000000006, - ]; - - let deserialized = PendingTaggedLog::deserialize(serialized_pending_tagged_log_from_typescript); - - assert_eq(deserialized, pending_log); - } -} 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/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/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/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/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 7157b985dded..8844b1b26323 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,11 +12,16 @@ 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 /// 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))] @@ -26,7 +31,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::{ @@ -38,38 +43,35 @@ 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. - handshakes: Map, Context>, Context>, Context>, + /// 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>, } /// 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 a [`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(); @@ -80,7 +82,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), ); @@ -89,36 +91,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 stored handshake. + /// 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"); @@ -128,23 +119,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 b87b85b422b5..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 @@ -1,7 +1,13 @@ 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, + }, + }, oracle::shared_secret::get_shared_secret, protocol::{ address::AztecAddress, @@ -14,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(); @@ -41,24 +44,38 @@ 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).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); +} + #[test] unconstrained fn non_interactive_handshake_stores_handshake_for_sender_and_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 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 @@ -73,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 @@ -84,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); @@ -111,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); @@ -149,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")] @@ -162,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] @@ -174,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); @@ -200,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] @@ -232,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()); } @@ -247,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()); } @@ -262,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), ); } @@ -280,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 @@ -295,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, @@ -310,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), ); } @@ -319,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"); @@ -336,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"); @@ -349,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), ); } @@ -358,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), ); } @@ -375,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")] @@ -385,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] @@ -481,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)); @@ -491,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); @@ -499,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); @@ -510,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)); @@ -524,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); @@ -541,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); } @@ -560,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)); @@ -573,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] @@ -584,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)); @@ -601,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/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..d17950c89343 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/main.nr @@ -0,0 +1,91 @@ +//! 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, + note::note_viewer_options::NoteViewerOptions, + 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, value: Field) { + self.storage.notes.at(recipient).insert(FieldNote { value }).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, value: Field) { + self.emit(DeliveryEvent { value }).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) -> Option { + let registry = HandshakeRegistry::at(STANDARD_HANDSHAKE_REGISTRY_ADDRESS); + 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 + /// 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. + #[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..b423f116d042 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/constrained_delivery_test_contract/src/test.nr @@ -0,0 +1,84 @@ +//! 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::{ + protocol::address::AztecAddress, + standard_addresses::STANDARD_HANDSHAKE_REGISTRY_ADDRESS, + test::helpers::test_environment::{CallPrivateOptions, DeployOptions, ExecuteUtilityOptions, TestEnvironment}, +}; +use handshake_registry_contract::HandshakeRegistry; + +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, 1)); + let first_secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + 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)); + let second_secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + 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"); + + 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); +} + +#[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)); + let secret = env + .execute_utility_opts( + ExecuteUtilityOptions::new().with_from(test_address), + registry.get_app_siloed_secret(sender, recipient), + ) + .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, 1)); +} 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-contracts/pinned-standard-contracts.tar.gz b/noir-projects/noir-contracts/pinned-standard-contracts.tar.gz index d8cd0e2ce188..29e560a371b1 100644 Binary files a/noir-projects/noir-contracts/pinned-standard-contracts.tar.gz and b/noir-projects/noir-contracts/pinned-standard-contracts.tar.gz differ 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/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/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/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-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/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/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/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_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_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_constrained_delivery.test.ts b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts new file mode 100644 index 000000000000..50a108c75e7a --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_constrained_delivery.test.ts @@ -0,0 +1,266 @@ +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, + 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, setupPXEAndGetWallet } from './fixtures/setup.js'; +import { TestWallet } from './test-wallet/test_wallet.js'; + +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, 1).send({ from: sender }); + + const { result: secretAfterFirstSend } = await contract.methods + .get_app_siloed_secret(sender, recipient) + .simulate({ from: sender }); + expect(secretAfterFirstSend).toBeDefined(); + + await contract.methods.emit_event(recipient, 1).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, recipient) + .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, 1).send({ from: sender }), + contract.methods.emit_note(recipient, 1).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).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) + .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).send({ from: sender }); + + await new BatchCall(wallet, [ + contract.methods.emit_note(batchRecipient2, 1), + contract.methods.emit_note(batchRecipient2, 1), + ]).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient2) + .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) + .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, 1), + contract.methods.emit_note(batchRecipient4, 1), + ]).send({ from: sender }); + + const { result: secret } = await contract.methods + .get_app_siloed_secret(sender, batchRecipient4) + .simulate({ from: sender }); + expect(secret).toBeDefined(); + + const { result: index } = await contract.methods.next_index_for_secret(secret).simulate({ from: sender }); + expect(index).toEqual(1n); + }); + }); +}); + +// 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); + }); +}); 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_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/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/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/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/foundation/src/trees/membership_witness.ts b/yarn-project/foundation/src/trees/membership_witness.ts index bbc46f886b1e..645d9a37da87 100644 --- a/yarn-project/foundation/src/trees/membership_witness.ts +++ b/yarn-project/foundation/src/trees/membership_witness.ts @@ -38,14 +38,6 @@ export class MembershipWitness { return [new Fr(this.leafIndex), ...this.siblingPath]; } - /** - * Returns a representation of the membership witness as expected by intrinsic Noir deserialization. - */ - public toNoirRepresentation(): (string | string[])[] { - // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js - return [new Fr(this.leafIndex).toString() as string, this.siblingPath.map(fr => fr.toString()) as string[]]; - } - static schemaFor(size: N) { return schemas.Buffer.transform(b => MembershipWitness.fromBuffer(b, size)); } 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/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/index.ts b/yarn-project/pxe/src/contract_function_simulator/index.ts index 15ec0a51e5a0..27a7efaba3e3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/index.ts +++ b/yarn-project/pxe/src/contract_function_simulator/index.ts @@ -1,12 +1,9 @@ export { ORACLE_REGISTRY, makeEntry, - type HandlersForPrefix, type NamedValue, type OracleRegistryEntry, type ParamTypes, - type RegistryParam, - type StripOraclePrefix, } from './oracle/oracle_registry.js'; export { ARRAY, @@ -39,6 +36,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'; @@ -47,7 +45,7 @@ export { HashedValuesCache } from './hashed_values_cache.js'; export { pickNotes } from './pick_notes.js'; export type { IMiscOracle, IUtilityExecutionOracle, IPrivateExecutionOracle } from './oracle/interfaces.js'; export type { NoteData } from './noir-structs/note_data.js'; -export { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; +export type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; export { MessageContextService } from '../messages/message_context_service.js'; export { UtilityExecutionOracle } from './oracle/utility_execution_oracle.js'; export { PrivateExecutionOracle } from './oracle/private_execution_oracle.js'; @@ -56,12 +54,15 @@ export { executePrivateFunction, extractPrivateCircuitPublicInputs } from './ora export { generateSimulatedProvingResult } from './contract_function_simulator.js'; export { packAsHintedNote } from './oracle/note_packing_utils.js'; export { BoundedVec } from './noir-structs/bounded_vec.js'; +export type { ContractClassLogData } from './noir-structs/contract_class_log_data.js'; +export type { EmbeddedCurvePoint } from './noir-structs/embedded_curve_point.js'; export { EphemeralArray } from './noir-structs/ephemeral_array.js'; export { Option } from './noir-structs/option.js'; -export { UtilityContext } from './noir-structs/utility_context.js'; +export type { UtilityContext } from './noir-structs/utility_context.js'; export { EventValidationRequest } from './noir-structs/event_validation_request.js'; -export { LogRetrievalRequest } from './noir-structs/log_retrieval_request.js'; -export { LogRetrievalResponse } from './noir-structs/log_retrieval_response.js'; +export type { LogRetrievalRequest } from './noir-structs/log_retrieval_request.js'; +export type { LogRetrievalResponse } from './noir-structs/log_retrieval_response.js'; export { NoteValidationRequest } from './noir-structs/note_validation_request.js'; -export { ProvidedSecret } from './noir-structs/provided_secret.js'; +export type { TxEffectData } from './noir-structs/tx_effect_data.js'; +export type { ProvidedSecret } from './noir-structs/provided_secret.js'; export { TransientArrayService } from './transient_array_service.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/bounded_vec.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/bounded_vec.ts index cf3ea4b5bd61..3899b08ea6dd 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/bounded_vec.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/bounded_vec.ts @@ -1,9 +1,4 @@ -/** - * TypeScript counterpart of Noir's `BoundedVec`. - * - * Carries the actual `data` plus wire-format metadata (`maxLength`, `elementSize`) so the ACVM - * serializer can pad the storage slot to exactly `maxLength * elementSize` fields. - */ +/** TypeScript counterpart of Noir's `BoundedVec`. */ export class BoundedVec { private constructor( public readonly data: T[], @@ -15,16 +10,11 @@ export class BoundedVec { * Construct a BoundedVec with data. * * @param data - Actual elements. Length must be `<= maxLength`. - * @param maxLength - Maximum capacity declared at the Noir call site. - * The storage slot is padded to this many elements. - * @param elementSize - Number of Fr fields each element contributes when serialized. - * `1` for scalar elements (u8, Field) — this is the default. - * `> 1` for compound elements (e.g. a packed note that spans multiple fields). - * - * @example A bounded vec of bytes (elementSize defaults to 1): - * ```ts - * BoundedVec.from({ data: plaintext, maxLength: ciphertext.maxLength }) - * ``` + * @param maxLength - Maximum capacity declared at the Noir call site. The storage slot is padded to this many + * elements. + * @param elementSize - Number of Fr fields each element contributes when serialized. Only consulted for + * variable-width elements whose width isn't statically known from their shape (e.g. a packed note spanning + * `packedHintedNoteLength` fields); fixed-width elements derive it from the shape, so it can be omitted. * * @example A bounded vec of packed notes, each spanning `packedHintedNoteLength` fields: * ```ts @@ -43,16 +33,6 @@ export class BoundedVec { return new BoundedVec(data, maxLength, elementSize); } - /** - * Construct an empty BoundedVec, typically used as a shape template for `Option.empty(...)`. - * - * @param maxLength - Maximum capacity declared at the Noir call site. - * @param elementSize - Number of Fr fields each element contributes when serialized (default 1). - */ - static empty({ maxLength, elementSize = 1 }: { maxLength: number; elementSize?: number }): BoundedVec { - return new BoundedVec([], maxLength, elementSize); - } - equals(other: BoundedVec, innerEquals: (a: T, b: T) => boolean): boolean { return ( this.maxLength === other.maxLength && diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/contract_class_log_data.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/contract_class_log_data.ts new file mode 100644 index 000000000000..fc7918535e50 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/contract_class_log_data.ts @@ -0,0 +1,9 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; + +/** Wire form of a contract class log. */ +export type ContractClassLogData = { + contractAddress: AztecAddress; + fields: Fr[]; + emittedLength: number; +}; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/embedded_curve_point.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/embedded_curve_point.ts new file mode 100644 index 000000000000..56d715ea6e23 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/embedded_curve_point.ts @@ -0,0 +1,4 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; + +/** Wire form of a Grumpkin point crossing the oracle boundary: Noir's `EmbeddedCurvePoint` serializes to `[x, y]`. */ +export type EmbeddedCurvePoint = { x: Fr; y: Fr }; 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 deleted file mode 100644 index b7a6eaf9ce96..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/event_validation_request.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { EventSelector } from '@aztec/stdlib/abi'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { TxHash } from '@aztec/stdlib/tx'; - -import { EventValidationRequest } from './event_validation_request.js'; - -describe('EventValidationRequest', () => { - it('output of Noir serialization deserializes as expected', () => { - const serialized = [ - 1, // contract_address - 2, // event_type_id - 3, // randomness - 10, // max_event_serialized_len - 4, // serialized_event[0] - 5, // serialized_event[1] - 0, // serialized_event padding start - 0, - 0, - 0, - 0, - 0, - 0, - 0, // serialized_event padding end - 2, // bounded_vec_len - 6, // event_commitment - 7, // tx_hash - ].map(n => new Fr(n)); - - const request = EventValidationRequest.fromFields(serialized); - - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(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)]); - expect(request.eventCommitment).toEqual(new Fr(6)); - expect(request.txHash).toEqual(TxHash.fromBigInt(7n)); - }); - - it('throws if fed more fields than expected', () => { - const serialized = [ - 1, // contract_address - 2, // event_type_id - 3, // randomness - 10, // max_event_serialized_len (10) - 4, // serialized_event[0] - 5, // serialized_event[1] - 0, // serialized_event padding (11 storage fields total, but max_event_serialized_len=10) - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 2, // bounded_vec_len - 6, // event_commitment - 7, // tx_hash - ].map(n => new Fr(n)); - - expect(() => EventValidationRequest.fromFields(serialized)).toThrow( - 'Error converting array of fields to EventValidationRequest: expected 17 fields but received 18 (maxEventSerializedLen=10).', - ); - }); -}); 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 deleted file mode 100644 index 175f7f906bcb..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_request.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { Tag } from '@aztec/stdlib/logs'; - -import { LogRetrievalRequest, LogSource } from './log_retrieval_request.js'; - -describe('LogRetrievalRequest', () => { - it('output of Noir serialization with defaults deserializes as expected', () => { - const serialized = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000002', - '0x0000000000000000000000000000000000000000000000000000000000000002', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - ].map(Fr.fromHexString); - - const request = LogRetrievalRequest.fromFields(serialized); - - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); - expect(request.tag).toEqual(new Tag(new Fr(2))); - expect(request.source).toEqual(LogSource.PUBLIC_AND_PRIVATE); - expect(request.fromBlock).toBeUndefined(); - expect(request.toBlock).toBeUndefined(); - }); - - it('output of Noir serialization with values deserializes as expected', () => { - const serialized = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000002', - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x000000000000000000000000000000000000000000000000000000000000000a', - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000014', - ].map(Fr.fromHexString); - - const request = LogRetrievalRequest.fromFields(serialized); - - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); - expect(request.tag).toEqual(new Tag(new Fr(2))); - expect(request.source).toEqual(LogSource.PUBLIC); - expect(request.fromBlock).toEqual(BlockNumber(10)); - expect(request.toBlock).toEqual(BlockNumber(20)); - }); - - it('rejects an invalid LogSource value', () => { - const serialized = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000002', - '0x000000000000000000000000000000000000000000000000000000000000002a', // 42 — invalid - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - ].map(Fr.fromHexString); - - expect(() => LogRetrievalRequest.fromFields(serialized)).toThrow(/Invalid LogSource value 42/); - }); - - 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 restored = LogRetrievalRequest.fromFields(fields); - expect(restored.source).toEqual(source); - } - }); - - it('round-trips through toFields and fromFields', () => { - const original = new LogRetrievalRequest( - AztecAddress.fromBigInt(42n), - new Tag(new Fr(99)), - LogSource.PRIVATE, - BlockNumber(5), - BlockNumber(100), - ); - - const restored = LogRetrievalRequest.fromFields(original.toFields()); - - expect(restored.contractAddress).toEqual(original.contractAddress); - expect(restored.tag).toEqual(original.tag); - expect(restored.source).toEqual(original.source); - expect(restored.fromBlock).toEqual(original.fromBlock); - expect(restored.toBlock).toEqual(original.toBlock); - }); -}); 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..3caafdb407c6 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 @@ -1,8 +1,9 @@ -import { BlockNumber } from '@aztec/foundation/branded-types'; +import type { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; -import { FieldReader } from '@aztec/foundation/serialize'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { Tag } from '@aztec/stdlib/logs'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { Tag } from '@aztec/stdlib/logs'; + +import type { Option } from './option.js'; /** Discriminant for which log source to query. */ export enum LogSource { @@ -15,47 +16,20 @@ export enum LogSource { * Intermediate struct used to perform batch log retrieval by PXE. The `utilityBulkRetrieveLogs` oracle expects values of this * type to be stored in a `EphemeralArray`. */ -export class LogRetrievalRequest { - constructor( - public contractAddress: AztecAddress, - public tag: Tag, - public source: LogSource = LogSource.PUBLIC_AND_PRIVATE, - public fromBlock?: BlockNumber, - public toBlock?: BlockNumber, - ) {} - - toFields(): Fr[] { - return [ - this.contractAddress.toField(), - this.tag.value, - new Fr(this.source), - new Fr(this.fromBlock !== undefined ? 1 : 0), - new Fr(this.fromBlock ?? 0), - new Fr(this.toBlock !== undefined ? 1 : 0), - new Fr(this.toBlock ?? 0), - ]; - } - - static fromFields(fields: Fr[] | FieldReader): LogRetrievalRequest { - const reader = FieldReader.asReader(fields); - - const contractAddress = AztecAddress.fromField(reader.readField()); - const tag = new Tag(reader.readField()); - const sourceNum = reader.readField().toNumber(); - if (!(sourceNum in LogSource)) { - const validNames = Object.keys(LogSource).filter(k => isNaN(Number(k))); - throw new Error(`Invalid LogSource value ${sourceNum}, expected one of ${validNames.join(', ')}`); - } - const source = sourceNum as LogSource; - - const fromBlockIsSome = reader.readBoolean(); - const fromBlockValue = reader.readField(); - const fromBlock = fromBlockIsSome ? BlockNumber(fromBlockValue.toNumber()) : undefined; - - const toBlockIsSome = reader.readBoolean(); - const toBlockValue = reader.readField(); - const toBlock = toBlockIsSome ? BlockNumber(toBlockValue.toNumber()) : undefined; - - return new LogRetrievalRequest(contractAddress, tag, source, fromBlock, toBlock); +export type LogRetrievalRequest = { + contractAddress: AztecAddress; + tag: Tag; + source: LogSource; + fromBlock: Option; + toBlock: Option; +}; + +/** Parses a `LogSource` from its wire field, throwing on an out-of-range value. */ +export function logSourceFromField(field: Fr): LogSource { + const sourceNum = field.toNumber(); + if (!(sourceNum in LogSource)) { + const validNames = Object.keys(LogSource).filter(k => isNaN(Number(k))); + throw new Error(`Invalid LogSource value ${sourceNum}, expected one of ${validNames.join(', ')}`); } + return sourceNum as LogSource; } diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.test.ts deleted file mode 100644 index e3bed92a9a10..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { updateInlineTestData } from '@aztec/foundation/testing/files'; -import { TxHash } from '@aztec/stdlib/tx'; - -import { LogRetrievalResponse } from './log_retrieval_response.js'; - -describe('LogRetrievalResponse', () => { - it('serialization of some matches snapshots and output of Noir serialization', () => { - const logPayload = [new Fr(1n), new Fr(2n), new Fr(3n)]; - const txHash = new TxHash(new Fr(4)); - const uniqueNoteHashes = [new Fr(5n), new Fr(6n)]; - const firstNullifier = new Fr(7n); - - const response = new LogRetrievalResponse(logPayload, txHash, uniqueNoteHashes, firstNullifier); - const serialized = LogRetrievalResponse.toSerializedOption(response); - - // Test against snapshot - expect(serialized.map(f => f.toString())).toMatchInlineSnapshot(` - [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000007", - ] - `); - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - const fieldArrayStr = `[${serialized.map(f => f.toString()).join(',')}]`; - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr', - 'serialized_some_log_retrieval_response_from_typescript', - fieldArrayStr, - ); - }); - - it('serialization of some matches snapshots and output of Noir serialization', () => { - const serialized = LogRetrievalResponse.toSerializedOption(null); - - // Test against snapshot - expect(serialized.map(f => f.toString())).toMatchInlineSnapshot(` - [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - ] - `); - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - const fieldArrayStr = `[${serialized.map(f => f.toString()).join(',')}]`; - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/messages/processing/log_retrieval_response.nr', - 'serialized_none_log_retrieval_response_from_typescript', - fieldArrayStr, - ); - }); - - it('serialization length of empty matches', () => { - const logPayload = [new Fr(1n), new Fr(2n), new Fr(3n)]; - const txHash = new TxHash(new Fr(4)); - const uniqueNoteHashes = [new Fr(5n), new Fr(6n)]; - const firstNullifier = new Fr(7n); - - const pendingLog = new LogRetrievalResponse(logPayload, txHash, uniqueNoteHashes, firstNullifier); - - expect(pendingLog.toFields().length).toEqual(LogRetrievalResponse.toEmptyFields().length); - }); -}); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.ts index 590fd28c1c84..2d1df9aa580c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/log_retrieval_response.ts @@ -1,66 +1,13 @@ -import { MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_CIPHERTEXT_LEN } from '@aztec/constants'; -import { range } from '@aztec/foundation/array'; -import { Fr } from '@aztec/foundation/curves/bn254'; +import type { Fr } from '@aztec/foundation/curves/bn254'; import type { TxHash } from '@aztec/stdlib/tx'; -const MAX_LOG_CONTENT_LEN = PRIVATE_LOG_CIPHERTEXT_LEN; - /** * Intermediate struct used to perform batch log retrieval by PXE. The `utilityBulkRetrieveLogs` oracle stores values of this * type in a `EphemeralArray`. */ -export class LogRetrievalResponse { - constructor( - public logPayload: Fr[], - public txHash: TxHash, - public uniqueNoteHashesInTx: Fr[], - public firstNullifierInTx: Fr, - ) {} - - toFields(): Fr[] { - return [ - // We need to trim the payload since public logs can be larger than MAX_LOG_CONTENT_LEN. - // This is currently not a problem since this class is only used with public logs for note completion. - ...serializeBoundedVec(this.logPayload.slice(0, MAX_LOG_CONTENT_LEN), MAX_LOG_CONTENT_LEN), - this.txHash.hash, - ...serializeBoundedVec(this.uniqueNoteHashesInTx, MAX_NOTE_HASHES_PER_TX), - this.firstNullifierInTx, - ]; - } - - static toEmptyFields(): Fr[] { - const serializationLen = - MAX_LOG_CONTENT_LEN + - 1 /* logPayload BVec */ + - 1 /* txHash */ + - MAX_NOTE_HASHES_PER_TX + - 1 /* uniqueNoteHashesInTx BVec */ + - 1; /* firstNullifierInTx */ - return range(serializationLen).map(_ => Fr.zero()); - } - - static toSerializedOption(response: LogRetrievalResponse | null): Fr[] { - if (response) { - return [new Fr(1), ...response.toFields()]; - } else { - return [new Fr(0), ...LogRetrievalResponse.toEmptyFields()]; - } - } -} - -/** - * Helper function to serialize a bounded vector according to Noir's BoundedVec format - * @param values - The values to serialize - * @param maxLength - The maximum length of the bounded vector - * @returns The serialized bounded vector as Fr[] - */ -function serializeBoundedVec(values: Fr[], maxLength: number): Fr[] { - if (values.length > maxLength) { - throw new Error(`Attempted to serialize ${values} values into a BoundedVec with max length ${maxLength}`); - } - - const lengthDiff = maxLength - values.length; - const zeroPaddingArray = Array(lengthDiff).fill(Fr.ZERO); - const storage = values.concat(zeroPaddingArray); - return [...storage, new Fr(values.length)]; -} +export type LogRetrievalResponse = { + logPayload: Fr[]; + txHash: TxHash; + uniqueNoteHashesInTx: Fr[]; + firstNullifierInTx: Fr; +}; 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 deleted file mode 100644 index 6c1b4384b3e6..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/note_validation_request.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { TxHash } from '@aztec/stdlib/tx'; - -import { NoteValidationRequest } from './note_validation_request.js'; - -describe('NoteValidationRequest', () => { - it('output of Noir serialization deserializes as expected', () => { - const serialized = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', // contract address - '0x0000000000000000000000000000000000000000000000000000000000000032', // owner - '0x0000000000000000000000000000000000000000000000000000000000000002', // storage slot - '0x000000000000000000000000000000000000000000000000000000000000002a', // randomness - '0x0000000000000000000000000000000000000000000000000000000000000003', // note nonce - '0x0000000000000000000000000000000000000000000000000000000000000008', // max_note_packed_len - '0x0000000000000000000000000000000000000000000000000000000000000004', // content begin: note content 1 - '0x0000000000000000000000000000000000000000000000000000000000000005', // note content 2 - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', // content end (8 storage fields) - '0x0000000000000000000000000000000000000000000000000000000000000002', // content length - '0x0000000000000000000000000000000000000000000000000000000000000006', // note hash - '0x0000000000000000000000000000000000000000000000000000000000000007', // nullifier - '0x0000000000000000000000000000000000000000000000000000000000000008', // tx hash - ].map(Fr.fromHexString); - - const request = NoteValidationRequest.fromFields(serialized); - - expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n)); - expect(request.owner).toEqual(AztecAddress.fromBigInt(50n)); - expect(request.storageSlot).toEqual(new Fr(2)); - expect(request.randomness).toEqual(new Fr(42)); - expect(request.noteNonce).toEqual(new Fr(3)); - expect(request.content).toEqual([new Fr(4), new Fr(5)]); - expect(request.noteHash).toEqual(new Fr(6)); - expect(request.nullifier).toEqual(new Fr(7)); - expect(request.txHash).toEqual(TxHash.fromBigInt(8n)); - }); - - it('throws if fed more fields than expected', () => { - const serialized = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', // contract address - '0x0000000000000000000000000000000000000000000000000000000000000032', // owner - '0x0000000000000000000000000000000000000000000000000000000000000002', // storage slot - '0X000000000000000000000000000000000000000000000000000000000000002a', // randomness - '0x0000000000000000000000000000000000000000000000000000000000000003', // note nonce - '0x0000000000000000000000000000000000000000000000000000000000000008', // max_note_packed_len (8) - '0x0000000000000000000000000000000000000000000000000000000000000004', // content begin: note content 1 - '0x0000000000000000000000000000000000000000000000000000000000000005', // note content 2 - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', // extra field beyond max_note_packed_len - '0x0000000000000000000000000000000000000000000000000000000000000002', // content length - '0x0000000000000000000000000000000000000000000000000000000000000006', // note hash - '0x0000000000000000000000000000000000000000000000000000000000000007', // nullifier - '0x0000000000000000000000000000000000000000000000000000000000000008', // tx hash - ].map(Fr.fromHexString); - - expect(() => NoteValidationRequest.fromFields(serialized)).toThrow( - 'Error converting array of fields to NoteValidationRequest: expected 18 fields but received 19 (maxNotePackedLen=8).', - ); - }); -}); 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/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/noir-structs/provided_secret.test.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.test.ts deleted file mode 100644 index c884a78f31bd..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { AppTaggingSecretKind } from '@aztec/stdlib/logs'; - -import { ProvidedSecret } from './provided_secret.js'; - -describe('ProvidedSecret', () => { - it('deserializes delivery mode 2 as unconstrained', () => { - const provided = ProvidedSecret.fromFields([new Fr(42), new Fr(2)]); - expect(provided.secret).toEqual(new Fr(42)); - expect(provided.mode).toEqual(AppTaggingSecretKind.UNCONSTRAINED); - }); - - it('deserializes delivery mode 3 as constrained', () => { - const provided = ProvidedSecret.fromFields([new Fr(42), new Fr(3)]); - expect(provided.secret).toEqual(new Fr(42)); - expect(provided.mode).toEqual(AppTaggingSecretKind.CONSTRAINED); - }); - - it('rejects an invalid mode value', () => { - expect(() => ProvidedSecret.fromFields([Fr.random(), new Fr(99)])).toThrow( - 'Unrecognized delivery mode for tagging', - ); - }); -}); diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.ts index 4e06ff6a9e83..f874e746cb2d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/provided_secret.ts @@ -1,16 +1,5 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { FieldReader } from '@aztec/foundation/serialize'; -import { AppTaggingSecretKind, appTaggingSecretKindFromDeliveryMode } from '@aztec/stdlib/logs'; +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { AppTaggingSecretKind } from '@aztec/stdlib/logs'; /** A tagging secret an app supplies explicitly to `getPendingTaggedLogs` when PXE cannot derive it internally. */ -export class ProvidedSecret { - constructor( - public secret: Fr, - public mode: AppTaggingSecretKind, - ) {} - - static fromFields(fields: Fr[] | FieldReader): ProvidedSecret { - const reader = FieldReader.asReader(fields); - return new ProvidedSecret(reader.readField(), appTaggingSecretKindFromDeliveryMode(reader.readField().toNumber())); - } -} +export type ProvidedSecret = { secret: Fr; mode: AppTaggingSecretKind }; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/tx_effect_data.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/tx_effect_data.ts new file mode 100644 index 000000000000..11b131d97894 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/tx_effect_data.ts @@ -0,0 +1,15 @@ +import type { FieldsOf } from '@aztec/foundation/types'; +import type { FlatPublicLogs } from '@aztec/stdlib/logs'; +import type { TxEffect } from '@aztec/stdlib/tx'; + +import type { ContractClassLogData } from './contract_class_log_data.js'; + +/** + * Wire form of a {@link TxEffect} as the `getTxEffect` oracle returns it: identical to the domain type except its logs + * are flattened to their Noir layout. Keeping the conversion in the handler lets the `TX_EFFECT` mapping stay purely + * structural. + */ +export type TxEffectData = Omit, 'publicLogs' | 'contractClassLogs'> & { + publicLogs: FlatPublicLogs; + contractClassLogs: ContractClassLogData[]; +}; diff --git a/yarn-project/pxe/src/contract_function_simulator/noir-structs/utility_context.ts b/yarn-project/pxe/src/contract_function_simulator/noir-structs/utility_context.ts index 6faa0bbd7f4b..5891b2c9ec2b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/noir-structs/utility_context.ts +++ b/yarn-project/pxe/src/contract_function_simulator/noir-structs/utility_context.ts @@ -1,24 +1,11 @@ -import { toACVMField } from '@aztec/simulator/client'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockHeader } from '@aztec/stdlib/tx'; /** * TypeScript counterpart of utility_context.nr. Used only as a return value for the utilityGetUtilityContext oracle. */ -export class UtilityContext { - constructor( - public readonly blockHeader: BlockHeader, - public readonly contractAddress: AztecAddress, - public readonly msgSender: AztecAddress, - ) {} - - /** - * Returns a representation of the utility context as expected by intrinsic Noir deserialization. - * The order of the fields has to be the same as the order of the fields in the utility_context.nr. - */ - public toNoirRepresentation(): (string | string[])[] { - // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js - const blockHeaderFields = this.blockHeader.toFields().map(toACVMField); - return [...blockHeaderFields, this.contractAddress.toString() as string, this.msgSender.toString() as string]; - } -} +export type UtilityContext = { + blockHeader: BlockHeader; + contractAddress: AztecAddress; + msgSender: AztecAddress; +}; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/message_load_oracle_inputs.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/message_load_oracle_inputs.ts index 1ab274c93e83..70f70068c680 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/message_load_oracle_inputs.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/message_load_oracle_inputs.ts @@ -1,23 +1,8 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; import type { SiblingPath } from '@aztec/foundation/trees'; -export class MessageLoadOracleInputs { - constructor( - /** The index of the message commitment in the merkle tree. */ - public index: bigint, - /** The path in the merkle tree to the message. */ - public siblingPath: SiblingPath, - ) {} - - toFields(): Fr[] { - return [new Fr(this.index), ...this.siblingPath.toFields()]; - } - - /** - * Returns a representation of the public data witness as expected by intrinsic Noir deserialization. - */ - public toNoirRepresentation(): (string | string[])[] { - // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js - return [new Fr(this.index).toString() as string, this.siblingPath.toFields().map(fr => fr.toString()) as string[]]; - } -} +export type MessageLoadOracleInputs = { + /** The index of the message commitment in the merkle tree. */ + index: bigint; + /** The path in the merkle tree to the message. */ + siblingPath: SiblingPath; +}; 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_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..2694485e3abe 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 @@ -15,7 +15,7 @@ import { BUFFER, BYTE, CALL_PRIVATE_RESULT, - CONTRACT_CLASS_LOG_INPUT, + CONTRACT_CLASS_LOG, CONTRACT_INSTANCE, DELIVERY_MODE, EPHEMERAL_ARRAY, @@ -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, 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, }), @@ -285,8 +292,8 @@ export const ORACLE_REGISTRY = { aztec_utl_decryptAes128: makeEntry({ params: [ { name: 'ciphertext', type: BOUNDED_VEC(BYTE) }, - { name: 'iv', type: BUFFER(8) }, - { name: 'symKey', type: BUFFER(8) }, + { name: 'iv', type: BUFFER(8, 16) }, + { name: 'symKey', type: BUFFER(8, 16) }, ], returnType: OPTION(BOUNDED_VEC(BYTE)), }), @@ -456,7 +463,7 @@ export const ORACLE_REGISTRY = { aztec_prv_notifyCreatedContractClassLog: makeEntry({ params: [ - { name: 'log', type: CONTRACT_CLASS_LOG_INPUT }, + { name: 'log', type: CONTRACT_CLASS_LOG }, { name: 'counter', type: U32 }, ], }), @@ -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) { @@ -569,7 +582,7 @@ export function makeEntry { +interface RegistryParam { name: TName; type: TypeMapping; } @@ -602,7 +615,7 @@ type InferDeserializedParams = { // ─── Derived Handler Interfaces ───────────────────────────────────────────── /** Strips the `aztec_{scope}_` prefix from an oracle key to get the handler method name. */ -export type StripOraclePrefix = K extends `aztec_${string}_${infer M}` ? M : never; +type StripOraclePrefix = K extends `aztec_${string}_${infer M}` ? M : never; /** Derives the handler function signature from a registry entry's deserialization/serialization types. */ type HandlerFn = ( 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..ee6405fd1b3f --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_type_mappings.test.ts @@ -0,0 +1,346 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; +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 { type LogRetrievalRequest, LogSource } 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, + PROVIDED_SECRET, + 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('PROVIDED_SECRET', () => { + it('deserializes a secret and delivery mode from a two-field slot', () => { + const provided = PROVIDED_SECRET.deserialization!.fn([new FieldReader([new Fr(42), new Fr(3)])]); + expect(provided.secret).toEqual(new Fr(42)); + expect(provided.mode).toBe(AppTaggingSecretKind.CONSTRAINED); + }); + + it('declares the two-field wire shape', () => { + expect(PROVIDED_SECRET.shape).toEqual([{ len: 2 }]); + }); + }); + + 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', () => { + const data = [ + { x: Fr.random(), y: Fr.random() }, + { x: Fr.random(), y: Fr.random() }, + ]; + const out = roundTrip(BOUNDED_VEC(POINT), BoundedVec.from({ data, maxLength: 3 })); + 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 }); + 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 = toRow(LOG_RETRIEVAL_REQUEST, { + contractAddress: await AztecAddress.random(), + tag: new Tag(Fr.random()), + source: LogSource.PUBLIC_AND_PRIVATE, + fromBlock: Option.none(), + toBlock: Option.none(), + }); + 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..7debfdfcac08 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_CLASS_LOG_SIZE_IN_FIELDS, + FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH, L1_TO_L2_MSG_TREE_HEIGHT, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, @@ -8,48 +8,84 @@ import { MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, + PRIVATE_LOG_CIPHERTEXT_LEN, + PRIVATE_LOG_SIZE_IN_FIELDS, + PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/constants'; -import { BlockNumber } from '@aztec/foundation/branded-types'; +import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; -import { Point } from '@aztec/foundation/curves/grumpkin'; +import type { EthAddress } from '@aztec/foundation/eth-address'; import { FieldReader } from '@aztec/foundation/serialize'; -import { MembershipWitness } from '@aztec/foundation/trees'; +import { MembershipWitness, type SiblingPath } from '@aztec/foundation/trees'; import { type ACVMField, fromUintArray } from '@aztec/simulator/client'; import { FunctionSelector, NoteSelector } from '@aztec/stdlib/abi'; -import { PublicDataWrite } from '@aztec/stdlib/avm'; +import { PublicDataWrite, RevertCode } from '@aztec/stdlib/avm'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash } from '@aztec/stdlib/block'; import type { ContractInstance, PartialAddress } from '@aztec/stdlib/contract'; +import type { GasFees } from '@aztec/stdlib/gas'; import { KeyValidationRequest } from '@aztec/stdlib/kernel'; import type { PublicKeys } from '@aztec/stdlib/keys'; import { type AppTaggingSecretKind, - ContractClassLog, - ContractClassLogFields, - FlatPublicLogs, - MessageContext, - PendingTaggedLog, + type FlatPublicLogs, + type MessageContext, + type PendingTaggedLog, PrivateLog, Tag, appTaggingSecretKindFromDeliveryMode, } from '@aztec/stdlib/logs'; -import { NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; -import { BlockHeader, TxEffect, TxHash } from '@aztec/stdlib/tx'; +import { + type AppendOnlyTreeSnapshot, + type NullifierLeaf, + type NullifierLeafPreimage, + NullifierMembershipWitness, + type PublicDataTreeLeaf, + type PublicDataTreeLeafPreimage, + PublicDataWitness, +} from '@aztec/stdlib/trees'; +import { + BlockHeader, + type GlobalVariables, + type PartialStateReference, + type StateReference, + TxHash, +} from '@aztec/stdlib/tx'; import { BoundedVec } from '../noir-structs/bounded_vec.js'; +import type { ContractClassLogData } from '../noir-structs/contract_class_log_data.js'; +import type { EmbeddedCurvePoint } from '../noir-structs/embedded_curve_point.js'; import { EphemeralArray } from '../noir-structs/ephemeral_array.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; -import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; -import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; +import { type LogRetrievalRequest, type LogSource, logSourceFromField } from '../noir-structs/log_retrieval_request.js'; +import type { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import type { NoteData } from '../noir-structs/note_data.js'; import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import { Option } from '../noir-structs/option.js'; -import { ProvidedSecret } from '../noir-structs/provided_secret.js'; -import { UtilityContext } from '../noir-structs/utility_context.js'; -import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; +import type { ProvidedSecret } from '../noir-structs/provided_secret.js'; +import type { TxEffectData } from '../noir-structs/tx_effect_data.js'; +import type { UtilityContext } from '../noir-structs/utility_context.js'; +import type { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; import { packAsHintedNote } from './note_packing_utils.js'; +// `ORACLE_REGISTRY` infers its entry signatures from these mappings, so its emitted declaration (and the oracle +// interfaces derived from it) names the protocol types below. Re-exporting them gives tsc a portable path to each +// type instead of falling back to a deep node_modules path that breaks .d.ts portability (TS2742). +export type { + AppTaggingSecretKind, + BlockHash, + BlockHeader, + ContractInstance, + MembershipWitness, + MessageContext, + NullifierMembershipWitness, + PendingTaggedLog, + PublicDataWitness, + TxHash, +}; + // ─── Core Types ────────────────────────────────────────────────────────────── /** One ACVM input slot: an array of hex-encoded field strings. */ @@ -58,6 +94,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 +116,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` → `['scalar', { len: N }, 'scalar']` // [addr], [fields], [len] + */ + shape: SlotShape[]; } export type MaybePromise = T | Promise; @@ -101,12 +149,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 +169,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 +190,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,118 +219,187 @@ 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.fromFieldUnsafe(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 = { +const TAG: TypeMapping = { serialization: { fn: v => [v.value] }, - deserialization: { fn: ([reader]) => new Tag(reader.readField()), slots: 1 }, -}; - -export const POINT: TypeMapping = { - serialization: { fn: p => [p.toFields()] }, - deserialization: { - fn: ([reader]) => Point.fromFields([reader.readField(), reader.readField()]), - slots: 1, - }, + deserialization: { fn: ([reader]) => new Tag(reader.readField()) }, + shape: ['scalar'], }; -// ─── Struct Type Mappings ──────────────────────────────────────────────────── +export const POINT: TypeMapping = STRUCT([ + { name: 'x', type: FIELD }, + { name: 'y', type: FIELD }, +]); -export const BLOCK_HEADER: TypeMapping = { - serialization: { fn: v => v.toFields() }, - deserialization: { fn: ([reader]) => BlockHeader.fromFields(reader.readFieldArray(BLOCK_HEADER_LENGTH)), slots: 1 }, -}; - -export const KEY_VALIDATION_REQUEST: TypeMapping = { - serialization: { fn: v => v.toFields() }, - deserialization: { - fn: ([reader]) => KeyValidationRequest.fromFields(reader.readFieldArray(KEY_VALIDATION_REQUEST_LENGTH)), - slots: 1, - }, -}; - -export const CONTRACT_INSTANCE: TypeMapping = { - serialization: { - fn: v => [ - v.salt, - v.deployer.toField(), - // Note that the nr side of this struct does not contain the current class, only original - v.originalContractClassId, - v.initializationHash, - v.immutablesHash, - ...v.publicKeys.toFields(), - ], - }, -}; - -export const NULLIFIER_MEMBERSHIP_WITNESS: TypeMapping = { - serialization: { - fn: (w: NullifierMembershipWitness) => - w - .toNoirRepresentation() - .map(slot => (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), - }, -}; - -export const PUBLIC_DATA_WITNESS: TypeMapping = { - serialization: { - fn: (w: PublicDataWitness) => - w - .toNoirRepresentation() - .map(slot => (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), - }, -}; - -export const MESSAGE_LOAD_ORACLE_INPUTS: TypeMapping> = { - serialization: { - fn: (m: MessageLoadOracleInputs) => - m - .toNoirRepresentation() - .map(slot => (Array.isArray(slot) ? slot.map(s => Fr.fromString(s)) : Fr.fromString(slot as string))), - }, +const LOG_SOURCE: TypeMapping = { + serialization: { fn: v => [new Fr(v)] }, + deserialization: { fn: ([reader]) => logSourceFromField(reader.readField()) }, + shape: ['scalar'], }; -export const UTILITY_CONTEXT: TypeMapping = { - serialization: { - fn: (ctx: UtilityContext) => [ - ...ctx.blockHeader.toFields(), - ctx.contractAddress.toField(), - ctx.msgSender.toField(), - ], - }, +const ETH_ADDRESS: TypeMapping = { + serialization: { fn: v => [v.toField()] }, + shape: ['scalar'], }; +const SLOT_NUMBER: TypeMapping = { + serialization: { fn: v => [new Fr(v)] }, + shape: ['scalar'], +}; + +const APPEND_ONLY_TREE_SNAPSHOT: TypeMapping = STRUCT([ + { name: 'root', type: FIELD }, + { name: 'nextAvailableLeafIndex', type: U32 }, +]); + +const PARTIAL_STATE_REFERENCE: TypeMapping = STRUCT([ + { name: 'noteHashTree', type: APPEND_ONLY_TREE_SNAPSHOT }, + { name: 'nullifierTree', type: APPEND_ONLY_TREE_SNAPSHOT }, + { name: 'publicDataTree', type: APPEND_ONLY_TREE_SNAPSHOT }, +]); + +const STATE_REFERENCE: TypeMapping = STRUCT([ + { name: 'l1ToL2MessageTree', type: APPEND_ONLY_TREE_SNAPSHOT }, + { name: 'partial', type: PARTIAL_STATE_REFERENCE }, +]); + +const GAS_FEES: TypeMapping = STRUCT([ + { name: 'feePerDaGas', type: BIGINT }, + { name: 'feePerL2Gas', type: BIGINT }, +]); + +const GLOBAL_VARIABLES: TypeMapping = STRUCT([ + { name: 'chainId', type: FIELD }, + { name: 'version', type: FIELD }, + { name: 'blockNumber', type: BLOCK_NUMBER }, + { name: 'slotNumber', type: SLOT_NUMBER }, + { name: 'timestamp', type: BIGINT }, + { name: 'coinbase', type: ETH_ADDRESS }, + { name: 'feeRecipient', type: AZTEC_ADDRESS }, + { name: 'gasFees', type: GAS_FEES }, +]); + +export const BLOCK_HEADER: TypeMapping = STRUCT([ + { name: 'lastArchive', type: APPEND_ONLY_TREE_SNAPSHOT }, + { name: 'state', type: STATE_REFERENCE }, + { name: 'spongeBlobHash', type: FIELD }, + { name: 'globalVariables', type: GLOBAL_VARIABLES }, + { name: 'totalFees', type: FIELD }, + { name: 'totalManaUsed', type: FIELD }, +]); + +export const KEY_VALIDATION_REQUEST: TypeMapping = STRUCT([ + { name: 'pkMHash', type: FIELD }, + { name: 'skApp', type: FIELD }, +]); + +const PUBLIC_KEYS: TypeMapping = STRUCT([ + { name: 'npkMHash', type: FIELD }, + { name: 'ivpkM', type: POINT }, + { name: 'ovpkMHash', type: FIELD }, + { name: 'tpkMHash', type: FIELD }, + { name: 'mspkMHash', type: FIELD }, + { name: 'fbpkMHash', type: FIELD }, +]); + +export const CONTRACT_INSTANCE: TypeMapping = STRUCT([ + { name: 'salt', type: FIELD }, + { name: 'deployer', type: AZTEC_ADDRESS }, + // Note that the nr side of this struct does not contain the current class, only original + { name: 'originalContractClassId', type: FIELD }, + { name: 'initializationHash', type: FIELD }, + { name: 'immutablesHash', type: FIELD }, + { name: 'publicKeys', type: PUBLIC_KEYS }, +]); + +const NULLIFIER_LEAF: TypeMapping = STRUCT([{ name: 'nullifier', type: FIELD }]); + +const NULLIFIER_LEAF_PREIMAGE: TypeMapping = STRUCT([ + { name: 'leaf', type: NULLIFIER_LEAF }, + { name: 'nextKey', type: FIELD }, + { name: 'nextIndex', type: BIGINT }, +]); + +export const NULLIFIER_MEMBERSHIP_WITNESS: TypeMapping = STRUCT( + [ + { name: 'leafPreimage', type: NULLIFIER_LEAF_PREIMAGE }, + { name: 'index', type: BIGINT }, + { name: 'siblingPath', type: SIBLING_PATH(NULLIFIER_TREE_HEIGHT) }, + ], +); + +const PUBLIC_DATA_LEAF: TypeMapping = STRUCT([ + { name: 'slot', type: FIELD }, + { name: 'value', type: FIELD }, +]); + +const PUBLIC_DATA_LEAF_PREIMAGE: TypeMapping = STRUCT([ + { name: 'leaf', type: PUBLIC_DATA_LEAF }, + { name: 'nextKey', type: FIELD }, + { name: 'nextIndex', type: BIGINT }, +]); + +export const PUBLIC_DATA_WITNESS: TypeMapping = STRUCT([ + { name: 'index', type: BIGINT }, + { name: 'leafPreimage', type: PUBLIC_DATA_LEAF_PREIMAGE }, + { name: 'siblingPath', type: SIBLING_PATH(PUBLIC_DATA_TREE_HEIGHT) }, +]); + +export const MESSAGE_LOAD_ORACLE_INPUTS: TypeMapping> = STRUCT< + MessageLoadOracleInputs +>([ + { name: 'index', type: BIGINT }, + { name: 'siblingPath', type: SIBLING_PATH(L1_TO_L2_MSG_TREE_HEIGHT) }, +]); + +export const UTILITY_CONTEXT: TypeMapping = STRUCT([ + { name: 'blockHeader', type: BLOCK_HEADER }, + { name: 'contractAddress', type: AZTEC_ADDRESS }, + { name: 'msgSender', type: AZTEC_ADDRESS }, +]); + +// TODO: `call_private_function.nr` returns `[Field; 2]`, so this has to be a single array slot. If the oracle returned +// a struct/tuple instead (destructured to 2 scalars), this could be `STRUCT`. export const CALL_PRIVATE_RESULT: TypeMapping<{ endSideEffectCounter: Fr; returnsHash: Fr }> = { serialization: { fn: v => [[v.endSideEffectCounter, v.returnsHash]] }, + shape: [{ len: 2 }], }; +// TODO: `getPublicKeysAndPartialAddress` returns `Option<[Field; 8]>`, so this has to be a single array slot. If the +// oracle returned `(PublicKeys, PartialAddress)` destructured instead, this could be `STRUCT`. export const PUBLIC_KEYS_AND_PARTIAL_ADDRESS: TypeMapping<{ publicKeys: PublicKeys; partialAddress: PartialAddress; @@ -286,50 +407,59 @@ 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 length = lengthReader.readField().toNumber(); - return new ContractClassLog(addr, fields, length); - }, - // ContractClassLog input occupies 3 ACVM slots: [contractAddress], [message fields...], [length]. - slots: 3, - }, -}; +export const CONTRACT_CLASS_LOG: TypeMapping = STRUCT([ + { name: 'contractAddress', type: AZTEC_ADDRESS }, + { name: 'fields', type: FIXED_ARRAY(FIELD, CONTRACT_CLASS_LOG_SIZE_IN_FIELDS) }, + { name: 'emittedLength', type: U32 }, +]); -export const TX_EFFECT: TypeMapping = { - serialization: { - fn: (effect: TxEffect) => { - const flatPublicLogs = FlatPublicLogs.fromLogs(effect.publicLogs); - return [ - effect.revertCode.toField(), - effect.txHash.hash, - effect.transactionFee, - padArrayEnd(effect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), - padArrayEnd(effect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX), - padArrayEnd(effect.l2ToL1Msgs, Fr.ZERO, MAX_L2_TO_L1_MSGS_PER_TX), - padArrayEnd( - effect.publicDataWrites, - PublicDataWrite.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ).flatMap(w => w.toFields()), - padArrayEnd(effect.privateLogs, PrivateLog.empty(), MAX_PRIVATE_LOGS_PER_TX).flatMap(l => l.toFields()), - new Fr(flatPublicLogs.length), - flatPublicLogs.payload, - padArrayEnd(effect.contractClassLogs, ContractClassLog.empty(), MAX_CONTRACT_CLASS_LOGS_PER_TX).flatMap(l => [ - ...l.fields.toFields(), - new Fr(l.emittedLength), - l.contractAddress.toField(), - ]), - ] as (Fr | Fr[])[]; - }, - }, +const REVERT_CODE: TypeMapping = { + serialization: { fn: rc => [rc.toField()] }, + shape: ['scalar'], }; +const PUBLIC_DATA_WRITE: TypeMapping = STRUCT([ + { name: 'leafSlot', type: FIELD }, + { name: 'value', type: FIELD }, +]); + +const PRIVATE_LOG: TypeMapping = STRUCT([ + { name: 'fields', type: FIXED_ARRAY(FIELD, PRIVATE_LOG_SIZE_IN_FIELDS) }, + { name: 'emittedLength', type: U32 }, +]); + +const FLAT_PUBLIC_LOGS: TypeMapping = STRUCT([ + { name: 'length', type: U32 }, + { name: 'payload', type: FIXED_ARRAY(FIELD, FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH) }, +]); + +const CONTRACT_CLASS_LOG_ENTRY: TypeMapping = STRUCT([ + { name: 'fields', type: FIXED_ARRAY(FIELD, CONTRACT_CLASS_LOG_SIZE_IN_FIELDS) }, + { name: 'emittedLength', type: U32 }, + { name: 'contractAddress', type: AZTEC_ADDRESS }, +]); + +const CONTRACT_CLASS_LOGS: TypeMapping = FIXED_ARRAY( + CONTRACT_CLASS_LOG_ENTRY, + MAX_CONTRACT_CLASS_LOGS_PER_TX, +); + +export const TX_EFFECT: TypeMapping = STRUCT([ + { name: 'revertCode', type: REVERT_CODE }, + { name: 'txHash', type: TX_HASH }, + { name: 'transactionFee', type: FIELD }, + { name: 'noteHashes', type: FIXED_ARRAY(FIELD, MAX_NOTE_HASHES_PER_TX) }, + { name: 'nullifiers', type: FIXED_ARRAY(FIELD, MAX_NULLIFIERS_PER_TX) }, + { name: 'l2ToL1Msgs', type: FIXED_ARRAY(FIELD, MAX_L2_TO_L1_MSGS_PER_TX) }, + { name: 'publicDataWrites', type: FIXED_ARRAY(PUBLIC_DATA_WRITE, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) }, + { name: 'privateLogs', type: FIXED_ARRAY(PRIVATE_LOG, MAX_PRIVATE_LOGS_PER_TX) }, + { name: 'publicLogs', type: FLAT_PUBLIC_LOGS }, + { name: 'contractClassLogs', type: CONTRACT_CLASS_LOGS }, +]); + export const NOTE: TypeMapping = { serialization: { fn: noteData => @@ -343,79 +473,108 @@ export const NOTE: TypeMapping = { note: noteData.note, }), }, -}; - -export const PENDING_TAGGED_LOG: TypeMapping = { - serialization: { fn: log => [log.toFields()] }, + // 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 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, - }, -}; - -export const LOG_RETRIEVAL_REQUEST: TypeMapping = { - serialization: { fn: req => [req.toFields()] }, - deserialization: { - fn: ([reader]) => LogRetrievalRequest.fromFields(reader), - slots: 1, }, -}; - -export const LOG_RETRIEVAL_RESPONSE: TypeMapping = { - serialization: { fn: resp => [resp.toFields()] }, -}; - -export const MESSAGE_CONTEXT: TypeMapping = { - serialization: { fn: mc => [mc.toFields()] }, -}; + shape: ['variable'], +}; + +export const LOG_RETRIEVAL_REQUEST: TypeMapping = STRUCT([ + { name: 'contractAddress', type: AZTEC_ADDRESS }, + { name: 'tag', type: TAG }, + { name: 'source', type: LOG_SOURCE }, + { name: 'fromBlock', type: OPTION(BLOCK_NUMBER) }, + { name: 'toBlock', type: OPTION(BLOCK_NUMBER) }, +]); + +export const LOG_RETRIEVAL_RESPONSE: TypeMapping = STRUCT([ + { name: 'logPayload', type: FIXED_BOUNDED_VEC(FIELD, PRIVATE_LOG_CIPHERTEXT_LEN) }, + { name: 'txHash', type: TX_HASH }, + { name: 'uniqueNoteHashesInTx', type: FIXED_BOUNDED_VEC(FIELD, MAX_NOTE_HASHES_PER_TX) }, + { name: 'firstNullifierInTx', type: FIELD }, +]); + +export const MESSAGE_CONTEXT: TypeMapping = STRUCT([ + { name: 'txHash', type: TX_HASH }, + { name: 'uniqueNoteHashesInTx', type: FIXED_BOUNDED_VEC(FIELD, MAX_NOTE_HASHES_PER_TX) }, + { name: 'firstNullifierInTx', type: FIELD }, +]); + +export const PENDING_TAGGED_LOG: TypeMapping = STRUCT([ + { name: 'log', type: FIXED_BOUNDED_VEC(FIELD, PRIVATE_LOG_SIZE_IN_FIELDS) }, + { name: 'context', type: MESSAGE_CONTEXT }, +]); export const PROVIDED_SECRET: TypeMapping = { deserialization: { - fn: ([reader]) => ProvidedSecret.fromFields(reader), - slots: 1, + fn: ([reader]) => ({ + secret: reader.readField(), + mode: appTaggingSecretKindFromDeliveryMode(BYTE.deserialization!.fn([reader])), + }), }, + 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> { +function SIBLING_PATH(height: N): TypeMapping> { return { - serialization: { - fn: (witness: MembershipWitness) => [new Fr(witness.leafIndex), [...witness.siblingPath]], - }, + serialization: { fn: sp => [sp.toFields()] }, + shape: [{ len: height }], }; } +export function MEMBERSHIP_WITNESS(height: N): TypeMapping> { + return STRUCT>([ + { name: 'leafIndex', type: BIGINT }, + { name: 'siblingPath', type: FIXED_ARRAY(FIELD, height) }, + ]); +} + export function ARRAY(inner: TypeMapping): TypeMapping & { kind: 'array'; inner: TypeMapping } { return { kind: 'array', inner, - serialization: inner.serialization - ? { fn: values => [values.flatMap(v => inner.serialization!.fn(v).flat())] } - : undefined, + serialization: inner.serialization ? { fn: values => [packElements(inner, values)] } : undefined, deserialization: inner.deserialization ? { - fn: ([reader]) => { - const result: T[] = []; - while (!reader.isFinished()) { - result.push(inner.deserialization!.fn([reader])); - } - return result; - }, - slots: 1, + // The whole slot is the array, so read as many elements as its fields hold. + fn: ([reader]) => unpackElements(inner, reader, reader.remainingFields() / fieldWidth(inner.shape)), } : undefined, + // One slot of variable length (all elements flattened into it). + shape: [{ lenFrom: (size: { length: number }) => size.length * fieldWidth(inner.shape) }], + }; +} + +/** + * Noir's fixed-length array `[T; maxLength]` in one slot: serializes `values` (each flattened via `element`) + * zero-padded to exactly `maxLength * elementWidth` fields, and deserializes all `maxLength` elements back. An absent + * element is the zero encoding, so the padding is derived from the shape. + */ +function FIXED_ARRAY(element: TypeMapping, maxLength: number): TypeMapping { + const elementWidth = fieldWidth(element.shape); + return { + serialization: element.serialization + ? { fn: values => [padArrayEnd(packElements(element, values), Fr.ZERO, maxLength * elementWidth)] } + : undefined, + deserialization: element.deserialization + ? { fn: ([reader]) => unpackElements(element, reader, maxLength) } + : undefined, + shape: [{ len: maxLength * elementWidth }], }; } @@ -444,45 +603,71 @@ export function BOUNDED_VEC( if (bv.data.length > bv.maxLength) { throw new Error(`Got ${bv.data.length} items, but maxLength is ${bv.maxLength}`); } - const flat = bv.data.flatMap(item => inner.serialization!.fn(item).flat()); - return [padArrayEnd(flat, Fr.ZERO, bv.maxLength * bv.elementSize), new Fr(bv.data.length)]; + // Fixed-width elements take their width from the shape (consistent with deserialization); only a + // variable-width element (e.g. a packed note) falls back to the runtime `elementSize` the value carries. + const elementWidth = tryFieldWidth(inner.shape) ?? bv.elementSize; + const flat = padArrayEnd(packElements(inner, bv.data), Fr.ZERO, bv.maxLength * elementWidth); + return [flat, new Fr(bv.data.length)]; }, } : undefined, 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, then drain the trailing zero-padding so the storage reader is fully consumed. + const maxLength = storageReader.remainingFields() / fieldWidth(inner.shape); const length = lengthReader.readField().toNumber(); - const elements: T[] = []; - for (let i = 0; i < length; i++) { - elements.push(inner.deserialization!.fn([storageReader])); - } - // Drain the trailing zero-padding (maxLength - length unused element slots) so the storage reader is - // fully consumed. + const elements = unpackElements(inner, storageReader, length); 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. + * Noir's `BoundedVec` in its padded form (one fixed-width slot): `maxLength * elementWidth` storage fields + * (zero-padded) followed by the actual length, with no length prefix, so the width is statically known. Serialize-only. + * Throws if the input exceeds `maxLength`. + */ +function FIXED_BOUNDED_VEC(element: TypeMapping, maxLength: number): TypeMapping { + const width = fieldWidth(element.shape); + return { + serialization: element.serialization + ? { + fn: values => { + if (values.length > maxLength) { + throw new Error(`Got ${values.length} items, but maxLength is ${maxLength}`); + } + const flat = padArrayEnd(packElements(element, values), Fr.ZERO, maxLength * width); + return [[...flat, new Fr(values.length)]]; + }, + } + : undefined, + shape: [{ len: maxLength * width + 1 }], + }; +} + +/** + * Wraps an inner TypeMapping in Noir-style `Option`, adding a leading discriminant slot. * - * @example Serializing `Option.some(AztecAddress.fromField(Fr(42)))` with `OPTION(AZTEC_ADDRESS)`: + * 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.fromFieldUnsafe(Fr(42)))` with `OPTION(AZTEC_ADDRESS)`: * ``` * slot 0: Fr(1) // discriminant: Some * 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,76 +676,215 @@ 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], }; } -/** A packed uint buffer (e.g. `[u8; N]` in Noir): 1 slot of packed uint values ↔ `Buffer`. */ -export function BUFFER(bitSize: number): TypeMapping { +/** + * A fixed packed uint buffer (Noir `[u8; length]` at `bitSize` 8): one slot of `length` packed uint values ↔ `Buffer`. + * `length` is the field count, which equals the byte count at `bitSize` 8. + */ +export function BUFFER(bitSize: number, length: number): TypeMapping { return { serialization: { fn: buf => [Array.from(buf).map(b => new Fr(b))], }, deserialization: { - fn: ([reader]) => { - const fields = reader.readFieldArray(reader.remainingFields()).map(f => f.toString()); - return fromUintArray(fields, bitSize); - }, - slots: 1, + fn: ([reader]) => + fromUintArray( + reader.readFieldArray(length).map(f => f.toString()), + bitSize, + ), }, + shape: [{ len: length }], }; } 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 => { - const value = element.deserialization!.fn(readers); - assertReadersConsumed(readers); - return value; - }, - slots: element.deserialization.slots, + fn: ([rowReader]) => deserializeElement(element, rowReader.readFieldArray(rowReader.remainingFields())), }, + // `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 { serialization: element.serialization - ? { fn: ea => [ea.materializeSlot(v => element.serialization!.fn(v).flat() as Fr[])] } + ? { fn: ea => [ea.materializeSlot(v => serializeElement(element, v))] } : 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'], + }; +} + +/** A named field within a {@link STRUCT}: it owns however many wire slots its {@link TypeMapping} declares. */ +type StructField = { name: TName; type: TypeMapping }; + +/** + * A Noir struct: its `shape` and (de)serialization are the concatenation of its fields', so callers never hand-write a + * `shape`. `T` is the struct's TS value type and must match the field layout — serialization reads each field by name + * off the value, deserialization returns the decoded bag as `T`; convert in the handler, not here, when `T` differs. + */ +function STRUCT(fields: readonly StructField[]): TypeMapping { + return { + serialization: fields.every(f => f.type.serialization) + ? { + fn: (value: T) => { + const props = value as any; + return fields.flatMap(f => f.type.serialization!.fn(props[f.name])); + }, + } + : undefined, + deserialization: fields.every(f => f.type.deserialization) + ? { + fn: readers => { + const props: Record = {}; + let slot = 0; + for (const f of fields) { + const slotCount = f.type.shape.length; + props[f.name] = f.type.deserialization!.fn(readers.slice(slot, slot + slotCount)); + slot += slotCount; + } + return props as T; + }, + } : undefined, + shape: fields.flatMap(f => f.type.shape), }; } + +/** 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, or `undefined` if any slot is variable-width. */ +function tryFieldWidth(shape: SlotShape[]): number | undefined { + let total = 0; + for (const slot of shape) { + if (slot === 'scalar') { + total += 1; + } else if (typeof slot === 'object' && 'len' in slot) { + total += slot.len; + } else { + return undefined; + } + } + return total; +} + +/** 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 { + const width = tryFieldWidth(shape); + if (width === undefined) { + throw new Error('Cannot compute a fixed field width for a variable-width shape'); + } + return width; +} + +/** 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; +} + +/** Serializes one element to its flat fields. Element must be serializable. */ +function serializeElement(element: TypeMapping, value: T): Fr[] { + return element.serialization!.fn(value).flat(); +} + +/** + * Deserializes one element from its exact flat fields, reconstructing its per-slot readers from its shape and asserting + * they are fully consumed (so trailing fields are rejected). Element must be deserializable. + */ +function deserializeElement(element: TypeMapping, fields: Fr[]): T { + const readers = splitByShape(fields, element.shape); + const value = element.deserialization!.fn(readers); + assertReadersConsumed(readers); + return value; +} + +/** Flattens `values` into one contiguous run of fields by serializing each element. Element must be serializable. */ +function packElements(element: TypeMapping, values: T[]): Fr[] { + return values.flatMap(v => serializeElement(element, v)); +} + +/** + * Reads `count` fixed-width elements out of a packed run (the inverse of {@link packElements}). Element must be + * deserializable. + */ +function unpackElements(element: TypeMapping, reader: FieldReader, count: number): T[] { + const elementWidth = fieldWidth(element.shape); + return Array.from({ length: count }, () => deserializeElement(element, reader.readFieldArray(elementWidth))); +} + +/** 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/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..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 @@ -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, @@ -534,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 b5705dad2af8..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 @@ -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'; @@ -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, @@ -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 7fae6481e3e4..b2198dc1dee4 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 @@ -16,7 +16,8 @@ import { PrivateContextInputs } from '@aztec/stdlib/kernel'; import { AppTaggingSecret, AppTaggingSecretKind, - type ContractClassLog, + ContractClassLog, + ContractClassLogFields, type TaggingIndexRange, } from '@aztec/stdlib/logs'; import { Note, type NoteStatus } from '@aztec/stdlib/note'; @@ -35,6 +36,7 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { BoundedVec } from '../noir-structs/bounded_vec.js'; +import type { ContractClassLogData } from '../noir-structs/contract_class_log_data.js'; import type { NoteData } from '../noir-structs/note_data.js'; import { Option } from '../noir-structs/option.js'; import { pickNotes } from '../pick_notes.js'; @@ -177,9 +179,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 +199,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); @@ -471,10 +471,15 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * Emit a contract class log. * This fn exists because we only carry a poseidon hash through the kernels, and need to * keep the preimage in ts for later. - * @param log - The contract class log to be emitted. + * @param logData - The contract class log to be emitted. * @param counter - The contract class log's counter. */ - public notifyCreatedContractClassLog(log: ContractClassLog, counter: number) { + public notifyCreatedContractClassLog(logData: ContractClassLogData, counter: number) { + const log = ContractClassLog.from({ + contractAddress: logData.contractAddress, + fields: new ContractClassLogFields(logData.fields), + emittedLength: logData.emittedLength, + }); this.contractClassLogs.push(new CountedContractClassLog(log, counter)); const text = log.toBuffer().toString('hex'); this.logger.verbose( @@ -559,7 +564,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 3082b816e7f4..838328ec436e 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 { @@ -15,9 +24,9 @@ import { } from '@aztec/stdlib/contract'; 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 { AppTaggingSecret, AppTaggingSecretKind, 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, @@ -42,13 +51,14 @@ 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'; +import type { EmbeddedCurvePoint } from '../noir-structs/embedded_curve_point.js'; import { EphemeralArray } from '../noir-structs/ephemeral_array.js'; -import { ProvidedSecret } from '../noir-structs/provided_secret.js'; +import type { ProvidedSecret } from '../noir-structs/provided_secret.js'; import { TransientArrayService } from '../transient_array_service.js'; import { UtilityExecutionOracle, type UtilityExecutionOracleArgs } from './utility_execution_oracle.js'; @@ -62,7 +72,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>; @@ -86,7 +96,7 @@ describe('Utility Execution test suite', () => { aztecNode = mock(); senderTaggingStore = mock(); recipientTaggingStore = mock(); - senderAddressBookStore = mock(); + taggingSecretSourcesStore = mock(); capsuleStore = mock(); privateEventStore = mock(); contractSyncService = mock(); @@ -98,7 +108,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(() => []))); @@ -119,7 +130,7 @@ describe('Utility Execution test suite', () => { l2TipsStore, senderTaggingStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, capsuleStore, privateEventStore, simulator, @@ -436,6 +447,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()]], + ]); + }); + + 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(); @@ -478,7 +571,11 @@ describe('Utility Execution test suite', () => { const response = await utilityExecutionOracle.getMessageContextsByTxHash(requests); const [responseValue] = response.readAll(service); expect(responseValue.isSome()).toBe(true); - expect(responseValue.value).toEqual(new MessageContext(txHash, [noteHash], firstNullifier)); + expect(responseValue.value).toEqual({ + txHash, + uniqueNoteHashesInTx: [noteHash], + firstNullifierInTx: firstNullifier, + }); }); it('sets null in response for tx effects beyond anchor block', async () => { @@ -536,7 +633,7 @@ describe('Utility Execution test suite', () => { const oracleA = makeOracle({ contractAddress: contractAddressA }); const oracleB = makeOracle({ contractAddress: contractAddressB }); - const ephPksArray = EphemeralArray.fromValues(service, [ephPk]); + const ephPksArray = EphemeralArray.fromValues(service, [ephPk]); const responseA = await oracleA.getSharedSecrets(owner, ephPksArray, contractAddressA); const [secretA] = responseA.readAll(service); @@ -555,7 +652,7 @@ describe('Utility Execution test suite', () => { const { masterIncomingViewingSecretKey: ownerIvskM } = await deriveKeys(ownerSecretKey); keyStore.getMasterSecretKey.mockResolvedValue(ownerIvskM); - const ephPksArray = EphemeralArray.fromValues(service, [ephPk]); + const ephPksArray = EphemeralArray.fromValues(service, [ephPk]); const wrongAddress = await AztecAddress.random(); await expect(utilityExecutionOracle.getSharedSecrets(owner, ephPksArray, wrongAddress)).rejects.toThrow( /expected/, @@ -566,29 +663,62 @@ describe('Utility Execution test suite', () => { describe('getPendingTaggedLogs', () => { const service = new EphemeralArrayService(); - it('searches tags derived from provided secrets', async () => { - // Capture every tag the node is queried with so we can assert the provided secret was searched. - const queriedTags: Fr[] = []; - aztecNode.getPrivateLogsByTags.mockImplementation(query => { - for (const entry of query.tags) { - queriedTags.push('tag' in entry ? entry.tag.value : entry.value); - } - return Promise.resolve(query.tags.map(() => [])); + it("uses the provided secret's delivery mode when querying pending log tags", async () => { + const sharedSecret = Fr.random(); + const providedMode = AppTaggingSecretKind.CONSTRAINED; + const providedSecrets: ProvidedSecret[] = [{ secret: sharedSecret, mode: providedMode }]; + const constrainedModeTag = await SiloedTag.compute({ + extendedSecret: new AppTaggingSecret(sharedSecret, contractAddress, providedMode), + index: 0, }); + const sameSecretUnconstrainedModeTag = await SiloedTag.compute({ + extendedSecret: new AppTaggingSecret(sharedSecret, contractAddress, AppTaggingSecretKind.UNCONSTRAINED), + index: 0, + }); + expect(constrainedModeTag.equals(sameSecretUnconstrainedModeTag)).toBe(false); + + const log = { + logData: [Fr.random(), Fr.random()], + blockNumber: anchorBlockHeader.globalVariables.blockNumber, + blockHash: await anchorBlockHeader.hash(), + blockTimestamp: anchorBlockHeader.globalVariables.timestamp, + txHash: TxHash.random(), + txIndexWithinBlock: 0, + logIndexWithinTx: 0, + noteHashes: [Fr.random()], + nullifiers: [Fr.random()], + }; - const providedSecret = Fr.random(); - const providedSecrets = EphemeralArray.fromValues(service, [ - new ProvidedSecret(providedSecret, AppTaggingSecretKind.UNCONSTRAINED), - ]); + aztecNode.getPrivateLogsByTags.mockImplementation(query => { + return Promise.resolve( + query.tags.map(entry => { + const tag = 'tag' in entry ? entry.tag : entry; + return tag.equals(constrainedModeTag) ? [log] : []; + }), + ); + }); - await utilityExecutionOracle.getPendingTaggedLogs(owner, providedSecrets); + const result = await utilityExecutionOracle.getPendingTaggedLogs( + owner, + EphemeralArray.fromValues(service, providedSecrets), + ); - // The first-window tag of the provided secret must appear among the tags queried against the node. - const expectedTag = await SiloedTag.compute({ - extendedSecret: new AppTaggingSecret(providedSecret, contractAddress, AppTaggingSecretKind.UNCONSTRAINED), - index: 0, - }); - expect(queriedTags.map(tag => tag.toString())).toContain(expectedTag.value.toString()); + const queried = aztecNode.getPrivateLogsByTags.mock.calls.flatMap(([query]) => + query.tags.map(entry => ('tag' in entry ? entry.tag.value.toString() : entry.value.toString())), + ); + expect(queried).toContain(constrainedModeTag.value.toString()); + expect(queried).not.toContain(sameSecretUnconstrainedModeTag.value.toString()); + const resultLogs = result.readAll(service); + expect(resultLogs).toEqual([ + { + log: log.logData, + context: { + txHash: log.txHash, + uniqueNoteHashesInTx: log.noteHashes, + firstNullifierInTx: log.nullifiers[0], + }, + }, + ]); }); }); @@ -611,7 +741,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 84a27e63724a..e423e2234037 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 @@ -3,7 +3,7 @@ import type { BlockNumber } from '@aztec/foundation/branded-types'; import { uniqueBy } from '@aztec/foundation/collection'; import { Aes128 } from '@aztec/foundation/crypto/aes128'; import { Fr } from '@aztec/foundation/curves/bn254'; -import type { Point } from '@aztec/foundation/curves/grumpkin'; +import { Point } from '@aztec/foundation/curves/grumpkin'; import { LogLevels, type Logger, createLogger } from '@aztec/foundation/log'; import { MembershipWitness } from '@aztec/foundation/trees'; import type { KeyStore } from '@aztec/key-store'; @@ -27,7 +27,8 @@ import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { PublicKeys, computeAddressSecret, hashPublicKey } from '@aztec/stdlib/keys'; import { AppTaggingSecret, - MessageContext, + FlatPublicLogs, + type MessageContext, type PendingTaggedLog, deriveAppSiloedSharedSecret, } from '@aztec/stdlib/logs'; @@ -40,7 +41,6 @@ import { type Capsule, type IndexedTxEffect, type OffchainEffect, - TxEffect, type TxHash, } from '@aztec/stdlib/tx'; @@ -59,9 +59,10 @@ 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 type { EmbeddedCurvePoint } from '../noir-structs/embedded_curve_point.js'; import { EphemeralArray } from '../noir-structs/ephemeral_array.js'; import type { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import type { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; @@ -70,12 +71,12 @@ import type { NoteData } from '../noir-structs/note_data.js'; import type { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import { Option } from '../noir-structs/option.js'; import type { ProvidedSecret } from '../noir-structs/provided_secret.js'; -import { UtilityContext } from '../noir-structs/utility_context.js'; +import type { TxEffectData } from '../noir-structs/tx_effect_data.js'; +import type { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import type { TransientArrayService } from '../transient_array_service.js'; import { buildACIRCallback } from './acir_callback.js'; import type { IMiscOracle, IUtilityExecutionOracle } from './interfaces.js'; -import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; /** Args for UtilityExecutionOracle constructor. */ export type UtilityExecutionOracleArgs = { @@ -90,7 +91,7 @@ export type UtilityExecutionOracleArgs = { addressStore: AddressStore; aztecNode: AztecNode; recipientTaggingStore: RecipientTaggingStore; - senderAddressBookStore: SenderAddressBookStore; + taggingSecretSourcesStore: TaggingSecretSourcesStore; capsuleService: CapsuleService; privateEventStore: PrivateEventStore; messageContextService: MessageContextService; @@ -132,7 +133,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 +157,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; @@ -195,17 +196,22 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public getUtilityContext(): UtilityContext { - return new UtilityContext(this.anchorBlockHeader, this.callContext.contractAddress, this.callContext.msgSender); + return { + blockHeader: this.anchorBlockHeader, + contractAddress: this.callContext.contractAddress, + msgSender: this.callContext.msgSender, + }; } /** * 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 +265,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 +357,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 }); } @@ -486,7 +492,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra await this.anchorBlockHeader.hash(), ); - return new MessageLoadOracleInputs(messageIndex, siblingPath); + return { index: messageIndex, siblingPath }; } /** @@ -585,7 +591,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.l2TipsStore, this.keyStore, this.recipientTaggingStore, - this.senderAddressBookStore, + this.taggingSecretSourcesStore, this.addressStore, this.jobId, this.logger.getBindings(), @@ -650,9 +656,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); } @@ -660,17 +664,26 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * Fetches the effects of a transaction by its hash. Returns null if the tx is not found or is beyond the anchor * block. */ - public async getTxEffect(txHash: TxHash): Promise> { + public async getTxEffect(txHash: TxHash): Promise> { if (txHash.hash.isZero()) { throw new Error('Invalid tx hash passed into aztec_utl_getTxEffect oracle handler'); } 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); + const txEffect = receipt.txEffect; + return Option.some({ + ...txEffect, + publicLogs: FlatPublicLogs.fromLogs(txEffect.publicLogs), + contractClassLogs: txEffect.contractClassLogs.map(log => ({ + contractAddress: log.contractAddress, + fields: log.fields.toFields(), + emittedLength: log.emittedLength, + })), + }); } public setCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[], scope: AztecAddress): void { @@ -692,7 +705,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 +753,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 }); } } @@ -753,7 +766,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra */ public async getSharedSecrets( address: AztecAddress, - ephPks: EphemeralArray, + ephPks: EphemeralArray, contractAddress: AztecAddress, ): Promise> { if (!contractAddress.equals(this.contractAddress)) { @@ -768,7 +781,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const ephPkPoints = ephPks.readAll(this.ephemeralArrayService); const secrets = await Promise.all( - ephPkPoints.map(ephPk => deriveAppSiloedSharedSecret(addressSecret, ephPk, this.contractAddress)), + ephPkPoints.map(({ x, y }) => deriveAppSiloedSharedSecret(addressSecret, new Point(x, y), this.contractAddress)), ); return EphemeralArray.fromValues(this.ephemeralArrayService, secrets); @@ -847,12 +860,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), @@ -912,7 +920,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, @@ -1017,3 +1025,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))'; + +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; +} 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 ae81397c6454..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'); @@ -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 @@ -144,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]])); @@ -169,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; @@ -251,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]); @@ -353,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/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/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/events/event_service.test.ts b/yarn-project/pxe/src/events/event_service.test.ts index 24e88448d9f7..cabbeffdbd4c 100644 --- a/yarn-project/pxe/src/events/event_service.test.ts +++ b/yarn-project/pxe/src/events/event_service.test.ts @@ -12,7 +12,7 @@ import { type IndexedTxEffect, TxEffect } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; -import { EventValidationRequest } from '../contract_function_simulator/noir-structs/event_validation_request.js'; +import type { EventValidationRequest } from '../contract_function_simulator/noir-structs/event_validation_request.js'; import { PrivateEventStore } from '../storage/private_event_store/private_event_store.js'; import { EventService } from './event_service.js'; @@ -84,14 +84,14 @@ describe('validateAndStoreEvents', () => { txEffectsMap?: Map; } = {}, ) { - const request = new EventValidationRequest( + const request: EventValidationRequest = { contractAddress, - eventSelector, + eventTypeId: eventSelector, randomness, - overrides.eventContent ?? eventContent, - overrides.eventCommitment ?? eventCommitment, - txEffect.txHash, - ); + serializedEvent: overrides.eventContent ?? eventContent, + eventCommitment: overrides.eventCommitment ?? eventCommitment, + txHash: txEffect.txHash, + }; const map = overrides.txEffectsMap ?? defaultTxEffectsMap(); await eventService.validateAndStoreEvents([request], recipient, map); diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index f3b8385328aa..6adcfda9c724 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -11,10 +11,14 @@ import { makeBlockHeader, randomPrivateLogResult } from '@aztec/stdlib/testing'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { LogRetrievalRequest, LogSource } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; +import { + type LogRetrievalRequest, + LogSource, +} from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; +import { Option } from '../contract_function_simulator/noir-structs/option.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 +27,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 +38,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 +56,7 @@ describe('LogService', () => { mock(), keyStore, recipientTaggingStore, - senderAddressBookStore, + taggingSecretSourcesStore, addressStore, 'test', ); @@ -61,7 +65,7 @@ describe('LogService', () => { it('returns empty arrays if no logs are found', async () => { aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); aztecNode.getPublicLogsByTags.mockResolvedValue([[]]); - const request = new LogRetrievalRequest(contractAddress, tag); + const request = makeLogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses).toEqual([[]]); }); @@ -73,7 +77,7 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[scopedLog1, scopedLog2]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); - const request = new LogRetrievalRequest(contractAddress, tag); + const request = makeLogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses[0]).toHaveLength(2); @@ -88,7 +92,7 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[scopedLog1, scopedLog2]]); - const request = new LogRetrievalRequest(contractAddress, tag); + const request = makeLogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses[0]).toHaveLength(2); @@ -103,7 +107,7 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[publicLog]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[privateLog]]); - const request = new LogRetrievalRequest(contractAddress, tag); + const request = makeLogRetrievalRequest(contractAddress, tag); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses[0]).toHaveLength(2); @@ -113,8 +117,8 @@ describe('LogService', () => { it('rejects a batch where at least one request targets a different contract', async () => { const differentContract = await AztecAddress.random(); - const validRequest = new LogRetrievalRequest(contractAddress, tag); - const invalidRequest = new LogRetrievalRequest(differentContract, Tag.random()); + const validRequest = makeLogRetrievalRequest(contractAddress, tag); + const invalidRequest = makeLogRetrievalRequest(differentContract, Tag.random()); await expect(logService.fetchLogsByTag(contractAddress, [validRequest, invalidRequest])).rejects.toThrow( /Got a log retrieval request from/, @@ -133,9 +137,9 @@ describe('LogService', () => { aztecNode.getPrivateLogsByTags.mockResolvedValue([[], [privateLog2], []]); const requests = [ - new LogRetrievalRequest(contractAddress, tag1), - new LogRetrievalRequest(contractAddress, tag2), - new LogRetrievalRequest(contractAddress, tag3), + makeLogRetrievalRequest(contractAddress, tag1), + makeLogRetrievalRequest(contractAddress, tag2), + makeLogRetrievalRequest(contractAddress, tag3), ]; const responses = await logService.fetchLogsByTag(contractAddress, requests); @@ -167,7 +171,12 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[logAtBoundary]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); - const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PUBLIC_AND_PRIVATE, BlockNumber(10)); + const request = makeLogRetrievalRequest( + contractAddress, + tag, + LogSource.PUBLIC_AND_PRIVATE, + Option.some(BlockNumber(10)), + ); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(aztecNode.getPublicLogsByTags).toHaveBeenCalledWith( @@ -183,12 +192,12 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[logBeforeBoundary]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); - const request = new LogRetrievalRequest( + const request = makeLogRetrievalRequest( contractAddress, tag, LogSource.PUBLIC_AND_PRIVATE, undefined, - BlockNumber(10), + Option.some(BlockNumber(10)), ); const responses = await logService.fetchLogsByTag(contractAddress, [request]); @@ -205,12 +214,12 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[logInRange]]); aztecNode.getPrivateLogsByTags.mockResolvedValue([[]]); - const request = new LogRetrievalRequest( + const request = makeLogRetrievalRequest( contractAddress, tag, LogSource.PUBLIC_AND_PRIVATE, - BlockNumber(10), - BlockNumber(20), + Option.some(BlockNumber(10)), + Option.some(BlockNumber(20)), ); const responses = await logService.fetchLogsByTag(contractAddress, [request]); @@ -228,7 +237,7 @@ describe('LogService', () => { aztecNode.getPublicLogsByTags.mockResolvedValue([[publicLog]]); - const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PUBLIC); + const request = makeLogRetrievalRequest(contractAddress, tag, LogSource.PUBLIC); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses[0]).toHaveLength(1); @@ -241,7 +250,7 @@ describe('LogService', () => { aztecNode.getPrivateLogsByTags.mockResolvedValue([[privateLog]]); - const request = new LogRetrievalRequest(contractAddress, tag, LogSource.PRIVATE); + const request = makeLogRetrievalRequest(contractAddress, tag, LogSource.PRIVATE); const responses = await logService.fetchLogsByTag(contractAddress, [request]); expect(responses[0]).toHaveLength(1); @@ -263,9 +272,9 @@ describe('LogService', () => { aztecNode.getPrivateLogsByTags.mockResolvedValue([[privateLog2], [privateLog3]]); const requests = [ - new LogRetrievalRequest(contractAddress, tag1, LogSource.PUBLIC), - new LogRetrievalRequest(contractAddress, tag2, LogSource.PRIVATE), - new LogRetrievalRequest(contractAddress, tag3, LogSource.PUBLIC_AND_PRIVATE), + makeLogRetrievalRequest(contractAddress, tag1, LogSource.PUBLIC), + makeLogRetrievalRequest(contractAddress, tag2, LogSource.PRIVATE), + makeLogRetrievalRequest(contractAddress, tag3, LogSource.PUBLIC_AND_PRIVATE), ]; const responses = await logService.fetchLogsByTag(contractAddress, requests); @@ -296,3 +305,13 @@ describe('LogService', () => { }); }); }); + +function makeLogRetrievalRequest( + contractAddress: AztecAddress, + tag: Tag, + source: LogSource = LogSource.PUBLIC_AND_PRIVATE, + fromBlock: Option = Option.none(), + toBlock: Option = Option.none(), +): LogRetrievalRequest { + return { contractAddress, tag, source, fromBlock, toBlock }; +} diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 0b9ed0dbe282..cf13c76c2d33 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -1,20 +1,29 @@ +import { PRIVATE_LOG_CIPHERTEXT_LEN } from '@aztec/constants'; 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, + type PendingTaggedLog, + SiloedTag, + computeSharedTaggingSecret, +} from '@aztec/stdlib/logs'; import type { BlockHeader } from '@aztec/stdlib/tx'; import { type LogRetrievalRequest, LogSource, } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; -import { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js'; +import type { 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 +43,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, @@ -139,12 +148,14 @@ export class LogService { ): Map { const groups = new Map(); for (const entry of entries) { - const key = rangeKey(entry.request.fromBlock, entry.request.toBlock); + const fromBlock = entry.request.fromBlock.value; + const toBlock = entry.request.toBlock.value; + const key = rangeKey(fromBlock, toBlock); const existing = groups.get(key); if (existing) { existing.entries.push(entry); } else { - groups.set(key, { fromBlock: entry.request.fromBlock, toBlock: entry.request.toBlock, entries: [entry] }); + groups.set(key, { fromBlock, toBlock, entries: [entry] }); } } return groups; @@ -158,12 +169,14 @@ export class LogService { if (nullifiers.length === 0) { throw new Error(`Log for tx ${log.txHash} returned no nullifiers from the node`); } - return new LogRetrievalResponse( - log.logData.slice(1), // Skip the tag - log.txHash, - noteHashes, - nullifiers[0], - ); + return { + // Skip the tag, and clip to the wire cap: public logs can exceed PRIVATE_LOG_CIPHERTEXT_LEN, which is the fixed + // size of the oracle's BoundedVec slot. A no-op for private logs, which are already within the cap. + logPayload: log.logData.slice(1, 1 + PRIVATE_LOG_CIPHERTEXT_LEN), + txHash: log.txHash, + uniqueNoteHashesInTx: noteHashes, + firstNullifierInTx: nullifiers[0], + }; } public async fetchTaggedLogs( @@ -171,12 +184,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, @@ -193,44 +214,57 @@ export class LogService { if (nullifiers.length === 0) { throw new Error(`Log for tx ${log.txHash} returned no nullifiers from the node`); } - return new PendingTaggedLog(log.logData, log.txHash, noteHashes, nullifiers[0]); + return { + log: log.logData, + context: { txHash: log.txHash, uniqueNoteHashesInTx: noteHashes, firstNullifierInTx: nullifiers[0] }, + }; }); } - 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/messages/message_context_service.test.ts b/yarn-project/pxe/src/messages/message_context_service.test.ts index 36e95e60bf34..159d72f541e9 100644 --- a/yarn-project/pxe/src/messages/message_context_service.test.ts +++ b/yarn-project/pxe/src/messages/message_context_service.test.ts @@ -2,7 +2,6 @@ import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded- import { Fr } from '@aztec/foundation/curves/bn254'; import { BlockHash } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { MessageContext } from '@aztec/stdlib/logs'; import { DroppedTxReceipt, MinedTxReceipt, TxEffect, TxExecutionResult, TxHash, TxStatus } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; @@ -115,7 +114,7 @@ describe('MessageContextService', () => { const results = await service.getMessageContextsByTxHash([txHash.hash], anchorBlockNumber); - expect(results).toEqual([new MessageContext(txHash, noteHashes, firstNullifier)]); + expect(results).toEqual([{ txHash, uniqueNoteHashesInTx: noteHashes, firstNullifierInTx: firstNullifier }]); }); it('resolves tx hashes in different situations', async () => { @@ -159,7 +158,12 @@ describe('MessageContextService', () => { anchorBlockNumber, ); - expect(results).toEqual([null, new MessageContext(validTxHash, validNoteHashes, validNullifier), null, null]); + expect(results).toEqual([ + null, + { txHash: validTxHash, uniqueNoteHashesInTx: validNoteHashes, firstNullifierInTx: validNullifier }, + null, + null, + ]); // Zero hash should not trigger getTxReceipt expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(3); @@ -190,7 +194,11 @@ describe('MessageContextService', () => { anchorBlockNumber, ); - const expected = new MessageContext(txEffect.txHash, txEffect.noteHashes, txEffect.nullifiers[0]); + const expected = { + txHash: txEffect.txHash, + uniqueNoteHashesInTx: txEffect.noteHashes, + firstNullifierInTx: txEffect.nullifiers[0], + }; expect(results).toEqual([expected, expected, expected]); expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(1); }); diff --git a/yarn-project/pxe/src/messages/message_context_service.ts b/yarn-project/pxe/src/messages/message_context_service.ts index 4768ee3fcf0d..7f2de475fb25 100644 --- a/yarn-project/pxe/src/messages/message_context_service.ts +++ b/yarn-project/pxe/src/messages/message_context_service.ts @@ -1,7 +1,7 @@ import { uniqueBy } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { MessageContext } from '@aztec/stdlib/logs'; +import type { MessageContext } from '@aztec/stdlib/logs'; import { type IndexedTxEffect, TxHash } from '@aztec/stdlib/tx'; /** Resolves transaction hashes into the context needed to process messages. */ @@ -56,7 +56,11 @@ export class MessageContextService { throw new Error(`Tx effect for ${txHash} has no nullifiers`); } - return new MessageContext(data.txHash, data.noteHashes, data.nullifiers[0]); + return { + txHash: data.txHash, + uniqueNoteHashesInTx: data.noteHashes, + firstNullifierInTx: data.nullifiers[0], + }; }); } } diff --git a/yarn-project/pxe/src/notes/note_service.test.ts b/yarn-project/pxe/src/notes/note_service.test.ts index 1ae5206634cb..6f454e85f7bf 100644 --- a/yarn-project/pxe/src/notes/note_service.test.ts +++ b/yarn-project/pxe/src/notes/note_service.test.ts @@ -15,7 +15,7 @@ import { type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; -import { NoteValidationRequest } from '../contract_function_simulator/noir-structs/note_validation_request.js'; +import type { NoteValidationRequest } from '../contract_function_simulator/noir-structs/note_validation_request.js'; import { NoteStore } from '../storage/note_store/note_store.js'; import { NoteService } from './note_service.js'; @@ -253,18 +253,17 @@ describe('NoteService', () => { */ setSyncedBlockNumber(blockNumber); - buildRequest = (overrides = {}) => - new NoteValidationRequest( - overrides.contractAddress ?? contractAddress, - overrides.owner ?? owner, - overrides.storageSlot ?? storageSlot, - overrides.randomness ?? randomness, - overrides.noteNonce ?? noteNonce, - overrides.content ?? content, - overrides.noteHash ?? noteHash, - overrides.nullifier ?? nullifier, - overrides.txHash ?? txHash, - ); + buildRequest = (overrides = {}) => ({ + contractAddress: overrides.contractAddress ?? contractAddress, + owner: overrides.owner ?? owner, + storageSlot: overrides.storageSlot ?? storageSlot, + randomness: overrides.randomness ?? randomness, + noteNonce: overrides.noteNonce ?? noteNonce, + content: overrides.content ?? content, + noteHash: overrides.noteHash ?? noteHash, + nullifier: overrides.nullifier ?? nullifier, + txHash: overrides.txHash ?? txHash, + }); aztecNode.findLeavesIndexes.mockImplementation((_queryBlockParam, _treeId, leaves) => { // By default the notes are not yet nullified. diff --git a/yarn-project/pxe/src/oracle_version.ts b/yarn-project/pxe/src/oracle_version.ts index 9b9a096ea9f8..6e61b7e44910 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 = '79e59055e15813ab550dd93d95b2a73ff6bfe5efcdb04c060fd424c1a1ab99c5'; 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/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/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..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 @@ -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'; /** @@ -106,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), @@ -125,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); @@ -188,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), @@ -204,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 => ({ @@ -243,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), @@ -293,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), @@ -320,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), @@ -338,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), @@ -391,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); @@ -491,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); @@ -506,16 +507,30 @@ 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.fromBigIntUnsafe(2n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigIntUnsafe(3n)); + await taggingSecretSourcesStore.addSender(AztecAddress.fromBigIntUnsafe(5n)); + + await taggingSecretSourcesStore.addSharedSecret( + 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)), + ); }, 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')), }), }, @@ -525,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); @@ -621,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), @@ -642,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/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/recipient_tagging_store.test.ts b/yarn-project/pxe/src/storage/tagging_store/recipient_tagging_store.test.ts index d06398c44645..766c460ece6d 100644 --- a/yarn-project/pxe/src/storage/tagging_store/recipient_tagging_store.test.ts +++ b/yarn-project/pxe/src/storage/tagging_store/recipient_tagging_store.test.ts @@ -1,5 +1,5 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { type AppTaggingSecret, AppTaggingSecretKind } from '@aztec/stdlib/logs'; +import { AppTaggingSecret, AppTaggingSecretKind } from '@aztec/stdlib/logs'; import { randomAppTaggingSecret } from '@aztec/stdlib/testing'; import { RecipientTaggingStore } from './recipient_tagging_store.js'; @@ -85,4 +85,25 @@ describe('RecipientTaggingStore', () => { expect(await taggingStore.getHighestFinalizedIndex(secret1, 'job1')).toBeUndefined(); }); }); + + // A single handshake shared secret is scanned under both delivery modes (constrained + unconstrained). The two + // share the same underlying Fr and app but differ in kind, so they must be tracked as independent index sequences: + // advancing one mode's finalized index must not clobber the other's. + describe('mode independence', () => { + it('tracks the same secret under different kinds independently', async () => { + const unconstrained = await randomAppTaggingSecret(AppTaggingSecretKind.UNCONSTRAINED); + const constrained = new AppTaggingSecret( + unconstrained.secret, + unconstrained.app, + AppTaggingSecretKind.CONSTRAINED, + ); + + await taggingStore.updateHighestFinalizedIndex(unconstrained, 4, 'job1'); + await taggingStore.updateHighestFinalizedIndex(constrained, 9, 'job1'); + await taggingStore.commit('job1'); + + expect(await taggingStore.getHighestFinalizedIndex(unconstrained, 'job2')).toBe(4); + expect(await taggingStore.getHighestFinalizedIndex(constrained, 'job2')).toBe(9); + }); + }); }); 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..5dc475e79880 --- /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.fromStringUnsafe); + }); + } + + 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/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..d6f611b133db 100644 --- a/yarn-project/standard-contracts/src/standard_contract_data.ts +++ b/yarn-project/standard-contracts/src/standard_contract_data.ts @@ -20,17 +20,21 @@ 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('0x04fc897b6deff5e3c18900a6a1c8ad601772027a500b8f329083810b7bffaf26'), + MultiCallEntrypoint: AztecAddress.fromStringUnsafe( + '0x1aaa6153c0d5780188c64dd4d2c28372d42e5bcee5210c4184a312db0fe709d0', + ), + PublicChecks: AztecAddress.fromStringUnsafe('0x1da099c6dff135d8e519a4e973f27c0e25cf0897d7cf968c7d38329f46df1056'), + HandshakeRegistry: AztecAddress.fromStringUnsafe( + '0x215f91f8907b8d6406a9b209b88b3d8d01c764c81d3704af257a2de6f0cd908d', + ), }; export const StandardContractClassId: Record = { - AuthRegistry: Fr.fromString('0x1e68edd1786a3a9ef773ad5e481fee8b829bf92a2119204087df62af2f01359e'), - MultiCallEntrypoint: Fr.fromString('0x2c613ebbf351bf6b493ef7d089edf2c8b8365677a109ff900695c5693e18e2e8'), - PublicChecks: Fr.fromString('0x140586e8046f3c579bc34d660079204656126e3aaa219a323dcf487d095feb25'), - HandshakeRegistry: Fr.fromString('0x25388ee6f42d552caa5f96df8be2d7ec313aea2dda20cf924048d2e942f43e71'), + AuthRegistry: Fr.fromString('0x1034dc7a2f6e79bc30624e31aa1bcbfbf7771dcb18b1ce15996bf2ec8486ed34'), + MultiCallEntrypoint: Fr.fromString('0x0c631fbcc97778edadd2cc20bc42a7f849a1933d363d93106a3a655856286f31'), + PublicChecks: Fr.fromString('0x22c35afeea9f8bfc36b0fc9ba78bdd92769fd42f3926822cf85d4b0596e00999'), + HandshakeRegistry: Fr.fromString('0x278dcc5acdb19e5a7f0ae9b36c6a79bf25215a3e1bcad54caa47179a24d40289'), }; export const StandardContractClassIdPreimage: Record< @@ -38,23 +42,23 @@ export const StandardContractClassIdPreimage: Record< { artifactHash: Fr; privateFunctionsRoot: Fr; publicBytecodeCommitment: Fr } > = { AuthRegistry: { - artifactHash: Fr.fromString('0x04124f7c586b2537ade4e6411412bbecce96b1e1eb638c0bfd0499e39ddfc507'), + artifactHash: Fr.fromString('0x05c4f994f0dd2b9e03b57781ec0d651a79cc657a24786673a757c76d62f31598'), privateFunctionsRoot: Fr.fromString('0x17b584350f4c3ccafd8f688729afb9feab8976114fb40012e9dee65022c072a4'), publicBytecodeCommitment: Fr.fromString('0x2545f39893766508ce37bb5cea5e4dcab04c6f7f79f3089b1c076876e9d268b2'), }, MultiCallEntrypoint: { - artifactHash: Fr.fromString('0x2401384960d38f4fded6a6cb494d583c7c5dfb893529aa3529e46cff20497987'), + artifactHash: Fr.fromString('0x2bb5473f1f2f29e39e85439dff7e5dbf727c4d840640c21a6009bf25626e0dae'), privateFunctionsRoot: Fr.fromString('0x0e68dfbb256e80b08b3aef47aca1f2669e97a9c6259787893c1223ac083ad5d5'), publicBytecodeCommitment: Fr.fromString('0x0ce4c618c3ed7f3a20410e618c06bb701e150af7fe28a3e92f68e7733809f33e'), }, PublicChecks: { - artifactHash: Fr.fromString('0x0dff0f7b848e225b89854cfb56bb5dcad68fa1edab5aa70cb08ec7853c438b10'), + artifactHash: Fr.fromString('0x2c439427c5bde0db108e859d0a0602f1feffae12e7250193b5ffe0a9442a9790'), privateFunctionsRoot: Fr.fromString('0x202860adb1b8975971eeaf571aaaa88a27f4035290d58532ae7d60b0dfaad54c'), publicBytecodeCommitment: Fr.fromString('0x013c4f854a5c87c9daf86c5f9bc07a42c2a061f1d924a5b3564ec7edc8e18cb7'), }, HandshakeRegistry: { - artifactHash: Fr.fromString('0x2dbd127b85e874eef3dba3ccf23b3407d5e225ec051c3f8c0499a7edd3371f0f'), - privateFunctionsRoot: Fr.fromString('0x0de4de94f6ec6d3aefc28afd86aee259ab8e36e2c394221632b34b091946d7cb'), + artifactHash: Fr.fromString('0x006e70d1d4d34b07e89bf4aab8a59a4d7e66d469cae4164723d63e66828be903'), + privateFunctionsRoot: Fr.fromString('0x2e839c3fda7214a7ba230e564fe13c9bfed033132e731bf223321485f0b8068c'), publicBytecodeCommitment: Fr.fromString('0x0ce4c618c3ed7f3a20410e618c06bb701e150af7fe28a3e92f68e7733809f33e'), }, }; @@ -90,15 +94,15 @@ export const StandardContractPrivateFunctions: Record< HandshakeRegistry: [ { selector: FunctionSelector.fromField( - Fr.fromString('0x000000000000000000000000000000000000000000000000000000009968d9e2'), + Fr.fromString('0x00000000000000000000000000000000000000000000000000000000d12ace81'), ), - vkHash: Fr.fromString('0x2f1c34c6e08be968dabafdba8ebe5300de23e682914b795368cde3d4a32b1088'), + vkHash: Fr.fromString('0x2bf48dfeb80efd1b12ca08992ed1f900938764b1cca4afb50aad54096485e7dc'), }, { selector: FunctionSelector.fromField( - Fr.fromString('0x00000000000000000000000000000000000000000000000000000000f7b8f754'), + Fr.fromString('0x00000000000000000000000000000000000000000000000000000000db548fcf'), ), - vkHash: Fr.fromString('0x1efc96ed0a270c9b2dd8a0c4ee308803985d6fad24fdd6822063207745385f78'), + vkHash: Fr.fromString('0x0eaa1eff3977b3636573dd23f2e32196b7b0f1b13b38d98e6c3c9b5774c40668'), }, ], }; 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/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/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/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(), }), 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.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..55742a6f3177 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 { @@ -71,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), ); } @@ -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/stdlib/src/logs/contract_class_log.test.ts b/yarn-project/stdlib/src/logs/contract_class_log.test.ts index 0ef4d69f33b2..f46cfff22412 100644 --- a/yarn-project/stdlib/src/logs/contract_class_log.test.ts +++ b/yarn-project/stdlib/src/logs/contract_class_log.test.ts @@ -18,26 +18,15 @@ describe('ContractClassLog', () => { expect(res).toEqual(log); }); - it('serializes to field array and deserializes it back', () => { - const fieldArray = log.toFields(); - const res = ContractClassLog.fromFields(fieldArray); - expect(res).toEqual(log); - }); - it('convert to and from json', () => { const parsed = ContractClassLog.schema.parse(JSON.parse(jsonStringify(log))); expect(parsed).toEqual(log); }); - it('number of fields matches constant', () => { - const fields = log.toFields(); - expect(fields.length * Fr.SIZE_IN_BYTES).toBe(ContractClassLog.SIZE_IN_BYTES); - }); - 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 +36,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/logs/contract_class_log.ts b/yarn-project/stdlib/src/logs/contract_class_log.ts index fd4f43a33e6e..fdff6655b28b 100644 --- a/yarn-project/stdlib/src/logs/contract_class_log.ts +++ b/yarn-project/stdlib/src/logs/contract_class_log.ts @@ -138,15 +138,6 @@ export class ContractClassLog { ); } - static fromFields(fields: Fr[] | FieldReader) { - const reader = FieldReader.asReader(fields); - return new ContractClassLog( - reader.readObject(AztecAddress), - reader.readObject(ContractClassLogFields), - reader.readU32(), - ); - } - getEmittedFields() { return this.fields.getEmittedFields(this.emittedLength); } diff --git a/yarn-project/stdlib/src/logs/message_context.test.ts b/yarn-project/stdlib/src/logs/message_context.test.ts deleted file mode 100644 index 474342f6129b..000000000000 --- a/yarn-project/stdlib/src/logs/message_context.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { updateInlineTestData } from '@aztec/foundation/testing/files'; - -import { TxHash } from '../tx/tx_hash.js'; -import { MessageContext } from './message_context.js'; - -describe('MessageContext', () => { - it('serialization matches snapshots and output of Noir serialization', () => { - // Setup test data - const txHash = new TxHash(new Fr(123n)); - const uniqueNoteHashes = [new Fr(4n), new Fr(5n)]; - const firstNullifier = new Fr(6n); - - // Create a MessageContext instance - const messageContext = new MessageContext(txHash, uniqueNoteHashes, firstNullifier); - - // Serialize the message context - const serialized = messageContext.toFields(); - - // Compare with snapshot - expect(serialized.map(f => f.toString())).toMatchInlineSnapshot(` - [ - "0x000000000000000000000000000000000000000000000000000000000000007b", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000006", - ] - `); - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - const fieldArrayStr = `[${serialized.map(f => f.toString()).join(',')}]`; - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/messages/processing/message_context.nr', - 'serialized_message_context_from_typescript', - fieldArrayStr, - ); - }); - - it('serialization of some option matches snapshot', () => { - const txHash = new TxHash(new Fr(123)); - const uniqueNoteHashes = [new Fr(4n), new Fr(5n)]; - const firstNullifier = new Fr(6n); - const ctx = new MessageContext(txHash, uniqueNoteHashes, firstNullifier); - const serialized = MessageContext.toSerializedOption(ctx); - // is_some flag + fields - expect(serialized[0]).toEqual(new Fr(1)); - expect(serialized.length).toEqual(1 + ctx.toFields().length); - }); - - it('serialization of none option matches snapshot', () => { - const serialized = MessageContext.toSerializedOption(null); - expect(serialized[0]).toEqual(new Fr(0)); - // All fields should be zero - for (const f of serialized) { - expect(f).toEqual(Fr.zero()); - } - }); - - it('serialization length of empty matches some', () => { - const txHash = new TxHash(new Fr(123)); - const ctx = new MessageContext(txHash, [new Fr(4n), new Fr(5n)], new Fr(6n)); - expect(ctx.toFields().length).toEqual(MessageContext.toEmptyFields().length); - }); -}); diff --git a/yarn-project/stdlib/src/logs/message_context.ts b/yarn-project/stdlib/src/logs/message_context.ts index 62046f096a82..eb2f3f1db47c 100644 --- a/yarn-project/stdlib/src/logs/message_context.ts +++ b/yarn-project/stdlib/src/logs/message_context.ts @@ -1,8 +1,6 @@ -import { MAX_NOTE_HASHES_PER_TX } from '@aztec/constants'; -import { range } from '@aztec/foundation/array'; -import { Fr } from '@aztec/foundation/curves/bn254'; +import type { Fr } from '@aztec/foundation/curves/bn254'; -import { TxHash } from '../tx/tx_hash.js'; +import type { TxHash } from '../tx/tx_hash.js'; /** * Additional information needed to process a message. @@ -13,64 +11,8 @@ import { TxHash } from '../tx/tx_hash.js'; * * A TS version of `message_context.nr`. */ -export class MessageContext { - constructor( - public txHash: TxHash, - public uniqueNoteHashesInTx: Fr[], - public firstNullifierInTx: Fr, - ) {} - - toFields(): Fr[] { - return [ - this.txHash.hash, - ...serializeBoundedVec(this.uniqueNoteHashesInTx, MAX_NOTE_HASHES_PER_TX), - this.firstNullifierInTx, - ]; - } - - toNoirStruct() { - /* eslint-disable camelcase */ - return { - tx_hash: this.txHash.hash, - unique_note_hashes_in_tx: this.uniqueNoteHashesInTx, - first_nullifier_in_tx: this.firstNullifierInTx, - }; - /* eslint-enable camelcase */ - } - - static empty(): MessageContext { - return new MessageContext(TxHash.zero(), [], Fr.ZERO); - } - - static toEmptyFields(): Fr[] { - const serializationLen = - 1 /* txHash */ + MAX_NOTE_HASHES_PER_TX + 1 /* uniqueNoteHashesInTx BVec */ + 1; /* firstNullifierInTx */ - return range(serializationLen).map(_ => Fr.zero()); - } - - static toSerializedOption(response: MessageContext | null): Fr[] { - if (response) { - return [new Fr(1), ...response.toFields()]; - } else { - return [new Fr(0), ...MessageContext.toEmptyFields()]; - } - } -} - -/** - * Helper function to serialize a bounded vector according to Noir's BoundedVec format - * @param values - The values to serialize - * @param maxLength - The maximum length of the bounded vector - * @returns The serialized bounded vector as Fr[] - * @dev Copied over from pending_tagged_log.ts. - */ -function serializeBoundedVec(values: Fr[], maxLength: number): Fr[] { - if (values.length > maxLength) { - throw new Error(`Attempted to serialize ${values} values into a BoundedVec with max length ${maxLength}`); - } - - const lengthDiff = maxLength - values.length; - const zeroPaddingArray = Array(lengthDiff).fill(Fr.ZERO); - const storage = values.concat(zeroPaddingArray); - return [...storage, new Fr(values.length)]; -} +export type MessageContext = { + txHash: TxHash; + uniqueNoteHashesInTx: Fr[]; + firstNullifierInTx: Fr; +}; diff --git a/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts b/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts deleted file mode 100644 index b327fc5237fa..000000000000 --- a/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { updateInlineTestData } from '@aztec/foundation/testing/files'; - -import { TxHash } from '../tx/tx_hash.js'; -import { PendingTaggedLog } from './pending_tagged_log.js'; - -describe('PendingTaggedLog', () => { - it('serialization matches snapshots and output of Noir serialization', () => { - const log = [new Fr(1n), new Fr(2n), new Fr(3n)]; - const txHash = new TxHash(new Fr(123n)); - const uniqueNoteHashes = [new Fr(4n), new Fr(5n)]; - const firstNullifier = new Fr(6n); - - const pendingLog = new PendingTaggedLog(log, txHash, uniqueNoteHashes, firstNullifier); - const serialized = pendingLog.toFields(); - - // Test against snapshot - expect(serialized.map(f => f.toString())).toMatchInlineSnapshot(` - [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x000000000000000000000000000000000000000000000000000000000000007b", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000000000000000000000000000006", - ] - `); - - // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data - const fieldArrayStr = `[${serialized.map(f => f.toString()).join(',')}]`; - updateInlineTestData( - 'noir-projects/aztec-nr/aztec/src/messages/processing/pending_tagged_log.nr', - 'serialized_pending_tagged_log_from_typescript', - fieldArrayStr, - ); - }); -}); diff --git a/yarn-project/stdlib/src/logs/pending_tagged_log.ts b/yarn-project/stdlib/src/logs/pending_tagged_log.ts index 70b0f85ecac1..6d1b68711755 100644 --- a/yarn-project/stdlib/src/logs/pending_tagged_log.ts +++ b/yarn-project/stdlib/src/logs/pending_tagged_log.ts @@ -1,39 +1,12 @@ -import { PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; -import { Fr } from '@aztec/foundation/curves/bn254'; +import type { Fr } from '@aztec/foundation/curves/bn254'; -import type { TxHash } from '../tx/tx_hash.js'; -import { MessageContext } from './message_context.js'; +import type { MessageContext } from './message_context.js'; /** * Represents a pending tagged log as it is stored in the pending tagged log array to which the fetchTaggedLogs oracle * inserts found private logs. A TS version of `pending_tagged_log.nr`. */ -export class PendingTaggedLog { - private context: MessageContext; - - constructor( - public log: Fr[], - txHash: TxHash, - uniqueNoteHashesInTx: Fr[], - firstNullifierInTx: Fr, - ) { - this.context = new MessageContext(txHash, uniqueNoteHashesInTx, firstNullifierInTx); - } - - toFields(): Fr[] { - return [...serializeBoundedVec(this.log, PRIVATE_LOG_SIZE_IN_FIELDS), ...this.context.toFields()]; - } -} - -/** - * Helper function to serialize a bounded vector according to Noir's BoundedVec format - * @param values - The values to serialize - * @param maxLength - The maximum length of the bounded vector - * @returns The serialized bounded vector as Fr[] - */ -function serializeBoundedVec(values: Fr[], maxLength: number): Fr[] { - const lengthDiff = maxLength - values.length; - const zeroPaddingArray = Array(lengthDiff).fill(Fr.ZERO); - const storage = values.concat(zeroPaddingArray); - return [...storage, new Fr(values.length)]; -} +export type PendingTaggedLog = { + log: Fr[]; + context: MessageContext; +}; 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 { return new MembershipWitness(NULLIFIER_TREE_HEIGHT, this.index, this.siblingPath.toTuple()); } - - /** Serializes as `(NullifierLeafPreimage, MembershipWitness)` to match the Noir oracle return type. */ - public toNoirRepresentation(): (string | string[])[] { - // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js - return [ - ...(this.leafPreimage.toFields().map(fr => fr.toString()) as string[]), - new Fr(this.index).toString() as string, - this.siblingPath.toFields().map(fr => fr.toString()) as string[], - ]; - } } diff --git a/yarn-project/stdlib/src/trees/public_data_witness.ts b/yarn-project/stdlib/src/trees/public_data_witness.ts index 02ebf1f8c9e7..7184ffa9e19c 100644 --- a/yarn-project/stdlib/src/trees/public_data_witness.ts +++ b/yarn-project/stdlib/src/trees/public_data_witness.ts @@ -1,6 +1,5 @@ import { PUBLIC_DATA_TREE_HEIGHT } from '@aztec/constants'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -import { Fr } from '@aztec/foundation/curves/bn254'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees'; @@ -43,36 +42,6 @@ export class PublicDataWitness { .transform(({ index, leafPreimage, siblingPath }) => new PublicDataWitness(index, leafPreimage, siblingPath)); } - /** - * Returns a field array representation of a public data witness. - * @returns A field array representation of a public data witness. - */ - public toFields(): Fr[] { - return [ - new Fr(this.index), - new Fr(this.leafPreimage.leaf.slot), - new Fr(this.leafPreimage.leaf.value), - new Fr(this.leafPreimage.nextIndex), - new Fr(this.leafPreimage.nextKey), - ...this.siblingPath.toFields(), - ]; - } - - /** - * Returns a representation of the public data witness as expected by intrinsic Noir deserialization. - */ - public toNoirRepresentation(): (string | string[])[] { - // TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js - return [ - new Fr(this.index).toString() as string, - new Fr(this.leafPreimage.leaf.slot).toString() as string, - new Fr(this.leafPreimage.leaf.value).toString() as string, - new Fr(this.leafPreimage.nextKey).toString() as string, - new Fr(this.leafPreimage.nextIndex).toString() as string, - this.siblingPath.toFields().map(fr => fr.toString()) as string[], - ]; - } - toBuffer(): Buffer { return serializeToBuffer([this.index, this.leafPreimage, this.siblingPath]); } diff --git a/yarn-project/stdlib/src/tx/execution_payload.ts b/yarn-project/stdlib/src/tx/execution_payload.ts index dff77d5b730a..04217585e02d 100644 --- a/yarn-project/stdlib/src/tx/execution_payload.ts +++ b/yarn-project/stdlib/src/tx/execution_payload.ts @@ -54,7 +54,8 @@ export function mergeExecutionPayloads(requests: ExecutionPayload[]): ExecutionP ); } - const feePayer = uniqueFeePayers.size === 1 ? AztecAddress.fromString(Array.from(uniqueFeePayers)[0]) : undefined; + const feePayer = + uniqueFeePayers.size === 1 ? AztecAddress.fromStringUnsafe(Array.from(uniqueFeePayers)[0]) : undefined; return new ExecutionPayload(calls, combinedAuthWitnesses, combinedCapsules, combinedExtraHashedArgs, feePayer); } diff --git a/yarn-project/stdlib/src/tx/global_variables.ts b/yarn-project/stdlib/src/tx/global_variables.ts index c346332232ea..a78f8c951a66 100644 --- a/yarn-project/stdlib/src/tx/global_variables.ts +++ b/yarn-project/stdlib/src/tx/global_variables.ts @@ -125,7 +125,7 @@ export class GlobalVariables { SlotNumber(reader.readU32()), reader.readField().toBigInt(), EthAddress.fromField(reader.readField()), - AztecAddress.fromField(reader.readField()), + AztecAddress.fromFieldUnsafe(reader.readField()), GasFees.fromFields(reader), ); } @@ -220,7 +220,7 @@ export class GlobalVariables { blockNumber: BlockNumber(randomInt(100_000)), slotNumber: SlotNumber(randomInt(100_000)), coinbase: EthAddress.random(), - feeRecipient: AztecAddress.fromField(Fr.random()), + feeRecipient: AztecAddress.fromFieldUnsafe(Fr.random()), gasFees: GasFees.random(), timestamp: BigInt(randomInt(100_000_000)), ...overrides, diff --git a/yarn-project/stdlib/src/tx/tx.test.ts b/yarn-project/stdlib/src/tx/tx.test.ts index d0bb5dc8dbeb..fd30aafcc5f1 100644 --- a/yarn-project/stdlib/src/tx/tx.test.ts +++ b/yarn-project/stdlib/src/tx/tx.test.ts @@ -45,7 +45,7 @@ describe('Tx', () => { }); } - 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/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/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 4230904a4ac1..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,10 +37,10 @@ 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: 'none', inputs: {}, output: Option.none(AztecAddress.ZERO) }, + { scenario: 'some', inputs: {}, output: Option.some(AztecAddress.fromNumberUnsafe(7)) }, + { scenario: 'none', inputs: {}, output: Option.none() }, ], test_labeled: [ { scenario: 'some', inputs: { slot: new Fr(10) }, output: Option.some(new Fr(1)) }, @@ -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_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 3d3fc4aa098e..28793585f543 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, @@ -42,20 +44,29 @@ import { } from '../constants.js'; import type { ForeignCallArgs, ForeignCallResult } from '../utils/encoding.js'; +// Spreading `ORACLE_REGISTRY` re-materializes its entries into `TXE_ORACLE_REGISTRY`'s inferred type, which names the +// protocol types below. Re-exporting them gives tsc a portable path to each instead of falling back to a deep +// node_modules path that breaks .d.ts portability (TS2742). +export type { ContractClassLogData, EmbeddedCurvePoint, TxEffectData } from '@aztec/pxe/simulator'; +export type { BlockHash } from '@aztec/stdlib/block'; +export type { MembershipWitness } from '@aztec/foundation/trees'; +export type { NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; + 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 +102,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 +131,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 +146,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 +175,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 = { @@ -337,7 +374,6 @@ export const TXE_ORACLE_REGISTRY = { { name: 'argsLength', type: U32 }, { name: 'args', type: ARRAY(FIELD) }, ], - returnType: ARRAY(FIELD), }), aztec_avm_staticCall: makeEntry({ @@ -348,10 +384,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_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 17139ca29023..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 @@ -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, @@ -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( @@ -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/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'; 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), }); } 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, 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;