Files
khadhroony-solana-project/deltas/0.2.5/pre.003.md
2026-08-19 10:52:21 +02:00

440 lines
11 KiB
Markdown

<!-- file: deltas/0.2.5/pre.003.md -->
<!-- version: 1 -->
# 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
```