v0.2.5-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/formats/KSPWALLET_V1.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# `.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` 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`.
|
||||
`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. 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`.
|
||||
|
||||
@@ -798,6 +798,87 @@ Une ouverture VIEW ne déchiffre **jamais** `owner_control` ni `secret` et ne ma
|
||||
|
||||
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 :
|
||||
@@ -846,6 +927,6 @@ caller path explicite
|
||||
|
||||
`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.
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
||||
<!-- version: 51 -->
|
||||
<!-- version: 52 -->
|
||||
|
||||
# Séquence des releases fonctionnelles KSP
|
||||
|
||||
@@ -418,6 +418,8 @@ La release doit fournir `docs/formats/KSPWALLET_V1.md` comme spécification sép
|
||||
|
||||
`0.2.5-pre.006` ajoute la persistence native sans Config : `create_wallet_file_v1` reçoit un chemin explicite du caller et publie uniquement en no-clobber via un fichier temporaire créé dans le même répertoire, écrit puis `sync_all` avant `persist_noclobber`; `open_wallet_view_file_v1`, `open_wallet_owner_file_v1` et `inspect_locked_wallet_file_v1` effectuent une lecture bornée à la limite V1 avant de déléguer au parser/crypto acquis. Les opérations filesystem bloquantes sont isolées par `spawn_blocking`. Les tests couvrent destination existante, concurrence avec un seul gagnant, fault injection avant publication, cleanup ordinaire des temporaires et rejet d'un fichier surdimensionné. La synchronisation du répertoire parent est best-effort sur Unix et n'est pas transformée en garantie portable de crash-durability. Les ACL/permissions OS restent hors du modèle Wallet.
|
||||
|
||||
`0.2.5-pre.007` complète l'administration native capability-bound : OWNER signe des messages Solana sans getter secret, modifie alias/notes, change son password ou celui de VIEW et peut disable/recreate VIEW avec rekey metadata fort ; VIEW ne peut que tourner son propre credential en rewrappant le même `K_metadata`. Les mutations sont staged puis remplacent le fichier uniquement si la destination courante correspond encore à l'enveloppe authentifiée attendue ; un handle stale ou une mauvaise cible reçoit `wallet.state_conflict`. Ce garde-fou ne prétend pas fournir un CAS filesystem portable ni un anti-rollback externe. La keypair reste encapsulée dans Wallet et aucune nouvelle dépendance tierce n'est ajoutée. `pre.008` reprend les adapters import/export Solana CLI JSON et Base58 générique.
|
||||
|
||||
## `0.2.6` — Wallet Desk
|
||||
|
||||
Mission : valider Config composite + `.kspwallet` + transport HTTP dans une application Tauri mince.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# Plan `0.2.5` — Wallet foundation
|
||||
|
||||
@@ -436,9 +436,11 @@ L'absence d'une ligne « audited » dans ce document ne signifie donc pas « sû
|
||||
|
||||
```text
|
||||
base64 0.23.1 acquis depuis pre.003 pour Base64url sans padding des champs binaires JSON
|
||||
tempfile 3.27.0 acquis en pre.006 pour temp files same-directory + publication no-clobber
|
||||
tempfile 3.27.0 acquis en pre.006 pour temp files same-directory + publication no-clobber / replacement administratif contrôlé
|
||||
```
|
||||
|
||||
`pre.007` n'ajoute aucune dépendance tierce. Le remplacement administratif est lié aux capabilities VIEW/OWNER et réutilise `tempfile`; aucune primitive publique générale d'overwrite n'est introduite.
|
||||
|
||||
`pre.006` retient `NamedTempFile::new_in` et `persist_noclobber` parce que la publication reste sur le même filesystem et ne peut pas écraser une destination existante. La documentation upstream précise que `persist_noclobber` n'est pas atomique sur absolument toutes les plateformes/filesystems et peut laisser un hard-link temporaire après certains crashes : KSP documente cette limite au lieu de surpromettre une durabilité universelle.
|
||||
|
||||
## 9. Format natif `.kspwallet` V1
|
||||
@@ -1049,7 +1051,7 @@ create/open KDF via spawn_blocking
|
||||
vecteur complet test-only généré et revérifié indépendamment du Rust
|
||||
```
|
||||
|
||||
La signature Solana publique, les mutations metadata et les rotations restent volontairement absentes de cette tranche ; la persistence est désormais acquise par `pre.006`.
|
||||
La signature Solana publique, les mutations metadata, les rotations OWNER/VIEW, disable/recreate VIEW avec rekey metadata fort et le remplacement administratif capability-bound sont désormais acquis par `pre.007`. La keypair Solana reste encapsulée dans OWNER et n'est jamais réexportée via Core ou un getter Wallet.
|
||||
|
||||
### 19.8 État acquis après `pre.006`
|
||||
|
||||
@@ -1062,7 +1064,26 @@ open_wallet_owner_file_v1(path, ...) -> bounded read -> open OWNER acquis
|
||||
inspect_locked_wallet_file_v1(path) -> bounded read -> signature-state verify sans KDF
|
||||
```
|
||||
|
||||
La lecture est bornée à `KSPWALLET_MAX_FILE_BYTES` avant parsing, y compris si le fichier grossit entre metadata et lecture. Le filesystem blocking est exécuté via `tokio::task::spawn_blocking`. La création concurrente a exactement un gagnant ; les autres reçoivent `wallet.destination_exists`. Aucun répertoire n'est créé ou choisi par Wallet. Le replace administratif reste volontairement pour `pre.007`, où il pourra être lié à une capability OWNER ou à la self-rotation VIEW au lieu d'exposer une primitive générale d'overwrite.
|
||||
La lecture est bornée à `KSPWALLET_MAX_FILE_BYTES` avant parsing, y compris si le fichier grossit entre metadata et lecture. Le filesystem blocking est exécuté via `tokio::task::spawn_blocking`. La création concurrente a exactement un gagnant ; les autres reçoivent `wallet.destination_exists`. Aucun répertoire n'est créé ou choisi par Wallet.
|
||||
|
||||
### 19.9 État acquis après `pre.007`
|
||||
|
||||
`pre.007` complète l'administration native sans ouvrir de surface de secret brut :
|
||||
|
||||
```text
|
||||
WalletOwner::sign(message) -> signature Ed25519 Solana 64 octets
|
||||
WalletOwner::update_alias(path, alias) -> metadata reencrypt + OWNER resign
|
||||
WalletOwner::add/update/delete_note(path, ...) -> metadata reencrypt + OWNER resign
|
||||
WalletView::rotate_view_password(path, new_pass) -> même slot_id + même K_metadata + state signature inchangée
|
||||
WalletOwner::rotate_view_password(path, new_pass) -> même rotation sans ancien VIEW password
|
||||
WalletOwner::rotate_owner_password(path, new_pass) -> même K_owner_root + nouveau credential + OWNER resign
|
||||
WalletOwner::disable_view(path) -> nouveau K_metadata + VIEW supprimé + OWNER resign
|
||||
WalletOwner::recreate_view(path, new_pass) -> nouveau K_metadata + nouveau slot_id VIEW + OWNER resign
|
||||
```
|
||||
|
||||
Toutes les mutations persistent un état staged puis ne mettent à jour le handle qu'après publication réussie. Le replacement est crate-private et vérifie que la destination courante correspond sémantiquement à l'enveloppe authentifiée attendue ; un mauvais chemin ou un handle stale retourne `wallet.state_conflict`. Deux handles OWNER ouverts sur le même état ne peuvent donc pas s'écraser silencieusement dans le flux KSP : après la première mutation publiée, le second est stale. Ce garde-fou n'est pas présenté comme un CAS filesystem portable : une fenêtre TOCTOU subsiste entre la dernière vérification et le remplacement, et V1 ne fournit ni verrou interprocess universel ni anti-rollback externe.
|
||||
|
||||
La rotation simple VIEW rewrappe le même `K_metadata` et n'est pas une révocation forte. `disable_view`/`recreate_view` génèrent au contraire un nouveau `K_metadata`, rechiffrent les metadata et réécrivent `owner_control`; elles constituent la révocation forte des futures metadata de l'état courant. Dans tous les cas, `format_version` et la keypair Solana restent inchangés.
|
||||
|
||||
## 20. Sizing
|
||||
|
||||
@@ -1105,7 +1126,7 @@ Une `fix` ou tranche supplémentaire est préférable à la suppression d'une ga
|
||||
|
||||
## 22. Dépendances par tranche
|
||||
|
||||
État réellement acquis au terme de `pre.005` :
|
||||
État réellement acquis au terme de `pre.007` :
|
||||
|
||||
```text
|
||||
pre.002 zeroize ^1.9
|
||||
@@ -1116,6 +1137,8 @@ pre.004 argon2 ^0.5
|
||||
pre.005 ed25519-dalek ^2.2
|
||||
solana-keypair ^3.1
|
||||
tokio déjà workspace, feature locale rt pour spawn_blocking
|
||||
pre.006 tempfile ^3.27
|
||||
pre.007 aucune nouvelle dépendance tierce
|
||||
```
|
||||
|
||||
Toutes les dépendances tierces communes restent centralisées sous `[workspace.dependencies]`; le membre Wallet active uniquement les features nécessaires. `ed25519-dalek ^2.2` est volontairement aligné avec la contrainte `^2.1.1` de `solana-keypair 3.1.2` afin de permettre une seule génération Dalek et d'activer `zeroize` sur la `SigningKey` partagée par résolution Cargo.
|
||||
@@ -1123,11 +1146,12 @@ Toutes les dépendances tierces communes restent centralisées sous `[workspace.
|
||||
Candidates restantes, à réauditer juste avant insertion :
|
||||
|
||||
```text
|
||||
tempfile ^3.27 # acquis pre.006 : temp same-directory + persist_noclobber
|
||||
solana-signer ^3.0 # seulement si un contrat public/impl l'exige réellement
|
||||
solana-signature ^3.5 # seulement si le type public l'exige
|
||||
solana-signature ^3.5 # seulement si un type public futur l'exige réellement
|
||||
```
|
||||
|
||||
`pre.007` confirme qu'une signature Solana publique peut rester un `[u8; 64]` KSP sans ajouter `solana-signature` à la surface publique. `solana-keypair` reste propriétaire de Wallet : Core continue de réexporter uniquement la `Pubkey` transversale et ne devient pas propriétaire d'un secret/signing capability.
|
||||
|
||||
Déjà présents et réutilisés :
|
||||
|
||||
```text
|
||||
@@ -1231,4 +1255,4 @@ Une future `format_version >= 2` pourra réétudier des facteurs/ancrages extern
|
||||
|
||||
## 26. Suite immédiate
|
||||
|
||||
`0.2.5-pre.003` fige le codec JSON strict, les limites structurelles, `slot_id` 16 octets, le descripteur VIEW, les DTOs d’enveloppe/key slots, les TLV transcript/AAD et la première spécification `docs/formats/KSPWALLET_V1.md`. `pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS et le wrapping de content keys. Le benchmark opérateur a ensuite permis à `pre.005` de retenir le profil initial `64 MiB / 3 / 1`, de figer les payloads `owner_control`/metadata/secret, d'introduire l'autorité Ed25519 OWNER distincte de la keypair Solana, de créer/ouvrir réellement VIEW et OWNER en mémoire et de publier un vecteur `.kspwallet` complet interopérable. **La suite immédiate est `pre.007` : signature Solana publique + administration metadata/passwords/VIEW**, sans déplacer de logique filesystem dans Config.
|
||||
`0.2.5-pre.003` fige le codec JSON strict, les limites structurelles, `slot_id` 16 octets, le descripteur VIEW, les DTOs d’enveloppe/key slots, les TLV transcript/AAD et la première spécification `docs/formats/KSPWALLET_V1.md`. `pre.004` ajoute Argon2id/XChaCha20-Poly1305/CSPRNG OS et le wrapping de content keys. Le benchmark opérateur permet à `pre.005` de retenir le profil initial `64 MiB / 3 / 1`, de figer les payloads `owner_control`/metadata/secret, d'introduire l'autorité Ed25519 OWNER distincte de la keypair Solana et de publier un vecteur complet. `pre.006` ajoute la persistence no-clobber et les ouvertures fichier. `pre.007` ajoute maintenant signature Solana, administration metadata, rotations OWNER/VIEW, révocation forte VIEW et remplacement administratif contrôlé. **La suite immédiate est `pre.008` : adapters import/export, Solana CLI JSON, keypair Base58 générique et inspect des formats de transfert**, sans déplacer les secrets ni la logique Wallet dans Config.
|
||||
|
||||
Reference in New Issue
Block a user