1053 lines
42 KiB
Markdown
1053 lines
42 KiB
Markdown
<!-- file: docs/formats/KSPWALLET_V1.md -->
|
||
<!-- version: 14 -->
|
||
|
||
# `.kspwallet` V1 — spécification du format natif Wallet KSP
|
||
|
||
## 1. Statut et objectif
|
||
|
||
Ce document est l'autorité normative du **wire `.kspwallet` `format_version = 1`**.
|
||
|
||
`0.2.5-pre.003` fige :
|
||
|
||
```text
|
||
enveloppe JSON UTF-8 stricte
|
||
encodage Base64url sans padding
|
||
key slots OWNER / VIEW
|
||
limites structurelles V1
|
||
transcript binaire OWNER
|
||
AAD de wrapping OWNER / VIEW
|
||
AAD des compartiments owner-control / metadata / secret
|
||
règles unknown-field / unknown-version
|
||
```
|
||
|
||
`0.2.5-pre.004` ajoute les primitives KDF/AEAD normatives et un premier vecteur cryptographique public. `0.2.5-pre.005` fixe les payloads plaintext V1, l'autorité Ed25519 OWNER, les procédures de création et d'ouverture VIEW/OWNER, le profil de création KSP issu du benchmark opérateur et un vecteur `.kspwallet` complet généré indépendamment du code Rust. `0.2.5-pre.006` matérialise la persistence filesystem bornée et la création no-clobber. `0.2.5-pre.007` matérialise la signature Solana OWNER, l'administration des metadata, les rotations OWNER/VIEW, la révocation forte VIEW et leur remplacement filesystem capability-bound. `0.2.5-pre.008` matérialise les adapters Solana CLI JSON et Base58 complet, leur inspection sûre, l'import no-clobber vers un nouveau `.kspwallet` et l'export secret OWNER explicite. `pre.009` ferme l'audit adversarial/interoperability/compliance et `pre.010` synchronise la documentation de clôture sans modifier le wire ni les primitives. Après publication stable de V1, toute évolution qui modifie un élément déclaré **figé** par cette spécification doit être explicitement tracée ; une incompatibilité de wire exige un nouveau `format_version`.
|
||
|
||
Depuis `0.2.6-pre.015`, V1 reste explicitement supporté comme format historique stable tandis que V2 définit le nouveau wire binaire. Les APIs versionnées V1 sont conservées et les lectures génériques auto-détectent V1/V2 sans migration implicite.
|
||
|
||
Le but final est qu'une implémentation indépendante en Rust, Python, Go, C/C++, Java ou autre puisse créer, parser, vérifier et ouvrir un `.kspwallet` sans lire le code source de `ksp-wallet-lib`.
|
||
|
||
## 2. Modèle de confiance V1
|
||
|
||
V1 est **autonome**. Ouvrir un wallet ne requiert aucun :
|
||
|
||
```text
|
||
salt externe
|
||
pepper KSP
|
||
OTP
|
||
secret compilé dans KSP
|
||
service distant
|
||
réseau
|
||
keychain OS
|
||
fichier secret annexe
|
||
ancre de confiance externe
|
||
```
|
||
|
||
Tous les salts, nonces, paramètres KDF, wrapped keys, ciphertexts et éléments nécessaires à l'interprétation cryptographique sont dans le fichier.
|
||
|
||
Sans password OWNER, un détenteur du fichier ne doit pas obtenir la keypair Solana, signer, exporter le secret ni produire une modification OWNER-authentifiée des metadata. VIEW donne uniquement accès à Pubkey/alias/notes et à la rotation de **son propre password VIEW**.
|
||
|
||
Les ACL et permissions du système de fichiers sont hors du contrat cryptographique de `.kspwallet`. Remplacer entièrement un fichier par un autre wallet valide ne révèle pas l'ancien secret et revient à substituer une autre identité Wallet.
|
||
|
||
## 3. Encodage général
|
||
|
||
Le document est :
|
||
|
||
```text
|
||
JSON
|
||
UTF-8
|
||
objet top-level unique
|
||
maximum 1 048 576 octets
|
||
trailing whitespace JSON autorisé
|
||
trailing data non-whitespace interdit
|
||
```
|
||
|
||
L'ordre des propriétés JSON n'est **pas sémantique**. Une implémentation peut réordonner ou réindenter le JSON sans changer le transcript cryptographique tant que l'état sémantique parsé est identique.
|
||
|
||
Tous les champs binaires utilisent **Base64url RFC 4648 alphabet URL-safe, sans padding** :
|
||
|
||
```text
|
||
alphabet : A-Z a-z 0-9 - _
|
||
padding = interdit
|
||
trailing bits non canoniques = interdits
|
||
```
|
||
|
||
Le parsing normatif effectue conceptuellement :
|
||
|
||
```text
|
||
decode Base64url sans padding
|
||
-> re-encode Base64url sans padding
|
||
-> la chaîne obtenue doit être exactement égale à l'entrée
|
||
```
|
||
|
||
## 4. Magic et version
|
||
|
||
Valeurs V1 :
|
||
|
||
```text
|
||
magic = "KSPWALLET"
|
||
format_version = 1
|
||
```
|
||
|
||
Un `magic` différent est invalide.
|
||
|
||
Un `format_version` différent doit être signalé comme **version non supportée**, et non interprété avec la grammaire V1.
|
||
|
||
`format_version` ne change pas lors d'une rotation de password, d'une modification d'alias/note ou d'une réécriture atomique.
|
||
|
||
## 5. Enveloppe JSON exacte
|
||
|
||
La forme V1 est :
|
||
|
||
```json
|
||
{
|
||
"magic": "KSPWALLET",
|
||
"format_version": 1,
|
||
"owner_auth_public_key": "<32 octets Base64url>",
|
||
"view_descriptor": {
|
||
"enabled": true,
|
||
"slot_id": "<16 octets Base64url ou null>"
|
||
},
|
||
"key_slots": [
|
||
{
|
||
"slot_id": "<16 octets Base64url>",
|
||
"role": "owner",
|
||
"kdf": {
|
||
"algorithm": "argon2id",
|
||
"version": 19,
|
||
"memory_kib": 65536,
|
||
"iterations": 3,
|
||
"parallelism": 1,
|
||
"salt": "<16..64 octets Base64url>"
|
||
},
|
||
"wrap": {
|
||
"algorithm": "xchacha20-poly1305",
|
||
"nonce": "<24 octets Base64url>",
|
||
"ciphertext": "<Base64url>"
|
||
}
|
||
},
|
||
{
|
||
"slot_id": "<16 octets Base64url>",
|
||
"role": "view",
|
||
"kdf": {
|
||
"algorithm": "argon2id",
|
||
"version": 19,
|
||
"memory_kib": 32768,
|
||
"iterations": 4,
|
||
"parallelism": 1,
|
||
"salt": "<16..64 octets Base64url>"
|
||
},
|
||
"wrap": {
|
||
"algorithm": "xchacha20-poly1305",
|
||
"nonce": "<24 octets Base64url>",
|
||
"ciphertext": "<Base64url>"
|
||
}
|
||
}
|
||
],
|
||
"owner_control": {
|
||
"control_version": 1,
|
||
"algorithm": "xchacha20-poly1305",
|
||
"nonce": "<24 octets Base64url>",
|
||
"ciphertext": "<Base64url>"
|
||
},
|
||
"metadata": {
|
||
"metadata_version": 1,
|
||
"algorithm": "xchacha20-poly1305",
|
||
"nonce": "<24 octets Base64url>",
|
||
"ciphertext": "<Base64url>"
|
||
},
|
||
"secret": {
|
||
"secret_version": 1,
|
||
"algorithm": "xchacha20-poly1305",
|
||
"nonce": "<24 octets Base64url>",
|
||
"ciphertext": "<Base64url>"
|
||
},
|
||
"state_signature": {
|
||
"algorithm": "ed25519",
|
||
"signature": "<64 octets Base64url>"
|
||
}
|
||
}
|
||
```
|
||
|
||
Les valeurs Argon2 montrées dans l'exemple OWNER correspondent au profil de création KSP retenu en `pre.005`. L'exemple VIEW reste seulement illustratif : chaque key slot sérialise ses propres paramètres et toute combinaison respectant les bornes V1 reste lisible. Le profil par défaut KSP ne constitue donc pas une contrainte imposée aux implémentations externes conformes.
|
||
|
||
## 6. `owner_auth_public_key`
|
||
|
||
`owner_auth_public_key` contient exactement **32 octets** : la clé publique Ed25519 de l'autorité d'administration du format Wallet.
|
||
|
||
Elle est distincte de la keypair Solana. La Pubkey Solana reste dans le compartiment metadata chiffré et ne doit pas apparaître dans l'enveloppe verrouillée.
|
||
|
||
La clé privée correspondant à `owner_auth_public_key` appartient au matériau OWNER protégé ; elle n'est jamais un champ public du fichier.
|
||
|
||
## 7. Descripteur VIEW
|
||
|
||
`view_descriptor` est OWNER-authentifié.
|
||
|
||
Règles :
|
||
|
||
```text
|
||
enabled = true => slot_id contient exactement 16 octets et un unique slot role="view" possède le même slot_id
|
||
enabled = false => slot_id est null et aucun slot role="view" n'existe
|
||
```
|
||
|
||
Le `slot_id` VIEW est stable pendant une rotation self-service du password VIEW.
|
||
|
||
VIEW peut modifier uniquement les paramètres de protection de **son slot VIEW courant** dans les limites V1 et rewrapper la même capability metadata. VIEW ne peut pas modifier le descripteur signé, désactiver/recréer VIEW ou transformer son slot en OWNER.
|
||
|
||
## 8. Key slots
|
||
|
||
V1 accepte :
|
||
|
||
```text
|
||
exactement 1 slot role="owner"
|
||
0 ou 1 slot role="view"
|
||
maximum total = 2
|
||
slot_id unique entre les slots
|
||
```
|
||
|
||
L'ordre des entrées dans le tableau JSON `key_slots` n'est pas sémantique. Pour le transcript, OWNER est traité séparément et VIEW est représenté par son descripteur stable.
|
||
|
||
### 8.1 KDF
|
||
|
||
Identifiant V1 :
|
||
|
||
```text
|
||
algorithm = "argon2id"
|
||
version = 19
|
||
```
|
||
|
||
Bornes structurelles de parsing :
|
||
|
||
```text
|
||
memory_kib : 1 .. 1 048 576
|
||
iterations : 1 .. 64
|
||
parallelism : 1 .. 64
|
||
memory_kib >= 8 * parallelism
|
||
salt : 16 .. 64 octets
|
||
```
|
||
|
||
Ces plafonds sont des bornes de format/rejet hostile ; ils ne définissent pas à eux seuls les paramètres de création.
|
||
|
||
Le profil de création KSP V1 retenu en `0.2.5-pre.005` est :
|
||
|
||
```text
|
||
memory_kib = 65 536
|
||
iterations = 3
|
||
parallelism = 1
|
||
salt = 32 octets CSPRNG neufs par slot
|
||
```
|
||
|
||
Le benchmark opérateur du 2026-08-19, exécuté avec le test calibrateur livré par `pre.004`, a mesuré environ 1742 ms pour `64 MiB / 3 / 1`, 3459 ms pour `128 MiB / 3 / 1` et 6925 ms pour `256 MiB / 3 / 1`. Le premier candidat est retenu comme équilibre initial ; ces mesures caractérisent la machine/profil testés et ne sont pas une promesse de latence portable. Les paramètres étant sérialisés dans chaque slot, KSP pourra durcir les defaults futurs sans rendre les wallets existants illisibles.
|
||
|
||
Le password KDF est la séquence exacte des octets UTF-8 fournis, sans normalisation Unicode implicite, avec une longueur maximale de 1024 octets et un password vide refusé.
|
||
|
||
### 8.2 Wrapping
|
||
|
||
Identifiant V1 :
|
||
|
||
```text
|
||
algorithm = "xchacha20-poly1305"
|
||
nonce = 24 octets
|
||
ciphertext >= 16 octets
|
||
ciphertext <= 4096 octets
|
||
```
|
||
|
||
Le ciphertext contient le tag Poly1305 de 16 octets produit par l'AEAD.
|
||
|
||
Le plaintext wrappé est exactement **32 octets** :
|
||
|
||
```text
|
||
OWNER slot -> K_owner_root (32 octets)
|
||
VIEW slot -> K_metadata (32 octets)
|
||
```
|
||
|
||
OWNER et VIEW ont des KDF/salts/nonces indépendants. OWNER n'a jamais besoin du slot VIEW pour accéder à `K_metadata`, puisque cette clé est également contenue dans `owner_control` sous `K_owner_root`.
|
||
|
||
## 9. Compartiments chiffrés
|
||
|
||
Trois compartiments indépendants existent :
|
||
|
||
```text
|
||
owner_control
|
||
metadata
|
||
secret
|
||
```
|
||
|
||
Leurs versions initiales sont indépendantes :
|
||
|
||
```text
|
||
control_version = 1
|
||
metadata_version = 1
|
||
secret_version = 1
|
||
```
|
||
|
||
Les trois utilisent :
|
||
|
||
```text
|
||
algorithm = "xchacha20-poly1305"
|
||
nonce = 24 octets
|
||
```
|
||
|
||
Bornes ciphertext V1 :
|
||
|
||
```text
|
||
owner_control : 16 .. 4096 octets
|
||
metadata : 16 .. 65 552 octets
|
||
secret : 16 .. 4096 octets
|
||
```
|
||
|
||
La metadata plaintext V1 reste bornée à 65 536 octets. `pre.005` fixe les trois plaintexts :
|
||
|
||
### 9.1 `owner_control`
|
||
|
||
Plaintext binaire de **96 octets exacts**, dans cet ordre :
|
||
|
||
```text
|
||
offset 0..32 : admin_signing_secret Ed25519 (32 octets)
|
||
offset 32..64 : K_metadata (32 octets)
|
||
offset 64..96 : K_secret (32 octets)
|
||
```
|
||
|
||
`admin_signing_secret` est la seed privée Ed25519 correspondant à `owner_auth_public_key`. Cette autorité est distincte de la keypair Solana.
|
||
|
||
### 9.2 `metadata`
|
||
|
||
Plaintext JSON UTF-8, objet strict sans champs inconnus :
|
||
|
||
```json
|
||
{
|
||
"pubkey": "<Pubkey Solana Base58 canonique>",
|
||
"alias": "<string ou null>",
|
||
"notes": [
|
||
{"id": "<16 octets Base64url sans padding>", "text": "<UTF-8>"}
|
||
]
|
||
}
|
||
```
|
||
|
||
Contraintes :
|
||
|
||
```text
|
||
pubkey = Base58 canonique d'une Pubkey Solana 32 octets
|
||
alias = null ou <= 256 octets UTF-8
|
||
notes = maximum 64
|
||
note.id = exactement 16 octets, Base64url canonique sans padding, unique dans le payload
|
||
note.text = <= 8192 octets UTF-8
|
||
payload total <= 65 536 octets
|
||
```
|
||
|
||
L'ordre des propriétés JSON du plaintext metadata n'est pas cryptographiquement canonicalisé : l'AEAD protège les octets plaintext réellement choisis par le créateur, et la signature OWNER protège ensuite le ciphertext. Le serializer KSP émet un JSON compact dans l'ordre `pubkey`, `alias`, `notes`, puis `id`, `text` pour chaque note.
|
||
|
||
### 9.3 `secret`
|
||
|
||
Plaintext binaire de **64 octets exacts**, compatible avec la représentation Solana/Ed25519 standard :
|
||
|
||
```text
|
||
offset 0..32 : secret Ed25519 Solana
|
||
offset 32..64 : public Ed25519 Solana
|
||
```
|
||
|
||
À l'ouverture OWNER, l'implémentation doit valider que la moitié publique correspond au secret et que la Pubkey dérivée est exactement celle du payload metadata. Un mismatch est du key material invalide, jamais une nouvelle identité acceptée silencieusement.
|
||
|
||
## 10. Signature d'état OWNER
|
||
|
||
V1 :
|
||
|
||
```text
|
||
algorithm = "ed25519"
|
||
signature = 64 octets
|
||
```
|
||
|
||
La signature porte sur le **transcript sémantique OWNER-controlled**, jamais sur les octets JSON bruts.
|
||
|
||
`state_signature.signature` elle-même est exclue du transcript. L'identifiant `algorithm = "ed25519"` est inclus.
|
||
|
||
Les paramètres/salt/nonce/ciphertext du slot VIEW self-service sont exclus de la signature OWNER ; le descripteur stable VIEW est inclus.
|
||
|
||
La vérification Ed25519 de l'état OWNER-controlled doit précéder toute dérivation de password. `owner_auth_public_key` est parsée comme clé de vérification Ed25519 et la signature est vérifiée en mode strict sur le transcript exact de la section 12. Une mutation de metadata/secret/owner-control/slot OWNER sans clé privée d'administration est donc rejetée avant Argon2. Lors d'une ouverture OWNER, la seed d'administration déchiffrée depuis `owner_control` doit en plus redériver exactement `owner_auth_public_key`.
|
||
|
||
## 11. Codec binaire transcript/AAD
|
||
|
||
### 11.1 Préfixe de domaine
|
||
|
||
Chaque transcript/AAD commence par :
|
||
|
||
```text
|
||
ASCII(domain_separator) || 0x00
|
||
```
|
||
|
||
### 11.2 Champ TLV
|
||
|
||
Chaque champ suivant utilise exactement :
|
||
|
||
```text
|
||
tag : u16 big-endian
|
||
length : u64 big-endian
|
||
value : `length` octets
|
||
```
|
||
|
||
Un entier `u32` est encodé dans `value` comme **4 octets big-endian**.
|
||
|
||
Un booléen est encodé comme un octet :
|
||
|
||
```text
|
||
false = 0x00
|
||
true = 0x01
|
||
```
|
||
|
||
Les champs binaires sont les **octets décodés** du Base64url, jamais le texte Base64url.
|
||
|
||
### 11.3 Tags V1
|
||
|
||
| Tag hex | Champ |
|
||
|--------:|----------------------------------------------------|
|
||
| `0001` | magic |
|
||
| `0002` | format_version |
|
||
| `0003` | owner_auth_public_key |
|
||
| `0010` | view enabled |
|
||
| `0011` | rôle VIEW littéral `view` |
|
||
| `0012` | view slot_id ; longueur zéro lorsque VIEW disabled |
|
||
| `0100` | slot_id |
|
||
| `0101` | slot role |
|
||
| `0102` | KDF algorithm |
|
||
| `0103` | KDF version |
|
||
| `0104` | KDF memory_kib |
|
||
| `0105` | KDF iterations |
|
||
| `0106` | KDF parallelism |
|
||
| `0107` | KDF salt |
|
||
| `0108` | wrap algorithm |
|
||
| `0109` | wrap nonce |
|
||
| `010A` | wrap ciphertext |
|
||
| `0200` | compartment kind |
|
||
| `0201` | compartment payload version |
|
||
| `0202` | compartment algorithm |
|
||
| `0203` | compartment nonce |
|
||
| `0204` | compartment ciphertext |
|
||
| `0500` | state-signature algorithm |
|
||
|
||
Les tags ne remplacent pas l'ordre normatif ; **l'ordre ci-dessous est obligatoire**.
|
||
|
||
## 12. Transcript OWNER state signature
|
||
|
||
Domain separator :
|
||
|
||
```text
|
||
KSPWALLET-V1-STATE
|
||
```
|
||
|
||
Ordre exact :
|
||
|
||
```text
|
||
0001 magic = ASCII "KSPWALLET"
|
||
0002 format_version = u32 BE 1
|
||
0003 owner_auth_public_key = 32 octets
|
||
|
||
0010 view enabled = 00/01
|
||
0011 ASCII "view"
|
||
0012 view slot_id = 16 octets si enabled, longueur 0 sinon
|
||
|
||
OWNER slot uniquement :
|
||
0100 owner slot_id
|
||
0101 ASCII "owner"
|
||
0102 ASCII "argon2id"
|
||
0103 Argon2 version u32 BE
|
||
0104 memory_kib u32 BE
|
||
0105 iterations u32 BE
|
||
0106 parallelism u32 BE
|
||
0107 owner salt
|
||
0108 ASCII "xchacha20-poly1305"
|
||
0109 owner wrap nonce
|
||
010A owner wrap ciphertext
|
||
|
||
owner_control :
|
||
0200 ASCII "owner-control"
|
||
0201 control_version u32 BE
|
||
0202 ASCII "xchacha20-poly1305"
|
||
0203 nonce
|
||
0204 ciphertext
|
||
|
||
metadata :
|
||
0200 ASCII "metadata"
|
||
0201 metadata_version u32 BE
|
||
0202 ASCII "xchacha20-poly1305"
|
||
0203 nonce
|
||
0204 ciphertext
|
||
|
||
secret :
|
||
0200 ASCII "secret"
|
||
0201 secret_version u32 BE
|
||
0202 ASCII "xchacha20-poly1305"
|
||
0203 nonce
|
||
0204 ciphertext
|
||
|
||
0500 ASCII "ed25519"
|
||
```
|
||
|
||
Sont explicitement **exclus** du transcript OWNER :
|
||
|
||
```text
|
||
VIEW KDF parameters
|
||
VIEW salt
|
||
VIEW wrap nonce
|
||
VIEW wrap ciphertext
|
||
state_signature.signature
|
||
```
|
||
|
||
Cette exclusion autorise VIEW à changer son propre password en rewrappant sa capability sans posséder l'autorité OWNER. Elle ne lui permet pas de modifier metadata/secret/OWNER state, qui restent signés.
|
||
|
||
## 13. AAD des key slots
|
||
|
||
### 13.1 OWNER
|
||
|
||
Domain separator :
|
||
|
||
```text
|
||
KSPWALLET-V1-AAD-OWNER-SLOT
|
||
```
|
||
|
||
Ordre exact :
|
||
|
||
```text
|
||
0001 magic
|
||
0002 format_version
|
||
0003 owner_auth_public_key
|
||
0100 slot_id
|
||
0101 "owner"
|
||
0102 "argon2id"
|
||
0103 KDF version
|
||
0104 memory_kib
|
||
0105 iterations
|
||
0106 parallelism
|
||
0107 salt
|
||
0108 "xchacha20-poly1305"
|
||
```
|
||
|
||
Le nonce est passé séparément à l'AEAD et n'est pas répété dans l'AAD. Le ciphertext/tag est le résultat AEAD et n'appartient pas à son propre AAD.
|
||
|
||
### 13.2 VIEW
|
||
|
||
Domain separator :
|
||
|
||
```text
|
||
KSPWALLET-V1-AAD-VIEW-SLOT
|
||
```
|
||
|
||
La suite TLV est identique à OWNER sauf :
|
||
|
||
```text
|
||
0101 = ASCII "view"
|
||
0100 = slot_id VIEW signé par view_descriptor
|
||
```
|
||
|
||
Les paramètres KDF/salt sont volontairement dans l'AAD calculé pour **l'état courant** du slot VIEW. VIEW peut les remplacer lors d'une rotation de son password, puis produire un nouveau wrapping valide de la même capability metadata.
|
||
|
||
## 14. AAD des compartiments
|
||
|
||
Les trois AAD commencent par les champs communs :
|
||
|
||
```text
|
||
0001 magic
|
||
0002 format_version
|
||
0003 owner_auth_public_key
|
||
```
|
||
|
||
Puis :
|
||
|
||
```text
|
||
0200 compartment kind
|
||
0201 compartment payload version
|
||
0202 "xchacha20-poly1305"
|
||
```
|
||
|
||
Domain separators :
|
||
|
||
```text
|
||
owner_control : KSPWALLET-V1-AAD-OWNER-CONTROL
|
||
metadata : KSPWALLET-V1-AAD-METADATA
|
||
secret : KSPWALLET-V1-AAD-SECRET
|
||
```
|
||
|
||
Valeurs de `compartment kind` :
|
||
|
||
```text
|
||
owner-control
|
||
metadata
|
||
secret
|
||
```
|
||
|
||
Le nonce est fourni séparément à l'AEAD et le ciphertext/tag est le résultat de l'opération ; ni l'un ni l'autre n'est dupliqué dans cet AAD.
|
||
|
||
## 15. Parsing et rejet stricts
|
||
|
||
V1 rejette :
|
||
|
||
```text
|
||
magic inconnu
|
||
champ top-level inconnu
|
||
champ nested inconnu
|
||
champ requis absent
|
||
champ dupliqué JSON
|
||
role slot autre que owner/view
|
||
algorithme autre que les identifiants V1
|
||
Argon2 version autre que 19
|
||
KDF hors bornes structurelles
|
||
Base64url invalide, paddé ou non canonique
|
||
owner_auth_public_key != 32 octets
|
||
slot_id != 16 octets
|
||
XChaCha nonce != 24 octets
|
||
state signature != 64 octets
|
||
OWNER slot absent ou dupliqué
|
||
VIEW slot dupliqué
|
||
slot_id OWNER == slot_id VIEW
|
||
VIEW descriptor incohérent avec VIEW slot
|
||
payload version autre que 1
|
||
ciphertext sous 16 octets ou au-dessus de sa borne V1
|
||
document > 1 MiB
|
||
trailing data non-whitespace
|
||
```
|
||
|
||
La vérification de taille du document précède le parsing JSON et toute opération KDF coûteuse.
|
||
|
||
## 16. JSON produit par KSP
|
||
|
||
Le serializer KSP V1 émet actuellement :
|
||
|
||
```text
|
||
JSON pretty-print
|
||
ordre stable des champs du modèle KSP
|
||
OWNER slot avant VIEW slot
|
||
newline final
|
||
```
|
||
|
||
Cet ordre et ce pretty-print sont un **profil de sortie KSP**, pas une canonicalisation cryptographique. Une implémentation externe conforme peut produire un autre ordre/espacement JSON si le parsing V1 aboutit au même état sémantique.
|
||
|
||
## 17. Checksum
|
||
|
||
Aucun checksum supplémentaire V1.
|
||
|
||
La détection cryptographique repose sur :
|
||
|
||
```text
|
||
AEAD des wrapped keys/compartiments
|
||
state_signature OWNER pour l'état OWNER-controlled
|
||
AAD domain-separated
|
||
```
|
||
|
||
Un checksum non authentifié n'ajouterait pas de garantie de sécurité utile.
|
||
|
||
## 18. Fixture structurelle `pre.003`
|
||
|
||
Le dépôt contient :
|
||
|
||
```text
|
||
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_wire_only.json
|
||
```
|
||
|
||
Cette fixture est **wire-only et test-only** :
|
||
|
||
```text
|
||
ses ciphertexts sont des octets artificiels
|
||
sa signature est artificielle
|
||
elle ne constitue pas un wallet cryptographiquement valide
|
||
elle ne fixe pas les defaults Argon2 de production
|
||
elle ne contient aucune clé réelle
|
||
```
|
||
|
||
Elle fige néanmoins :
|
||
|
||
```text
|
||
JSON V1
|
||
Base64url
|
||
longueurs
|
||
slots/descripteur
|
||
transcript OWNER exact
|
||
AAD exacts
|
||
round-trip du codec
|
||
```
|
||
|
||
Le premier vecteur cryptographique public KDF+wrapping est ajouté par `pre.004`. `pre.005` ajoute en plus un `.kspwallet` complet cryptographiquement valide et ses métadonnées de contrôle test-only, décrits en section 22.
|
||
|
||
## 19. État de matérialisation avant publication stable
|
||
|
||
Toutes les opérations V1 prévues par `0.2.5` sont matérialisées et auditées :
|
||
|
||
```text
|
||
pre.006 : persistence async/atomique/no-clobber acquis
|
||
pre.007 : signature Solana, metadata admin, rotations et révocation acquis
|
||
pre.008 : import/export Solana CLI JSON + Base58 complet acquis
|
||
pre.009 : audit adversarial/interoperability/compliance acquis
|
||
pre.010 : README/USAGE/spec/graphes/matrice de clôture acquis
|
||
```
|
||
|
||
`pre.010` ne modifie ni la grammaire, ni les payloads plaintext, ni les tags, ni l'ordre du transcript, ni les domain separators. Toute découverte imposant un tel changement après publication stable devra passer par une évolution explicitement versionnée ; une incompatibilité de wire V1 ne peut pas être introduite silencieusement sous `format_version = 1`.
|
||
|
||
## 20. Primitives cryptographiques effectives depuis `pre.004`
|
||
|
||
### 20.1 Argon2id
|
||
|
||
La dérivation d'une key-encryption key (KEK) V1 utilise exactement :
|
||
|
||
```text
|
||
algorithm = Argon2id
|
||
version = 19 / 0x13
|
||
output = 32 octets
|
||
password = octets UTF-8 exacts fournis par le caller
|
||
salt = octets sérialisés dans le key slot
|
||
m_cost = memory_kib sérialisé
|
||
t_cost = iterations sérialisé
|
||
p_cost = parallelism sérialisé
|
||
pepper = aucun
|
||
secret Argon2 externe = aucun
|
||
```
|
||
|
||
Un password vide est rejeté. V1 limite l'entrée password à 1024 octets UTF-8. Le KDF est une opération CPU/mémoire coûteuse ; les API async create/open l'exécutent hors du thread executor via la boundary blocking documentée par Wallet.
|
||
|
||
Le default de création n'est **pas** déterminé par les defaults de la crate RustCrypto. Le benchmark opérateur a comparé :
|
||
|
||
```text
|
||
64 MiB / 3 passes / 1 lane -> 1742 ms
|
||
128 MiB / 3 passes / 1 lane -> 3459 ms
|
||
256 MiB / 3 passes / 1 lane -> 6925 ms
|
||
```
|
||
|
||
KSP retient donc initialement `64 MiB / 3 / 1` avec un salt CSPRNG de 32 octets par slot. Un wallet conserve toujours ses propres paramètres sérialisés, indépendamment des defaults futurs.
|
||
|
||
### 20.2 XChaCha20-Poly1305
|
||
|
||
V1 utilise une clé de **32 octets** et un nonce de **24 octets**. Le résultat `ciphertext` stocké est la sortie AEAD avec tag Poly1305 postfixé de 16 octets. Chaque chiffrement/wrapping de production reçoit un nonce neuf fourni par le CSPRNG OS. Les AAD sont les TLV domain-separated définis dans cette spécification ; ils ne sont ni secrets ni chiffrés.
|
||
|
||
Une erreur de déchiffrement/tag est exposée comme une erreur générique d'authentification Wallet et ne distingue pas ciphertext, key, nonce ou AAD incorrect.
|
||
|
||
### 20.3 CSPRNG
|
||
|
||
KSP utilise la source cryptographique du système d'exploitation via `getrandom`. V1 ne définit aucun PRNG, seed ou état aléatoire propriétaire KSP. La génération de content keys, salts et nonces de production doit échouer si le CSPRNG OS échoue.
|
||
|
||
### 20.4 Content keys et wrapping
|
||
|
||
Les content keys V1 manipulées par les primitives de `pre.004` sont des clés symétriques de 32 octets :
|
||
|
||
```text
|
||
OWNER password -> Argon2id -> KEK_OWNER -> XChaCha wrap -> K_owner_root
|
||
VIEW password -> Argon2id -> KEK_VIEW -> XChaCha wrap -> K_metadata
|
||
```
|
||
|
||
Les clés possédées en mémoire sont non-`Copy`, non-`Clone` par défaut, redacted en `Debug` et zeroized au `Drop`. Lors d'un unwrap, le plaintext intermédiaire est zeroized après copie dans le type secret possédé.
|
||
|
||
### 20.5 Vecteur interopérable `pre.004`
|
||
|
||
Le fichier public :
|
||
|
||
```text
|
||
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_crypto_vectors.json
|
||
```
|
||
|
||
contient un password, un salt, une content key et un nonce **test-only publics**. Il fixe :
|
||
|
||
```text
|
||
Argon2id -> derived_key attendu
|
||
XChaCha20-Poly1305(derived_key, nonce, AAD, content_key) -> wrapped_key attendu
|
||
```
|
||
|
||
Le vecteur a été recalculé indépendamment de l'implémentation Rust avec Argon2id puis la construction XChaCha20-Poly1305 `HChaCha20 + ChaCha20-Poly1305 IETF`. Ces valeurs ne constituent jamais des secrets de production.
|
||
|
||
## 21. Procédures V1 matérialisées par `pre.005`
|
||
|
||
### 21.1 Création
|
||
|
||
Une création KSP V1 en mémoire suit conceptuellement :
|
||
|
||
```text
|
||
1. générer K_owner_root, K_metadata et K_secret indépendants (32 octets chacun) ;
|
||
2. générer une seed Ed25519 d'administration de format indépendante de la keypair Solana ;
|
||
3. générer une nouvelle keypair Solana Ed25519 et dériver la Pubkey metadata ;
|
||
4. encoder owner_control, metadata et secret selon la section 9 ;
|
||
5. générer slot_id/salt/nonce OWNER et, si demandé, slot_id/salt/nonce VIEW indépendants ;
|
||
6. Argon2id(password OWNER) -> KEK_OWNER -> wrap K_owner_root ;
|
||
7. si VIEW existe : Argon2id(password VIEW) -> KEK_VIEW -> wrap K_metadata ;
|
||
8. chiffrer owner_control avec K_owner_root, metadata avec K_metadata, secret avec K_secret ;
|
||
9. construire le transcript OWNER et le signer avec l'autorité Ed25519 de format ;
|
||
10. vérifier immédiatement la signature produite avant de retourner le handle OWNER.
|
||
```
|
||
|
||
Les opérations Argon2id sont exécutées hors du thread executor async par une frontière blocking dédiée. La construction cryptographique reste entièrement en mémoire ; `pre.006` ajoute ensuite la publication filesystem no-clobber décrite en section 23.
|
||
|
||
### 21.2 Ouverture VIEW
|
||
|
||
```text
|
||
1. parser/rejeter strictement l'enveloppe ;
|
||
2. vérifier la state signature OWNER avant tout KDF ;
|
||
3. exiger un slot VIEW cohérent avec view_descriptor ;
|
||
4. Argon2id(password VIEW, paramètres du slot VIEW) ;
|
||
5. unwrap K_metadata avec l'AAD VIEW courant ;
|
||
6. déchiffrer uniquement metadata ;
|
||
7. valider le payload metadata ;
|
||
8. retourner Pubkey/alias/notes + capability VIEW.
|
||
```
|
||
|
||
Une ouverture VIEW ne déchiffre **jamais** `owner_control` ni `secret` et ne matérialise ni `K_owner_root`, ni `K_secret`, ni la keypair Solana, ni la seed Ed25519 d'administration.
|
||
|
||
### 21.3 Ouverture OWNER
|
||
|
||
```text
|
||
1. parser/rejeter strictement l'enveloppe ;
|
||
2. vérifier la state signature OWNER avant tout KDF ;
|
||
3. Argon2id(password OWNER, paramètres slot OWNER) ;
|
||
4. unwrap K_owner_root ;
|
||
5. déchiffrer owner_control et récupérer admin_signing_secret, K_metadata, K_secret ;
|
||
6. vérifier que admin_signing_secret redérive owner_auth_public_key ;
|
||
7. déchiffrer/valider metadata ;
|
||
8. déchiffrer secret, reconstruire strictement la keypair Solana 64 octets ;
|
||
9. vérifier cohérence secret/public et égalité avec la Pubkey metadata ;
|
||
10. retourner capability OWNER en conservant les secrets seulement dans l'état OWNER opaque.
|
||
```
|
||
|
||
Aucun getter public de secret n'est introduit par cette procédure.
|
||
|
||
### 21.4 Signature Solana OWNER
|
||
|
||
La signature de message est une capacité OWNER. L'implémentation reconstruit/utilise la keypair Solana déjà authentifiée lors de l'ouverture OWNER et produit une signature Ed25519 de **64 octets** sur les octets exacts du message fourni :
|
||
|
||
```text
|
||
OWNER + message bytes -> Ed25519(Solana keypair) -> signature[64]
|
||
```
|
||
|
||
VIEW ne possède aucune primitive de signature. La surface publique n'expose ni getter de seed, ni getter de keypair brute, ni type Solana secret supplémentaire. Une rotation de password ou une mutation metadata ne change pas la keypair Solana ; signer le même message avec le même wallet produit donc la même signature Ed25519 déterministe.
|
||
|
||
### 21.5 Administration OWNER des metadata
|
||
|
||
OWNER peut modifier l'alias et les notes protégées. La Pubkey metadata reste **immutable** en V1. Une mutation metadata suit conceptuellement :
|
||
|
||
```text
|
||
metadata authentifiées courantes
|
||
-> mutation alias/note validée
|
||
-> nouveau nonce metadata
|
||
-> rechiffrement avec le même K_metadata
|
||
-> reconstruction du transcript OWNER
|
||
-> nouvelle state_signature OWNER
|
||
```
|
||
|
||
Les notes conservent des identifiants 16 octets uniques. Une mise à jour ou suppression visant un identifiant absent retourne `wallet.note_not_found`. VIEW peut lire le nouvel état après réouverture mais ne dispose d'aucune API pour produire cette mutation.
|
||
|
||
### 21.6 Rotation simple du password VIEW
|
||
|
||
VIEW peut changer **uniquement son propre password VIEW**. OWNER peut effectuer la même rotation sans connaître l'ancien password VIEW. La rotation simple conserve :
|
||
|
||
```text
|
||
slot_id VIEW
|
||
K_metadata
|
||
metadata ciphertext
|
||
owner_control
|
||
secret
|
||
keypair Solana
|
||
state_signature OWNER
|
||
```
|
||
|
||
Elle génère de nouveaux paramètres de credential autorisés pour le slot VIEW : au minimum salt, KDF de création courant, wrap nonce et wrapped key. Le wrapping réencapsule le **même `K_metadata`** sous le nouveau password. La `state_signature` reste inchangée parce que les champs self-service rotatables du slot VIEW sont explicitement exclus du transcript OWNER V1, alors que son descripteur stable reste OWNER-signed.
|
||
|
||
Après publication réussie, l'ancien password n'ouvre plus le slot VIEW **courant**. Cette opération est une rotation de credential et non une révocation forte : un détenteur ayant conservé une ancienne copie du fichier et/ou `K_metadata` peut toujours lire l'état correspondant à cette ancienne copie.
|
||
|
||
### 21.7 Rotation du password OWNER
|
||
|
||
OWNER peut changer son password sans changer l'identité Solana. La rotation conserve `slot_id OWNER` et `K_owner_root`, génère de nouveaux KDF/salt/wrap nonce, rewrappe `K_owner_root`, reconstruit le transcript et produit une nouvelle `state_signature` avec la même autorité administrative.
|
||
|
||
Elle ne modifie ni `K_metadata`, ni `K_secret`, ni la keypair Solana, ni la Pubkey. Après publication réussie, l'ancien password OWNER n'ouvre plus le slot OWNER courant.
|
||
|
||
### 21.8 Disable/recreate VIEW et révocation forte
|
||
|
||
`disable_view` et `recreate_view` sont OWNER-only et constituent la primitive de révocation forte V1 pour les **futures metadata sous l'état courant**. OWNER génère un nouveau `K_metadata`, rechiffre les metadata, met à jour `owner_control`, puis :
|
||
|
||
```text
|
||
disable_view -> aucun slot VIEW + view_descriptor disabled
|
||
recreate_view -> nouveau slot_id VIEW + nouveau password VIEW + view_descriptor enabled
|
||
```
|
||
|
||
Le nouvel état est resigné par OWNER. L'ancien password VIEW ne peut pas ouvrir le nouvel état, même s'il connaissait le wrapping précédent. Cette garantie n'efface pas les anciennes copies déjà possédées : V1 ne fournit ni anti-rollback externe ni ancre de confiance externe.
|
||
|
||
### 21.9 Remplacement persistant capability-bound
|
||
|
||
Les mutations `pre.007` ne publient aucun `replace(path, bytes)` générique. Elles partent d'un handle VIEW/OWNER déjà authentifié, construisent le nouvel état en mémoire, puis demandent à la persistence de remplacer **le chemin explicitement fourni par le caller**.
|
||
|
||
Avant publication, la persistence relit le fichier destination, le parse strictement, vérifie sa `state_signature` OWNER et compare son enveloppe sémantique à l'état authentifié attendu par le handle. Un mauvais chemin ou un handle devenu stale retourne `wallet.state_conflict` au lieu d'écraser silencieusement un autre état. Le contrôle est répété juste avant la publication du temp file.
|
||
|
||
```text
|
||
expected authenticated envelope
|
||
-> bounded read current destination
|
||
-> strict parse + OWNER state signature verify
|
||
-> semantic equality check
|
||
-> temp same-directory + write + sync_all
|
||
-> second expected-state check
|
||
-> atomic/best-effort platform replace
|
||
-> sync published file
|
||
-> parent sync best-effort on Unix
|
||
-> commit du nouvel état dans le handle seulement après succès
|
||
```
|
||
|
||
Cette vérification protège les erreurs de cible et les mises à jour stale dans l'API KSP, mais **ne constitue pas un compare-and-swap filesystem portable et linéarisable** : il subsiste une fenêtre TOCTOU entre la dernière vérification et le remplacement sur les plateformes/filesystems sans primitive transactionnelle correspondante. Les ACL, verrous interprocess externes, anti-rollback et remplacement total hostile du fichier restent hors du contrat V1.
|
||
|
||
## 22. Vecteur complet interopérable `pre.005`
|
||
|
||
Le dépôt publie :
|
||
|
||
```text
|
||
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json
|
||
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector_meta.json
|
||
```
|
||
|
||
Ces fichiers sont **TEST ONLY**. Ils publient volontairement passwords, secrets, clés et résultats attendus et ne doivent jamais servir de wallet réel ni d'exemple de paramètres sécurisés. Pour garder les tests rapides, leurs key slots utilisent `32 KiB / 2 / 1`, très en dessous du profil de création KSP.
|
||
|
||
Le vector fixe notamment :
|
||
|
||
```text
|
||
password OWNER = pre005-owner-password
|
||
password VIEW = pre005-view-password
|
||
Pubkey attendue = 8zH45w576QJUEGtpXqZvEi6UPddmMfKopLatocZGDw6
|
||
alias attendu = pre005-vector-wallet
|
||
2 notes test-only
|
||
owner/view derived keys
|
||
autorité Ed25519
|
||
K_owner_root / K_metadata / K_secret
|
||
owner-control plaintext
|
||
metadata plaintext
|
||
keypair Solana 64 octets
|
||
state transcript exact
|
||
state signature exacte
|
||
wallet JSON complet
|
||
```
|
||
|
||
Le vecteur a été généré et revérifié indépendamment du code Rust avec Argon2id, XChaCha20-Poly1305 construit via HChaCha20 + ChaCha20-Poly1305 IETF et Ed25519. Une implémentation externe conforme doit pouvoir reproduire les mêmes dérivations, déchiffrements et vérifications à partir des deux fichiers sans dépendre d'un type Rust KSP.
|
||
|
||
## 23. Persistence native matérialisée par `pre.006`
|
||
|
||
La persistence ne modifie aucun octet du wire V1. Elle définit uniquement comment un document V1 complet est publié et relu :
|
||
|
||
```text
|
||
caller path explicite
|
||
-> temp file unique dans le même répertoire
|
||
-> écriture complète des bytes JSON verrouillés
|
||
-> sync_all(temp)
|
||
-> persist_noclobber(destination)
|
||
-> sync_all(fichier publié)
|
||
-> sync parent directory best-effort sur Unix
|
||
```
|
||
|
||
`create_wallet_file_v1` n'écrase jamais une destination existante. Une collision ou une course concurrente retourne `wallet.destination_exists`; il n'existe aucun mode overwrite pour create/import. Les ouvertures fichier sont bornées à `KSPWALLET_MAX_FILE_BYTES` avant parser/KDF et délèguent ensuite exactement aux procédures VIEW/OWNER des sections 21.2/21.3.
|
||
|
||
La couche filesystem est async-first, mais les appels OS bloquants sont regroupés derrière `tokio::task::spawn_blocking`. KSP ne lit ni Config ni environnement pour choisir le chemin et ne journalise pas les chemins par défaut. Les mutations `pre.007` réutilisent la même stratégie temp/sync pour un remplacement **capability-bound** avec contrôle d'état attendu décrit en section 21.9 ; ce remplacement n'est jamais exposé comme primitive publique générique et ne modifie pas la règle create/import = no-clobber.
|
||
|
||
La durabilité est décrite sans surpromesse : `persist_noclobber` ne garantit pas une atomicité universelle sur tous les filesystems et un crash brutal peut laisser un artefact ou hard-link temporaire complet. KSP garantit le no-clobber et l'absence de publication partielle dans le flux normal et dans les fault tests avant publication ; il ne garantit pas la suppression des temporaires après kill/power-loss ni une durabilité identique de l'entrée de répertoire sur tous les OS. Ces limites concernent la persistence et ne changent pas les garanties cryptographiques du format.
|
||
|
||
## 24. Adapters de transfert matérialisés par `pre.008`
|
||
|
||
Les formats de transfert ne modifient pas le wire `.kspwallet` V1. Ils servent uniquement à importer/exporter la keypair Solana immuable de 64 octets. Deux formats sont retenus immédiatement :
|
||
|
||
```text
|
||
solana_cli_json JSON array de exactement 64 entiers u8
|
||
solana_keypair_base58 Base58 canonique de la keypair complète 64 octets
|
||
```
|
||
|
||
### 24.1 Solana CLI JSON
|
||
|
||
Le format correspond au codec `solana-keypair` actuel : un tableau JSON contenant exactement les 64 octets de la keypair. KSP accepte les espaces JSON usuels mais exige exactement 64 valeurs `0..255`, puis reconstruit la keypair avec validation de cohérence secret/public. La taille du source est bornée à **1024 octets** avant parsing.
|
||
|
||
### 24.2 Base58 de keypair complète
|
||
|
||
Le format Base58 encode les **64 octets complets** de la keypair, et non seulement le secret seed de 32 octets. KSP utilise le codec maintenu par `solana-keypair`, refuse le whitespace périphérique et impose `decode -> re-encode == input` afin de n'accepter que la représentation canonique. La taille du source est bornée à **128 octets** avant décodage. Aucun `bs58` direct n'est ajouté à Wallet.
|
||
|
||
### 24.3 Inspection sûre
|
||
|
||
`inspect_wallet_transfer` et `inspect_wallet_transfer_file` exigent que le caller choisisse explicitement le format. KSP ne fait pas d'auto-détection heuristique. Après validation complète de la keypair, l'inspection expose seulement :
|
||
|
||
```text
|
||
Pubkey Solana via ksp_core_lib::Pubkey
|
||
WalletTransferFormat
|
||
```
|
||
|
||
Aucun secret, seed, keypair brute, alias ou note n'est projeté.
|
||
|
||
### 24.4 Import vers `.kspwallet`
|
||
|
||
Un import valide :
|
||
|
||
```text
|
||
source transfer validée
|
||
-> même keypair Solana 64 octets
|
||
-> nouvelles clés OWNER/admin/content KSP
|
||
-> nouveaux slots OWNER/VIEW selon passwords fournis
|
||
-> nouvelles metadata KSP fournies par le caller
|
||
-> nouveau document .kspwallet V1
|
||
-> publication no-clobber
|
||
```
|
||
|
||
La source n'est jamais modifiée. Une destination `.kspwallet` existante retourne `wallet.destination_exists`. L'import n'est donc jamais une mutation ou un remplacement de keypair d'un wallet V1 existant.
|
||
|
||
### 24.5 Export OWNER
|
||
|
||
Seul `WalletOwner` expose l'export. `WalletView` ne possède aucune API correspondante. Deux formes existent :
|
||
|
||
```text
|
||
export_transfer(format)
|
||
export_transfer_file(destination, format)
|
||
```
|
||
|
||
La première retourne au caller des octets contenant volontairement le secret et exige donc que le caller en limite la durée de vie et les zeroize lorsque pertinent. La seconde publie un nouveau fichier en no-clobber ; sur Unix, KSP tente `0600` comme hygiène filesystem, sans transformer cette permission en garantie cryptographique du format. Aucun chemin n'est découvert via Config ou environnement.
|
||
|
||
Les exports Base58 et Solana CLI JSON doivent reconstruire exactement la même keypair et la même Pubkey que le wallet OWNER source.
|
||
|
||
## 25. Audit security/interoperability `pre.009`
|
||
|
||
`0.2.5-pre.009` n'ajoute aucun octet au wire V1 et ne change aucun algorithme. La tranche ferme le gate technique par des canaris adversariaux, une reproduction externe des vecteurs et un audit du graphe Cargo.
|
||
|
||
Les canaris exécutables vérifient notamment :
|
||
|
||
```text
|
||
tampering canonique du slot OWNER / owner-control / metadata / secret / state_signature
|
||
-> wallet.authentication_failed avant unlock
|
||
|
||
state_signature invalide + password vide
|
||
-> wallet.authentication_failed avant Argon2
|
||
|
||
changement du ciphertext de wrapping VIEW uniquement
|
||
-> signature OWNER toujours valide
|
||
-> OWNER reste ouvrable
|
||
-> VIEW échoue avec wallet.view_unlock_failed
|
||
|
||
wrong OWNER / wrong VIEW
|
||
-> erreurs génériques dédiées
|
||
-> aucun password reflété dans Display/Debug
|
||
|
||
public API
|
||
-> aucun re-export solana-keypair / solana-signer / solana-signature
|
||
-> aucune surface sign/export sur WalletView
|
||
```
|
||
|
||
Le vecteur complet publié en section 22 a été reproduit indépendamment du code Rust KSP avec une sonde externe :
|
||
|
||
```text
|
||
Argon2id v19 dérivations OWNER et VIEW exactes
|
||
XChaCha20-Poly1305 vecteur de wrapping pre.004 exact
|
||
Ed25519 state_signature exacte vérifiée sur state_transcript
|
||
Base58 keypair 64 octets et Pubkey attendue reproduites
|
||
```
|
||
|
||
Cette sonde n'est pas une dépendance KSP et n'est pas requise au runtime ; elle sert uniquement de preuve d'interopérabilité indépendante.
|
||
|
||
Le profil de création `64 MiB / 3 / 1` est cohérent avec la seconde recommandation Argon2id de RFC 9106 pour les environnements contraints. Les paramètres restent sérialisés par slot afin que les futurs defaults puissent évoluer sans rendre les wallets existants illisibles. XChaCha20-Poly1305 conserve une clé 256 bits et un nonce 192 bits généré par le CSPRNG OS. Les signatures d'état utilisent Ed25519 au format 64 octets défini par RFC 8032.
|
||
|
||
Le graphe Cargo observé au gate historique `pre.009` conservait une seule génération `ed25519-dalek 2.2.0`. `pre.010-fix.001` met à niveau la dépendance directe KSP vers `ed25519-dalek 3.0.0`; `solana-keypair 3.1.2` restant sur Dalek `2.x`, le graphe final observé contient donc **deux générations Dalek intentionnelles** (`3.0.0` direct KSP et `2.2.0` transitive Solana), tout en conservant une seule `solana-address 2.7.0` et un unique parent KSP direct de `solana-keypair` : `ksp-wallet-lib`. Les doublons `digest 0.10/0.11`, `crypto-common 0.1/0.2`, `block-buffer 0.10/0.12`, `cpufeatures 0.2/0.3`, `getrandom 0.3/0.4`, `rand 0.9/0.10`, `rand_core 0.6/0.9/0.10`, `sha2 0.10/0.11` et `syn 2/3` restent des conséquences transitives des générations RustCrypto/Solana/Logging.
|
||
|
||
Cette évolution de dépendance d'implémentation **ne modifie aucun octet du format V1**, aucun domaine de transcript/AAD, aucune taille de clé/signature ni aucun algorithme sérialisé : Ed25519 reste Ed25519 et les fixtures/vecteurs V1 restent les contrats d'interopérabilité.
|
||
|
||
Limites explicitement conservées en V1 :
|
||
|
||
- aucun anti-rollback ni détection d'un remplacement intégral par un autre wallet valide sans ancre externe ;
|
||
- aucune promesse de CAS filesystem linéarisable ; l'atomicité réelle dépend de l'OS/filesystem ;
|
||
- les permissions `0600` d'export Unix sont une hygiène, pas une garantie cryptographique ;
|
||
- la zeroization réduit les copies possédées mais ne constitue pas une preuve d'effacement physique de toute copie potentielle produite par le compilateur, l'OS ou le matériel ;
|
||
- aucune revendication de résistance side-channel supplémentaire au-delà des primitives et bibliothèques retenues.
|
||
|
||
## 26. Statut de clôture V1
|
||
|
||
À la publication stable `0.2.5`, cette spécification constitue la version normative V1 du format `.kspwallet`. Les guides d'utilisation KSP sont [`../../crates/ksp-wallet-lib/README.md`](../../crates/ksp-wallet-lib/README.md) et [`../../crates/ksp-wallet-lib/USAGE.md`](../../crates/ksp-wallet-lib/USAGE.md) ; ils ne remplacent pas le présent document comme autorité normative du wire.
|
||
|
||
La matrice [`../validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](../validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md) enregistre les canaris adversariaux, la reproduction indépendante des vecteurs, les limites V1 et le checkpoint opérateur final de `pre.010-fix.003`.
|
||
|
||
La publication stable `0.2.5` n’apporte aucune nouvelle décision cryptographique ni changement de wire par rapport à la candidate validée. Toute évolution ultérieure incompatible du format devra passer par une nouvelle version de format explicitement spécifiée.
|