Skip to main content

Command Palette

Search for a command to run...

New CKB Crypto Algorithm Calling Solution for a Secure, Consistent, and Developer-Friendly Experience

Updated
8 min read
New CKB Crypto Algorithm Calling Solution for a Secure, Consistent, and Developer-Friendly Experience

As the foundation layer of the Nervos Network,designed with security and scalability in mind, CKB relies heavily on cryptographic algorithms in its smart contract execution environment. In practice, operations such as signing, hashing, and encryption or decryption are foundational for CKB contract development.

Developers typically follow this workflow:

  1. Find a reliable implementation—typically an official or community-recommended library;

  2. Statically link it into the current project—either as source code or a static library;

  3. Resolve compilation compatibility issues and dependency conflicts.

Though straightforward, however, this approach often turns out to be more painful than expected:

  • Limited language support: Many algorithms are only available in C. Other languages (like JS) might lack usable versions or suffer from poor performance, making them impractical for contract use.

  • Inconsistent implementation quality: Especially for critical functions like signatures, a flawed implementation may introduce critical security vulnerabilities and potential asset loss.

  • Dependency hell: Conflicts between libraries can be time-consuming to resolve—sometimes with no clear fix.

  • Behavior mismatches across libraries: Differences in edge-case handling can cause signature validation to fail.

  • Bloated contract size: Bundling cryptographic libraries bloats contract code and increases deployment cost.

Integration Status Across Languages

Let's take a look at the current landscape for integrating cryptographic functions in the most common CKB development languages:

  • Rust: Most commonly used algorithms have mature implementations, though dependency conflicts still occur. (ckb-crypto-service itself is written in Rust.)

  • C: Integration is more complex. Small algorithms can be included via #include, but larger libraries require manual Makefile setup and linking.

  • JavaScript: Few third-party libraries offer decent performance. While ckb-js-vm includes some C-based implementations, it is updated infrequently and lacks the agility of ckb-crypto-service.

In short, there's a clear need for a unified, cross-language cryptographic service to reduce integration friction.

Bundled Cryptographic Algorithms via Spawn + IPC

CKB introduced support for multi-process execution (spawn) and inter-process communication (IPC) via its Meepo hard fork. This upgrade allows contracts to safely invoke external processes at runtime. (For a deeper dive, check out Introducing Multiprocessing and IPC on CKB: New Approach to CKB Contract Development

Built on top of this feature, ckb-crypto-service packages commonly-used cryptographic algorithms behind a consistent, secure, and easy-to-use IPC interface. The goal is not to replace every cryptographic library—but rather to serve as a consistent, cross-language building block that contract developers can access via IPC, without the hassle of managing and embedding algorithms themselves.

ckb-crypto-service currently supports the following algorithms:

  • Blake2b

  • SHA-256 (SHA-2)

  • RIPEMD-160

  • Secp256k1

  • Schnorr

  • Ed25519

Why Use ckb-crypto-service?

  • Works across languages: No need to integrate a separate cryptographic stack for each language. In C, you can skip Makefile tweaks and third-party links; whereas in ckb-js-vm, without embedding algorithms into the VM.

  • Consistency: All algorithms share the same codebase and test coverage, avoiding bugs caused by inconsistent implementations.

  • Smooth developer experience: In Rust, IPC macros are auto-generated, making calling remote service feel like local functions. In C and JS, building and parsing payloads is far easier than integrating full libraries.

Whether you're working in Rust, C, or JavaScript, ckb-crypto-service provides a reliable foundation for cryptographic operations. It's currently the go-to solution for smart contract developers on CKB.

Using ckb-crypto-service in Multiple Languages

Thanks to the Spawn + IPC architecture, ckb-crypto-service is simple to use for any supported language.

Rust

Use ckb-crypto-interface and ckb-script-ipc-common:

use ckb_crypto_interface::{CkbCryptoClient, HasherType};
...

let (read_pipe, write_pipe) = spawn_cell_server(
    code_hash,
    ckb_std::ckb_types::core::ScriptHashType::Data2,
    &[CString::new("").unwrap().as_ref()],
)
.unwrap();
let crypto_cli = CkbCryptoClient::new(read_pipe, write_pipe);
let ctx = crypto_cli.hasher_new(HasherType::CkbBlake2b);
crypto_cli
    .hasher_update(ctx.clone(), crypto_info.witness.clone())
    .expect("update ckb blake2b");
let hash = crypto_cli
    .hasher_finalize(ctx)
    .expect("ckb blake2b finallize");

Sample Code

JavaScript

JavaScript doesn’t yet offer auto-generated IPC bindings like Rust does (ckb_script_ipc::service). So you’ll need to construct and parse JSON payloads manually. See this example.

function runFunction(channel: Channel, payload: Object) {
  let payloadHex = new bindings.TextEncoder().encode(JSON.stringify(payload));
  let res = channel.call(new RequestPacket(payloadHex));
  if (res.errorCode() != 0) {
    throw Error(`IPC Error: ${res.errorCode()}`);
  }

  let resPayload = new bindings.TextDecoder().decode(res.payload());
  return Object.values(JSON.parse(resPayload))[0];
}

function ckbBlake2b(channel: Channel, data: number[]) {
  let hasher_ctx = runFunction(channel, { "HasherNew": { "hash_type": "CkbBlake2b" } });
  runFunction(channel, { "HasherUpdate": { "ctx": hasher_ctx, "data": data } });
  let hash = new Uint8Array(resultOk(runFunction(channel, { "HasherFinalize": { "ctx": hasher_ctx, } })));

  return hash;
}

After that, use the IPC API to create it:

function startService(): Channel {
  const args = HighLevel.loadScript().args;
  const codeHash = args.slice(35, 35 + 32);
  const [readPipe, writePipe] = spawnCellServer(codeHash, bindings.SCRIPT_HASH_TYPE_DATA2, []);
  return new Channel(readPipe, writePipe);
}

Sample Code

C

C follows a similar pattern as JS—manual JSON construction. IPC-related functions are provided in ckb_script_ipc.h, which developers can use to run ckb-crypto-service.”

csi_init_payload(g_payload_buf, sizeof(g_payload_buf), 2);
csi_init_iobuf(g_io_buf, sizeof(g_io_buf), 2);

CSIChannel channel = {0};
err = csi_spawn_cell_server(code_hash, 1, NULL, 0, &channel);
if (err) {
    printf("failed to spawn server: %d\n", err);
    return err;
}

Next, use csi_call to send the request:

int run_ipc_func(CSIChannel* client_channel, char* payload,
                 uint64_t payload_len, CSIResponsePacket* response) {
    CSIRequestPacket request = {0};
    request.version = 0;
    request.method_id = 0;

    request.payload_len = payload_len;
    request.payload = payload;

    int err = csi_call(client_channel, &request, response);
    if (err) {
        printf("csi_call failed, err: %d", err);
        return err;
    }

    if (response->error_code) {
        printf("csi_call response->error_code: %d", response->error_code);
        return err;
    }

    return 0;
}

Now, developers can cal ckb-crypto-service by constructing the payload:

int hasher_update(CSIChannel* channel, uint64_t ctx, uint8_t* buf,
                  uint64_t buf_len) {
    int err = 0;
    CSIResponsePacket response;
    char payload[1024];

    int offset = 0;
    offset += sprintf_(payload + offset,
                       "{ \"HasherUpdate\": { \"ctx\": %d, \"data\": [", ctx);
    for (uint64_t i = 0; i < buf_len; i++) {
        if (i == buf_len - 1)
            offset += sprintf_(payload + offset, "%u", buf[i]);
        else
            offset += sprintf_(payload + offset, "%u,", buf[i]);
    }
    offset += sprintf_(payload + offset, "] }}");
    uint64_t payload_len = offset;

    err = run_ipc_func(channel, payload, payload_len, &response);
    if (err) {
        return err;
    }

    csi_client_free_response_payload(&response);
    return 0;
}

Note:

  • The payload for ckb-crypto-service is in JSON. Due to its simplicity, no third-party library is used for parsing.

  • The response itself is stack-allocated, but its payload is dynamically allocated. Be sure to call csi_client_free_response_payload to free the memory after use—otherwise, subsequent calls may fail due to memory issues.

  • The subsequent hasher_update and hasher_finalize should be invoked in a similar manner.

Sample Code

Impact on Contract Size

Using ckb-crypto-service instead of bundling cryptographic libraries can dramatically reduce contract size:

  • Rust: Requires serde and serde_json for JSON handling, adding ~50–60KB to contract sizeworth considering if you're constrained for space. Not small, but the cost is one-time and reusable, also avoids even larger bloat from full cryptographic libraries.

  • JavaScript (ckb-js-vm): Since JSON support is built into the VM, there's virtually zero additional size.

  • C: Manual string-based JSON construction avoids the need for a full JSON library. Adds just ~4KBa negligible cost for most contracts.

Implementation Details

ckb-crypto-service is implemented on top of ckb-script-ipc. The server defines a Rust trait containing callable methods. In Rust, the IPC framework auto-generates bindings that make remote calls feel native. In C and JS, you’ll need to manually build and parse JSON.

Payload Format Specification

Each service process implements a single trait, so there's no need to specify service or trait names in the payload. While method_id is supported, ckb-crypto-service uses 0 by default for simplicity.

Request format:

{
  "FunctionName": {
    "arg_name_1": "data",
    "arg_name_2": "data"
  }
}
  • FunctionName uses UpperCamelCase (e.g., HasherUpdate)

  • Argument names use snake_case, matching Rust convention

  • Parameter values are the actual data, such as integers, byte arrays, etc.

  • Byte arrays are represented as [0, 1, 2, 255], indicating a u8 array.

Response format:

{
  "FunctionName": {
    "Ok": return_value
  }
}

Or

{
  "FunctionName": {
    "Err": "ErrorType"
  }
}

This mirrors Rust's Result<T, E> pattern.

In other environments:

  • Ok: The call succeeded and contains the function’s actual return value (e.g., a hash context ID or a byte array);

  • Err: Call failed with a CryptoError variant like InvalidSig or VerifyFailed

  • The call failed, with the value being one of the CryptoError enum variants (such as InvalidSig, VerifyFailed, etc.).

Trait Definition (Excerpt)

pub enum CryptoError {
    InvalidContext,
    InvalidSig,
    InvalidPrehash,
    InvalidRecoveryId,
    InvalidPubkey,
    RecoveryFailed,
    VerifyFailed,
}

pub struct HasherCtx(pub u64);

pub trait CkbCrypto {
    fn hasher_new(hash_type: HasherType) -> HasherCtx;
    fn hasher_update(ctx: HasherCtx, data: Vec<u8>) -> Result<(), CryptoError>;
    fn hasher_finalize(ctx: HasherCtx) -> Result<Vec<u8>, CryptoError>;

    fn secp256k1_recovery(prehash: Vec<u8>, signature: Vec<u8>, recovery_id: u8)
        -> Result<Vec<u8>, CryptoError>;

    fn secp256k1_verify(public_key: Vec<u8>, prehash: Vec<u8>, signature: Vec<u8>)
        -> Result<(), CryptoError>;

    fn schnorr_verify(public_key: Vec<u8>, prehash: Vec<u8>, signature: Vec<u8>)
        -> Result<(), CryptoError>;

    fn ed25519_verify(public_key: Vec<u8>, prehash: Vec<u8>, signature: Vec<u8>)
        -> Result<(), CryptoError>;
}

You can use these definitions to build your JSON payload. For example:

{
  "SchnorrVerify": {
    "public_key": [2, 170, 187, ...],
    "prehash": [1, 2, 3, ...],
    "signature": [255, 1, 0, ...]
  }
}

Conclusion

ckb-crypto-service is the current best practice for adding cryptographic functions to CKB smart contracts. By leveraging CKB's multiprocessing and IPC capabilities, it offers a unified, secure, and language-agnostic way to run cryptographic operations without bloating your contracts or managing brittle dependencies. Whether you're building in Rust, C, or JavaScript, ckb-crypto-service provides a consistent and maintainable path forward for on-chain cryptography.


🧑‍💻 Author: Han Zishuang, a developer and DevRel for CKB, L1 of the Nervos Network.

Follow him for more technical insights on CKB:

Engineering

Part 3 of 33

Design -> Developing -> Testing -> Debugging. Engineer apes are working nonstop on innovative solutions to build a secure, scalable and decentralized infrastructure!

Up next

Ckb 加密算法调用新方案

实现安全一致的开发体验