v0.2.5-pre.005

This commit is contained in:
2026-08-19 13:09:16 +02:00
parent 3c069347b7
commit 36b98e0abc
27 changed files with 2305 additions and 113 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: docs/formats/000-README.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Formats KSP
@@ -9,4 +9,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 son enveloppe JSON stricte, ses limites structurelles, ses key slots, son transcript OWNER et ses AAD ; `0.2.5-pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS, le wrapping de content keys et le premier vecteur crypto déterministe. Le default Argon2 de création reste volontairement en attente du benchmark opérateur livré par cette tranche.
- [`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 et `pre.005` fixe les payloads plaintext, le profil de création KSP calibré, l'autorité Ed25519 OWNER, les procédures create/open VIEW/OWNER et un vecteur complet interopérable test-only.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/formats/KSPWALLET_V1.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# `.kspwallet` V1 — spécification du format natif Wallet KSP
@@ -20,7 +20,7 @@ AAD des compartiments owner-control / metadata / secret
règles unknown-field / unknown-version
```
`0.2.5-pre.004` complète maintenant les primitives KDF/AEAD normatives et un premier vecteur cryptographique public. Le **default de création Argon2id** reste volontairement non normatif tant que le benchmark opérateur de `pre.004` n'a pas été exécuté. Les prereleases suivantes complètent les payloads plaintext exacts, l'autorité Ed25519, les opérations create/open et les vecteurs de wallet complets. Toute évolution qui modifie un élément déjà déclaré **figé** par cette spécification exige une évolution explicitement tracée avant la release stable ; après publication de V1, une incompatibilité de wire exige un nouveau `format_version`.
`0.2.5-pre.004` ajoute les primitives KDF/AEAD normatives et un premier vecteur cryptographique public. `0.2.5-pre.005` fixe maintenant 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. La persistence filesystem, les mutations administratives, la signature Solana publique et les adapters import/export restent dans les tranches suivantes. Toute évolution qui modifie un élément déjà déclaré **figé** par cette spécification exige une évolution explicitement tracée avant la release stable ; après publication de V1, une incompatibilité de wire exige un nouveau `format_version`.
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`.
@@ -166,7 +166,7 @@ La forme V1 est :
}
```
Les valeurs Argon2 chiffrées dans cet exemple sont **des valeurs de fixture structurelle**, pas les defaults de création V1. Les defaults ne deviennent normatifs qu'après benchmark de `pre.004`.
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`
@@ -223,9 +223,20 @@ memory_kib >= 8 * parallelism
salt : 16 .. 64 octets
```
Ces plafonds sont des bornes de format/rejet hostile ; ils ne définissent pas les paramètres de **création par défaut**. Ceux-ci sont benchmarkés séparément.
Ces plafonds sont des bornes de format/rejet hostile ; ils ne définissent pas à eux seuls les paramètres de création.
Le password KDF futur 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é à la 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
@@ -240,7 +251,14 @@ ciphertext <= 4096 octets
Le ciphertext contient le tag Poly1305 de 16 octets produit par l'AEAD.
Le contenu plaintext exact des wrapped capabilities est figé avec la couche crypto/payload suivante ; la grammaire envelope/key-slot et son AAD sont déjà figés ici.
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
@@ -275,19 +293,57 @@ metadata : 16 .. 65 552 octets
secret : 16 .. 4096 octets
```
La metadata plaintext V1 reste bornée à 65 536 octets. Les payloads plaintext exacts sont consolidés dans la tranche dédiée, mais les champs wire ci-dessus ne changent pas.
La metadata plaintext V1 reste bornée à 65 536 octets. `pre.005` fixe les trois plaintexts :
Le compartiment metadata contient à terme au minimum :
### 9.1 `owner_control`
Plaintext binaire de **96 octets exacts**, dans cet ordre :
```text
Pubkey Solana Base58 canonique
alias optionnel <= 256 octets UTF-8
maximum 64 notes
texte note <= 8192 octets UTF-8
id de note = 16 octets aléatoires
offset 0..32 : admin_signing_secret Ed25519 (32 octets)
offset 32..64 : K_metadata (32 octets)
offset 64..96 : K_secret (32 octets)
```
Le compartiment secret contient la keypair Solana exacte nécessaire à la signature OWNER.
`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
@@ -304,6 +360,8 @@ La signature porte sur le **transcript sémantique OWNER-controlled**, jamais su
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
@@ -603,19 +661,20 @@ AAD exacts
round-trip du codec
```
Le premier vecteur cryptographique public KDF+wrapping est ajouté par `pre.004`. Les vecteurs de wallet complets incluant payloads et state signature sont ajoutés après implémentation des compartiments et de l'autorité OWNER.
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. Invariants encore à compléter sans modifier le wire figé
Les tranches suivantes doivent compléter :
Après `pre.005`, les tranches restantes portent sur les opérations autour du format déjà défini :
```text
pre.004 : sélection finale du default Argon2 après benchmark opérateur (KDF/AEAD/wrapping/vecteur déjà implémentés)
pre.005 : payloads owner-control/metadata/secret + create/open VIEW/OWNER + state signature effective
pre.006+ : persistence/administration/signature/import-export selon le plan Wallet
pre.006 : persistence async/atomique/no-clobber
pre.007 : signature Solana, metadata admin, rotations OWNER/VIEW et révocation forte VIEW
pre.008 : import/export
pre.009+ : audit adversarial, compliance et documentation de clôture
```
Toute découverte imposant de modifier la grammaire, les tags, l'ordre transcript ou les domain separators définis dans ce document doit être traitée explicitement avant la publication stable, jamais masquée par une tolérance du parseur.
Toute découverte imposant de modifier la grammaire, les payloads plaintext, les tags, l'ordre transcript ou les domain separators définis dans ce document doit être traitée explicitement avant la publication stable, jamais masquée par une tolérance du parseur.
## 20. Primitives cryptographiques effectives depuis `pre.004`
@@ -638,15 +697,15 @@ 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 futures API async create/open l'exécuteront hors du thread executor conformément au plan Wallet.
Le default de création n'est **pas** déterminé par les defaults de la crate RustCrypto. Le benchmark opérateur compare explicitement :
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
128 MiB / 3 passes / 1 lane
256 MiB / 3 passes / 1 lane
64 MiB / 3 passes / 1 lane -> 1742 ms
128 MiB / 3 passes / 1 lane -> 3459 ms
256 MiB / 3 passes / 1 lane -> 6925 ms
```
Le profil retenu sera enregistré après mesure sur machine cible. Un wallet conserve toujours ses propres paramètres sérialisés, indépendamment des defaults futurs.
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
@@ -685,3 +744,89 @@ 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 création `pre.005` est **in-memory** : la persistence arrive en `pre.006`.
### 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.
## 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.