v0.2.6-pre.017

This commit is contained in:
2026-08-22 09:39:13 +02:00
parent 5c0be421da
commit 362123f357
22 changed files with 896 additions and 109 deletions

View File

@@ -19,7 +19,7 @@ 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. `0.2.6-pre.016` matérialise la création/ouverture/persistence V2, l'auto-détection V1/V2, les APIs génériques/versionnées et le basculement de Wallet Desk vers la façade non versionnée.
`0.2.6-pre.015` fige la grammaire binaire, les identifiants numériques, les bornes structurelles et les transcripts/AAD V2. `0.2.6-pre.016` matérialise la création/ouverture/persistence V2, l'auto-détection V1/V2, les APIs génériques/versionnées et le basculement de Wallet Desk vers la façade non versionnée. `0.2.6-pre.017` ajoute la migration V1 -> V2 explicite et OWNER-authentifiée, en copie no-clobber ou remplacement atomique in-place.
V1 reste un format stable supporté. V2 ne réinterprète jamais un document V1 avec sa propre grammaire.
@@ -104,14 +104,14 @@ Une implémentation conforme doit vérifier les bornes **avant** toute allocatio
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` |
| 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 :
@@ -154,21 +154,21 @@ Aucun compteur de slots ou de compartiments n'est nécessaire : leur cardinalit
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 |
| 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 :
@@ -189,14 +189,14 @@ Toute divergence est invalide avant KDF/déchiffrement.
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 |
| 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 :
@@ -214,10 +214,10 @@ Les payloads plaintext V2 conservent le modèle fonctionnel établi en V1 pour c
La fin du document est :
| Champ | Taille | Valeur / règle |
|-----------------------------|-------:|---------------------------|
| `state_signature.algorithm` | 1 | `0x01` Ed25519 |
| `state_signature.signature` | 64 | signature detached exacte |
| 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.
@@ -377,17 +377,17 @@ Le nonce/ciphertext n'est pas inclus dans son propre AAD.
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 |
| 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 :
@@ -443,7 +443,21 @@ 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.
`pre.017` matérialise cette politique avec trois surfaces :
```text
migrate_wallet_v1_to_v2(...)
migrate_wallet_file_v1_to_v2(...)
migrate_wallet_file_v1_to_v2_in_place(...)
```
La migration authentifie toujours le wallet source avec OWNER. Elle ne transcode pas les slots/ciphertexts V1 : elle ouvre le V1, conserve l'identité Solana et le payload metadata exact — y compris les identifiants de notes — puis construit un nouvel envelope V2 avec de nouveaux matériaux cryptographiques et les domains/transcripts V2. Le mot de passe OWNER fourni reste le credential OWNER cible.
Lorsque VIEW est activé en V1, le caller doit fournir un mot de passe VIEW cible pour V2, car le key-wrap V1 ne peut pas être réutilisé sous l'AAD V2. OWNER peut fournir l'ancien credential ou en choisir un nouveau, conformément à son autorité existante de rotation VIEW. Une migration pure conserve en revanche la forme de capability : elle ne peut ni activer VIEW sur une source désactivée, ni supprimer VIEW d'une source activée.
La migration fichier vers une autre destination est no-clobber et laisse V1 intact. La variante in-place compare l'état V1 authentifié attendu avant publication et utilise le remplacement atomique existant ; un changement concurrent retourne `wallet.state_conflict`. `wallet.migration_invalid` couvre une demande de migration incohérente.
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