Files
khadhroony-solana-project/docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md
2026-08-19 09:11:36 +02:00

51 KiB

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 :

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 :

ksp-wallet-lib
    -> ksp-core-lib
    -> ksp-logging-lib
    -> primitives crypto/key/signature low-level explicitement retenues

Interdictions :

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 :

  1. .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 ;
  2. 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 ;
  3. toute mutation persistante acceptée sous l'autorité d'un wallet V1 est OWNER-authorized : key slots, paramètres KDF, compartments, metadata et état de protection sont couverts par l'authentification OWNER ; VIEW reste incapable de forger un nouvel état valide sous cette même autorité ;
  4. 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.

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, permissions Unix 0700/0600, effacement des buffers sérialisés et frontière Signer sans exposition publique des octets privés.

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 :

.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 :

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 :

Pubkey
alias
notes

Il ne doit pouvoir dériver ni :

OWNER KEK
owner root key
Solana secret/keypair
format-admin signing secret

L'API KSP VIEW ne conserve pas la metadata content key après déchiffrement ; elle expose une projection read-only et ne possède aucune méthode de signature ou d'administration.

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 et l'état global accepté est couvert par l'authentification OWNER définie plus bas. Toute modification partielle d'un champ couvert doit provoquer un rejet déterministe sans exposer une cause crypto trop fine.

5.6 Remplacement total / rollback

Un attaquant capable de remplacer le fichier entier peut substituer :

owner auth public key
key slots
ciphertexts
state signature

par ceux d'un autre wallet valide. Un format autonome ne peut pas détecter cette substitution à partir d'une information de confiance stockée uniquement dans le fichier substitué.

La même limite vaut pour un rollback complet vers une ancienne copie valide. Cette limite n'autorise aucune dépendance externe en V1 : le format V1 reste volontairement auto-contenu et interdit comme prérequis tout salt externe, pepper, OTP, secret KSP, service distant, keychain ou ancre de confiance extérieure. Une éventuelle version de format future pourrait étudier un facteur/ancrage externe, mais ce mécanisme n'appartient ni au format V1 ni à 0.2.5.

5.7 Matrice des garanties

Garantie Cryptographie fichier Types/capabilities KSP Filesystem Ancre externe éventuelle, hors V1
confidentialité metadata verrouillées oui oui complément non
confidentialité secret Solana face à VIEW oui oui complément non
indépendance VIEW/OWNER oui oui non non
VIEW ne signe pas séparation de clés oui non non
VIEW ne modifie pas via API indirect oui non non
metadata modifiées par VIEW détectées sous la même autorité OWNER oui, state signature oui non non
détection corruption/tampering partiel oui parsing strict complément non
no-clobber / atomic replace non API oui non
protection autres users OS non non best-effort non
détection remplacement total valide non non permissions seulement théoriquement oui, mais interdite comme dépendance V1
détection rollback total valide non non non théoriquement oui, mais interdite comme dépendance V1

6. Niveau B — VIEW read-only renforcé cryptographiquement

pre.001 retient B.

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 tout l'état mutable et de sécurité du fichier, à l'exception de la signature elle-même : magic/version, autorité publique, key slots et leurs paramètres KDF/wrapping, versions de compartments, algorithmes, nonces et ciphertexts. Toute opération qui change le fichier doit donc être réalisée depuis une capacité OWNER capable de récupérer la clé privée d'administration et de produire une nouvelle signature valide.

Conséquence : un détenteur VIEW qui aurait conservé la metadata content key peut techniquement produire un nouveau ciphertext metadata, mais il ne peut pas produire, sous la même autorité OWNER, une nouvelle state_signature acceptée. Il ne peut pas davantage modifier un key slot, changer un password, remplacer le compartment secret ou substituer la keypair tout en conservant un état accepté sous l'autorité OWNER originale.

La cryptographie ne peut pas empêcher un attaquant disposant d'un accès écriture filesystem de modifier physiquement les octets ; elle garantit que ces octets modifiés sont rejetés faute d'authentification OWNER valide. Les permissions filesystem complètent cette propriété en empêchant l'écriture lorsque l'OS peut effectivement l'interdire.

Limite incompressible : une substitution du fichier entier par un autre wallet auto-cohérent, incluant une nouvelle autorité publique OWNER et une nouvelle signature valide, est indétectable par le seul fichier substitué. Cela reste vrai en particulier si l'attaquant connaît le password VIEW et crée le faux wallet avec ce même password. Détecter cette substitution ou un rollback intégral exigerait de comparer à un état de confiance conservé ailleurs ; V1 n'en dépend pas et ne prétend donc pas fournir cette garantie.

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 :

modifie Pubkey / alias / notes
modifie un paramètre KDF, salt, nonce ou wrapped key
remplace / supprime / ajoute un key slot
change le password VIEW ou OWNER
remplace le compartment secret
swap la keypair Solana
change les versions/algorithmes couverts

Le parseur applique d'abord les bornes structurelles/anti-DoS, puis vérifie la state_signature avant tout déverrouillage coûteux ou projection autorisée. Une mutation non signée 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 :

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

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 :

exactement 1 slot OWNER
0 ou 1 slot VIEW

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 :

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 :

metadata_version
Solana Pubkey
alias optionnel
notes

VIEW et OWNER peuvent le déchiffrer.

7.5 Compartiment secret

Chiffré avec K_secret :

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 : nouveau salt/KDF/KEK et nouveau wrapping du même K_metadata. L'ancien password ne déverrouille plus le fichier courant, mais cette opération ne révoque pas une K_metadata déjà exfiltrée par un VIEW malveillant.

révocation/recréation forte de VIEW : OWNER génère une nouvelle K_metadata, rechiffre les metadata, met à jour le control compartment puis crée ou retire le slot 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 : nouveau salt/KDF/KEK et nouveau wrapping de K_owner_root, sans 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 le 2026-08-18

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 déjà workspace, conserver
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 doit donc prouver par compilation/cargo-tree que le Pubkey possédé par ksp-core-lib et le Address/Pubkey exposé par solana-keypair sont de la même génération avant de figer les signatures publiques Wallet. Aucun second type d'adresse KSP n'est introduit pour contourner un mismatch.

Un cargo tree est obligatoire lors de l'introduction réelle afin de contrôler les versions ed25519-dalek, solana-signature, rand/getrandom et éviter des duplications évitables.

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 seront benchmarkés sur les machines cibles. Ils ne sont pas copiés de bot3, d'un RFC ou d'un default de crate. Le fichier sérialise tous les paramètres nécessaires afin qu'un ancien wallet conserve son profil historique.

Le parseur impose des bornes maximales avant de lancer le KDF, afin qu'un fichier hostile ne puisse demander arbitrairement mémoire/CPU. Les valeurs exactes de création et de rejet sont gelées en pre.004 après benchmark.

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 comme candidate low-level pour les octets aléatoires propres au format. Les API de génération de keypair Solana restent libres d'utiliser leur CSPRNG interne maintenu.

8.5 Secret memory

zeroize 1.9.0 est retenu. secrecy n'est pas ajouté tant qu'un besoin ergonomique concret n'est pas démontré ; des types KSP simples peuvent imposer eux-mêmes redaction/non-Clone et utiliser Zeroize/Zeroizing.

Pour la clé admin Ed25519 distincte, ed25519-dalek 3.0.0 est une candidate standard, mais son ajout direct est conditionné au cargo-tree de la tranche qui implémente l'authentification afin d'éviter une génération concurrente inutile avec celle déjà tirée par solana-keypair.

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 :

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

Candidates :

base64 0.23.1   pour Base64url sans padding des champs binaires JSON
tempfile 3.27.0 pour temp files privés same-directory + persist/persist_noclobber

Elles ne sont ajoutées que lorsqu'elles sont réellement consommées.

9. Format natif .kspwallet V1

9.1 Choix wire

V1 retient une enveloppe JSON UTF-8 stricte. Objectifs :

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 de travail à figer par le codec pre.003 :

{
  "magic": "KSPWALLET",
  "format_version": 1,
  "owner_auth_public_key": "<base64url>",
  "key_slots": [
    {
      "role": "owner|view",
      "kdf": {
        "algorithm": "argon2id",
        "version": 19,
        "memory_kib": 0,
        "iterations": 0,
        "parallelism": 0,
        "salt": "<base64url>"
      },
      "wrap": {
        "algorithm": "xchacha20-poly1305",
        "nonce": "<base64url>",
        "ciphertext": "<base64url>"
      }
    }
  ],
  "owner_control": {
    "algorithm": "xchacha20-poly1305",
    "nonce": "<base64url>",
    "ciphertext": "<base64url>"
  },
  "metadata": {
    "algorithm": "xchacha20-poly1305",
    "nonce": "<base64url>",
    "ciphertext": "<base64url>"
  },
  "secret": {
    "algorithm": "xchacha20-poly1305",
    "nonce": "<base64url>",
    "ciphertext": "<base64url>"
  },
  "state_signature": {
    "algorithm": "ed25519",
    "signature": "<base64url>"
  }
}

Les 0 ci-dessus signifient « valeur déterminée par le benchmark futur », pas des paramètres valides.

9.3 Informations visibles wallet verrouillé

Autorisé :

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 :

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.

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 :

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 :

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 :

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. Les bornes restent vérifiables pendant pre.003; toute modification avant freeze wire doit être tracée.

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 :

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 :

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 et la state_signature protège l'état sémantique global accepté sous l'autorité OWNER. 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 :

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 KDF param/salt/nonce/ciphertext inclus
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 :

magic/format_version
owner_auth_public_key
kind de compartiment ou rôle de slot
identifiants algo/version pertinents
paramètres publics nécessaires à lier le ciphertext à son contexte

Le layout exact du transcript et de chaque AAD devient normatif dans docs/formats/KSPWALLET_V1.md et dans les vecteurs déterministes.

11. Secret en mémoire

Contrat :

ViewPassword / OwnerPassword     non Copy, non Clone par défaut, Debug redacted
WalletView                       aucune key de secret et aucune metadata key retenue
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<u8>.

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 :

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 :

LockedWalletInfo
WalletInfo
WalletNote
WalletView
WalletOwner
ViewPassword
OwnerPassword
WalletCreateOptions
WalletCapability

Surface conceptuelle :

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 :

pubkey()
alias()
notes()
info()

Aucune méthode de mutation/signature/export secret/key-slot.

WalletOwner :

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(...)

Les mutations persistées sont async et atomiques. sign() est locale/CPU et reste naturellement sync.

13. Async / sync

Séparation retenue :

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

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 privé 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.

tempfile::NamedTempFile/persist_noclobber/persist est la candidate de pre.006, complétée par les fsync nécessaires. Sa sémantique exacte doit être testée par plateforme avant de revendiquer la durabilité crash-safe.

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 Unix

Durcissement visé :

wallet file créé par KSP     0600
répertoire dédié créé par KSP 0700

KSP ne change pas arbitrairement les permissions d'un parent fourni par l'appelant.

Les sources secrètes d'import doivent être des fichiers réguliers et les symlinks sont refusés par défaut pour réduire les ambiguïtés/TOCTOU usuelles. La stratégie exacte est testée dans la tranche persistence/import.

14.4 Autres plateformes

Aucune promesse POSIX 0600/0700 sur Windows. La documentation distingue garanties portables, ACL/permissions système et durcissement Unix.

Un crash peut laisser un temp artifact complet à nettoyer ; l'invariant prioritaire est de ne pas transformer l'ancien wallet valide en fichier partiellement remplacé.

15. Erreurs et non-oracle

Le domaine KSP commun reste ksp-core-lib::ErrorCode/Error/Result.

Catégories de travail :

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 :

password
secret/keypair bytes
seed phrase
KDF input
plaintext secret
ciphertext complet
notes/alias par défaut

16. Logging

Cible KSP future proposée :

ksp.wallet

Via ksp-logging-lib uniquement.

Événements sûrs :

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 :

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

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 :

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 :

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 :

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 ;
  • aucune signature/mutation/admin/export 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 d'un key slot/KDF/wrapped key par détenteur VIEW rejetée ;
  • tentative de changer password VIEW/OWNER sans OWNER 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 conserve keypair/Pubkey ;
  • old password VIEW n'ouvre plus le slot courant ;
  • 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 ;
  • permissions Unix ;
  • symlink/source handling ;
  • é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.

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

pre.001  audit bot2/bot3 + deps actuelles + threat model + VIEW/OWNER + format/API + sizing
pre.002  crate foundation + capability/password/info types + 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 + Unix hardening + 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 candidates par tranche

Aucune ajoutée en pre.001.

Liste de travail, à réauditer juste avant insertion sous [workspace.dependencies] :

argon2              ^0.5
base64              ^0.23
chacha20poly1305    ^0.11
ed25519-dalek       ^3.0    # seulement si cargo-tree justifie le direct
getrandom           ^0.4
solana-keypair      ^3.1
solana-signer       ^3.0    # si réellement nécessaire directement
solana-signature    ^3.5    # seulement si le type public l'exige
tempfile            ^3.27
zeroize             ^1.9

Déjà présents et réutilisables :

serde
serde_json
tokio
solana-pubkey
ksp-core-lib
ksp-logging-lib

Non retenus nativement en V1 :

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 le 2026-08-18 :

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/3.0.0/
https://docs.rs/base64/0.23.1/
https://docs.rs/tempfile/3.27.0/

Les versions sont des constats d'audit du gate, pas des dépendances ajoutées par ce delta. Chaque tranche réaudite sa candidate 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 :

.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/admin/exporter via l'API
niveau B rejette toute mutation de l'état couvert sous la même autorité OWNER sans signature OWNER valide
VIEW connu ne permet ni changement de password/key slot ni swap de keypair sous l'autorité OWNER originale
limite substitution/rollback complet est documentée sans surpromesse
keypair V1 est immuable après création/import
rotation password 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é

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.002 crée seulement la foundation de ksp-wallet-lib : boundaries Cargo, types de capability/password/info, erreurs, façade logging et canaries architecturales. Le codec et la cryptographie du fichier restent à pre.003+.