# Validation `0.2.5` — Wallet security / interoperability / compliance ## 1. Objet Cette matrice constitue la validation durable de clôture de `0.2.5`. `pre.009` a fermé le premier gate technique security/interoperability/compliance et `pre.010` a ajouté le gate documentaire. `pre.010-fix.001` rouvre volontairement le gate technique pour la mise à niveau `ed25519-dalek 3.0.0` et la normalisation Rust workspace-wide avant toute publication stable. Elle ne remplace ni la spécification [`../formats/KSPWALLET_V1.md`](../formats/KSPWALLET_V1.md), ni le threat model du plan [`../plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](../plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md), ni les deltas. Le verdict recherché porte sur quatre axes : ```text security/adversarial interoperability indépendante frontières KSP Cargo/dependency compliance ``` ## 2. État fonctionnel audité Au gate `pre.009`, Wallet possède : ```text format autonome .kspwallet V1 VIEW / OWNER indépendants Argon2id v19 XChaCha20-Poly1305 CSPRNG OS content keys séparées OWNER / metadata / secret Ed25519 distinct pour state_signature OWNER keypair Solana immuable et encapsulée create/open VIEW/OWNER persistence no-clobber replacement administratif capability-bound signature Solana OWNER alias/notes + rotations OWNER/VIEW strong disable/recreate VIEW import/export Solana CLI JSON + Base58 keypair 64 octets ``` Aucune dépendance Wallet vers Config, Transport, ExecutionPolicy, Store ou Tauri n'est autorisée. ## 3. Matrice adversariale | Cas | Résultat attendu | Preuve | |-----------------------------------------------------|---------------------------------------------------------|-----------------------------------------------------------------------------------------------| | mutation canonique KDF/wrap OWNER | rejet | `security_tests::owner_signed_regions_reject_canonical_tampering_before_unlock` | | mutation owner-control | rejet | même canari | | mutation metadata | rejet | même canari | | mutation secret | rejet | même canari | | mutation `state_signature` | rejet | même canari | | état OWNER-signed invalide + password vide | `wallet.authentication_failed` avant Argon2 | `security_tests::owner_signature_verification_precedes_owner_and_view_password_kdf` | | mutation des credentials rotatables VIEW uniquement | état OWNER toujours valide, OWNER ouvrable, VIEW rejeté | `security_tests::view_credential_tampering_does_not_forge_owner_state_and_cannot_unlock_view` | | wrong VIEW | `wallet.view_unlock_failed` générique | tests Wallet + security | | wrong OWNER | `wallet.owner_unlock_failed` générique | tests Wallet + security | | password canary dans Display/Debug | absent | `security_tests::unlock_failures_do_not_echo_password_material` | | metadata plaintext dans wallet verrouillé | absent | `wallet::tests::locked_full_vector_contains_no_authorized_identity_or_metadata_plaintext` | | stale OWNER handle | `wallet.state_conflict` | administration tests | | wallet A handle vers wallet B | `wallet.state_conflict` | administration tests | | écriture interrompue avant publish | ancien/destination absent préservé | persistence fault tests | | concurrence create no-clobber | exactement un gagnant | persistence concurrency test | | export secret depuis VIEW | surface publique absente | dependency/public-surface canary | ## 4. Ordre de vérification et anti-oracle Pour VIEW et OWNER : ```text parse structurel borné -> state_signature OWNER -> seulement ensuite Argon2 du slot demandé -> unwrap AEAD -> déchiffrement compartment -> parsing plaintext protégé ``` Le canari `state_signature invalide + password vide` distingue explicitement cet ordre : si Argon2 était évalué avant l'authentification d'état, l'entrée vide produirait une erreur de paramètres crypto ; le résultat attendu reste `wallet.authentication_failed`. Les erreurs de password ne distinguent pas KDF, wrapping AEAD ou contenu protégé et ne reflètent pas le password fourni. ## 5. Interopérabilité indépendante Les fixtures publiques test-only restent : ```text kspwallet_v1_wire_only.json kspwallet_v1_crypto_vectors.json kspwallet_v1_full_vector.json kspwallet_v1_full_vector_meta.json ``` Une sonde indépendante hors Rust/KSP exécutée pendant `pre.009` reproduit : ```text Argon2id OWNER derived key = exact Argon2id VIEW derived key = exact pre.004 XChaCha wrapped key = exact pre.004 unwrap content key = exact Ed25519 state signature = vérifiée sur le transcript exact Solana keypair Base58 = reproduite depuis 64 octets Solana Pubkey Base58 = reproduite depuis les 32 octets publics ``` La sonde utilise Python avec Argon2 et Ed25519/ChaCha20-Poly1305 disponibles localement ; XChaCha20 est reproduit par la construction HChaCha20 + ChaCha20-Poly1305 IETF. Cette sonde n'est ni versionnée ni requise par KSP : elle sert uniquement de seconde implémentation de vérification. Références cryptographiques normatives/primaires réauditées : - RFC 9106 — Argon2 ; le profil Argon2id `64 MiB / t=3` est la seconde recommandation pour environnements contraints ; - RFC 8032 — Ed25519/EdDSA et signatures 64 octets ; - documentation RustCrypto `chacha20poly1305` — XChaCha20-Poly1305 et nonce étendu 192 bits ; - documentation `solana-keypair 3.1.2` — keypair Ed25519 64 octets, validation secret/public et codecs Base58/JSON ; - documentation `tempfile 3.27` — publication/replacement et limites d'atomicité selon OS/filesystem. ## 6. Audit Cargo final observé après `pre.009` Commande opérateur : ```bash cargo tree -p ksp-wallet-lib cargo tree -p ksp-wallet-lib -d cargo tree -i solana-keypair@3.1.2 ``` Dépendances directes de Wallet observées : ```text argon2 0.5.3 base64 0.23.1 chacha20poly1305 0.11.0 ed25519-dalek 2.2.0 getrandom 0.4.3 ksp-core-lib ksp-logging-lib serde 1.0.229 serde_json 1.0.151 solana-keypair 3.1.2 tempfile 3.27.0 tokio 1.53.1 zeroize 1.9.0 ``` Points de convergence recherchés : ```text ed25519-dalek 2.2.0 : une seule génération, partagée KSP + solana-keypair solana-address 2.7.0: une seule génération, partagée solana-pubkey + solana-keypair solana-keypair 3.1.2: unique parent KSP direct = ksp-wallet-lib ``` Doublons transitifs observés et acceptés au gate : ```text block-buffer 0.10 / 0.12 cpufeatures 0.2 / 0.3 crypto-common 0.1 / 0.2 digest 0.10 / 0.11 getrandom 0.3 / 0.4 rand 0.9 / 0.10 rand_core 0.6 / 0.9 / 0.10 sha2 0.10 / 0.11 syn 2 / 3 ``` Ces doublons proviennent des générations transitives actuelles de RustCrypto, `solana-keypair`/`solana-ed25519` et Logging. Ils ne correspondent pas à deux versions directes déclarées par Wallet pour une même fonction. Les éliminer exigerait de forcer des upstream incompatibles ou de régresser les versions retenues ; aucun contournement KSP n'est introduit. ## 7. Frontières KSP Canaries durables : ```text Wallet -> Core + Logging + primitives explicites Wallet -X-> Config Wallet -X-> Transport Wallet -X-> ExecutionPolicy Wallet -X-> Store Wallet -X-> Tauri Wallet -X-> tracing direct Wallet -X-> std::env Wallet -X-> solana-pubkey direct Wallet -X-> solana-signer direct Wallet -X-> solana-signature direct Wallet -X-> bs58 direct ``` `ksp-core-lib::Pubkey` reste le type public transversal. `solana-keypair` reste un détail secret/signing propre à Wallet ; aucune `Keypair`, `Signer` ou `Signature` Solana externe n'est réexportée par la surface publique Wallet. `WalletTransferFormat` est `#[non_exhaustive]` : la liste built-in reste additive et les consumers ne peuvent pas supposer qu'elle est fermée. `pre.009` ne crée pas de trait/plugin externe de codec secret : un tel trait devrait exposer les 64 octets de keypair à une implémentation tierce et constituerait une nouvelle surface secrète. Cette extension éventuelle exige un contrat dédié futur plutôt qu'une abstraction V1 prématurée. ## 8. Limites et non-garanties V1 Le verdict security est positif **dans le threat model documenté**, avec les limites explicites suivantes : 1. un remplacement total par un autre wallet autonome valide n'est pas détectable à partir du nouveau fichier seul ; 2. un rollback total vers une ancienne copie valide n'est pas détecté sans état/ancre externe ; 3. le garde-fou `wallet.state_conflict` réduit les erreurs de cible/stale handle mais n'est pas un CAS filesystem portable linéarisable ; 4. l'atomicité et la crash-durability du filesystem dépendent de l'OS/filesystem ; 5. les ACL/permissions sont une hygiène externe, pas une garantie cryptographique ; 6. `zeroize` s'applique aux buffers possédés explicitement mais ne prouve pas l'effacement physique de toutes les copies potentielles ; 7. aucune protection hardware, second facteur, keychain, remote signer ou anti-rollback externe n'est incluse en V1. `pre.010` les conserve explicitement dans README/USAGE/spec et ne les transforme pas en garanties. ## 9. Gate opérateur `pre.009` À exécuter après application du delta : ```bash cargo fmt --all cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-wallet-lib cargo test --workspace cargo tree -p ksp-wallet-lib cargo tree -p ksp-wallet-lib -d cargo tree -i ed25519-dalek@2.2.0 cargo tree -i solana-keypair@3.1.2 cargo tree -i solana-address@2.7.0 ``` Ce gate est considéré vert uniquement si les canaris adversariaux passent sans warning et si le tree ne révèle aucune nouvelle dépendance directe ou génération Dalek/Solana-address inattendue ; le résultat effectivement observé est enregistré en section 10. ## 10. Résultat opérateur `pre.009` Checkpoint communiqué le 2026-08-19 après application de `0.2.5-pre.009` : ```text cargo fmt --all OK cargo check --workspace OK, aucun warning cargo clippy --workspace --all-targets OK, aucun warning cargo test -p ksp-wallet-lib OK unit 58 passed / 1 ignored dependency_boundary 3 passed public_api 9 passed doctests 2 passed cargo test --workspace OK ``` Le tree opérateur confirme : ```text ed25519-dalek 2.2.0 <- ksp-wallet-lib <- solana-keypair 3.1.2 <- ksp-wallet-lib solana-keypair 3.1.2 <- ksp-wallet-lib uniquement comme parent KSP direct solana-address 2.7.0 <- solana-keypair 3.1.2 <- solana-pubkey 4.3.0 via ksp-core-lib ``` Les doublons transitifs déjà inventoriés restent inchangés et aucun nouveau bypass de frontière n'est observé. ## 11. Gate documentaire `pre.010` La candidate finale doit vérifier : ```text crates/ksp-wallet-lib/README.md existe et décrit les responsabilités/frontières crates/ksp-wallet-lib/USAGE.md couvre create/open/sign/admin/rotation/import/export KSPWALLET_V1.md ne laisse aucune opération V1 planifiée comme restant à implémenter le graphe Wallet documente Pubkey via Core et keypair encapsulée dans Wallet le plan 0.2.5 pointe désormais vers rel.001 le prompt 0.2.6 Wallet Desk existe et commence par audit/sizing les liens Markdown locaux modifiés sont valides aucun fichier Rust ni manifest Cargo n'est modifié par pre.010 workspace.package.version reste 0.2.5-pre.9 car pre.010 est doc-only ``` ## 12. Correctif technique `pre.010-fix.001` Le correctif de clôture apporte deux changements qui exigent une nouvelle validation opérateur : ```text ed25519-dalek direct KSP : ^2.2 -> ^3.0 normalisation Rust workspace + scripts/audit_rust_workspace_rules.py obligatoire ``` Le résultat `pre.009` des sections 6 et 10 reste une preuve historique valide pour l'état `2.2.0`; il ne doit pas être présenté comme le tree final après le fix. `solana-keypair 3.1.2` conserve actuellement sa branche Dalek `2.x`, donc le tree attendu après le fix est : ```text ed25519-dalek 3.0.0 <- ksp-wallet-lib direct ed25519-dalek 2.2.0 <- solana-keypair 3.1.2 <- ksp-wallet-lib solana-address 2.7.0 <- solana-keypair 3.1.2 <- solana-pubkey 4.3.0 via ksp-core-lib ``` Gate opérateur post-fix : ```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 --workspace cargo tree -p ksp-wallet-lib cargo tree -p ksp-wallet-lib -d cargo tree -i ed25519-dalek@3.0.0 cargo tree -i ed25519-dalek@2.2.0 cargo tree -i solana-keypair@3.1.2 cargo tree -i solana-address@2.7.0 ``` Le fix ne modifie ni le wire `.kspwallet` V1, ni les paramètres Argon2/XChaCha, ni la sémantique Ed25519, ni les capabilities VIEW/OWNER. Le verdict final reste **en attente de ce checkpoint opérateur**. ## 13. Verdict final candidate `0.2.5` Le verdict `pre.009` était positif pour l'état alors audité, mais **la candidate stable n'est plus considérée fermée tant que `pre.010-fix.001` n'a pas passé son gate opérateur**. Le changement de génération Dalek et la normalisation Rust sont intentionnels et ne changent pas le wire V1, mais ils constituent un changement technique réel qui doit être compilé, linté et testé dans l'environnement opérateur. Après validation complète de `pre.010-fix.001`, le verdict peut redevenir positif et seulement alors `0.2.5-rel.001` redevient la prochaine étape publicationnelle.