Skip to content

About

Rust implementation of the Knish.IO client SDK

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Knish.IO: Post-Blockchain Platform
info@wishknish.com | https://wishknish.com

Knish.IO Rust Client SDK

This is the official Rust implementation of the Knish.IO client SDK. Its purpose is to expose libraries for building and signing Knish.IO Molecules, composing Atoms, generating Wallets, and much more with native performance, memory safety, and quantum-resistant security.

Installation

The SDK can be installed via Cargo:

# Add to Cargo.toml
[dependencies]
knishio-client = "2.2.0"

# Or install from the command line
cargo add knishio-client

Requirements:

  • Rust 1.89 or higher (the crate's declared rust-version)
  • Cargo for dependency management
  • Required dependencies: serde, shake, hex, base64, chrono, rand

After installation, import the SDK in your project:

use knishio_client::{KnishIOClient, Molecule, Wallet, Atom};
use knishio_client::crypto::shake256;
use knishio_client::types::{MetaItem, Isotope};

kcore feature

The optional kcore feature runs the WOTS+ chains (signing, verification, OTS fragments) and the WOTS+ address through KnishIO-Crypto-Core, linked statically from its release package. Outputs are identical with and without it; ML-KEM stays on libcrux.

[dependencies]
knishio-client = { version = "2.0", features = ["kcore"] }

The build needs KNISHIO_KCORE_DIR pointing at an extracted KnishIO-Crypto-Core 0.2.0 package (the directory holding include/kcore.h and lib/libkcore.a, from the 0.2.0 release); without it, or with another kcore version, the build stops rather than silently falling back. Supported targets are those whose package ships lib/libkcore.a: Linux x86_64 and aarch64 (gnu and musl) and macOS (universal). wasm32 and Windows are not supported. knishio_client::kcore::available() reports whether kcore is linked.

Thread safety

Wallet, Molecule, Atom and KnishIOClient are Send + Sync, and kcore is reentrant (no threads, no mutable globals). Every call runs on the caller's thread, so parallelize at the caller: threads or Rayon over independent molecules. One molecule's sign or verify is not split across threads: it is too small to pay for the hand-off.

The optional parallel feature adds that fan-out for batches of served molecules. CheckMolecule::verify_from_server_data_batch (and ResponseMetaTypeViaAtom::verify_integrity, which uses it) verifies 4 or more molecules on the current Rayon pool and returns the results in input order; smaller batches, and builds without the feature, run sequentially. Both paths block the calling thread, so call them from spawn_blocking in async code. To bound the threads, run the call inside your own pool's ThreadPool::install.

[dependencies]
knishio-client = { version = "2.2", features = ["parallel"] }

Basic Usage

The purpose of the Knish.IO SDK is to expose various ledger functions to new or existing applications.

There are two ways to take advantage of these functions:

  1. The easy way: use the KnishIOClient wrapper struct

  2. The granular way: build Atom and Molecule instances and broadcast GraphQL messages yourself

This document will explain both ways.

The Easy Way: KnishIOClient Wrapper

  1. Include the wrapper struct in your application code:

    use knishio_client::{KnishIOClient, ClientBuilder};
  2. Instantiate the client — either with the builder (recommended) or directly:

    Using ClientBuilder (recommended):

    let client = ClientBuilder::new(
        vec!["https://some.knishio.validator.node.url/graphql".to_string()],
        "myTopSecretCode".to_string(),
    )
    .cell_slug("my-cell-slug".to_string())
    .build()?;

    Using KnishIOClient::new() directly:

    let mut client = KnishIOClient::new(
        vec!["https://some.knishio.validator.node.url/graphql".to_string()],
        Some("my-cell-slug".to_string()),
        None,  // socket config
        None,  // GraphQL client
        None,  // server SDK version
        None,  // logging
    );
    client.set_secret("myTopSecretCode");
  3. Set your secret for authentication:

    client.set_secret("myTopSecretCode");
    
    // Note: The Rust SDK uses stored secret for cryptographic operations
    // This is equivalent to the JavaScript SDK's await client.requestAuthToken()

    (Note: The secret parameter can be a salted combination of username + password, a biometric hash, an existing user identifier from an external authentication process, for example)

  4. Begin using client to trigger commands described below...

KnishIOClient Methods

  • Query metadata for a Wallet Bundle. Omit the bundle_hash parameter to query your own Wallet Bundle:

    let response = client.query_bundle(
        Some("c47e20f99df190e418f0cc5ddfa2791e9ccc4eb297cfa21bd317dc0f98313b1d")
    ).await?;
    
    println!("{}", response); // Raw Metadata as JSON Value
  • Query metadata for a Meta Asset:

    let result = client.query_meta(
        "Vehicle",           // meta_type
        Some("CAR123"),      // meta_id
        Some("LicensePlate"), // key
        Some("1H17P"),       // value
        Some(true)           // through_atom (query via atom path)
    ).await?;
    
    println!("{}", result); // Raw Metadata as JSON Value
  • Writing new metadata for a Meta Asset:

    use std::collections::HashMap;
    use serde_json::json;
    
    let mut metadata = HashMap::new();
    metadata.insert("type".to_string(), json!("fire"));
    metadata.insert("weaknesses".to_string(), json!("rock,water,electric"));
    metadata.insert("immunities".to_string(), json!("ground"));
    metadata.insert("hp".to_string(), json!("78"));
    metadata.insert("attack".to_string(), json!("84"));
    
    let response = client.create_meta(
        "Pokemon",      // meta_type
        "Charizard",    // meta_id
        metadata,
        None            // policy (optional)
    ).await?;
    
    if response.success() {
        println!("Metadata created successfully!");
    }
    
    println!("{}", response.data()); // Raw response
  • Query Wallets associated with a Wallet Bundle:

    let wallets = client.query_wallets(
        Some("c47e20f99df190e418f0cc5ddfa2791e9ccc4eb297cfa21bd317dc0f98313b1d"), // bundle_hash
        Some("FOO"), // token (optional)
    ).await?;
    
    println!("{:?}", wallets); // Vec<Wallet>
  • Declaring new Wallets:

    (Note: If Tokens are sent to undeclared Wallets, Shadow Wallets will be used (placeholder Wallets that can receive, but cannot send) to store tokens until they are claimed.)

    let response = client.create_wallet("FOO").await?; // Token Slug for the wallet we are declaring
    
    if response.success() {
        println!("Wallet created successfully!");
    }
    
    println!("{}", response.data()); // Raw response
  • Issuing new Tokens:

    use std::collections::HashMap;
    use serde_json::json;
    
    let mut token_meta = HashMap::new();
    token_meta.insert("name".to_string(), json!("CrazyCoin"));
    token_meta.insert("fungibility".to_string(), json!("fungible"));
    token_meta.insert("supply".to_string(), json!("limited"));
    token_meta.insert("decimals".to_string(), json!("2"));
    
    let response = client.create_token(
        "CRZY",                  // Token slug (ticker symbol)
        Some(100000000.0),       // Initial amount to issue
        Some(token_meta),        // Token metadata
        None,                    // batch_id (optional, for stackable tokens)
        vec![],                  // units (optional, for stackable tokens)
    ).await?;
    
    if response.success() {
        println!("Token created successfully!");
    }
    
    println!("{}", response.data()); // Raw response
  • Transferring Tokens to other users:

    let response = client.transfer_token(
        "7bf38257401eb3b0f20cabf5e6cf3f14c76760386473b220d95fa1c38642b61d", // Recipient's bundle hash
        "CRZY",        // Token slug
        Some(100.0),   // Amount
        vec![],        // units (optional, for stackable tokens)
        None,          // batch_id (optional, for stackable tokens)
        None,          // source_wallet (optional, auto-resolved)
    ).await?;
    
    if response.success() {
        println!("Token transferred successfully!");
    }
    
    println!("{}", response.data()); // Raw response
  • Creating a new Rule:

    use serde_json::json;
    
    let rule = vec![
        json!({"key": "amount", "operator": "<=", "value": "1000"})
    ];
    
    let response = client.create_rule(
        "MyMetaType",  // meta_type
        "MyMetaId",    // meta_id
        rule,
        None           // policy (optional)
    ).await?;
    
    if response.success() {
        println!("Rule created successfully!");
    }
    
    println!("{}", response.data()); // Raw response
  • Querying Atoms:

    let atoms = client.query_atom(
        None,           // molecular_hash
        Some("bundle-hash-here"), // bundle_hash
        None,           // position
        None,           // wallet_address
        Some("V"),      // isotope (as string: "V", "C", "M", "T", etc.)
        Some("CRZY"),   // token_slug
        None,           // batch_id
        None,           // meta_type
        None,           // meta_id
    ).await?;
    
    println!("{:?}", atoms); // Vec<Value>
  • Working with Buffer Tokens:

    use std::collections::HashMap;
    
    // Deposit to buffer
    let mut trade_rates = HashMap::new();
    trade_rates.insert("OTHER_TOKEN".to_string(), 0.5);
    
    let deposit_response = client.deposit_buffer_token(
        "CRZY",          // token_slug
        100.0,           // amount
        trade_rates,     // trade_rates
        None,            // source_wallet (optional)
    ).await?;
    
    // Withdraw from buffer
    let withdraw_response = client.withdraw_buffer_token(
        "CRZY",  // token_slug
        50.0,    // amount
        None,    // source_wallet (optional)
    ).await?;
    
    println!("{} {}", deposit_response.data(), withdraw_response.data());

Advanced Usage: Working with Molecules

For more granular control, you can work directly with Molecules:

  • Create a new Molecule:

    use knishio_client::Molecule;
    
    let mut molecule = Molecule::with_params(
        Some("secret".to_string()),
        None,                    // bundle
        Some(source_wallet),     // source_wallet
        None,                    // remainder_wallet
        Some("cell_slug".to_string()),
        None                     // version
    );
  • Create a custom Mutation:

    use knishio_client::mutation::MutationProposeMolecule;
    
    let mutation = MutationProposeMolecule::from_molecule(molecule);
  • Sign and check a Molecule:

    molecule.sign(None, false, true)?;
    
    if molecule.check(None)? {
        println!("Molecule validation passed!");
    } else {
        println!("Molecule validation failed!");
    }
  • Execute a custom Query or Mutation:

    let response = client.execute_query(&mutation, None).await?;
    
    if response.success() {
        println!("Molecule executed successfully!");
    }

The Hard Way: DIY Everything

This method involves individually building Atoms and Molecules, triggering the signature and validation processes, and communicating the resulting signed Molecule mutation or Query to a Knish.IO node via GraphQL.

  1. Include the relevant structures in your application code:

    use knishio_client::{Molecule, Wallet, Atom};
    use knishio_client::crypto;
    use knishio_client::types::{Isotope, MetaItem};
  2. Generate a 2048-symbol hexadecimal secret, either randomly, or via hashing login + password + salt, OAuth secret ID, biometric ID, or any other static value.

  3. (optional) Initialize a signing wallet with:

    let wallet = Wallet::create(
        Some("secret"),
        None,              // bundle (optional)
        "USER",            // token
        None,              // position (optional)
        None,              // characters (optional)
        None,              // mlkem_parameter_set (optional, defaults to ML-KEM-1024)
    )?;

    WARNING 1: If ContinuID is enabled on the node, you will need to use a specific wallet, and therefore will first need to query the node to retrieve the position for that wallet.

    WARNING 2: The Knish.IO protocol mandates that all C and M transactions be signed with a USER token wallet.

  4. Build your molecule with:

    let mut molecule = Molecule::with_params(
        Some("secret".to_string()),
        None,                    // bundle (optional)
        Some(source_wallet),     // source_wallet (optional)
        None,                    // remainder_wallet (optional)
        Some("cell_slug".to_string()), // cell_slug (optional)
        None                     // version (optional)
    );
  5. Either use one of the shortcut methods provided by the Molecule struct (which will build Atom instances for you), or create Atom instances yourself.

    DIY example:

    // This example records a new Wallet on the ledger
    
    // Define metadata for our new wallet
    let new_wallet_meta = vec![
        MetaItem::new("address", &new_wallet.address),
        MetaItem::new("token", &new_wallet.token),
        MetaItem::new("bundle", &new_wallet.bundle),
        MetaItem::new("position", &new_wallet.position.unwrap_or_default()),
        MetaItem::new("batchId", &new_wallet.batch_id.unwrap_or_default()),
    ];
    
    // Build the C isotope atom
    let wallet_creation_atom = Atom::new(
        &source_wallet.position.unwrap(),
        &source_wallet.address,
        Isotope::C,
        &source_wallet.token
    );
    wallet_creation_atom.meta_type = Some("wallet".to_string());
    wallet_creation_atom.meta_id = Some(new_wallet.address.clone());
    wallet_creation_atom.meta = Some(new_wallet_meta);
    wallet_creation_atom.index = Some(molecule.generate_index());
    
    // Add the atom to our molecule
    molecule.add_atom(wallet_creation_atom);
    
    // Adding a ContinuID / remainder atom
    molecule.add_continuid_atom()?;

    Molecule shortcut method example:

    // This example commits metadata to some Meta Asset
    
    // Defining our metadata
    let metadata = vec![
        MetaItem::new("foo", "Foo"),
        MetaItem::new("bar", "Bar"),
    ];
    
    molecule.init_meta(
        metadata,
        "MyMetaType",
        "MetaId123",
        None  // policy (optional)
    )?;
  6. Sign the molecule with the stored user secret:

    molecule.sign(None, false, true)?;
  7. Make sure everything checks out by verifying the molecule:

    match molecule.check(None) {
        Ok(true) => {
            println!("Molecule validation passed!");
        }
        Ok(false) => {
            println!("Molecule validation failed!");
        }
        Err(e) => {
            eprintln!("Molecule check error: {:?}", e);
        }
    }
  8. Broadcast the molecule to a Knish.IO node:

    use knishio_client::mutation::MutationProposeMolecule;
    
    // Build our mutation object
    let mutation = MutationProposeMolecule::from_molecule(molecule);
    
    // Send the mutation to the node and get a response
    let response = client.execute_query(&mutation, None).await?;
  9. Inspect the response...

    // For basic queries, we look at the data property:
    println!("{}", response.data());
    
    // For mutations, check if the molecule was accepted by the ledger:
    println!("{}", if response.success() { "Success" } else { "Failed" });
    
    // We can also check the reason for rejection
    println!("{:?}", response.reason());
    
    // Some queries may also produce a payload, with additional data:
    println!("{:?}", response.payload());

    Payloads are provided by responses to the following queries:

    1. QueryBalance and QueryContinuId -> returns a Wallet instance
    2. QueryWalletList -> returns a list of Wallet instances
    3. MutationProposeMolecule, MutationRequestAuthorization, MutationCreateIdentifier, MutationLinkIdentifier, MutationClaimShadowWallet, MutationCreateToken, MutationRequestTokens, and MutationTransferTokens -> returns molecule metadata

Network Freshness

The GraphQL transport (reqwest) issues a fresh network request per query — there is no response caching (the connection pool is TCP-level only) — so a long-lived client never serves a stale read of ledger state. No fresh-read knob (e.g. a request policy) is required.

Secret storage

Master secrets are stored at rest in the cross-SDK AES-256-GCM envelope (PBKDF2-HMAC-SHA256 ×100000, camelCase metadata; frozen in sdks/shared-test-results/cross-platform-test-vectors.json). Every SDK decrypts every other SDK's envelope.

Provider providerType Custody (hardwareBacked) Where the key lives Recovery passphrase
MemorySecretStorageProvider memory Software (false) Process memory Optional
AesGcmSecretStorageProvider aes-gcm Software (false) Any StorageBackend (MemoryStorageBackend, FileStorageBackend [0o600]) Optional
OsKeychainSecretStorageProvider os-keychain-aes-gcm Software (false) OS login keychain ACL (feature keyring) Optional
Tpm2SecretStorageProvider tpm2-aes-gcm Hardware (true when TpmIdentity::Hardware) Hardware TPM 2.0 NVRAM (feature tpm) Required when Tpm2Policy is set unless allow_unrecoverable
SecureEnclaveSecretStorageProvider secure-enclave-aes-gcm Hardware (true) Apple Secure Enclave P-256 key via Data Protection Keychain (macOS, feature secure-enclave) Required unless allow_unrecoverable

hardwareBacked is derived by the provider from the platform, never accepted from the caller; software providers always report false. A hardware provider refuses to store without a recovery passphrase unless allow_unrecoverable is set — the recovery envelope (knishio:recovery:<bundleHash>, providerType: "aes-gcm") is software custody whose strength is bounded by that passphrase: enforce passphrase entropy or keep it on a second device.

macOS note: SecureEnclaveSecretStorageProvider requires a process entitled with keychain-access-groups running inside an app bundle with an active Apple provisioning profile; unbundled CLI binaries fail closed safely (errSecMissingEntitlement / AMFI -413).

Getting Help

Knish.IO is under active development, and our team is ready to assist with integration questions. The best way to seek help is to stop by our Telegram Support Channel. You can also send us a contact request via our website.

About

Rust implementation of the Knish.IO client SDK

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages