Skip to main content

CKB Crypto Service

ckb-crypto-service is an encryption service built on CKB’s multi-process architecture and IPC (inter-process communication) mechanism. It provides a unified, secure, and easy-to-use cryptographic interface for smart contracts.

Unlike traditional approaches where cryptographic libraries are embedded directly into contracts—leading to compatibility issues, dependency conflicts, and inconsistent implementations—this service decouples cryptographic logic from contracts. It supports multiple programming languages such as Rust, C, and JavaScript, significantly improving development efficiency and security. It is the preferred infrastructure for implementing cryptographic functionality in CKB smart contracts.

Supported Algorithms

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

How It Works

ckb-crypto-service communicates across processes using the CKB Spawn mechanism and wraps IPC interfaces via ckb-script-ipc. See related IPC documentation.

The service is started using spawn, with a pair of file descriptors passed in. After startup, it listens for requests from the parent process using ckb_script_ipc_common::spawn::run_server. On receiving a request, it executes the corresponding cryptographic function and sends the result back.

Rust Integration

A Rust wrapper is provided via ckb-crypto-interface for convenient usage.

Initialization

let (read_pipe, write_pipe) = spawn_cell_server(
&args[0..32],
ckb_std::ckb_types::core::ScriptHashType::Data2,
&[CString::new("").unwrap().as_ref()],
).unwrap();

let crypto_cli = CkbCryptoClient::new(read_pipe, write_pipe);

(CkbCryptoClient is auto-generated by ckb-script-ipc.)

API Methods

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

These methods can be called using the initialized crypto_cli instance.

Hashing Example

Supported hash functions:

  • CkbBlake2b
  • Blake2b
  • Sha256
  • Ripemd160

Usage:

  1. Create a context using hasher_new
  2. Feed data using hasher_update
  3. Retrieve the result using hasher_finalize

Example:

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 finalize");

Signature Verification

Available methods:

  • secp256k1_recovery: recover public key from signature
  • secp256k1_verify: verify signature using known public key (more efficient)
  • schnorr_verify
  • ed25519_verify

Notes:

  • prehash must be 32 bytes (already hashed input)
  • signature is variable-length
  • recovery_id is required for recovering public key in secp256k1_recovery

JavaScript Integration

JavaScript currently lacks an auto-generation tool like Rust’s ckb_script_ipc::service, so IPC packets must be constructed and parsed manually. See TypeScript example.

Basic usage:

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

// Example: run Blake2b hash
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;
}

Create the IPC channel:

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

Deployment

None