use anyhow::Context;
use assert_matches::assert_matches;
use miden_protocol::account::auth::{AuthScheme, AuthSecretKey};
use miden_protocol::account::{Account, AccountBuilder};
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::{ConditionalAuthComponent, ERR_WRONG_ARGS_MSG};
use miden_standards::testing::mock_account::MockAccountExt;
use miden_tx::TransactionExecutorError;
use miden_tx::auth::{BasicAuthenticator, SigningInputs, TransactionAuthenticator};
use crate::{Auth, MockChain, TestTransactionBuilder, assert_transaction_executor_error};
pub const ERR_WRONG_ARGS: MasmError = MasmError::from_static_str(ERR_WRONG_ARGS_MSG);
#[tokio::test]
async fn test_auth_procedure_args() -> anyhow::Result<()> {
let account =
Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ConditionalAuthComponent);
let auth_args = [
Felt::new_unchecked(97),
Felt::new_unchecked(98),
Felt::new_unchecked(99),
ONE, ];
let tx_context = TestTransactionBuilder::new(account).auth_args(auth_args.into()).build()?;
tx_context.execute().await.context("failed to execute transaction")?;
Ok(())
}
#[tokio::test]
async fn test_auth_procedure_args_wrong_inputs() -> anyhow::Result<()> {
let account =
Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ConditionalAuthComponent);
let auth_args = [
ONE, Felt::new_unchecked(103),
Felt::new_unchecked(102),
Felt::new_unchecked(101),
];
let tx_context = TestTransactionBuilder::new(account).auth_args(auth_args.into()).build()?;
let execution_result = tx_context.execute().await;
assert_transaction_executor_error!(execution_result, ERR_WRONG_ARGS);
Ok(())
}
#[tokio::test]
async fn test_auth_procedure_called_from_wrong_context() -> anyhow::Result<()> {
let (auth_component, _) = Auth::IncrNonce.build_component();
let account = AccountBuilder::new([42; 32])
.with_auth_component(auth_component.clone())
.with_component(BasicWallet)
.build_existing()?;
let tx_script_source = "
@transaction_script
pub proc main
call.::incr_nonce::auth_incr_nonce
end
";
let tx_script = CodeBuilder::default()
.with_dynamically_linked_library(auth_component.component_code())?
.compile_tx_script(tx_script_source)?;
let tx_context = TestTransactionBuilder::new(account).tx_script(tx_script).build()?;
let execution_result = tx_context.execute().await;
assert_transaction_executor_error!(
execution_result,
ERR_EPILOGUE_AUTH_PROCEDURE_CALLED_FROM_WRONG_CONTEXT
);
Ok(())
}
#[tokio::test]
async fn test_auth_request_from_script_is_rejected() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let account = builder.add_existing_mock_account(Auth::BasicAuth {
auth_scheme: AuthScheme::Falcon512Poseidon2,
})?;
let chain = builder.build()?;
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();
let authenticator = BasicAuthenticator::new(core::slice::from_ref(&secret_key));
let signature = authenticator
.get_signature(pub_key_commitment, &SigningInputs::Blind(message))
.await?;
let tx_script_source = format!(
"
use {{AUTH_REQUEST_EVENT}} from miden::protocol::auth
@transaction_script
pub proc main
push.2
push.{pub_key_commitment}
push.{message}
# => [MESSAGE, PK_COMM, scheme_id]
emit.AUTH_REQUEST_EVENT
# unreachable once the request is rejected; keeps the script well-formed
dropw dropw drop
end
"
);
let tx_script = CodeBuilder::new().compile_tx_script(&tx_script_source)?;
let execution_result = chain
.build_tx_context(account.id(), &[], &[])?
.tx_script(tx_script)
.add_signature(pub_key_commitment, message, signature)
.build()?
.execute()
.await;
assert_matches!(
execution_result,
Err(TransactionExecutorError::AuthRequestOutsideAuthProcedure)
);
Ok(())
}
#[tokio::test]
async fn test_privileged_event_from_script_is_rejected() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let account = builder.add_existing_mock_account(Auth::BasicAuth {
auth_scheme: AuthScheme::Falcon512Poseidon2,
})?;
let chain = builder.build()?;
let tx_script_source = "
const START_EVENT = event(\"miden::protocol::epilogue::auth_proc_start\")
@transaction_script
pub proc main
emit.START_EVENT
end
";
let tx_script = CodeBuilder::new().compile_tx_script(tx_script_source)?;
let execution_result = chain
.build_tx_context(account.id(), &[], &[])?
.tx_script(tx_script)
.build()?
.execute()
.await;
assert_matches!(
execution_result,
Err(TransactionExecutorError::PrivilegedEventFromOutsideTransactionKernelContext(_))
);
Ok(())
}