use crate::error::AptosResult;
use crate::transaction::authenticator::TransactionAuthenticator;
use crate::transaction::payload::TransactionPayload;
use crate::types::{AccountAddress, ChainId, HashValue};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawTransaction {
pub sender: AccountAddress,
pub sequence_number: u64,
pub payload: TransactionPayload,
pub max_gas_amount: u64,
pub gas_unit_price: u64,
pub expiration_timestamp_secs: u64,
pub chain_id: ChainId,
}
impl RawTransaction {
pub fn new(
sender: AccountAddress,
sequence_number: u64,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
Self {
sender,
sequence_number,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransaction");
let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawTransactionOrderless {
pub sender: AccountAddress,
pub nonce: Vec<u8>,
pub payload: TransactionPayload,
pub max_gas_amount: u64,
pub gas_unit_price: u64,
pub expiration_timestamp_secs: u64,
pub chain_id: ChainId,
}
impl RawTransactionOrderless {
pub fn new(
sender: AccountAddress,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
let mut nonce = vec![0u8; 32];
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut nonce);
Self {
sender,
nonce,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn with_nonce(
sender: AccountAddress,
nonce: Vec<u8>,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
Self {
sender,
nonce,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionOrderless");
let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignedTransactionOrderless {
pub raw_txn: RawTransactionOrderless,
pub authenticator: TransactionAuthenticator,
}
impl SignedTransactionOrderless {
pub fn new(raw_txn: RawTransactionOrderless, authenticator: TransactionAuthenticator) -> Self {
Self {
raw_txn,
authenticator,
}
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
pub fn sender(&self) -> AccountAddress {
self.raw_txn.sender
}
pub fn nonce(&self) -> &[u8] {
&self.raw_txn.nonce
}
pub fn hash(&self) -> AptosResult<HashValue> {
let bcs_bytes = self.to_bcs()?;
let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
data.extend_from_slice(&prefix);
data.push(2);
data.extend_from_slice(&bcs_bytes);
Ok(HashValue::new(crate::crypto::sha3_256(&data)))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignedTransaction {
pub raw_txn: RawTransaction,
pub authenticator: TransactionAuthenticator,
}
impl SignedTransaction {
pub fn new(raw_txn: RawTransaction, authenticator: TransactionAuthenticator) -> Self {
Self {
raw_txn,
authenticator,
}
}
#[must_use]
pub fn for_simulate_endpoint(&self) -> Self {
Self {
raw_txn: self.raw_txn.clone(),
authenticator: self.authenticator.clone().for_simulate_endpoint(),
}
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
pub fn sender(&self) -> AccountAddress {
self.raw_txn.sender
}
pub fn sequence_number(&self) -> u64 {
self.raw_txn.sequence_number
}
pub fn hash(&self) -> AptosResult<HashValue> {
let bcs_bytes = self.to_bcs()?;
let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
data.extend_from_slice(&prefix);
data.push(0); data.extend_from_slice(&bcs_bytes);
Ok(HashValue::new(crate::crypto::sha3_256(&data)))
}
pub fn verify_signature(&self) -> AptosResult<()> {
match &self.authenticator {
#[cfg(feature = "ed25519")]
TransactionAuthenticator::Ed25519 {
public_key,
signature,
} => {
let public_key = crate::crypto::Ed25519PublicKey::from_bytes(&public_key.0)?;
let signature = crate::crypto::Ed25519Signature::from_bytes(&signature.0)?;
let signing_message = self.raw_txn.signing_message()?;
public_key.verify(&signing_message, &signature)?;
if public_key.to_address() != self.raw_txn.sender {
return Err(crate::error::AptosError::InvalidSignature(
"signed transaction sender does not match authenticator address".into(),
));
}
Ok(())
}
#[cfg(not(feature = "ed25519"))]
TransactionAuthenticator::Ed25519 { .. } => Err(
crate::error::AptosError::FeatureNotEnabled("Ed25519 verification".into()),
),
#[cfg(feature = "ed25519")]
TransactionAuthenticator::MultiEd25519 {
public_key,
signature,
} => {
let public_key = crate::crypto::MultiEd25519PublicKey::from_bytes(public_key)?;
let signature = crate::crypto::MultiEd25519Signature::from_bytes(signature)?;
let signing_message = self.raw_txn.signing_message()?;
public_key.verify(&signing_message, &signature)?;
if public_key.to_address() != self.raw_txn.sender {
return Err(crate::error::AptosError::InvalidSignature(
"signed transaction sender does not match authenticator address".into(),
));
}
Ok(())
}
#[cfg(not(feature = "ed25519"))]
TransactionAuthenticator::MultiEd25519 { .. } => Err(
crate::error::AptosError::FeatureNotEnabled("MultiEd25519 verification".into()),
),
TransactionAuthenticator::SingleSender { sender } => {
let signing_message = self.raw_txn.signing_message()?;
sender.verify(&signing_message)?;
if sender.derived_address()? != self.raw_txn.sender {
return Err(crate::error::AptosError::InvalidSignature(
"signed transaction sender does not match authenticator address".into(),
));
}
Ok(())
}
TransactionAuthenticator::MultiAgent {
sender,
secondary_signer_addresses,
secondary_signers,
} => {
if secondary_signer_addresses.len() != secondary_signers.len() {
return Err(crate::error::AptosError::InvalidSignature(
"secondary signer count does not match secondary signer addresses".into(),
));
}
let signing_message = MultiAgentRawTransaction::new(
self.raw_txn.clone(),
secondary_signer_addresses.clone(),
)
.signing_message()?;
sender.verify(&signing_message)?;
if sender.derived_address()? != self.raw_txn.sender {
return Err(crate::error::AptosError::InvalidSignature(
"signed transaction sender does not match authenticator address".into(),
));
}
for (expected_address, signer) in secondary_signer_addresses
.iter()
.zip(secondary_signers.iter())
{
signer.verify(&signing_message)?;
if signer.derived_address()? != *expected_address {
return Err(crate::error::AptosError::InvalidSignature(
"secondary signer address does not match authenticator address".into(),
));
}
}
Ok(())
}
TransactionAuthenticator::FeePayer {
sender,
secondary_signer_addresses,
secondary_signers,
fee_payer_address,
fee_payer_signer,
} => {
if secondary_signer_addresses.len() != secondary_signers.len() {
return Err(crate::error::AptosError::InvalidSignature(
"secondary signer count does not match secondary signer addresses".into(),
));
}
let signing_message = FeePayerRawTransaction::new(
self.raw_txn.clone(),
secondary_signer_addresses.clone(),
*fee_payer_address,
)
.signing_message()?;
sender.verify(&signing_message)?;
if sender.derived_address()? != self.raw_txn.sender {
return Err(crate::error::AptosError::InvalidSignature(
"signed transaction sender does not match authenticator address".into(),
));
}
for (expected_address, signer) in secondary_signer_addresses
.iter()
.zip(secondary_signers.iter())
{
signer.verify(&signing_message)?;
if signer.derived_address()? != *expected_address {
return Err(crate::error::AptosError::InvalidSignature(
"secondary signer address does not match authenticator address".into(),
));
}
}
fee_payer_signer.verify(&signing_message)?;
if fee_payer_signer.derived_address()? != *fee_payer_address {
return Err(crate::error::AptosError::InvalidSignature(
"fee payer address does not match authenticator address".into(),
));
}
Ok(())
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransactionInfo {
pub hash: HashValue,
#[serde(default)]
pub version: Option<u64>,
#[serde(default)]
pub success: Option<bool>,
#[serde(default)]
pub vm_status: Option<String>,
#[serde(default)]
pub gas_used: Option<u64>,
}
impl TransactionInfo {
pub fn is_success(&self) -> bool {
self.success.unwrap_or(false)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MultiAgentRawTransaction {
pub raw_txn: RawTransaction,
pub secondary_signer_addresses: Vec<AccountAddress>,
}
impl MultiAgentRawTransaction {
pub fn new(raw_txn: RawTransaction, secondary_signer_addresses: Vec<AccountAddress>) -> Self {
Self {
raw_txn,
secondary_signer_addresses,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
#[derive(Serialize)]
enum RawTransactionWithData<'a> {
MultiAgent {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
},
}
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
let data = RawTransactionWithData::MultiAgent {
raw_txn: &self.raw_txn,
secondary_signer_addresses: &self.secondary_signer_addresses,
};
let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeePayerRawTransaction {
pub raw_txn: RawTransaction,
pub secondary_signer_addresses: Vec<AccountAddress>,
pub fee_payer_address: AccountAddress,
}
impl FeePayerRawTransaction {
pub fn new(
raw_txn: RawTransaction,
secondary_signer_addresses: Vec<AccountAddress>,
fee_payer_address: AccountAddress,
) -> Self {
Self {
raw_txn,
secondary_signer_addresses,
fee_payer_address,
}
}
pub fn new_simple(raw_txn: RawTransaction, fee_payer_address: AccountAddress) -> Self {
Self {
raw_txn,
secondary_signer_addresses: vec![],
fee_payer_address,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
#[derive(Serialize)]
enum RawTransactionWithData<'a> {
#[allow(dead_code)]
MultiAgent {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
},
MultiAgentWithFeePayer {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
fee_payer_address: &'a AccountAddress,
},
}
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
let data = RawTransactionWithData::MultiAgentWithFeePayer {
raw_txn: &self.raw_txn,
secondary_signer_addresses: &self.secondary_signer_addresses,
fee_payer_address: &self.fee_payer_address,
};
let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::payload::EntryFunction;
use crate::types::MoveModuleId;
fn create_test_raw_transaction() -> RawTransaction {
RawTransaction::new(
AccountAddress::ONE,
0,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
)
}
#[cfg(feature = "ed25519")]
fn create_transfer_raw_transaction(sender: AccountAddress) -> RawTransaction {
RawTransaction::new(
sender,
0,
EntryFunction::apt_transfer(AccountAddress::from_hex("0x2").unwrap(), 1)
.unwrap()
.into(),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
)
}
#[test]
fn test_raw_transaction_signing_message() {
let txn = create_test_raw_transaction();
let message = txn.signing_message().unwrap();
assert!(!message.is_empty());
assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
}
#[test]
fn test_raw_transaction_fields() {
let txn = create_test_raw_transaction();
assert_eq!(txn.sender, AccountAddress::ONE);
assert_eq!(txn.sequence_number, 0);
assert_eq!(txn.max_gas_amount, 100_000);
assert_eq!(txn.gas_unit_price, 100);
assert_eq!(txn.expiration_timestamp_secs, 1_000_000_000);
assert_eq!(txn.chain_id, ChainId::testnet());
}
#[test]
fn test_raw_transaction_bcs_serialization() {
let txn = create_test_raw_transaction();
let bcs = txn.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_transaction() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
assert_eq!(signed.sender(), AccountAddress::ONE);
}
#[test]
fn test_signed_transaction_bcs() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
let bcs = signed.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_authenticator_bcs_format() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0xab; 32]),
signature: Ed25519Signature([0xcd; 64]),
};
let bcs = aptos_bcs::to_bytes(&auth).unwrap();
println!("Authenticator BCS bytes: {}", const_hex::encode(&bcs));
println!("First byte (variant index): {}", bcs[0]);
println!("Second byte (length prefix): {}", bcs[1]);
println!("Third byte (first pubkey byte): {}", bcs[2]);
assert_eq!(bcs[0], 0, "Ed25519 variant index should be 0");
assert_eq!(bcs[1], 32, "Pubkey length prefix should be 32");
assert_eq!(bcs[2], 0xab, "First pubkey byte should be 0xab");
assert_eq!(bcs[34], 64, "Signature length prefix should be 64");
assert_eq!(bcs[35], 0xcd, "First signature byte should be 0xcd");
assert_eq!(bcs.len(), 99, "BCS length should be 99");
}
#[test]
fn test_transaction_info_deserialization() {
let json = r#"{
"version": 12345,
"hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
"gas_used": 100,
"success": true,
"vm_status": "Executed successfully"
}"#;
let info: TransactionInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.version, Some(12345));
assert_eq!(info.gas_used, Some(100));
assert_eq!(info.success, Some(true));
assert_eq!(info.vm_status, Some("Executed successfully".to_string()));
}
#[test]
fn test_fee_payer_raw_transaction_new() {
let raw_txn = create_test_raw_transaction();
let secondary_addr = AccountAddress::from_hex("0x2").unwrap();
let fee_payer_addr = AccountAddress::THREE;
let fee_payer = FeePayerRawTransaction::new(raw_txn, vec![secondary_addr], fee_payer_addr);
assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
assert_eq!(fee_payer.secondary_signer_addresses.len(), 1);
}
#[test]
fn test_fee_payer_raw_transaction_new_simple() {
let raw_txn = create_test_raw_transaction();
let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
assert!(fee_payer.secondary_signer_addresses.is_empty());
}
#[test]
fn test_fee_payer_signing_message() {
let raw_txn = create_test_raw_transaction();
let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
let message = fee_payer.signing_message().unwrap();
assert!(!message.is_empty());
}
#[test]
fn test_signed_transaction_hash() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
let hash = signed.hash().unwrap();
assert_eq!(hash.as_bytes().len(), 32);
let hash2 = signed.hash().unwrap();
assert_eq!(hash, hash2);
}
#[test]
fn test_signed_transaction_sequence_number() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
assert_eq!(signed.sequence_number(), 0);
}
#[test]
fn test_transaction_info_is_success() {
let info_success = TransactionInfo {
hash: HashValue::new([0; 32]),
version: Some(1),
success: Some(true),
vm_status: None,
gas_used: Some(100),
};
assert!(info_success.is_success());
let info_failed = TransactionInfo {
hash: HashValue::new([0; 32]),
version: Some(1),
success: Some(false),
vm_status: Some("Failed".to_string()),
gas_used: Some(100),
};
assert!(!info_failed.is_success());
let info_unknown = TransactionInfo {
hash: HashValue::new([0; 32]),
version: None,
success: None,
vm_status: None,
gas_used: None,
};
assert!(!info_unknown.is_success());
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_ed25519_sender_mismatch() {
use crate::account::Ed25519Account;
use crate::error::AptosError;
use crate::transaction::builder::sign_transaction;
let signer = Ed25519Account::generate();
let claimed_sender = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
let err = signed_txn.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
);
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_multi_ed25519_sender_mismatch() {
use crate::account::Ed25519Account;
use crate::account::MultiEd25519Account;
use crate::crypto::Ed25519PrivateKey;
use crate::error::AptosError;
use crate::transaction::builder::sign_transaction;
let signer = MultiEd25519Account::new(
vec![Ed25519PrivateKey::generate(), Ed25519PrivateKey::generate()],
2,
)
.unwrap();
let claimed_sender = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
let err = signed_txn.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
);
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_single_sender_sender_mismatch() {
use crate::account::Ed25519SingleKeyAccount;
use crate::error::AptosError;
use crate::transaction::builder::sign_transaction;
let signer = Ed25519SingleKeyAccount::generate();
let claimed_sender = Ed25519SingleKeyAccount::generate();
let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
let err = signed_txn.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
);
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_multi_agent_sender_mismatch() {
use crate::account::{Account, Ed25519Account};
use crate::error::AptosError;
use crate::transaction::builder::sign_multi_agent_transaction;
let signer = Ed25519Account::generate();
let claimed_sender = Ed25519Account::generate();
let secondary = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
let multi_agent = MultiAgentRawTransaction::new(raw_txn, vec![secondary.address()]);
let secondary_refs: Vec<&dyn Account> = vec![&secondary];
let signed_txn =
sign_multi_agent_transaction(&multi_agent, &signer, &secondary_refs).unwrap();
let err = signed_txn.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
);
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_multi_agent_secondary_address_mismatch() {
use crate::account::{Account, Ed25519Account};
use crate::error::AptosError;
use crate::transaction::builder::sign_multi_agent_transaction;
let sender = Ed25519Account::generate();
let expected_secondary = Ed25519Account::generate();
let wrong_secondary = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(sender.address());
let multi_agent =
MultiAgentRawTransaction::new(raw_txn, vec![expected_secondary.address()]);
let secondary_refs: Vec<&dyn Account> = vec![&wrong_secondary];
let signed = sign_multi_agent_transaction(&multi_agent, &sender, &secondary_refs).unwrap();
let err = signed.verify_signature().unwrap_err();
assert!(matches!(err, AptosError::InvalidSignature(msg) if msg
.contains("secondary signer address does not match")));
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_fee_payer_sender_mismatch() {
use crate::account::{Account, Ed25519Account};
use crate::error::AptosError;
use crate::transaction::builder::sign_fee_payer_transaction;
let signer = Ed25519Account::generate();
let claimed_sender = Ed25519Account::generate();
let secondary = Ed25519Account::generate();
let fee_payer = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
let fee_payer_txn =
FeePayerRawTransaction::new(raw_txn, vec![secondary.address()], fee_payer.address());
let secondary_refs: Vec<&dyn Account> = vec![&secondary];
let signed_txn =
sign_fee_payer_transaction(&fee_payer_txn, &signer, &secondary_refs, &fee_payer)
.unwrap();
let err = signed_txn.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
);
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_fee_payer_secondary_address_mismatch() {
use crate::account::{Account, Ed25519Account};
use crate::error::AptosError;
use crate::transaction::builder::sign_fee_payer_transaction;
let sender = Ed25519Account::generate();
let expected_secondary = Ed25519Account::generate();
let wrong_secondary = Ed25519Account::generate();
let fee_payer = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(sender.address());
let fee_payer_txn = FeePayerRawTransaction::new(
raw_txn,
vec![expected_secondary.address()],
fee_payer.address(),
);
let secondary_refs: Vec<&dyn Account> = vec![&wrong_secondary];
let signed =
sign_fee_payer_transaction(&fee_payer_txn, &sender, &secondary_refs, &fee_payer)
.unwrap();
let err = signed.verify_signature().unwrap_err();
assert!(matches!(err, AptosError::InvalidSignature(msg) if msg
.contains("secondary signer address does not match")));
}
#[cfg(feature = "ed25519")]
#[test]
fn test_verify_signature_fee_payer_address_mismatch() {
use crate::account::{Account, Ed25519Account};
use crate::error::AptosError;
use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
use crate::transaction::builder::sign_fee_payer_transaction;
let sender = Ed25519Account::generate();
let secondary = Ed25519Account::generate();
let fee_payer = Ed25519Account::generate();
let impostor_fee_payer = Ed25519Account::generate();
let raw_txn = create_transfer_raw_transaction(sender.address());
let fee_payer_txn =
FeePayerRawTransaction::new(raw_txn, vec![secondary.address()], fee_payer.address());
let secondary_refs: Vec<&dyn Account> = vec![&secondary];
let mut signed =
sign_fee_payer_transaction(&fee_payer_txn, &sender, &secondary_refs, &fee_payer)
.unwrap();
let signing_message = FeePayerRawTransaction::new(
signed.raw_txn.clone(),
vec![secondary.address()],
fee_payer.address(),
)
.signing_message()
.unwrap();
let impostor_auth = AccountAuthenticator::ed25519(
impostor_fee_payer.public_key_bytes(),
impostor_fee_payer.sign(&signing_message).unwrap(),
);
if let TransactionAuthenticator::FeePayer {
fee_payer_signer, ..
} = &mut signed.authenticator
{
*fee_payer_signer = impostor_auth;
} else {
panic!("expected FeePayer authenticator");
}
let err = signed.verify_signature().unwrap_err();
assert!(
matches!(err, AptosError::InvalidSignature(msg) if msg.contains("fee payer address"))
);
}
fn create_test_orderless_transaction() -> RawTransactionOrderless {
RawTransactionOrderless::with_nonce(
AccountAddress::ONE,
vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
],
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
)
}
#[test]
fn test_orderless_transaction_with_nonce() {
let nonce = vec![0xab; 32];
let txn = RawTransactionOrderless::with_nonce(
AccountAddress::ONE,
nonce.clone(),
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
assert_eq!(txn.sender, AccountAddress::ONE);
assert_eq!(txn.nonce, nonce);
assert_eq!(txn.max_gas_amount, 100_000);
assert_eq!(txn.gas_unit_price, 100);
}
#[test]
fn test_orderless_transaction_new_generates_random_nonce() {
let txn1 = RawTransactionOrderless::new(
AccountAddress::ONE,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
let txn2 = RawTransactionOrderless::new(
AccountAddress::ONE,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
assert_ne!(txn1.nonce, txn2.nonce);
assert_eq!(txn1.nonce.len(), 32);
assert_eq!(txn2.nonce.len(), 32);
}
#[test]
fn test_orderless_transaction_signing_message() {
let txn = create_test_orderless_transaction();
let message = txn.signing_message().unwrap();
assert!(!message.is_empty());
assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
}
#[test]
fn test_orderless_transaction_bcs() {
let txn = create_test_orderless_transaction();
let bcs = txn.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_orderless_transaction() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
assert_eq!(signed.sender(), AccountAddress::ONE);
assert_eq!(signed.nonce().len(), 32);
}
#[test]
fn test_signed_orderless_transaction_bcs() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
let bcs = signed.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_orderless_transaction_hash() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
let hash = signed.hash().unwrap();
assert_eq!(hash.as_bytes().len(), 32);
let hash2 = signed.hash().unwrap();
assert_eq!(hash, hash2);
}
#[test]
fn test_multi_agent_raw_transaction() {
let raw_txn = create_test_raw_transaction();
let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary.clone());
assert_eq!(multi_agent.secondary_signer_addresses, secondary);
}
#[test]
fn test_multi_agent_signing_message() {
let raw_txn = create_test_raw_transaction();
let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary);
let message = multi_agent.signing_message().unwrap();
assert!(!message.is_empty());
}
}