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

As the foundation layer of the [Nervos Network](https://www.nervos.org/),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](https://medium.com/@CKBHolders/the-meepo-hard-fork-ushering-in-the-next-era-of-nervos-ckb-3ecfc500b10a). 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](https://yakihonne.com/profile/nprofile1qqsrawj77sdjq6caf85tr0njg8t744was7mnwfsa5exrhs8zw50j8tsmp50vv)）

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`](https://github.com/XuJiandong/ckb-script-ipc/tree/main/crates/ckb-crypto-interface) and `ckb-script-ipc-common`:

```rust
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](https://github.com/XuJiandong/ckb-script-ipc/blob/main/contracts/unit-tests-crypto/src/main.rs#L71)

### 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](https://github.com/joii2020/docs.nervos.org/blob/v2.dev.spawn/examples/ckb-ts-script/contracts/call-crypto-service/src/index.ts).

```jsx
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:

```tsx
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](https://github.com/nervosnetwork/docs.nervos.org/blob/v2/examples/ckb-ts-script/contracts/call-crypto-service/src/index.ts)

### C

C follows a similar pattern as JS—manual JSON construction. IPC-related functions are provided in [`ckb_script_ipc.h`](https://github.com/XuJiandong/ckb-script-ipc/blob/main/c/ckb_script_ipc.h), which developers can use to run ckb-crypto-service.”

```c
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:

```c
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:

```c
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](https://github.com/joii2020/docs.nervos.org/blob/v2.dev.rs-crypto/examples/c-script-examples/c/crypto-service-hash.c)

## 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 size**—**worth 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 **~4KB**—**a negligible cost** for most contracts.
    

## Implementation Details

`ckb-crypto-service` is implemented on top of [`ckb-script-ipc`](https://github.com/XuJiandong/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:**

```rust
{
  "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:**

```rust
{
  "FunctionName": {
    "Ok": return_value
  }
}
```

Or

```rust
{
  "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)

```plaintext
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:

```plaintext
{
  "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**](https://blog.cryptape.com/nervos.org).

Follow him for more technical insights on CKB:

* [Introducing Multiprocessing and IPC on CKB: New Approach to CKB Contract Development](https://blog.cryptape.com/introducing-multiprocessing-and-ipc-on-ckb) / [引入多进程和进程间通信: CKB 合约开发的新方式](https://blog.cryptape.com/introducing-multiprocessing-and-ipc-on-ckb-cn)
    

* [**Quantum Computation: New Challenge to CKB’s Security?**](https://blog.cryptape.com/quantum-computation-new-challenge-to-ckbs-security)
    
* [**Implementing EIP-712 on CKB-VM**](https://blog.cryptape.com/implementing-eip-712-on-ckb-vm)
