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

14 KiB

Delta 0.2.5-pre.005 — payloads V1 + create/open VIEW/OWNER + autorité OWNER Ed25519

Base requise

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 :

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 :

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 :

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 :

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 :

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 :

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] :

ed25519-dalek = { version = "^2.2", default-features = false }
solana-keypair = { version = "^3.1", default-features = false }

Le membre Wallet consomme :

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 :

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 :

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é :

{
  "pubkey": "<canonical Solana Pubkey Base58>",
  "alias": null,
  "notes": [
    { "id": "<16 bytes Base64url-no-pad>", "text": "..." }
  ]
}

Invariants :

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 :

offset  0..32  secret Ed25519
offset 32..64  public Ed25519

À l'ouverture OWNER :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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é :

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 :

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 :

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 :

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

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

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

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 :

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 :

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 :

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

v0.2.5-pre.005