# Delta `0.2.5-pre.003` — wire `.kspwallet` V1 strict + transcript/AAD + spécification interop ## Base requise ```text 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 : ```text 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` : ```text 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 : ```toml base64 = { version = "^0.23" } ``` Le manifest Wallet consomme : ```toml 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 : ```text 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 : ```text 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 : ```text 1 OWNER 0 ou 1 VIEW maximum 2 slots slot_id uniques ``` `view_descriptor` est figé comme : ```text 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 : ```text 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 : ```text control_version = 1 metadata_version = 1 secret_version = 1 ``` Tous utilisent le wire `xchacha20-poly1305` avec nonce 24 octets. Limites ciphertext : ```text 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é : ```text 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 : ```text KSPWALLET-V1-STATE ``` Le transcript inclut : ```text 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 : ```text 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 : ```text 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 : ```text crates/ksp-wallet-lib/tests/fixtures/kspwallet_v1_wire_only.json ``` Cette fixture : ```text 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 : ```text KspWalletEnvelopeV1 WalletKeySlotV1 WalletKeySlotRoleV1 WalletKdfParametersV1 WalletKeyWrapV1 WalletViewDescriptorV1 WalletEncryptedCompartmentV1 WalletCompartmentKindV1 WalletStateSignatureV1 identifiants d'algorithmes V1 constantes de limites/domain separation ``` `KspWalletEnvelopeV1` fournit : ```text 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 : ```text 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 : ```text 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 : ```text 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 ```text 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 ```text 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 ```text 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 ```text 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 : ```text 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 ```bash 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 : ```bash 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` ```text 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 ```text v0.2.5-pre.003 ```