#![cfg(feature = "ed25519")]
use aptos_sdk::account::{Account, Ed25519Account, Ed25519SingleKeyAccount};
use aptos_sdk::crypto::{AnyPublicKey, Ed25519PrivateKey, sha3_256};
use aptos_sdk::transaction::TransactionPayload;
use aptos_sdk::transaction::authenticator::{
AccountAuthenticator, Ed25519PublicKey as AuthEd25519PublicKey,
Ed25519Signature as AuthEd25519Signature,
};
use aptos_sdk::transaction::payload::EntryFunction;
use aptos_sdk::transaction::types::{RawTransaction, RawTransactionOrderless};
use aptos_sdk::types::{AccountAddress, ChainId, TypeTag};
const FIXED_ED25519_SEED: [u8; 32] = [1u8; 32];
const FIXED_EXPIRATION: u64 = 9_999_999_999;
fn fixed_apt_transfer_payload() -> TransactionPayload {
let recipient = AccountAddress::from_hex("0x2").unwrap();
TransactionPayload::EntryFunction(EntryFunction::apt_transfer(recipient, 1_000_000).unwrap())
}
fn fixed_raw_transaction() -> RawTransaction {
RawTransaction {
sender: AccountAddress::ONE,
sequence_number: 42,
payload: fixed_apt_transfer_payload(),
max_gas_amount: 100_000,
gas_unit_price: 100,
expiration_timestamp_secs: FIXED_EXPIRATION,
chain_id: ChainId::testnet(),
}
}
#[test]
fn raw_transaction_signing_message_prefix_is_correct() {
let raw = fixed_raw_transaction();
let msg = raw.signing_message().expect("signing message must build");
let expected_prefix = sha3_256(b"APTOS::RawTransaction");
assert_eq!(
&msg[..32],
&expected_prefix[..],
"domain prefix must be SHA3-256(\"APTOS::RawTransaction\")",
);
let body = raw.to_bcs().unwrap();
assert_eq!(&msg[32..], &body[..]);
}
#[test]
fn raw_transaction_bcs_layout_is_reproducible() {
let raw = fixed_raw_transaction();
let hex = const_hex::encode(raw.to_bcs().unwrap());
assert!(
hex.starts_with(
"0000000000000000000000000000000000000000000000000000000000000001\
2a00000000000000\
02"
.split_whitespace()
.collect::<String>()
.as_str()
),
"leading bytes (sender + sequence_number + payload variant) drifted: {hex}",
);
assert!(hex.ends_with("02"), "chain id byte drifted: {hex}");
assert!(
hex.contains("2a00000000000000"),
"sequence_number little-endian encoding drifted: {hex}",
);
assert!(
hex.contains("a086010000000000"),
"max_gas_amount little-endian encoding drifted: {hex}",
);
assert!(
hex.contains("6400000000000000"),
"gas_unit_price little-endian encoding drifted: {hex}",
);
assert!(
hex.contains("ffe30b5402000000"),
"expiration_timestamp_secs little-endian encoding drifted: {hex}",
);
}
#[test]
fn orderless_signing_message_uses_distinct_prefix() {
let raw = RawTransactionOrderless::with_nonce(
AccountAddress::ONE,
vec![0xde, 0xad, 0xbe, 0xef],
fixed_apt_transfer_payload(),
100_000,
100,
FIXED_EXPIRATION,
ChainId::testnet(),
);
let msg = raw.signing_message().unwrap();
let expected_prefix = sha3_256(b"APTOS::RawTransactionOrderless");
assert_eq!(&msg[..32], &expected_prefix[..]);
assert_ne!(expected_prefix, sha3_256(b"APTOS::RawTransaction"));
}
#[test]
fn account_authenticator_legacy_ed25519_bcs_layout_is_pinned() {
let account = Ed25519Account::from_private_key(
Ed25519PrivateKey::from_bytes(&FIXED_ED25519_SEED).unwrap(),
);
let message = b"fixture message";
let sig_bytes: [u8; 64] = account
.sign(message)
.unwrap()
.try_into()
.expect("ed25519 sig length = 64");
let mut pk_arr = [0u8; 32];
pk_arr.copy_from_slice(&account.public_key_bytes());
let auth = AccountAuthenticator::Ed25519 {
public_key: AuthEd25519PublicKey(pk_arr),
signature: AuthEd25519Signature(sig_bytes),
};
let bytes = aptos_bcs::to_bytes(&auth).unwrap();
let hex = const_hex::encode(&bytes);
assert_eq!(bytes.len(), 1 + 1 + 32 + 1 + 64, "BCS length mismatch");
assert!(
hex.starts_with("0020"),
"variant byte (0x00) + ULEB128(32) prefix drifted: {hex}",
);
let sig_header_offset = (1 + 1 + 32) * 2;
assert_eq!(
&hex[sig_header_offset..sig_header_offset + 2],
"40",
"ULEB128(64) before signature drifted: {hex}",
);
}
#[test]
fn account_authenticator_single_key_ed25519_bcs_layout_is_pinned() {
let account = Ed25519SingleKeyAccount::from_private_key(
Ed25519PrivateKey::from_bytes(&FIXED_ED25519_SEED).unwrap(),
);
let pk = account.public_key();
let any_pk_bytes = AnyPublicKey::ed25519(pk).to_bcs_bytes();
assert_eq!(any_pk_bytes.len(), 1 + 1 + 32);
assert_eq!(
any_pk_bytes[0], 0x00,
"AnyPublicKey::Ed25519 must be variant 0",
);
assert_eq!(any_pk_bytes[1], 32, "ULEB128(32) length prefix");
let synthetic_sig = {
let mut s = vec![0u8, 64u8];
s.extend_from_slice(&[0u8; 64]);
s
};
let auth = AccountAuthenticator::SingleKey {
public_key: any_pk_bytes.clone(),
signature: synthetic_sig,
};
let outer = aptos_bcs::to_bytes(&auth).unwrap();
let outer_hex = const_hex::encode(&outer);
assert_eq!(outer.len(), 1 + 34 + 66);
assert!(
outer_hex.starts_with("0200"),
"SingleKey variant byte (0x02) + AnyPublicKey::Ed25519 variant (0x00) drifted: {outer_hex}",
);
let sig_var_offset = (1 + 34) * 2;
assert_eq!(
&outer_hex[sig_var_offset..sig_var_offset + 2],
"00",
"AnySignature::Ed25519 variant tag drifted: {outer_hex}",
);
}
#[test]
fn raw_transaction_full_bcs_hex_is_pinned() {
let raw = fixed_raw_transaction();
assert_eq!(
const_hex::encode(raw.to_bcs().unwrap()),
"00000000000000000000000000000000000000000000000000000000000000012a000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022000000000000000000000000000000000000000000000000000000000000000020840420f0000000000a0860100000000006400000000000000ffe30b540200000002",
);
}
#[cfg(feature = "ed25519")]
#[test]
fn account_authenticator_multi_ed25519_bcs_layout_is_pinned() {
use aptos_sdk::account::MultiEd25519Account;
let keys: Vec<Ed25519PrivateKey> = (1u8..=3)
.map(|n| Ed25519PrivateKey::from_bytes(&[n; 32]).unwrap())
.collect();
let account = MultiEd25519Account::new(keys, 2).unwrap();
let pk_bytes = account.public_key().to_bytes();
assert_eq!(pk_bytes.len(), 32 * 3 + 1);
assert_eq!(
*pk_bytes.last().unwrap(),
2u8,
"trailing threshold byte must be 2",
);
let sig_bytes: Vec<u8> =
<MultiEd25519Account as Account>::sign(&account, b"fixture message").unwrap();
assert_eq!(sig_bytes.len(), 64 * 2 + 4);
let bitmap = &sig_bytes[sig_bytes.len() - 4..];
assert_eq!(
bitmap,
&[0xc0, 0x00, 0x00, 0x00],
"bitmap MSB-first: signers 0 and 1 only",
);
let auth = AccountAuthenticator::MultiEd25519 {
public_key: pk_bytes.clone(),
signature: sig_bytes.clone(),
};
let outer = aptos_bcs::to_bytes(&auth).unwrap();
let outer_hex = const_hex::encode(&outer);
assert_eq!(
outer.len(),
1 + 1 + 97 + 2 + 132,
"MultiEd25519 outer BCS length drifted: {outer_hex}",
);
assert!(
outer_hex.starts_with("0161"),
"MultiEd25519 variant(1) + ULEB(97) prefix drifted: {outer_hex}",
);
assert_eq!(
&outer_hex[198..202],
"8401",
"MultiEd25519 inner ULEB128(132) header drifted: {outer_hex}",
);
}
#[cfg(all(feature = "ed25519", feature = "secp256k1"))]
#[test]
fn account_authenticator_multi_key_bcs_layout_is_pinned() {
use aptos_sdk::account::{AnyPrivateKey, MultiKeyAccount};
use aptos_sdk::crypto::{Ed25519PrivateKey as EdK, Secp256k1PrivateKey};
let keys = vec![
AnyPrivateKey::ed25519(EdK::from_bytes(&[1u8; 32]).unwrap()),
AnyPrivateKey::secp256k1(Secp256k1PrivateKey::from_bytes(&[2u8; 32]).unwrap()),
AnyPrivateKey::ed25519(EdK::from_bytes(&[3u8; 32]).unwrap()),
];
let account = MultiKeyAccount::new(keys, 2).unwrap();
let pk_bytes = account.public_key().to_bytes();
assert_eq!(
pk_bytes.len(),
1 + (1 + 1 + 32) + (1 + 1 + 65) + (1 + 1 + 32) + 1,
"MultiKey public-key BCS length drifted",
);
assert_eq!(pk_bytes[0], 3u8, "leading ULEB128(3) public-key count");
assert_eq!(*pk_bytes.last().unwrap(), 2u8, "trailing threshold = 2");
assert_eq!(pk_bytes[1], 0x00, "first AnyPublicKey variant = Ed25519");
assert_eq!(
pk_bytes[35], 0x01,
"second AnyPublicKey variant = Secp256k1",
);
let sig_bytes: Vec<u8> =
<MultiKeyAccount as Account>::sign(&account, b"fixture message").unwrap();
let expected_sig_len = 1 + (1 + 1 + 64) + (1 + 1 + 64) + 1 + 4;
assert_eq!(
sig_bytes.len(),
expected_sig_len,
"MultiKey signature BCS length drifted",
);
assert_eq!(sig_bytes[0], 2u8, "leading ULEB128(2) signature count");
assert_eq!(sig_bytes[1], 0x00, "first signature variant = Ed25519");
assert_eq!(sig_bytes[67], 0x01, "second signature variant = Secp256k1");
let bitvec_offset = sig_bytes.len() - 5;
assert_eq!(sig_bytes[bitvec_offset], 4u8, "BitVec ULEB128(4) header");
assert_eq!(
&sig_bytes[bitvec_offset + 1..],
&[0xc0, 0x00, 0x00, 0x00],
"MultiKey bitmap MSB-first: signers 0 and 1 only",
);
let auth = AccountAuthenticator::MultiKey {
public_key: pk_bytes.clone(),
signature: sig_bytes.clone(),
};
let outer = aptos_bcs::to_bytes(&auth).unwrap();
assert_eq!(
outer.len(),
1 + pk_bytes.len() + sig_bytes.len(),
"MultiKey outer length must NOT include extra ULEB prefixes",
);
assert_eq!(outer[0], 0x03, "MultiKey variant byte");
}
#[test]
fn type_tag_nested_generic_bcs_embeds_inner_typetag() {
let outer = TypeTag::from_str_strict("0x1::coin::Coin<0x1::aptos_coin::AptosCoin>").unwrap();
let inner = TypeTag::aptos_coin();
let outer_bytes = aptos_bcs::to_bytes(&outer).unwrap();
let inner_bytes = aptos_bcs::to_bytes(&inner).unwrap();
let outer_hex = const_hex::encode(&outer_bytes);
let inner_hex = const_hex::encode(&inner_bytes);
assert!(
outer_hex.contains(&inner_hex),
"outer TypeTag BCS ({outer_hex}) must contain inner ({inner_hex})",
);
}