miden-standards 0.17.0

Standards of the Miden protocol
Documentation
use assert_matches::assert_matches;
use miden_protocol::Word;
use miden_protocol::account::AccountBuilder;
use miden_protocol::account::auth::{self, AuthSecretKey, PublicKeyCommitment};
use miden_protocol::asset::NonFungibleAsset;
use miden_protocol::crypto::rand::RandomCoin;
use miden_protocol::errors::NoteError;
use miden_protocol::note::NoteType;
use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;

use crate::account::auth::{
    Approver,
    ApproverSet,
    AuthMultisig,
    AuthMultisigConfig,
    AuthSingleSig,
    AuthTxFeeCollector,
    NoAuth,
};
use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
use crate::account::wallets::BasicWallet;
use crate::note::SwapNote;
use crate::testing::account_component::{IncrNonceAuthComponent, MockAccountComponent};
use crate::testing::account_interface::get_public_keys_from_account;

/// Checks that building a SWAP note should fail if the requested asset is the same as the
/// offered asset.
#[test]
fn test_required_asset_same_as_offered() {
    let offered_asset = NonFungibleAsset::mock(&[1, 2, 3, 4]);
    let requested_asset = NonFungibleAsset::mock(&[1, 2, 3, 4]);

    let result = SwapNote::builder()
        .sender(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap())
        .offered_asset(offered_asset)
        .requested_asset(requested_asset)
        .note_type(NoteType::Public)
        .payback_note_type(NoteType::Public)
        .generate_serial_number(&mut RandomCoin::new(Word::from([1, 2, 3, 4u32])))
        .build();

    assert_matches!(result, Err(NoteError::Other { error_msg, .. }) if error_msg == "requested asset same as offered asset".into());
}

// HELPERS
// ================================================================================================

fn get_mock_falcon_auth_component() -> AuthSingleSig {
    let mock_word = Word::from([0, 1, 2, 3u32]);
    let mock_public_key = PublicKeyCommitment::from(mock_word);
    AuthSingleSig::new(Approver::new(mock_public_key, auth::AuthScheme::Falcon512Poseidon2))
}

// CUSTOM COMPONENT IDENTIFICATION TESTS
// ================================================================================================

/// An account assembled purely from standard components must not be reported with a custom
/// component, so that the presence of `Custom` is a reliable signal for non-standard code.
#[test]
fn test_standard_only_account_has_no_custom_component() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let standard_account = AccountBuilder::new(mock_seed)
        .with_component(NoAuth)
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create standard-only account");

    let interface = AccountInterface::from_account(&standard_account);

    assert!(interface.components().contains(&AccountComponentInterface::BasicWallet));
    assert!(interface.components().contains(&AccountComponentInterface::AuthNoAuth));
    assert!(
        !interface
            .components()
            .iter()
            .any(|component| matches!(component, AccountComponentInterface::Custom(_)))
    );
}

/// An account exporting non-standard procedures must report them in a custom component.
#[test]
fn test_custom_component_holds_the_non_standard_procedures() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let custom_account = AccountBuilder::new(mock_seed)
        .with_component(NoAuth)
        .with_component(MockAccountComponent::with_empty_slots())
        .build_existing()
        .expect("failed to create account with a custom component");

    let interface = AccountInterface::from_account(&custom_account);

    let custom_procedures = interface
        .components()
        .iter()
        .find_map(|component| match component {
            AccountComponentInterface::Custom(proc_roots) => Some(proc_roots),
            _ => None,
        })
        .expect("account with non-standard procedures should report a custom component");

    assert!(!custom_procedures.is_empty());
    // The procedures of the identified standard component are not reported as custom.
    for proc_root in NoAuth::code().procedure_roots() {
        assert!(!custom_procedures.contains(&proc_root));
    }
}

// AUTH COMPONENT IDENTIFICATION TESTS
// ================================================================================================

#[test]
fn test_account_interface_identifies_single_sig_auth() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let wallet_account = AccountBuilder::new(mock_seed)
        .with_component(get_mock_falcon_auth_component())
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create wallet account");

    let wallet_account_interface = AccountInterface::from_account(&wallet_account);

    assert!(matches!(
        wallet_account_interface.auth_component(),
        AccountComponentInterface::AuthSingleSig
    ));
}

#[test]
fn test_account_interface_identifies_no_auth() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let no_auth_account = AccountBuilder::new(mock_seed)
        .with_component(NoAuth)
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create no-auth account");

    let no_auth_account_interface = AccountInterface::from_account(&no_auth_account);

    assert!(matches!(
        no_auth_account_interface.auth_component(),
        AccountComponentInterface::AuthNoAuth
    ));
}

#[test]
fn test_account_interface_identifies_tx_fee_collector_auth() {
    let mock_seed = Word::from([4, 5, 6, 7u32]).as_bytes();
    let collector_account = AccountBuilder::new(mock_seed)
        .with_component(AuthTxFeeCollector::from_public_key(
            AuthSecretKey::new_ecdsa_k256_keccak().public_key(),
        ))
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create fee collector account");

    let interface = AccountInterface::from_account(&collector_account);

    assert!(matches!(
        interface.auth_component(),
        AccountComponentInterface::AuthTxFeeCollector
    ));
}

#[test]
fn test_account_interface_identifies_custom_auth() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let custom_auth_account = AccountBuilder::new(mock_seed)
        .with_component(IncrNonceAuthComponent)
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create custom-auth account");

    // `AccountCode` places the authentication procedure at index 0.
    let auth_procedure = custom_auth_account.code().procedures()[0];

    let custom_auth_account_interface = AccountInterface::from_account(&custom_auth_account);

    assert_matches!(
        custom_auth_account_interface.auth_component(),
        AccountComponentInterface::CustomAuth(proc_root) if *proc_root == auth_procedure
    );

    // The authentication procedure must not also be reported as a plain custom procedure.
    for component in custom_auth_account_interface.components() {
        if let AccountComponentInterface::Custom(proc_roots) = component {
            assert!(!proc_roots.contains(&auth_procedure));
        }
    }
}

#[test]
fn test_basic_wallet_is_not_an_auth_component() {
    assert!(!AccountComponentInterface::BasicWallet.is_auth_component());
    assert!(AccountComponentInterface::AuthSingleSig.is_auth_component());
    assert!(AccountComponentInterface::AuthNoAuth.is_auth_component());
}

#[test]
fn test_public_key_extraction_regular_account() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let wallet_account = AccountBuilder::new(mock_seed)
        .with_component(get_mock_falcon_auth_component())
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create wallet account");

    // Test public key extraction like miden-client would do
    let pub_keys = get_public_keys_from_account(&wallet_account);

    assert_eq!(pub_keys.len(), 1);
    assert_eq!(pub_keys[0], Word::from([0, 1, 2, 3u32]));
}

#[test]
fn test_public_key_extraction_multisig_account() {
    // Create test public keys
    let pub_key_1 = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0]));
    let pub_key_2 = PublicKeyCommitment::from(Word::from([2u32, 0, 0, 0]));
    let pub_key_3 = PublicKeyCommitment::from(Word::from([3u32, 0, 0, 0]));

    let approvers = vec![
        Approver::new(pub_key_1, auth::AuthScheme::Falcon512Poseidon2),
        Approver::new(pub_key_2, auth::AuthScheme::Falcon512Poseidon2),
        Approver::new(pub_key_3, auth::AuthScheme::EcdsaK256Keccak),
    ];

    let threshold = 2u32;

    // Create multisig component
    let approver_set = ApproverSet::new(approvers.clone(), threshold).unwrap();
    let multisig_component = AuthMultisig::new(AuthMultisigConfig::new(approver_set))
        .expect("multisig component creation failed");

    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let multisig_account = AccountBuilder::new(mock_seed)
        .with_component(multisig_component)
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create multisig account");

    let pub_keys = get_public_keys_from_account(&multisig_account);

    assert_eq!(pub_keys.len(), 3);
    assert_eq!(pub_keys[0], Word::from([1u32, 0, 0, 0]));
    assert_eq!(pub_keys[1], Word::from([2u32, 0, 0, 0]));
    assert_eq!(pub_keys[2], Word::from([3u32, 0, 0, 0]));
}

#[test]
fn test_public_key_extraction_no_auth_account() {
    let mock_seed = Word::from([0, 1, 2, 3u32]).as_bytes();
    let no_auth_account = AccountBuilder::new(mock_seed)
        .with_component(NoAuth)
        .with_component(BasicWallet)
        .build_existing()
        .expect("failed to create no-auth account");

    // Test public key extraction
    let pub_keys = get_public_keys_from_account(&no_auth_account);

    // NoAuth should not contribute any public keys
    assert_eq!(pub_keys.len(), 0);
}