304 lines
11 KiB
Markdown
304 lines
11 KiB
Markdown
<!-- file: crates/ksp-wallet-lib/USAGE.md -->
|
||
<!-- version: 4 -->
|
||
|
||
# Utilisation de `ksp-wallet-lib`
|
||
|
||
Ce guide présente les principales surfaces publiques multi-version de Wallet. V1 reste spécifié par [`../../docs/formats/KSPWALLET_V1.md`](../../docs/formats/KSPWALLET_V1.md) et V2 par [`../../docs/formats/KSPWALLET_V2.md`](../../docs/formats/KSPWALLET_V2.md).
|
||
|
||
Les exemples utilisent des chemins explicites : Wallet ne lit ni Config ni environnement pour découvrir un répertoire.
|
||
|
||
## 1. Créer un nouveau `.kspwallet`
|
||
|
||
```rust
|
||
async fn create_example() -> ksp_core_lib::Result<()> {
|
||
let metadata = ksp_wallet_lib::WalletCreateMetadata::new(
|
||
Some(std::string::String::from("devnet-main")),
|
||
vec![std::string::String::from("wallet de test")],
|
||
);
|
||
let owner_password = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("OWNER-PASSWORD"));
|
||
let view_password = ksp_wallet_lib::ViewPassword::new(std::string::String::from("VIEW-PASSWORD"));
|
||
let created = ksp_wallet_lib::create_wallet_file(
|
||
"wallets/devnet-main.kspwallet",
|
||
owner_password,
|
||
Some(view_password),
|
||
metadata,
|
||
)
|
||
.await;
|
||
let owner = match created {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
println!("{}", owner.pubkey());
|
||
return Ok(());
|
||
}
|
||
```
|
||
|
||
La destination doit avoir un parent existant. Une destination existante n'est jamais remplacée par une création.
|
||
|
||
Pour créer uniquement en mémoire avec le default, utiliser `create_wallet`. `WalletOwner::to_native_bytes()` restitue ensuite le wire natif courant. Les APIs `_v1` et `_v2` restent disponibles lorsqu’un caller doit forcer une version précise.
|
||
|
||
## 2. Inspecter un wallet verrouillé
|
||
|
||
```rust
|
||
async fn inspect_example() -> ksp_core_lib::Result<()> {
|
||
let inspected = ksp_wallet_lib::inspect_locked_wallet_file("wallets/devnet-main.kspwallet").await;
|
||
let locked = match inspected {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
println!("format={}", locked.format_version());
|
||
println!("view_enabled={}", locked.view_enabled());
|
||
return Ok(());
|
||
}
|
||
```
|
||
|
||
Cette projection ne contient volontairement ni Pubkey, ni alias, ni notes.
|
||
|
||
## 3. Ouvrir avec VIEW
|
||
|
||
```rust
|
||
async fn open_view_example() -> ksp_core_lib::Result<()> {
|
||
let password = ksp_wallet_lib::ViewPassword::new(std::string::String::from("VIEW-PASSWORD"));
|
||
let opened = ksp_wallet_lib::open_wallet_view_file("wallets/devnet-main.kspwallet", password).await;
|
||
let mut view = match opened {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
println!("pubkey={}", view.pubkey());
|
||
println!("alias={:?}", view.alias());
|
||
for note in view.notes() {
|
||
println!("note={} text={}", note.id(), note.text());
|
||
}
|
||
let rotated = view
|
||
.rotate_view_password(
|
||
"wallets/devnet-main.kspwallet",
|
||
ksp_wallet_lib::ViewPassword::new(std::string::String::from("NEW-VIEW-PASSWORD")),
|
||
)
|
||
.await;
|
||
if let Err(error) = rotated {
|
||
return Err(error);
|
||
}
|
||
return Ok(());
|
||
}
|
||
```
|
||
|
||
VIEW ne possède aucune API `sign`, `export_transfer`, `update_alias`, `add_note`, `rotate_owner_password`, `disable_view` ou `recreate_view`.
|
||
|
||
## 4. Ouvrir avec OWNER et signer
|
||
|
||
```rust
|
||
async fn sign_example(message: &[u8]) -> ksp_core_lib::Result<[u8; ksp_wallet_lib::KSPWALLET_SOLANA_SIGNATURE_BYTES]> {
|
||
let password = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("OWNER-PASSWORD"));
|
||
let opened = ksp_wallet_lib::open_wallet_owner_file("wallets/devnet-main.kspwallet", password).await;
|
||
let owner = match opened {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
return owner.sign(message);
|
||
}
|
||
```
|
||
|
||
La signature retournée contient 64 octets Ed25519. Aucun getter public ne retourne la keypair Solana.
|
||
|
||
## 5. Administrer alias et notes
|
||
|
||
```rust
|
||
async fn metadata_example() -> ksp_core_lib::Result<()> {
|
||
let password = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("OWNER-PASSWORD"));
|
||
let opened = ksp_wallet_lib::open_wallet_owner_file("wallets/devnet-main.kspwallet", password).await;
|
||
let mut owner = match opened {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
let alias_result = owner
|
||
.update_alias("wallets/devnet-main.kspwallet", Some(std::string::String::from("primary-devnet")))
|
||
.await;
|
||
if let Err(error) = alias_result {
|
||
return Err(error);
|
||
}
|
||
let added = owner
|
||
.add_note("wallets/devnet-main.kspwallet", std::string::String::from("rotation trimestrielle"))
|
||
.await;
|
||
let note_id = match added {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
let updated = owner
|
||
.update_note(
|
||
"wallets/devnet-main.kspwallet",
|
||
note_id.as_str(),
|
||
std::string::String::from("rotation contrôlée"),
|
||
)
|
||
.await;
|
||
if let Err(error) = updated {
|
||
return Err(error);
|
||
}
|
||
return Ok(());
|
||
}
|
||
```
|
||
|
||
Chaque mutation vérifie que le fichier courant correspond encore à l'état authentifié attendu par le handle. Un handle stale reçoit `wallet.state_conflict`.
|
||
|
||
## 6. Rotations de credentials
|
||
|
||
OWNER peut changer son password :
|
||
|
||
```rust
|
||
let result = owner
|
||
.rotate_owner_password(
|
||
"wallets/devnet-main.kspwallet",
|
||
ksp_wallet_lib::OwnerPassword::new(std::string::String::from("NEW-OWNER-PASSWORD")),
|
||
)
|
||
.await;
|
||
```
|
||
|
||
OWNER peut aussi changer le password VIEW sans connaître l'ancien :
|
||
|
||
```rust
|
||
let result = owner
|
||
.rotate_view_password(
|
||
"wallets/devnet-main.kspwallet",
|
||
ksp_wallet_lib::ViewPassword::new(std::string::String::from("NEW-VIEW-PASSWORD")),
|
||
)
|
||
.await;
|
||
```
|
||
|
||
Ces rotations de credentials ne changent pas la keypair Solana.
|
||
|
||
## 7. Révocation forte VIEW
|
||
|
||
La simple rotation VIEW remplace le credential courant mais ne peut pas retirer des metadata déjà connues d'un ancien détenteur VIEW. OWNER peut effectuer une révocation forte pour les metadata futures :
|
||
|
||
```rust
|
||
let disabled = owner.disable_view("wallets/devnet-main.kspwallet").await;
|
||
if let Err(error) = disabled {
|
||
return Err(error);
|
||
}
|
||
|
||
let recreated = owner
|
||
.recreate_view(
|
||
"wallets/devnet-main.kspwallet",
|
||
ksp_wallet_lib::ViewPassword::new(std::string::String::from("FRESH-VIEW-PASSWORD")),
|
||
)
|
||
.await;
|
||
```
|
||
|
||
`disable_view` rekey les metadata. `recreate_view` crée ensuite un nouveau slot VIEW autour du nouveau `K_metadata`. La keypair Solana reste inchangée.
|
||
|
||
## 8. Inspecter et importer une keypair externe
|
||
|
||
Le format doit être choisi explicitement ; Wallet ne fait pas d'auto-détection heuristique.
|
||
|
||
```rust
|
||
async fn inspect_transfer_example(source: &[u8]) -> ksp_core_lib::Result<()> {
|
||
let inspected = ksp_wallet_lib::inspect_wallet_transfer(
|
||
source,
|
||
ksp_wallet_lib::WalletTransferFormat::SolanaCliJson,
|
||
);
|
||
let info = match inspected {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
println!("{}", info.pubkey());
|
||
return Ok(());
|
||
}
|
||
```
|
||
|
||
Import fichier vers un nouveau `.kspwallet` :
|
||
|
||
```rust
|
||
let imported = ksp_wallet_lib::import_wallet_transfer_file(
|
||
"wallets/imported.kspwallet",
|
||
"wallets/legacy-id.json",
|
||
ksp_wallet_lib::WalletTransferFormat::SolanaCliJson,
|
||
ksp_wallet_lib::OwnerPassword::new(std::string::String::from("OWNER-PASSWORD")),
|
||
None,
|
||
ksp_wallet_lib::WalletCreateMetadata::new(Some(std::string::String::from("imported")), vec![]),
|
||
)
|
||
.await;
|
||
```
|
||
|
||
L'import valide la cohérence secret/public, conserve exactement la keypair Solana et génère un nouvel environnement cryptographique KSP. Une destination native existante n'est jamais écrasée.
|
||
|
||
## 9. Exporter avec OWNER
|
||
|
||
En mémoire :
|
||
|
||
```rust
|
||
let exported = owner.export_transfer(ksp_wallet_lib::WalletTransferFormat::SolanaKeypairBase58);
|
||
```
|
||
|
||
Vers un fichier no-clobber :
|
||
|
||
```rust
|
||
let exported = owner
|
||
.export_transfer_file(
|
||
"exports/devnet-main.keypair",
|
||
ksp_wallet_lib::WalletTransferFormat::SolanaCliJson,
|
||
)
|
||
.await;
|
||
```
|
||
|
||
Les octets retournés par `export_transfer` contiennent volontairement le secret. Le caller doit limiter leur durée de vie et les zeroize lorsqu'approprié. Sur Unix, l'export fichier tente `0600`, qui reste une hygiène filesystem et non une garantie cryptographique.
|
||
|
||
## 10. Surfaces in-memory
|
||
|
||
Les équivalents sans I/O filesystem sont :
|
||
|
||
```text
|
||
create_wallet -> default V2
|
||
create_wallet_v1 / _v2 -> version forcée
|
||
open_wallet_view -> détection V1/V2
|
||
open_wallet_owner -> détection V1/V2
|
||
inspect_locked_wallet -> détection V1/V2
|
||
open/inspect *_v1 / *_v2 -> version forcée
|
||
inspect_wallet_transfer -> format transfer explicite
|
||
```
|
||
|
||
Les handles `WalletOwner` et `WalletView` sérialisent leur document courant avec `to_native_bytes()`. `to_json_bytes()` est conservé comme compatibilité V1 et renvoie une erreur de format pour un handle V2 au lieu de transcoder implicitement.
|
||
|
||
## 11. Erreurs et diagnostics
|
||
|
||
Les erreurs Wallet sont des `ksp_core_lib::Error` avec codes `wallet.*`. Les erreurs de password restent génériques (`owner_unlock_failed`, `view_unlock_failed`) et ne doivent jamais être transformées en oracle détaillant KDF/wrap/ciphertext.
|
||
|
||
Les `Debug` des passwords et metadata protégées sont redacted. Les logs Wallet ne doivent contenir ni password, ni keypair, ni payload exporté.
|
||
|
||
## 12. Intégration avec Config et Transport
|
||
|
||
Wallet lui-même ne dépend ni de Config ni de Transport. Une application compose explicitement les couches :
|
||
|
||
```text
|
||
ksp-config-lib
|
||
-> fournit chemins/profils applicatifs
|
||
|
||
ksp-wallet-lib
|
||
-> ouvre le wallet et fournit la Pubkey autorisée
|
||
|
||
ksp-onchain-transport-lib
|
||
-> utilise cette Pubkey pour getBalance et autres lectures réseau
|
||
```
|
||
|
||
Cette composition est le rôle de `0.2.6 — ksp-app-wallet-desk`, pas de `ksp-wallet-lib`.
|
||
|
||
## Wire/runtime V2 (`0.2.6-pre.015` / `pre.016`)
|
||
|
||
Le codec structurel V2 reste disponible directement pour les outils qui travaillent explicitement au niveau wire :
|
||
|
||
```rust
|
||
let envelope = ksp_wallet_lib::KspWalletEnvelopeV2::parse_binary(bytes)?;
|
||
let canonical = envelope.to_binary_bytes()?;
|
||
```
|
||
|
||
Une application normale doit préférer les façades Wallet :
|
||
|
||
```text
|
||
create_wallet_file(...) default V2
|
||
create_wallet_file_v1(...) V1 forcé
|
||
create_wallet_file_v2(...) V2 forcé
|
||
open_wallet_*_file(...) auto-détection V1/V2
|
||
open_wallet_*_file_v1/_v2 version forcée
|
||
inspect_locked_wallet_file(...) auto-détection V1/V2
|
||
```
|
||
|
||
`DEFAULT_WALLET_FORMAT` et `LATEST_SUPPORTED_WALLET_FORMAT` sont intentionnellement indépendants. L'arrivée d'un futur V3 ne changera pas automatiquement le default V2.
|