12 KiB
Wallets, sécurité et opérations
1. Objet
Ce guide décrit le contrat opérationnel de ks-wallet à partir de 0.5.2.
ks-wallet possède :
- le stockage local des wallets Solana persistants ;
- la protection par mot de passe ;
- l'ouverture authentifiée ;
- la capacité de signature ;
- les imports/exports de formats secrets explicitement supportés.
Il ne possède pas :
- la politique d'autorisation d'une transaction ;
- les plafonds de dépense ;
- le routage RPC ;
- la classification on-chain d'une pubkey ;
- les DTO frontend d'une application Tauri.
2. Modèle de données
2.1 Identité publique
Une identité publique contient uniquement :
- alias ;
- pubkey ;
- persistance temporaire ou persistante.
Une identité publique ne contient jamais le secret, le mot de passe, le ciphertext ou un chemin local.
2.2 Wallet temporaire
TemporaryWallet::generate() crée un keypair en mémoire. Il ne nécessite ni mot de passe ni fichier.
Cette surface reste adaptée aux tests synthétiques et signers jetables. Les fixtures/keypairs persistées uniquement pour les campagnes historiques restent sous wallets/temporary/**; elles ne doivent pas être confondues avec les .kswallet persistants de wallets/.
2.3 Wallet persistant
Le format natif est :
<alias>.kswallet
Le layout exact est défini dans ../NATIVE_FORMAT.md.
Le store automatique de WalletManager :
- scanne uniquement son répertoire configuré ;
- ne descend pas récursivement ;
- ignore les extensions étrangères ;
- refuse symlinks et fichiers non réguliers ;
- exige des permissions privées sous Unix ;
- valide le conteneur complet avant de retourner un handle.
3. Protection cryptographique
Le format v1 utilise :
| Élément | Contrat v1 |
|---|---|
| KDF | Argon2id v19 |
| Profil d'écriture | 64 MiB, 3 passes, 4 lanes |
| Sel | 16 octets |
| Clé dérivée | 32 octets |
| AEAD | XChaCha20-Poly1305 |
| Nonce | 24 octets |
| Plaintext | keypair Solana exacte de 64 octets |
| Ciphertext + tag | 80 octets |
| AAD | header + alias + sel + nonce |
Les paramètres lus sont bornés avant l'exécution du KDF.
La pubkey présente dans le header n'est pas considérée comme authentifiée avant l'unlock. Après déchiffrement, ks-wallet reconstruit le keypair et vérifie que sa pubkey correspond exactement au header avant de produire une capacité de signature.
4. Mot de passe et cycle de vie
WalletPassword :
- prend possession de sa chaîne ;
- n'est pas clonable ;
- n'est pas sérialisable ;
- possède un
Debugredacted ; - zéroïse la valeur qu'il possède à la destruction.
WalletManager::create() crée un wallet natif et retourne UnlockedWallet.
WalletManager::unlock() ouvre un wallet du store par alias.
WalletManager::unlock_file() ouvre un WalletFileHandle obtenu par inspection explicite d'un fichier externe.
UnlockedWallet :
- possède le keypair authentifié ;
- n'est pas clonable ;
- expose
Signer/Signer + Sync; - n'expose pas les 64 octets privés ;
- est verrouillé par consommation explicite avec
lock()ou par fin de portée.
Un changement de mot de passe ne change pas la keypair. Il produit un nouveau sel et un nouveau nonce, puis reprotège exactement la même identité Solana.
Une UnlockedWallet déjà remise à un consommateur n'est pas révoquée rétroactivement par un changement de mot de passe. Son propriétaire doit la détruire ou appeler lock().
5. Écriture et permissions
Les nouveaux .kswallet sont publiés sans écrasement silencieux.
Sous Unix :
- répertoire wallet :
0700; - fichier
.kswallet:0600; - exports secrets :
0600.
Les écritures natives utilisent un fichier temporaire privé dans le même répertoire, sync_all, puis une publication atomique/no-clobber. Le changement de mot de passe remplace le fichier après authentification de l'ancien secret.
Les collisions concurrentes de création d'un même alias doivent produire exactement un gagnant et conserver une destination native valide.
6. Configuration
wallet.config.json contient une racine globale wallets_directory et des profils.
Un profil peut sélectionner :
{
"wallet_alias": "operator"
}
ou laisser :
{
"wallet_alias": null
}
L'alias est une sélection non sensible. Aucun mot de passe n'est stocké dans ks-config.
Lorsqu'un alias persistant est configuré, un consommateur ne doit pas retomber silencieusement sur un wallet temporaire : il doit acquérir explicitement le mot de passe et appeler l'API d'unlock.
7. Migration legacy
Le legacy historique est :
<alias>.json
avec le tableau JSON des 64 octets du keypair Solana.
WalletManager::migrate_legacy() :
- lit la source legacy privée ;
- valide le keypair ;
- crée
<alias>.kswallet; - vérifie que la pubkey est identique ;
- conserve la source JSON intacte.
La suppression du legacy reste une décision explicite de l'opérateur après validation.
TemporaryWalletStore reste disponible pour les scénarios historiques. Ses erreurs, logs et Debug ne doivent pas projeter les chemins locaux, même si ses getters explicites directory() et wallet_path() restent nécessaires aux outils de fixtures.
8. Import et export
Les formats secrets actuellement supportés sont :
| Format | Import | Export | Mot de passe requis à l'export |
|---|---|---|---|
WalletTransferFormat::SolanaCliJson |
oui | oui | oui |
WalletTransferFormat::SolanaPrivateKeyBase58 |
oui | oui | oui |
La matrice détaillée est dans ../WALLET_FORMAT_COMPATIBILITY.md.
L'import refuse :
- source non régulière ou symlink ;
- permissions trop ouvertes sous Unix ;
- format invalide ;
- keypair de taille incorrecte ;
- alias déjà occupé ;
- pubkey déjà gérée sous un autre alias natif.
L'export authentifie d'abord le .kswallet, puis seulement après crée la destination. Il n'écrase jamais silencieusement un fichier existant.
9. Keypair, wallet, mint et authority
Une keypair Solana ne contient pas son rôle métier.
Le même type de secret Ed25519 peut servir comme :
- payer/opérateur ;
- mint authority ;
- freeze authority ;
- clé utilisée lors de la création d'un mint ;
- recipient possédant lui-même un signer ;
- autre authority ou signer de fixture.
Il est donc incorrect de décider « ce fichier est un mint et non un wallet » uniquement à partir du tableau JSON de 64 octets.
La future inspection de wallets/temporary/** prévue avec 0.5.4 doit produire un inventaire local factuel :
format détecté
keypair valide / invalide / format non supporté
pubkey si valide
Un consommateur peut ensuite interroger un réseau donné avec cette pubkey et classifier l'état actuel de l'adresse, par exemple :
- compte absent ;
- compte système ;
- mint SPL Token ;
- mint Token-2022 ;
- compte token ;
- programme ou autre compte.
Cette classification reste réseau-dépendante et ne prouve pas tous les rôles historiques du signer.
ks-wallet ne doit pas dépendre de ks-onchain-transport pour réaliser cette classification.
10. Desktop de démonstration
kb-app-demo-desktop possède ses propres DTO Tauri et ne sérialise pas les types secrets de ks-wallet.
La fenêtre Wallets permet :
- inventaire des
.kswalletdewallets/; - création d'un wallet natif avec un secret backend-only ;
- inspection d'un
.kswalletexterne ; - inspection/import d'un keypair Solana CLI JSON ou Base58 sans exposer son secret au frontend ;
- export secret authentifié vers
data/wallets/avec nom de fichier borné et sans chemin arbitraire côté frontend ; - sélection explicite d'un profil RPC pour les lectures publiques ;
- consultation du solde SOL, des comptes SPL Token/Token-2022, des signatures récentes et du détail
getTransaction; - sélection session-only du
.kswalletutilisé par les démos Devnet.
Le mot de passe de démonstration est lu uniquement côté backend depuis :
KB_SECRET_DEMO_WALLET_PASSWORD
La sélection runtime du wallet d'exécution est authentifiée avant activation et ne réécrit pas wallet.config.json. L'ordre de résolution est :
override session demo_wallet
-> wallet_alias du profil
-> wallet temporaire uniquement si aucun alias persistant n'est sélectionné
Lorsqu'un alias persistant est effectif, les adaptateurs Devnet System/SPL/Metadata utilisent une capacité UnlockedWallet et ne retombent pas silencieusement sur le JSON temporaire. La validation opérateur 0.5.2 a observé persistence="persistent" avec la pubkey sélectionnée.
Le profil RPC choisi dans la fenêtre Wallets ne modifie ni le profil actif global ni le store wallet actif ; il borne uniquement les lectures on-chain de cette fenêtre.
10.1 Répertoires opérateur
La convention du desktop est :
wallets/
<alias>.kswallet
temporary/<profil>/...
data/wallets/
<exports secrets explicites>
wallets/ est la racine persistante native. wallets/temporary/** appartient aux fixtures/keypairs historiques. data/wallets/ contient les exports explicites destinés à des outils externes et ne doit pas être rescanné automatiquement comme store natif.
10.2 Validation du changement de mot de passe
Le changement de mot de passe n'est volontairement pas exposé dans le frontend. La crate ks-wallet-demo-scenarios valide ce cycle comme consommateur externe de l'API publique :
A -> B
A rejeté
B ouvre et signe
B -> A
A ouvre et signe
Le test utilise un .kswallet synthétique dans un répertoire temporaire privé et ne modifie aucun wallet réel de l'opérateur.
11. Non-divulgation
Ne jamais placer un secret wallet dans :
wallet.config.json;KS_PUBLIC_*ouKB_PUBLIC_*;- un payload Tauri ;
- un DTO TS-RS généraliste ;
- un log ;
- un message d'erreur normal ;
- un
Debugautomatique d'un type sensible.
Les chemins locaux ne sont pas des secrets cryptographiques, mais ils restent des informations internes et ne doivent pas apparaître inutilement dans les logs, erreurs et DTO fonctionnels.
12. Modèle de menace borné
0.5.2 protège le secret persistant contre la lecture directe du fichier sans mot de passe et impose des permissions privées ainsi qu'un chiffrement authentifié.
Cette version ne prétend pas protéger contre :
- une machine déjà compromise pendant qu'un wallet est déverrouillé ;
- un processus disposant des mêmes droits utilisateur et capable de lire la mémoire ;
- la saisie volontaire du mot de passe dans un programme malveillant ;
- la copie volontaire d'un
.kswalletet de son mot de passe vers une autre installation compatible ; - hardware wallet, Ledger, KMS ou HSM.