12 KiB
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 et V2 par ../../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
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é
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
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
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
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 :
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 :
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 :
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.
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 :
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 :
let exported = owner.export_transfer(ksp_wallet_lib::WalletTransferFormat::SolanaKeypairBase58);
Vers un fichier no-clobber :
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 :
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 :
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 stable (0.2.6)
Le codec structurel V2 reste disponible directement pour les outils qui travaillent explicitement au niveau wire :
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 :
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 :
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 :
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 :
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.