v0.2.6-pre.016

This commit is contained in:
2026-08-22 08:18:38 +02:00
parent 946d88322b
commit 92a2c4fff3
43 changed files with 2813 additions and 280 deletions

View File

@@ -0,0 +1,18 @@
// file: crates/ksp-wallet-lib/unit_tests/format.rs
// version: 1
#[test]
fn default_and_latest_are_explicit_and_currently_v2() {
assert_eq!(crate::DEFAULT_WALLET_FORMAT, crate::WalletFormat::V2);
assert_eq!(crate::LATEST_SUPPORTED_WALLET_FORMAT, crate::WalletFormat::V2);
assert_eq!(crate::DEFAULT_WALLET_FORMAT.version(), crate::KSPWALLET_FORMAT_VERSION_V2);
}
#[test]
fn detector_distinguishes_v1_json_and_v2_binary_without_crypto() -> ksp_core_lib::Result<()> {
let v1 = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json");
let v2 = include_bytes!("../tests/fixtures/kspwallet_v2_wire_only.bin");
assert_eq!(crate::detect_wallet_format(v1)?, crate::WalletFormat::V1);
assert_eq!(crate::detect_wallet_format(v2)?, crate::WalletFormat::V2);
return std::result::Result::Ok(());
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/unit_tests/transfer.rs
// version: 3
// version: 4
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
@@ -144,6 +144,41 @@ fn cli_json_import_creates_new_no_clobber_wallet_with_imported_identity_and_meta
source.zeroize();
}
#[test]
fn default_transfer_import_creates_v2_without_removing_explicit_v1_import() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");
let default_destination = directory.path().join("default-v2.kspwallet");
let explicit_v1_destination = directory.path().join("explicit-v1.kspwallet");
let keypair = test_keypair();
let mut source = cli_json(&keypair);
let runtime = runtime();
let default_owner = runtime
.block_on(crate::import_wallet_transfer(
default_destination.as_path(),
source.as_slice(),
crate::WalletTransferFormat::SolanaCliJson,
crate::OwnerPassword::new(std::string::String::from("pre016-default-import-owner")),
std::option::Option::None,
crate::WalletCreateMetadata::default(),
))
.expect("default transfer import must succeed");
assert_eq!(default_owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
let default_bytes = std::fs::read(default_destination).expect("default imported Wallet must be readable");
assert_eq!(crate::detect_wallet_format(default_bytes.as_slice()).expect("default imported Wallet must detect"), crate::WalletFormat::V2);
let explicit_owner = runtime
.block_on(crate::import_wallet_transfer_v1(
explicit_v1_destination.as_path(),
source.as_slice(),
crate::WalletTransferFormat::SolanaCliJson,
crate::OwnerPassword::new(std::string::String::from("pre016-explicit-v1-import-owner")),
std::option::Option::None,
crate::WalletCreateMetadataV1::default(),
))
.expect("explicit V1 transfer import must remain available");
assert_eq!(explicit_owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1);
source.zeroize();
}
#[test]
fn transfer_file_import_is_non_destructive_and_bounded() {
let directory = tempfile::tempdir().expect("Wallet transfer test directory must be creatable");

View File

@@ -0,0 +1,126 @@
// file: crates/ksp-wallet-lib/unit_tests/wallet_v2.rs
// version: 1
const V1_FULL_VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
fn runtime() -> tokio::runtime::Runtime {
return tokio::runtime::Builder::new_current_thread().build().expect("Wallet V2 test runtime must build");
}
#[test]
fn v2_create_open_and_generic_dispatch_preserve_authorized_identity() {
let runtime = runtime();
let owner_password_text = std::string::String::from("pre016-v2-owner-password");
let view_password_text = std::string::String::from("pre016-v2-view-password");
let owner = runtime
.block_on(crate::create_wallet_v2(
crate::OwnerPassword::new(owner_password_text.clone()),
std::option::Option::Some(crate::ViewPassword::new(view_password_text.clone())),
crate::WalletCreateMetadataV1::new(std::option::Option::Some(std::string::String::from("pre016-v2")), std::vec::Vec::new()),
))
.expect("V2 wallet creation must succeed");
assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
let pubkey = *owner.pubkey();
let bytes = owner.to_native_bytes().expect("V2 owner must serialize native bytes");
assert_eq!(crate::detect_wallet_format(bytes.as_slice()).expect("V2 framing must detect"), crate::WalletFormat::V2);
assert!(owner.to_json_bytes().is_err());
let view = runtime
.block_on(crate::open_wallet_view(bytes.as_slice(), crate::ViewPassword::new(view_password_text)))
.expect("generic VIEW open must dispatch V2");
assert_eq!(view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert_eq!(*view.pubkey(), pubkey);
assert_eq!(view.alias(), std::option::Option::Some("pre016-v2"));
let reopened = runtime
.block_on(crate::open_wallet_owner(bytes.as_slice(), crate::OwnerPassword::new(owner_password_text)))
.expect("generic OWNER open must dispatch V2");
assert_eq!(reopened.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert_eq!(*reopened.pubkey(), pubkey);
}
#[test]
fn generic_dispatch_keeps_v1_read_compatibility() {
let runtime = runtime();
assert_eq!(crate::detect_wallet_format(V1_FULL_VECTOR).expect("V1 framing must detect"), crate::WalletFormat::V1);
let locked = crate::inspect_locked_wallet(V1_FULL_VECTOR).expect("generic inspect must keep V1 compatibility");
assert_eq!(locked.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1);
let owner = runtime
.block_on(crate::open_wallet_owner(V1_FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("generic OWNER open must dispatch V1");
assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1);
}
#[test]
fn default_file_creation_is_v2_and_v2_administration_stays_v2() {
let directory = tempfile::Builder::new().prefix("ksp-pre016-v2-").tempdir().expect("V2 test directory must be creatable");
let path = directory.path().join("default.kspwallet");
let runtime = runtime();
let mut owner = runtime
.block_on(crate::create_wallet_file(
path.as_path(),
crate::OwnerPassword::new(std::string::String::from("pre016-default-owner")),
std::option::Option::Some(crate::ViewPassword::new(std::string::String::from("pre016-default-view"))),
crate::WalletCreateMetadataV1::default(),
))
.expect("default file creation must succeed");
assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
let locked = runtime.block_on(crate::inspect_locked_wallet_file(path.as_path())).expect("generic inspect must read default file");
assert_eq!(locked.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
runtime
.block_on(owner.update_alias(path.as_path(), std::option::Option::Some(std::string::String::from("pre016-updated"))))
.expect("V2 metadata update must persist in V2");
runtime
.block_on(owner.rotate_owner_password(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre016-owner-rotated"))))
.expect("V2 OWNER rotation must persist in V2");
runtime
.block_on(owner.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated"))))
.expect("V2 VIEW rotation by OWNER must persist in V2");
let reopened_owner = runtime
.block_on(crate::open_wallet_owner_file(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre016-owner-rotated"))))
.expect("rotated V2 OWNER must reopen through generic dispatch");
assert_eq!(reopened_owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert_eq!(reopened_owner.alias(), std::option::Option::Some("pre016-updated"));
let reopened_view = runtime
.block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated"))))
.expect("rotated V2 VIEW must reopen through generic dispatch");
assert_eq!(reopened_view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert_eq!(reopened_view.alias(), std::option::Option::Some("pre016-updated"));
runtime.block_on(owner.disable_view(path.as_path())).expect("V2 strong VIEW disable must persist");
let disabled = runtime.block_on(crate::inspect_locked_wallet_file(path.as_path())).expect("disabled V2 must remain inspectable");
assert!(!disabled.view_enabled());
let old_view = runtime.block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated"))));
assert_eq!(old_view.expect_err("disabled V2 VIEW must not reopen").code(), crate::ERROR_CODE_CAPABILITY_INSUFFICIENT);
runtime
.block_on(owner.recreate_view(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-recreated"))))
.expect("V2 strong VIEW recreation must persist");
let mut recreated_view = runtime
.block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-recreated"))))
.expect("recreated V2 VIEW must open");
runtime
.block_on(recreated_view.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-self-rotated"))))
.expect("V2 VIEW self-rotation must persist");
let final_view = runtime
.block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-self-rotated"))))
.expect("self-rotated V2 VIEW must reopen");
assert_eq!(final_view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
let bytes = std::fs::read(path).expect("V2 file must remain readable for framing canary");
assert_eq!(crate::detect_wallet_format(bytes.as_slice()).expect("administered file must still be V2"), crate::WalletFormat::V2);
}
#[test]
fn explicit_versioned_readers_reject_the_other_native_wire_format() {
let runtime = runtime();
let v1_as_v2 =
runtime.block_on(crate::open_wallet_owner_v2(V1_FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from("irrelevant-before-v2-parse"))));
assert_eq!(v1_as_v2.expect_err("explicit V2 reader must reject V1 framing").code(), crate::ERROR_CODE_FORMAT_INVALID);
let owner = runtime
.block_on(crate::create_wallet_v2(
crate::OwnerPassword::new(std::string::String::from("pre016-strict-v2-owner")),
std::option::Option::None,
crate::WalletCreateMetadata::default(),
))
.expect("strict-reader V2 fixture creation must succeed");
let bytes = owner.to_native_bytes().expect("strict-reader V2 fixture must serialize");
let v2_as_v1 =
runtime.block_on(crate::open_wallet_owner_v1(bytes.as_slice(), crate::OwnerPassword::new(std::string::String::from("irrelevant-before-v1-parse"))));
assert_eq!(v2_as_v1.expect_err("explicit V1 reader must reject V2 framing").code(), crate::ERROR_CODE_FORMAT_INVALID);
}