Files
khadhroony-solana-project/crates/ksp-wallet-lib/USAGE.md
2026-08-22 03:21:49 +02:00

9.8 KiB

Utilisation de ksp-wallet-lib

Ce guide présente les principales surfaces publiques de Wallet V1. La spécification cryptographique du fichier reste ../../docs/formats/KSPWALLET_V1.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::WalletCreateMetadataV1::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_v1(
        "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, utiliser create_wallet_v1 puis WalletOwner::to_json_bytes() si le caller possède lui-même une autre boundary de stockage.

2. Inspecter un wallet verrouillé

async fn inspect_example() -> ksp_core_lib::Result<()> {
    let inspected = ksp_wallet_lib::inspect_locked_wallet_file_v1("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_v1("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_v1("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_v1("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_v1(
    "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::WalletCreateMetadataV1::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_v1
open_wallet_view_v1
open_wallet_owner_v1
inspect_locked_wallet_v1
inspect_wallet_transfer

Les handles WalletOwner et WalletView peuvent être sérialisés vers le document natif courant avec to_json_bytes(). Ces bytes restent un .kspwallet chiffré, pas un export de la keypair.

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 V2 (0.2.6-pre.015)

Le codec structurel V2 peut être utilisé pour analyser une fixture/document V2 déjà produit :

let envelope = ksp_wallet_lib::KspWalletEnvelopeV2::parse_binary(bytes)?;
let canonical = envelope.to_binary_bytes()?;

La persistence applicative ne doit pas encore appeler ce codec directement pour créer un wallet : pre.016 introduit les APIs génériques/versionnées et le default V2.