v0.2.5-pre.002
This commit is contained in:
16
crates/ksp-wallet-lib/Cargo.toml
Normal file
16
crates/ksp-wallet-lib/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
# file: crates/ksp-wallet-lib/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "ksp-wallet-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
||||
zeroize.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
15
crates/ksp-wallet-lib/src/capability.rs
Normal file
15
crates/ksp-wallet-lib/src/capability.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// file: crates/ksp-wallet-lib/src/capability.rs
|
||||
// version: 1
|
||||
|
||||
/// Authorized capability represented by an unlocked Wallet handle.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum WalletCapability {
|
||||
/// Metadata access plus rotation of the current VIEW password only.
|
||||
View,
|
||||
/// Full Wallet ownership including signing and administration.
|
||||
Owner,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/capability.rs"]
|
||||
mod tests;
|
||||
7
crates/ksp-wallet-lib/src/constants.rs
Normal file
7
crates/ksp-wallet-lib/src/constants.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// file: crates/ksp-wallet-lib/src/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Wallet-owned constants.
|
||||
|
||||
/// Owning tracing target for events emitted by the Wallet crate.
|
||||
pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib";
|
||||
29
crates/ksp-wallet-lib/src/error.rs
Normal file
29
crates/ksp-wallet-lib/src/error.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// file: crates/ksp-wallet-lib/src/error.rs
|
||||
// version: 1
|
||||
|
||||
/// Error code used when a native Wallet structure is invalid.
|
||||
pub const ERROR_CODE_FORMAT_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_invalid");
|
||||
/// Error code used when a native Wallet format version is unsupported.
|
||||
pub const ERROR_CODE_FORMAT_VERSION_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_version_unsupported");
|
||||
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
|
||||
pub const ERROR_CODE_CRYPTO_PARAMETERS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_parameters_invalid");
|
||||
/// Error code used when an authenticated Wallet structure cannot be verified.
|
||||
pub const ERROR_CODE_AUTHENTICATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "authentication_failed");
|
||||
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_VIEW_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "view_unlock_failed");
|
||||
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub const ERROR_CODE_OWNER_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "owner_unlock_failed");
|
||||
/// Error code used when an operation requires a capability that the caller does not own.
|
||||
pub const ERROR_CODE_CAPABILITY_INSUFFICIENT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "capability_insufficient");
|
||||
/// Error code used when Wallet filesystem I/O fails.
|
||||
pub const ERROR_CODE_IO_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "io_failed");
|
||||
/// Error code used when a no-clobber create or import destination already exists.
|
||||
pub const ERROR_CODE_DESTINATION_EXISTS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "destination_exists");
|
||||
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
|
||||
pub const ERROR_CODE_ATOMIC_PERSISTENCE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "atomic_persistence_failed");
|
||||
/// Error code used when an import/export transfer format is unsupported.
|
||||
pub const ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "transfer_format_unsupported");
|
||||
/// Error code used when imported or decoded key material is invalid.
|
||||
pub const ERROR_CODE_KEY_MATERIAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "key_material_invalid");
|
||||
/// Error code used when a Wallet signing operation fails.
|
||||
pub const ERROR_CODE_SIGNATURE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "signature_failed");
|
||||
66
crates/ksp-wallet-lib/src/lib.rs
Normal file
66
crates/ksp-wallet-lib/src/lib.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
// file: crates/ksp-wallet-lib/src/lib.rs
|
||||
// version: 1
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Autonomous KSP Wallet foundation.
|
||||
//!
|
||||
//! `ksp-wallet-lib` owns the native `.kspwallet` domain, VIEW/OWNER capability model, protected metadata projection, password-secret wrappers and Wallet
|
||||
//! error contract. The `0.2.5-pre.002` foundation deliberately contains no file codec, KDF/AEAD implementation, Solana secret material, persistence,
|
||||
//! network access, Config integration or execution policy. Public keys are consumed exclusively through the [`ksp_core_lib::Pubkey`] re-export owned by
|
||||
//! KSP Core, and behavioral observability uses only `ksp-logging-lib` with the explicit crate target defined in `src/constants.rs`.
|
||||
|
||||
mod capability;
|
||||
mod constants;
|
||||
mod error;
|
||||
mod metadata;
|
||||
mod owner;
|
||||
mod password;
|
||||
mod view;
|
||||
|
||||
/// Authorized capability represented by an unlocked Wallet handle.
|
||||
pub use self::capability::WalletCapability;
|
||||
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
|
||||
pub use self::error::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED;
|
||||
/// Error code used when an authenticated Wallet structure cannot be verified.
|
||||
pub use self::error::ERROR_CODE_AUTHENTICATION_FAILED;
|
||||
/// Error code used when an operation requires a capability that the caller does not own.
|
||||
pub use self::error::ERROR_CODE_CAPABILITY_INSUFFICIENT;
|
||||
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
|
||||
pub use self::error::ERROR_CODE_CRYPTO_PARAMETERS_INVALID;
|
||||
/// Error code used when a no-clobber create or import destination already exists.
|
||||
pub use self::error::ERROR_CODE_DESTINATION_EXISTS;
|
||||
/// Error code used when a native Wallet structure is invalid.
|
||||
pub use self::error::ERROR_CODE_FORMAT_INVALID;
|
||||
/// Error code used when a native Wallet format version is unsupported.
|
||||
pub use self::error::ERROR_CODE_FORMAT_VERSION_UNSUPPORTED;
|
||||
/// Error code used when Wallet filesystem I/O fails.
|
||||
pub use self::error::ERROR_CODE_IO_FAILED;
|
||||
/// Error code used when imported or decoded key material is invalid.
|
||||
pub use self::error::ERROR_CODE_KEY_MATERIAL_INVALID;
|
||||
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub use self::error::ERROR_CODE_OWNER_UNLOCK_FAILED;
|
||||
/// Error code used when a Wallet signing operation fails.
|
||||
pub use self::error::ERROR_CODE_SIGNATURE_FAILED;
|
||||
/// Error code used when an import/export transfer format is unsupported.
|
||||
pub use self::error::ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED;
|
||||
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
|
||||
pub use self::error::ERROR_CODE_VIEW_UNLOCK_FAILED;
|
||||
/// Minimal non-secret information available while a native Wallet remains locked.
|
||||
pub use self::metadata::LockedWalletInfo;
|
||||
/// Safe metadata projection produced after VIEW or OWNER authorization.
|
||||
pub use self::metadata::WalletInfo;
|
||||
/// One protected Wallet note exposed only after authorization.
|
||||
pub use self::metadata::WalletNote;
|
||||
/// Authorized OWNER capability handle.
|
||||
pub use self::owner::WalletOwner;
|
||||
/// Owned OWNER password material with redacted diagnostics and drop-time zeroization.
|
||||
pub use self::password::OwnerPassword;
|
||||
/// Owned VIEW password material with redacted diagnostics and drop-time zeroization.
|
||||
pub use self::password::ViewPassword;
|
||||
/// Authorized VIEW capability handle.
|
||||
pub use self::view::WalletView;
|
||||
|
||||
/// Wallet-owned tracing target used by the KSP logging facade.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
109
crates/ksp-wallet-lib/src/metadata.rs
Normal file
109
crates/ksp-wallet-lib/src/metadata.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// file: crates/ksp-wallet-lib/src/metadata.rs
|
||||
// version: 1
|
||||
|
||||
/// One protected Wallet note exposed only after VIEW or OWNER authorization.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct WalletNote {
|
||||
id: std::string::String,
|
||||
text: std::string::String,
|
||||
}
|
||||
|
||||
impl WalletNote {
|
||||
/// Returns the stable note identifier used by Wallet administration operations.
|
||||
#[must_use]
|
||||
pub fn id(&self) -> &str {
|
||||
return self.id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the protected note text after authorization.
|
||||
#[must_use]
|
||||
pub fn text(&self) -> &str {
|
||||
return self.text.as_str();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WalletNote {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("WalletNote").field("id", &"<redacted>").field("text", &"<redacted>").finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe metadata projection produced after VIEW or OWNER authorization.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct WalletInfo {
|
||||
format_version: u32,
|
||||
capability: crate::WalletCapability,
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
alias: std::option::Option<std::string::String>,
|
||||
notes: std::vec::Vec<crate::WalletNote>,
|
||||
}
|
||||
|
||||
impl WalletInfo {
|
||||
/// Returns the native Wallet format version parsed for this projection.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.format_version;
|
||||
}
|
||||
|
||||
/// Returns the authorization capability that produced this projection.
|
||||
#[must_use]
|
||||
pub const fn capability(&self) -> crate::WalletCapability {
|
||||
return self.capability;
|
||||
}
|
||||
|
||||
/// Returns the authorized Solana public key using the KSP Core re-export.
|
||||
#[must_use]
|
||||
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||
return &self.pubkey;
|
||||
}
|
||||
|
||||
/// Returns the protected internal alias after authorization, when present.
|
||||
#[must_use]
|
||||
pub fn alias(&self) -> std::option::Option<&str> {
|
||||
return self.alias.as_deref();
|
||||
}
|
||||
|
||||
/// Returns the protected Wallet notes after authorization.
|
||||
#[must_use]
|
||||
pub fn notes(&self) -> &[crate::WalletNote] {
|
||||
return self.notes.as_slice();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WalletInfo {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("WalletInfo")
|
||||
.field("format_version", &self.format_version)
|
||||
.field("capability", &self.capability)
|
||||
.field("pubkey", &self.pubkey)
|
||||
.field("alias", &self.alias.as_ref().map(|_| "<redacted>"))
|
||||
.field("note_count", &self.notes.len())
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal non-secret information available while a native Wallet remains locked.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct LockedWalletInfo {
|
||||
format_version: u32,
|
||||
view_enabled: bool,
|
||||
}
|
||||
|
||||
impl LockedWalletInfo {
|
||||
/// Returns the native Wallet format version.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.format_version;
|
||||
}
|
||||
|
||||
/// Reports whether a VIEW capability slot exists without exposing its protected contents.
|
||||
#[must_use]
|
||||
pub const fn view_enabled(&self) -> bool {
|
||||
return self.view_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/metadata.rs"]
|
||||
mod tests;
|
||||
54
crates/ksp-wallet-lib/src/owner.rs
Normal file
54
crates/ksp-wallet-lib/src/owner.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// file: crates/ksp-wallet-lib/src/owner.rs
|
||||
// version: 1
|
||||
|
||||
/// Authorized OWNER capability handle.
|
||||
///
|
||||
/// OWNER exposes all authorized metadata and will receive signing plus Wallet administration operations in later `0.2.5` tranches without exposing a
|
||||
/// general-purpose secret-key getter.
|
||||
pub struct WalletOwner {
|
||||
info: crate::WalletInfo,
|
||||
}
|
||||
|
||||
impl WalletOwner {
|
||||
/// Returns the authorization capability represented by this handle.
|
||||
#[must_use]
|
||||
pub const fn capability(&self) -> crate::WalletCapability {
|
||||
return crate::WalletCapability::Owner;
|
||||
}
|
||||
|
||||
/// Returns the authorized Solana public key.
|
||||
#[must_use]
|
||||
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||
return self.info.pubkey();
|
||||
}
|
||||
|
||||
/// Returns the protected internal alias, when present.
|
||||
#[must_use]
|
||||
pub fn alias(&self) -> std::option::Option<&str> {
|
||||
return self.info.alias();
|
||||
}
|
||||
|
||||
/// Returns the protected notes.
|
||||
#[must_use]
|
||||
pub fn notes(&self) -> &[crate::WalletNote] {
|
||||
return self.info.notes();
|
||||
}
|
||||
|
||||
/// Returns the complete safe metadata projection for this OWNER capability.
|
||||
#[must_use]
|
||||
pub fn info(&self) -> &crate::WalletInfo {
|
||||
ksp_logging_lib::trace!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_info_projection",
|
||||
capability = "owner",
|
||||
"wallet metadata projection requested"
|
||||
);
|
||||
return &self.info;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WalletOwner {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("WalletOwner").field("info", &self.info).finish();
|
||||
}
|
||||
}
|
||||
74
crates/ksp-wallet-lib/src/password.rs
Normal file
74
crates/ksp-wallet-lib/src/password.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
// file: crates/ksp-wallet-lib/src/password.rs
|
||||
// version: 1
|
||||
|
||||
/// Owned VIEW password material.
|
||||
///
|
||||
/// The value is never exposed through `Debug` or `Display`, is intentionally non-`Clone`, and is zeroized on drop. Consumers should move an existing
|
||||
/// `String` into this type instead of keeping unnecessary clear-text copies.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// let password = ksp_wallet_lib::ViewPassword::new(std::string::String::from("test-only"));
|
||||
/// let duplicated = password.clone();
|
||||
/// let _ = duplicated;
|
||||
/// ```
|
||||
pub struct ViewPassword {
|
||||
value: std::string::String,
|
||||
}
|
||||
|
||||
impl ViewPassword {
|
||||
/// Takes ownership of VIEW password material.
|
||||
#[must_use]
|
||||
pub fn new(value: std::string::String) -> Self {
|
||||
return Self { value };
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ViewPassword {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.write_str("ViewPassword(<redacted>)");
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Drop for ViewPassword {
|
||||
fn drop(&mut self) {
|
||||
zeroize::Zeroize::zeroize(&mut self.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned OWNER password material.
|
||||
///
|
||||
/// The value is never exposed through `Debug` or `Display`, is intentionally non-`Clone`, and is zeroized on drop. Consumers should move an existing
|
||||
/// `String` into this type instead of keeping unnecessary clear-text copies.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// let password = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("test-only"));
|
||||
/// let duplicated = password.clone();
|
||||
/// let _ = duplicated;
|
||||
/// ```
|
||||
pub struct OwnerPassword {
|
||||
value: std::string::String,
|
||||
}
|
||||
|
||||
impl OwnerPassword {
|
||||
/// Takes ownership of OWNER password material.
|
||||
#[must_use]
|
||||
pub fn new(value: std::string::String) -> Self {
|
||||
return Self { value };
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OwnerPassword {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.write_str("OwnerPassword(<redacted>)");
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Drop for OwnerPassword {
|
||||
fn drop(&mut self) {
|
||||
zeroize::Zeroize::zeroize(&mut self.value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/password.rs"]
|
||||
mod tests;
|
||||
54
crates/ksp-wallet-lib/src/view.rs
Normal file
54
crates/ksp-wallet-lib/src/view.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// file: crates/ksp-wallet-lib/src/view.rs
|
||||
// version: 1
|
||||
|
||||
/// Authorized VIEW capability handle.
|
||||
///
|
||||
/// VIEW exposes protected metadata and, in later `0.2.5` tranches, will expose only self-rotation of its VIEW password. It never owns the Solana secret or
|
||||
/// OWNER administration material.
|
||||
pub struct WalletView {
|
||||
info: crate::WalletInfo,
|
||||
}
|
||||
|
||||
impl WalletView {
|
||||
/// Returns the authorization capability represented by this handle.
|
||||
#[must_use]
|
||||
pub const fn capability(&self) -> crate::WalletCapability {
|
||||
return crate::WalletCapability::View;
|
||||
}
|
||||
|
||||
/// Returns the authorized Solana public key.
|
||||
#[must_use]
|
||||
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||
return self.info.pubkey();
|
||||
}
|
||||
|
||||
/// Returns the protected internal alias, when present.
|
||||
#[must_use]
|
||||
pub fn alias(&self) -> std::option::Option<&str> {
|
||||
return self.info.alias();
|
||||
}
|
||||
|
||||
/// Returns the protected notes.
|
||||
#[must_use]
|
||||
pub fn notes(&self) -> &[crate::WalletNote] {
|
||||
return self.info.notes();
|
||||
}
|
||||
|
||||
/// Returns the complete safe metadata projection for this VIEW capability.
|
||||
#[must_use]
|
||||
pub fn info(&self) -> &crate::WalletInfo {
|
||||
ksp_logging_lib::trace!(
|
||||
target: crate::TRACING_TARGET,
|
||||
operation = "wallet_info_projection",
|
||||
capability = "view",
|
||||
"wallet metadata projection requested"
|
||||
);
|
||||
return &self.info;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WalletView {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("WalletView").field("info", &self.info).finish();
|
||||
}
|
||||
}
|
||||
59
crates/ksp-wallet-lib/tests/dependency_boundary.rs
Normal file
59
crates/ksp-wallet-lib/tests/dependency_boundary.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// file: crates/ksp-wallet-lib/tests/dependency_boundary.rs
|
||||
// version: 1
|
||||
|
||||
//! Wallet-specific dependency and ownership canaries.
|
||||
|
||||
fn crate_root() -> std::path::PathBuf {
|
||||
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
}
|
||||
|
||||
fn rust_source_files(directory: &std::path::Path) -> std::vec::Vec<std::path::PathBuf> {
|
||||
let entries = std::fs::read_dir(directory).expect("Wallet source directory must be readable during integration tests");
|
||||
let mut files = std::vec::Vec::new();
|
||||
for entry in entries {
|
||||
let entry = entry.expect("Wallet source directory entry must be readable");
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
files.extend(rust_source_files(path.as_path()));
|
||||
} else if path.extension() == std::option::Option::Some(std::ffi::OsStr::new("rs")) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_manifest_preserves_dependency_firewall() {
|
||||
let manifest = std::fs::read_to_string(crate_root().join("Cargo.toml")).expect("Wallet manifest must be readable during integration tests");
|
||||
assert!(manifest.contains("ksp-core-lib"));
|
||||
assert!(manifest.contains("ksp-logging-lib"));
|
||||
assert!(manifest.contains("zeroize.workspace = true"));
|
||||
for forbidden in [
|
||||
"ksp-config-lib",
|
||||
"ksp-onchain-transport-lib",
|
||||
"ksp-execution-policy-api",
|
||||
"ksp-store-api",
|
||||
"ksp-store-lib",
|
||||
"tauri",
|
||||
"tracing =",
|
||||
"solana-pubkey",
|
||||
] {
|
||||
assert!(!manifest.contains(forbidden), "forbidden direct Wallet dependency detected: {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_sources_use_core_pubkey_logging_facade_and_no_environment() {
|
||||
let source_files = rust_source_files(crate_root().join("src").as_path());
|
||||
let mut all_source = std::string::String::new();
|
||||
for source_file in source_files {
|
||||
let source = std::fs::read_to_string(source_file.as_path()).expect("Wallet Rust source must be readable during integration tests");
|
||||
all_source.push_str(source.as_str());
|
||||
}
|
||||
assert!(all_source.contains("ksp_core_lib::Pubkey"));
|
||||
assert!(all_source.contains("ksp_logging_lib::trace!"));
|
||||
assert!(all_source.contains("pub(crate) const TRACING_TARGET: &str = \"ksp-wallet-lib\";"));
|
||||
assert!(!all_source.contains("solana_pubkey::"));
|
||||
assert!(!all_source.contains("std::env::"));
|
||||
assert!(!all_source.contains("tracing::"));
|
||||
}
|
||||
61
crates/ksp-wallet-lib/tests/public_api.rs
Normal file
61
crates/ksp-wallet-lib/tests/public_api.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
// file: crates/ksp-wallet-lib/tests/public_api.rs
|
||||
// version: 1
|
||||
|
||||
//! Public API canaries for the Wallet foundation.
|
||||
|
||||
fn accepts_view_handle(_: std::option::Option<&ksp_wallet_lib::WalletView>) {}
|
||||
|
||||
fn accepts_owner_handle(_: std::option::Option<&ksp_wallet_lib::WalletOwner>) {}
|
||||
|
||||
#[test]
|
||||
fn capability_and_handle_types_are_available_from_crate_root() {
|
||||
assert_eq!(ksp_wallet_lib::WalletCapability::View, ksp_wallet_lib::WalletCapability::View);
|
||||
assert_ne!(ksp_wallet_lib::WalletCapability::View, ksp_wallet_lib::WalletCapability::Owner);
|
||||
accepts_view_handle(std::option::Option::None);
|
||||
accepts_owner_handle(std::option::Option::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_types_redact_public_debug_output() {
|
||||
let view = ksp_wallet_lib::ViewPassword::new(std::string::String::from("PUBLIC-VIEW-CANARY"));
|
||||
let owner = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("PUBLIC-OWNER-CANARY"));
|
||||
let view_debug = format!("{view:?}");
|
||||
let owner_debug = format!("{owner:?}");
|
||||
assert!(!view_debug.contains("PUBLIC-VIEW-CANARY"));
|
||||
assert!(!owner_debug.contains("PUBLIC-OWNER-CANARY"));
|
||||
assert!(std::mem::needs_drop::<ksp_wallet_lib::ViewPassword>());
|
||||
assert!(std::mem::needs_drop::<ksp_wallet_lib::OwnerPassword>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_projection_methods_use_core_pubkey_contract() {
|
||||
let pubkey_method: fn(&ksp_wallet_lib::WalletInfo) -> &ksp_core_lib::Pubkey = ksp_wallet_lib::WalletInfo::pubkey;
|
||||
let view_pubkey_method: fn(&ksp_wallet_lib::WalletView) -> &ksp_core_lib::Pubkey = ksp_wallet_lib::WalletView::pubkey;
|
||||
let owner_pubkey_method: fn(&ksp_wallet_lib::WalletOwner) -> &ksp_core_lib::Pubkey = ksp_wallet_lib::WalletOwner::pubkey;
|
||||
let _ = pubkey_method;
|
||||
let _ = view_pubkey_method;
|
||||
let _ = owner_pubkey_method;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_error_codes_are_available_from_crate_root() {
|
||||
let codes = [
|
||||
ksp_wallet_lib::ERROR_CODE_FORMAT_INVALID,
|
||||
ksp_wallet_lib::ERROR_CODE_FORMAT_VERSION_UNSUPPORTED,
|
||||
ksp_wallet_lib::ERROR_CODE_CRYPTO_PARAMETERS_INVALID,
|
||||
ksp_wallet_lib::ERROR_CODE_AUTHENTICATION_FAILED,
|
||||
ksp_wallet_lib::ERROR_CODE_VIEW_UNLOCK_FAILED,
|
||||
ksp_wallet_lib::ERROR_CODE_OWNER_UNLOCK_FAILED,
|
||||
ksp_wallet_lib::ERROR_CODE_CAPABILITY_INSUFFICIENT,
|
||||
ksp_wallet_lib::ERROR_CODE_IO_FAILED,
|
||||
ksp_wallet_lib::ERROR_CODE_DESTINATION_EXISTS,
|
||||
ksp_wallet_lib::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED,
|
||||
ksp_wallet_lib::ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED,
|
||||
ksp_wallet_lib::ERROR_CODE_KEY_MATERIAL_INVALID,
|
||||
ksp_wallet_lib::ERROR_CODE_SIGNATURE_FAILED,
|
||||
];
|
||||
assert_eq!(codes.len(), 13);
|
||||
for code in codes {
|
||||
assert_eq!(code.domain(), "wallet");
|
||||
}
|
||||
}
|
||||
9
crates/ksp-wallet-lib/unit_tests/capability.rs
Normal file
9
crates/ksp-wallet-lib/unit_tests/capability.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
// file: crates/ksp-wallet-lib/unit_tests/capability.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn capability_variants_are_distinct_and_stable() {
|
||||
assert_ne!(super::WalletCapability::View, super::WalletCapability::Owner);
|
||||
assert_eq!(format!("{:?}", super::WalletCapability::View), "View");
|
||||
assert_eq!(format!("{:?}", super::WalletCapability::Owner), "Owner");
|
||||
}
|
||||
56
crates/ksp-wallet-lib/unit_tests/metadata.rs
Normal file
56
crates/ksp-wallet-lib/unit_tests/metadata.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// file: crates/ksp-wallet-lib/unit_tests/metadata.rs
|
||||
// version: 1
|
||||
|
||||
fn test_pubkey() -> ksp_core_lib::Pubkey {
|
||||
return ksp_core_lib::PRGIDPK_SOLANA_SYSTEM;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorized_info_exposes_metadata_after_authorization() {
|
||||
let note = super::WalletNote { id: std::string::String::from("purpose"), text: std::string::String::from("devnet test wallet") };
|
||||
let info = super::WalletInfo {
|
||||
format_version: 1,
|
||||
capability: crate::WalletCapability::View,
|
||||
pubkey: test_pubkey(),
|
||||
alias: std::option::Option::Some(std::string::String::from("devnet-owner")),
|
||||
notes: std::vec![note],
|
||||
};
|
||||
assert_eq!(info.format_version(), 1);
|
||||
assert_eq!(info.capability(), crate::WalletCapability::View);
|
||||
assert_eq!(info.pubkey(), &test_pubkey());
|
||||
assert_eq!(info.alias(), std::option::Option::Some("devnet-owner"));
|
||||
assert_eq!(info.notes()[0].id(), "purpose");
|
||||
assert_eq!(info.notes()[0].text(), "devnet test wallet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorized_info_debug_redacts_alias_and_note_text() {
|
||||
let note = super::WalletNote {
|
||||
id: std::string::String::from("NOTE-ID-CANARY"),
|
||||
text: std::string::String::from("NOTE-SECRET-CANARY"),
|
||||
};
|
||||
let info = super::WalletInfo {
|
||||
format_version: 1,
|
||||
capability: crate::WalletCapability::Owner,
|
||||
pubkey: test_pubkey(),
|
||||
alias: std::option::Option::Some(std::string::String::from("ALIAS-SECRET-CANARY")),
|
||||
notes: std::vec![note],
|
||||
};
|
||||
let rendered = format!("{info:?}");
|
||||
assert!(rendered.contains("<redacted>"));
|
||||
assert!(rendered.contains("note_count"));
|
||||
assert!(!rendered.contains("ALIAS-SECRET-CANARY"));
|
||||
assert!(!rendered.contains("NOTE-ID-CANARY"));
|
||||
assert!(!rendered.contains("NOTE-SECRET-CANARY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_info_does_not_carry_authorized_identity_or_metadata() {
|
||||
let info = super::LockedWalletInfo { format_version: 1, view_enabled: true };
|
||||
assert_eq!(info.format_version(), 1);
|
||||
assert!(info.view_enabled());
|
||||
let rendered = format!("{info:?}");
|
||||
assert!(!rendered.contains("Pubkey"));
|
||||
assert!(!rendered.contains("alias"));
|
||||
assert!(!rendered.contains("notes"));
|
||||
}
|
||||
24
crates/ksp-wallet-lib/unit_tests/password.rs
Normal file
24
crates/ksp-wallet-lib/unit_tests/password.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// file: crates/ksp-wallet-lib/unit_tests/password.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn view_password_debug_is_redacted() {
|
||||
let password = super::ViewPassword::new(std::string::String::from("VIEW-SECRET-CANARY"));
|
||||
let rendered = format!("{password:?}");
|
||||
assert_eq!(rendered, "ViewPassword(<redacted>)");
|
||||
assert!(!rendered.contains("VIEW-SECRET-CANARY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owner_password_debug_is_redacted() {
|
||||
let password = super::OwnerPassword::new(std::string::String::from("OWNER-SECRET-CANARY"));
|
||||
let rendered = format!("{password:?}");
|
||||
assert_eq!(rendered, "OwnerPassword(<redacted>)");
|
||||
assert!(!rendered.contains("OWNER-SECRET-CANARY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_wrappers_require_drop_for_zeroization() {
|
||||
assert!(std::mem::needs_drop::<super::ViewPassword>());
|
||||
assert!(std::mem::needs_drop::<super::OwnerPassword>());
|
||||
}
|
||||
Reference in New Issue
Block a user