v0.2.6-pre.015

This commit is contained in:
2026-08-22 03:21:49 +02:00
parent bd561cad47
commit d370027c02
27 changed files with 2630 additions and 364 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 187
# version: 188
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-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.6-pre.14.fix.1"
version = "0.2.6-pre.15"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md -->
<!-- version: 76 -->
<!-- version: 77 -->
# Roadmap KSP
@@ -50,15 +50,14 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
- [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.
- [X] `0.2.5` — Wallet foundation stable : `.kspwallet` V1, VIEW/OWNER indépendants, Argon2id/XChaCha20-Poly1305, autorité Ed25519 OWNER, persistence no-clobber, signature, administration/rotations/révocation VIEW forte, import/export Solana CLI JSON + Base58, canaris adversariaux, interop externe et documentation durable publiés. La clôture `pre.010-fix.001``fix.003` ajoute `ed25519-dalek 3.0.0` direct, normalise le Rust workspace et installe laudit structurel Python complémentaire à rustfmt/Clippy. `Pubkey` reste via `ksp-core-lib`, la keypair reste encapsulée dans Wallet et Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet.
- [/] `0.2.6``ksp-app-wallet-desk` : surface fonctionnelle Wallet Desk acquise ; `pre.014` harmonise le gabarit/splash/Tauri multi-app puis `pre.015` clôture documentation et build final. Plan détaillé : `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
- [ ] `0.2.7`Durcir la persistence `.kspwallet` dans `ksp-wallet-lib` avec un conteneur binaire versionné rétrocompatible ; conserver les semantics cryptographiques V1 et rejouer Wallet Desk comme consommateur sans changement fonctionnel attendu.
- [ ] `0.2.8`É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.9` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
- [ ] `0.2.10`Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
- [ ] `0.2.11` — Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
- [ ] `0.2.12` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
- [ ] `0.2.13` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
- [ ] `0.2.14` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
- [/] `0.2.6``ksp-app-wallet-desk` + évolution `.kspwallet` V2 : surface Desk acquise ; `pre.014` polish, `pre.015` wire binaire V2, `pre.016` APIs génériques/versionnées + V2, `pre.017` migration/canaris, `pre.018` candidate finale. Plan détaillé : `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
- [ ] `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.
- [ ] `0.2.9` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
- [ ] `0.2.10`Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
- [ ] `0.2.11` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
- [ ] `0.2.12` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
- [ ] `0.2.13` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
### Règles Transport pour toute la série

View File

@@ -914,23 +914,23 @@ impl AppState {
std::result::Result::Err(_) => return std::result::Result::Err(session_lock_error()),
};
let previous = std::mem::replace(&mut *session, crate::WalletSession::no_selection());
match previous {
return match previous {
crate::WalletSession::ViewOperation { wallet_id: reserved_wallet_id, path: reserved_path, pubkey: reserved_pubkey }
if reserved_wallet_id == wallet_id && reserved_path == path && reserved_pubkey == pubkey =>
{
*session = crate::WalletSession::View { wallet_id: wallet_id.clone(), path, wallet: view };
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet VIEW privileged operation completed");
return std::result::Result::Ok(dto);
std::result::Result::Ok(dto)
},
other => {
*session = other;
drop(view);
return std::result::Result::Err(ksp_core_lib::Error::new(
std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_WALLET_SESSION_INVALID,
"Wallet VIEW operation completion no longer owns the selected session",
));
))
},
}
};
}
fn restore_view_after_operation_failure(&self, context: ViewOperationContext) {
@@ -1039,7 +1039,7 @@ impl AppState {
std::result::Result::Err(_) => return std::result::Result::Err(session_lock_error()),
};
let previous = std::mem::replace(&mut *session, crate::WalletSession::no_selection());
match previous {
return match previous {
crate::WalletSession::OwnerOperation {
wallet_id: reserved_wallet_id,
path: reserved_path,
@@ -1048,17 +1048,17 @@ impl AppState {
} if reserved_wallet_id == wallet_id && reserved_path == path && reserved_pubkey == pubkey && reserved_view_enabled == view_enabled => {
*session = crate::WalletSession::Owner { wallet_id: wallet_id.clone(), path, view_enabled: result_view_enabled, wallet: owner };
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER privileged operation completed");
return std::result::Result::Ok(dto);
std::result::Result::Ok(dto)
},
other => {
*session = other;
drop(owner);
return std::result::Result::Err(ksp_core_lib::Error::new(
std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_WALLET_SESSION_INVALID,
"Wallet OWNER operation completion no longer owns the selected session",
));
))
},
}
};
}
fn restore_owner_after_operation_failure(&self, context: OwnerOperationContext) {

View File

@@ -1,11 +1,11 @@
<!-- file: crates/ksp-wallet-lib/README.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# `ksp-wallet-lib`
Statut : **stable depuis KSP `0.2.5`**.
`ksp-wallet-lib` est la bibliothèque KSP propriétaire du Wallet Solana natif. Elle possède le format autonome `.kspwallet` V1, les capacités indépendantes VIEW/OWNER, la protection du secret Solana, la signature, l'administration des metadata, les rotations de credentials, la persistence native et les adapters d'import/export explicitement supportés.
`ksp-wallet-lib` est la bibliothèque KSP propriétaire du Wallet Solana natif. Elle possède le format autonome `.kspwallet` V1 et, depuis `0.2.6-pre.015`, le wire binaire V2 canonique, les capacités indépendantes VIEW/OWNER, la protection du secret Solana, la signature, l'administration des metadata, les rotations de credentials, la persistence native et les adapters d'import/export explicitement supportés.
La crate est volontairement indépendante de Config, du réseau et de Tauri. Un consumer fournit les chemins, passwords et metadata ; Wallet ouvre, protège, signe et persiste sans décider d'une policy de dépense ni contacter un RPC.
@@ -14,6 +14,7 @@ La crate est volontairement indépendante de Config, du réseau et de Tauri. Un
La crate possède :
- le format natif `.kspwallet` V1 et son parser JSON strict ;
- le wire binaire `.kspwallet` V2, son codec borné/canonique et ses domains/transcripts distincts ;
- les key slots OWNER/VIEW indépendants ;
- Argon2id pour les KDF de passwords ;
- XChaCha20-Poly1305 pour le wrapping et les compartiments ;
@@ -192,3 +193,7 @@ Elles couvrent le wire, Argon2id/XChaCha20-Poly1305, l'ouverture VIEW/OWNER, la
- [`../../docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](../../docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md) — plan historique et threat model de `0.2.5` ;
- [`../../docs/validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](../../docs/validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md) — matrice de sécurité/interoperabilité/compliance ;
- [`../../prompts/011-V0_2_6_START_PROMPT.md`](../../prompts/011-V0_2_6_START_PROMPT.md) — reprise vers Wallet Desk après publication stable de `0.2.5`.
## V2 en `0.2.6-pre.015`
`pre.015` ajoute uniquement le codec structurel V2 et sa spécification. Les APIs de persistence runtime restent V1 jusquà `pre.016`, qui ajoute `create_wallet_file(...)` default V2, les variantes `_v1`/`_v2` et le dispatch générique de lecture. Le default est une décision explicite et ne suit pas automatiquement une future V3.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-wallet-lib/USAGE.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Utilisation de `ksp-wallet-lib`
@@ -277,3 +277,14 @@ ksp-onchain-transport-lib
```
Cette composition est le rôle de `0.2.6 — ksp-app-wallet-desk`, pas de `ksp-wallet-lib`.
## Wire V2 (`0.2.6-pre.015`)
Le codec structurel V2 peut être utilisé pour analyser une fixture/document V2 déjà produit :
```rust
let envelope = ksp_wallet_lib::KspWalletEnvelopeV2::parse_binary(bytes)?;
let canonical = envelope.to_binary_bytes()?;
```
La persistence applicative ne doit pas encore appeler ce codec directement pour créer un wallet : `pre.016` introduit les APIs génériques/versionnées et le default V2.

View File

@@ -1,10 +1,12 @@
// file: crates/ksp-wallet-lib/src/constants.rs
// version: 7
// version: 8
//! Wallet-owned constants.
/// Native Wallet format version implemented by the V1 codec.
pub const KSPWALLET_FORMAT_VERSION_V1: u32 = 1;
/// Native Wallet format version implemented by the V2 binary codec.
pub const KSPWALLET_FORMAT_VERSION_V2: u32 = 2;
/// Exact magic string required by every native `.kspwallet` document.
pub const KSPWALLET_MAGIC: &str = "KSPWALLET";
/// Maximum accepted `.kspwallet` document size before JSON parsing.
@@ -85,6 +87,50 @@ pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
pub const KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-VIEW-SLOT";
/// Byte length of an XChaCha20-Poly1305 nonce.
pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
/// Byte length of the Poly1305 authentication tag appended to each V2 ciphertext.
pub const KSPWALLET_V2_AEAD_TAG_BYTES: usize = KSPWALLET_V1_AEAD_TAG_BYTES;
/// Argon2 version serialized by V2 key slots.
pub const KSPWALLET_V2_ARGON2_VERSION: u32 = KSPWALLET_V1_ARGON2_VERSION;
/// Byte length of an Ed25519 public key used as V2 Wallet format authority.
pub const KSPWALLET_V2_ED25519_PUBLIC_KEY_BYTES: usize = KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES;
/// Byte length of an Ed25519 detached V2 state signature.
pub const KSPWALLET_V2_ED25519_SIGNATURE_BYTES: usize = KSPWALLET_V1_ED25519_SIGNATURE_BYTES;
/// Initial protected payload version used independently by V2 control, metadata and secret compartments.
pub const KSPWALLET_V2_INITIAL_PAYLOAD_VERSION: u32 = KSPWALLET_V1_INITIAL_PAYLOAD_VERSION;
/// Structural V2 ceiling for serialized Argon2 iteration cost.
pub const KSPWALLET_V2_MAX_ARGON2_ITERATIONS: u32 = KSPWALLET_V1_MAX_ARGON2_ITERATIONS;
/// Structural V2 ceiling for serialized Argon2 memory cost.
pub const KSPWALLET_V2_MAX_ARGON2_MEMORY_KIB: u32 = KSPWALLET_V1_MAX_ARGON2_MEMORY_KIB;
/// Structural V2 ceiling for serialized Argon2 parallelism.
pub const KSPWALLET_V2_MAX_ARGON2_PARALLELISM: u32 = KSPWALLET_V1_MAX_ARGON2_PARALLELISM;
/// Maximum Argon2 salt size accepted by V2.
pub const KSPWALLET_V2_MAX_KDF_SALT_BYTES: usize = KSPWALLET_V1_MAX_KDF_SALT_BYTES;
/// Maximum wrapped-key ciphertext size accepted by V2.
pub const KSPWALLET_V2_MAX_KEY_WRAP_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_KEY_WRAP_CIPHERTEXT_BYTES;
/// Maximum protected metadata ciphertext size accepted by V2.
pub const KSPWALLET_V2_MAX_METADATA_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_METADATA_CIPHERTEXT_BYTES;
/// Maximum OWNER-control ciphertext size accepted by V2.
pub const KSPWALLET_V2_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES;
/// Maximum OWNER-only secret ciphertext size accepted by V2.
pub const KSPWALLET_V2_MAX_SECRET_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_SECRET_CIPHERTEXT_BYTES;
/// Domain separator for V2 metadata-compartment AEAD AAD.
pub const KSPWALLET_V2_METADATA_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-METADATA";
/// Minimum Argon2 salt size accepted by V2.
pub const KSPWALLET_V2_MIN_KDF_SALT_BYTES: usize = KSPWALLET_V1_MIN_KDF_SALT_BYTES;
/// Domain separator for V2 OWNER-control compartment AEAD AAD.
pub const KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-OWNER-CONTROL";
/// Domain separator for V2 OWNER key-slot wrapping AAD.
pub const KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-OWNER-SLOT";
/// Domain separator for V2 OWNER-only secret compartment AEAD AAD.
pub const KSPWALLET_V2_SECRET_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-SECRET";
/// Byte length of every V2 key-slot identifier.
pub const KSPWALLET_V2_SLOT_ID_BYTES: usize = KSPWALLET_V1_SLOT_ID_BYTES;
/// Domain separator for the V2 OWNER state-signature transcript.
pub const KSPWALLET_V2_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V2-STATE";
/// Domain separator for V2 VIEW key-slot wrapping AAD.
pub const KSPWALLET_V2_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V2-AAD-VIEW-SLOT";
/// Byte length of an XChaCha20-Poly1305 nonce in V2.
pub const KSPWALLET_V2_XCHACHA_NONCE_BYTES: usize = KSPWALLET_V1_XCHACHA_NONCE_BYTES;
/// Owning tracing target for events emitted by the Wallet crate.
pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib";

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/lib.rs
// version: 11
// version: 12
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -15,7 +15,8 @@
//! adds Solana message signing, protected metadata administration, password rotation and strong VIEW disable/recreate with capability-bound atomic
//! replacement. `0.2.5-pre.008` adds bounded Solana CLI JSON and canonical full-keypair Base58 import/export adapters with safe inspection and no-clobber
//! native import/export publication. `0.2.5-pre.009` adds adversarial security/compliance canaries and records the final dependency/interop audit before
//! documentation closure. Public keys are consumed exclusively through the [`ksp_core_lib::Pubkey`] re-export owned by KSP Core, and behavioral
//! documentation closure. `0.2.6-pre.015` defines the strict binary `.kspwallet` V2 wire and its bounded canonical codec without yet changing the public
//! persistence default or Wallet Desk dispatch. 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`.
@@ -29,10 +30,12 @@ mod password;
mod payload;
mod persistence;
mod transcript;
mod transcript_v2;
mod transfer;
mod view;
mod wallet;
mod wire;
mod wire_v2;
#[cfg(test)]
#[path = "../unit_tests/security.rs"]
@@ -42,6 +45,8 @@ mod security_tests;
pub use self::capability::WalletCapability;
/// Native `.kspwallet` V1 format version.
pub use self::constants::KSPWALLET_FORMAT_VERSION_V1;
/// Native `.kspwallet` V2 binary format version.
pub use self::constants::KSPWALLET_FORMAT_VERSION_V2;
/// Exact magic string required by every native `.kspwallet` document.
pub use self::constants::KSPWALLET_MAGIC;
/// Maximum accepted `.kspwallet` document size before parsing.
@@ -122,6 +127,50 @@ pub use self::constants::KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN;
pub use self::constants::KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN;
/// Byte length of an XChaCha20-Poly1305 nonce.
pub use self::constants::KSPWALLET_V1_XCHACHA_NONCE_BYTES;
/// Byte length of the AEAD authentication tag appended to V2 ciphertexts.
pub use self::constants::KSPWALLET_V2_AEAD_TAG_BYTES;
/// Argon2 version serialized by `.kspwallet` V2 key slots.
pub use self::constants::KSPWALLET_V2_ARGON2_VERSION;
/// Byte length of the V2 Ed25519 format-authority public key.
pub use self::constants::KSPWALLET_V2_ED25519_PUBLIC_KEY_BYTES;
/// Byte length of the V2 Ed25519 detached state signature.
pub use self::constants::KSPWALLET_V2_ED25519_SIGNATURE_BYTES;
/// Initial per-compartment protected payload version for V2.
pub use self::constants::KSPWALLET_V2_INITIAL_PAYLOAD_VERSION;
/// Maximum structural Argon2 iteration count accepted by the V2 parser.
pub use self::constants::KSPWALLET_V2_MAX_ARGON2_ITERATIONS;
/// Maximum structural Argon2 memory cost accepted by the V2 parser.
pub use self::constants::KSPWALLET_V2_MAX_ARGON2_MEMORY_KIB;
/// Maximum structural Argon2 parallelism accepted by the V2 parser.
pub use self::constants::KSPWALLET_V2_MAX_ARGON2_PARALLELISM;
/// Maximum Argon2 salt size accepted by V2.
pub use self::constants::KSPWALLET_V2_MAX_KDF_SALT_BYTES;
/// Maximum wrapped-key ciphertext size accepted by V2.
pub use self::constants::KSPWALLET_V2_MAX_KEY_WRAP_CIPHERTEXT_BYTES;
/// Maximum protected metadata ciphertext size accepted by V2.
pub use self::constants::KSPWALLET_V2_MAX_METADATA_CIPHERTEXT_BYTES;
/// Maximum OWNER-control ciphertext size accepted by V2.
pub use self::constants::KSPWALLET_V2_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES;
/// Maximum OWNER-only secret ciphertext size accepted by V2.
pub use self::constants::KSPWALLET_V2_MAX_SECRET_CIPHERTEXT_BYTES;
/// Domain separator for V2 metadata-compartment AEAD AAD.
pub use self::constants::KSPWALLET_V2_METADATA_AAD_DOMAIN;
/// Minimum Argon2 salt size accepted by V2.
pub use self::constants::KSPWALLET_V2_MIN_KDF_SALT_BYTES;
/// Domain separator for V2 OWNER-control compartment AEAD AAD.
pub use self::constants::KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN;
/// Domain separator for V2 OWNER key-slot wrapping AAD.
pub use self::constants::KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN;
/// Domain separator for V2 OWNER-only secret compartment AEAD AAD.
pub use self::constants::KSPWALLET_V2_SECRET_AAD_DOMAIN;
/// Byte length of every V2 key-slot identifier.
pub use self::constants::KSPWALLET_V2_SLOT_ID_BYTES;
/// Domain separator for the V2 OWNER state-signature transcript.
pub use self::constants::KSPWALLET_V2_STATE_TRANSCRIPT_DOMAIN;
/// Domain separator for V2 VIEW key-slot wrapping AAD.
pub use self::constants::KSPWALLET_V2_VIEW_SLOT_AAD_DOMAIN;
/// Byte length of an XChaCha20-Poly1305 nonce in V2.
pub use self::constants::KSPWALLET_V2_XCHACHA_NONCE_BYTES;
/// 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.
@@ -224,6 +273,30 @@ pub use self::wire::WalletStateSignatureAlgorithmV1;
pub use self::wire::WalletStateSignatureV1;
/// OWNER-signed stable descriptor of the optional VIEW slot.
pub use self::wire::WalletViewDescriptorV1;
/// Strict semantic representation of one parsed native `.kspwallet` V2 binary envelope.
pub use self::wire_v2::KspWalletEnvelopeV2;
/// Authenticated-encryption algorithm fixed by native Wallet V2.
pub use self::wire_v2::WalletAeadAlgorithmV2;
/// Protected compartment kind fixed by native Wallet V2.
pub use self::wire_v2::WalletCompartmentKindV2;
/// One validated encrypted V2 compartment.
pub use self::wire_v2::WalletEncryptedCompartmentV2;
/// Password KDF fixed by native Wallet V2.
pub use self::wire_v2::WalletKdfAlgorithmV2;
/// Parsed Argon2id parameters from one V2 key slot.
pub use self::wire_v2::WalletKdfParametersV2;
/// Role of one native V2 key slot.
pub use self::wire_v2::WalletKeySlotRoleV2;
/// One validated OWNER or VIEW V2 key slot.
pub use self::wire_v2::WalletKeySlotV2;
/// Parsed AEAD wrapped-key payload from one V2 key slot.
pub use self::wire_v2::WalletKeyWrapV2;
/// State-signature algorithm fixed by native Wallet V2.
pub use self::wire_v2::WalletStateSignatureAlgorithmV2;
/// Detached OWNER state signature embedded in a V2 envelope.
pub use self::wire_v2::WalletStateSignatureV2;
/// OWNER-signed stable descriptor of the optional VIEW slot in V2.
pub use self::wire_v2::WalletViewDescriptorV2;
/// Wallet-owned tracing target used by the KSP logging facade.
pub(crate) use self::constants::TRACING_TARGET;
@@ -277,6 +350,12 @@ pub(crate) use self::transcript::compartment_aad;
pub(crate) use self::transcript::slot_aad;
/// Internal deterministic OWNER-state transcript codec shared by Wallet crypto layers.
pub(crate) use self::transcript::state_transcript;
/// Internal deterministic V2 compartment-AAD codec shared by Wallet crypto layers.
pub(crate) use self::transcript_v2::compartment_aad_v2;
/// Internal deterministic V2 key-slot-AAD codec shared by Wallet crypto layers.
pub(crate) use self::transcript_v2::slot_aad_v2;
/// Internal deterministic V2 OWNER-state transcript codec shared by Wallet crypto layers.
pub(crate) use self::transcript_v2::state_transcript_v2;
/// Internal no-clobber transfer-file writer used only by OWNER export.
pub(crate) use self::transfer::write_wallet_transfer_file_v1;
/// Crate-internal `OwnerStateV1` state shared across the owning crate.

View File

@@ -0,0 +1,141 @@
// file: crates/ksp-wallet-lib/src/transcript_v2.rs
// version: 1
//! Deterministic `.kspwallet` V2 state-transcript and AEAD-AAD encoding.
const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202;
const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204;
const TAG_COMPARTMENT_KIND: u16 = 0x0200;
const TAG_COMPARTMENT_NONCE: u16 = 0x0203;
const TAG_COMPARTMENT_VERSION: u16 = 0x0201;
const TAG_FORMAT_VERSION: u16 = 0x0002;
const TAG_KDF_ALGORITHM: u16 = 0x0102;
const TAG_KDF_ITERATIONS: u16 = 0x0105;
const TAG_KDF_MEMORY_KIB: u16 = 0x0104;
const TAG_KDF_PARALLELISM: u16 = 0x0106;
const TAG_KDF_SALT: u16 = 0x0107;
const TAG_KDF_VERSION: u16 = 0x0103;
const TAG_MAGIC: u16 = 0x0001;
const TAG_OWNER_AUTH_PUBLIC_KEY: u16 = 0x0003;
const TAG_SLOT_ID: u16 = 0x0100;
const TAG_SLOT_ROLE: u16 = 0x0101;
const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500;
const TAG_VIEW_ENABLED: u16 = 0x0010;
const TAG_VIEW_ROLE: u16 = 0x0011;
const TAG_VIEW_SLOT_ID: u16 = 0x0012;
const TAG_WRAP_ALGORITHM: u16 = 0x0108;
const TAG_WRAP_CIPHERTEXT: u16 = 0x010A;
const TAG_WRAP_NONCE: u16 = 0x0109;
/// Builds the normative OWNER state-signature transcript for one validated V2 envelope.
pub(crate) fn state_transcript_v2(envelope: &crate::KspWalletEnvelopeV2) -> std::vec::Vec<u8> {
let mut output = start(crate::KSPWALLET_V2_STATE_TRANSCRIPT_DOMAIN);
push_common(&mut output, envelope);
push_bool(&mut output, TAG_VIEW_ENABLED, envelope.view_descriptor().enabled());
push_u8(&mut output, TAG_VIEW_ROLE, crate::WalletKeySlotRoleV2::View.wire_id());
match envelope.view_descriptor().slot_id() {
std::option::Option::Some(slot_id) => push_bytes(&mut output, TAG_VIEW_SLOT_ID, slot_id),
std::option::Option::None => push_bytes(&mut output, TAG_VIEW_SLOT_ID, &[]),
}
push_slot(&mut output, envelope.owner_slot(), true);
push_compartment(&mut output, envelope.owner_control(), true);
push_compartment(&mut output, envelope.metadata(), true);
push_compartment(&mut output, envelope.secret(), true);
push_u8(&mut output, TAG_STATE_SIGNATURE_ALGORITHM, envelope.state_signature().algorithm().wire_id());
return output;
}
/// Builds the normative wrapping AAD for one validated V2 OWNER or VIEW key slot.
pub(crate) fn slot_aad_v2(envelope: &crate::KspWalletEnvelopeV2, slot: &crate::WalletKeySlotV2) -> std::vec::Vec<u8> {
let domain = match slot.role() {
crate::WalletKeySlotRoleV2::Owner => crate::KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN,
crate::WalletKeySlotRoleV2::View => crate::KSPWALLET_V2_VIEW_SLOT_AAD_DOMAIN,
};
let mut output = start(domain);
push_common(&mut output, envelope);
push_slot(&mut output, slot, false);
return output;
}
/// Builds the normative AEAD AAD for one validated V2 encrypted compartment.
pub(crate) fn compartment_aad_v2(envelope: &crate::KspWalletEnvelopeV2, compartment: &crate::WalletEncryptedCompartmentV2) -> std::vec::Vec<u8> {
let domain = match compartment.kind() {
crate::WalletCompartmentKindV2::OwnerControl => crate::KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN,
crate::WalletCompartmentKindV2::Metadata => crate::KSPWALLET_V2_METADATA_AAD_DOMAIN,
crate::WalletCompartmentKindV2::Secret => crate::KSPWALLET_V2_SECRET_AAD_DOMAIN,
};
let mut output = start(domain);
push_common(&mut output, envelope);
push_compartment(&mut output, compartment, false);
return output;
}
fn start(domain: &[u8]) -> std::vec::Vec<u8> {
let mut output = std::vec::Vec::with_capacity(512);
output.extend_from_slice(domain);
output.push(0);
return output;
}
fn push_bool(output: &mut std::vec::Vec<u8>, tag: u16, value: bool) {
let byte = if value { 1_u8 } else { 0_u8 };
push_bytes(output, tag, &[byte]);
return;
}
fn push_bytes(output: &mut std::vec::Vec<u8>, tag: u16, value: &[u8]) {
output.extend_from_slice(tag.to_be_bytes().as_slice());
let length = value.len() as u64;
output.extend_from_slice(length.to_be_bytes().as_slice());
output.extend_from_slice(value);
return;
}
fn push_common(output: &mut std::vec::Vec<u8>, envelope: &crate::KspWalletEnvelopeV2) {
push_bytes(output, TAG_MAGIC, crate::KSPWALLET_MAGIC.as_bytes());
push_u32(output, TAG_FORMAT_VERSION, envelope.format_version());
push_bytes(output, TAG_OWNER_AUTH_PUBLIC_KEY, envelope.owner_auth_public_key());
return;
}
fn push_compartment(output: &mut std::vec::Vec<u8>, compartment: &crate::WalletEncryptedCompartmentV2, include_ciphertext: bool) {
push_u8(output, TAG_COMPARTMENT_KIND, compartment.kind().wire_id());
push_u32(output, TAG_COMPARTMENT_VERSION, compartment.payload_version());
push_u8(output, TAG_COMPARTMENT_ALGORITHM, compartment.algorithm().wire_id());
if include_ciphertext {
push_bytes(output, TAG_COMPARTMENT_NONCE, compartment.nonce());
push_bytes(output, TAG_COMPARTMENT_CIPHERTEXT, compartment.ciphertext());
}
return;
}
fn push_slot(output: &mut std::vec::Vec<u8>, slot: &crate::WalletKeySlotV2, include_wrap_payload: bool) {
push_bytes(output, TAG_SLOT_ID, slot.slot_id());
push_u8(output, TAG_SLOT_ROLE, slot.role().wire_id());
push_u8(output, TAG_KDF_ALGORITHM, slot.kdf().algorithm().wire_id());
push_u32(output, TAG_KDF_VERSION, slot.kdf().version());
push_u32(output, TAG_KDF_MEMORY_KIB, slot.kdf().memory_kib());
push_u32(output, TAG_KDF_ITERATIONS, slot.kdf().iterations());
push_u32(output, TAG_KDF_PARALLELISM, slot.kdf().parallelism());
push_bytes(output, TAG_KDF_SALT, slot.kdf().salt());
push_u8(output, TAG_WRAP_ALGORITHM, slot.wrap().algorithm().wire_id());
if include_wrap_payload {
push_bytes(output, TAG_WRAP_NONCE, slot.wrap().nonce());
push_bytes(output, TAG_WRAP_CIPHERTEXT, slot.wrap().ciphertext());
}
return;
}
fn push_u32(output: &mut std::vec::Vec<u8>, tag: u16, value: u32) {
push_bytes(output, tag, value.to_be_bytes().as_slice());
return;
}
fn push_u8(output: &mut std::vec::Vec<u8>, tag: u16, value: u8) {
push_bytes(output, tag, &[value]);
return;
}
#[cfg(test)]
#[path = "../unit_tests/transcript_v2.rs"]
mod tests;

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/tests/public_api.rs
// version: 9
// version: 10
//! Public API canaries for the Wallet foundation.
@@ -81,6 +81,25 @@ fn strict_v1_envelope_and_transcript_are_available_from_crate_root() -> ksp_core
return std::result::Result::Ok(());
}
#[test]
fn public_pre_015_v2_binary_wire_codec_is_available_from_crate_root() -> ksp_core_lib::Result<()> {
let source = include_bytes!("fixtures/kspwallet_v2_wire_only.bin");
let envelope = match ksp_wallet_lib::KspWalletEnvelopeV2::parse_binary(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(envelope.format_version(), ksp_wallet_lib::KSPWALLET_FORMAT_VERSION_V2);
assert_eq!(envelope.owner_slot().role(), ksp_wallet_lib::WalletKeySlotRoleV2::Owner);
assert!(envelope.view_descriptor().enabled());
assert_eq!(envelope.metadata().kind(), ksp_wallet_lib::WalletCompartmentKindV2::Metadata);
let encoded = match envelope.to_binary_bytes() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(encoded.as_slice(), source);
return std::result::Result::Ok(());
}
#[test]
fn public_pre_005_create_open_and_calibrated_defaults_are_available_from_crate_root() {
let _ = ksp_wallet_lib::create_wallet_v1;

View File

@@ -0,0 +1,55 @@
// file: crates/ksp-wallet-lib/unit_tests/transcript_v2.rs
// version: 1
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v2_wire_only.bin");
#[test]
fn v2_state_transcript_is_domain_separated_and_deterministic() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let first = envelope.state_transcript();
let second = envelope.state_transcript();
assert_eq!(first, second);
assert!(first.starts_with(crate::KSPWALLET_V2_STATE_TRANSCRIPT_DOMAIN));
assert!(!first.starts_with(crate::KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN));
return std::result::Result::Ok(());
}
#[test]
fn v2_owner_and_view_slot_aad_are_role_domain_separated() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner = envelope.owner_slot_aad();
let view = match envelope.view_slot_aad() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "fixture VIEW slot is missing"));
},
};
assert!(owner.starts_with(crate::KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN));
assert!(view.starts_with(crate::KSPWALLET_V2_VIEW_SLOT_AAD_DOMAIN));
assert_ne!(owner, view);
return std::result::Result::Ok(());
}
#[test]
fn v2_compartment_aad_is_domain_separated_by_kind() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner_control = envelope.compartment_aad(crate::WalletCompartmentKindV2::OwnerControl);
let metadata = envelope.compartment_aad(crate::WalletCompartmentKindV2::Metadata);
let secret = envelope.compartment_aad(crate::WalletCompartmentKindV2::Secret);
assert!(owner_control.starts_with(crate::KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN));
assert!(metadata.starts_with(crate::KSPWALLET_V2_METADATA_AAD_DOMAIN));
assert!(secret.starts_with(crate::KSPWALLET_V2_SECRET_AAD_DOMAIN));
assert_ne!(owner_control, metadata);
assert_ne!(metadata, secret);
assert_ne!(owner_control, secret);
return std::result::Result::Ok(());
}

View File

@@ -0,0 +1,136 @@
// file: crates/ksp-wallet-lib/unit_tests/wire_v2.rs
// version: 1
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v2_wire_only.bin");
const V1_JSON_FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json");
#[test]
fn strict_v2_fixture_parses_and_round_trips_byte_exact() -> ksp_core_lib::Result<()> {
let parsed = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(parsed.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert!(parsed.view_descriptor().enabled());
assert_eq!(parsed.owner_slot().role(), crate::WalletKeySlotRoleV2::Owner);
assert_eq!(parsed.view_slot().map(crate::WalletKeySlotV2::role), std::option::Option::Some(crate::WalletKeySlotRoleV2::View));
assert_eq!(parsed.owner_control().kind(), crate::WalletCompartmentKindV2::OwnerControl);
assert_eq!(parsed.metadata().kind(), crate::WalletCompartmentKindV2::Metadata);
assert_eq!(parsed.secret().kind(), crate::WalletCompartmentKindV2::Secret);
let encoded = match parsed.to_binary_bytes() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(encoded.as_slice(), FIXTURE);
return std::result::Result::Ok(());
}
#[test]
fn v2_wire_fixture_is_materially_smaller_than_equivalent_v1_json_fixture() {
assert!(FIXTURE.len() < V1_JSON_FIXTURE.len());
assert_eq!(FIXTURE.len(), 628);
assert_eq!(V1_JSON_FIXTURE.len(), 2037);
}
#[test]
fn unknown_v2_format_version_is_rejected_before_body_parsing() {
let mut source = FIXTURE.to_vec();
source[9..11].copy_from_slice(3_u16.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_VERSION_UNSUPPORTED);
}
#[test]
fn declared_length_mismatch_and_trailing_bytes_are_rejected() {
let mut mismatched = FIXTURE.to_vec();
mismatched[11..15].copy_from_slice(627_u32.to_be_bytes().as_slice());
let mismatch_result = crate::KspWalletEnvelopeV2::parse_binary(mismatched.as_slice());
assert!(mismatch_result.is_err());
let mut trailing = FIXTURE.to_vec();
trailing.push(0);
trailing[11..15].copy_from_slice(629_u32.to_be_bytes().as_slice());
let trailing_result = crate::KspWalletEnvelopeV2::parse_binary(trailing.as_slice());
assert!(trailing_result.is_err());
}
#[test]
fn unknown_header_flags_are_rejected() {
let mut source = FIXTURE.to_vec();
source[15..17].copy_from_slice(2_u16.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn enabled_view_descriptor_must_match_the_v2_view_slot() {
let mut source = FIXTURE.to_vec();
source[49] ^= 0x01;
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_key_slots_require_canonical_owner_then_view_order() {
let mut source = FIXTURE.to_vec();
source[65] = 2;
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_pathological_kdf_parameters_are_rejected_before_crypto() {
let mut source = FIXTURE.to_vec();
source[87..91].copy_from_slice(0_u32.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_CRYPTO_PARAMETERS_INVALID);
}
#[test]
fn v2_oversized_document_is_rejected_before_body_allocation() {
let oversized = std::vec![0_u8; crate::KSPWALLET_MAX_FILE_BYTES + 1];
let result = crate::KspWalletEnvelopeV2::parse_binary(oversized.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_envelope_debug_does_not_render_ciphertext_contents() -> ksp_core_lib::Result<()> {
let parsed = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rendered = format!("{parsed:?}");
assert!(rendered.contains("ciphertext_bytes"));
assert!(!rendered.contains("128, 129, 130, 131"));
assert!(!rendered.contains("224, 225, 226, 227"));
return std::result::Result::Ok(());
}

435
deltas/0.2.6/pre.015.md Normal file
View File

@@ -0,0 +1,435 @@
<!-- file: deltas/0.2.6/pre.015.md -->
<!-- version: 1 -->
# Delta `0.2.6-pre.015` — wire binaire `.kspwallet` V2 et codec canonique
## Base requise
```text
0.2.6-pre.014-fix.001 appliquée
workspace.package.version = 0.2.6-pre.14.fix.1
```
Le checkpoint opérateur de la base est intégralement vert :
```text
cargo fmt --all OK
python3 scripts/audit_rust_workspace_rules.py clean
cargo check --workspace OK
cargo clippy --workspace --all-targets OK
cargo test -p ksp-app-config-desk OK
cargo test -p ksp-app-wallet-desk OK
cargo test --workspace OK
Wallet Desk release_compliance 4/4 OK
smokes live toujours opt-in/ignored
Tauri Config Desk crate-local OK visuel
Tauri Wallet Desk crate-local OK visuel + import/balance Devnet manuel
```
Le correctif `pre.014-fix.001` est donc accepté comme base de `pre.015`.
## Type de livraison
Cette tranche est fournie sous forme de **delta uniquement**, à appliquer sur la base précédente :
```text
ksp-general-0.2.6-pre.015-delta.zip
```
Le signal technique devient :
```text
workspace.package.version = 0.2.6-pre.15
commit = v0.2.6-pre.015
```
Aucun tag prerelease.
Les `package.json` et `tauri.conf.json` des applications ne changent pas : `pre.015` ne modifie ni frontend, ni Tauri, ni contrat de build des Desks.
## Objectif de la tranche
`pre.015` fige le **format `.kspwallet` V2 binaire** et son codec structurel strict dans `ksp-wallet-lib`.
Cette tranche réalise :
```text
spécification normative V2
framing binaire KSP propriétaire
codec parse/serialize byte-exact
identifiants numériques stables
bornes structurelles avant crypto
transcripts/AAD V2 séparés de V1
fixture binaire canonique wire-only
tests déterministes du codec et des invariants
API publique du codec V2 depuis la racine de crate
documentation de la politique default != latest
réalignement du plan 0.2.6 et des releases 0.2.7+
```
Cette tranche **ne réalise pas encore** :
```text
create_wallet_file_v2
open_wallet_*_file_v2
façades génériques multi-version
basculement de Wallet Desk vers les façades génériques
migration V1 -> V2
réécriture automatique de wallets V1
second facteur / V3
build Tauri de production
```
Ces responsabilités restent réparties entre `pre.016`, `pre.017` et `pre.018`.
## Décision V1 / V2
V1 reste un format stable historique :
```text
format_version = 1
JSON UTF-8 strict
bytes -> Base64URL sans padding
APIs _v1 conservées
lecture durable obligatoire
```
V2 est un nouveau format explicite :
```text
format_version = 2
wire binaire KSP
aucune Base64
aucune compression
entiers multi-octets big-endian
ordre et cardinalité canoniques
aucun trailing byte
```
Le caractère binaire n'est pas présenté comme une mesure cryptographique. Les propriétés de confidentialité/authenticité restent possédées par Argon2id, XChaCha20-Poly1305, Ed25519 et les règles VIEW/OWNER.
## Wire V2
Le document commence par :
```text
magic 9 bytes = KSPWALLET
format_version u16 BE = 2
document_length u32 BE = taille totale exacte
flags u16 BE
owner_auth_public_key 32 bytes
view_slot_id 16 bytes si VIEW_ENABLED
```
Flags :
```text
bit 0 = VIEW_ENABLED
autres bits = réservés et obligatoirement zéro
```
Le body est strictement :
```text
OWNER key slot
VIEW key slot si VIEW_ENABLED
OWNER-CONTROL compartment
METADATA compartment
SECRET compartment
state signature
EOF immédiat
```
Les slots et compartiments transportent leurs IDs numériques de rôle/kind/algorithme, leurs longueurs explicites et leurs bytes directement.
Bornes et détails complets :
```text
docs/formats/KSPWALLET_V2.md
```
## Codec strict
Nouveau module :
```text
crates/ksp-wallet-lib/src/wire_v2.rs
```
Surface publique principale :
```rust
KspWalletEnvelopeV2::parse_binary(...)
KspWalletEnvelopeV2::to_binary_bytes(...)
```
Le parser rejette avant toute opération cryptographique :
```text
> 1 MiB
magic/version incorrects
document_length incohérent
flags réservés
troncature / longueur hors input
role/kind/algorithme inconnu
ordre OWNER/VIEW ou compartiments non canonique
VIEW descriptor incohérent
Argon2 structurellement invalide
salt/ciphertext hors bornes
payload_version non supporté
trailing bytes
```
Le codec n'authentifie ni ne déchiffre encore un wallet V2 ; cette composition avec les primitives Wallet existantes est réservée à `pre.016`.
## Transcripts et AAD V2
Nouveau module :
```text
crates/ksp-wallet-lib/src/transcript_v2.rs
```
V2 possède des domain separators distincts :
```text
KSPWALLET-V2-STATE
KSPWALLET-V2-AAD-OWNER-SLOT
KSPWALLET-V2-AAD-VIEW-SLOT
KSPWALLET-V2-AAD-OWNER-CONTROL
KSPWALLET-V2-AAD-METADATA
KSPWALLET-V2-AAD-SECRET
```
La discipline TLV déterministe de V1 est conservée mais les rôles, algorithmes et kinds V2 sont transcriptés sous leurs IDs numériques. Les domains V1/V2 restent séparés pour éviter toute interchangeabilité cryptographique entre versions.
Le VIEW wrap mutable reste hors du state transcript, comme en V1 ; le descriptor signé lie son identité stable et permet la self-rotation VIEW sans OWNER.
## Fixture canonique
Nouvelle fixture structurelle :
```text
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v2_wire_only.bin
```
Mesure :
```text
V1 JSON wire-only = 2037 octets
V2 binary wire-only = 628 octets
réduction = 1409 octets, environ 69,2 %
```
Hash du fixture V2 :
```text
sha256 = cbacb37326c76e6dde8d614601cd765a8c0fd9744f891d640b8a3cd71cf60a93
```
Cette fixture valide framing/canonicalité ; elle n'est pas revendiquée comme un wallet V2 cryptographiquement authentifié tant que création/open V2 n'est pas matérialisé en `pre.016`.
## Politique API durable : default distinct de latest
La documentation fige dès `pre.015` la politique qui sera matérialisée en code dans `pre.016`.
Deux notions doivent rester distinctes :
```text
DEFAULT_WALLET_FORMAT
LATEST_SUPPORTED_WALLET_FORMAT
```
Après intégration runtime V2 :
```text
DEFAULT_WALLET_FORMAT = V2
LATEST_SUPPORTED_WALLET_FORMAT = V2
```
Si V3 existe plus tard :
```text
DEFAULT_WALLET_FORMAT = V2 # peut rester V2
LATEST_SUPPORTED_WALLET_FORMAT = V3
```
Une nouvelle version supportée ne change jamais automatiquement le default.
### Création
API générique :
```text
create_wallet_file(...) -> DEFAULT_WALLET_FORMAT, donc V2
```
APIs forcées :
```text
create_wallet_file_v1(...)
create_wallet_file_v2(...)
create_wallet_file_v3(...) si V3 existe un jour
```
Les fonctions `_vN` restent une possibilité explicite pour les applications qui veulent imposer un wire précis.
### Lecture / inspection
API générique :
```text
open_wallet_*_file(...)
inspect_locked_wallet_file(...)
```
doit détecter et dispatcher les versions supportées.
API versionnée :
```text
open_wallet_*_file_v1(...)
open_wallet_*_file_v2(...)
inspect_locked_wallet_file_v1(...)
inspect_locked_wallet_file_v2(...)
```
doit exiger exactement la version indiquée.
La même règle est appliquée aux autres opérations pour lesquelles la version native du fichier est pertinente.
## Future V3 / second facteur
V2 est volontairement **sans second facteur**.
Si un futur V3 introduit password + 2FA :
```text
ksp-wallet-lib possède la politique d'autorisation
ksp-wallet-lib décide si un second facteur est requis
ksp-wallet-lib possède challenge et validation cryptographique
WalletOwner est impossible tant que les facteurs exigés ne sont pas satisfaits
Wallet Desk ne décide jamais qu'une preuve 2FA est valide
```
Wallet Desk ou un autre client peut néanmoins évoluer pour l'UX interactive : OTP, push/attente, enrollment, recovery, hardware/WebAuthn ou consentement externe.
`ksp-wallet-lib` doit conserver sa frontière autonome vis-à-vis de Config/Transport/Tauri. Si un fournisseur réseau est nécessaire, une abstraction/provider KSP séparée peut obtenir la preuve tandis que Wallet reste propriétaire de la règle d'autorisation.
## Nouveau découpage de `0.2.6`
La documentation/candidate anciennement prévue en `pre.015` est repoussée afin de ne pas mélanger wire, runtime/migration et packaging :
```text
pre.015 wire binaire V2 + codec canonique
pre.016 APIs génériques/versionnées + création/open V2 + Wallet Desk sur API générique
pre.017 migration explicite V1 -> V2 + persistence/canaris + régression Wallet Desk
pre.018 documentation finale + candidate + stratégie release/bundle/CWD + build Tauri final
rel.001 publication stable v0.2.6
```
Le build Tauri reste interdit avant `pre.018` et devra être l'absolue dernière opération de validation de la candidate.
## Réalignement de la série
`pre.014` avait temporairement intercalé le chantier binaire en `0.2.7`. Cette décision est superseded puisque V2 est désormais traité dans `0.2.6`.
La suite redevient :
```text
0.2.7 WebSocket Solana standard
0.2.8 Helius LaserStream WebSocket
0.2.9 Yellowstone gRPC standard/provider-neutral
0.2.10 off-chain price transport
0.2.11 price visualization desk + intégration Wallet Desk
0.2.12 interface/wire foundation
0.2.13 program-api foundation
```
Le prompt `prompts/012-V0_2_7_START_PROMPT.md` est réaligné sur WebSocket Solana standard.
## Fichiers ajoutés
```text
crates/ksp-wallet-lib/src/transcript_v2.rs
crates/ksp-wallet-lib/src/wire_v2.rs
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v2_wire_only.bin
crates/ksp-wallet-lib/unit_tests/transcript_v2.rs
crates/ksp-wallet-lib/unit_tests/wire_v2.rs
docs/formats/KSPWALLET_V2.md
deltas/0.2.6/pre.015.md
```
## Fichiers modifiés
```text
Cargo.toml
ROADMAP.md
crates/ksp-wallet-lib/README.md
crates/ksp-wallet-lib/USAGE.md
crates/ksp-wallet-lib/src/constants.rs
crates/ksp-wallet-lib/src/lib.rs
crates/ksp-wallet-lib/tests/public_api.rs
docs/000-README.md
docs/IDEAS.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/formats/000-README.md
docs/formats/KSPWALLET_V1.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md
docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md
prompts/000-README.md
prompts/011-V0_2_6_START_PROMPT.md
prompts/012-V0_2_7_START_PROMPT.md
```
## Fichiers volontairement non modifiés
```text
CHANGELOG.md
crates/ksp-app-config-desk/*
crates/ksp-app-wallet-desk/*
```
Le CHANGELOG reste réservé à la clôture. Les Desks ne changent pas tant que la façade générique/runtime V2 n'est pas prête en `pre.016`.
## Contrôles assistant
L'environnement de génération ne fournit pas `cargo`, `rustc` ou `rustfmt`. Les contrôles exécutables disponibles sont donc structurels :
```text
python3 scripts/audit_rust_workspace_rules.py requis avant livraison
fixture V2 header/length/layout vérification indépendante
fixture V2 sha256 vérification indépendante
comparaison V1/V2 2037 / 628 octets
delta vérifié contre pre.014-fix.001
```
Compilation, Clippy et tests restent autoritatifs côté opérateur.
## Validation opérateur requise
Après application du delta :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-wallet-lib
cargo test -p ksp-app-wallet-desk
cargo test --workspace
```
Points attendus :
```text
V1 reste intégralement vert
nouveaux tests wire_v2/transcript_v2 verts
public API V2 codec visible depuis crate root
aucune régression Wallet Desk
smokes réseau restent ignored sauf opt-in explicite
```
**Ne pas exécuter `cargo tauri build` en `pre.015`.** Le build final reste réservé à `pre.018` et doit demeurer l'ultime opération de validation de `0.2.6`.

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<!-- file: docs/IDEAS.md -->
<!-- version: 20 -->
<!-- version: 21 -->
# Idées à explorer
@@ -202,13 +202,13 @@ Formats/cibles à inventorier et prioriser selon usage réel :
Chaque format doit être étudié côté sécurité, round-trip, secret/public, dépendances et compatibilité avant engagement.
### Conteneur binaire `.kspwallet` et formats logiques futurs
### `.kspwallet` V2 binaire et formats futurs
**Status :** Retenu pour audit en `0.2.7` / facteurs futurs à explorer
**Status :** V2 retenu et matérialisé en `0.2.6-pre.015` / facteurs futurs à explorer
Le JSON V1 actuel est un format dinterop lisible. Base64 seul napporte aucune sécurité et resterait un texte trivialement décodable avec environ un tiers de surcharge. `0.2.7` doit donc étudier un **conteneur de persistence binaire versionné** séparé du `format_version` logique/cryptographique : magic/framing explicite, lecture rétrocompatible du JSON V1 historique, écriture binaire par défaut après validation, migration explicite et test vectors. Le choix du codec/framing exact est audité avant engagement ; il ne doit pas casser les transcripts/AAD, VIEW/OWNER, keypair ou import/export.
Le JSON V1 actuel reste le format historique stable et lisible. Base64 seul napporte aucune sécurité et resterait un texte trivialement décodable avec environ un tiers de surcharge. La décision initialement envisagée pour `0.2.7` a été ramenée dans `0.2.6` : `pre.015` définit un **wire binaire V2 KSP** avec magic/framing explicite, entiers big-endian, identifiants numériques stables, longueurs bornées et lecture/écriture canonique stricte. V1 reste supporté sans réinterprétation ; la façade de lecture multi-version, la création V2 et la politique `DEFAULT_WALLET_FORMAT = V2` arrivent en `pre.016`, puis la migration explicite V1 -> V2 en `pre.017`. Le V2 ne modifie pas à lui seul les garanties cryptographiques de VIEW/OWNER, keypair ou import/export.
Un futur `format_version >= 2` pourra introduire dautres modèles dautorisation, notamment password + facteur supplémentaire. `ksp-wallet-lib` restera propriétaire du format, des challenges et de la vérification, mais toute interaction réelle (OTP, enrollment/recovery, hardware/WebAuthn, validation distante) exigera une évolution de Wallet Desk ou du client concerné. Un seed TOTP stocké uniquement dans le même fichier que le wallet ne doit pas être présenté automatiquement comme un second facteur indépendant contre un attaquant possédant ce fichier.
V2 est désormais réservé au wire binaire KSP sans second facteur. Un futur V3 pourra introduire dautres modèles dautorisation, notamment password + facteur supplémentaire. `ksp-wallet-lib` restera propriétaire du format, des challenges et de la vérification, mais toute interaction réelle (OTP, enrollment/recovery, hardware/WebAuthn, validation distante) exigera une évolution de Wallet Desk ou du client concerné. Un seed TOTP stocké uniquement dans le même fichier que le wallet ne doit pas être présenté automatiquement comme un second facteur indépendant contre un attaquant possédant ce fichier.
## Pipelines

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 16 -->
<!-- version: 17 -->
# Inventaire initial des composants KSP
@@ -26,14 +26,14 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1``0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
| Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
| Wallet Desk | `ksp-app-wallet-desk` | app | Retenu | `0.2.6` | Wallet + Config composite + HTTP/balance |
| Wallet persistence | `ksp-wallet-lib` | lib | Retenu | `0.2.7` | conteneur binaire rétrocompatible autour du payload V1 |
| Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | WebSocket Solana complet, sessions/subscriptions |
| Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.9` | LaserStream WebSocket comme extension du moteur standard |
| Yellowstone | `ksp-onchain-transport-lib` | lib | Pressenti | `0.2.10` | client gRPC standard/provider-neutral |
| Off-chain price | `ksp-offchain-transport-lib` | lib | Retenu | `0.2.11` | première abstraction/provider de prix SOL/USD, SOL/EUR |
| Price Desk | nom à fixer | app | Retenu | `0.2.12` | visualisation/validation des prix + intégration Wallet Desk |
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.13` | façade wire officielle + API publique wire |
| Program API | `ksp-program-api` | API | Retenu | `0.2.14` | contrats extensibles Program |
| Wallet V2 | `ksp-wallet-lib` | lib | En cours | `0.2.6` | wire binaire V2 + API default/versionnée + migration |
| Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.7` | WebSocket Solana complet, sessions/subscriptions |
| Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | LaserStream WebSocket comme extension du moteur standard |
| Yellowstone | `ksp-onchain-transport-lib` | lib | Pressenti | `0.2.9` | client gRPC standard/provider-neutral |
| Off-chain price | `ksp-offchain-transport-lib` | lib | Retenu | `0.2.10` | première abstraction/provider de prix SOL/USD, SOL/EUR |
| Price Desk | nom à fixer | app | Retenu | `0.2.11` | visualisation/validation des prix + intégration Wallet Desk |
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.12` | façade wire officielle + API publique wire |
| Program API | `ksp-program-api` | API | Retenu | `0.2.13` | contrats extensibles Program |
| Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles |
| Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` |
| Store API | `ksp-store-api` | API | Retenu | `0.3.1` | contrats persistence backend-agnostic, RAW d'abord |

View File

@@ -1,5 +1,5 @@
<!-- file: docs/formats/000-README.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Formats KSP
@@ -10,3 +10,4 @@ Une spécification de format décrit le wire exact, les encodages, les limites,
## Formats actifs
- [`KSPWALLET_V1.md`](KSPWALLET_V1.md) — spécification du format natif autonome `.kspwallet` V1. `0.2.5-pre.003` fige l'enveloppe/wire et les transcripts/AAD, `pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS, `pre.005` fixe les payloads plaintext, le profil de création KSP calibré, l'autorité Ed25519 OWNER et le vecteur complet, `pre.006``pre.008` matérialisent persistence/administration/transfert, `pre.009` ferme l'audit adversarial/interoperabilité/compliance et `pre.010` synchronise la documentation finale sans modifier le wire V1.
- [`KSPWALLET_V2.md`](KSPWALLET_V2.md) — wire binaire natif V2 introduit par `0.2.6-pre.015` : framing canonique KSP, IDs numériques, longueurs big-endian, aucun Base64/compression, domains/transcripts V2 distincts et politique `default != latest`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/formats/KSPWALLET_V1.md -->
<!-- version: 11 -->
<!-- version: 12 -->
# `.kspwallet` V1 — spécification du format natif Wallet KSP
@@ -22,6 +22,8 @@ règles unknown-field / unknown-version
`0.2.5-pre.004` ajoute les primitives KDF/AEAD normatives et un premier vecteur cryptographique public. `0.2.5-pre.005` fixe les payloads plaintext V1, l'autorité Ed25519 OWNER, les procédures de création et d'ouverture VIEW/OWNER, le profil de création KSP issu du benchmark opérateur et un vecteur `.kspwallet` complet généré indépendamment du code Rust. `0.2.5-pre.006` matérialise la persistence filesystem bornée et la création no-clobber. `0.2.5-pre.007` matérialise la signature Solana OWNER, l'administration des metadata, les rotations OWNER/VIEW, la révocation forte VIEW et leur remplacement filesystem capability-bound. `0.2.5-pre.008` matérialise les adapters Solana CLI JSON et Base58 complet, leur inspection sûre, l'import no-clobber vers un nouveau `.kspwallet` et l'export secret OWNER explicite. `pre.009` ferme l'audit adversarial/interoperability/compliance et `pre.010` synchronise la documentation de clôture sans modifier le wire ni les primitives. Après publication stable de V1, toute évolution qui modifie un élément déclaré **figé** par cette spécification doit être explicitement tracée ; une incompatibilité de wire exige un nouveau `format_version`.
Depuis `0.2.6-pre.015`, V1 reste explicitement supporté comme format historique stable tandis que V2 définit le nouveau wire binaire. Les APIs versionnées V1 sont conservées et une lecture générique future doit auto-détecter V1/V2 sans migration implicite.
Le but final est qu'une implémentation indépendante en Rust, Python, Go, C/C++, Java ou autre puisse créer, parser, vérifier et ouvrir un `.kspwallet` sans lire le code source de `ksp-wallet-lib`.
## 2. Modèle de confiance V1

View File

@@ -0,0 +1,441 @@
<!-- file: docs/formats/KSPWALLET_V2.md -->
<!-- version: 1 -->
# `.kspwallet` V2 — spécification du wire binaire natif KSP
## 1. Statut et objectif
Ce document est l'autorité normative du **wire `.kspwallet` `format_version = 2`** introduit par `0.2.6-pre.015`.
V2 remplace l'enveloppe JSON/Base64url de V1 par un framing binaire KSP canonique. L'objectif est :
```text
réduire fortement la taille persistée
supprimer l'encodage Base64 des champs déjà binaires
éviter qu'un .kspwallet nouvellement créé soit un document JSON lisible comme tel
conserver un format documenté et implémentable hors Rust
préserver les frontières cryptographiques Wallet
```
Le caractère binaire **n'ajoute aucune propriété cryptographique**. La confidentialité et l'authenticité continuent de dépendre d'Argon2id, XChaCha20-Poly1305 et Ed25519, jamais de l'absence de JSON.
`0.2.6-pre.015` fige la grammaire binaire, les identifiants numériques, les bornes structurelles et les transcripts/AAD V2. La création/ouverture/persistence V2, l'auto-détection V1/V2 et le basculement de l'API générique vers V2 sont matérialisés dans les tranches suivantes de `0.2.6`.
V1 reste un format stable supporté. V2 ne réinterprète jamais un document V1 avec sa propre grammaire.
## 2. Politique de version et API
Les notions suivantes sont distinctes :
```text
DEFAULT_WALLET_FORMAT
LATEST_SUPPORTED_WALLET_FORMAT
```
À partir de l'intégration V2 :
```text
DEFAULT_WALLET_FORMAT = V2
LATEST_SUPPORTED_WALLET_FORMAT = V2
```
Si un futur V3 apparaît, par exemple pour un modèle d'autorisation avec second facteur, `LATEST_SUPPORTED_WALLET_FORMAT` pourra devenir V3 tandis que `DEFAULT_WALLET_FORMAT` pourra **rester V2**. Le default ne suit jamais automatiquement la dernière version.
La politique d'API retenue est :
```text
create_wallet_file(...) -> format default explicitement choisi, V2
create_wallet_file_v1(...) -> force V1
create_wallet_file_v2(...) -> force V2
create_wallet_file_v3(...) -> force V3 si V3 existe un jour
open_wallet_*_file(...) -> détecte puis dispatch les versions supportées
open_wallet_*_file_v1(...) -> exige V1
open_wallet_*_file_v2(...) -> exige V2
open_wallet_*_file_v3(...) -> exige V3 si V3 existe un jour
```
Le même principe s'applique aux opérations dont la version de fichier est pertinente, notamment inspection et import natif.
## 3. Encodage général
Un document V2 est :
```text
binaire
maximum 1 048 576 octets
entiers multi-octets = unsigned big-endian
ordre des champs = normatif
aucun padding implicite
aucune Base64
aucune compression
aucun trailing byte
```
Les longueurs sont exprimées en **octets**.
Une implémentation conforme doit vérifier les bornes **avant** toute allocation dépendant d'une longueur reçue. Le document entier est déjà borné par `1 048 576` octets avant parsing.
## 4. Header V2
Le début de fichier est strictement :
| Ordre | Champ | Taille | Valeur / règle |
|------:|---------------------------|-----------------:|------------------------------------------------|
| 1 | `magic` | 9 | ASCII exact `KSPWALLET` |
| 2 | `format_version` | 2 | `0x0002` |
| 3 | `document_length` | 4 | longueur totale exacte du fichier |
| 4 | `flags` | 2 | bit 0 = VIEW activé ; tous les autres bits = 0 |
| 5 | `owner_auth_public_key` | 32 | clé publique Ed25519 OWNER |
| 6 | `view_descriptor.slot_id` | 16 conditionnels | présent uniquement si `flags & 0x0001 != 0` |
Offsets fixes avant le descripteur conditionnel :
```text
0x0000..0x0008 magic
0x0009..0x000A format_version
0x000B..0x000E document_length
0x000F..0x0010 flags
0x0011..0x0030 owner_auth_public_key
```
Pour un wallet VIEW-enabled, `view_descriptor.slot_id` occupe ensuite `0x0031..0x0040`.
### 4.1 Flags
```text
0x0001 VIEW_ENABLED
0xFFFE réservé, doit être zéro
```
Tout bit réservé non nul rend le document invalide. V2 n'emploie pas les bits réservés comme mécanisme d'extension silencieuse ; une modification incompatible exige un nouveau format.
## 5. Ordre canonique du body
Après le header :
```text
OWNER key slot obligatoire
VIEW key slot présent seulement si VIEW_ENABLED
OWNER-CONTROL encrypted compartment obligatoire
METADATA encrypted compartment obligatoire
SECRET encrypted compartment obligatoire
state signature obligatoire
EOF immédiat
```
Aucun compteur de slots ou de compartiments n'est nécessaire : leur cardinalité et leur ordre sont déterminés par V2.
## 6. Key slot V2
Chaque key slot est encodé ainsi :
| Champ | Taille | Valeur / règle |
|--------------------------|---------:|-----------------------------------------|
| `role` | 1 | `0x01` OWNER, `0x02` VIEW |
| `slot_id` | 16 | identifiant binaire exact |
| `kdf_algorithm` | 1 | `0x01` Argon2id |
| `kdf_version` | 4 | `19` |
| `memory_kib` | 4 | `1..1 048 576`, et `>= parallelism * 8` |
| `iterations` | 4 | `1..64` |
| `parallelism` | 4 | `1..64` |
| `salt_length` | 1 | `16..64` |
| `salt` | variable | exactement `salt_length` octets |
| `wrap_algorithm` | 1 | `0x01` XChaCha20-Poly1305 |
| `wrap_nonce` | 24 | nonce exact |
| `wrap_ciphertext_length` | 2 | `16..4096` |
| `wrap_ciphertext` | variable | exactement la longueur déclarée |
Ordre obligatoire :
```text
premier slot = OWNER
second slot = VIEW seulement si VIEW_ENABLED
```
Si VIEW est activé :
```text
header.view_descriptor.slot_id == VIEW key slot.slot_id
```
Toute divergence est invalide avant KDF/déchiffrement.
## 7. Compartiments V2
Chaque compartiment est encodé :
| Champ | Taille | Valeur / règle |
|---------------------|---------:|------------------------------------------------------|
| `kind` | 1 | `0x01` OWNER-CONTROL, `0x02` METADATA, `0x03` SECRET |
| `payload_version` | 4 | `1` pour le profil initial V2 |
| `algorithm` | 1 | `0x01` XChaCha20-Poly1305 |
| `nonce` | 24 | nonce exact |
| `ciphertext_length` | 4 | longueur exacte |
| `ciphertext` | variable | ciphertext + tag Poly1305 |
Bornes initiales :
```text
OWNER-CONTROL : 16..4096 octets
METADATA : 16..65552 octets
SECRET : 16..4096 octets
```
L'ordre exact est OWNER-CONTROL, METADATA, SECRET. Le `kind` encodé doit correspondre à la position attendue ; il n'autorise pas un réordonnancement.
Les payloads plaintext V2 conservent le modèle fonctionnel établi en V1 pour cette évolution : owner-control, metadata et secret Solana restent des compartiments distincts. Une future modification incompatible de leur sémantique exige une version de format explicite.
## 8. Signature d'état
La fin du document est :
| Champ | Taille | Valeur / règle |
|-----------------------------|-------:|---------------------------|
| `state_signature.algorithm` | 1 | `0x01` Ed25519 |
| `state_signature.signature` | 64 | signature detached exacte |
Aucun octet ne peut suivre ces 65 octets.
## 9. Identifiants numériques figés
### 9.1 Rôles
```text
0x01 OWNER
0x02 VIEW
```
### 9.2 Algorithmes
```text
KDF
0x01 Argon2id
AEAD
0x01 XChaCha20-Poly1305
state signature
0x01 Ed25519
```
### 9.3 Compartiments
```text
0x01 OWNER-CONTROL
0x02 METADATA
0x03 SECRET
```
`0x00` est invalide dans ces espaces. Les autres valeurs sont réservées et rejetées par V2 tant qu'elles ne sont pas explicitement normalisées par une évolution compatible documentée ; aucune valeur inconnue n'est devinée.
## 10. Canonicalité et rejet
Le parser V2 doit rejeter avant crypto :
```text
fichier > 1 MiB
magic incorrect
format_version != 2
document_length différent de la taille reçue
flag réservé non nul
champ tronqué
longueur qui dépasse le reste du document
role/algorithm/kind inconnu
ordre OWNER/VIEW invalide
VIEW descriptor et VIEW slot incohérents
Argon2 hors bornes
salt hors bornes
ciphertext hors bornes
payload_version non supporté
trailing bytes
```
Il n'existe qu'un encodage canonique d'un même état sémantique V2 : mêmes champs, même ordre, mêmes largeurs, même endianness et aucune donnée ignorée.
## 11. Transcripts et AAD V2
V2 possède ses propres domaines ; V1 et V2 ne partagent jamais un domain separator :
```text
KSPWALLET-V2-STATE
KSPWALLET-V2-AAD-OWNER-SLOT
KSPWALLET-V2-AAD-VIEW-SLOT
KSPWALLET-V2-AAD-OWNER-CONTROL
KSPWALLET-V2-AAD-METADATA
KSPWALLET-V2-AAD-SECRET
```
Le transcript/AAD V2 conserve la discipline TLV déterministe de V1 :
```text
domain || 0x00
puis pour chaque champ :
tag:u16 big-endian
length:u64 big-endian
value:length bytes
```
Les tags restent alignés avec les familles V1 :
```text
0x0001 magic
0x0002 format_version
0x0003 owner_auth_public_key
0x0010 view_enabled
0x0011 view_role
0x0012 view_slot_id
0x0100 slot_id
0x0101 slot_role
0x0102 kdf_algorithm
0x0103 kdf_version
0x0104 kdf_memory_kib
0x0105 kdf_iterations
0x0106 kdf_parallelism
0x0107 kdf_salt
0x0108 wrap_algorithm
0x0109 wrap_nonce
0x010A wrap_ciphertext
0x0200 compartment_kind
0x0201 compartment_version
0x0202 compartment_algorithm
0x0203 compartment_nonce
0x0204 compartment_ciphertext
0x0500 state_signature_algorithm
```
Différence normative V2 : les rôles/algorithmes/kinds sont transcriptés sous leur **ID numérique d'un octet**, et `format_version = 2` est transcripté en `u32` big-endian. Les domaines distincts empêchent qu'un transcript V1 et un transcript V2 soient interchangeables.
### 11.1 State transcript
Le state transcript contient :
```text
common
view descriptor
OWNER slot avec wrap nonce+ciphertext
OWNER-CONTROL avec nonce+ciphertext
METADATA avec nonce+ciphertext
SECRET avec nonce+ciphertext
state signature algorithm
```
Le VIEW wrap mutable n'est pas ajouté au state transcript, selon le modèle d'autorisation VIEW déjà retenu : son identité stable reste liée par le descriptor signé tandis que son credential peut être self-rotaté sans OWNER.
### 11.2 Slot AAD
Le slot AAD contient :
```text
common
slot_id
role
KDF algorithm/version/parameters/salt
wrap algorithm
```
Le wrap nonce/ciphertext n'est pas inclus dans son propre AAD.
### 11.3 Compartment AAD
Le compartment AAD contient :
```text
common
kind
payload_version
algorithm
```
Le nonce/ciphertext n'est pas inclus dans son propre AAD.
## 12. V1 et V2
V1 et V2 sont deux formats explicites :
| Propriété | V1 | V2 |
|--------------------------|------------------------------------|---------------|
| enveloppe | JSON UTF-8 | binaire KSP |
| champs binaires | Base64url no-pad | bytes directs |
| version | `1` | `2` |
| canonicalité | sémantique JSON + Base64 canonique | byte-exact |
| taille fixture wire-only | 2037 octets | 628 octets |
| Argon2id | oui | oui |
| XChaCha20-Poly1305 | oui | oui |
| Ed25519 OWNER state | oui | oui |
| VIEW/OWNER | oui | oui |
La réduction mesurée sur le fixture structurel de référence est :
```text
2037 -> 628 octets
-1409 octets
~69,2 %
```
Cette mesure n'est pas une promesse de ratio constant : la taille dépend notamment des metadata chiffrées.
## 13. Fixture structurelle
Fixture canonique de `pre.015` :
```text
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v2_wire_only.bin
size = 628
sha256 = cbacb37326c76e6dde8d614601cd765a8c0fd9744f891d640b8a3cd71cf60a93
```
Préfixe hexadécimal :
```text
4b535057414c4c4554 0002 00000274 0001
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
202122232425262728292a2b2c2d2e2f
```
Lecture :
```text
4b535057414c4c4554 = "KSPWALLET"
0002 = format_version V2
00000274 = 628 octets
0001 = VIEW_ENABLED
```
La fixture `wire_only` vérifie framing/canonicalité ; elle n'est pas présentée comme une signature cryptographique V2 valide tant que la création/open V2 n'est pas intégrée par la tranche suivante.
## 14. Compatibilité et migration
V2 n'autorise aucune migration implicite à l'ouverture.
Politique prévue :
```text
lecture générique V1/V2 oui
création générique V2
création V1 forcée oui
création V2 forcée oui
réécriture V1 -> V2 cachée non
migration V1 -> V2 opération explicite
```
Une migration doit authentifier le wallet source avec la capability requise, produire un nouveau document V2 valide et respecter la persistence no-clobber/atomique. Une simple transcodification non authentifiée des bytes V1 n'est pas suffisante puisque V2 possède ses propres domains/transcripts.
## 15. Future V3 / second facteur
V2 ne réalise aucun second facteur.
Si un futur V3 ajoute une autorisation dépendant d'un facteur externe :
```text
ksp-wallet-lib possède la règle qui exige le facteur
ksp-wallet-lib possède le challenge et la validation cryptographique
aucun WalletOwner ne peut être obtenu si le facteur requis n'est pas satisfait
Wallet Desk ne décide jamais que le second facteur est valide
```
Une UI comme Wallet Desk peut néanmoins devoir évoluer pour orchestrer l'expérience : attente de confirmation, saisie OTP, enrollment, recovery, hardware/WebAuthn ou consentement externe.
Le réseau ou le fournisseur externe ne doit pas forcer `ksp-wallet-lib` à dépendre directement de Config/Transport/Tauri. Une abstraction/provider KSP séparée peut fournir la preuve à Wallet tandis que Wallet reste propriétaire de la politique d'autorisation.
L'arrivée d'un V3 ne change pas automatiquement le default : V2 peut rester `DEFAULT_WALLET_FORMAT` aussi longtemps que KSP le décide explicitement.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/000-README.md -->
<!-- version: 49 -->
<!-- version: 50 -->
# Plans KSP
@@ -21,7 +21,7 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou
- [`010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de la release stable `0.2.3 — HTTP Transactions`, ouvert par `pre.001`, exécuté jusqu'à `pre.009` puis publié par `rel.001`; il couvre les 11 méthodes, la classification `8 Read / 2 WriteSubmission / 1 Simulation`, `KSP-TRANSPORT-007`, le no-resend et la préparation de `0.2.4`.
- [`011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan historique clôturé de la release stable `0.2.4`, ouvert par `pre.001`, exécuté jusquà `pre.009`, complété par le fix documentaire Wallet `pre.009-fix.001` puis publié par `rel.001`; il couvre les 10 Blocks + 5 Economics et la compliance finale `52/52 + 14/14` sous `KSP-TRANSPORT-007`.
- [`012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](012-V0_2_5_WALLET_FOUNDATION_PLAN.md) — plan historique clôturé de la release stable `0.2.5 — Wallet foundation`, ouvert par `pre.001`, livré jusquà `pre.010`, renforcé par `pre.010-fix.001``fix.003` pour Dalek 3 et la normalisation Rust/audit structurel, puis publié par `rel.001`; il couvre `.kspwallet` V1, VIEW/OWNER, crypto, persistence, administration, transfer et compliance.
- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; `pre.002` matérialise le shell Tauri et `pre.003` la composition `std.wallet`/composite ainsi que la préparation des répertoires. Le plan fixe aussi secrets `KSP_SECRET_WALLET_PASS_*`, inventory DataTables/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, balance HTTP, administration retenue et forecast souple détaillé jusquà `pre.015`, avec `pre.014` réservé au polish Bootstrap/splashscreen puis `pre.015` à la documentation finale, au prompt `0.2.7` et au build Tauri final.
- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; `pre.002` matérialise le shell Tauri et `pre.003` la composition `std.wallet`/composite ainsi que la préparation des répertoires. Le plan fixe aussi secrets `KSP_SECRET_WALLET_PASS_*`, inventory DataTables/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, balance HTTP, administration retenue et forecast étendu jusquà `pre.018` : `pre.014` polish Bootstrap/splashscreen, `pre.015` wire binaire `.kspwallet` V2, `pre.016` APIs génériques/versionnées + V2, `pre.017` migration/canaris, puis `pre.018` documentation candidate et build Tauri final.
Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
<!-- version: 75 -->
<!-- version: 76 -->
# Séquence des releases fonctionnelles KSP
@@ -361,14 +361,13 @@ Par défaut :
0.2.4 HTTP Blocks + Economics + compliance complète
0.2.5 wallet foundation (.kspwallet)
0.2.6 Wallet Desk
0.2.7 .kspwallet binary persistence container
0.2.8 standard Solana WebSocket
0.2.9 Helius LaserStream WebSocket
0.2.10 Yellowstone gRPC standard foundation
0.2.11 off-chain price transport
0.2.12 price visualization desk + intégration prix dans Wallet Desk
0.2.13 interface/wire foundation
0.2.14 program-api foundation
0.2.7 standard Solana WebSocket
0.2.8 Helius LaserStream WebSocket
0.2.9 Yellowstone gRPC standard foundation
0.2.10 off-chain price transport
0.2.11 price visualization desk + intégration prix dans Wallet Desk
0.2.12 interface/wire foundation
0.2.13 program-api foundation
```
`0.2.1-pre.001` a appliqué le gate de sizing et refusé le scope HTTP monolithique initial : l'inventaire du 2026-08-17 contient 52 méthodes courantes et 14 méthodes Deprecated historiques. Ce premier delta avait réparti la couverture typée sur `0.2.1``0.2.6`. `0.2.1-pre.001-fix.001` recalibre ensuite les 48 méthodes restantes sur trois releases complémentaires `0.2.2``0.2.4`, soit trois sessions nominales au maximum si chaque release utilise sa session complète. Si une release se clôt plus vite que prévu, la même session peut enchaîner la suivante après clôture complète de la précédente et nouveau gate de sizing positif. Un Wallet Desk utile doit pouvoir lire le solde du wallet : `getBalance` fait donc partie des quatre canaris de la foundation `0.2.1`, avant Wallet. Les transports live arrivent ensuite ; Interface/Program restent préparés avant les couches de données décodées.
@@ -443,29 +442,26 @@ pre.011 disable/recreate VIEW fort
pre.012 export OWNER fichier CLI JSON/Base58
pre.013 intégration, compliance, sécurité et smoke
pre.014 polish du gabarit Bootstrap et du splashscreen
pre.015 documentation finale, validations, prompt 0.2.7 et cargo tauri build en dernière opération
pre.015 .kspwallet V2 wire binaire + codec canonique
pre.016 APIs génériques/versionnées + création/ouverture V2
pre.017 migration V1 -> V2 + persistence/canaris + régression Wallet Desk
pre.018 documentation finale, validations, prompt 0.2.7 et cargo tauri build en dernière opération
rel.001 publication stable 0.2.6
```
La tranche `pre.014` est réservée aux défauts visuels/templating observés en usage réel, notamment les scrollbars occasionnelles du splashscreen ; son contenu précis sera borné à partir du retour opérateur avant implémentation. `ROADMAP.md` reste synthétique et `CHANGELOG.md` n'est synchronisé qu'à la phase documentaire finale.
## `0.2.7` — `.kspwallet` binary persistence container
Mission : faire évoluer uniquement la couche de persistence de `ksp-wallet-lib` afin quun `.kspwallet` nouvellement écrit ne soit plus un document JSON directement lisible dans un éditeur texte ordinaire, sans prétendre ajouter de sécurité cryptographique par simple obfuscation.
Le gate `pre.001` doit comparer framing binaire custom, codecs binaires stables et éventuelle compression, puis fixer un conteneur explicitement versionné. Base64 seul est exclu comme solution : il reste textuel, trivialement réversible et augmente la taille. Le conteneur et le `format_version` logique/cryptographique sont séparés afin de conserver V1 pour les semantics VIEW/OWNER actuelles et de réserver les futurs formats logiques à de vraies évolutions dautorisation. La lecture rétrocompatible des JSON V1 historiques et les migrations/test vectors sont obligatoires. Wallet Desk ne doit pas être modifié fonctionnellement ; ses tests/smokes sont rejoués comme preuve consommateur.
## `0.2.8` — WebSocket Solana standard
## `0.2.7` — WebSocket Solana standard
Mission : couvrir la surface WebSocket standard officielle ciblée.
Une URL peut avoir plusieurs sessions physiques ; une session peut avoir plusieurs subscriptions. Un pool automatique de sessions est reporté jusqu'à besoin concret.
## `0.2.9` — Helius LaserStream WebSocket
## `0.2.8` — Helius LaserStream WebSocket
Mission : étendre le moteur WebSocket standard avec les opérations/filtres/capabilities Helius ciblés sans copier le client.
## `0.2.10` — Yellowstone gRPC standard
## `0.2.9` — Yellowstone gRPC standard
Mission : introduire un backend Yellowstone standard/provider-neutral.
@@ -473,21 +469,21 @@ Le `pre.001` est un gate de sizing : inventorier toute la surface normative cibl
Les profiles/adapters Helius/Triton/ERPC/Chainstack/Shyft sont reportés après les priorités fondatrices.
## `0.2.11` / `0.2.12` — Off-chain price + app
## `0.2.10` / `0.2.11` — Off-chain price + app
`0.2.11` introduit `ksp-offchain-transport-lib` avec au minimum SOL/USD et SOL/EUR via une abstraction indépendante du premier provider.
`0.2.10` introduit `ksp-offchain-transport-lib` avec au minimum SOL/USD et SOL/EUR via une abstraction indépendante du premier provider.
`0.2.12` ajoute une petite application desk de visualisation/validation. Après stabilisation de cette application spécialisée, la même release doit intégrer la capacité de prix offchain dans `ksp-app-wallet-desk` sans dupliquer la récupération/normalisation appartenant au composant spécialisé.
`0.2.11` ajoute une petite application desk de visualisation/validation. Après stabilisation de cette application spécialisée, la même release doit intégrer la capacité de prix offchain dans `ksp-app-wallet-desk` sans dupliquer la récupération/normalisation appartenant au composant spécialisé.
Metadata HTTP/IPFS/Arweave viendra au premier besoin Metadata réel.
## `0.2.13` — Interface foundation
## `0.2.12` — Interface foundation
`ksp-interface-lib` devient la façade wire officielle et expose une API publique wire utilisable par les implementations officielles et externes.
Aucune `ksp-interface-api` séparée n'est retenue pour l'instant.
## `0.2.14` — Program API foundation
## `0.2.13` — Program API foundation
Introduire `ksp-program-api`, sans suffixe `-lib`, comme contrat d'extension Program.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md -->
<!-- version: 32 -->
<!-- version: 33 -->
# Plan `0.2.6` — Wallet Desk
@@ -771,32 +771,32 @@ outil de logging contrôlé
## 12. Command map
| Command | Entrée frontend | Sortie sûre | Délégation |
|----------------------------------------------|--------------------|-------------------------------------|---------------------------------|
| `get_runtime_status` | — | `WalletRuntimeStatusDto` | Config/app state |
| `list_wallets` | — | `Vec<WalletInventoryEntryDto>` | app filesystem + Wallet inspect |
| `refresh_wallets` | — | inventory DTO | app filesystem + Wallet inspect |
| `select_wallet` | wallet id | `LockedWalletDto` | app resolver + Wallet inspect |
| `deselect_wallet` | — | session DTO | app state |
| `create_wallet` | create request | `WalletAuthorizedDto` OWNER | Wallet create file |
| `unlock_wallet_view` | password | authorized VIEW | Wallet open VIEW |
| `unlock_wallet_owner` | password | authorized OWNER | Wallet open OWNER |
| `unlock_wallet_view_with_configured_secret` | — | authorized VIEW / operation result | Config env + Wallet open VIEW |
| `unlock_wallet_owner_with_configured_secret` | — | authorized OWNER / operation result | Config env + Wallet open OWNER |
| `lock_wallet` | — | `LockedWalletDto` | drop handle + inspect |
| `refresh_wallet_balance` | — | `WalletBalanceDto` | Transport getBalance |
| `inspect_import_source` | format only | transfer inspection DTO / cancel | Rust native picker + Wallet inspect |
| `import_wallet` | import request | authorized OWNER | staged bytes + Wallet import |
| `update_wallet_alias` | alias mutation | authorized DTO | WalletOwner |
| `add_wallet_note` | note create | authorized DTO | WalletOwner |
| `update_wallet_note` | note update | authorized DTO | WalletOwner |
| `delete_wallet_note` | note id | authorized DTO | WalletOwner |
| `rotate_owner_password` | rotation request | operation result | WalletOwner |
| `rotate_view_password` | rotation request | operation result | WalletOwner |
| `disable_view` | confirmation | authorized DTO | WalletOwner |
| `recreate_view` | new VIEW password | authorized DTO | WalletOwner |
| `export_wallet_owner` | format + save path | operation result | Wallet export file |
| `emit_frontend_log` | redacted payload | `()` | Logging facade |
| Command | Entrée frontend | Sortie sûre | Délégation |
|----------------------------------------------|--------------------|-------------------------------------|-------------------------------------|
| `get_runtime_status` | — | `WalletRuntimeStatusDto` | Config/app state |
| `list_wallets` | — | `Vec<WalletInventoryEntryDto>` | app filesystem + Wallet inspect |
| `refresh_wallets` | — | inventory DTO | app filesystem + Wallet inspect |
| `select_wallet` | wallet id | `LockedWalletDto` | app resolver + Wallet inspect |
| `deselect_wallet` | — | session DTO | app state |
| `create_wallet` | create request | `WalletAuthorizedDto` OWNER | Wallet create file |
| `unlock_wallet_view` | password | authorized VIEW | Wallet open VIEW |
| `unlock_wallet_owner` | password | authorized OWNER | Wallet open OWNER |
| `unlock_wallet_view_with_configured_secret` | — | authorized VIEW / operation result | Config env + Wallet open VIEW |
| `unlock_wallet_owner_with_configured_secret` | — | authorized OWNER / operation result | Config env + Wallet open OWNER |
| `lock_wallet` | — | `LockedWalletDto` | drop handle + inspect |
| `refresh_wallet_balance` | — | `WalletBalanceDto` | Transport getBalance |
| `inspect_import_source` | format only | transfer inspection DTO / cancel | Rust native picker + Wallet inspect |
| `import_wallet` | import request | authorized OWNER | staged bytes + Wallet import |
| `update_wallet_alias` | alias mutation | authorized DTO | WalletOwner |
| `add_wallet_note` | note create | authorized DTO | WalletOwner |
| `update_wallet_note` | note update | authorized DTO | WalletOwner |
| `delete_wallet_note` | note id | authorized DTO | WalletOwner |
| `rotate_owner_password` | rotation request | operation result | WalletOwner |
| `rotate_view_password` | rotation request | operation result | WalletOwner |
| `disable_view` | confirmation | authorized DTO | WalletOwner |
| `recreate_view` | new VIEW password | authorized DTO | WalletOwner |
| `export_wallet_owner` | format + save path | operation result | Wallet export file |
| `emit_frontend_log` | redacted payload | `()` | Logging facade |
Aucune commande de signature arbitraire n'est ajoutée dans cette release.
@@ -1677,7 +1677,7 @@ workspace checkpoint complet
Le smoke live est volontairement `ignored` dans `cargo test --workspace`. Il doit être déclenché explicitement avec un `KSP_WALLETS_DIRECTORY` dédié ; le test charge ce paramètre uniquement via `ConfigEnvironment`, crée un `.kspwallet` réel dans le répertoire effectif du profil composite, ouvre VIEW, exécute `getBalance` via le Transport composite puis supprime le wallet canari. Ainsi le smoke cross-crates vit dans la surface d'orchestration Wallet Desk et non dans Config ou Transport.
Cette tranche doit produire la preuve que les surfaces ajoutées fonctionnent ensemble avant le polish visuel `pre.014` et la documentation finale `pre.015`.
Cette tranche doit produire la preuve que les surfaces ajoutées fonctionnent ensemble avant le polish visuel `pre.014` et les tranches V2 binaires intercalées avant la candidate finale.
### `pre.014` — polish gabarit Bootstrap et splashscreen
@@ -1693,25 +1693,65 @@ réutilisation du gabarit commun sans déplacer de logique métier
Cette tranche n'est pas un prétexte pour refondre l'UX métier. Les détails précis des défauts et critères d'acceptation seront fournis par l'opérateur lorsqu'elle sera ouverte.
### `pre.015` — documentation finale, candidate build et prompt suivant
### `pre.015` — `.kspwallet` V2 : wire binaire + codec canonique
Cette tranche intercalée remplace l'ancien départ immédiat en documentation finale. Elle fixe le wire binaire V2 et son codec structurel dans `ksp-wallet-lib` sans modifier encore Wallet Desk ni les APIs de persistence V1 utilisées en production.
```text
KSPWALLET magic binaire
format_version = 2
longueur totale explicite
flags réservés stricts
OWNER puis VIEW optionnel
Argon2id / XChaCha20-Poly1305 / Ed25519 par IDs numériques
compartiments OWNER-CONTROL / METADATA / SECRET
entiers big-endian
aucune Base64
aucune compression
aucun trailing byte
domains/transcripts/AAD V2 distincts de V1
fixture wire-only canonique + comparaison de taille
```
La politique API est figée dès cette tranche : `DEFAULT_WALLET_FORMAT` et `LATEST_SUPPORTED_WALLET_FORMAT` sont deux notions distinctes. À l'intégration V2, le default devient V2. Une future V3 n'entraîne jamais automatiquement le déplacement du default.
### `pre.016` — APIs génériques/versionnées + création/ouverture V2
Matérialiser :
```text
create_wallet_file(...) -> V2 par default explicite
create_wallet_file_v1(...) conservé
create_wallet_file_v2(...) ajouté
open/inspect génériques -> détection V1/V2
open/inspect _v1/_v2 -> format forcé strict
create/open in-memory V2 + crypto/transcripts V2
Wallet Desk migre vers les APIs génériques, jamais vers un numéro de format
```
Une future API `_v3` pourra être ajoutée sans transformer automatiquement le default en V3.
### `pre.017` — migration V1 -> V2 + persistence/canaris
Ajouter la migration explicite et authentifiée V1 vers V2, les paths de remplacement/no-clobber nécessaires, les canaris adversariaux/interop et la régression complète Wallet Desk. Aucune migration silencieuse lors d'une simple ouverture.
### `pre.018` — documentation finale, candidate build et prompt suivant
Dernière tranche prévue avant `rel.001` :
```text
crates/ksp-app-wallet-desk/README.md
crates/ksp-app-wallet-desk/USAGE.md
docs/architecture / inventory / dependency graph synchronisés si impactés
README/USAGE Wallet Desk
docs/validation 0.2.6 finalisée
ROADMAP / functional sequence / CHANGELOG synchronisés
stratégie CWD/resources release packagée fermée
dépendances et versions finales réauditées
commandes smoke documentées
prompt de démarrage 0.2.7 préparé
validation Rust workspace finale
parcours fonctionnel cargo tauri dev final
prompt de démarrage 0.2.7 WebSocket finalisé
cargo tauri build en toute dernière opération
```
Si le build final ou la documentation révèle une correction technique, utiliser `pre.015-fix.NNN` ou insérer une tranche supplémentaire. `pre.015` n'est pas une obligation de clôture artificielle.
Si une correction technique apparaît, utiliser `pre.018-fix.NNN` ou insérer une tranche supplémentaire.
### `rel.001` — publication stable
@@ -1871,7 +1911,7 @@ KSP_WALLETS_DIRECTORY=var/wallet-desk-pre014 \
bash -lc 'cd crates/ksp-app-wallet-desk && cargo tauri dev'
```
Le build Tauri de production reste reporté à `pre.015` et doit demeurer labsolue dernière opération.
À ce stade de `pre.014`, le build Tauri de production était encore reporté à `pre.015`. Cette décision historique est **superseded par lextension décidée en `pre.015`** : le build final est désormais réservé à `pre.018` et doit demeurer labsolue dernière opération.
### Correctif `0.2.6-pre.014-fix.001` — layout desktop et canari durable
@@ -1887,6 +1927,6 @@ splash : feed #debug-info pleine largeur dans les deux Desks
pre.013 gate : le canari ne fige plus une shell_phase destinée à évoluer à chaque tranche
```
Le recalage du CWD n'existe actuellement qu'en `debug_assertions` dans les deux `main.rs`. La candidate `pre.015` doit donc vérifier explicitement la stratégie de résolution production/bundle pour `config`, `.env`, `logs` et `wallets` avant le build final ; le comportement release ne doit pas être supposé identique au parcours `cargo tauri dev`.
Le recalage du CWD n'existe actuellement qu'en `debug_assertions` dans les deux `main.rs`. La candidate finale `pre.018` doit donc vérifier explicitement la stratégie de résolution production/bundle pour `config`, `.env`, `logs` et `wallets` avant le build final ; le comportement release ne doit pas être supposé identique au parcours `cargo tauri dev`.
Ce fix touche le frontend/build contract et porte donc le signal technique `workspace.package.version = 0.2.6-pre.14.fix.1`. `ROADMAP.md`, `CHANGELOG.md` et `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` restent inchangés.

View File

@@ -1,11 +1,11 @@
<!-- file: docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Validation `0.2.6` — Wallet Desk integration / compliance / security
## 1. Objet
Cette matrice est ouverte par `0.2.6-pre.013` comme preuve durable de composition et de conformité de `ksp-app-wallet-desk`. Elle sera complétée en `pre.015` avec les résultats finaux, le polish visuel de `pre.014`, la documentation de clôture et le dernier build Tauri.
Cette matrice est ouverte par `0.2.6-pre.013` comme preuve durable de composition et de conformité de `ksp-app-wallet-desk`. Le calendrier a été étendu après `pre.014` : `pre.015` introduit le wire V2, `pre.016` les APIs génériques/versionnées, `pre.017` migration/régression, puis `pre.018` porte la clôture documentaire et le dernier build Tauri.
Elle ne remplace ni le plan [`../plans/013-V0_2_6_WALLET_DESK_PLAN.md`](../plans/013-V0_2_6_WALLET_DESK_PLAN.md), ni la matrice Wallet V1 [`008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md), ni les deltas `0.2.6`.
@@ -51,19 +51,19 @@ Le checkpoint opérateur `pre.012` du 21 août 2026 est vert : `cargo fmt`, audi
## 3. Matrice de frontières
| Frontière | Contrat `0.2.6` | Preuve durable |
|---|---|---|
| Frontend -> Config | aucun accès direct env/.env | `release_compliance::backend_keeps_config_wallet_transport_and_logging_ownership_boundaries` + ownership Config |
| Frontend -> Wallet secret | passwords uniquement request-only ; jamais de keypair en IPC | `desktop_security` + `release_compliance::response_dtos_keep_secret_key_material_out_of_ipc` |
| Frontend -> filesystem | aucun path arbitraire ; picker natif Rust | tests import/export + capability audit |
| Frontend -> réseau Solana | aucun `fetch`/WebSocket/RPC direct | `release_compliance::frontend_has_no_direct_filesystem_network_or_secret_persistence_surface` |
| Wallet Desk -> Solana crates | aucune dépendance protocole directe | `release_compliance::wallet_desk_dependency_firewall_and_tauri_capabilities_are_minimal` |
| Wallet Desk -> HTTP | `ksp-onchain-transport-lib` uniquement | manifest + smoke Devnet |
| Wallet Desk -> Wallet crypto | `ksp-wallet-lib` uniquement | manifest + feature canaries |
| Wallet Desk -> logging | façade `ksp-logging-lib` + adapter `tauri-plugin-tracing` | source audit + workspace logging canary |
| locked -> frontend | format/version/VIEW enabled seulement | `desktop_security` |
| VIEW/OWNER handles | Rust-only `AppState` | session/security tests |
| export | OWNER-only, file direct, no-clobber | `desktop_security` + runtime `pre.012` |
| Frontière | Contrat `0.2.6` | Preuve durable |
|------------------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| Frontend -> Config | aucun accès direct env/.env | `release_compliance::backend_keeps_config_wallet_transport_and_logging_ownership_boundaries` + ownership Config |
| Frontend -> Wallet secret | passwords uniquement request-only ; jamais de keypair en IPC | `desktop_security` + `release_compliance::response_dtos_keep_secret_key_material_out_of_ipc` |
| Frontend -> filesystem | aucun path arbitraire ; picker natif Rust | tests import/export + capability audit |
| Frontend -> réseau Solana | aucun `fetch`/WebSocket/RPC direct | `release_compliance::frontend_has_no_direct_filesystem_network_or_secret_persistence_surface` |
| Wallet Desk -> Solana crates | aucune dépendance protocole directe | `release_compliance::wallet_desk_dependency_firewall_and_tauri_capabilities_are_minimal` |
| Wallet Desk -> HTTP | `ksp-onchain-transport-lib` uniquement | manifest + smoke Devnet |
| Wallet Desk -> Wallet crypto | `ksp-wallet-lib` uniquement | manifest + feature canaries |
| Wallet Desk -> logging | façade `ksp-logging-lib` + adapter `tauri-plugin-tracing` | source audit + workspace logging canary |
| locked -> frontend | format/version/VIEW enabled seulement | `desktop_security` |
| VIEW/OWNER handles | Rust-only `AppState` | session/security tests |
| export | OWNER-only, file direct, no-clobber | `desktop_security` + runtime `pre.012` |
## 4. Inventory / path adversarial
@@ -238,4 +238,9 @@ cargo test --workspace : BLOCKED par un faux positif du canari Logging global
Le blocage workspace est limité à `ksp-logging-lib/tests/ownership.rs::workspace_crates_do_not_bypass_ksp_logging_facade` : le scanner voit le littéral `tracing::` dans `release_compliance.rs`, alors que ce littéral est la chaîne recherchée par le canari et non un usage de `tracing`. `pre.013-fix.002` supprime ce littéral statique en construisant le motif à l'exécution, sans modifier le scanner global ni relâcher la politique Logging.
La matrice reste ouverte jusqu'à `pre.015`, où seront ajoutés le résultat du polish `pre.014`, le parcours fonctionnel final, les docs README/USAGE et le build Tauri exécuté en toute dernière opération.
La matrice reste ouverte jusqu'à `pre.018`, après les tranches V2 `pre.015``pre.017`; `pre.018` ajoute le parcours fonctionnel final, README/USAGE et le build Tauri exécuté en toute dernière opération.
## 11. Intercalation V2 à partir de `pre.015`
`pre.015` ne modifie pas encore Wallet Desk : il ajoute dans `ksp-wallet-lib` un codec binaire V2 strict et une fixture canonique. Les gates Desk `pre.013`/`pre.014` restent donc applicables sans changement. `pre.016` devra faire migrer le Desk vers les APIs génériques afin quil ne connaisse plus `_v1`/`_v2`.

View File

@@ -1,5 +1,5 @@
<!-- file: prompts/000-README.md -->
<!-- version: 18 -->
<!-- version: 19 -->
# Prompts KSP
@@ -32,5 +32,4 @@ Le prompt générique `0.1.x` a été affiné pendant `0.0.3` puis remplacé par
- [`009-V0_2_4_START_PROMPT.md`](009-V0_2_4_START_PROMPT.md) — prompt préparé par `0.2.3-pre.009`, destiné à ouvrir `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale` après publication stable de `0.2.3`; il cible les 15 wrappers restants et impose `KSP-TRANSPORT-007` ainsi qu'un nouvel audit/sizing à `pre.001`.
- [`010-V0_2_5_START_PROMPT.md`](010-V0_2_5_START_PROMPT.md) — prompt préparé par `0.2.4-pre.009` puis finalisé en version 2 par `pre.009-fix.001`, destiné à ouvrir `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`; il impose audit/threat-model/sizing avant choix cryptographiques et cadre `.kspwallet` interopérable, capacités indépendantes VIEW/OWNER, metadata protégées, key slots/rotations, signature, persistence atomique et import/export extensible sans `WalletPolicy`.
- [`011-V0_2_6_START_PROMPT.md`](011-V0_2_6_START_PROMPT.md) — prompt préparé par `0.2.5-pre.010` puis renforcé pendant `pre.010-fix.001``fix.003`, destiné à ouvrir `0.2.6 — Wallet Desk` après le tag stable `v0.2.5`; il impose une première tranche audit/sizing, rappelle les règles Rust/audit structurel, cadre Config composite + Wallet + HTTP `getBalance`, lifecycle VIEW/OWNER, sécurité password/export, validation frontend/Tauri et conserve le TODO `0.2.12` dintégration des prix offchain dans Wallet Desk après validation de la Price Desk spécialisée.
- [`012-V0_2_7_START_PROMPT.md`](012-V0_2_7_START_PROMPT.md) — prompt préparé pendant `0.2.6-pre.014`, destiné à ouvrir `0.2.7 — conteneur binaire .kspwallet` après publication stable de `0.2.6`; il sépare version de conteneur et `format_version` logique V1, exclut Base64 comme pseudo-sécurité, impose compatibilité JSON V1/migration/interop et utilise Wallet Desk comme canari consommateur sans modification fonctionnelle attendue.
- [`012-V0_2_7_START_PROMPT.md`](012-V0_2_7_START_PROMPT.md) — prompt réaligné par `0.2.6-pre.015` pour ouvrir `0.2.7 — WebSocket Solana standard`; le chantier `.kspwallet` V2 ayant été ramené dans `0.2.6`, il rétablit la séquence Transport et impose audit officiel/sizing avant implémentation.

View File

@@ -1,5 +1,5 @@
<!-- file: prompts/011-V0_2_6_START_PROMPT.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Prompt de démarrage `0.2.6` — Wallet Desk
@@ -190,7 +190,7 @@ pre.006 wallet details + balance HTTP + refresh/diagnostics
pre.007 metadata administration + rotations VIEW/OWNER
pre.008 strong VIEW disable/recreate + export OWNER + security UX
pre.009 integration tests + Devnet smoke + compliance/security review
pre.010 README/USAGE/build final + prompt 0.2.7 si nécessaire
pre.010 README/USAGE/build final + prompt suivant si nécessaire
rel.001 publication stable
```
@@ -652,7 +652,7 @@ Le gate n'est considéré franchi que si ces livrables sont écrits/consignés d
## 24. Hors périmètre
```text
modification du format logique/cryptographique .kspwallet V1 sans défaut démontré (le conteneur de persistence est traité séparément en 0.2.7)
modification destructive du wire V1 stable ; V2 binaire est ajouté explicitement en `pre.015+` sans réécrire V1
WalletPolicy / execution policy
construction/simulation/envoi de transaction
WebSocket/gRPC
@@ -666,11 +666,11 @@ trading
Le Wallet Desk valide et administre Wallet ; il ne devient pas l'application globale KSP.
### TODO futur `0.2.12` — prix offchain dans Wallet Desk
### TODO futur `0.2.11` — prix offchain dans Wallet Desk
La visualisation de prix offchain n'appartient pas au scope de `0.2.6`. Une application spécialisée de visualisation de prix offchain doit d'abord être réalisée et valider durablement ses sources, contrats, rafraîchissement, cache et UX.
Pour `0.2.12`, prévoir explicitement un chantier d'intégration de cette capacité dans `ksp-app-wallet-desk` afin qu'un wallet puisse afficher les informations de prix offchain pertinentes sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé. Le futur travail devra réauditer la frontière entre application, bibliothèque/service partagé et Transport avant implémentation ; le TODO n'autorise pas l'ajout anticipé de logique de prix dans Wallet Desk pendant `0.2.6`.
Pour `0.2.11`, prévoir explicitement un chantier d'intégration de cette capacité dans `ksp-app-wallet-desk` afin qu'un wallet puisse afficher les informations de prix offchain pertinentes sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé. Le futur travail devra réauditer la frontière entre application, bibliothèque/service partagé et Transport avant implémentation ; le TODO n'autorise pas l'ajout anticipé de logique de prix dans Wallet Desk pendant `0.2.6`.
## 25. Résultat attendu de `0.2.6`
@@ -718,4 +718,9 @@ canari pre.013 : ne plus figer la phase shell historique
version technique : 0.2.6-pre.14.fix.1
```
Le workflow Tauri reste crate-local. En debug, les deux binaires recalent ensuite leur current working directory sur la racine du workspace via `CARGO_MANIFEST_DIR/../..`; la stratégie release/bundle reste un point explicite à fermer en `pre.015`.
Le workflow Tauri reste crate-local. En debug, les deux binaires recalent ensuite leur current working directory sur la racine du workspace via `CARGO_MANIFEST_DIR/../..`; la stratégie release/bundle reste un point explicite à fermer dans la candidate finale `pre.018`.
## Addendum `pre.015` — V2 binaire intercalé
Après validation du polish `pre.014-fix.001`, la release est volontairement étendue : `pre.015` wire/codec V2, `pre.016` APIs génériques/versionnées et V2 runtime, `pre.017` migration/régression, `pre.018` candidate finale. Le prompt `0.2.7` redevient WebSocket Solana standard.

View File

@@ -1,268 +1,82 @@
<!-- file: prompts/012-V0_2_7_START_PROMPT.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Prompt de démarrage `0.2.7` — conteneur binaire `.kspwallet`
# Prompt de démarrage `0.2.7` — WebSocket Solana standard
## 1. Contexte de reprise
La base attendue est la release stable :
Base attendue : tag stable `v0.2.6`.
```text
v0.2.6
```
`0.2.5` a stabilisé `ksp-wallet-lib` et le format logique/cryptographique `.kspwallet` V1 : capacités VIEW/OWNER indépendantes, Argon2id, XChaCha20-Poly1305, autorité OWNER Ed25519, persistence atomique/no-clobber, rotations, révocation VIEW forte, signature et transferts Solana CLI JSON/Base58.
`0.2.6` a validé cette surface au travers de `ksp-app-wallet-desk` sans déplacer la propriété du format ou des secrets hors de Wallet.
`0.2.6` a validé Wallet Desk puis intégré le wire binaire `.kspwallet` V2, les APIs Wallet génériques/versionnées, la compatibilité V1/V2 et les migrations explicites. `0.2.7` revient donc à la séquence Transport prévue avant l'intercalation temporaire du chantier binaire.
La release à ouvrir est :
```text
0.2.7 — conteneur binaire de persistence .kspwallet
0.2.7 — WebSocket Solana standard
```
La première tranche est `0.2.7-pre.001` et commence obligatoirement par **audit + threat-model + options de framing/codec + compatibilité/migration + sizing**, avant toute modification du wire de persistence.
`0.2.7-pre.001` commence obligatoirement par audit officiel actuel + inventory exhaustif des subscriptions/unsubscriptions/notifications + threat-model/reconnexion + sizing avant implémentation lourde.
## 2. Motivation
## 2. Mission
Le document `.kspwallet` V1 actuellement persisté est un JSON strict. Ses valeurs sensibles restent cryptographiquement protégées, mais le fichier peut être ouvert et lu comme texte par un éditeur ordinaire.
Étendre `ksp-onchain-transport-lib` à la surface WebSocket Solana standard retenue, sans déplacer Config dans Transport et sans introduire encore Helius LaserStream.
L'objectif de `0.2.7` est de rendre la **persistence physique** binaire et explicitement framed, sans prétendre que cette transformation ajoute une protection cryptographique supplémentaire.
### Non-solution explicite : Base64 seul
Base64 seul n'est pas une solution acceptable :
- il reste textuel/ASCII ;
- il est trivialement réversible ;
- il ne fournit aucune confidentialité ni intégrité supplémentaire ;
- il augmente typiquement la taille d'environ un tiers.
Le changement recherché est un vrai **conteneur binaire de persistence**, pas une obfuscation textuelle.
## 3. Séparation normative : conteneur vs format logique
Ne pas consommer artificiellement `format_version = 2` uniquement parce que la persistence devient binaire.
Distinguer explicitement :
Contrat de sessions :
```text
container/framing version
!=
wallet logical/cryptographic format_version
une même URL peut porter plusieurs sessions physiques
une session peut porter plusieurs subscriptions
pas de pool/scheduler automatique complexe sans besoin démontré
reconnexion bornée et états de subscription explicites
notifications typées sans fuite d'URL/secrets dans Debug/logs
```
Le payload logique V1 doit pouvoir conserver ses invariants actuels :
## 3. Frontières
Conserver :
```text
VIEW / OWNER
KDF / salts / slots
AEAD / nonces / AAD
OWNER signature/transcript
metadata/secret compartments
Solana keypair identity
rotation / strong VIEW administration
Transport -X-> Config
Transport -X-> Wallet
Transport -X-> Store
Transport -X-> tracing direct
Config -> Transport autorisé
ksp-logging-lib seul propriétaire des émissions applicatives
```
Un futur `format_version >= 2` reste disponible pour de vraies évolutions d'autorisation/crypto, notamment second facteur.
## 4. Audit pre.001
## 4. Audit obligatoire `pre.001`
Auditer au minimum :
Comparer au minimum :
- documentation Solana/Agave actuelle ;
- méthodes subscribe/unsubscribe courantes, deprecated/unstable et notifications associées ;
- formes de paramètres/options et réponses ;
- reconnexion, resubscribe, backpressure, cancellation et shutdown ;
- plusieurs sessions pour une même URL ;
- limites provider utiles sans les figer comme règles Solana universelles ;
- dépendances WebSocket Rust actuelles et leur graphe ;
- sizing « une release = une session ».
1. framing binaire custom KSP avec grammar explicite ;
2. codecs binaires Serde adaptés et stables ;
3. formats self-describing éventuels ;
4. éventuelle compression du payload si elle apporte une vraie valeur ;
5. impact interop externe Rust/Python/Go/C ;
6. complexité de parsing borné et rejet des inputs adversariaux ;
7. dépendances nouvelles et leur maturité/maintenance ;
8. capacité à préserver les test vectors cryptographiques V1 ;
9. migration et détection sûre du JSON V1 legacy ;
10. règles d'unknown container version / taille / trailing bytes / canonicalité.
Ne choisir aucun codec uniquement parce qu'il est pratique dans Rust : le format doit rester spécifiable indépendamment de l'implémentation.
## 5. Compatibilité obligatoire
`ksp-wallet-lib` doit rester capable de lire les `.kspwallet` JSON V1 déjà créés.
Le candidat doit définir explicitement :
## 5. Hors périmètre
```text
legacy JSON V1 read
new binary container read
new binary container write default
migration explicite legacy -> binary
no-clobber / atomic replace
rollback/interruption behavior
Helius LaserStream WebSocket -> 0.2.8
Yellowstone gRPC
Store/materialization
Wallet/2FA
pool automatique sophistiqué de sessions WebSocket
frontend réseau direct
```
Une simple ouverture d'un legacy wallet ne doit pas le réécrire silencieusement sans contrat explicite.
## 6. Sécurité
Le conteneur binaire n'est **pas** présenté comme une nouvelle couche de chiffrement.
Le threat model V1 reste notamment :
- attaquant possédant le fichier ;
- connaissance complète de la spec ;
- essais offline illimités sur les passwords ;
- connaissance éventuelle d'un seul credential VIEW ou OWNER ;
- capacité à modifier/remplacer/rollback le fichier.
Les gains de `0.2.7` sont :
```text
framing déterministe
non-textual persistence
parsing/versioning physique explicite
meilleure séparation container/payload
préparation des futures migrations
```
Pas :
```text
obscurity = security
Base64 = encryption
binaire = protection contre attaque offline
```
## 7. Frontière `ksp-wallet-lib`
Le changement doit rester possédé par `ksp-wallet-lib`.
`ksp-app-wallet-desk` ne doit pas connaître :
- le codec du conteneur ;
- sa magic/version physique ;
- son layout ;
- ses offsets ;
- la distinction legacy/new lors des opérations ordinaires.
Les APIs de haut niveau Wallet doivent absorber cette évolution autant que possible.
## 8. Wallet Desk comme canari consommateur
Aucune modification fonctionnelle de `ksp-app-wallet-desk` n'est attendue uniquement pour ce changement de persistence.
Après modification de Wallet, rejouer au minimum :
```bash
cargo test -p ksp-wallet-lib
cargo test -p ksp-app-wallet-desk
cargo test --workspace
```
Puis un parcours fonctionnel Wallet Desk doit prouver au minimum :
```text
inventory
create
locked inspect
VIEW unlock
OWNER unlock
metadata
rotation
strong VIEW disable/recreate
export/import
balance HTTP
```
Si le Desk doit être modifié pour comprendre le conteneur physique, considérer cela comme un signal d'abstraction insuffisante et réauditer la frontière Wallet avant d'accepter le changement.
## 9. Interop et test vectors
Mettre à jour la spec `.kspwallet` avec une grammar byte-level du conteneur :
- magic ;
- version du conteneur ;
- longueur(s) ;
- codec/payload kind ;
- endianess ;
- bornes ;
- trailing bytes ;
- canonicalité ;
- unknown-version policy.
Conserver les test vectors cryptographiques V1 lorsque les transcripts/AAD ne changent pas, et ajouter des fixtures déterministes container + payload.
Prévoir au moins un parseur externe de canari afin d'éviter un format binaire « Rust-only » accidentel.
## 10. Futur `format_version >= 2` / second facteur
Ce chantier **ne réalise pas** password + OTP/2FA.
Pour une future version logique :
- `ksp-wallet-lib` possède le format, les facteurs, challenges et vérifications ;
- un client interactif tel que Wallet Desk devra évoluer lorsqu'un OTP, enrollment/recovery, hardware/WebAuthn ou consentement externe doit être saisi/présenté ;
- un seed TOTP enfermé uniquement dans le même fichier ne doit pas être assimilé automatiquement à un second facteur indépendant contre un attaquant qui possède ce fichier.
Le conteneur `0.2.7` doit simplement laisser cette évolution possible sans la pré-concevoir excessivement.
## 11. Dépendances
Toute nouvelle dépendance externe commune est déclarée dans `[workspace.dependencies]` puis consommée avec `.workspace = true`.
Avant ajout :
- vérifier la version actuelle ;
- auditer maintenance/licence/graphe ;
- éviter les générations obsolètes inutiles ;
- vérifier qu'une petite grammar custom n'est pas plus durable qu'un codec lourd, ou inversement.
## 12. Discipline Rust / tests
Conserver toutes les règles KSP actuelles : Rust 2024, async-first, pas de `unsafe`, `unwrap`, `expect`, `panic` runtime, audits structurels, dépendance Logging via `ksp-logging-lib`, Config hors Wallet.
Validation générale :
## 6. Validation
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-wallet-lib
cargo test -p ksp-app-wallet-desk
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
```
## 13. Forecast souple à recalibrer en `pre.001`
Point de départ recommandé :
```text
pre.001 audit framing/codec/threat-model/interop/sizing
pre.002 spec container + parser bounded + fixtures
pre.003 writer binary + persistence integration
pre.004 legacy JSON read + migration explicite
pre.005 adversarial/interop/test vectors
pre.006 Wallet Desk regression + live functional smoke
pre.007 compliance/docs/prompt suivant
rel.001
```
Ce découpage est indicatif ; le gate `pre.001` doit le recalibrer selon le choix de format réel.
## 14. Résultat attendu
À la clôture stable :
```text
les nouveaux .kspwallet sont persistés dans un conteneur binaire spécifié
les anciens JSON V1 restent lisibles
aucune sécurité fictive n'est attribuée au binaire/Base64
les semantics cryptographiques V1 restent intactes sauf défaut démontré
les migrations sont explicites et testées
Wallet Desk fonctionne sans connaissance du format physique
interop externe et parsing adversarial sont prouvés
```
## 15. Instruction d'ouverture
Commencer par relire la spec `.kspwallet` V1, les modules `wire`, `transcript`, `persistence`, `wallet`, `owner`, `transfer` et tous les test vectors actuels.
Ensuite produire l'audit `0.2.7-pre.001` avant de coder le nouveau conteneur.
Prévoir des fixtures déterministes et un smoke réseau opt-in seulement lorsqu'une composition réelle le justifie.