# Plan `0.2.5` — Wallet foundation ## 1. Objet et gate `pre.001` La release `0.2.5` ouvre, sur la base stable `v0.2.4`, le premier cœur Wallet autonome KSP : ```text crates/ksp-wallet-lib format natif .kspwallet ``` `0.2.5-pre.001` est volontairement documentaire. Il ne crée pas encore la crate et n'ajoute aucune dépendance cryptographique : il réaudite l'héritage bot2/bot3, fixe le threat model, choisit le design VIEW/OWNER, cadre le format wire, réaudite les primitives actuelles et redimensionne la release avant toute implémentation sensible. Le gate est **positif avec découpage élargi** : la release reste cohérente comme unité fonctionnelle, mais la prévision initiale en huit prereleases compresse trop fortement le format, les key slots, l'authentification cryptographique des metadata, la persistence et les validations adversariales. La prévision passe donc à dix prereleases avant `rel.001`. ## 2. Frontières non négociables Direction autorisée : ```text ksp-wallet-lib -> ksp-core-lib -> ksp-logging-lib -> primitives crypto/key/signature low-level explicitement retenues ``` Interdictions : ```text ksp-wallet-lib -X-> ksp-config-lib ksp-wallet-lib -X-> ksp-onchain-transport-lib ksp-wallet-lib -X-> ksp-execution-policy-api ksp-wallet-lib -X-> Store ksp-wallet-lib -X-> Tauri ksp-wallet-lib -X-> tracing direct ksp-wallet-lib -X-> accès environnement direct ``` Le Wallet possède le stockage local, le déverrouillage, la projection autorisée, la signature et l'administration du wallet. Il ne possède aucune règle d'autorisation de dépense, de programme, de réseau, de simulation ou de plafond. ## 3. Décisions antérieures conservées `pre.001` confirme sans les rouvrir : 1. le suffixe natif est `.kspwallet` ; 2. aucune crate `ksp-wallet-api` séparée n'est créée dans l'architecture actuelle ; 3. `WalletPolicy` n'appartient pas au Wallet ; 4. le JSON temporaire bot2/bot3 n'est pas migré ; 5. `.kswallet` reste historique et ne devient pas un alias du nouveau format ; 6. les secrets Wallet ne transitent jamais par Config ; 7. Transport n'est pas requis par le cœur Wallet ; 8. l'import/export reste extensible mais limité aux formats prouvés utiles ; 9. les scénarios futurs pourront utiliser de vrais `.kspwallet` dédiés Devnet/tests ; 10. Wallet Desk reste `0.2.6` ; 11. `.kspwallet` est publiquement spécifiable et récupérable sans KSP ; 12. aucun pepper/global secret KSP n'est requis ; 13. Pubkey, alias et notes restent cachés wallet verrouillé ; 14. l'alias est interne et indépendant du filename ; 15. les notes sont des metadata protégées administrées par OWNER ; 16. `format_version` versionne le format, pas les éditions ; 17. les paramètres crypto nécessaires à la lecture sont sérialisés ; 18. VIEW et OWNER sont deux capacités indépendantes ; 19. OWNER ne dépend jamais de VIEW ; 20. VIEW compromis ne donne ni le secret Solana ni OWNER ; 21. rotation des passwords ne change pas la keypair ; 22. OWNER peut supprimer/recréer VIEW sans l'ancien password VIEW. Clarifications confirmées pendant la revue du gate : 23. **`.kspwallet` V1 est entièrement autonome** : le fichier et le password correspondant sont les seuls éléments requis pour parser, vérifier l'intégrité sous son autorité OWNER embarquée, déverrouiller et récupérer les capacités prévues. V1 n'utilise et ne requiert aucun salt externe, pepper KSP, OTP, secret serveur, keychain, état machine, réseau ou ancre de confiance externe ; 24. la spécification finale est un document séparé `docs/formats/KSPWALLET_V1.md`, indépendant de l'implémentation Rust et suffisamment précis pour réimplémenter le format dans un autre langage sans lire `ksp-wallet-lib` ; 25. l'état **OWNER-controlled** d'un wallet V1 est authentifié par OWNER : identité Solana, metadata, secret, slot OWNER, activation/désactivation de VIEW, descripteur stable du slot VIEW et paramètres de format concernés ne peuvent pas être modifiés sous l'autorité courante sans OWNER ; 26. **VIEW peut modifier une seule chose : son propre password VIEW**. Cette opération remplace uniquement les paramètres KDF VIEW autorisés par V1, le salt et le nonce/ciphertext de wrapping de son slot en rewrappant le même accès metadata ; elle ne donne aucun droit d'écriture sur Pubkey/alias/notes, aucun droit de signature/export, aucun droit de changer OWNER et aucun droit de désactiver/recréer VIEW ; 27. **OWNER peut changer son propre password OWNER et le password VIEW**, sans connaître l'ancien password VIEW ; OWNER reste seul capable de l'administration metadata et des key slots au-delà du self-service de rotation VIEW ; 28. la keypair Solana est **immuable dans un wallet V1 après création/import**. V1 ne fournit pas d'opération de remplacement de keypair ; une autre keypair crée un autre wallet ; 29. les permissions/ACL du système de fichiers ne constituent **ni une garantie cryptographique ni une responsabilité de sécurité de `ksp-wallet-lib`**. Le contrat Wallet porte sur les capacités : sans OWNER, aucune signature Solana, aucun export secret, aucune écriture acceptée de Pubkey/alias/notes et aucune mutation OWNER-controlled ; la seule mutation volontairement accordée à VIEW est la rotation de son propre password ; 30. tout import produit un **nouveau `.kspwallet`** selon la sémantique no-clobber de `create`. Un import ne remplace, n'écrase et ne transforme jamais un `.kspwallet` existant ; 31. `ksp-wallet-lib` ne possède aucun « répertoire Wallet par défaut » ni configuration correspondante. Les opérations de persistence reçoivent un chemin/répertoire explicite du caller ; une future application peut faire résoudre son propre défaut par Config puis transmettre le chemin à Wallet, sans créer de dépendance Wallet -> Config. ## 4. Réaudit de l'héritage bot2/bot3 ### 4.1 bot2 — `TemporaryWalletStore` L'archive historique contient un store de laboratoire basé sur le keypair JSON Solana 64 octets. Les propriétés utiles sont : validation stricte du keypair, no-clobber, effacement des buffers sérialisés et frontière `Signer` sans exposition publique des octets privés. Les permissions Unix `0700/0600` observées historiquement ne sont pas reprises comme garantie de sécurité Wallet : les ACL/permissions OS restent hors du threat model de `.kspwallet` V1. Le format lui-même est **abandonné** pour KSP natif : il n'offre aucun chiffrement password et le prompt `0.2.5` exclut explicitement sa migration. ### 4.2 bot3 — `ks-wallet` Le dernier `ks-wallet` historique fournit : ```text .kswallet / magic KSWALLET format binaire V1 Argon2id v19 XChaCha20-Poly1305 salt 16 octets nonce 24 octets un password unique alias + Pubkey en clair dans le header ciphertext exact d'un keypair Solana 64 octets atomic create / replace import/export Solana CLI JSON + Base58 UnlockedWallet / signature WalletPolicy historique migration du JSON legacy ``` Ses defaults Argon2 historiques (`65536 KiB`, 3 passes, 4 lanes) sont seulement un fait d'archive ; ils ne deviennent **pas** les defaults KSP. ### 4.3 Matrice de reprise | Élément historique | Décision `0.2.5` | Motif | |----------------------------------------------------------|-----------------------------------------|---------------------------------------------------------| | création `Keypair::new()` / key material standard Solana | réutiliser conceptuellement | primitive standard maintenue | | validation stricte 64 octets + cohérence secret/pubkey | réutiliser conceptuellement | bon invariant d'import et de secret payload | | frontière de signature sans getter secret | réutiliser conceptuellement | invariant de sécurité public | | password owned, redacted, non-Clone | refondre | séparer `ViewPassword` / `OwnerPassword` | | zeroization des buffers possédés | réutiliser conceptuellement | utile avec limites documentées | | `spawn_blocking` pour Argon2 | réutiliser conceptuellement | KDF CPU/memory-bound hors executor async | | CSPRNG OS | réutiliser conceptuellement | pas de RNG KSP maison | | parsing borné + identifiants algo/version | réutiliser conceptuellement | indispensable au format hostile | | AEAD + données associées | réutiliser conceptuellement | authentification locale du wire | | temp-file + `sync_all` + publication atomique | refondre | préserver l'idée, durcir la portabilité | | concurrent no-clobber : un seul créateur gagne | réutiliser conceptuellement | invariant de création | | import Solana CLI JSON | réutiliser/refondre | format immédiatement utile | | Base58 keypair complet | réutiliser/refondre | format générique Solana utile, sans l'étiqueter Phantom | | `inspect_transfer_file` sans persister | réutiliser/refondre | projection sûre d'un import | | `.kswallet` / magic `KSWALLET` | abandonner | nouveau contrat `.kspwallet` | | alias dans filename/header | abandonner | alias interne indépendant du fichier | | Pubkey/alias visibles verrouillé | abandonner | contradictoire avec les invariants KSP | | password unique | abandonner | contradictoire avec VIEW/OWNER | | chiffrement direct du keypair avec password | abandonner | rotations/capabilities exigent envelope keys | | `WalletPolicy` | abandonner | responsabilité Execution Policy | | JSON temporaire + migration legacy | abandonner | explicitement hors périmètre | | logging `tracing` direct | abandonner | façade `ksp-logging-lib` obligatoire | | mnemonics/hardware/remote/recovery | reporter | hors périmètre `0.2.5` | | adapters propriétaires Backpack/Phantom/Solflare/Trust | reporter tant que wire exact non prouvé | ne pas coder par supposition | ## 5. Threat model `.kspwallet` ### 5.1 Attaquant avec copie du fichier Un détenteur d'une copie du fichier peut lancer des essais de passwords **hors ligne**, sans rate-limit serveur. La sécurité d'un password faible ne peut donc pas être promise. Le KDF memory-hard augmente le coût unitaire de chaque tentative ; il ne transforme pas une faible entropie humaine en secret fort. Les salts sont publics et indépendants. Leur rôle est d'empêcher la mutualisation efficace de précalculs entre wallets/slots ; ils ne sont ni un secret ni un pepper. ### 5.2 Spécification et paramètres entièrement connus Le threat model suppose que l'attaquant connaît : ```text format exact algorithmes paramètres KDF salts nonces ciphertexts code source KSP ``` Le format ouvert n'est pas une faiblesse : la confidentialité repose sur les secrets d'accès et les primitives standard. ### 5.3 VIEW compromis Avec seulement le password VIEW, l'attaquant peut légitimement lire : ```text Pubkey alias notes ``` Il ne doit pouvoir dériver ni : ```text OWNER KEK owner root key Solana secret/keypair format-admin signing secret ``` L'API KSP VIEW expose une projection metadata read-only et ne possède aucune méthode de signature, d'export secret ni d'administration metadata/OWNER. Elle peut toutefois conserver, pour la durée de vie explicitement contrôlée de la capability déverrouillée, le **minimum de matériau VIEW** nécessaire à la lecture et au rewrap de son propre slot ; ce matériau est privé, zeroized au drop lorsque raisonnablement possible et ne contient jamais le secret Solana ni une clé OWNER. ### 5.4 OWNER compromis OWNER donne volontairement la capacité complète : metadata, signature, administration et export secret explicitement demandé. Une rotation ultérieure du **password** OWNER retire l'ancien password du fichier courant, mais ne peut pas révoquer rétroactivement une `owner_root_key` qu'un OWNER malveillant aurait déjà exfiltrée. Une future opération de rekey cryptographique complet pourrait changer l'owner root et les content keys, mais elle n'est pas confondue avec `rotate_owner_password`. ### 5.5 Modification partielle du fichier Les ciphertexts sont authentifiés par AEAD. L'état OWNER-controlled est couvert par l'authentification OWNER définie plus bas ; les champs self-service du slot VIEW sont couverts par leur wrapping AEAD et leur binding AAD. Toute modification partielle d'un champ hors contrat doit provoquer un rejet déterministe sans exposer une cause crypto trop fine. ### 5.6 Remplacement total / rollback Un remplacement intégral par un autre `.kspwallet` valide ne révèle **aucun** matériau secret de l'ancien wallet : il revient seulement à substituer un autre wallet, avec sa propre autorité OWNER, ses propres key slots et sa propre keypair Solana. Si l'attaquant ne connaît pas VIEW de l'ancien wallet, cette substitution ne lui donne pas davantage accès à la Pubkey/alias/notes de l'ancien fichier. Un format V1 entièrement autonome ne peut pas distinguer, à partir du seul nouveau fichier, qu'une autre autorité OWNER a remplacé l'ancienne. La même limite vaut pour un rollback complet vers une ancienne copie valide. Ce point n'est **pas** une garantie recherchée par `ksp-wallet-lib` V1 et n'autorise aucune dépendance externe : aucun salt externe, pepper, OTP, secret KSP, service distant, keychain ou ancre de confiance extérieure n'est requis. Les permissions/ACL et le contrôle de qui peut écrire le chemin relèvent du système d'exploitation et sont hors du threat model Wallet. Le contrat V1 porte sur les capacités : **sans OWNER**, aucun détenteur ne peut signer avec la keypair Solana, exporter son secret, modifier Pubkey/alias/notes ni muter l'état OWNER-controlled. Un détenteur VIEW authentifié conserve uniquement l'exception explicitement accordée de rotation de son propre password VIEW. ### 5.7 Matrice des garanties | Garantie | Cryptographie du format | Types/capabilities KSP | |------------------------------------------------------------------------|-------------------------------------------------------:|-------------------------:| | confidentialité metadata verrouillées | oui | oui | | confidentialité secret Solana face à VIEW | oui | oui | | indépendance VIEW/OWNER | oui | oui | | VIEW ne signe pas | séparation de clés | oui | | VIEW ne modifie pas Pubkey/alias/notes via API | authentification OWNER des metadata | oui | | VIEW change son propre password | slot VIEW rewrappable sous la même capability metadata | oui, opération dédiée | | VIEW ne change pas password OWNER / activation VIEW / autres key slots | authentification OWNER de l'état de contrôle | oui | | metadata modifiées par VIEW détectées sous la même autorité OWNER | oui, `state_signature` | oui | | détection corruption/tampering partiel | oui | parsing strict | | signature Solana sans OWNER | impossible sous les primitives retenues | API absente hors OWNER | | export secret sans OWNER | secret non déverrouillable | API absente hors OWNER | | mutation Pubkey/alias/notes/OWNER-state sans OWNER | état non authentifiable | API absente hors OWNER | | no-clobber / atomic replace | non | propriété de persistence | | détection remplacement total par un autre wallet valide | hors garantie V1 | hors garantie V1 | | détection rollback total vers une copie valide | hors garantie V1 | hors garantie V1 | Les ACL/permissions OS ne figurent volontairement pas dans cette matrice : elles ne participent pas au modèle de sécurité de `.kspwallet` V1. ## 6. Niveau B — metadata VIEW read-only + self-service credential `pre.001` retient **B** pour les données et capacités OWNER-controlled, tout en accordant explicitement à VIEW une mutation étroite : la rotation de **son propre password VIEW**. Le wallet possède une **clé d'administration Ed25519 propre au format**, distincte de la keypair Solana. Sa clé publique de vérification est dans l'enveloppe minimale ; sa clé privée est accessible seulement par OWNER. Chaque état persistant valide porte une `state_signature` OWNER calculée sur un transcript déterministe de l'**état OWNER-controlled** : magic/version, autorité publique, slot OWNER complet, owner-control, metadata, secret, versions/algorithmes couverts, ainsi qu'un **descripteur VIEW stable signé** indiquant notamment si VIEW est activé et l'identité/rôle du slot attendu. Les seuls champs mutables exclus de cette autorisation OWNER sont les **champs de protection du slot VIEW courant** nécessaires à son self-service de password : paramètres Argon2id VIEW autorisés par V1, salt VIEW, nonce et ciphertext du wrapping AEAD de la même capability metadata. Les identifiants d’algorithmes restent imposés par V1 et ne deviennent pas librement mutables par VIEW. Ces champs sont eux-mêmes authentifiés par le wrapping AEAD et liés par AAD au wallet, au rôle VIEW et au descripteur de slot signé. VIEW peut donc rewrapper le même accès metadata sous un nouveau password sans posséder la clé privée d'administration OWNER. Conséquence : un détenteur VIEW peut changer son password VIEW, mais ne peut pas produire, sous la même autorité OWNER, une modification acceptée de Pubkey/alias/notes, du slot OWNER, de l'owner-control, du compartiment secret, de l'activation/désactivation de VIEW, du descripteur de slot VIEW ou de la keypair Solana. Modifier ou supprimer le slot VIEW d'une manière incompatible avec son descripteur OWNER-signed est rejeté ; seule la réécriture de ses champs de protection selon le contrat de rotation est admise. `ksp-wallet-lib` ne contrôle pas qui peut écrire des octets sur le chemin : les ACL/permissions OS sont hors périmètre. Sa garantie porte sur les capacités acceptées par le format et l'API. Un remplacement du **fichier entier** par un autre wallet auto-cohérent crée seulement l'équivalent d'un autre wallet ; il ne révèle pas l'ancien secret et ne donne aucun moyen de signer avec l'ancienne keypair. V1 ne cherche pas à distinguer cette substitution d'un fichier autonome valide ni un rollback intégral vers une ancienne copie valide. La keypair Solana **n'est pas** réutilisée comme clé d'administration du format. ### 6.1 Garantie d'intégrité V1 sous l'autorité OWNER originale Sans password OWNER ni clé privée d'administration exfiltrée, un attaquant — y compris s'il connaît le format complet et le password VIEW — ne peut pas produire un état accepté sous l'autorité OWNER originale qui : ```text modifie Pubkey / alias / notes modifie le slot OWNER ou son password remplace / supprime / ajoute un key slot hors rotation du slot VIEW existant active / désactive / recrée VIEW change le descripteur stable du slot VIEW remplace le compartment secret swap la keypair Solana change les versions/algorithmes OWNER-controlled ``` La seule exception volontaire est : ```text VIEW authentifié -> nouveau password VIEW -> nouveaux paramètres/salt/KEK VIEW valides -> rewrap de la même capability metadata -> même descripteur VIEW OWNER-signed ``` Le parseur applique d'abord les bornes structurelles/anti-DoS. Il vérifie l'état OWNER-signed et, lors d'un open VIEW, l'authenticité/binding du slot VIEW fourni. Une mutation de metadata ou d'état OWNER-controlled sans signature OWNER valide est un fichier invalide, pas un nouvel état Wallet. Cette garantie est volontairement formulée « sous l'autorité OWNER originale » afin de ne pas confondre **forgery/tampering** avec la substitution totale d'un fichier auto-signé par une autre autorité. ## 7. Architecture de clés V1 ### 7.1 Matériaux aléatoires À la création : ```text K_owner_root 32 octets aléatoires K_metadata 32 octets aléatoires K_secret 32 octets aléatoires admin_signing_key clé Ed25519 distincte ``` ### 7.2 Key slots ```text password VIEW -> Argon2id(params_view, salt_view) -> KEK_view -> slot VIEW -> wrap K_metadata seulement password OWNER -> Argon2id(params_owner, salt_owner) -> KEK_owner -> slot OWNER -> wrap K_owner_root seulement ``` Les salts et paramètres sont indépendants par slot. V1 autorise : ```text exactement 1 slot OWNER 0 ou 1 slot VIEW ``` Le slot OWNER et l'état de contrôle sont OWNER-signed. Pour VIEW, l'état OWNER-signed contient un **descripteur stable** (`enabled` et `slot_id`), figé par le wire V1 de `pre.003`. Lorsque VIEW est activé, le fichier doit contenir exactement un slot VIEW correspondant à ce descripteur. Ses paramètres Argon2id autorisés, son salt et son nonce/ciphertext de wrapping restent rotatables sans OWNER, mais les algorithmes V1, son rôle, son identité et son activation ne le sont pas. Le wrapping VIEW utilise un AAD qui lie au minimum format/version, autorité wallet, rôle VIEW et identifiant signé du slot. Le wire utilise néanmoins un tableau générique `key_slots` afin qu'un futur `format_version` puisse introduire d'autres rôles sans remodeler toute l'enveloppe. ### 7.3 Compartiment OWNER control Chiffré avec `K_owner_root`, il contient au minimum : ```text control_version K_metadata K_secret admin_signing_secret ``` OWNER peut donc accéder à metadata/secret et gérer VIEW sans dépendre du slot VIEW. ### 7.4 Compartiment metadata Chiffré avec `K_metadata` : ```text metadata_version Solana Pubkey alias optionnel notes ``` VIEW et OWNER peuvent le déchiffrer. ### 7.5 Compartiment secret Chiffré avec `K_secret` : ```text secret_version Solana keypair material ``` Seul OWNER reçoit `K_secret` via le control compartment. ### 7.6 Rotation et révocation Il faut distinguer : **rotation de password VIEW par VIEW** : VIEW authentifié choisit un nouveau password, de nouveaux paramètres/salt/KEK VIEW et rewrappe le même `K_metadata` dans le **même slot VIEW/descripteur signé**. Il ne modifie aucune metadata et n'a pas besoin d'OWNER. L'ancien password ne déverrouille plus le slot courant. Cette opération est une rotation de credential, **pas une révocation cryptographique forte** : un ancien détenteur ayant conservé une ancienne copie/slot ou déjà extrait `K_metadata` peut conserver la capacité de lecture correspondante. **rotation de password VIEW par OWNER** : OWNER récupère `K_metadata` via owner-control et peut produire directement un nouveau wrapping VIEW avec un nouveau password, sans connaître l'ancien password VIEW. Le descripteur reste inchangé pour une simple rotation. **révocation/recréation forte de VIEW** : OWNER génère une nouvelle `K_metadata`, rechiffre les metadata, met à jour le control compartment puis recrée ou retire le slot/descripteur VIEW. Une ancienne `K_metadata` ne lit alors plus les **futurs** états metadata. Les anciennes copies du wallet restent naturellement lisibles par qui en possédait déjà les secrets. **rotation du password OWNER** : OWNER génère un nouveau salt/KDF/KEK et un nouveau wrapping de `K_owner_root`, puis renouvelle la `state_signature` OWNER ; aucun changement de keypair Solana ni dépendance à VIEW. **keypair Solana** : immuable dans V1 après création/import. Aucun `replace_keypair` n'est exposé. Toute tentative de modifier `secret` ou la Pubkey metadata sans une réécriture OWNER valide échoue sur la `state_signature`; même avec OWNER, l'import d'une autre keypair crée un nouveau `.kspwallet` au lieu de muter l'identité cryptographique existante. ## 8. Primitives et dépendances réauditées jusqu’au 2026-08-19 Aucune dépendance ci-dessous n'est ajoutée dans `pre.001`. Ce sont les candidates pour les tranches qui les consommeront réellement. ### 8.1 Solana low-level | Besoin | Crate publiée auditée | Décision candidate | |------------------|--------------------------|---------------------------------------------------------------------------------------------| | Pubkey | `solana-pubkey 4.3.0` | propriété Core ; Wallet consomme uniquement `ksp_core_lib::Pubkey`, sans dépendance directe | | keypair concret | `solana-keypair 3.1.2` | retenir, features minimales | | interface signer | `solana-signer 3.0.1` | retenir si contrat public/impl l'exige | | signature | `solana-signature 3.5.2` | dépendance directe seulement si le type public l'exige | `solana-keypair` fournit le keypair Ed25519, la conversion stricte depuis 64 octets, la signature, le format JSON de 64 entiers et une représentation Base58 complète. Aucun client RPC Solana n'est nécessaire. La génération SDK récente définit `Pubkey` comme alias du type `Address`, mais l'écosystème a déjà connu des incompatibilités lorsque plusieurs générations de `solana-address` coexistent. `pre.002` verrouille donc la surface publique Wallet sur **`ksp_core_lib::Pubkey` exclusivement** et n'ajoute aucune dépendance directe `solana-pubkey`. `pre.005` introduit réellement `solana-keypair 3.1.2` uniquement pour posséder/valider la keypair Ed25519 ; la Pubkey Wallet reste construite via `ksp_core_lib::Pubkey` depuis les 32 octets publics du keypair, sans exposer un second type d'adresse KSP. Un `cargo tree` est obligatoire avec `pre.005` afin de confirmer l'unification de `solana-address`, `ed25519-dalek`, `rand/getrandom` et d'éviter des duplications crypto injustifiées. Audit source notable : `solana-keypair 3.1.2` contient un bloc `unsafe` interne dans sa conversion Base58 vers `String`. Cela ne modifie pas la règle `#![forbid(unsafe_code)]` du code KSP, mais doit rester visible dans l'audit des transitifs ; `pre.008` réévaluera le chemin Base58 réellement appelé et les alternatives avant de figer l'adapter. ### 8.2 KDF | Primitive | Version publiée auditée | Décision V1 | |-----------|------------------------:|--------------------------------------------| | Argon2 | `0.5.3` | **retenu : Argon2id v19** | | scrypt | `0.12.0` | alternative maintenue, non ajoutée | | PBKDF2 | `0.13.0` | compatibilité/legacy seulement, non ajouté | Les paramètres Argon2 de création ont été mesurés avec le benchmark opérateur de `pre.004` : `64 MiB / 3 / 1 = 1742 ms`, `128 MiB / 3 / 1 = 3459 ms`, `256 MiB / 3 / 1 = 6925 ms` sur la machine/profil testés le 2026-08-19. `pre.005` retient donc **64 MiB / 3 passes / 1 lane** comme profil initial de création KSP, avec un salt CSPRNG indépendant de 32 octets par slot. Ce choix n'est copié ni de bot3, ni d'un RFC, ni d'un default de crate. Le fichier sérialise tous les paramètres nécessaires afin qu'un ancien wallet conserve son profil historique même lorsque les defaults KSP seront durcis. Le parseur impose des **bornes maximales** avant de lancer le KDF, afin qu'un fichier hostile ne puisse demander arbitrairement mémoire/CPU. `pre.004` ajoute également l'invariant Argon2 `memory_kib >= 8 * parallelism` avant tout calcul coûteux. Les bornes structurelles restent indépendantes du profil de création KSP. ### 8.3 AEAD | Primitive | Version publiée auditée | Décision V1 | |--------------------|--------------------------:|------------------| | XChaCha20-Poly1305 | `chacha20poly1305 0.11.0` | **retenu** | | AES-256-GCM-SIV | `aes-gcm-siv 0.12.0` | non retenu en V1 | XChaCha20-Poly1305 fournit une clé 256 bits et un nonce étendu 192 bits. Un nonce neuf est généré pour chaque wrapping/chiffrement. La crate RustCrypto documente un audit NCC Group sans constat significatif. AES-GCM-SIV apporte une meilleure tolérance à la réutilisation accidentelle de nonce que GCM classique, mais la crate RustCrypto courante indique ne pas avoir elle-même fait l'objet d'un audit de sécurité. V1 n'a pas besoin de deux AEAD : la seconde primitive est donc écartée sans créer de dépendance concurrente. ### 8.4 CSPRNG `getrandom 0.4.3` est retenu depuis `pre.004` comme primitive low-level pour les octets aléatoires propres au format. `pre.005` l'utilise également pour les seeds/slot IDs/salts/nonces de création. Les API de génération de keypair Solana ne deviennent pas pour autant propriétaires du CSPRNG du format. ### 8.5 Secret memory `zeroize 1.9.0` est retenu et devient la seule nouvelle dépendance tierce de `pre.002`, car les wrappers `ViewPassword` / `OwnerPassword` l’utilisent immédiatement pour leur nettoyage au `Drop`. `secrecy` n'est pas ajouté tant qu'un besoin ergonomique concret n'est pas démontré ; des types KSP simples imposent eux-mêmes redaction/non-Clone et utilisent `zeroize`. `pre.005` introduit directement **`ed25519-dalek ^2.2`** avec `default-features = false` et les features `signature` + `zeroize`. La génération `3.0.0`, bien que plus récente, n'est volontairement pas ajoutée : `solana-keypair 3.1.2` dépend de `ed25519-dalek ^2.1.1`, donc la branche directe `2.2` permet à Cargo d'unifier une seule génération Dalek et d'activer `zeroize` sur la `SigningKey` utilisée à la fois par l'autorité de format KSP et par le wrapper Solana. Une duplication `2.x + 3.x` n'apporterait aucune capacité nécessaire à V1. ### 8.6 État des audits de sécurité publics Le gate ne généralise pas une absence d'audit à partir du silence d'une page de crate. Il enregistre seulement les assertions primaires explicitement retrouvées : ```text chacha20poly1305 audit NCC Group documenté, sans constat significatif aes-gcm-siv crate documentant explicitement qu'aucun audit de cette crate n'a été réalisé ed25519-dalek documentation courante mettant en avant zeroization/constant-time/forbid unsafe du crate direct argon2/scrypt/... maintenance et versions réauditées ; statut d'audit à réexaminer avant ajout si aucune source primaire explicite n'est jointe ``` L'absence d'une ligne « audited » dans ce document ne signifie donc pas « sûr par absence de preuve » ; chaque dépendance sensible reste soumise à un réaudit au moment de son introduction. ### 8.7 Encodage et persistence État au terme de `pre.006` : ```text base64 0.23.1 acquis depuis pre.003 pour Base64url sans padding des champs binaires JSON tempfile 3.27.0 acquis en pre.006 pour temp files same-directory + publication no-clobber ``` `pre.006` retient `NamedTempFile::new_in` et `persist_noclobber` parce que la publication reste sur le même filesystem et ne peut pas écraser une destination existante. La documentation upstream précise que `persist_noclobber` n'est pas atomique sur absolument toutes les plateformes/filesystems et peut laisser un hard-link temporaire après certains crashes : KSP documente cette limite au lieu de surpromettre une durabilité universelle. ## 9. Format natif `.kspwallet` V1 ### 9.1 Choix wire V1 retient une **enveloppe JSON UTF-8 stricte**. Objectifs : ```text interopérabilité multi-langages immédiate inspection structurelle simple sans secret aucune dépendance endianness pour la grammaire JSON champs crypto explicites et auto-descriptifs fixtures/vecteurs lisibles ``` Les champs binaires sont encodés en **Base64url sans padding**. Le fichier JSON n'est **jamais** signé comme octets JSON bruts et aucune canonicalisation JSON externe n'est requise. ### 9.2 Enveloppe conceptuelle Structure V1 figée par le codec `pre.003` (les longueurs/encodages exacts sont normatifs dans `docs/formats/KSPWALLET_V1.md`) : ```json { "magic": "KSPWALLET", "format_version": 1, "owner_auth_public_key": "", "view_descriptor": { "enabled": true, "slot_id": "" }, "key_slots": [ { "slot_id": "", "role": "owner|view", "kdf": { "algorithm": "argon2id", "version": 19, "memory_kib": 0, "iterations": 0, "parallelism": 0, "salt": "" }, "wrap": { "algorithm": "xchacha20-poly1305", "nonce": "", "ciphertext": "" } } ], "owner_control": { "control_version": 1, "algorithm": "xchacha20-poly1305", "nonce": "", "ciphertext": "" }, "metadata": { "metadata_version": 1, "algorithm": "xchacha20-poly1305", "nonce": "", "ciphertext": "" }, "secret": { "secret_version": 1, "algorithm": "xchacha20-poly1305", "nonce": "", "ciphertext": "" }, "state_signature": { "algorithm": "ed25519", "signature": "" } } ``` Les paramètres de création KSP V1 sont désormais `memory_kib = 65536`, `iterations = 3`, `parallelism = 1`, avec un salt CSPRNG indépendant de 32 octets par slot. Le wire continue toutefois d'accepter tout profil V1 valide dans les bornes documentées, car les paramètres sont sérialisés par wallet. ### 9.3 Informations visibles wallet verrouillé Autorisé : ```text magic format_version identifiants crypto paramètres KDF salts nonces wrapped keys/key slots ciphertexts owner_auth_public_key state_signature présence ou absence d'un slot VIEW ``` Interdit en clair : ```text Solana Pubkey alias notes Solana secret/keypair admin signing secret content keys password-derived keys ``` La clé publique d'administration révèle un fingerprint aléatoire du **format wallet**, pas l'identité blockchain Solana. Elle constitue la racine de vérification **embarquée** de l'état sous la même autorité OWNER ; sa présence ne crée aucune dépendance à un facteur externe. `pre.003` fige `view_descriptor` avec `enabled` + `slot_id`: `slot_id` vaut exactement 16 octets Base64url lorsque VIEW est activé et `null` lorsqu'il est désactivé ; le descripteur est OWNER-signed alors que seuls les champs de protection du slot VIEW correspondant sont self-rotatables. ### 9.4 Pubkey et secret payload Dans le compartiment metadata, la Pubkey est représentée par sa chaîne Base58 Solana canonique. Le parseur doit pouvoir vérifier `parse -> re-encode == input`. Dans le compartiment secret, V1 conserve le keypair Solana exact 64 octets. L'ouverture OWNER vérifie systématiquement : ```text keypair 64 octets valide Pubkey dérivée du secret == Pubkey metadata ``` ### 9.5 Alias et notes Décisions de bornes initiales à implémenter/tester : ```text alias optionnel <= 256 octets UTF-8 notes <= 64 entrées texte d'une note <= 8 KiB UTF-8 metadata plaintext total <= 64 KiB fichier .kspwallet total <= 1 MiB password encodé UTF-8 <= 1024 octets ``` Une note V1 possède : ```text id = 16 octets aléatoires, Base64url sans padding data = texte UTF-8 protégé ``` L'ID facilite update/delete sans rendre le texte lui-même identifiant. `pre.003` fige ces bornes structurelles V1 ; les payloads plaintext exacts restent implémentés dans les tranches suivantes sans modifier ces plafonds. ### 9.6 Password text contract V1 définit le KDF input comme les **octets UTF-8 exacts** du password fourni. Aucune normalisation Unicode implicite n'est appliquée. La documentation d'interop doit rendre ce point explicite. La création refuse un password vide. L'API peut construire des wrappers depuis des octets ou un texte UTF-8, mais ne doit pas conserver un `String` en clair au-delà de la dérivation nécessaire. ### 9.7 Strictness / unknown fields V1 : ```text magic inconnu rejet format_version inconnu rejet explicite champ top-level inconnu rejet champ crypto/slot inconnu rejet role slot inconnu rejet slot OWNER dupliqué/absent rejet slot VIEW dupliqué rejet valeur enum/algo inconnue rejet base64 non canonique rejet trailing data non whitespace rejet fichier hors bornes rejet avant crypto coûteuse ``` L'extensibilité se fait par un nouveau `format_version`, pas par une tolérance silencieuse qui changerait le sens de données authentifiées. ### 9.8 Versionnement protégé `format_version` reste la version de l'enveloppe/wire/crypto/transcript. Il n'est pas incrémenté pour une rotation ou une édition metadata. `pre.001` **ne retient pas un unique `payload_version` global**. Les trois compartiments évoluent indépendamment et portent plutôt : ```text control_version metadata_version secret_version ``` Une évolution locale d'un payload protégé pourra ainsi être migrée explicitement sans prétendre que l'enveloppe entière change nécessairement de grammaire. Aucune migration automatique mutante n'est exécutée lors d'un simple `open`. Toute migration future est explicite, OWNER-authorized, testable et atomique. ### 9.9 Checksum Pas de checksum séparé V1 : AEAD protège chaque ciphertext/wrapping, la `state_signature` protège l'état OWNER-controlled, et le slot VIEW self-service est authentifié par son wrapping AEAD lié au descripteur OWNER-signed. Un checksum supplémentaire ne fournit pas de garantie de sécurité utile. ## 10. Authentification, transcript et AAD ### 10.1 Pas de signature des octets JSON La signature d'état porte sur un **transcript binaire déterministe** construit après parsing strict, avec : ```text domain separation KSPWALLET-V1-STATE ordre de champs fixé par la spec longueurs explicites entiers en big-endian octets binaires décodés, pas leurs représentations Base64 texte rôle/algorithme/version inclus chaque champ OWNER-controlled, KDF/slot OWNER et ciphertext OWNER-controlled inclus descripteur stable VIEW inclus paramètres Argon2id VIEW autorisés + salt + nonce/ciphertext de wrapping du slot VIEW explicitement exclus state_signature elle-même exclue ``` Une réindentation ou un ordre JSON différent qui se parse vers le même état sémantique n'exige donc pas une canonicalisation JSON pour vérifier la signature. ### 10.2 AEAD AAD Chaque opération de wrapping/chiffrement utilise un AAD domain-separated incluant au minimum : ```text magic/format_version owner_auth_public_key kind de compartiment ou rôle de slot identifiant/descripteur signé du slot lorsqu'il existe identifiants algo/version pertinents paramètres publics nécessaires à lier le ciphertext à son contexte ``` `pre.003` fige le layout exact dans `docs/formats/KSPWALLET_V1.md` : domain separator ASCII terminé par `0x00`, puis champs TLV `tag u16 BE || length u64 BE || value`; les `u32` sont encodés sur 4 octets big-endian. Le transcript OWNER inclut le slot OWNER complet et le descripteur VIEW stable mais exclut KDF/salt/wrap VIEW self-service. Les AAD OWNER/VIEW et owner-control/metadata/secret utilisent des domain separators distincts. Les vecteurs déterministes de la tranche verrouillent les octets produits. ## 11. Secret en mémoire Contrat : ```text ViewPassword / OwnerPassword non Copy, non Clone par défaut, Debug redacted WalletView aucun secret Solana/OWNER ; seulement matériau VIEW minimal privé pour lecture + rotation de son password WalletOwner secret/root/admin keys privés, sans getter raw public secret/key buffers temporaires Zeroizing/Zeroize lorsque possédés message signé jamais loggé password détruit dès que la KDF n'en a plus besoin ``` `WalletOwner::sign()` permet la signature sans export du secret brut. Une exportation secrète n'existe que comme opération OWNER explicitement nommée via un adapter de transfert. Elle ne crée pas de méthode de commodité `secret() -> Vec`. ### Limites de zeroization KSP peut garantir que ses buffers possédés explicitement zeroizables exécutent leur effacement de drop et éviter les clones volontaires. KSP ne promet pas d'effacer : ```text registres CPU copies internes de bibliothèques tierces copies transitoires produites par l'allocateur/runtime swap/core dumps copies déjà exfiltrées par un processus compromis ``` Le threat model ne transforme donc pas `zeroize` en garantie de mémoire secrète absolue. ## 12. API publique à concevoir en `pre.002` Responsabilités proposées, noms encore ajustables : ```text LockedWalletInfo WalletInfo WalletNote WalletView WalletOwner ViewPassword OwnerPassword WalletCreateOptions WalletCapability ``` Surface conceptuelle : ```rust inspect(path) -> LockedWalletInfo create(path, owner_password, optional_view_password, options) -> WalletOwner open_view(path, view_password) -> WalletView open_owner(path, owner_password) -> WalletOwner ``` `WalletView` : ```text pubkey() alias() notes() info() rotate_view_password(new_password, ...) ``` `rotate_view_password` est la **seule mutation** disponible à VIEW : elle ne modifie que les champs de protection de son slot courant et rewrappe la même capability metadata. Aucune méthode de mutation Pubkey/alias/notes, signature, export secret, changement OWNER, disable/recreate VIEW ou autre administration n'est exposée. `WalletOwner` : ```text pubkey()/alias()/notes()/info() sign(message) update_alias(...) add_note(...) update_note(...) delete_note(...) rotate_view_password(...) disable_view() recreate_view(...) rotate_owner_password(...) export_with(...) ``` OWNER peut donc changer son propre password et le password VIEW. Une simple rotation du password VIEW par OWNER n'exige pas de connaître l'ancien password VIEW ; OWNER récupère le matériau metadata depuis owner-control. Les mutations persistées sont async et atomiques. `sign()` est locale/CPU et reste naturellement sync. ## 13. Async / sync Séparation retenue : ```text filesystem public API async-first KDF Argon2 spawn_blocking codec/AEAD local sync interne, enveloppé par flux async signature locale sync atomic persistence blocking OS work isolé hors executor async si nécessaire ``` La crate peut donc être utilisée depuis Tauri plus tard sans dépendre de Tauri et sans bloquer naïvement un executor Tokio avec Argon2. ## 14. Persistence ### 14.1 Baseline portable Le chemin de destination est toujours fourni explicitement par le caller ; Wallet ne lit aucune Config ni variable d'environnement pour choisir un répertoire par défaut. Pour créer/remplacer un `.kspwallet` : 1. sérialiser complètement le nouvel état en mémoire bornée ; 2. créer un temp file unique **dans le même répertoire** que la destination ; 3. écrire tout le contenu ; 4. `flush`/`sync_all` le temp file ; 5. publier avec une primitive no-clobber pour `create` ou replace pour administration ; 6. synchroniser le répertoire parent lorsque la plateforme le permet ; 7. ne jamais considérer un fichier partiellement écrit comme nouveau wallet valide. `pre.006` retient `tempfile::NamedTempFile` créé dans le même répertoire que la destination et `persist_noclobber` pour les créations. Le temp file est synchronisé avant publication puis le fichier publié est resynchronisé. Sur Unix, une synchronisation du répertoire parent est tentée en best-effort ; son échec est journalisé sans transformer une publication déjà réussie en échec ambigu. KSP ne revendique donc pas une durabilité power-loss/crash-safe universelle sur tous les OS/filesystems. ### 14.2 No-clobber `create` ne fait jamais d'overwrite implicite. En concurrence, un seul créateur peut publier la destination ; les autres reçoivent `destination_exists`. ### 14.3 Import vers format natif Tout import d'un format externe est une **création de wallet natif** : ```text source externe valide -> conversion en nouvelle identité/metadata native -> create no-clobber d'un nouveau fichier .kspwallet ``` L'import ne fournit aucun mode overwrite/replace d'un `.kspwallet` existant et ne sert jamais à remplacer la keypair d'un wallet déjà créé. Une destination existante retourne `destination_exists`. ### 14.4 Écriture interrompue `pre.006` teste un fault injecté après écriture+`sync_all` du temp file mais avant publication : aucune destination partielle n'apparaît et le temp est nettoyé lors d'un retour d'erreur normal. Un crash brutal du processus peut néanmoins laisser un temp artifact/hard-link complet selon la plateforme ; KSP ne traite jamais ces noms temporaires comme le wallet destination et ne promet pas leur suppression après kill/power-loss. L'invariant prioritaire est que `create` ne publie jamais un contenu partiel et ne remplace jamais une destination existante. Les ACL/permissions du système d'exploitation ne font pas partie des garanties de `ksp-wallet-lib`. ## 15. Erreurs et non-oracle Le domaine KSP commun reste `ksp-core-lib::ErrorCode/Error/Result`. Catégories de travail : ```text wallet.format_invalid wallet.format_version_unsupported wallet.crypto_parameters_invalid wallet.authentication_failed wallet.view_unlock_failed wallet.owner_unlock_failed wallet.capability_insufficient wallet.io_failed wallet.destination_exists wallet.atomic_persistence_failed wallet.transfer_format_unsupported wallet.key_material_invalid wallet.signature_failed ``` Les erreurs de structure publiques peuvent être précises. Une fois une tentative de déverrouillage cryptographique engagée, mauvais password, tag AEAD invalide et key unwrap invalide ne doivent pas devenir un oracle détaillé inutile : la cause externe est regroupée par rôle/opération. Jamais dans message/context/source reformaté volontairement : ```text password secret/keypair bytes seed phrase KDF input plaintext secret ciphertext complet notes/alias par défaut ``` ## 16. Logging Le target principal Wallet est explicitement possédé par `src/constants.rs` conformément à `DEP-LOG-010` : ```text pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib"; ``` Toutes les émissions passent par `ksp-logging-lib` uniquement. Wallet ne dépend jamais directement de `tracing` et n’utilise pas `env!("CARGO_PKG_NAME")` comme target. Événements sûrs : ```text operation=create/open_view/open_owner/rotate/sign/import/export capability=view|owner format_version transfer format code success/failure category duration ``` Pas de full path/filename par défaut dans les logs Wallet ; le caller peut corréler l'opération à son propre contexte applicatif. Ne jamais logger password, secret, payload signé, contenu complet, ciphertext complet, alias ou notes, même en trace. ## 17. Import/export extensible ### 17.1 Architecture Le cœur ne doit pas utiliser un enum central fermé qui oblige à modifier toutes les branches à chaque nouveau format. `pre.008` doit introduire un contrat d'adapter/descriptor ouvert avec un code stable et des implémentations built-in enregistrables. L'import et l'export sont séparables pour qu'un format read-only n'implémente pas artificiellement l'autre sens. Toute API d'export qui remet temporairement du key material à un adapter est explicitement OWNER-only, nommée dangereusement et limite la durée de vie de la copie via zeroization. Aucun getter secret général n'est introduit. ### 17.2 Formats engagés pour `0.2.5` Minimum retenu : ```text solana-cli-json tableau strict de 64 u8 solana-keypair-base58 Base58 du keypair complet 64 octets ``` Le second nom reste générique : `pre.001` ne l'appelle pas « Phantom » ou « Solflare » sans spécification wire normative prouvant une équivalence contractuelle. Ces deux formats suffisent à démontrer import, export, inspection et extensibilité sans dépendance propriétaire. ### 17.3 Formats audités mais reportés ```text Phantom Solflare Backpack Trust Wallet mnemonic/seed phrase hardware/remote signer ``` Ils peuvent être ajoutés ultérieurement uniquement après réaudit du wire réel et du threat model correspondant. ## 18. Interopérabilité externe La release doit créer/finaliser une **spécification séparée de l'implémentation Rust** : ```text docs/formats/000-README.md docs/formats/KSPWALLET_V1.md ``` Comme `docs/formats/` n'existe pas encore dans `v0.2.4`, son premier ajout doit respecter `FILE_CONTRACTS.md` : le contrat documentaire de cette nouvelle famille et l'index `docs/000-README.md` sont mis à jour dans la même tranche qui introduit la première spec (`pre.003` au plus tard). `KSPWALLET_V1.md` est l'autorité normative du wire V1. Il ne doit pas renvoyer à un type Rust ou au code source comme condition nécessaire pour comprendre le format. Une implémentation indépendante en Python, Go, C/C++, Java, Rust ou autre doit pouvoir créer, vérifier, ouvrir VIEW/OWNER et effectuer les rotations définies à partir de cette seule spécification et des vecteurs publics. La spec normative contiendra : ```text JSON schema grammaticale exacte encodages Base64url/Base58/UTF-8 KDF et paramètres sérialisés key wrapping et compartiments AAD exacts transcript state signature exact big-endian/length prefixes du transcript règles strictes de parsing bornes VIEW open OWNER open rotations/revocation VIEW rotation OWNER migration/versioning limites anti-substitution/rollback ``` ### 18.1 Vecteurs publics déterministes Les fixtures publieront volontairement : ```text Solana secret/keypair TEST-ONLY connu admin key TEST-ONLY connue password VIEW connu password OWNER connu salts/nonces/content keys déterministes metadata attendues KDF outputs attendus wrapped keys attendues ciphertexts attendus state transcript attendu state signature attendue fichier .kspwallet final attendu projection VIEW/OWNER attendue signature Solana d'un message test attendu ``` Les vecteurs sont explicitement **insecure / tests only** et ne servent jamais de wallet de production. Ils sont auto-contenus : aucun secret, salt, pepper, OTP, fichier annexe ou état KSP non publié n'est requis pour reproduire les résultats. L'injection de random déterministe reste privée aux tests/codec fixtures ; l'API production ne permet pas au caller de remplacer silencieusement le CSPRNG. ## 19. Tests de sécurité et contrat ### 19.1 Format - round-trip natif ; - magic/version ; - unknown fields/version rejetés ; - troncation, trailing bytes, types, Base64, tailles rejetés ; - tampering partiel détecté ; - aucun secret/Pubkey/alias/note en clair ; - paramètres KDF/crypto complets ; - transcript/vecteurs externes reproductibles. ### 19.2 VIEW/OWNER - VIEW lit seulement metadata autorisées ; - VIEW peut changer son propre password VIEW ; - VIEW ne peut modifier ni Pubkey/alias/notes, ni OWNER, ni activation/descripteur VIEW ; - aucune signature/export secret/admin OWNER via type VIEW ; - OWNER fonctionne sans VIEW ; - OWNER signe et administre ; - mauvais rôle/password échoue sans fuite ; - salts/KDF material indépendants ; - VIEW ne dérive pas OWNER/secret ; - modification metadata par détenteur VIEW sans admin signing key rejetée ; - modification du slot OWNER ou du descripteur/activation VIEW par détenteur VIEW rejetée ; - rotation valide des seuls paramètres Argon2id VIEW autorisés, salt et nonce/ciphertext de wrapping du slot VIEW courant par VIEW acceptée ; - tentative de changer password OWNER via VIEW rejetée ; - substitution du ciphertext secret/keypair sans OWNER rejetée ; - `state_signature` invalide/manquante rejetée avant projection ; - substitution totale explicitement non promise. ### 19.3 Rotation/revocation - rotation VIEW par VIEW conserve keypair/Pubkey/metadata et ne change que son credential ; - rotation VIEW par OWNER fonctionne sans ancien password VIEW ; - old password VIEW n'ouvre plus le slot courant après rotation normale ; - limites de révocation d'un ancien détenteur ayant conservé `K_metadata`/une ancienne copie explicitement testées/documentées ; - rotation OWNER conserve keypair/Pubkey ; - old password OWNER n'ouvre plus le slot courant ; - disable/recreate VIEW ne nécessite pas ancien password VIEW ; - recreate VIEW forte change `K_metadata` ; - anciennes copies restent hors de la promesse de révocation ; - paramètres KDF historiques restent lisibles après changement des defaults. ### 19.4 Secret/diagnostics - `Debug`/projection/error/log sans secret ; - wrappers secrets non `Copy` et non `Clone` par défaut ; - VIEW ne matérialise jamais le keypair ; - signature sans getter secret ; - zeroization testée seulement là où observable honnêtement. ### 19.5 Persistence - create ; - destination existante ; - concurrence no-clobber ; - replace atomique ; - fault injection avant publication préserve l'ancien wallet ; - import vers destination nouvelle/no-clobber ; - import vers destination existante rejeté sans écrasement ; - éventuels temp remnants documentés sans corruption du wallet publié. ### 19.6 Architecture - canary crate-root ; - Wallet -X-> Config/Transport/ExecutionPolicy/Store/Tauri ; - Wallet -X-> `tracing` direct ; - aucun env direct ; - aucun `unsafe` KSP ; - cargo-tree sans duplication crypto injustifiée. ### 19.7 État acquis après `pre.005` `pre.005` matérialise désormais les invariants crypto centraux sans filesystem : ```text K_owner_root, K_metadata, K_secret indépendants OWNER slot -> K_owner_root VIEW slot -> K_metadata owner_control = admin Ed25519 secret || K_metadata || K_secret (96 octets) metadata = JSON protégé Pubkey/alias/notes avec note IDs 16 octets uniques secret = keypair Solana 64 octets secret||public state_signature Ed25519 vérifiée avant tout KDF open VIEW ne matérialise jamais owner-control/secret open OWNER vérifie autorité admin + cohérence keypair + Pubkey metadata create/open KDF via spawn_blocking vecteur complet test-only généré et revérifié indépendamment du Rust ``` La signature Solana publique, les mutations metadata et les rotations restent volontairement absentes de cette tranche ; la persistence est désormais acquise par `pre.006`. ### 19.8 État acquis après `pre.006` `pre.006` ajoute la frontière filesystem sans Config ni environnement : ```text create_wallet_file_v1(path, ...) -> create in-memory -> JSON verrouillé -> temp same-directory -> sync_all -> persist_noclobber open_wallet_view_file_v1(path, ...) -> bounded read -> open VIEW acquis open_wallet_owner_file_v1(path, ...) -> bounded read -> open OWNER acquis inspect_locked_wallet_file_v1(path) -> bounded read -> signature-state verify sans KDF ``` La lecture est bornée à `KSPWALLET_MAX_FILE_BYTES` avant parsing, y compris si le fichier grossit entre metadata et lecture. Le filesystem blocking est exécuté via `tokio::task::spawn_blocking`. La création concurrente a exactement un gagnant ; les autres reçoivent `wallet.destination_exists`. Aucun répertoire n'est créé ou choisi par Wallet. Le replace administratif reste volontairement pour `pre.007`, où il pourra être lié à une capability OWNER ou à la self-rotation VIEW au lieu d'exposer une primitive générale d'overwrite. ## 20. Sizing | Domaine | Taille | Risque principal | |-------------------------------|-------:|----------------------------------------------| | crate/API foundation | M | capability surface durable | | threat model | M | faux niveau de garantie | | format wire | L | strict parsing/versioning | | interop/test vectors | L | transcript exact multi-langages | | KDF/AEAD/key wrapping | L | paramètres + nonce/AAD | | VIEW/OWNER key slots | XL | indépendance et rotations | | auth crypto metadata niveau B | L/XL | clé admin + transcript + substitution limits | | persistence | L | no-clobber + crash semantics multi-OS | | signing | M | aucun secret getter | | password/key-slot rotation | L | rotation vs vraie révocation | | alias/notes | M | bornes + persistence | | import/export | M/L | extension sans secret API générale | | security/adversarial tests | XL | tamper/fault/diagnostics | | spec/README/USAGE | L | contrat externe autonome | Conclusion : **pas de rescoping fonctionnel**, mais split supplémentaire avant crypto lourde. ## 21. Prévision souple révisée ```text pre.001 audit bot2/bot3 + deps actuelles + threat model + VIEW/OWNER + format/API + sizing pre.002 crate foundation + capability/password/info types (VIEW self-rotation only, OWNER admin) + erreurs + logging contract + canaries architecture pre.003 codec JSON strict + limites + key-slot/envelope DTOs + transcript/AAD codec + contrat docs/formats + spec V1 initiale pre.004 benchmark KDF + Argon2id/XChaCha wrapping + content keys + deterministic crypto vectors in-memory pre.005 owner-control + metadata/secret compartments + create/open VIEW/OWNER + state signature niveau B pre.006 persistence async/atomic/no-clobber + interrupted-write + fault/concurrency tests pre.007 signature Solana + alias/notes + rotations passwords + disable/recreate VIEW avec rekey metadata pre.008 import/export adapters + Solana CLI JSON + generic keypair Base58 + inspect pre.009 audit security/interoperability/compliance + adversarial vectors/tests + cargo trees pre.010 spec finale + README/USAGE + graphes/docs + candidate de clôture + prompt 0.2.6 rel.001 publication strictement publicationnelle ``` Une `fix` ou tranche supplémentaire est préférable à la suppression d'une garantie sécurité si un des gates révèle une incompatibilité. ## 22. Dépendances par tranche État réellement acquis au terme de `pre.005` : ```text pre.002 zeroize ^1.9 pre.003 base64 ^0.23 pre.004 argon2 ^0.5 chacha20poly1305 ^0.11 getrandom ^0.4 pre.005 ed25519-dalek ^2.2 solana-keypair ^3.1 tokio déjà workspace, feature locale rt pour spawn_blocking ``` Toutes les dépendances tierces communes restent centralisées sous `[workspace.dependencies]`; le membre Wallet active uniquement les features nécessaires. `ed25519-dalek ^2.2` est volontairement aligné avec la contrainte `^2.1.1` de `solana-keypair 3.1.2` afin de permettre une seule génération Dalek et d'activer `zeroize` sur la `SigningKey` partagée par résolution Cargo. Candidates restantes, à réauditer juste avant insertion : ```text tempfile ^3.27 # acquis pre.006 : temp same-directory + persist_noclobber solana-signer ^3.0 # seulement si un contrat public/impl l'exige réellement solana-signature ^3.5 # seulement si le type public l'exige ``` Déjà présents et réutilisés : ```text serde serde_json tokio ksp-core-lib # propriétaire/réexport de Pubkey pour Wallet ksp-logging-lib ``` `ksp-wallet-lib` ne déclare pas `solana-pubkey` : toute Pubkey publique ou interne du domaine Wallet passe par `ksp_core_lib::Pubkey`. Non retenus nativement en V1 : ```text scrypt pbkdf2 aes-gcm-siv secrecy bs58 Solana RPC clients Config/Store/Tauri ``` Le fait que `bs58` ne soit pas requis sera révalidé lorsque les adapters sont codés : `solana-keypair` fournit déjà la conversion Base58 du keypair complet. ## 23. Sources externes réauditées Sources primaires consultées initialement le 2026-08-18 puis réauditées le 2026-08-19 pour les dépendances effectivement introduites par `pre.005` : ```text https://docs.rs/solana-keypair/3.1.2/ https://docs.rs/solana-signer/3.0.1/ https://docs.rs/solana-pubkey/4.3.0/ https://docs.rs/solana-signature/latest/ https://docs.rs/argon2/0.5.3/ https://docs.rs/scrypt/0.12.0/ https://docs.rs/pbkdf2/0.13.0/ https://docs.rs/chacha20poly1305/0.11.0/ https://docs.rs/aes-gcm-siv/0.12.0/ https://docs.rs/getrandom/0.4.3/ https://docs.rs/zeroize/1.9.0/ https://docs.rs/ed25519-dalek/2.2.0/ https://docs.rs/base64/0.23.1/ https://docs.rs/tempfile/3.27.0/ ``` Les versions sont des constats d'audit successifs : elles ne signifient pas que toutes les crates listées ont été ajoutées dans une même tranche. Chaque dépendance sensible est réauditée au moment où elle devient réellement consommée. ## 24. Critères de sortie `0.2.5` La release n'est clôturable que si : ```text .kspwallet V1 est entièrement autonome : fichier + password suffisent, sans facteur/ancre externe KSPWALLET_V1.md permet une implémentation indépendante sans consulter le code Rust .kspwallet V1 est spécifié hors code et couvert par vecteurs publics auto-contenus VIEW/OWNER sont indépendants cryptographiquement et dans l'API Pubkey/alias/notes restent cachés verrouillé VIEW ne peut pas signer/exporter ni modifier Pubkey/alias/notes/OWNER via l'API VIEW peut changer uniquement son propre password en rewrappant la même capability metadata dans son slot courant niveau B rejette toute mutation de l'état OWNER-controlled sous la même autorité OWNER sans signature OWNER valide VIEW connu ne permet ni changement du password OWNER/descripteur VIEW/slot OWNER ni swap de keypair sous l'autorité OWNER originale OWNER peut changer son propre password et le password VIEW sans dépendre de VIEW limite substitution/rollback complet est documentée sans surpromesse keypair V1 est immuable après création/import rotation de password VIEW ou OWNER ne change pas keypair OWNER gère VIEW sans ancien password VIEW révocation forte VIEW rekey les metadata futures sign() n'exige pas d'extraction publique du secret persistence no-clobber/atomique respecte le contrat documenté interop Solana CLI JSON + Base58 est prouvée aucun Config/Transport/Policy/Store/Tauri/env/tracing direct ne fuit dans Wallet cargo tree et diagnostics secrets sont audités README/USAGE/spec/vecteurs sont cohérents ``` ## 25. Hors périmètre confirmé ```text ksp-app-wallet-desk balance/réseau HTTP/WebSocket/gRPC WalletPolicy / execution policy construction/simulation/envoi transaction Store seed phrase UI hardware wallet/Ledger remote signer browser/mobile/cloud custody trading recovery slot réel hardware/automation slots réels anti-rollback externe salt/pepper externe requis par le format V1 OTP / second facteur externe requis par le format V1 ancre de confiance externe requise par le format V1 ``` Une future `format_version >= 2` pourra réétudier des facteurs/ancrages externes, mais cette piste n'est ni conçue ni implémentée dans `0.2.5`. ## 26. Suite immédiate `0.2.5-pre.003` fige le codec JSON strict, les limites structurelles, `slot_id` 16 octets, le descripteur VIEW, les DTOs d’enveloppe/key slots, les TLV transcript/AAD et la première spécification `docs/formats/KSPWALLET_V1.md`. `pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS et le wrapping de content keys. Le benchmark opérateur a ensuite permis à `pre.005` de retenir le profil initial `64 MiB / 3 / 1`, de figer les payloads `owner_control`/metadata/secret, d'introduire l'autorité Ed25519 OWNER distincte de la keypair Solana, de créer/ouvrir réellement VIEW et OWNER en mémoire et de publier un vecteur `.kspwallet` complet interopérable. **La suite immédiate est `pre.007` : signature Solana publique + administration metadata/passwords/VIEW**, sans déplacer de logique filesystem dans Config.