# Delta `0.2.5-pre.005` — payloads V1 + create/open VIEW/OWNER + autorité OWNER Ed25519 ## Base requise ```text livraison : 0.2.5-pre.004-fix.002 workspace.package.version = "0.2.5-pre.4.fix.2" ``` Les validations opérateur de cette base sont propres : ```text cargo fmt --all OK cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-wallet-lib OK : 22 passés, 1 benchmark ignoré + 2 dependency-boundary + 5 public API + 2 doc-tests compile_fail cargo test -p ksp-logging-lib --test ownership OK : 2/2 cargo test --workspace OK ``` Le benchmark Argon2 opérateur acquis avant cette tranche donne : ```text 64 MiB / 3 passes / 1 lane -> 1742 ms 128 MiB / 3 passes / 1 lane -> 3459 ms 256 MiB / 3 passes / 1 lane -> 6925 ms ``` ## Objectif Matérialiser les compartiments protégés et les capabilities in-memory complètes de lecture sans ouvrir encore la persistence filesystem ni l'administration : ```text profil de création KSP V1 issu du benchmark payload owner_control exact payload metadata exact payload secret Solana exact autorité de format Ed25519 distincte de la keypair Solana state_signature OWNER vérifiée avant KDF create V1 in-memory open VIEW indépendant open OWNER indépendant inspection locked authentifiée vecteur .kspwallet complet interopérable ``` Restent explicitement hors tranche : ```text filesystem / persistence atomique / no-clobber signature Solana publique export du secret modification alias/notes rotation password VIEW par VIEW rotation password VIEW/OWNER par OWNER disable/recreate VIEW révocation VIEW forte import/export ``` Ces opérations restent prévues dans `pre.006+` sans déplacer de responsabilité vers Config ou Transport. ## Version Cargo Conformément au séquencement des prereleases : ```text 0.2.5-pre.4.fix.2 -> 0.2.5-pre.5 ``` `workspace.package.version` reste le signal technique de la tranche. ## Profil Argon2 de création KSP À partir des mesures opérateur, les créations KSP V1 utilisent initialement : ```text algorithm Argon2id version 19 memory_kib 65_536 iterations 3 parallelism 1 salt 32 octets CSPRNG indépendants par slot output KEK 32 octets ``` Le choix vise un coût d'environ 1,7 s sur la machine de calibration plutôt que les profils 128/256 MiB mesurés à environ 3,5/6,9 s. Ce profil est un **default de création KSP**, jamais une condition de lecture du format : chaque slot sérialise toujours ses paramètres KDF complets. Un ancien wallet reste donc lisible après un futur durcissement des defaults. ## Dépendances introduites Nouvelles dépendances communes sous `[workspace.dependencies]` : ```toml ed25519-dalek = { version = "^2.2", default-features = false } solana-keypair = { version = "^3.1", default-features = false } ``` Le membre Wallet consomme : ```toml ed25519-dalek = { workspace = true, features = ["signature", "zeroize"] } solana-keypair.workspace = true tokio = { workspace = true, features = ["rt"] } ``` `tokio` était déjà une dépendance commune du workspace ; seule la feature locale nécessaire à `spawn_blocking` est activée. Le choix direct `ed25519-dalek ^2.2` est volontaire : `solana-keypair 3.1.2` dépend lui-même de `ed25519-dalek ^2.1.1`. Rester dans la génération 2.x doit permettre à Cargo d'unifier Dalek au lieu d'ajouter une branche 3.x concurrente. La feature `zeroize` est activée explicitement. Aucune dépendance directe `solana-pubkey`, `solana-signer` ou `solana-signature` n'est ajoutée. La Pubkey du domaine Wallet reste exclusivement `ksp_core_lib::Pubkey`. Un `cargo tree` opérateur est obligatoire pour confirmer la résolution réelle après application. ## Content keys et autorité OWNER La création génère indépendamment : ```text K_owner_root 32 octets K_metadata 32 octets K_secret 32 octets admin seed 32 octets Ed25519 Solana secret 32 octets Ed25519 distincts ``` La clé d'administration du format reste distincte de la keypair Solana. Le slot OWNER wrappe uniquement `K_owner_root`. Le slot VIEW optionnel wrappe uniquement `K_metadata`. OWNER ne dépend donc jamais de VIEW et VIEW ne reçoit jamais `K_owner_root` ni `K_secret`. ## Payload `owner_control` Le plaintext V1 est fixé à exactement 96 octets : ```text offset 0..32 admin Ed25519 secret seed offset 32..64 K_metadata offset 64..96 K_secret ``` Il est chiffré sous `K_owner_root` avec l'AAD compartment figé en `pre.003`. À l'ouverture OWNER, la seed admin redérive obligatoirement la `owner_auth_public_key` de l'enveloppe. Un mismatch est rejeté. ## Payload metadata Le plaintext metadata est un JSON UTF-8 strict et protégé : ```json { "pubkey": "", "alias": null, "notes": [ { "id": "<16 bytes Base64url-no-pad>", "text": "..." } ] } ``` Invariants : ```text Pubkey type ksp_core_lib::Pubkey après parsing alias optionnel, <= 256 octets UTF-8 notes maximum 64 note.id exactement 16 octets, unique dans le payload note.text <= 8192 octets UTF-8 payload metadata <= 65 536 octets ``` Les IDs de note sont générés au CSPRNG. Une collision pendant la création est traitée comme une défaillance de génération plutôt que de produire des IDs ambigus. Un payload décodé avec IDs dupliqués est rejeté. ## Payload secret Solana Le plaintext secret est exactement le keypair Ed25519 Solana 64 octets : ```text offset 0..32 secret Ed25519 offset 32..64 public Ed25519 ``` À l'ouverture OWNER : ```text 1. le payload doit faire exactement 64 octets ; 2. solana-keypair valide la cohérence secret/public ; 3. les 32 octets publics construisent un ksp_core_lib::Pubkey ; 4. cette Pubkey doit correspondre exactement à la Pubkey metadata. ``` Un mismatch n'est jamais accepté comme changement d'identité implicite. ## `state_signature` OWNER La signature d'état V1 est désormais effective : ```text algorithm Ed25519 public key owner_auth_public_key dans l'enveloppe message state_transcript TLV figé en pre.003 signature 64 octets ``` La vérification stricte de cette signature intervient **avant toute dérivation Argon2** lors de `inspect`, `open VIEW` et `open OWNER`. Le transcript continue d'exclure le matériau self-service rotatable du slot VIEW (`KDF/salt/wrap`) et d'authentifier son descripteur stable OWNER-controlled. Cette frontière préserve la future rotation du propre password VIEW sans donner à VIEW la capacité de modifier metadata/secret/OWNER-state. ## API in-memory La crate expose désormais : ```rust create_wallet_v1(owner_password, optional_view_password, metadata).await open_wallet_view_v1(source, view_password).await open_wallet_owner_v1(source, owner_password).await inspect_locked_wallet_v1(source) ``` `create_wallet_v1` crée une nouvelle identité Solana et une autorité admin séparée, mais **n'effectue aucun I/O filesystem**. `WalletOwner::to_json_bytes()` et `WalletView::to_json_bytes()` permettent seulement de projeter l'enveloppe verrouillée complète en mémoire en attendant `pre.006`. ### VIEW L'ouverture VIEW : ```text parse strict verify state_signature OWNER Argon2 du seul slot VIEW via spawn_blocking unwrap K_metadata decrypt metadata retour WalletView ``` Elle ne déchiffre jamais `owner_control` ou `secret`, ne construit jamais la keypair Solana et ne matérialise jamais `K_secret`. ### OWNER L'ouverture OWNER : ```text parse strict verify state_signature OWNER Argon2 du slot OWNER via spawn_blocking unwrap K_owner_root decrypt owner_control valider admin secret -> owner_auth_public_key récupérer K_metadata / K_secret decrypt metadata decrypt et valider keypair Solana 64 octets vérifier Pubkey keypair == Pubkey metadata retour WalletOwner ``` OWNER reste fonctionnel même si VIEW est absent, inconnu ou inutilisable. ## Secret memory Les invariants précédents sont conservés : ```text password wrappers non-Clone et zeroized au Drop content keys non-Copy/non-Clone et zeroized au Drop buffers plaintext owner-control/metadata/secret zeroized après consommation raisonnable admin SigningKey avec feature zeroize solana-keypair conservé uniquement dans l'état OWNER opaque aucun Debug/Display public de secret aucun secret/password/alias/note dans les logs ``` Les limites habituelles de zeroization Rust restent documentées : aucune garantie absolue n'est revendiquée contre toutes les copies temporaires possibles du compilateur/runtime. ## Logging Les nouveaux flux utilisent exclusivement `ksp-logging-lib` avec : ```text target = crate::TRACING_TARGET = "ksp-wallet-lib" ``` Les événements ne contiennent que l'opération/capability/version non secrète et l'issue catégorisée. Aucun path, password, secret, metadata ou payload arbitraire n'est journalisé. ## Vecteur complet d'interopérabilité Nouveaux fixtures publics TEST ONLY : ```text crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector_meta.json ``` Ils fixent de manière auto-contenue : ```text password OWNER connu password VIEW connu salts / slot IDs / nonces content keys test-only admin key test-only keypair Solana test-only Pubkey / alias / notes attendus ciphertexts owner-control / metadata / secret wrapped keys OWNER / VIEW state transcript state signature Ed25519 ``` Le vecteur utilise volontairement un KDF faible/test-fast (`32 KiB / 2 / 1`) et ne constitue jamais un wallet ni un profil de production recommandé. Une implémentation indépendante du code Rust a revérifié : ```text state transcript exact : 1182 octets signature Ed25519 valide dérivations OWNER/VIEW exactes unwrap K_owner_root / K_metadata exact déchiffrement owner_control exact déchiffrement metadata exact déchiffrement secret exact : 64 octets admin seed -> owner_auth_public_key exacte secret Solana -> public half cohérente ``` ## Tests Les tests Wallet ajoutent notamment : ```text vecteur complet ouvre VIEW et OWNER indépendamment password VIEW ne déverrouille pas OWNER password OWNER ne déverrouille pas VIEW mutation OWNER-authentifiée metadata rejetée mutation du seul wrapping VIEW ne casse pas OWNER mais invalide VIEW fichier verrouillé ne contient pas Pubkey/alias/notes en clair salts / slot IDs / nonces OWNER/VIEW indépendants create utilise le profil KSP benchmarké projection locked reste privée duplicate note IDs rejetés ``` Les tests d'architecture continuent d'imposer : ```text Wallet -X-> Config Wallet -X-> Transport Wallet -X-> ExecutionPolicy Wallet -X-> Store Wallet -X-> Tauri Wallet -X-> tracing direct Wallet -X-> solana-pubkey direct Wallet -X-> accès environnement direct ``` ## Documentation Mises à jour : ```text ROADMAP.md docs/000-README.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/012-V0_2_5_WALLET_FOUNDATION_PLAN.md ``` `KSPWALLET_V1.md` devient suffisamment précis pour les payloads et procédures `pre.005`; la persistence et les opérations d'administration restent explicitement futures. `CHANGELOG.md` reste inchangé : la policy KSP réserve les entrées de release stable à la clôture/publication, les prereleases étant décrites par plans/deltas/roadmap. ## Fichiers ajoutés ```text crates/ksp-wallet-lib/src/payload.rs crates/ksp-wallet-lib/src/wallet.rs crates/ksp-wallet-lib/unit_tests/payload.rs crates/ksp-wallet-lib/unit_tests/wallet.rs crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector_meta.json deltas/0.2.5/pre.005.md ``` ## Fichiers modifiés ```text Cargo.toml ROADMAP.md crates/ksp-wallet-lib/Cargo.toml crates/ksp-wallet-lib/src/constants.rs crates/ksp-wallet-lib/src/crypto.rs crates/ksp-wallet-lib/src/error.rs crates/ksp-wallet-lib/src/lib.rs crates/ksp-wallet-lib/src/metadata.rs crates/ksp-wallet-lib/src/owner.rs crates/ksp-wallet-lib/src/password.rs crates/ksp-wallet-lib/src/view.rs crates/ksp-wallet-lib/src/wire.rs crates/ksp-wallet-lib/tests/dependency_boundary.rs crates/ksp-wallet-lib/tests/public_api.rs docs/000-README.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/012-V0_2_5_WALLET_FOUNDATION_PLAN.md ``` Aucun fichier hors de cette différence avec `pre.004-fix.002` ne doit être embarqué. ## Fichiers supprimés Aucun. ## Validation attendue après application ```bash cargo fmt --all cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-wallet-lib cargo test --workspace ``` Audit de dépendances obligatoire : ```bash cargo tree -p ksp-wallet-lib cargo tree -p ksp-wallet-lib -d cargo tree -e features -p ksp-wallet-lib cargo tree -i ed25519-dalek@2.2.0 cargo tree -i solana-keypair@3.1.2 cargo tree -i solana-address@2.7.0 ``` Points à confirmer : ```text une seule génération ed25519-dalek dans le sous-arbre Wallet pas de duplication crypto injustifiée introduite par Wallet solana-address résolu sans second type Pubkey KSP aucune dépendance Config/Transport/Tauri depuis Wallet zeroize effectivement activé sur ed25519-dalek ``` ## Décisions - retenir `65 536 KiB / 3 / 1` comme profil initial de création KSP V1 à partir du benchmark opérateur ; - conserver les paramètres KDF sérialisés comme autorité de lecture du wallet ; - utiliser une autorité Ed25519 de format distincte de la keypair Solana ; - vérifier la state signature avant tout KDF ; - garder `ed25519-dalek` en génération `2.x` pour s'aligner sur `solana-keypair 3.1.2` ; - ne jamais dépendre directement de `solana-pubkey` dans Wallet ; - ouvrir VIEW sans matérialiser le secret Solana ; - garder OWNER indépendant de VIEW ; - ne pas introduire persistence/signature/export/admin avant leurs tranches dédiées. ## Questions ouvertes Aucune question de format V1 nouvelle n'est introduite par cette tranche. `pre.006` doit choisir et prouver la stratégie filesystem portable pour : ```text create destination nouvelle/no-clobber atomic replace des futures mutations OWNER/VIEW fsync/durability raisonnable selon plateforme fault injection concurrence récupération après interruption ``` ## Commit attendu ```text v0.2.5-pre.005 ```