Files
khadhroony-solana-project/crates/ksp-wallet-lib/unit_tests/transfer.rs

233 lines
12 KiB
Rust

// file: crates/ksp-wallet-lib/unit_tests/transfer.rs
// version: 3
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
use zeroize::Zeroize; // rust-rules: trait-import
fn runtime() -> tokio::runtime::Runtime {
return tokio::runtime::Builder::new_current_thread().build().expect("Wallet transfer test runtime must build");
}
fn test_keypair() -> solana_keypair::Keypair {
return solana_keypair::Keypair::new_from_array([7_u8; 32]);
}
fn cli_json(keypair: &solana_keypair::Keypair) -> std::vec::Vec<u8> {
let mut bytes = keypair.to_bytes();
let encoded = serde_json::to_vec(bytes.as_slice()).expect("test keypair must encode as Solana CLI JSON");
bytes.zeroize();
return encoded;
}
#[test]
fn supported_transfer_formats_have_stable_codes_labels_and_extensions() {
let formats = crate::WalletTransferFormat::supported();
assert_eq!(formats, [crate::WalletTransferFormat::SolanaCliJson, crate::WalletTransferFormat::SolanaKeypairBase58]);
assert_eq!(formats[0].code(), "solana_cli_json");
assert_eq!(formats[0].default_extension(), "json");
assert!(formats[0].label().contains("64 bytes"));
assert_eq!(formats[1].code(), "solana_keypair_base58");
assert_eq!(formats[1].default_extension(), "txt");
assert!(formats[1].label().contains("64 bytes"));
}
#[test]
fn in_memory_inspection_accepts_cli_json_and_canonical_base58_without_exposing_secret() {
let keypair = test_keypair();
let mut json = cli_json(&keypair);
let mut base58 = keypair.to_base58_string();
let json_inspection =
crate::inspect_wallet_transfer(json.as_slice(), crate::WalletTransferFormat::SolanaCliJson).expect("Solana CLI JSON inspection must succeed");
let base58_inspection =
crate::inspect_wallet_transfer(base58.as_bytes(), crate::WalletTransferFormat::SolanaKeypairBase58).expect("canonical Base58 inspection must succeed");
assert_eq!(json_inspection.pubkey(), base58_inspection.pubkey());
assert_eq!(json_inspection.format(), crate::WalletTransferFormat::SolanaCliJson);
assert_eq!(base58_inspection.format(), crate::WalletTransferFormat::SolanaKeypairBase58);
assert!(!format!("{json_inspection:?}").contains(base58.as_str()));
json.zeroize();
base58.zeroize();
}
#[test]
fn external_seed7_transfer_canary_matches_known_solana_keypair_encodings() {
let expected_keypair_bytes = [
7_u8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 234, 74, 108, 99, 226, 156, 82, 10, 190, 245, 80,
123, 19, 46, 197, 249, 149, 71, 118, 174, 190, 190, 123, 146, 66, 30, 234, 105, 20, 70, 210, 44,
];
let expected_pubkey = "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB";
let expected_base58 = "99eUso3aSbE9tqGSTXzo3TLfKb9RkMTURrHKQ1K7Zh3StnzFNUx8FKCPPPPpR479qsw5zv2WNBKmgiz7WqgAJfM";
let keypair = test_keypair();
let mut actual_keypair_bytes = keypair.to_bytes();
assert_eq!(actual_keypair_bytes, expected_keypair_bytes);
let mut actual_base58 = keypair.to_base58_string();
assert_eq!(actual_base58, expected_base58);
let mut json = serde_json::to_vec(expected_keypair_bytes.as_slice()).expect("external keypair canary must encode as JSON");
let json_inspection =
crate::inspect_wallet_transfer(json.as_slice(), crate::WalletTransferFormat::SolanaCliJson).expect("external Solana CLI JSON canary must inspect");
let base58_inspection = crate::inspect_wallet_transfer(expected_base58.as_bytes(), crate::WalletTransferFormat::SolanaKeypairBase58)
.expect("external Base58 canary must inspect");
assert_eq!(json_inspection.pubkey().to_string(), expected_pubkey);
assert_eq!(base58_inspection.pubkey().to_string(), expected_pubkey);
json.zeroize();
actual_base58.zeroize();
actual_keypair_bytes.zeroize();
}
#[test]
fn transfer_decoders_reject_short_inconsistent_or_noncanonical_sources() {
let short_json = b"[1,2,3]";
assert_eq!(
crate::inspect_wallet_transfer(short_json, crate::WalletTransferFormat::SolanaCliJson).expect_err("short Solana CLI JSON must fail").code(),
crate::ERROR_CODE_KEY_MATERIAL_INVALID
);
let keypair = test_keypair();
let mut bytes = keypair.to_bytes();
bytes[63] ^= 0x01;
let mut inconsistent_json = serde_json::to_vec(bytes.as_slice()).expect("inconsistent fixture must encode");
assert_eq!(
crate::inspect_wallet_transfer(inconsistent_json.as_slice(), crate::WalletTransferFormat::SolanaCliJson)
.expect_err("inconsistent keypair bytes must fail")
.code(),
crate::ERROR_CODE_KEY_MATERIAL_INVALID
);
bytes.zeroize();
inconsistent_json.zeroize();
assert_eq!(
crate::inspect_wallet_transfer(b"not valid base58 !!!", crate::WalletTransferFormat::SolanaKeypairBase58)
.expect_err("invalid Base58 must fail")
.code(),
crate::ERROR_CODE_KEY_MATERIAL_INVALID
);
let mut base58 = keypair.to_base58_string();
let surrounded = format!(" {base58}");
assert_eq!(
crate::inspect_wallet_transfer(surrounded.as_bytes(), crate::WalletTransferFormat::SolanaKeypairBase58)
.expect_err("surrounding whitespace must fail")
.code(),
crate::ERROR_CODE_KEY_MATERIAL_INVALID
);
base58.zeroize();
}
#[test]
fn cli_json_import_creates_new_no_clobber_wallet_with_imported_identity_and_metadata() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");
let destination = directory.path().join("imported.kspwallet");
let keypair = test_keypair();
let mut source = cli_json(&keypair);
let expected = crate::inspect_wallet_transfer(source.as_slice(), crate::WalletTransferFormat::SolanaCliJson).expect("source inspection must succeed");
let runtime = runtime();
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre008-owner-password"));
let owner = runtime
.block_on(crate::import_wallet_transfer_v1(
destination.as_path(),
source.as_slice(),
crate::WalletTransferFormat::SolanaCliJson,
owner_password,
std::option::Option::None,
crate::WalletCreateMetadataV1::new(std::option::Option::Some(std::string::String::from("imported-wallet")), std::vec::Vec::new()),
))
.expect("Solana CLI JSON import must succeed");
assert_eq!(owner.pubkey(), expected.pubkey());
assert_eq!(owner.alias(), std::option::Option::Some("imported-wallet"));
let second_password = crate::OwnerPassword::new(std::string::String::from("pre008-second-owner-password"));
let second = runtime.block_on(crate::import_wallet_transfer_v1(
destination.as_path(),
source.as_slice(),
crate::WalletTransferFormat::SolanaCliJson,
second_password,
std::option::Option::None,
crate::WalletCreateMetadataV1::default(),
));
assert_eq!(second.expect_err("import must never overwrite an existing native Wallet").code(), crate::ERROR_CODE_DESTINATION_EXISTS);
source.zeroize();
}
#[test]
fn transfer_file_import_is_non_destructive_and_bounded() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");
let source_path = directory.path().join("source.json");
let destination = directory.path().join("imported.kspwallet");
let keypair = test_keypair();
let mut source = cli_json(&keypair);
std::fs::write(source_path.as_path(), source.as_slice()).expect("transfer fixture must be writable");
let original = std::fs::read(source_path.as_path()).expect("transfer fixture must be readable");
let runtime = runtime();
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre008-file-owner-password"));
runtime
.block_on(crate::import_wallet_transfer_file_v1(
destination.as_path(),
source_path.as_path(),
crate::WalletTransferFormat::SolanaCliJson,
owner_password,
std::option::Option::None,
crate::WalletCreateMetadataV1::default(),
))
.expect("transfer-file import must succeed");
assert_eq!(std::fs::read(source_path.as_path()).expect("source must survive import"), original);
let oversized_path = directory.path().join("oversized.json");
std::fs::write(oversized_path.as_path(), std::vec![b'1'; crate::KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES + 1])
.expect("oversized transfer fixture must be writable");
let error = runtime
.block_on(crate::inspect_wallet_transfer_file(oversized_path.as_path(), crate::WalletTransferFormat::SolanaCliJson))
.expect_err("oversized transfer source must fail before parse");
assert_eq!(error.code(), crate::ERROR_CODE_KEY_MATERIAL_INVALID);
source.zeroize();
}
#[test]
fn owner_exports_cli_json_and_base58_that_roundtrip_to_the_same_imported_keypair() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");
let destination = directory.path().join("imported.kspwallet");
let keypair = test_keypair();
let mut source_base58 = keypair.to_base58_string();
let mut expected_bytes = keypair.to_bytes();
let runtime = runtime();
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre008-export-owner-password"));
let owner = runtime
.block_on(crate::import_wallet_transfer_v1(
destination.as_path(),
source_base58.as_bytes(),
crate::WalletTransferFormat::SolanaKeypairBase58,
owner_password,
std::option::Option::None,
crate::WalletCreateMetadataV1::default(),
))
.expect("Base58 import must succeed");
let mut exported_json = owner.export_transfer(crate::WalletTransferFormat::SolanaCliJson).expect("OWNER JSON export must succeed");
let mut decoded_json: std::vec::Vec<u8> = serde_json::from_slice(exported_json.as_slice()).expect("OWNER JSON export must be valid JSON");
assert_eq!(decoded_json.as_slice(), expected_bytes.as_slice());
let mut exported_base58 = owner.export_transfer(crate::WalletTransferFormat::SolanaKeypairBase58).expect("OWNER Base58 export must succeed");
assert_eq!(exported_base58.as_slice(), source_base58.as_bytes());
exported_json.zeroize();
decoded_json.zeroize();
exported_base58.zeroize();
source_base58.zeroize();
expected_bytes.zeroize();
}
#[test]
fn owner_transfer_file_export_is_no_clobber() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");
let wallet_path = directory.path().join("wallet.kspwallet");
let export_path = directory.path().join("keypair.json");
let runtime = runtime();
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre008-file-export-password"));
let owner = runtime
.block_on(crate::create_wallet_file_v1(wallet_path.as_path(), owner_password, std::option::Option::None, crate::WalletCreateMetadataV1::default()))
.expect("native Wallet creation must succeed");
runtime
.block_on(owner.export_transfer_file(export_path.as_path(), crate::WalletTransferFormat::SolanaCliJson))
.expect("first OWNER transfer-file export must succeed");
let first = std::fs::read(export_path.as_path()).expect("exported transfer file must be readable");
let second = runtime.block_on(owner.export_transfer_file(export_path.as_path(), crate::WalletTransferFormat::SolanaCliJson));
assert_eq!(second.expect_err("OWNER transfer-file export must not overwrite").code(), crate::ERROR_CODE_DESTINATION_EXISTS);
assert_eq!(std::fs::read(export_path.as_path()).expect("first export must remain intact"), first);
#[cfg(unix)]
{
let mode = std::fs::metadata(export_path.as_path()).expect("export metadata must be readable").permissions().mode() & 0o777;
assert_eq!(mode, 0o600);
}
}