From db8d0256add55b304f9fc2f19e17e635a945c379 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 3 Aug 2026 13:12:53 +0300 Subject: [PATCH 1/4] fix(tx): gate only signature production outside the auth procedure --- CHANGELOG.md | 1 + .../account_component/auth_request_probe.rs | 75 +++++++++++++++++ .../src/testing/account_component/mod.rs | 3 + .../src/kernel_tests/tx/test_auth.rs | 83 +++++++++++++------ crates/miden-tx/src/executor/exec_host.rs | 31 +++---- 5 files changed, 148 insertions(+), 45 deletions(-) create mode 100644 crates/miden-standards/src/testing/account_component/auth_request_probe.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ecdec5da4..44a730da73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ Added a new `INPUT_NOTE_INDEX_LOOKUP_EVENT` that lets transaction hosts provide - Added `NonFungibleFaucet::asset_status` API and `AssetStatus` enum (`NotIssued` / `Issued` / `Burned`) for querying a commitment's issuance status from account storage, mirroring the on-chain `get_asset_status` procedure ([#3222](https://github.com/0xMiden/protocol/pull/3222)). - Cleaned up `signature.masm` by removing redundant scheme-id validation and duplication, dropping the `neq.0` double-negation in `assert_supported_scheme_word`, and eliminating the unused `NUM_OF_APPROVERS_LOC` slot; also optimized `verify_signatures` to reuse the signer index and approver public key from the operand stack instead of round-tripping them through local memory ([#3230](https://github.com/0xMiden/protocol/pull/3230)). - Fixed the transaction executor host honoring `AuthRequest` events emitted outside the registered auth procedure, which let untrusted note or transaction scripts force the host to sign; signature production is now restricted to the authentication procedure ([#3233](https://github.com/0xMiden/protocol/pull/3233)). +- Narrowed the `AuthRequest` gate introduced in [#3233](https://github.com/0xMiden/protocol/pull/3233) to signature *production* only: verifying an externally-supplied signature (which never uses the account's private key) is now permitted outside the authentication procedure, while production stays restricted to it. This keeps the fix intact and unblocks account procedures that verify co-signer signatures before the epilogue ([#TBD](https://github.com/0xMiden/protocol/pull/TBD)). - Changed the default `LocalTransactionProver` hash function from `BLAKE3` to `Poseidon2`, added ECDSA variants for every signature-authenticated transaction benchmark, and restructured the time counting benchmark IDs to encode the signing scheme and proving hash function (e.g. `poseidon2/falcon/single-p2id-note`) ([#3152](https://github.com/0xMiden/protocol/pull/3152)). - `ConstantFeePolicy` now aborts fee estimation for note scripts without a fee schedule entry instead of estimating them to a fee of 0; to make a note script free, schedule an explicit 0 fee for it. Fee schedule entries are stored as `[fee_amount, 0, 0, 1]`, where the last element is a set-marker distinguishing scheduled entries from unset keys ([#3326](https://github.com/0xMiden/protocol/issues/3326)). - [BREAKING] Added a fee asset ID slot to the `FeeManager` (set via the required `FeeManagerBuilder::fee_faucet_id`, read via the FPI-callable `get_fee_asset_id`); the manager asserts the fee asset returned by the active fee policy matches it, and `collect_sponsored_fees` / `create_network_note_sponsorships` now take the expected fee asset ID as a stack input ([#3347](https://github.com/0xMiden/protocol/pull/3347)). diff --git a/crates/miden-standards/src/testing/account_component/auth_request_probe.rs b/crates/miden-standards/src/testing/account_component/auth_request_probe.rs new file mode 100644 index 0000000000..600b13e0c8 --- /dev/null +++ b/crates/miden-standards/src/testing/account_component/auth_request_probe.rs @@ -0,0 +1,75 @@ +use miden_protocol::account::component::AccountComponentMetadata; +use miden_protocol::account::{AccountComponent, AccountComponentCode}; +use miden_protocol::utils::sync::LazyLock; + +use crate::code_builder::CodeBuilder; + +/// MASM for a component whose `emit_auth_request` procedure reproduces the standard authentication +/// procedure's signature request - building a valid transaction summary and emitting the +/// `AUTH_REQUEST` event - but from a regular account procedure that runs outside the epilogue +/// authentication phase. +/// +/// It is used to test that the host gates signature *production* to the authentication procedure: +/// when no signature is pre-supplied in the advice provider, the event drives production and must +/// be rejected outside the auth procedure. +const AUTH_REQUEST_PROBE_CODE: &str = " + use miden::standards::auth + use {AUTH_REQUEST_EVENT} from miden::protocol::auth + + #! Builds a transaction summary for the (empty) account delta and emits an `AUTH_REQUEST` for it. + #! + #! Inputs: [PK_COMM, scheme_id] + #! Outputs: [] + @account_procedure + pub proc emit_auth_request + # Prepend seven zero user params so the summary layout matches the auth procedure's. + push.0.0.0.0.0.0.0 + # => [user_params(7), PK_COMM, scheme_id] + + exec.auth::create_tx_summary + # => [SUMMARY(6 words), PK_COMM, scheme_id] + + exec.auth::hash_and_insert_tx_summary + # => [MESSAGE, PK_COMM, scheme_id] + + # Reproduces `auth::authenticate_transaction`'s request. With no pre-supplied signature the + # host must produce one, which is only allowed inside the auth procedure; here it is not. + emit.AUTH_REQUEST_EVENT + + # Reached only if the request is honored; in the production path the transaction aborts above. + dropw dropw drop + end +"; + +static AUTH_REQUEST_PROBE_PACKAGE: LazyLock = LazyLock::new(|| { + CodeBuilder::default() + .compile_component_code("mock::auth_request_probe", AUTH_REQUEST_PROBE_CODE) + .expect("auth request probe code should be valid") +}); + +/// A mock [`AccountComponent`] used to exercise the host's signature-production gating. +/// +/// It exposes a single `emit_auth_request` account procedure that emits an `AUTH_REQUEST` event +/// from outside the authentication procedure. Pair it with an auth component (e.g. +/// [`super::IncrNonceAuthComponent`]) when building an account. +pub struct AuthRequestProbeComponent; + +impl AuthRequestProbeComponent { + /// Returns the compiled component code, so a transaction script can link against it and `call.` + /// the `emit_auth_request` procedure. + pub fn code() -> &'static AccountComponentCode { + &AUTH_REQUEST_PROBE_PACKAGE + } +} + +impl From for AccountComponent { + fn from(_: AuthRequestProbeComponent) -> Self { + let metadata = AccountComponentMetadata::new("miden::testing::auth_request_probe") + .with_description( + "Testing component that emits AUTH_REQUEST outside the auth procedure", + ); + + AccountComponent::new(AUTH_REQUEST_PROBE_PACKAGE.clone(), vec![], metadata) + .expect("component should be valid") + } +} diff --git a/crates/miden-standards/src/testing/account_component/mod.rs b/crates/miden-standards/src/testing/account_component/mod.rs index e275eae553..eba8a69a3c 100644 --- a/crates/miden-standards/src/testing/account_component/mod.rs +++ b/crates/miden-standards/src/testing/account_component/mod.rs @@ -1,6 +1,9 @@ mod incr_nonce; pub use incr_nonce::IncrNonceAuthComponent; +mod auth_request_probe; +pub use auth_request_probe::AuthRequestProbeComponent; + mod conditional_auth; pub use conditional_auth::{ConditionalAuthComponent, ERR_WRONG_ARGS_MSG}; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index 84619ed244..d3834f7025 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -8,7 +8,11 @@ use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDAT use miden_protocol::{Felt, ONE, Word}; use miden_standards::account::wallets::BasicWallet; use miden_standards::code_builder::CodeBuilder; -use miden_standards::testing::account_component::{ConditionalAuthComponent, ERR_WRONG_ARGS_MSG}; +use miden_standards::testing::account_component::{ + AuthRequestProbeComponent, + ConditionalAuthComponent, + ERR_WRONG_ARGS_MSG, +}; use miden_standards::testing::mock_account::MockAccountExt; use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; @@ -103,28 +107,62 @@ async fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { Ok(()) } -/// Regression test: an untrusted transaction script must not be able to force the host to produce a -/// signature. -/// -/// The script emits `AUTH_REQUEST` directly, supplying a precomputed message on the stack and a -/// matching signature in the advice map. This deliberately bypasses `auth::create_tx_summary` -/// (which computes `account::compute_delta_commitment` and is now gated to the account context, so -/// it cannot be called from a script) and exercises the host's context check in isolation: the -/// request must be rejected with `AuthRequestOutsideAuthProcedure` because it originates outside -/// the authentication procedure. The check runs before the signature is validated, so a throwaway -/// key is sufficient - the test is intentionally artificial and only asserts that the original -/// error path is still reachable. +/// Regression test: signature production must not be forced from outside the authentication +/// procedure. #[tokio::test] -async fn test_auth_request_from_script_is_rejected() -> anyhow::Result<()> { +async fn test_auth_request_production_outside_auth_procedure_is_rejected() -> anyhow::Result<()> { let mut builder = MockChain::builder(); - let account = builder.add_existing_mock_account(Auth::BasicAuth { - auth_scheme: AuthScheme::Falcon512Poseidon2, - })?; + let account = builder.add_existing_account_from_components( + Auth::IncrNonce, + [AuthRequestProbeComponent.into()], + )?; let chain = builder.build()?; - // Precompute the AUTH_REQUEST inputs instead of building the summary on-chain. A throwaway key - // signs an arbitrary message; the resulting signature is placed in the advice map keyed by - // `merge(pub_key_commitment, message)`, which is exactly where the host looks it up. + // A dummy public key commitment; the request is rejected before any signature is verified. + let pub_key_commitment = Word::from([1u32, 2, 3, 4]); + let tx_script_source = format!( + " + @transaction_script + pub proc main + push.2 + push.{pub_key_commitment} + # => [PK_COMM, scheme_id] + + call.::mock::auth_request_probe::emit_auth_request + end + " + ); + + let tx_script = CodeBuilder::new() + .with_dynamically_linked_package(AuthRequestProbeComponent::code())? + .compile_tx_script(&tx_script_source)?; + + let execution_result = chain + .build_transaction(account.id()) + .tx_script(tx_script) + .build()? + .execute() + .await; + + assert_matches!( + execution_result, + Err(TransactionExecutorError::AuthRequestOutsideAuthProcedure) + ); + + Ok(()) +} + +/// Complements [`test_auth_request_production_outside_auth_procedure_is_rejected`]: verifying an +/// externally supplied signature is always allowed, even outside the authentication procedure. +#[tokio::test] +async fn test_auth_request_verification_outside_auth_procedure_is_allowed() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account = builder.add_existing_mock_account(Auth::IncrNonce)?; + let chain = builder.build()?; + + // A throwaway key signs an arbitrary message; the signature is placed in the advice map keyed + // by `merge(pub_key_commitment, message)`, which is exactly where the host looks it up, so the + // event resolves to the verification path rather than production. let message = Word::from([1u32, 2, 3, 4]); let secret_key = AuthSecretKey::new_falcon512_poseidon2(); let pub_key_commitment = secret_key.public_key().to_commitment(); @@ -147,7 +185,7 @@ async fn test_auth_request_from_script_is_rejected() -> anyhow::Result<()> { emit.AUTH_REQUEST_EVENT - # unreachable once the request is rejected; keeps the script well-formed + # drop the request inputs; the pushed signature stays on the advice stack, unused dropw dropw drop end " @@ -163,10 +201,7 @@ async fn test_auth_request_from_script_is_rejected() -> anyhow::Result<()> { .execute() .await; - assert_matches!( - execution_result, - Err(TransactionExecutorError::AuthRequestOutsideAuthProcedure) - ); + assert_matches!(execution_result, Ok(_)); Ok(()) } diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index e3746d54b2..438550cda2 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -98,12 +98,6 @@ where generated_signatures: BTreeMap>, /// Whether execution is currently inside the authentication procedure. - /// - /// The epilogue wraps the auth procedure between the `EpilogueAuthProcStart` and - /// `EpilogueAuthProcEnd` events, so this flag is `true` only while the registered auth - /// procedure is running. It is used to reject `AuthRequest` events emitted from any other - /// context (e.g. untrusted note or transaction scripts), which must never trigger signature - /// production. in_auth_procedure: bool, /// The source manager to track source code file span information, improving any MASM related @@ -600,22 +594,17 @@ where TransactionEvent::AuthRequest { pub_key_commitment, tx_summary_or_signature, - } => { - // Signature production is only permitted while the registered auth procedure - // is executing. An `AuthRequest` emitted from any other context (e.g. an - // untrusted note or transaction script) must not force the host to sign. - if !self.in_auth_procedure { - Err(TransactionKernelError::AuthRequestOutsideAuthProcedure) - } else { - match tx_summary_or_signature { - TxSummaryOrSignature::Signature(signature) => { - Ok(self.base_host.on_auth_requested(signature)) - }, - TxSummaryOrSignature::TxSummary(tx_summary) => { - self.on_auth_requested(pub_key_commitment, tx_summary).await - }, + } => match tx_summary_or_signature { + TxSummaryOrSignature::Signature(signature) => { + Ok(self.base_host.on_auth_requested(signature)) + }, + TxSummaryOrSignature::TxSummary(tx_summary) => { + if !self.in_auth_procedure { + Err(TransactionKernelError::AuthRequestOutsideAuthProcedure) + } else { + self.on_auth_requested(pub_key_commitment, tx_summary).await } - } + }, }, // This always returns an error to abort the transaction. From 4f0c39c0fe39a3db0952443cb10e23d2cdbb13b9 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 3 Aug 2026 13:16:06 +0300 Subject: [PATCH 2/4] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a730da73..a91c6b9130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,7 +91,7 @@ Added a new `INPUT_NOTE_INDEX_LOOKUP_EVENT` that lets transaction hosts provide - Added `NonFungibleFaucet::asset_status` API and `AssetStatus` enum (`NotIssued` / `Issued` / `Burned`) for querying a commitment's issuance status from account storage, mirroring the on-chain `get_asset_status` procedure ([#3222](https://github.com/0xMiden/protocol/pull/3222)). - Cleaned up `signature.masm` by removing redundant scheme-id validation and duplication, dropping the `neq.0` double-negation in `assert_supported_scheme_word`, and eliminating the unused `NUM_OF_APPROVERS_LOC` slot; also optimized `verify_signatures` to reuse the signer index and approver public key from the operand stack instead of round-tripping them through local memory ([#3230](https://github.com/0xMiden/protocol/pull/3230)). - Fixed the transaction executor host honoring `AuthRequest` events emitted outside the registered auth procedure, which let untrusted note or transaction scripts force the host to sign; signature production is now restricted to the authentication procedure ([#3233](https://github.com/0xMiden/protocol/pull/3233)). -- Narrowed the `AuthRequest` gate introduced in [#3233](https://github.com/0xMiden/protocol/pull/3233) to signature *production* only: verifying an externally-supplied signature (which never uses the account's private key) is now permitted outside the authentication procedure, while production stays restricted to it. This keeps the fix intact and unblocks account procedures that verify co-signer signatures before the epilogue ([#TBD](https://github.com/0xMiden/protocol/pull/TBD)). +- Fixed the `AuthRequest` gate introduced in [#3233](https://github.com/0xMiden/protocol/pull/3233) to restrict only signature production ([#3471](https://github.com/0xMiden/protocol/pull/3471)). - Changed the default `LocalTransactionProver` hash function from `BLAKE3` to `Poseidon2`, added ECDSA variants for every signature-authenticated transaction benchmark, and restructured the time counting benchmark IDs to encode the signing scheme and proving hash function (e.g. `poseidon2/falcon/single-p2id-note`) ([#3152](https://github.com/0xMiden/protocol/pull/3152)). - `ConstantFeePolicy` now aborts fee estimation for note scripts without a fee schedule entry instead of estimating them to a fee of 0; to make a note script free, schedule an explicit 0 fee for it. Fee schedule entries are stored as `[fee_amount, 0, 0, 1]`, where the last element is a set-marker distinguishing scheduled entries from unset keys ([#3326](https://github.com/0xMiden/protocol/issues/3326)). - [BREAKING] Added a fee asset ID slot to the `FeeManager` (set via the required `FeeManagerBuilder::fee_faucet_id`, read via the FPI-callable `get_fee_asset_id`); the manager asserts the fee asset returned by the active fee policy matches it, and `collect_sponsored_fees` / `create_network_note_sponsorships` now take the expected fee asset ID as a stack input ([#3347](https://github.com/0xMiden/protocol/pull/3347)). From 272d53e130f374b12f0f5ba5f4bebef011821593 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 3 Aug 2026 13:19:37 +0300 Subject: [PATCH 3/4] fix in_auth_procedure: bool comment --- crates/miden-tx/src/executor/exec_host.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 438550cda2..e999e4103a 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -98,6 +98,14 @@ where generated_signatures: BTreeMap>, /// Whether execution is currently inside the authentication procedure. + /// + /// The epilogue wraps the auth procedure between the `EpilogueAuthProcStart` and + /// `EpilogueAuthProcEnd` events, so this flag is `true` only while the registered auth + /// procedure is running. It is used to reject signature *production* requested from any other + /// context (e.g. untrusted note or transaction scripts): an `AuthRequest` with no pre-supplied + /// signature makes the authenticator sign with the account's key and must never be honored + /// outside the auth procedure. Verifying an externally-supplied signature does not touch the + /// private key and is always allowed. in_auth_procedure: bool, /// The source manager to track source code file span information, improving any MASM related From 698ddeb8cb254ff0e82c026a14bcc2b0f4544e11 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 3 Aug 2026 15:16:52 +0300 Subject: [PATCH 4/4] remove auth request probe component --- .../account_component/auth_request_probe.rs | 75 ------------------- .../src/testing/account_component/mod.rs | 3 - .../src/kernel_tests/tx/test_auth.rs | 61 +++++++++++---- 3 files changed, 46 insertions(+), 93 deletions(-) delete mode 100644 crates/miden-standards/src/testing/account_component/auth_request_probe.rs diff --git a/crates/miden-standards/src/testing/account_component/auth_request_probe.rs b/crates/miden-standards/src/testing/account_component/auth_request_probe.rs deleted file mode 100644 index 600b13e0c8..0000000000 --- a/crates/miden-standards/src/testing/account_component/auth_request_probe.rs +++ /dev/null @@ -1,75 +0,0 @@ -use miden_protocol::account::component::AccountComponentMetadata; -use miden_protocol::account::{AccountComponent, AccountComponentCode}; -use miden_protocol::utils::sync::LazyLock; - -use crate::code_builder::CodeBuilder; - -/// MASM for a component whose `emit_auth_request` procedure reproduces the standard authentication -/// procedure's signature request - building a valid transaction summary and emitting the -/// `AUTH_REQUEST` event - but from a regular account procedure that runs outside the epilogue -/// authentication phase. -/// -/// It is used to test that the host gates signature *production* to the authentication procedure: -/// when no signature is pre-supplied in the advice provider, the event drives production and must -/// be rejected outside the auth procedure. -const AUTH_REQUEST_PROBE_CODE: &str = " - use miden::standards::auth - use {AUTH_REQUEST_EVENT} from miden::protocol::auth - - #! Builds a transaction summary for the (empty) account delta and emits an `AUTH_REQUEST` for it. - #! - #! Inputs: [PK_COMM, scheme_id] - #! Outputs: [] - @account_procedure - pub proc emit_auth_request - # Prepend seven zero user params so the summary layout matches the auth procedure's. - push.0.0.0.0.0.0.0 - # => [user_params(7), PK_COMM, scheme_id] - - exec.auth::create_tx_summary - # => [SUMMARY(6 words), PK_COMM, scheme_id] - - exec.auth::hash_and_insert_tx_summary - # => [MESSAGE, PK_COMM, scheme_id] - - # Reproduces `auth::authenticate_transaction`'s request. With no pre-supplied signature the - # host must produce one, which is only allowed inside the auth procedure; here it is not. - emit.AUTH_REQUEST_EVENT - - # Reached only if the request is honored; in the production path the transaction aborts above. - dropw dropw drop - end -"; - -static AUTH_REQUEST_PROBE_PACKAGE: LazyLock = LazyLock::new(|| { - CodeBuilder::default() - .compile_component_code("mock::auth_request_probe", AUTH_REQUEST_PROBE_CODE) - .expect("auth request probe code should be valid") -}); - -/// A mock [`AccountComponent`] used to exercise the host's signature-production gating. -/// -/// It exposes a single `emit_auth_request` account procedure that emits an `AUTH_REQUEST` event -/// from outside the authentication procedure. Pair it with an auth component (e.g. -/// [`super::IncrNonceAuthComponent`]) when building an account. -pub struct AuthRequestProbeComponent; - -impl AuthRequestProbeComponent { - /// Returns the compiled component code, so a transaction script can link against it and `call.` - /// the `emit_auth_request` procedure. - pub fn code() -> &'static AccountComponentCode { - &AUTH_REQUEST_PROBE_PACKAGE - } -} - -impl From for AccountComponent { - fn from(_: AuthRequestProbeComponent) -> Self { - let metadata = AccountComponentMetadata::new("miden::testing::auth_request_probe") - .with_description( - "Testing component that emits AUTH_REQUEST outside the auth procedure", - ); - - AccountComponent::new(AUTH_REQUEST_PROBE_PACKAGE.clone(), vec![], metadata) - .expect("component should be valid") - } -} diff --git a/crates/miden-standards/src/testing/account_component/mod.rs b/crates/miden-standards/src/testing/account_component/mod.rs index eba8a69a3c..e275eae553 100644 --- a/crates/miden-standards/src/testing/account_component/mod.rs +++ b/crates/miden-standards/src/testing/account_component/mod.rs @@ -1,9 +1,6 @@ mod incr_nonce; pub use incr_nonce::IncrNonceAuthComponent; -mod auth_request_probe; -pub use auth_request_probe::AuthRequestProbeComponent; - mod conditional_auth; pub use conditional_auth::{ConditionalAuthComponent, ERR_WRONG_ARGS_MSG}; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs index d3834f7025..c0a3aeee82 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_auth.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_auth.rs @@ -1,18 +1,15 @@ use anyhow::Context; use assert_matches::assert_matches; use miden_protocol::account::auth::{AuthScheme, AuthSecretKey}; -use miden_protocol::account::{Account, AccountBuilder}; +use miden_protocol::account::component::AccountComponentMetadata; +use miden_protocol::account::{Account, AccountBuilder, AccountComponent}; use miden_protocol::errors::MasmError; use miden_protocol::errors::tx_kernel::ERR_EPILOGUE_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT; use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; use miden_protocol::{Felt, ONE, Word}; use miden_standards::account::wallets::BasicWallet; use miden_standards::code_builder::CodeBuilder; -use miden_standards::testing::account_component::{ - AuthRequestProbeComponent, - ConditionalAuthComponent, - ERR_WRONG_ARGS_MSG, -}; +use miden_standards::testing::account_component::{ConditionalAuthComponent, ERR_WRONG_ARGS_MSG}; use miden_standards::testing::mock_account::MockAccountExt; use miden_tx::TransactionExecutorError; use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator}; @@ -109,13 +106,46 @@ async fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> { /// Regression test: signature production must not be forced from outside the authentication /// procedure. +/// +/// The account exposes an `emit_auth_request` procedure that builds a real transaction summary and +/// emits `AUTH_REQUEST` for it, exactly like the standard auth procedure - but it runs as a normal +/// account procedure invoked from the transaction script, i.e. outside the epilogue authentication +/// phase. No signature is pre-supplied, so the event drives production, which must be rejected with +/// `AuthRequestOutsideAuthProcedure`. #[tokio::test] async fn test_auth_request_production_outside_auth_procedure_is_rejected() -> anyhow::Result<()> { - let mut builder = MockChain::builder(); - let account = builder.add_existing_account_from_components( - Auth::IncrNonce, - [AuthRequestProbeComponent.into()], + let probe_code = CodeBuilder::default().compile_component_code( + "mock::auth_request_probe", + " + use miden::standards::auth + use {AUTH_REQUEST_EVENT} from miden::protocol::auth + + #! Inputs: [PK_COMM, scheme_id] + @account_procedure + pub proc emit_auth_request + # Prepend seven zero user params so the summary layout matches the auth procedure's. + push.0.0.0.0.0.0.0 + exec.auth::create_tx_summary + exec.auth::hash_and_insert_tx_summary + # => [MESSAGE, PK_COMM, scheme_id] + + # With no pre-supplied signature the host must produce one, which is only allowed inside + # the auth procedure; here it is not, so the transaction aborts. + emit.AUTH_REQUEST_EVENT + + dropw dropw drop + end + ", + )?; + let probe_component = AccountComponent::new( + probe_code, + vec![], + AccountComponentMetadata::new("mock::auth_request_probe"), )?; + + let mut builder = MockChain::builder(); + let account = + builder.add_existing_account_from_components(Auth::IncrNonce, [probe_component.clone()])?; let chain = builder.build()?; // A dummy public key commitment; the request is rejected before any signature is verified. @@ -134,7 +164,7 @@ async fn test_auth_request_production_outside_auth_procedure_is_rejected() -> an ); let tx_script = CodeBuilder::new() - .with_dynamically_linked_package(AuthRequestProbeComponent::code())? + .with_dynamically_linked_package(probe_component.component_code())? .compile_tx_script(&tx_script_source)?; let execution_result = chain @@ -193,15 +223,16 @@ async fn test_auth_request_verification_outside_auth_procedure_is_allowed() -> a let tx_script = CodeBuilder::new().compile_tx_script(&tx_script_source)?; - let execution_result = chain + // The request must be honored (no `AuthRequestOutsideAuthProcedure`), so the transaction runs + // to completion under the trivial `IncrNonce` auth. + chain .build_transaction(account.id()) .tx_script(tx_script) .add_signature(pub_key_commitment, message, signature) .build()? .execute() - .await; - - assert_matches!(execution_result, Ok(_)); + .await + .context("verifying an externally-supplied signature outside the auth procedure should be allowed")?; Ok(()) }