# Ckb 加密算法调用新方案

CKB 作为注重安全与可扩展性的 [Nervos](https://www.nervos.org/) 底层公链，其合约执行环境对加密算法的依赖极为关键。在 CKB 合约开发中，签名、哈希、加解密等算法是常见的基础需求。传统做法通常是：

1. 找到一个可靠的实现（通常是官方或社区推荐库）；
    
2. 将其静态链接引入当前项目（以源码或者静态链接的方式）；
    
3. 解决编译兼容、依赖冲突等问题。
    

但这条路径往往困难重重：

* 语言支持不齐全：许多算法只提供了 C 语言实现，其他语言（如 JS）可能没有可用版本，或者性能极差、不适用于合约；
    
* 实现质量难以保障：一些实现质量难以保障，特别是签名算法，一旦存在漏洞可能导致资产损失；
    
* 依赖冲突难以解决：在集成时，可能会遇到依赖冲突等问题，处理这些问题可能需要花费大量的时间，甚至无解。
    
* 实现差异导致签名失败：处理一些边缘问题不同实现可能会有差异，会导致签名失败等问题。
    
* 合约代码膨胀：需要花费更多的CKB用于部署
    

### 多种语言下的集成现状

以 CKB 合约常用语言为例：

* **Rust**：大多数常用算法已有较成熟实现，但依赖冲突仍时有发生。（`ckb-crypto-service` 本身也是用 Rust 编写）
    
* **C 语言**：集成最复杂，小型算法可直接 `#include`，大型库则需通过子项目引入，依赖官方 Makefile 编译为 `.o` 或 `.a` 后链接。
    
* **JavaScript**：可用的第三方实现稀少，性能也乐观。我们在 `ckb-js-vm` 中使用C语言实现了一些常用算法，但该项目（ckb-js-vm）更新频率低，不及 `ckb-crypto-service` 快速支持新算法。
    

因此，一个统一、跨语言、低依赖的加密算法服务显得尤为必要。

## 基于 Spawn + IPC 的打包加密算法

CKB 通过第二次 [Meepo 硬分叉](https://medium.com/@CKBHolders/the-meepo-hard-fork-ushering-in-the-next-era-of-nervos-ckb-3ecfc500b10a)，引入了`spawn` 多进程与进程间通信（IPC）机制，能够让合约在运行时安全地调用外部进程执行逻辑（更多见[《引入多进程和进程间通信：CKB 合约开发的新方式 》](https://yakihonne.com/profile/nprofile1qqsrawj77sdjq6caf85tr0njg8t744was7mnwfsa5exrhs8zw50j8tsmp50vv)） 。

在此机制的基础上，为进一步提高开发体验，我们封装了常用加密算法服务，开发了 `ckb-crypto-service`。它是一套基于多进程与 IPC 机制、专为合约开发构建了一套统一、安全、易用的加密算法调用方式。`ckb-crypto-service` 的目标不是取代所有实现，而是在合约中提供一个可靠、简洁、跨语言的加密算法基础设施，通过 IPC 直接调用，无需手动集成。

`ckb-crypto-service`支持以下算法的 IPC 调用：

* Blake2b
    
* SHA-256 (SHA-2)
    
* RIPEMD-160
    
* Secp256k1
    
* Schnorr
    
* Ed25519
    

`ckb-crypto-service` 的优势包括：

* **跨语言统一**：无需针对每种语言分别集成算法。例如，C 中无需再手动改写 Makefile 或引入第三方库，`ckb-js-vm` 中也无需将算法预集成到虚拟机；
    
* **一致性**：算法实现统一、经过充分测试，避免因实现差异或低质量库导致安全问题；
    
* **开发体验优良**：Rust 中可通过自动生成的 IPC 宏调用，使用方式与本地函数无异；C 与 JS 则只需构造请求与解析响应，比起集成原生算法库简单得多。
    

无论是在 Rust、C 还是 JavaScript 中，`ckb-crypto-service` 都能作为可靠的基础设施，简化合约中加密功能的实现，可以看作是当前 CKB 合约开发的*首选加密方案*。

## `ckb-crypto-service` 的多语言调用

由于 `ckb-crypto-service` 基于 Spawn + IPC 实现，调用起来非常简单：

### Rust 调用

依赖 [ckb-crypto-interface](https://github.com/XuJiandong/ckb-script-ipc/tree/main/crates/ckb-crypto-interface) 和 `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");
```

[参考代码](https://github.com/XuJiandong/ckb-script-ipc/blob/main/contracts/unit-tests-crypto/src/main.rs#L71)

### JS 调用

由于 JS 暂无类似 Rust 的 `ckb_script_ipc::service` 自动生成工具，需手动构造 IPC 数据包并解析响应 ([Example](https://github.com/joii2020/docs.nervos.org/blob/v2.dev.spawn/examples/ckb-ts-script/contracts/call-crypto-service/src/index.ts)):

```rust
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;
}
```

之后再通过 IPC 的 API 创建：

```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);
}
```

[参考代码](https://github.com/nervosnetwork/docs.nervos.org/blob/v2/examples/ckb-ts-script/contracts/call-crypto-service/src/index.ts)

### C 调用

C 的情况与 JS 类似，并没有提供 `ckb_script_ipc::service` ，开发者也需要类似 JS 这样手动的构造。

[这里](https://github.com/XuJiandong/ckb-script-ipc/blob/main/c/ckb_script_ipc.h) 提供了 IPC 通讯的关键函数，开发者可以通过它来运行`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;
}
```

之后，通过`csi_call`发送请求：

```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;
}
```

这时，开发者就可以通过拼接 Payload 来调用`ckb-crypto-service`了：

```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;
}
```

`ckb-crypto-service`的 Payload 为 JSON，因为格式简单这里并没有直接使用第三方库来解析。

这里需要注意`response`虽然在栈中，但是其中的 Payload 则是动态分配的 ， 使用完后需要调用`csi_client_free_response_payload`来释放资源，否则再次调用可能会失败。

之后的`hasher_update` 和 `hasher_finalize` 也是通过类似上述的方式调用。

[完整代码](https://github.com/joii2020/docs.nervos.org/blob/v2.dev.rs-crypto/examples/c-script-examples/c/crypto-service-hash.c)

## 对合约体积的影响

相较于传统集成静态加密库的方式，使用 IPC 调用的一个显著优势在于**对合约体积的控制**。

由于不同语言的调用机制存在差异，体积影响也略有不同：

* **Rust** ：为构造与解析 JSON Payload，引入了`serde`与`serde_json`，这是目前最通用的序列化方案。代价是合约体积大约增加 **50KB ~ 60KB**，**在资源敏感的链上环境中需要权衡**。但这种体积开销是一次性的，后续可重用，也避免了集成多个算法库所带来的更大膨胀。
    
* **JavaScript（ckb-js-vm）**：由于虚拟机本身已内建 JSON 编解码能力，因此调用`ckb-crypto-service`不会引入额外体积，几乎 **无增量开销**。
    
* **C语言**：虽然也需要构造 JSON Payload，但通常通过字符串拼接实现，无需引入完整的 JSON 库，逻辑简单。整体体积增加约 **4KB 左右**，**影响极小**，非常适合资源受限的合约场景。
    

## 实现细节

`ckb-crypto-service` 底层基于 [ckb-script-ipc](https://github.com/XuJiandong/ckb-script-ipc) 实现，Server 端通过实现一个`trait`（可类比为接口）来定义可调用的服务方法。在 Rust 中，由于 IPC 框架支持自动生成代码，调用这些方法就像调用本地函数一样自然。而在 C 和 JavaScript 中目前还没有类似的功能，则需要手动构造 JSON 格式的请求数据并解析响应。

### Payload 格式约定

每个服务进程只能实现一个 `trait`，因此 Payload 中不需要声明服务名或 trait 名称。虽然底层协议支持 `method_id`，但目前全部使用默认值 `0`，保持格式统一简洁。

调用时的 Payload 格式如下：

```json
{
	"Function name (UpperCamelCase)": {
		"Arg name 1 (snake_case)": "arg 1 data",
		"Arg name 2": "arg 2 data",
	}
}
```

* `FunctionName` 使用 **UpperCamelCase**（例如 `HasherUpdate`）；
    
* 参数名采用 **snake\_case**，与 Rust 中定义一致；
    
* 参数值为实际数据：如整数、字节数组等；
    
* 字节数组用 `[0, 1, 2, 255]` 这种格式，表示 `u8` 数组。
    

**响应结构**

返回值也采用类似格式：

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

或

```json
{
  "FunctionName": {
    "Err": "ErrorType"
  }
}
```

返回值本质是 Rust 的 `Result<T, E>` 类型，其他语言可视为：

* `Ok`：调用成功，内含函数的实际返回值（如哈希上下文 ID 或字节数组）；
    
* `Err`：调用失败，值为 `CryptoError` 枚举之一（如 `InvalidSig`、`VerifyFailed` 等）。
    

### trait 接口定义参考

服务接口以 trait 形式定义如下 （[代码](https://github.com/XuJiandong/ckb-script-ipc/blob/main/crates/ckb-crypto-interface/src/lib.rs)）：

```rust
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>;
}
```

开发者可以据此推导出 Payload 的构造格式。例如：

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

## 总结

`ckb-crypto-service`是目前 CKB 合约开发中最推荐的加密解決方案之一。它基于 CKB 的多进程与 IPC 机制，为合约提供了一套统一、安全、易用的加密算法调用方式。相比传统集成算法库的方式，它不仅跨语言适配性更好，避免了依赖冲突和实现差异，还提升了开发效率和安全保障。无论是在 Rust、C 还是 JavaScript 中，它都能作为可靠的基础设施，简化合约中加密功能的实现。

---

🧑‍💻 本文作者：Han Zishuang

他是 [Nervos Network](https://www.nervos.org/) L1 CKB 区块链的开发者和 DevRel

欢迎关注他更多关于 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)
