核心概念

Signer

Signer 代表已连接的钱包,提供签名、地址查询和交易发送能力。

在 GitHub 上编辑

什么是 Signer?

ccc.Signer 是 CCC 的核心抽象,统一了多个区块链生态的签名方式。同一份应用代码,通过同一套 API,即可对接来自 CKB、EVM、Bitcoin、Nostr、Doge 等生态的钱包——最终统一控制 CKB 资产。

// 来自 packages/core/src/signer/signer/index.ts
export abstract class Signer {
  constructor(protected client_: Client) {}

  abstract get type(): SignerType;
  abstract get signType(): SignerSignType;

  get client(): Client {
    return this.client_;
  }
}
  • type——Signer 所属的区块链生态(CKB / EVM / BTC / Nostr / Doge)。
  • signType——具体的签名方案(CkbSecp256k1、EvmPersonal、BtcEcdsa、NostrEvent、JoyId、DogeEcdsa)。
  • client——用于与 CKB 节点通信的 ccc.Client 实例。

Signer 通常不需要手工构造。它们由钱包连接器生成(例如 React 连接器中的 useCcc()SignersController)。在服务端代码中也可以直接实例化(参见 获取 Signer)。

Signer 继承关系

Signer 枚举

SignerType

标识 Signer 所属的区块链生态。

// 来自 packages/core/src/signer/signer/index.ts
export enum SignerType {
  EVM   = "EVM",
  BTC   = "BTC",
  CKB   = "CKB",
  Nostr = "Nostr",
  Doge  = "Doge",
}

SignerSignType

标识 Signer 使用的具体签名方案。

// 来自 packages/core/src/signer/signer/index.ts
export enum SignerSignType {
  Unknown      = "Unknown",
  BtcEcdsa     = "BtcEcdsa",
  EvmPersonal  = "EvmPersonal",
  JoyId        = "JoyId",
  NostrEvent   = "NostrEvent",
  CkbSecp256k1 = "CkbSecp256k1",
  DogeEcdsa    = "DogeEcdsa",
}

核心方法

连接管理

// 连接钱包
abstract connect(): Promise<void>;

// 断开钱包连接
async disconnect(): Promise<void>;

// 检查当前是否已连接
abstract isConnected(): Promise<boolean>;

地址查询

// 获取钱包的内部(原生)地址字符串
abstract getInternalAddress(): Promise<string>;

// 获取用于验证签名的身份标识(通常是地址或公钥)
abstract getIdentity(): Promise<string>;

// 获取推荐的 CKB 地址字符串
async getRecommendedAddress(preference?: unknown): Promise<string>;

// 获取所有 CKB 地址字符串
async getAddresses(): Promise<string[]>;

// 获取所有 Address 对象(包含对应的 Script)
abstract getAddressObjs(): Promise<Address[]>;

// 获取推荐的 Address 对象
async getRecommendedAddressObj(_preference?: unknown): Promise<Address>;

余额查询

// 返回所有地址的总余额(单位:Shannon)
async getBalance(): Promise<Num>;

消息签名

// 对消息签名,返回完整的 Signature 对象
async signMessage(message: string | BytesLike): Promise<Signature>;

// 对消息签名,仅返回签名字符串(由子类实现)
abstract signMessageRaw(message: string | BytesLike): Promise<string>;

// 验证消息签名
async verifyMessage(
  message: string | BytesLike,
  signature: string | Signature,
): Promise<boolean>;

// 静态验证器,根据 signature.signType 自动调度算法
static async verifyMessage(
  message: string | BytesLike,
  signature: Signature,
): Promise<boolean>;

交易处理

// 准备交易(添加 cell deps、占位 witness 等)
async prepareTransaction(tx: TransactionLike): Promise<Transaction>;

// 对已准备好的交易签名
async signOnlyTransaction(_: TransactionLike): Promise<Transaction>;

// 准备并签名,一步完成
async signTransaction(tx: TransactionLike): Promise<Transaction>;

// 签名并广播,返回交易哈希
async sendTransaction(tx: TransactionLike): Promise<Hex>;

链上数据查询

// 异步生成器,逐个返回该 Signer 持有的 Cell
async *findCells(
  filter: ClientCollectableSearchKeyFilterLike,
  withData?: boolean | null,
  order?: "asc" | "desc",
  limit?: number,
): AsyncGenerator<Cell>;

// 异步生成器,逐个返回与该 Signer 相关的交易
async *findTransactions(
  filter: ClientCollectableSearchKeyFilterLike,
  groupByTransaction?: boolean | null,
  order?: "asc" | "desc",
  limit?: number,
): AsyncGenerator<...>;

具体实现

CKB Signer

SignerCkbPublicKey

基于 33 字节压缩公钥的只读 Signer,支持发现 AnyoneCanPay 地址。

  • getAddressObjSecp256k1()——返回 Secp256k1Blake160 地址。
  • getAddressObjs()——返回主地址,外加最多 10 个已发现的 AnyoneCanPay 地址。

SignerCkbPrivateKey

继承自 SignerCkbPublicKey,使用 32 字节私钥实现签名。

签名流程:

  1. 计算消息哈希:hashCkb("Nervos Message:" + message)
  2. 生成带恢复 ID 的 65 字节 ECDSA 签名。
  3. 将签名写入 witness 的 lock 字段。

EVM Signer

允许 Ethereum 系钱包通过 OmniLock 或 PWLock 脚本控制 CKB 资产。

Lock 脚本 args 格式:

  • OmniLock(新版本,flag 0x12):[0x12, ...evmAddress(20 字节), 0x00]
  • OmniLock(旧版本,flag 0x01):[0x01, ...evmAddress(20 字节), 0x00]
  • PWLock:evmAddress(20 字节),直接映射地址。

签名流程:

  • OmniLock:使用 personal_sign,消息前缀为 "CKB transaction: " + txHash
  • PWLock:使用 Keccak256 而非 Blake2b 计算交易哈希。

BTC Signer

Bitcoin Signer 使用 OmniLock 的 Bitcoin 认证标志(0x04)。

  • Args:[0x04, ...btcEcdsaPublicKeyHash(publicKey), 0x00]
  • btcEcdsaPublicKeyHash = RIPEMD160(SHA256(publicKey))

签名流程:

  • 消息格式:"CKB (Bitcoin Layer) transaction: " + txHash
  • 调用钱包的 signMessage(返回 base64),再调整恢复标志。

"调整恢复标志"是指将比特币钱包返回的签名中的恢复标志(recovery flag)从比特币标准格式转换为 CKB OmniLock 需要的格式。

Nostr Signer

通过 Nostr 事件签名(NIP-01)控制 CKB 资产。

NostrLock 脚本 args:[0x00, ...hashCkb(pubkey)[0:21]]

事件结构:

{
  "pubkey": "nostr_public_key_hex",
  "created_at": 1234567890,
  "kind": 23334,
  "tags": [["ckb_sighash_all", "transaction_hash_hex"]],
  "content": "Signing a CKB transaction...",
  "id": "event_id",
  "sig": "event_signature"
}

常量:

  • CKB_UNLOCK_EVENT_KIND = 23334
  • CKB_SIG_HASH_ALL_TAG = "ckb_sighash_all"

JoyID Signer

基于 WebAuthn / passkeys 的 CKB Signer。

子密钥(sub_key)钱包支持:

  • 向 JoyID Aggregator 服务查询解锁 SMT 证明。
  • 添加 COTA cell dependency。
  • 由于 WebAuthn 签名格式的特殊性,witness 占位大小为 1000 字节。

Lock 脚本生成汇总

Signer输入Lock 脚本Args 格式Witness 大小
CKB33 字节公钥Secp256k1Blake160hashCkb(pubkey)[0:20]65 字节
CKB(同上)AnyoneCanPayhashCkb(pubkey)[0:20] + ...65 字节
EVM20 字节地址OmniLock(现代)0x12 + address + 0x0085 字节
EVM(同上)OmniLock(旧版)0x01 + address + 0x0085 字节
EVM(同上)PWLockaddress65 字节
BTC33/65 字节公钥OmniLock0x04 + hash160(pubkey) + 0x0085 字节
Nostr32 字节公钥NostrLock0x00 + hashCkb(pubkey)[0:21]572 字节
JoyIDWebAuthn 公钥JoyIDhashCkb(pubkey)[0:20]1000 字节

钱包集成

EIP-6963(MetaMask、OKX 等)

import { Signer } from "@ckb-ccc/eip6963";

const signer = new Signer(client, provider);
await signer.connect();
const address = await signer.getRecommendedAddress();

NIP-07(Nostr 扩展)

import { Signer } from "@ckb-ccc/nip07";

const signer = new Signer(client, window.nostr);
await signer.connect();

JoyID

import { getJoyIdSigners } from "@ckb-ccc/joy-id";

const signers = getJoyIdSigners(client, "My App", "icon.png");

使用示例

获取地址与余额

import { ccc } from "@ckb-ccc/ccc";

async function printWalletInfo(signer: ccc.Signer) {
  if (!(await signer.isConnected())) {
    await signer.connect();
  }

  const address = await signer.getRecommendedAddress();
  console.log("CKB 地址:", address);

  const allAddresses = await signer.getAddresses();
  console.log("所有地址:", allAddresses);

  const balance = await signer.getBalance();
  console.log("余额(Shannon):", balance.toString());
}

签名与验证消息

import { ccc } from "@ckb-ccc/ccc";

async function signAndVerify(signer: ccc.Signer, message: string) {
  const sig = await signer.signMessage(message);
  console.log("签名:", sig.signature);
  console.log("签名类型:", sig.signType);

  // 实例方法验证(使用当前 Signer 的算法)
  const valid = await signer.verifyMessage(message, sig);

  // 静态方法验证——根据 sig.signType 自动分发,无需 Signer 实例
  const valid2 = await ccc.Signer.verifyMessage(message, sig);

  console.log("验证结果:", valid, valid2);
}

获取 Signer

实际开发中,Signer 通常由钱包连接器提供,而非直接实例化。

import { useCcc } from "@ckb-ccc/connector-react";

function MyComponent() {
  const { signer } = useCcc();

  if (!signer) {
    return <p>未连接钱包</p>;
  }

  // signer 即 ccc.Signer 实例
}
import { ccc } from "@ckb-ccc/shell";

// 服务端可直接构造 Signer
const signer = new ccc.SignerCkbPrivateKey(client, privateKey);
await signer.connect();

最后更新于

目录