# 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 appartient à `ksp-app-wallet-desk`, pas à `ksp-wallet-lib`. ## Wire/runtime V2 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. ## Migration V1 -> V2 explicite Migration en mémoire : ```rust let migrated = ksp_wallet_lib::migrate_wallet_v1_to_v2( v1_bytes.as_slice(), ksp_wallet_lib::OwnerPassword::new(owner_password), Some(ksp_wallet_lib::ViewPassword::new(target_view_password)), ) .await?; ``` Migration fichier vers une nouvelle destination : ```rust let migrated = ksp_wallet_lib::migrate_wallet_file_v1_to_v2( source_v1, destination_v2, ksp_wallet_lib::OwnerPassword::new(owner_password), Some(ksp_wallet_lib::ViewPassword::new(target_view_password)), ) .await?; ``` `destination_v2` est no-clobber et `source_v1` reste inchangé. Pour remplacer le fichier courant après vérification stale-state : ```rust let migrated = ksp_wallet_lib::migrate_wallet_file_v1_to_v2_in_place( source_v1, ksp_wallet_lib::OwnerPassword::new(owner_password), Some(ksp_wallet_lib::ViewPassword::new(target_view_password)), ) .await?; ``` Ces exemples utilisent `?` uniquement comme écriture illustrative de consumer ; les règles internes KSP restent celles du workspace. Une source VIEW activée exige `Some(target_view_password)` ; une source VIEW désactivée exige `None`. Le credential VIEW cible peut être l'ancien mot de passe ou un nouveau mot de passe choisi sous autorité OWNER. Identité Solana, alias, notes et note IDs sont conservés. Aucun open générique ne migre le fichier.