Files
2026-08-19 13:09:16 +02:00

488 lines
14 KiB
Markdown

<!-- file: deltas/0.2.5/pre.005.md -->
<!-- version: 1 -->
# Delta `0.2.5-pre.005` — payloads V1 + create/open VIEW/OWNER + autorité OWNER Ed25519
## Base requise
```text
livraison : 0.2.5-pre.004-fix.002
workspace.package.version = "0.2.5-pre.4.fix.2"
```
Les validations opérateur de cette base sont propres :
```text
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --workspace --all-targets OK
cargo test -p ksp-wallet-lib OK : 22 passés, 1 benchmark ignoré
+ 2 dependency-boundary
+ 5 public API
+ 2 doc-tests compile_fail
cargo test -p ksp-logging-lib --test ownership OK : 2/2
cargo test --workspace OK
```
Le benchmark Argon2 opérateur acquis avant cette tranche donne :
```text
64 MiB / 3 passes / 1 lane -> 1742 ms
128 MiB / 3 passes / 1 lane -> 3459 ms
256 MiB / 3 passes / 1 lane -> 6925 ms
```
## Objectif
Matérialiser les compartiments protégés et les capabilities in-memory complètes de lecture sans ouvrir encore la persistence filesystem ni l'administration :
```text
profil de création KSP V1 issu du benchmark
payload owner_control exact
payload metadata exact
payload secret Solana exact
autorité de format Ed25519 distincte de la keypair Solana
state_signature OWNER vérifiée avant KDF
create V1 in-memory
open VIEW indépendant
open OWNER indépendant
inspection locked authentifiée
vecteur .kspwallet complet interopérable
```
Restent explicitement hors tranche :
```text
filesystem / persistence atomique / no-clobber
signature Solana publique
export du secret
modification alias/notes
rotation password VIEW par VIEW
rotation password VIEW/OWNER par OWNER
disable/recreate VIEW
révocation VIEW forte
import/export
```
Ces opérations restent prévues dans `pre.006+` sans déplacer de responsabilité vers Config ou Transport.
## Version Cargo
Conformément au séquencement des prereleases :
```text
0.2.5-pre.4.fix.2 -> 0.2.5-pre.5
```
`workspace.package.version` reste le signal technique de la tranche.
## Profil Argon2 de création KSP
À partir des mesures opérateur, les créations KSP V1 utilisent initialement :
```text
algorithm Argon2id
version 19
memory_kib 65_536
iterations 3
parallelism 1
salt 32 octets CSPRNG indépendants par slot
output KEK 32 octets
```
Le choix vise un coût d'environ 1,7 s sur la machine de calibration plutôt que les profils 128/256 MiB mesurés à environ 3,5/6,9 s.
Ce profil est un **default de création KSP**, jamais une condition de lecture du format : chaque slot sérialise toujours ses paramètres KDF complets. Un ancien wallet reste donc lisible après un futur durcissement des defaults.
## Dépendances introduites
Nouvelles dépendances communes sous `[workspace.dependencies]` :
```toml
ed25519-dalek = { version = "^2.2", default-features = false }
solana-keypair = { version = "^3.1", default-features = false }
```
Le membre Wallet consomme :
```toml
ed25519-dalek = { workspace = true, features = ["signature", "zeroize"] }
solana-keypair.workspace = true
tokio = { workspace = true, features = ["rt"] }
```
`tokio` était déjà une dépendance commune du workspace ; seule la feature locale nécessaire à `spawn_blocking` est activée.
Le choix direct `ed25519-dalek ^2.2` est volontaire : `solana-keypair 3.1.2` dépend lui-même de `ed25519-dalek ^2.1.1`. Rester dans la génération 2.x doit permettre à Cargo d'unifier Dalek au lieu d'ajouter une branche 3.x concurrente. La feature `zeroize` est activée explicitement.
Aucune dépendance directe `solana-pubkey`, `solana-signer` ou `solana-signature` n'est ajoutée. La Pubkey du domaine Wallet reste exclusivement `ksp_core_lib::Pubkey`.
Un `cargo tree` opérateur est obligatoire pour confirmer la résolution réelle après application.
## Content keys et autorité OWNER
La création génère indépendamment :
```text
K_owner_root 32 octets
K_metadata 32 octets
K_secret 32 octets
admin seed 32 octets Ed25519
Solana secret 32 octets Ed25519 distincts
```
La clé d'administration du format reste distincte de la keypair Solana.
Le slot OWNER wrappe uniquement `K_owner_root`.
Le slot VIEW optionnel wrappe uniquement `K_metadata`.
OWNER ne dépend donc jamais de VIEW et VIEW ne reçoit jamais `K_owner_root` ni `K_secret`.
## Payload `owner_control`
Le plaintext V1 est fixé à exactement 96 octets :
```text
offset 0..32 admin Ed25519 secret seed
offset 32..64 K_metadata
offset 64..96 K_secret
```
Il est chiffré sous `K_owner_root` avec l'AAD compartment figé en `pre.003`.
À l'ouverture OWNER, la seed admin redérive obligatoirement la `owner_auth_public_key` de l'enveloppe. Un mismatch est rejeté.
## Payload metadata
Le plaintext metadata est un JSON UTF-8 strict et protégé :
```json
{
"pubkey": "<canonical Solana Pubkey Base58>",
"alias": null,
"notes": [
{ "id": "<16 bytes Base64url-no-pad>", "text": "..." }
]
}
```
Invariants :
```text
Pubkey type ksp_core_lib::Pubkey après parsing
alias optionnel, <= 256 octets UTF-8
notes maximum 64
note.id exactement 16 octets, unique dans le payload
note.text <= 8192 octets UTF-8
payload metadata <= 65 536 octets
```
Les IDs de note sont générés au CSPRNG. Une collision pendant la création est traitée comme une défaillance de génération plutôt que de produire des IDs ambigus. Un payload décodé avec IDs dupliqués est rejeté.
## Payload secret Solana
Le plaintext secret est exactement le keypair Ed25519 Solana 64 octets :
```text
offset 0..32 secret Ed25519
offset 32..64 public Ed25519
```
À l'ouverture OWNER :
```text
1. le payload doit faire exactement 64 octets ;
2. solana-keypair valide la cohérence secret/public ;
3. les 32 octets publics construisent un ksp_core_lib::Pubkey ;
4. cette Pubkey doit correspondre exactement à la Pubkey metadata.
```
Un mismatch n'est jamais accepté comme changement d'identité implicite.
## `state_signature` OWNER
La signature d'état V1 est désormais effective :
```text
algorithm Ed25519
public key owner_auth_public_key dans l'enveloppe
message state_transcript TLV figé en pre.003
signature 64 octets
```
La vérification stricte de cette signature intervient **avant toute dérivation Argon2** lors de `inspect`, `open VIEW` et `open OWNER`.
Le transcript continue d'exclure le matériau self-service rotatable du slot VIEW (`KDF/salt/wrap`) et d'authentifier son descripteur stable OWNER-controlled. Cette frontière préserve la future rotation du propre password VIEW sans donner à VIEW la capacité de modifier metadata/secret/OWNER-state.
## API in-memory
La crate expose désormais :
```rust
create_wallet_v1(owner_password, optional_view_password, metadata).await
open_wallet_view_v1(source, view_password).await
open_wallet_owner_v1(source, owner_password).await
inspect_locked_wallet_v1(source)
```
`create_wallet_v1` crée une nouvelle identité Solana et une autorité admin séparée, mais **n'effectue aucun I/O filesystem**.
`WalletOwner::to_json_bytes()` et `WalletView::to_json_bytes()` permettent seulement de projeter l'enveloppe verrouillée complète en mémoire en attendant `pre.006`.
### VIEW
L'ouverture VIEW :
```text
parse strict
verify state_signature OWNER
Argon2 du seul slot VIEW via spawn_blocking
unwrap K_metadata
decrypt metadata
retour WalletView
```
Elle ne déchiffre jamais `owner_control` ou `secret`, ne construit jamais la keypair Solana et ne matérialise jamais `K_secret`.
### OWNER
L'ouverture OWNER :
```text
parse strict
verify state_signature OWNER
Argon2 du slot OWNER via spawn_blocking
unwrap K_owner_root
decrypt owner_control
valider admin secret -> owner_auth_public_key
récupérer K_metadata / K_secret
decrypt metadata
decrypt et valider keypair Solana 64 octets
vérifier Pubkey keypair == Pubkey metadata
retour WalletOwner
```
OWNER reste fonctionnel même si VIEW est absent, inconnu ou inutilisable.
## Secret memory
Les invariants précédents sont conservés :
```text
password wrappers non-Clone et zeroized au Drop
content keys non-Copy/non-Clone et zeroized au Drop
buffers plaintext owner-control/metadata/secret zeroized après consommation raisonnable
admin SigningKey avec feature zeroize
solana-keypair conservé uniquement dans l'état OWNER opaque
aucun Debug/Display public de secret
aucun secret/password/alias/note dans les logs
```
Les limites habituelles de zeroization Rust restent documentées : aucune garantie absolue n'est revendiquée contre toutes les copies temporaires possibles du compilateur/runtime.
## Logging
Les nouveaux flux utilisent exclusivement `ksp-logging-lib` avec :
```text
target = crate::TRACING_TARGET = "ksp-wallet-lib"
```
Les événements ne contiennent que l'opération/capability/version non secrète et l'issue catégorisée. Aucun path, password, secret, metadata ou payload arbitraire n'est journalisé.
## Vecteur complet d'interopérabilité
Nouveaux fixtures publics TEST ONLY :
```text
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector_meta.json
```
Ils fixent de manière auto-contenue :
```text
password OWNER connu
password VIEW connu
salts / slot IDs / nonces
content keys test-only
admin key test-only
keypair Solana test-only
Pubkey / alias / notes attendus
ciphertexts owner-control / metadata / secret
wrapped keys OWNER / VIEW
state transcript
state signature Ed25519
```
Le vecteur utilise volontairement un KDF faible/test-fast (`32 KiB / 2 / 1`) et ne constitue jamais un wallet ni un profil de production recommandé.
Une implémentation indépendante du code Rust a revérifié :
```text
state transcript exact : 1182 octets
signature Ed25519 valide
dérivations OWNER/VIEW exactes
unwrap K_owner_root / K_metadata exact
déchiffrement owner_control exact
déchiffrement metadata exact
déchiffrement secret exact : 64 octets
admin seed -> owner_auth_public_key exacte
secret Solana -> public half cohérente
```
## Tests
Les tests Wallet ajoutent notamment :
```text
vecteur complet ouvre VIEW et OWNER indépendamment
password VIEW ne déverrouille pas OWNER
password OWNER ne déverrouille pas VIEW
mutation OWNER-authentifiée metadata rejetée
mutation du seul wrapping VIEW ne casse pas OWNER mais invalide VIEW
fichier verrouillé ne contient pas Pubkey/alias/notes en clair
salts / slot IDs / nonces OWNER/VIEW indépendants
create utilise le profil KSP benchmarké
projection locked reste privée
duplicate note IDs rejetés
```
Les tests d'architecture continuent d'imposer :
```text
Wallet -X-> Config
Wallet -X-> Transport
Wallet -X-> ExecutionPolicy
Wallet -X-> Store
Wallet -X-> Tauri
Wallet -X-> tracing direct
Wallet -X-> solana-pubkey direct
Wallet -X-> accès environnement direct
```
## Documentation
Mises à jour :
```text
ROADMAP.md
docs/000-README.md
docs/formats/000-README.md
docs/formats/KSPWALLET_V1.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md
```
`KSPWALLET_V1.md` devient suffisamment précis pour les payloads et procédures `pre.005`; la persistence et les opérations d'administration restent explicitement futures.
`CHANGELOG.md` reste inchangé : la policy KSP réserve les entrées de release stable à la clôture/publication, les prereleases étant décrites par plans/deltas/roadmap.
## Fichiers ajoutés
```text
crates/ksp-wallet-lib/src/payload.rs
crates/ksp-wallet-lib/src/wallet.rs
crates/ksp-wallet-lib/unit_tests/payload.rs
crates/ksp-wallet-lib/unit_tests/wallet.rs
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector_meta.json
deltas/0.2.5/pre.005.md
```
## Fichiers modifiés
```text
Cargo.toml
ROADMAP.md
crates/ksp-wallet-lib/Cargo.toml
crates/ksp-wallet-lib/src/constants.rs
crates/ksp-wallet-lib/src/crypto.rs
crates/ksp-wallet-lib/src/error.rs
crates/ksp-wallet-lib/src/lib.rs
crates/ksp-wallet-lib/src/metadata.rs
crates/ksp-wallet-lib/src/owner.rs
crates/ksp-wallet-lib/src/password.rs
crates/ksp-wallet-lib/src/view.rs
crates/ksp-wallet-lib/src/wire.rs
crates/ksp-wallet-lib/tests/dependency_boundary.rs
crates/ksp-wallet-lib/tests/public_api.rs
docs/000-README.md
docs/formats/000-README.md
docs/formats/KSPWALLET_V1.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md
```
Aucun fichier hors de cette différence avec `pre.004-fix.002` ne doit être embarqué.
## Fichiers supprimés
Aucun.
## Validation attendue après application
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-wallet-lib
cargo test --workspace
```
Audit de dépendances obligatoire :
```bash
cargo tree -p ksp-wallet-lib
cargo tree -p ksp-wallet-lib -d
cargo tree -e features -p ksp-wallet-lib
cargo tree -i ed25519-dalek@2.2.0
cargo tree -i solana-keypair@3.1.2
cargo tree -i solana-address@2.7.0
```
Points à confirmer :
```text
une seule génération ed25519-dalek dans le sous-arbre Wallet
pas de duplication crypto injustifiée introduite par Wallet
solana-address résolu sans second type Pubkey KSP
aucune dépendance Config/Transport/Tauri depuis Wallet
zeroize effectivement activé sur ed25519-dalek
```
## Décisions
- retenir `65 536 KiB / 3 / 1` comme profil initial de création KSP V1 à partir du benchmark opérateur ;
- conserver les paramètres KDF sérialisés comme autorité de lecture du wallet ;
- utiliser une autorité Ed25519 de format distincte de la keypair Solana ;
- vérifier la state signature avant tout KDF ;
- garder `ed25519-dalek` en génération `2.x` pour s'aligner sur `solana-keypair 3.1.2` ;
- ne jamais dépendre directement de `solana-pubkey` dans Wallet ;
- ouvrir VIEW sans matérialiser le secret Solana ;
- garder OWNER indépendant de VIEW ;
- ne pas introduire persistence/signature/export/admin avant leurs tranches dédiées.
## Questions ouvertes
Aucune question de format V1 nouvelle n'est introduite par cette tranche.
`pre.006` doit choisir et prouver la stratégie filesystem portable pour :
```text
create destination nouvelle/no-clobber
atomic replace des futures mutations OWNER/VIEW
fsync/durability raisonnable selon plateforme
fault injection
concurrence
récupération après interruption
```
## Commit attendu
```text
v0.2.5-pre.005
```