Files
2026-08-19 10:52:21 +02:00

11 KiB

Delta 0.2.5-pre.003 — wire .kspwallet V1 strict + transcript/AAD + spécification interop

Base requise

livraison : 0.2.5-pre.002-fix.002
workspace.package.version = "0.2.5-pre.2.fix.2"

La base effective est l'archive Gitea opérateur 0.2.5-pre.002, complétée successivement par pre.002-fix.001 et pre.002-fix.002. Les validations opérateur de cette base sont toutes passées : cargo fmt --all, cargo check --workspace, cargo clippy --workspace --all-targets et cargo test -p ksp-wallet-lib.

Objectif

Figer la grammaire externe et les octets d'authentification contextuelle V1 avant toute implémentation KDF/AEAD réelle.

Cette tranche introduit :

KspWalletEnvelopeV1
JSON UTF-8 strict
Base64url canonique sans padding
limite fichier 1 MiB
key slots OWNER + VIEW optionnel
slot_id 16 octets
view_descriptor signé conceptuellement par OWNER
paramètres Argon2id structuraux sérialisés
compartiments owner_control / metadata / secret versionnés
state_signature Ed25519 wire
transcript OWNER déterministe TLV
AAD OWNER slot / VIEW slot
AAD owner-control / metadata / secret
fixture wire-only déterministe
spécification docs/formats/KSPWALLET_V1.md

Aucune dérivation de clé, aucun chiffrement/déchiffrement, aucune vérification Ed25519, aucune keypair Solana et aucune persistence ne sont exécutés dans pre.003.

Version Cargo

Conformément à VER-ID-009 :

0.2.5-pre.2.fix.2 -> 0.2.5-pre.3

Dépendances

serde et serde_json étaient déjà centralisés au workspace et sont maintenant consommés par Wallet pour le codec strict. La feature derive appartient au manifest membre, conformément à DEP-CARGO-003.

Nouvelle dépendance commune :

base64 = { version = "^0.23" }

Le manifest Wallet consomme :

base64.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

La génération actuelle réauditée lors de la tranche est base64 0.23.1. Le moteur URL_SAFE_NO_PAD impose l'alphabet URL-safe sans padding et rejette les trailing bits non canoniques ; Wallet effectue en plus decode -> re-encode == input.

Aucune dépendance Argon2, AEAD, CSPRNG, Ed25519 ou Solana supplémentaire n'est encore ajoutée.

Wire V1 figé

Enveloppe

Champs top-level exacts :

magic
format_version
owner_auth_public_key
view_descriptor
key_slots
owner_control
metadata
secret
state_signature

Tous les DTO serde V1 utilisent deny_unknown_fields.

Le parser effectue d'abord un probe magic + format_version afin qu'une version future soit rejetée explicitement comme format_version_unsupported avant d'essayer d'imposer la forme V1.

Binary encoding

Tous les champs binaires V1 sont Base64url sans padding. Longueurs fixes principales :

owner_auth_public_key   32 octets
slot_id                 16 octets
XChaCha nonce           24 octets
Ed25519 state signature 64 octets
KDF salt                16..64 octets

Key slots

V1 accepte exactement :

1 OWNER
0 ou 1 VIEW
maximum 2 slots
slot_id uniques

view_descriptor est figé comme :

enabled=true  + slot_id 16 octets correspondant au slot VIEW
enabled=false + slot_id=null + aucun slot VIEW

L'ordre JSON du tableau key_slots n'est pas sémantique. Le serializer KSP émet OWNER puis VIEW.

KDF structural bounds

Sans exécuter Argon2, le parser rejette avant crypto coûteuse :

algorithm   = argon2id
version     = 19
memory_kib  = 1..1_048_576
iterations  = 1..64
parallelism = 1..64
salt        = 16..64 octets

Ces plafonds sont des limites de format/rejet hostile. Ils ne fixent pas les defaults de création, benchmarkés en pre.004.

Compartiments

Versions indépendantes initiales :

control_version  = 1
metadata_version = 1
secret_version   = 1

Tous utilisent le wire xchacha20-poly1305 avec nonce 24 octets. Limites ciphertext :

owner_control <= 4096 octets
metadata      <= 65552 octets
secret        <= 4096 octets

Transcript OWNER

La signature ne porte jamais sur les octets JSON bruts.

Codec binaire figé :

ASCII(domain) || 0x00
puis pour chaque champ :
    tag    u16 big-endian
    length u64 big-endian
    value  length octets

u32 => 4 octets big-endian dans value
bool => 00 ou 01

Domain :

KSPWALLET-V1-STATE

Le transcript inclut :

magic/version
autorité publique OWNER
descripteur VIEW stable
slot OWNER complet
owner_control complet
metadata complète
secret complet
algorithme state signature

Il exclut volontairement :

KDF/salt VIEW
wrap nonce/ciphertext VIEW
state_signature.signature

Cela matérialise le contrat décidé : VIEW peut rewrapper son accès metadata pour changer son propre password sans posséder l'autorité OWNER, mais ne peut pas modifier l'état OWNER-controlled.

AAD

Domains distincts :

KSPWALLET-V1-AAD-OWNER-SLOT
KSPWALLET-V1-AAD-VIEW-SLOT
KSPWALLET-V1-AAD-OWNER-CONTROL
KSPWALLET-V1-AAD-METADATA
KSPWALLET-V1-AAD-SECRET

Les AAD utilisent le même TLV déterministe et lient au minimum magic/version, owner_auth_public_key, rôle/kind et paramètres publics pertinents.

Le nonce est fourni séparément à l'AEAD et n'est pas dupliqué dans l'AAD ; le ciphertext/tag est le résultat de l'opération et n'appartient pas à son propre AAD.

Fixture structurelle

Ajout :

crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_wire_only.json

Cette fixture :

est strictement test-only
n'utilise aucune clé réelle
contient des ciphertexts artificiels
contient une signature artificielle
n'est pas un wallet cryptographiquement valide
ne fixe pas les defaults Argon2 de production

Elle fixe en revanche les octets attendus du transcript et des cinq AAD.

API publique

La façade réexporte les contrats V1 sans exposer les modules internes :

KspWalletEnvelopeV1
WalletKeySlotV1
WalletKeySlotRoleV1
WalletKdfParametersV1
WalletKeyWrapV1
WalletViewDescriptorV1
WalletEncryptedCompartmentV1
WalletCompartmentKindV1
WalletStateSignatureV1
identifiants d'algorithmes V1
constantes de limites/domain separation

KspWalletEnvelopeV1 fournit :

parse_json(...)
to_json_bytes()
getters wire read-only
state_transcript()
owner_slot_aad()
view_slot_aad()
compartment_aad(...)

Aucun constructeur public ne permet de fabriquer arbitrairement un envelope non validé.

Les Debug des enveloppes/slots/compartiments n'affichent pas les ciphertexts, salts ou nonces complets ; ils n'exposent que les algorithmes, versions et tailles nécessaires au diagnostic.

Documentation de format

Nouveau contrat durable :

docs/formats/

docs/rules/FILE_CONTRACTS.md est mis à jour dans la même tranche, avant que cette famille devienne une convention répétée.

Ajouts :

docs/formats/000-README.md
docs/formats/KSPWALLET_V1.md

KSPWALLET_V1.md est indépendant du code Rust et décrit déjà de manière normative : grammaire, encodages, limites, strictness, key slots, tags TLV, ordre transcript et AAD. Les opérations KDF/AEAD/payloads et vecteurs crypto complets seront ajoutés dans les tranches qui les implémentent.

Frontières confirmées

Toujours interdit :

Wallet -> Config
Wallet -> Transport
Wallet -> ExecutionPolicy
Wallet -> Store
Wallet -> Tauri
Wallet -> tracing direct
Wallet -> solana-pubkey direct
Wallet -> environnement processus

Pubkey reste possédée/réexportée par ksp-core-lib; pre.003 n'introduit aucun type Pubkey Solana direct.

Le target comportemental reste TRACING_TARGET = "ksp-wallet-lib" dans src/constants.rs et le parsing wire émet uniquement un trace non secret via ksp-logging-lib après validation réussie.

Tests ajoutés/étendus

Unitaires wire

fixture V1 parse + semantic round-trip
unknown format version
unknown top-level field
Base64url paddé/non canonique
mismatch view_descriptor / VIEW slot
KDF zéro/pathologique
fichier > 1 MiB
Debug sans ciphertext complet

Unitaires transcript/AAD

state transcript exact
OWNER slot AAD exact
VIEW slot AAD exact
owner-control AAD exact
metadata AAD exact
secret AAD exact
domain separation

Intégration

Le canari public API vérifie que l'enveloppe, les rôles et codecs transcript/AAD sont réellement consommables depuis le crate-root.

Le canari de dépendances est étendu à base64, serde et serde_json tout en conservant le firewall existant.

Fichiers ajoutés

crates/ksp-wallet-lib/src/transcript.rs
crates/ksp-wallet-lib/src/wire.rs
crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_wire_only.json
crates/ksp-wallet-lib/unit_tests/transcript.rs
crates/ksp-wallet-lib/unit_tests/wire.rs
docs/formats/000-README.md
docs/formats/KSPWALLET_V1.md
deltas/0.2.5/pre.003.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/lib.rs
crates/ksp-wallet-lib/tests/dependency_boundary.rs
crates/ksp-wallet-lib/tests/public_api.rs
docs/000-README.md
docs/plans/000-README.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md
docs/rules/FILE_CONTRACTS.md

Fichiers supprimés

Aucun.

Validations exécutées dans l'environnement de préparation

Cargo/Rust n'est pas installé dans l'environnement de préparation. Les contrôles statiques suivants ont été exécutés :

diff exact contre la base pre.002 + fix.001 + fix.002
scan production : aucun unwrap/expect/panic/?
scan boundaries : aucun solana_pubkey:: / tracing:: / std::env:: / Config / Transport / Tauri
scan RUST-API-004 : helpers pub(crate) transcript réexportés au crate-root
scan manifests : dépendances centralisées workspace
contrôle manuel des headers/version de fichiers
contrôle des longueurs/fixture et vecteurs transcript/AAD via implémentation indépendante Python du TLV

Validations opérateur requises

cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-wallet-lib

Si cette tranche constitue le checkpoint Rust de clôture intermédiaire retenu, exécuter également :

cargo test --workspace

Décisions

  • geler le wire JSON V1 maintenant, avant la crypto réelle ;
  • ne pas signer/canonicaliser les octets JSON ;
  • utiliser un transcript sémantique TLV déterministe ;
  • rendre l'ordre key_slots JSON non sémantique ;
  • fixer slot_id à 16 octets ;
  • permettre VIEW self-service uniquement via exclusion contrôlée de son wrapping du transcript OWNER ;
  • sérialiser les versions de payload indépendamment ;
  • rejeter les paramètres KDF structurellement pathologiques avant toute KDF ;
  • ne pas ajouter Argon2/AEAD/Ed25519 avant consommation effective ;
  • créer docs/formats/ avec son contrat dans FILE_CONTRACTS.md dans la même tranche.

Questions ouvertes reportées à pre.004

defaults Argon2id mesurés sur machines cibles
API exacte de spawn_blocking pour KDF
crate CSPRNG exacte lors de l'implémentation
crate XChaCha20-Poly1305 exacte/feature set final
layout plaintext exact des wrapped capabilities
premiers vecteurs crypto publics valides

Ces questions ne doivent pas modifier silencieusement le wire/transcript/AAD figés par pre.003.

Commit attendu

v0.2.5-pre.003