v0.2.6-pre.017
This commit is contained in:
263
deltas/0.2.6/pre.017.md
Normal file
263
deltas/0.2.6/pre.017.md
Normal file
@@ -0,0 +1,263 @@
|
||||
<!-- file: deltas/0.2.6/pre.017.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.6-pre.017` — migration explicite `.kspwallet` V1 -> V2 et canaris de persistence
|
||||
|
||||
## Base requise
|
||||
|
||||
```text
|
||||
0.2.6-pre.016-fix.002 appliquée
|
||||
workspace.package.version = 0.2.6-pre.16.fix.2
|
||||
```
|
||||
|
||||
Le checkpoint opérateur de cette base est intégralement vert :
|
||||
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
python3 scripts/audit_rust_workspace_rules.py clean
|
||||
cargo check --workspace OK
|
||||
cargo clippy --workspace --all-targets OK
|
||||
cargo test --workspace OK
|
||||
ksp-wallet-lib 78 passed / 1 ignored
|
||||
Wallet Desk desktop_contract 16/16 OK
|
||||
Wallet Desk release_compliance 4/4 OK
|
||||
smokes réseau / benchmark ignored comme prévu
|
||||
```
|
||||
|
||||
`pre.016-fix.002` est donc accepté comme base de `pre.017`.
|
||||
|
||||
## Signal technique
|
||||
|
||||
Cette tranche ajoute des APIs/runtime/persistence de migration Wallet :
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.2.6-pre.17
|
||||
commit = v0.2.6-pre.017
|
||||
```
|
||||
|
||||
Aucun tag prerelease. Aucun `package.json`/`tauri.conf.json` n'est modifié.
|
||||
|
||||
## Objectif de la tranche
|
||||
|
||||
`pre.017` ferme le chantier fonctionnel V1/V2 avant la candidate documentaire `pre.018` :
|
||||
|
||||
```text
|
||||
migration V1 -> V2 explicite
|
||||
OWNER authentifie toujours la source
|
||||
migration mémoire
|
||||
copie filesystem no-clobber
|
||||
remplacement in-place atomique et stale-protected
|
||||
identité Solana conservée
|
||||
metadata protégées conservées exactement
|
||||
note IDs stables conservés
|
||||
VIEW enabled/disabled conservé
|
||||
credential VIEW cible explicite si VIEW est activé
|
||||
aucune migration implicite lors d'un open/inspect
|
||||
canaris tampering / no-clobber / stale-state
|
||||
régression Wallet Desk non-migrante
|
||||
```
|
||||
|
||||
## APIs publiques de migration
|
||||
|
||||
### Snapshot mémoire
|
||||
|
||||
```rust
|
||||
migrate_wallet_v1_to_v2(
|
||||
source,
|
||||
owner_password,
|
||||
target_view_password,
|
||||
)
|
||||
```
|
||||
|
||||
Le `source` doit être un V1 strict. La fonction :
|
||||
|
||||
1. parse le V1 ;
|
||||
2. authentifie OWNER ;
|
||||
3. récupère en mémoire l'identité Solana autorisée et le payload metadata exact ;
|
||||
4. construit un nouvel envelope V2 ;
|
||||
5. restaure alias, notes et identifiants stables de notes dans le payload V2 ;
|
||||
6. retourne un `WalletOwner` V2.
|
||||
|
||||
Aucun octet source n'est modifié.
|
||||
|
||||
### Migration vers une nouvelle destination
|
||||
|
||||
```rust
|
||||
migrate_wallet_file_v1_to_v2(
|
||||
source,
|
||||
destination,
|
||||
owner_password,
|
||||
target_view_password,
|
||||
)
|
||||
```
|
||||
|
||||
La source V1 est conservée. La destination V2 utilise la persistence no-clobber existante : une destination existante retourne `wallet.destination_exists` et reste inchangée.
|
||||
|
||||
### Migration in-place
|
||||
|
||||
```rust
|
||||
migrate_wallet_file_v1_to_v2_in_place(
|
||||
source,
|
||||
owner_password,
|
||||
target_view_password,
|
||||
)
|
||||
```
|
||||
|
||||
L'enveloppe V1 observée avant OWNER unlock devient l'état authentifié attendu. Après construction complète du V2, la publication passe par le remplacement atomique V1 existant :
|
||||
|
||||
```text
|
||||
relire source bornée
|
||||
parse V1 strict
|
||||
vérifier signature OWNER
|
||||
comparer à expected V1
|
||||
écrire/sync temp
|
||||
revérifier expected V1 juste avant publication
|
||||
rename atomique
|
||||
sync fichier / parent best-effort
|
||||
```
|
||||
|
||||
Un autre état V1 valide publié entre-temps provoque `wallet.state_conflict` et n'est jamais écrasé.
|
||||
|
||||
## Politique credential VIEW pendant migration
|
||||
|
||||
V1 et V2 ont des domaines/AAD distincts. Le VIEW key-wrap V1 ne peut donc pas être réutilisé directement dans V2.
|
||||
|
||||
Règle `pre.017` :
|
||||
|
||||
```text
|
||||
source V1 VIEW disabled -> target_view_password = None
|
||||
source V1 VIEW enabled -> target_view_password = Some(...)
|
||||
```
|
||||
|
||||
Lorsque VIEW est activé, le credential cible peut être :
|
||||
|
||||
- le même mot de passe VIEW que V1 ;
|
||||
- un nouveau mot de passe VIEW choisi pendant la migration.
|
||||
|
||||
Cela ne constitue pas une élévation supplémentaire : OWNER possède déjà en V1/V2 l'autorité de rotation du credential VIEW sans connaître l'ancien mot de passe VIEW.
|
||||
|
||||
La migration pure ne change toutefois pas la **forme de capability** : elle ne peut ni activer VIEW sur une source désactivée ni supprimer VIEW sur une source activée.
|
||||
|
||||
Une incohérence de cette politique retourne le nouveau code :
|
||||
|
||||
```text
|
||||
wallet.migration_invalid
|
||||
```
|
||||
|
||||
## Ce qui est préservé et ce qui est renouvelé
|
||||
|
||||
Préservé :
|
||||
|
||||
```text
|
||||
Solana keypair / Pubkey
|
||||
alias protégé
|
||||
notes protégées
|
||||
note IDs stables
|
||||
état VIEW enabled/disabled
|
||||
credential OWNER logique fourni par le caller
|
||||
```
|
||||
|
||||
Renouvelé en V2 :
|
||||
|
||||
```text
|
||||
owner_root
|
||||
metadata_key
|
||||
secret_key
|
||||
OWNER auth Ed25519 key
|
||||
OWNER/VIEW slot IDs
|
||||
Argon2 salts
|
||||
XChaCha nonces
|
||||
ciphertexts
|
||||
state signature
|
||||
AAD/transcripts V2
|
||||
```
|
||||
|
||||
La migration n'est donc jamais une transformation syntaxique JSON -> binaire des ciphertexts existants. Elle ouvre/authentifie V1 puis reconstruit cryptographiquement un document V2.
|
||||
|
||||
## Secret lifetime interne
|
||||
|
||||
`OwnerPassword` reste publiquement non-`Clone`. La migration doit toutefois dériver :
|
||||
|
||||
```text
|
||||
V1 OWNER KDF pour authentifier la source
|
||||
V2 OWNER KDF pour créer la cible
|
||||
```
|
||||
|
||||
`ksp-wallet-lib` ajoute donc uniquement un duplicate interne court-vivant du wrapper OWNER, non exposé aux consumers et toujours zéroïsé au drop. Aucun contrat public `Clone` n'est ajouté à `OwnerPassword` ou `ViewPassword`.
|
||||
|
||||
## Canaris `ksp-wallet-lib`
|
||||
|
||||
Nouveau module `unit_tests/migration.rs` :
|
||||
|
||||
- migration V1 full-vector -> V2 ;
|
||||
- identité Solana conservée ;
|
||||
- alias/notes/note IDs conservés ;
|
||||
- OWNER V2 rouvert avec le même credential OWNER ;
|
||||
- VIEW V2 rouvert avec le credential cible ;
|
||||
- ancien VIEW refusé lorsqu'un nouveau credential cible est choisi ;
|
||||
- forme VIEW activée/désactivée obligatoirement conservée ;
|
||||
- copie vers destination existante = no-clobber ;
|
||||
- copie réussie laisse V1 source inchangé ;
|
||||
- migration in-place produit un V2 ouvrable via APIs génériques ;
|
||||
- tampering OWNER-authenticated V1 rejeté avant publication ;
|
||||
- remplacement concurrent par un autre V1 valide = `wallet.state_conflict` ;
|
||||
- état concurrent plus récent conservé.
|
||||
|
||||
Le public API canary expose les trois fonctions de migration et `ERROR_CODE_MIGRATION_INVALID` depuis la racine de crate.
|
||||
|
||||
## Régression Wallet Desk
|
||||
|
||||
Wallet Desk ne reçoit volontairement **aucun bouton/commande de migration automatique** dans cette tranche.
|
||||
|
||||
Il reste sur :
|
||||
|
||||
```text
|
||||
create/import génériques -> V2
|
||||
open OWNER/VIEW générique -> V1 ou V2
|
||||
inventory inspect générique -> V1 ou V2
|
||||
```
|
||||
|
||||
Un nouveau canari `pre_017_wallet_desk_open_paths_remain_non_migrating` interdit l'introduction d'un appel `migrate_wallet_*` dans les chemins normaux `app_state`/inventory.
|
||||
|
||||
Cette séparation garantit qu'ouvrir un ancien V1 dans Wallet Desk ne modifie jamais le fichier à l'insu de l'utilisateur.
|
||||
|
||||
## Documentation synchronisée
|
||||
|
||||
Cette tranche met à jour :
|
||||
|
||||
```text
|
||||
docs/formats/KSPWALLET_V2.md
|
||||
docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md
|
||||
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||
docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md
|
||||
crates/ksp-wallet-lib/README.md
|
||||
crates/ksp-wallet-lib/USAGE.md
|
||||
prompts/011-V0_2_6_START_PROMPT.md
|
||||
docs/000-README.md
|
||||
docs/plans/000-README.md
|
||||
ROADMAP.md état global uniquement
|
||||
```
|
||||
|
||||
`CHANGELOG.md` reste réservé à `pre.018`/clôture de release conformément à la politique KSP.
|
||||
|
||||
## Validation opérateur requise
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-wallet-lib
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Aucun `cargo tauri build` dans cette tranche.
|
||||
|
||||
Si ce checkpoint est vert, la prochaine tranche est :
|
||||
|
||||
```text
|
||||
0.2.6-pre.018
|
||||
```
|
||||
|
||||
avec documentation/candidate finale, fermeture CWD/resources du bundle, parcours Tauri final, prompt `0.2.7`, puis `cargo tauri build` comme toute dernière opération de validation.
|
||||
Reference in New Issue
Block a user