264 lines
7.7 KiB
Markdown
264 lines
7.7 KiB
Markdown
<!-- 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.
|