From c97a8739abe8a52d95dd843bdff5307d4ccdbb14 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Wed, 19 Aug 2026 09:56:01 +0200 Subject: [PATCH] v0.2.5-pre.002 --- Cargo.toml | 7 +- ROADMAP.md | 4 +- crates/ksp-wallet-lib/Cargo.toml | 16 + crates/ksp-wallet-lib/src/capability.rs | 15 + crates/ksp-wallet-lib/src/constants.rs | 7 + crates/ksp-wallet-lib/src/error.rs | 29 ++ crates/ksp-wallet-lib/src/lib.rs | 66 ++++ crates/ksp-wallet-lib/src/metadata.rs | 109 ++++++ crates/ksp-wallet-lib/src/owner.rs | 54 +++ crates/ksp-wallet-lib/src/password.rs | 74 ++++ crates/ksp-wallet-lib/src/view.rs | 54 +++ .../tests/dependency_boundary.rs | 59 +++ crates/ksp-wallet-lib/tests/public_api.rs | 61 +++ .../ksp-wallet-lib/unit_tests/capability.rs | 9 + crates/ksp-wallet-lib/unit_tests/metadata.rs | 56 +++ crates/ksp-wallet-lib/unit_tests/password.rs | 24 ++ deltas/0.2.5/pre.002.md | 365 ++++++++++++++++++ docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md | 4 +- .../012-V0_2_5_WALLET_FOUNDATION_PLAN.md | 51 ++- 19 files changed, 1037 insertions(+), 27 deletions(-) create mode 100644 crates/ksp-wallet-lib/Cargo.toml create mode 100644 crates/ksp-wallet-lib/src/capability.rs create mode 100644 crates/ksp-wallet-lib/src/constants.rs create mode 100644 crates/ksp-wallet-lib/src/error.rs create mode 100644 crates/ksp-wallet-lib/src/lib.rs create mode 100644 crates/ksp-wallet-lib/src/metadata.rs create mode 100644 crates/ksp-wallet-lib/src/owner.rs create mode 100644 crates/ksp-wallet-lib/src/password.rs create mode 100644 crates/ksp-wallet-lib/src/view.rs create mode 100644 crates/ksp-wallet-lib/tests/dependency_boundary.rs create mode 100644 crates/ksp-wallet-lib/tests/public_api.rs create mode 100644 crates/ksp-wallet-lib/unit_tests/capability.rs create mode 100644 crates/ksp-wallet-lib/unit_tests/metadata.rs create mode 100644 crates/ksp-wallet-lib/unit_tests/password.rs create mode 100644 deltas/0.2.5/pre.002.md diff --git a/Cargo.toml b/Cargo.toml index 9045354..eda8209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 142 +# version: 143 [workspace] resolver = "3" -members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"] +members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.2.5-pre.1" +version = "0.2.5-pre.2" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" @@ -29,6 +29,7 @@ tauri = { version = "^2.11" } tauri-build = { version = "^2.6" } tauri-plugin-tracing = { version = "^0.3" } ts-rs = { version = "^12.0" } +zeroize = { version = "^1.9" } [workspace.lints.rust] missing_docs = "warn" diff --git a/ROADMAP.md b/ROADMAP.md index b070192..cb2d673 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,5 @@ - + # Roadmap KSP @@ -49,7 +49,7 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U - [X] `0.2.2` — HTTP Accounts + Tokens + Cluster : 22 wrappers typés (5 Accounts + 5 Tokens + 12 Cluster), canaries de complétude 52+14, smoke Devnet Transport pur et smoke historique Config -> Transport validés, documentation durable et prompt `0.2.3` publiés stables. - [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants. - [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication. -- [/] `0.2.5` — Wallet foundation ouverte par `pre.001` : threat model offline et héritage bot2/bot3 réaudités, `.kspwallet` V1 entièrement autonome sans facteur externe, design content-keys/key-slots VIEW/OWNER indépendant, metadata VIEW read-only niveau B via autorité Ed25519 de format distincte, slot VIEW auto-rotatable uniquement pour son propre password, OWNER capable de rotation OWNER/VIEW et d’administration complète, keypair V1 immuable, spec multi-langages séparée + vecteurs publics exigés, paramètres KDF à benchmarker avant freeze, persistence/signature/rotations/import-export répartis jusqu’à `pre.010` ; `WalletPolicy` reste exclu. +- [/] `0.2.5` — Wallet foundation : `pre.001` fixe le threat model et le format V1 autonome ; `pre.002` crée `ksp-wallet-lib` avec capabilities VIEW/OWNER, projections metadata protégées, wrappers password redacted/zeroized, erreurs Wallet, target `ksp-wallet-lib` via `ksp-logging-lib` et canaries de frontières. `Pubkey` est consommée uniquement via `ksp-core-lib`, sans dépendance directe `solana-pubkey`; Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet. Le wire/crypto/persistence/signature/rotations/import-export restent répartis jusqu’à `pre.010` ; `WalletPolicy` reste exclu. - [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP, notamment pour afficher l'identité et le solde d'un wallet. - [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe. - [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client. diff --git a/crates/ksp-wallet-lib/Cargo.toml b/crates/ksp-wallet-lib/Cargo.toml new file mode 100644 index 0000000..7371c00 --- /dev/null +++ b/crates/ksp-wallet-lib/Cargo.toml @@ -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 diff --git a/crates/ksp-wallet-lib/src/capability.rs b/crates/ksp-wallet-lib/src/capability.rs new file mode 100644 index 0000000..5fe54df --- /dev/null +++ b/crates/ksp-wallet-lib/src/capability.rs @@ -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; diff --git a/crates/ksp-wallet-lib/src/constants.rs b/crates/ksp-wallet-lib/src/constants.rs new file mode 100644 index 0000000..122dbf1 --- /dev/null +++ b/crates/ksp-wallet-lib/src/constants.rs @@ -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"; diff --git a/crates/ksp-wallet-lib/src/error.rs b/crates/ksp-wallet-lib/src/error.rs new file mode 100644 index 0000000..cb76ec1 --- /dev/null +++ b/crates/ksp-wallet-lib/src/error.rs @@ -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"); diff --git a/crates/ksp-wallet-lib/src/lib.rs b/crates/ksp-wallet-lib/src/lib.rs new file mode 100644 index 0000000..f53c357 --- /dev/null +++ b/crates/ksp-wallet-lib/src/lib.rs @@ -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; diff --git a/crates/ksp-wallet-lib/src/metadata.rs b/crates/ksp-wallet-lib/src/metadata.rs new file mode 100644 index 0000000..6c7f59d --- /dev/null +++ b/crates/ksp-wallet-lib/src/metadata.rs @@ -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", &"").field("text", &"").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, + notes: std::vec::Vec, +} + +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(|_| "")) + .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; diff --git a/crates/ksp-wallet-lib/src/owner.rs b/crates/ksp-wallet-lib/src/owner.rs new file mode 100644 index 0000000..253526e --- /dev/null +++ b/crates/ksp-wallet-lib/src/owner.rs @@ -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(); + } +} diff --git a/crates/ksp-wallet-lib/src/password.rs b/crates/ksp-wallet-lib/src/password.rs new file mode 100644 index 0000000..4c4a76a --- /dev/null +++ b/crates/ksp-wallet-lib/src/password.rs @@ -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()"); + } +} + +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()"); + } +} + +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; diff --git a/crates/ksp-wallet-lib/src/view.rs b/crates/ksp-wallet-lib/src/view.rs new file mode 100644 index 0000000..fcb2aa6 --- /dev/null +++ b/crates/ksp-wallet-lib/src/view.rs @@ -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(); + } +} diff --git a/crates/ksp-wallet-lib/tests/dependency_boundary.rs b/crates/ksp-wallet-lib/tests/dependency_boundary.rs new file mode 100644 index 0000000..561a024 --- /dev/null +++ b/crates/ksp-wallet-lib/tests/dependency_boundary.rs @@ -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 { + 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::")); +} diff --git a/crates/ksp-wallet-lib/tests/public_api.rs b/crates/ksp-wallet-lib/tests/public_api.rs new file mode 100644 index 0000000..731e090 --- /dev/null +++ b/crates/ksp-wallet-lib/tests/public_api.rs @@ -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::()); + assert!(std::mem::needs_drop::()); +} + +#[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"); + } +} diff --git a/crates/ksp-wallet-lib/unit_tests/capability.rs b/crates/ksp-wallet-lib/unit_tests/capability.rs new file mode 100644 index 0000000..1da934f --- /dev/null +++ b/crates/ksp-wallet-lib/unit_tests/capability.rs @@ -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"); +} diff --git a/crates/ksp-wallet-lib/unit_tests/metadata.rs b/crates/ksp-wallet-lib/unit_tests/metadata.rs new file mode 100644 index 0000000..ee3f1b5 --- /dev/null +++ b/crates/ksp-wallet-lib/unit_tests/metadata.rs @@ -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("")); + 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")); +} diff --git a/crates/ksp-wallet-lib/unit_tests/password.rs b/crates/ksp-wallet-lib/unit_tests/password.rs new file mode 100644 index 0000000..c7ef4aa --- /dev/null +++ b/crates/ksp-wallet-lib/unit_tests/password.rs @@ -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()"); + 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()"); + assert!(!rendered.contains("OWNER-SECRET-CANARY")); +} + +#[test] +fn password_wrappers_require_drop_for_zeroization() { + assert!(std::mem::needs_drop::()); + assert!(std::mem::needs_drop::()); +} diff --git a/deltas/0.2.5/pre.002.md b/deltas/0.2.5/pre.002.md new file mode 100644 index 0000000..18791ee --- /dev/null +++ b/deltas/0.2.5/pre.002.md @@ -0,0 +1,365 @@ + + + +# Delta `0.2.5-pre.002` — foundation crate/capabilities/passwords/errors/logging + +## Base requise + +```text +release : 0.2.5 +prerelease : pre.002 +identifiant de commit attendu : v0.2.5-pre.002 +workspace.package.version : 0.2.5-pre.2 +base : v0.2.5-pre.001-fix.001 +``` + +Le delta `pre.001-fix.001` est appliqué avant cette tranche. Les deltas historiques `pre.001.md` et `pre.001-fix.001.md` restent inchangés. + +## Objectif + +Créer la foundation Rust de `ksp-wallet-lib` sans commencer encore le codec `.kspwallet`, le KDF/AEAD, la persistence, la keypair Solana ou la signature. + +Cette tranche matérialise uniquement : + +```text +crate + frontières Cargo +WalletCapability +WalletView / WalletOwner opaques +LockedWalletInfo / WalletInfo / WalletNote +ViewPassword / OwnerPassword +codes d'erreur Wallet +contrat de logging KSP +canaries public API / dependency firewall +``` + +Les opérations `create/open/rotate/sign/import/export` restent volontairement absentes tant que leurs invariants cryptographiques/persistence ne sont pas implémentés dans les tranches prévues. + +## Version Cargo + +Conformément à `VER-ID-009` : + +```text +0.2.5-pre.1 -> 0.2.5-pre.2 +``` + +Toutes les crates membres continuent d'hériter `version.workspace = true`. + +## Workspace et dépendances + +Nouveau membre : + +```text +crates/ksp-wallet-lib +``` + +Dépendances directes : + +```text +ksp-core-lib +ksp-logging-lib +zeroize.workspace = true +``` + +`zeroize` est centralisé dans `[workspace.dependencies]` : + +```toml +zeroize = { version = "^1.9" } +``` + +La génération courante `zeroize 1.9.0` a été réauditée avant insertion. La crate est utilisée immédiatement pour nettoyer au `Drop` les buffers `String` possédés par les wrappers de passwords. Aucun `secrecy`, Argon2, AEAD, CSPRNG, Solana keypair/signer/signature ou codec supplémentaire n'est ajouté par anticipation. + +Interdictions confirmées dans le manifest Wallet : + +```text +ksp-config-lib +ksp-onchain-transport-lib +ksp-execution-policy-api +ksp-store-api / ksp-store-lib +Tauri +tracing direct +solana-pubkey direct +``` + +### Ownership de `Pubkey` + +`ksp-wallet-lib` ne dépend pas directement de `solana-pubkey`. + +Toute surface Wallet utilise : + +```text +ksp_core_lib::Pubkey +``` + +qui est le re-export possédé par `ksp-core-lib`. Les futures crates `solana-keypair`/`solana-signer` ne seront ajoutées que lorsqu'un chemin de compilation les consommera réellement ; leur compatibilité avec la génération de `Pubkey` possédée par Core sera alors contrôlée par compilation et `cargo tree`. + +### Aucun ownership Config + +Wallet n'a aucune configuration runtime propre et ne lit ni document Config, ni `.env`, ni environnement processus. + +Le futur chemin/répertoire de destination d'une création/import sera fourni explicitement par le caller. Une application pourra faire résoudre son propre défaut par Config puis transmettre ce chemin à Wallet sans créer de dépendance inverse Wallet -> Config. + +## Capabilities publiques + +### `WalletCapability` + +Deux rôles uniquement : + +```text +View +Owner +``` + +`View` signifie metadata autorisées plus future rotation de **son propre password VIEW uniquement**. + +`Owner` signifie metadata autorisées plus futures capacités de signature/export explicite/administration. + +Aucun rôle recovery/hardware/automation n'est ajouté en V1. + +### `WalletView` + +Handle opaque exposant actuellement uniquement la projection metadata : + +```text +capability() -> View +pubkey() +alias() +notes() +info() +``` + +Il n'expose aucun constructeur public, aucune signature, aucun export secret, aucune mutation alias/notes et aucune administration OWNER. + +La future méthode de rotation de son propre password sera ajoutée lorsque le slot VIEW réel et sa persistence existent ; `pre.002` ne simule pas cette opération. + +### `WalletOwner` + +Handle opaque exposant actuellement la même lecture metadata autorisée : + +```text +capability() -> Owner +pubkey() +alias() +notes() +info() +``` + +Il n'expose encore ni secret ni signature ni méthode d'administration. Les opérations OWNER réelles sont ajoutées avec les matériaux cryptographiques/persistence correspondants dans les tranches suivantes. + +### Projections metadata + +`LockedWalletInfo` contient uniquement : + +```text +format_version +view_enabled +``` + +Il ne contient aucune Pubkey, alias ou note. + +`WalletInfo` contient après autorisation : + +```text +format_version +capability +ksp_core_lib::Pubkey +alias optionnel +notes +``` + +`WalletNote` expose un identifiant stable et un texte protégé après autorisation. + +Les constructors de ces projections ne sont pas publics : elles sont destinées à être produites par Wallet, pas à fabriquer une capability. + +Les `Debug` de `WalletInfo`/`WalletNote` ne rendent pas alias, identifiant de note ou contenu de note. La Pubkey peut apparaître dans `WalletInfo::Debug` car cette projection n'existe qu'après autorisation et la Pubkey n'est pas un secret blockchain ; Wallet verrouillé ne possède pas cette projection. + +## Password wrappers + +Deux types distincts : + +```text +ViewPassword +OwnerPassword +``` + +Ils : + +- prennent ownership d'un `String` ; +- n'implémentent ni `Clone` ni `Copy` ; +- n'implémentent pas `Display` ; +- exposent un `Debug` strictement redacted ; +- implémentent un `Drop` appelant `zeroize::Zeroize` sur le buffer possédé ; +- n'exposent aucun getter public du password en clair. + +Deux doctests `compile_fail` canaris interdisent la régression vers `Clone`. + +Cette zeroization reste une hygiène best-effort sur le buffer possédé et ne prétend pas effacer des copies antérieures conservées par le caller, l'allocateur, les registres ou d'autres couches du runtime. + +## Erreurs + +Le domaine stable est : + +```text +wallet +``` + +Les 13 codes réservés/stabilisés dans cette foundation sont : + +```text +format_invalid +format_version_unsupported +crypto_parameters_invalid +authentication_failed +view_unlock_failed +owner_unlock_failed +capability_insufficient +io_failed +destination_exists +atomic_persistence_failed +transfer_format_unsupported +key_material_invalid +signature_failed +``` + +Ils utilisent exclusivement `ksp_core_lib::ErrorCode`. Aucun type d'erreur parallèle n'est créé. + +Les codes de déverrouillage restent volontairement orientés rôle/opération afin que l'implémentation crypto future puisse éviter de distinguer publiquement password incorrect, unwrap incorrect et tag AEAD incorrect lorsqu'une telle distinction créerait un oracle inutile. + +## Logging + +Le target Wallet est possédé explicitement par : + +```text +crates/ksp-wallet-lib/src/constants.rs +``` + +avec : + +```text +pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib"; +``` + +Les émissions utilisent exclusivement `ksp_logging_lib::*` et jamais `tracing` directement ni `env!("CARGO_PKG_NAME")` comme target. + +La foundation instrumente seulement la demande explicite de projection `info()` au niveau `trace`, avec : + +```text +operation=wallet_info_projection +capability=view|owner +``` + +Aucun alias, note, password, secret, payload arbitraire ou path n'est loggé. + +## Tests ajoutés + +La tranche définit **13 tests Rust déterministes** plus **2 doctests `compile_fail`** : + +- variants `WalletCapability` ; +- redaction `Debug` des deux password wrappers ; +- présence d'un `Drop` pour les wrappers secrets ; +- projection metadata autorisée ; +- redaction alias/note dans `Debug` ; +- projection locked sans identité/metadata ; +- canary crate-root pour `WalletView`/`WalletOwner`/`WalletCapability` ; +- canary crate-root pour les wrappers de password ; +- signatures de méthodes Pubkey typées en `ksp_core_lib::Pubkey` ; +- disponibilité des 13 codes d'erreur depuis le crate root ; +- manifest firewall Wallet ; +- source ownership canary Core Pubkey / Logging facade / absence env direct. + +Les tests d'intégration de firewall restent spécifiques à `ksp-wallet-lib`; aucun nouveau canary général du workspace n'est déplacé arbitrairement dans cette crate. + +## Documentation synchronisée + +Le plan Wallet est passé en version 5 pour : + +- figer `ksp_core_lib::Pubkey` comme seule frontière Pubkey de Wallet ; +- corriger le target de logging vers `ksp-wallet-lib` dans `src/constants.rs` conformément à `DEP-LOG-010` ; +- enregistrer `zeroize ^1.9` comme seul ajout tiers de `pre.002` ; +- repousser les crates Solana directes à leur premier usage réel ; +- confirmer qu'aucun répertoire par défaut n'appartient à Wallet et que le path sera fourni par le caller ; +- pointer la suite immédiate sur `pre.003`. + +`ROADMAP.md` et `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` enregistrent la matérialisation de la foundation sans recopier le détail des futures prereleases. + +## Fichiers ajoutés + +```text +crates/ksp-wallet-lib/Cargo.toml +crates/ksp-wallet-lib/src/capability.rs +crates/ksp-wallet-lib/src/constants.rs +crates/ksp-wallet-lib/src/error.rs +crates/ksp-wallet-lib/src/lib.rs +crates/ksp-wallet-lib/src/metadata.rs +crates/ksp-wallet-lib/src/owner.rs +crates/ksp-wallet-lib/src/password.rs +crates/ksp-wallet-lib/src/view.rs +crates/ksp-wallet-lib/unit_tests/capability.rs +crates/ksp-wallet-lib/unit_tests/metadata.rs +crates/ksp-wallet-lib/unit_tests/password.rs +crates/ksp-wallet-lib/tests/dependency_boundary.rs +crates/ksp-wallet-lib/tests/public_api.rs +deltas/0.2.5/pre.002.md +``` + +## Fichiers modifiés + +```text +Cargo.toml +ROADMAP.md +docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md +docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md +``` + +## Fichiers supprimés + +Aucun. + +## Validations exécutées dans l'environnement de préparation + +Contrôles statiques réellement exécutés : + +- parsing TOML du `Cargo.toml` racine et du manifest Wallet ; +- comparaison de l'arbre avec la base `pre.001-fix.001` ; +- inventaire exact des fichiers ajoutés/modifiés ; +- scan des sources Wallet : aucun `use` statement ; +- scan production : aucun `unwrap`, `expect`, `panic`, opérateur `?` ou bloc `unsafe` ajouté ; +- manifest firewall : absence Config/Transport/ExecutionPolicy/Store/Tauri/tracing/solana-pubkey directs ; +- source ownership : `ksp_core_lib::Pubkey`, `ksp_logging_lib::trace!`, target explicite `ksp-wallet-lib`, aucun `solana_pubkey::`, `tracing::` ou `std::env::` ; +- contrôle des headers `file:` / `version:` des fichiers ajoutés/modifiés ; +- contrôle de l'archive d'échange contre `VER-ARCHIVE-004` avant livraison. + +## Validations non exécutées + +L'environnement de préparation ne fournit pas la toolchain Rust/Cargo. Les validations suivantes doivent donc être exécutées par l'opérateur après application du delta : + +```bash +cargo fmt --all +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-wallet-lib +``` + +Un `cargo tree` complet des primitives Solana/crypto n'est pas encore pertinent : aucune primitive Solana/keypair/KDF/AEAD nouvelle n'est ajoutée en `pre.002`. `zeroize` doit néanmoins apparaître sans duplication injustifiée lors du contrôle Cargo opérateur. + +## Décisions prises + +- `Pubkey` Wallet est exclusivement `ksp_core_lib::Pubkey`; aucune dépendance directe `solana-pubkey` dans Wallet. +- Logging Wallet utilise exclusivement `ksp-logging-lib` et `TRACING_TARGET = "ksp-wallet-lib"` dans `src/constants.rs`. +- Wallet ne dépend pas de Config et ne choisira pas lui-même un répertoire par défaut ; le caller fournit le path. +- `zeroize ^1.9` est le seul nouveau tiers de `pre.002`. +- Les password wrappers sont distincts OWNER/VIEW, owned, redacted, non-Clone et zeroized au drop. +- Les handles VIEW/OWNER restent opaques et ne simulent aucune opération cryptographique non encore implémentée. +- VIEW conserve comme contrat futur la seule mutation de son propre password ; OWNER conserve les futures rotations OWNER/VIEW et l'administration complète. + +## Questions ouvertes + +Aucune question bloquante pour `pre.003`. + +Restent volontairement à décider/figer dans leurs tranches prévues : + +- wire exact JSON/key slots/transcript/AAD (`pre.003`) ; +- paramètres Argon2 benchmarkés et crypto effective (`pre.004`) ; +- matériaux owner-control/metadata/secret et state signature (`pre.005`) ; +- persistence async/atomique/no-clobber (`pre.006`) ; +- keypair/signature et rotations réelles (`pre.007`) ; +- adapters import/export (`pre.008`). diff --git a/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md b/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md index c0f9974..f6cc51b 100644 --- a/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md +++ b/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md @@ -1,5 +1,5 @@ - + # Séquence des releases fonctionnelles KSP @@ -408,6 +408,8 @@ Le format V1 est cadré comme JSON UTF-8 strict avec binary Base64url sans paddi La release doit fournir `docs/formats/KSPWALLET_V1.md` comme spécification séparée et indépendante de Rust, accompagnée de vecteurs publics auto-contenus permettant une réimplémentation dans un autre langage. La prévision est étendue jusqu'à `pre.010` afin de séparer codec, crypto, capabilities, persistence, administration, import/export, security audit et documentation interopérable. Le plan actif est `docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md`. +`0.2.5-pre.002` matérialise la crate sans ouvrir encore le codec ou la cryptographie du fichier : `WalletView`/`WalletOwner`, `WalletCapability`, projections `LockedWalletInfo`/`WalletInfo`/`WalletNote`, wrappers `ViewPassword`/`OwnerPassword`, codes d’erreur Wallet et target de logging explicite `ksp-wallet-lib`. La crate dépend seulement de Core, Logging et `zeroize`; elle consomme la Pubkey exclusivement via `ksp_core_lib::Pubkey` et ne dépend directement ni de `solana-pubkey`, ni de Config, Transport, ExecutionPolicy, Store ou Tauri. Les primitives Solana keypair/signature ne seront ajoutées que lorsqu’elles seront réellement consommées. + ## `0.2.6` — Wallet Desk Mission : valider Config composite + `.kspwallet` + transport HTTP dans une application Tauri mince. diff --git a/docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md b/docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md index db0824f..3464887 100644 --- a/docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md +++ b/docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.2.5` — Wallet foundation @@ -77,7 +77,8 @@ Clarifications confirmées pendant la revue du gate : 27. **OWNER peut changer son propre password OWNER et le password VIEW**, sans connaître l'ancien password VIEW ; OWNER reste seul capable de l'administration metadata et des key slots au-delà du self-service de rotation VIEW ; 28. la keypair Solana est **immuable dans un wallet V1 après création/import**. V1 ne fournit pas d'opération de remplacement de keypair ; une autre keypair crée un autre wallet ; 29. les permissions/ACL du système de fichiers ne constituent **ni une garantie cryptographique ni une responsabilité de sécurité de `ksp-wallet-lib`**. Le contrat Wallet porte sur les capacités : sans OWNER, aucune signature Solana, aucun export secret, aucune écriture acceptée de Pubkey/alias/notes et aucune mutation OWNER-controlled ; la seule mutation volontairement accordée à VIEW est la rotation de son propre password ; -30. tout import produit un **nouveau `.kspwallet`** selon la sémantique no-clobber de `create`. Un import ne remplace, n'écrase et ne transforme jamais un `.kspwallet` existant. +30. tout import produit un **nouveau `.kspwallet`** selon la sémantique no-clobber de `create`. Un import ne remplace, n'écrase et ne transforme jamais un `.kspwallet` existant ; +31. `ksp-wallet-lib` ne possède aucun « répertoire Wallet par défaut » ni configuration correspondante. Les opérations de persistence reçoivent un chemin/répertoire explicite du caller ; une future application peut faire résoudre son propre défaut par Config puis transmettre le chemin à Wallet, sans créer de dépendance Wallet -> Config. ## 4. Réaudit de l'héritage bot2/bot3 @@ -368,16 +369,16 @@ Aucune dépendance ci-dessous n'est ajoutée dans `pre.001`. Ce sont les candida ### 8.1 Solana low-level -| Besoin | Crate publiée auditée | Décision candidate | -|------------------|--------------------------|--------------------------------------------------------| -| Pubkey | `solana-pubkey 4.3.0` | déjà workspace, conserver | -| keypair concret | `solana-keypair 3.1.2` | retenir, features minimales | -| interface signer | `solana-signer 3.0.1` | retenir si contrat public/impl l'exige | -| signature | `solana-signature 3.5.2` | dépendance directe seulement si le type public l'exige | +| Besoin | Crate publiée auditée | Décision candidate | +|------------------|--------------------------|---------------------------------------------------------------------------------------------| +| Pubkey | `solana-pubkey 4.3.0` | propriété Core ; Wallet consomme uniquement `ksp_core_lib::Pubkey`, sans dépendance directe | +| keypair concret | `solana-keypair 3.1.2` | retenir, features minimales | +| interface signer | `solana-signer 3.0.1` | retenir si contrat public/impl l'exige | +| signature | `solana-signature 3.5.2` | dépendance directe seulement si le type public l'exige | `solana-keypair` fournit le keypair Ed25519, la conversion stricte depuis 64 octets, la signature, le format JSON de 64 entiers et une représentation Base58 complète. Aucun client RPC Solana n'est nécessaire. -La génération SDK récente définit `Pubkey` comme alias du type `Address`, mais l'écosystème a déjà connu des incompatibilités lorsque plusieurs générations de `solana-address` coexistent. `pre.002` doit donc prouver par compilation/cargo-tree que le `Pubkey` possédé par `ksp-core-lib` et le `Address/Pubkey` exposé par `solana-keypair` sont de la même génération avant de figer les signatures publiques Wallet. Aucun second type d'adresse KSP n'est introduit pour contourner un mismatch. +La génération SDK récente définit `Pubkey` comme alias du type `Address`, mais l'écosystème a déjà connu des incompatibilités lorsque plusieurs générations de `solana-address` coexistent. `pre.002` verrouille donc la surface publique Wallet sur **`ksp_core_lib::Pubkey` exclusivement** et n'ajoute aucune dépendance directe `solana-pubkey`. Lorsque `solana-keypair` sera réellement introduit, la tranche concernée devra prouver par compilation/cargo-tree que son `Address/Pubkey` est compatible avec la génération possédée par Core avant toute conversion interne. Aucun second type d'adresse KSP n'est introduit pour contourner un mismatch. Un `cargo tree` est obligatoire lors de l'introduction réelle afin de contrôler les versions `ed25519-dalek`, `solana-signature`, `rand/getrandom` et éviter des duplications évitables. @@ -412,7 +413,7 @@ AES-GCM-SIV apporte une meilleure tolérance à la réutilisation accidentelle d ### 8.5 Secret memory -`zeroize 1.9.0` est retenu. `secrecy` n'est pas ajouté tant qu'un besoin ergonomique concret n'est pas démontré ; des types KSP simples peuvent imposer eux-mêmes redaction/non-Clone et utiliser `Zeroize`/`Zeroizing`. +`zeroize 1.9.0` est retenu et devient la seule nouvelle dépendance tierce de `pre.002`, car les wrappers `ViewPassword` / `OwnerPassword` l’utilisent immédiatement pour leur nettoyage au `Drop`. `secrecy` n'est pas ajouté tant qu'un besoin ergonomique concret n'est pas démontré ; des types KSP simples imposent eux-mêmes redaction/non-Clone et utilisent `zeroize`. Pour la clé admin Ed25519 distincte, `ed25519-dalek 3.0.0` est une candidate standard, mais son ajout direct est **conditionné au cargo-tree** de la tranche qui implémente l'authentification afin d'éviter une génération concurrente inutile avec celle déjà tirée par `solana-keypair`. @@ -435,7 +436,7 @@ Candidates : ```text base64 0.23.1 pour Base64url sans padding des champs binaires JSON -tempfile 3.27.0 pour temp files privés same-directory + persist/persist_noclobber +tempfile 3.27.0 pour temp files same-directory + persist/persist_noclobber ``` Elles ne sont ajoutées que lorsqu'elles sont réellement consommées. @@ -766,10 +767,12 @@ La crate peut donc être utilisée depuis Tauri plus tard sans dépendre de Taur ### 14.1 Baseline portable +Le chemin de destination est toujours fourni explicitement par le caller ; Wallet ne lit aucune Config ni variable d'environnement pour choisir un répertoire par défaut. + Pour créer/remplacer un `.kspwallet` : 1. sérialiser complètement le nouvel état en mémoire bornée ; -2. créer un temp file privé **dans le même répertoire** que la destination ; +2. créer un temp file unique **dans le même répertoire** que la destination ; 3. écrire tout le contenu ; 4. `flush`/`sync_all` le temp file ; 5. publier avec une primitive no-clobber pour `create` ou replace pour administration ; @@ -836,13 +839,13 @@ notes/alias par défaut ## 16. Logging -Cible KSP future proposée : +Le target principal Wallet est explicitement possédé par `src/constants.rs` conformément à `DEP-LOG-010` : ```text -ksp.wallet +pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib"; ``` -Via `ksp-logging-lib` uniquement. +Toutes les émissions passent par `ksp-logging-lib` uniquement. Wallet ne dépend jamais directement de `tracing` et n’utilise pas `env!("CARGO_PKG_NAME")` comme target. Événements sûrs : @@ -1065,9 +1068,15 @@ Une `fix` ou tranche supplémentaire est préférable à la suppression d'une ga ## 22. Dépendances candidates par tranche -Aucune ajoutée en `pre.001`. +`pre.001` n’ajoutait aucune dépendance. `pre.002` ajoute uniquement : -Liste de travail, à réauditer juste avant insertion sous `[workspace.dependencies]` : +```text +zeroize ^1.9 +``` + +La dépendance est centralisée sous `[workspace.dependencies]` puis consommée avec `zeroize.workspace = true`. Elle est utilisée immédiatement par les wrappers de password ; aucune crate crypto/KDF/Solana supplémentaire n’est ajoutée par anticipation. La génération courante `zeroize 1.9.0` a été réauditée avant insertion. + +Liste de travail restante, à réauditer juste avant insertion sous `[workspace.dependencies]` : ```text argon2 ^0.5 @@ -1079,7 +1088,6 @@ solana-keypair ^3.1 solana-signer ^3.0 # si réellement nécessaire directement solana-signature ^3.5 # seulement si le type public l'exige tempfile ^3.27 -zeroize ^1.9 ``` Déjà présents et réutilisables : @@ -1088,11 +1096,12 @@ Déjà présents et réutilisables : serde serde_json tokio -solana-pubkey -ksp-core-lib +ksp-core-lib # propriétaire/réexport de Pubkey pour Wallet ksp-logging-lib ``` +`ksp-wallet-lib` ne déclare pas `solana-pubkey` : toute Pubkey publique ou interne du domaine Wallet passe par `ksp_core_lib::Pubkey`. + Non retenus nativement en V1 : ```text @@ -1184,4 +1193,4 @@ Une future `format_version >= 2` pourra réétudier des facteurs/ancrages extern ## 26. Suite immédiate -`0.2.5-pre.002` crée seulement la foundation de `ksp-wallet-lib` : boundaries Cargo, types de capability/password/info, erreurs, façade logging et canaries architecturales. Le codec et la cryptographie du fichier restent à `pre.003+`. +`0.2.5-pre.003` est la suite immédiate : codec JSON strict, limites, DTOs d’enveloppe/key slots, transcript/AAD et première spécification `docs/formats/KSPWALLET_V1.md`. La cryptographie effective KDF/AEAD reste à `pre.004+`.