v0.2.6-pre.015
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/formats/000-README.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Formats KSP
|
||||
|
||||
@@ -10,3 +10,4 @@ Une spécification de format décrit le wire exact, les encodages, les limites,
|
||||
## Formats actifs
|
||||
|
||||
- [`KSPWALLET_V1.md`](KSPWALLET_V1.md) — spécification du format natif autonome `.kspwallet` V1. `0.2.5-pre.003` fige l'enveloppe/wire et les transcripts/AAD, `pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS, `pre.005` fixe les payloads plaintext, le profil de création KSP calibré, l'autorité Ed25519 OWNER et le vecteur complet, `pre.006`–`pre.008` matérialisent persistence/administration/transfert, `pre.009` ferme l'audit adversarial/interoperabilité/compliance et `pre.010` synchronise la documentation finale sans modifier le wire V1.
|
||||
- [`KSPWALLET_V2.md`](KSPWALLET_V2.md) — wire binaire natif V2 introduit par `0.2.6-pre.015` : framing canonique KSP, IDs numériques, longueurs big-endian, aucun Base64/compression, domains/transcripts V2 distincts et politique `default != latest`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/formats/KSPWALLET_V1.md -->
|
||||
<!-- version: 11 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# `.kspwallet` V1 — spécification du format natif Wallet KSP
|
||||
|
||||
@@ -22,6 +22,8 @@ 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 une lecture générique future doit auto-détecter 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
|
||||
|
||||
441
docs/formats/KSPWALLET_V2.md
Normal file
441
docs/formats/KSPWALLET_V2.md
Normal file
@@ -0,0 +1,441 @@
|
||||
<!-- file: docs/formats/KSPWALLET_V2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# `.kspwallet` V2 — spécification du wire binaire natif KSP
|
||||
|
||||
## 1. Statut et objectif
|
||||
|
||||
Ce document est l'autorité normative du **wire `.kspwallet` `format_version = 2`** introduit par `0.2.6-pre.015`.
|
||||
|
||||
V2 remplace l'enveloppe JSON/Base64url de V1 par un framing binaire KSP canonique. L'objectif est :
|
||||
|
||||
```text
|
||||
réduire fortement la taille persistée
|
||||
supprimer l'encodage Base64 des champs déjà binaires
|
||||
éviter qu'un .kspwallet nouvellement créé soit un document JSON lisible comme tel
|
||||
conserver un format documenté et implémentable hors Rust
|
||||
préserver les frontières cryptographiques Wallet
|
||||
```
|
||||
|
||||
Le caractère binaire **n'ajoute aucune propriété cryptographique**. La confidentialité et l'authenticité continuent de dépendre d'Argon2id, XChaCha20-Poly1305 et Ed25519, jamais de l'absence de JSON.
|
||||
|
||||
`0.2.6-pre.015` fige la grammaire binaire, les identifiants numériques, les bornes structurelles et les transcripts/AAD V2. La création/ouverture/persistence V2, l'auto-détection V1/V2 et le basculement de l'API générique vers V2 sont matérialisés dans les tranches suivantes de `0.2.6`.
|
||||
|
||||
V1 reste un format stable supporté. V2 ne réinterprète jamais un document V1 avec sa propre grammaire.
|
||||
|
||||
## 2. Politique de version et API
|
||||
|
||||
Les notions suivantes sont distinctes :
|
||||
|
||||
```text
|
||||
DEFAULT_WALLET_FORMAT
|
||||
LATEST_SUPPORTED_WALLET_FORMAT
|
||||
```
|
||||
|
||||
À partir de l'intégration V2 :
|
||||
|
||||
```text
|
||||
DEFAULT_WALLET_FORMAT = V2
|
||||
LATEST_SUPPORTED_WALLET_FORMAT = V2
|
||||
```
|
||||
|
||||
Si un futur V3 apparaît, par exemple pour un modèle d'autorisation avec second facteur, `LATEST_SUPPORTED_WALLET_FORMAT` pourra devenir V3 tandis que `DEFAULT_WALLET_FORMAT` pourra **rester V2**. Le default ne suit jamais automatiquement la dernière version.
|
||||
|
||||
La politique d'API retenue est :
|
||||
|
||||
```text
|
||||
create_wallet_file(...) -> format default explicitement choisi, V2
|
||||
create_wallet_file_v1(...) -> force V1
|
||||
create_wallet_file_v2(...) -> force V2
|
||||
create_wallet_file_v3(...) -> force V3 si V3 existe un jour
|
||||
|
||||
open_wallet_*_file(...) -> détecte puis dispatch les versions supportées
|
||||
open_wallet_*_file_v1(...) -> exige V1
|
||||
open_wallet_*_file_v2(...) -> exige V2
|
||||
open_wallet_*_file_v3(...) -> exige V3 si V3 existe un jour
|
||||
```
|
||||
|
||||
Le même principe s'applique aux opérations dont la version de fichier est pertinente, notamment inspection et import natif.
|
||||
|
||||
## 3. Encodage général
|
||||
|
||||
Un document V2 est :
|
||||
|
||||
```text
|
||||
binaire
|
||||
maximum 1 048 576 octets
|
||||
entiers multi-octets = unsigned big-endian
|
||||
ordre des champs = normatif
|
||||
aucun padding implicite
|
||||
aucune Base64
|
||||
aucune compression
|
||||
aucun trailing byte
|
||||
```
|
||||
|
||||
Les longueurs sont exprimées en **octets**.
|
||||
|
||||
Une implémentation conforme doit vérifier les bornes **avant** toute allocation dépendant d'une longueur reçue. Le document entier est déjà borné par `1 048 576` octets avant parsing.
|
||||
|
||||
## 4. Header V2
|
||||
|
||||
Le début de fichier est strictement :
|
||||
|
||||
| Ordre | Champ | Taille | Valeur / règle |
|
||||
|------:|---------------------------|-----------------:|------------------------------------------------|
|
||||
| 1 | `magic` | 9 | ASCII exact `KSPWALLET` |
|
||||
| 2 | `format_version` | 2 | `0x0002` |
|
||||
| 3 | `document_length` | 4 | longueur totale exacte du fichier |
|
||||
| 4 | `flags` | 2 | bit 0 = VIEW activé ; tous les autres bits = 0 |
|
||||
| 5 | `owner_auth_public_key` | 32 | clé publique Ed25519 OWNER |
|
||||
| 6 | `view_descriptor.slot_id` | 16 conditionnels | présent uniquement si `flags & 0x0001 != 0` |
|
||||
|
||||
Offsets fixes avant le descripteur conditionnel :
|
||||
|
||||
```text
|
||||
0x0000..0x0008 magic
|
||||
0x0009..0x000A format_version
|
||||
0x000B..0x000E document_length
|
||||
0x000F..0x0010 flags
|
||||
0x0011..0x0030 owner_auth_public_key
|
||||
```
|
||||
|
||||
Pour un wallet VIEW-enabled, `view_descriptor.slot_id` occupe ensuite `0x0031..0x0040`.
|
||||
|
||||
### 4.1 Flags
|
||||
|
||||
```text
|
||||
0x0001 VIEW_ENABLED
|
||||
0xFFFE réservé, doit être zéro
|
||||
```
|
||||
|
||||
Tout bit réservé non nul rend le document invalide. V2 n'emploie pas les bits réservés comme mécanisme d'extension silencieuse ; une modification incompatible exige un nouveau format.
|
||||
|
||||
## 5. Ordre canonique du body
|
||||
|
||||
Après le header :
|
||||
|
||||
```text
|
||||
OWNER key slot obligatoire
|
||||
VIEW key slot présent seulement si VIEW_ENABLED
|
||||
OWNER-CONTROL encrypted compartment obligatoire
|
||||
METADATA encrypted compartment obligatoire
|
||||
SECRET encrypted compartment obligatoire
|
||||
state signature obligatoire
|
||||
EOF immédiat
|
||||
```
|
||||
|
||||
Aucun compteur de slots ou de compartiments n'est nécessaire : leur cardinalité et leur ordre sont déterminés par V2.
|
||||
|
||||
## 6. Key slot V2
|
||||
|
||||
Chaque key slot est encodé ainsi :
|
||||
|
||||
| Champ | Taille | Valeur / règle |
|
||||
|--------------------------|---------:|-----------------------------------------|
|
||||
| `role` | 1 | `0x01` OWNER, `0x02` VIEW |
|
||||
| `slot_id` | 16 | identifiant binaire exact |
|
||||
| `kdf_algorithm` | 1 | `0x01` Argon2id |
|
||||
| `kdf_version` | 4 | `19` |
|
||||
| `memory_kib` | 4 | `1..1 048 576`, et `>= parallelism * 8` |
|
||||
| `iterations` | 4 | `1..64` |
|
||||
| `parallelism` | 4 | `1..64` |
|
||||
| `salt_length` | 1 | `16..64` |
|
||||
| `salt` | variable | exactement `salt_length` octets |
|
||||
| `wrap_algorithm` | 1 | `0x01` XChaCha20-Poly1305 |
|
||||
| `wrap_nonce` | 24 | nonce exact |
|
||||
| `wrap_ciphertext_length` | 2 | `16..4096` |
|
||||
| `wrap_ciphertext` | variable | exactement la longueur déclarée |
|
||||
|
||||
Ordre obligatoire :
|
||||
|
||||
```text
|
||||
premier slot = OWNER
|
||||
second slot = VIEW seulement si VIEW_ENABLED
|
||||
```
|
||||
|
||||
Si VIEW est activé :
|
||||
|
||||
```text
|
||||
header.view_descriptor.slot_id == VIEW key slot.slot_id
|
||||
```
|
||||
|
||||
Toute divergence est invalide avant KDF/déchiffrement.
|
||||
|
||||
## 7. Compartiments V2
|
||||
|
||||
Chaque compartiment est encodé :
|
||||
|
||||
| Champ | Taille | Valeur / règle |
|
||||
|---------------------|---------:|------------------------------------------------------|
|
||||
| `kind` | 1 | `0x01` OWNER-CONTROL, `0x02` METADATA, `0x03` SECRET |
|
||||
| `payload_version` | 4 | `1` pour le profil initial V2 |
|
||||
| `algorithm` | 1 | `0x01` XChaCha20-Poly1305 |
|
||||
| `nonce` | 24 | nonce exact |
|
||||
| `ciphertext_length` | 4 | longueur exacte |
|
||||
| `ciphertext` | variable | ciphertext + tag Poly1305 |
|
||||
|
||||
Bornes initiales :
|
||||
|
||||
```text
|
||||
OWNER-CONTROL : 16..4096 octets
|
||||
METADATA : 16..65552 octets
|
||||
SECRET : 16..4096 octets
|
||||
```
|
||||
|
||||
L'ordre exact est OWNER-CONTROL, METADATA, SECRET. Le `kind` encodé doit correspondre à la position attendue ; il n'autorise pas un réordonnancement.
|
||||
|
||||
Les payloads plaintext V2 conservent le modèle fonctionnel établi en V1 pour cette évolution : owner-control, metadata et secret Solana restent des compartiments distincts. Une future modification incompatible de leur sémantique exige une version de format explicite.
|
||||
|
||||
## 8. Signature d'état
|
||||
|
||||
La fin du document est :
|
||||
|
||||
| Champ | Taille | Valeur / règle |
|
||||
|-----------------------------|-------:|---------------------------|
|
||||
| `state_signature.algorithm` | 1 | `0x01` Ed25519 |
|
||||
| `state_signature.signature` | 64 | signature detached exacte |
|
||||
|
||||
Aucun octet ne peut suivre ces 65 octets.
|
||||
|
||||
## 9. Identifiants numériques figés
|
||||
|
||||
### 9.1 Rôles
|
||||
|
||||
```text
|
||||
0x01 OWNER
|
||||
0x02 VIEW
|
||||
```
|
||||
|
||||
### 9.2 Algorithmes
|
||||
|
||||
```text
|
||||
KDF
|
||||
0x01 Argon2id
|
||||
|
||||
AEAD
|
||||
0x01 XChaCha20-Poly1305
|
||||
|
||||
state signature
|
||||
0x01 Ed25519
|
||||
```
|
||||
|
||||
### 9.3 Compartiments
|
||||
|
||||
```text
|
||||
0x01 OWNER-CONTROL
|
||||
0x02 METADATA
|
||||
0x03 SECRET
|
||||
```
|
||||
|
||||
`0x00` est invalide dans ces espaces. Les autres valeurs sont réservées et rejetées par V2 tant qu'elles ne sont pas explicitement normalisées par une évolution compatible documentée ; aucune valeur inconnue n'est devinée.
|
||||
|
||||
## 10. Canonicalité et rejet
|
||||
|
||||
Le parser V2 doit rejeter avant crypto :
|
||||
|
||||
```text
|
||||
fichier > 1 MiB
|
||||
magic incorrect
|
||||
format_version != 2
|
||||
document_length différent de la taille reçue
|
||||
flag réservé non nul
|
||||
champ tronqué
|
||||
longueur qui dépasse le reste du document
|
||||
role/algorithm/kind inconnu
|
||||
ordre OWNER/VIEW invalide
|
||||
VIEW descriptor et VIEW slot incohérents
|
||||
Argon2 hors bornes
|
||||
salt hors bornes
|
||||
ciphertext hors bornes
|
||||
payload_version non supporté
|
||||
trailing bytes
|
||||
```
|
||||
|
||||
Il n'existe qu'un encodage canonique d'un même état sémantique V2 : mêmes champs, même ordre, mêmes largeurs, même endianness et aucune donnée ignorée.
|
||||
|
||||
## 11. Transcripts et AAD V2
|
||||
|
||||
V2 possède ses propres domaines ; V1 et V2 ne partagent jamais un domain separator :
|
||||
|
||||
```text
|
||||
KSPWALLET-V2-STATE
|
||||
KSPWALLET-V2-AAD-OWNER-SLOT
|
||||
KSPWALLET-V2-AAD-VIEW-SLOT
|
||||
KSPWALLET-V2-AAD-OWNER-CONTROL
|
||||
KSPWALLET-V2-AAD-METADATA
|
||||
KSPWALLET-V2-AAD-SECRET
|
||||
```
|
||||
|
||||
Le transcript/AAD V2 conserve la discipline TLV déterministe de V1 :
|
||||
|
||||
```text
|
||||
domain || 0x00
|
||||
puis pour chaque champ :
|
||||
tag:u16 big-endian
|
||||
length:u64 big-endian
|
||||
value:length bytes
|
||||
```
|
||||
|
||||
Les tags restent alignés avec les familles V1 :
|
||||
|
||||
```text
|
||||
0x0001 magic
|
||||
0x0002 format_version
|
||||
0x0003 owner_auth_public_key
|
||||
0x0010 view_enabled
|
||||
0x0011 view_role
|
||||
0x0012 view_slot_id
|
||||
0x0100 slot_id
|
||||
0x0101 slot_role
|
||||
0x0102 kdf_algorithm
|
||||
0x0103 kdf_version
|
||||
0x0104 kdf_memory_kib
|
||||
0x0105 kdf_iterations
|
||||
0x0106 kdf_parallelism
|
||||
0x0107 kdf_salt
|
||||
0x0108 wrap_algorithm
|
||||
0x0109 wrap_nonce
|
||||
0x010A wrap_ciphertext
|
||||
0x0200 compartment_kind
|
||||
0x0201 compartment_version
|
||||
0x0202 compartment_algorithm
|
||||
0x0203 compartment_nonce
|
||||
0x0204 compartment_ciphertext
|
||||
0x0500 state_signature_algorithm
|
||||
```
|
||||
|
||||
Différence normative V2 : les rôles/algorithmes/kinds sont transcriptés sous leur **ID numérique d'un octet**, et `format_version = 2` est transcripté en `u32` big-endian. Les domaines distincts empêchent qu'un transcript V1 et un transcript V2 soient interchangeables.
|
||||
|
||||
### 11.1 State transcript
|
||||
|
||||
Le state transcript contient :
|
||||
|
||||
```text
|
||||
common
|
||||
view descriptor
|
||||
OWNER slot avec wrap nonce+ciphertext
|
||||
OWNER-CONTROL avec nonce+ciphertext
|
||||
METADATA avec nonce+ciphertext
|
||||
SECRET avec nonce+ciphertext
|
||||
state signature algorithm
|
||||
```
|
||||
|
||||
Le VIEW wrap mutable n'est pas ajouté au state transcript, selon le modèle d'autorisation VIEW déjà retenu : son identité stable reste liée par le descriptor signé tandis que son credential peut être self-rotaté sans OWNER.
|
||||
|
||||
### 11.2 Slot AAD
|
||||
|
||||
Le slot AAD contient :
|
||||
|
||||
```text
|
||||
common
|
||||
slot_id
|
||||
role
|
||||
KDF algorithm/version/parameters/salt
|
||||
wrap algorithm
|
||||
```
|
||||
|
||||
Le wrap nonce/ciphertext n'est pas inclus dans son propre AAD.
|
||||
|
||||
### 11.3 Compartment AAD
|
||||
|
||||
Le compartment AAD contient :
|
||||
|
||||
```text
|
||||
common
|
||||
kind
|
||||
payload_version
|
||||
algorithm
|
||||
```
|
||||
|
||||
Le nonce/ciphertext n'est pas inclus dans son propre AAD.
|
||||
|
||||
## 12. V1 et V2
|
||||
|
||||
V1 et V2 sont deux formats explicites :
|
||||
|
||||
| Propriété | V1 | V2 |
|
||||
|--------------------------|------------------------------------|---------------|
|
||||
| enveloppe | JSON UTF-8 | binaire KSP |
|
||||
| champs binaires | Base64url no-pad | bytes directs |
|
||||
| version | `1` | `2` |
|
||||
| canonicalité | sémantique JSON + Base64 canonique | byte-exact |
|
||||
| taille fixture wire-only | 2037 octets | 628 octets |
|
||||
| Argon2id | oui | oui |
|
||||
| XChaCha20-Poly1305 | oui | oui |
|
||||
| Ed25519 OWNER state | oui | oui |
|
||||
| VIEW/OWNER | oui | oui |
|
||||
|
||||
La réduction mesurée sur le fixture structurel de référence est :
|
||||
|
||||
```text
|
||||
2037 -> 628 octets
|
||||
-1409 octets
|
||||
~69,2 %
|
||||
```
|
||||
|
||||
Cette mesure n'est pas une promesse de ratio constant : la taille dépend notamment des metadata chiffrées.
|
||||
|
||||
## 13. Fixture structurelle
|
||||
|
||||
Fixture canonique de `pre.015` :
|
||||
|
||||
```text
|
||||
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v2_wire_only.bin
|
||||
size = 628
|
||||
sha256 = cbacb37326c76e6dde8d614601cd765a8c0fd9744f891d640b8a3cd71cf60a93
|
||||
```
|
||||
|
||||
Préfixe hexadécimal :
|
||||
|
||||
```text
|
||||
4b535057414c4c4554 0002 00000274 0001
|
||||
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
|
||||
202122232425262728292a2b2c2d2e2f
|
||||
```
|
||||
|
||||
Lecture :
|
||||
|
||||
```text
|
||||
4b535057414c4c4554 = "KSPWALLET"
|
||||
0002 = format_version V2
|
||||
00000274 = 628 octets
|
||||
0001 = VIEW_ENABLED
|
||||
```
|
||||
|
||||
La fixture `wire_only` vérifie framing/canonicalité ; elle n'est pas présentée comme une signature cryptographique V2 valide tant que la création/open V2 n'est pas intégrée par la tranche suivante.
|
||||
|
||||
## 14. Compatibilité et migration
|
||||
|
||||
V2 n'autorise aucune migration implicite à l'ouverture.
|
||||
|
||||
Politique prévue :
|
||||
|
||||
```text
|
||||
lecture générique V1/V2 oui
|
||||
création générique V2
|
||||
création V1 forcée oui
|
||||
création V2 forcée oui
|
||||
réécriture V1 -> V2 cachée non
|
||||
migration V1 -> V2 opération explicite
|
||||
```
|
||||
|
||||
Une migration doit authentifier le wallet source avec la capability requise, produire un nouveau document V2 valide et respecter la persistence no-clobber/atomique. Une simple transcodification non authentifiée des bytes V1 n'est pas suffisante puisque V2 possède ses propres domains/transcripts.
|
||||
|
||||
## 15. Future V3 / second facteur
|
||||
|
||||
V2 ne réalise aucun second facteur.
|
||||
|
||||
Si un futur V3 ajoute une autorisation dépendant d'un facteur externe :
|
||||
|
||||
```text
|
||||
ksp-wallet-lib possède la règle qui exige le facteur
|
||||
ksp-wallet-lib possède le challenge et la validation cryptographique
|
||||
aucun WalletOwner ne peut être obtenu si le facteur requis n'est pas satisfait
|
||||
Wallet Desk ne décide jamais que le second facteur est valide
|
||||
```
|
||||
|
||||
Une UI comme Wallet Desk peut néanmoins devoir évoluer pour orchestrer l'expérience : attente de confirmation, saisie OTP, enrollment, recovery, hardware/WebAuthn ou consentement externe.
|
||||
|
||||
Le réseau ou le fournisseur externe ne doit pas forcer `ksp-wallet-lib` à dépendre directement de Config/Transport/Tauri. Une abstraction/provider KSP séparée peut fournir la preuve à Wallet tandis que Wallet reste propriétaire de la politique d'autorisation.
|
||||
|
||||
L'arrivée d'un V3 ne change pas automatiquement le default : V2 peut rester `DEFAULT_WALLET_FORMAT` aussi longtemps que KSP le décide explicitement.
|
||||
Reference in New Issue
Block a user