v0.5.2-pre.004

This commit is contained in:
2026-08-10 16:43:19 +02:00
parent ef630e3123
commit b8e6751747
15 changed files with 1489 additions and 89 deletions

View File

@@ -1,11 +1,11 @@
<!-- file: ks-wallet/USAGE.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# Utilisation de ks-wallet
## Statut
En `0.5.2-pre.003`, le manager valide désormais la structure complète du conteneur binaire `.kswallet` v1 avant de retourner un handle. Le password, le chiffrement/déchiffrement réel et la création persistante publique arrivent en `pre.004`.
En `0.5.2-pre.004`, le manager gère le premier cycle de vie natif complet : création `.kswallet`, ouverture authentifiée, ouverture d'un fichier explicitement sélectionné et changement de mot de passe. La migration legacy et l'import/export restent pour `pre.005`.
## Valider un alias
@@ -166,7 +166,7 @@ let wallets = match manager.scan().await {
};
```
`scan()` est non récursif et ne traite que les `<alias>.kswallet`. En `pre.003`, chaque candidat doit décoder entièrement selon le format v1 : magic/version, flags, IDs crypto, paramètres KDF, pubkey déclarée, alias, sel, nonce et ciphertext de taille exacte. Le nom du fichier et l'alias encodé doivent correspondre.
`scan()` est non récursif et ne traite que les `<alias>.kswallet`. Chaque candidat doit décoder entièrement selon le format v1 : magic/version, flags, IDs crypto, paramètres KDF, pubkey déclarée, alias, sel, nonce et ciphertext de taille exacte. Le nom du fichier et l'alias encodé doivent correspondre.
Le lookup par alias reste limité à la racine configurée :
@@ -194,14 +194,125 @@ println!("pubkey={}", handle.public_key());
println!("format={}", handle.format_version());
```
`WalletFileHandle` conserve le chemin en interne mais ne fournit aucun getter public vers ce chemin et son `Debug` ne l'affiche pas. Sa pubkey est une identité **déclarée** avant unlock : elle ne deviendra authentifiée qu'après validation AEAD et comparaison avec la keypair déchiffrée en `pre.004`. `inspect_file()` ne modifie pas la configuration et n'ajoute pas le fichier sélectionné au résultat de `scan()`.
`WalletFileHandle` conserve le chemin en interne mais ne fournit aucun getter public vers ce chemin et son `Debug` ne l'affiche pas. Sa pubkey reste une identité **déclarée** tant que le fichier n'a pas été ouvert : `unlock()`/`unlock_file()` authentifient l'AEAD puis vérifient que la pubkey de la keypair déchiffrée correspond exactement au header avant de retourner `UnlockedWallet`. `inspect_file()` ne modifie pas la configuration et n'ajoute pas le fichier sélectionné au résultat de `scan()`.
## Cycle de vie natif protégé par mot de passe
`WalletPassword` prend possession du mot de passe pour **une seule opération**. Il n'est ni clonable ni sérialisable et son `Debug` est toujours redacted. L'appelant construit donc une nouvelle valeur pour chaque création, ouverture ou changement de mot de passe.
### Créer un wallet persistant natif
```rust
let alias = match ks_wallet::WalletAlias::parse("operator") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let password = match ks_wallet::WalletPassword::new(password_string) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let wallet = match manager.create(alias.clone(), password).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
println!("pubkey={}", wallet.public_key());
```
`create()` génère une keypair Solana, la protège avec Argon2id + XChaCha20-Poly1305, publie `<alias>.kswallet` sans écraser une destination existante et retourne une capacité `UnlockedWallet`. Aucun getter public ne fournit les bytes privés.
### Ouvrir par alias
```rust
let password = match ks_wallet::WalletPassword::new(password_string) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let wallet = match manager.unlock(&alias, password).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let signature = match wallet.sign_message(b"authenticated operation") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
println!("signature={signature}");
wallet.lock();
```
Un mauvais mot de passe ou une altération des données authentifiées échoue avant la création de la capacité de signature. `lock()` consomme explicitement `UnlockedWallet`; laisser la valeur sortir de portée a le même effet de durée de vie.
### Ouvrir un fichier choisi hors du store
```rust
let handle = match manager.inspect_file(selected_path).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let password = match ks_wallet::WalletPassword::new(password_string) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let wallet = match manager.unlock_file(&handle, password).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
```
Le handle est revalidé contre le fichier avant l'ouverture authentifiée. Le fichier externe reste extérieur au scan/configuration du manager.
### Changer le mot de passe
```rust
let current_password = match ks_wallet::WalletPassword::new(current_password_string) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let new_password = match ks_wallet::WalletPassword::new(new_password_string) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
let handle = match manager
.change_password(&alias, current_password, new_password)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
println!("pubkey={}", handle.public_key());
```
`change_password()` n'altère pas la keypair : il authentifie l'ancien conteneur, rechiffre exactement le même matériau avec un nouveau sel/nonce et remplace atomiquement le `.kswallet`. La pubkey reste identique. Cette opération ne peut pas révoquer rétroactivement une `UnlockedWallet` déjà remise à un consommateur ; cette capacité doit être `lock()`/dropée par son propriétaire.
## Format binaire v1
Le layout exact, les bornes et la politique de publication cible sont documentés dans [`../docs/NATIVE_FORMAT.md`](../docs/NATIVE_FORMAT.md). `pre.003` implémente la lecture et le décodage stricts nécessaires au scan ; l'encodage de création et la publication atomique sont introduits en `pre.004`. Le payload secret v1 est strictement borné à une keypair Solana de 64 octets et produit un ciphertext/tag de 80 octets.
Le layout exact, les bornes et la politique de publication sont documentés dans [`../docs/NATIVE_FORMAT.md`](../docs/NATIVE_FORMAT.md). `pre.004` implémente désormais l'encodage, la lecture, la protection et la publication. Le payload secret v1 est strictement borné à une keypair Solana de 64 octets et produit un ciphertext/tag de 80 octets.
Le format encode Argon2id v19 avec un profil d'écriture par défaut de 64 MiB / 3 passes / 4 lanes et XChaCha20-Poly1305 avec nonce 24 octets. Ces paramètres sont seulement encodés/validés en `pre.003`; leur exécution cryptographique commence en `pre.004`.
Le format exécute Argon2id v19 avec un profil d'écriture par défaut de 64 MiB / 3 passes / 4 lanes et XChaCha20-Poly1305 avec nonce 24 octets. Les paramètres lus depuis un fichier restent bornés avant toute exécution du KDF.
## Cible persistante `0.5.2`
@@ -211,7 +322,7 @@ Le nouveau stockage natif doit utiliser :
<alias>.kswallet
```
Le fichier `.kswallet` sera un conteneur binaire versionné protégé par le mot de passe propre au wallet.
Le fichier `.kswallet` est un conteneur binaire versionné protégé par le mot de passe propre au wallet.
L'API cible doit permettre conceptuellement :
@@ -229,7 +340,7 @@ Le store découvrira les wallets persistants en scannant son répertoire résolu
Le changement de mot de passe rechiffre la même keypair. Il n'existe pas de rotation normale du secret Ed25519 conservant la même pubkey : une nouvelle clé secrète signifie une nouvelle identité Solana.
Le modèle runtime exact du wallet ouvert sera décidé dans les prereleases d'implémentation. Les noms `WalletManager`, `WalletFileHandle`, `WalletIdentity` et `WalletPersistence` font désormais partie de l'API de base.
Le modèle runtime ouvert est `UnlockedWallet`, capacité non clonable possédant le signer authentifié. `WalletManager`, `WalletFileHandle`, `WalletIdentity`, `WalletPersistence`, `WalletPassword` et `UnlockedWallet` font désormais partie de l'API de base.
## Import/export cible
@@ -262,4 +373,4 @@ L'export vers Solana CLI utilise son tableau JSON de 64 octets. Pour Phantom, So
- rejet des keypairs corrompus ;
- vérification des permissions Unix privées.
`pre.003` ajoute le décodage/validation v1 stricts, les bornes KDF/fichier et les tests de corruption/réouverture. `pre.004` ajoute l'encodage de création, la publication atomique, le password/changement de password et le chiffrement effectif ; les tranches suivantes couvrent la migration, l'import/export et les canaris de non-divulgation.
`pre.003` ajoute le décodage/validation v1 stricts et les bornes KDF/fichier. `pre.004` ajoute l'encodage, la publication atomique, le password, la dérivation/chiffrement effectifs, l'ouverture authentifiée, le changement de password et les tests de non-divulgation associés. Les tranches suivantes couvrent migration et import/export puis l'intégration des consommateurs.