Skip to Content

BTC Signer

比特币 UTXO 硬件签名全链路指南,覆盖地址确认、P2PKH / P2SH-P2WPKH / P2WPKH / Taproot 交易签名,强调“路径与脚本匹配、refTxs 完整、设备可验证”。

目录

  1. 工作原理
  2. 安装
  3. 初始化
  4. 适用场景
  5. 交互与状态
  6. 示例
  7. 验签与排障

工作原理

OneKey BTC signer 通过 HardwareSDK 与设备交互,通过 OneKey 协议请求 UTXO 审核与签名。核心要点:

  • 路径必须与脚本 purpose 对应(44/49/84/86)。
  • 在 refTxs 中提供签名流程请求的完整前序交易;具体要求取决于输入类型与固件。
  • 设备展示支持的交易确认信息,用户确认后返回签名或序列化交易。具体可展示字段取决于请求、设备与固件,应用应核对实际屏幕。

安装

npm i @onekeyfe/hd-core @onekeyfe/hd-common-connect-sdk

初始化

示例统一使用标准钱包。隐藏钱包应在每次地址和签名调用中,以经过确认的 passphraseState 替代 useEmptyPassphrase: true。受保护调用前先注册 UI 处理器,使用返回数据前检查 success。含待提供交易参数的代码是接入片段,应用仍需实现 UI、设备选择与网络数据获取。

import HardwareSDK from '@onekeyfe/hd-common-connect-sdk'; const initialized = await HardwareSDK.init({ env: 'webusb', fetchConfig: true, debug: false }); if (!initialized) throw new Error('SDK initialization failed'); const devices = await HardwareSDK.searchDevices(); if (!devices.success) throw new Error(devices.payload.error); if (!devices.payload.length) throw new Error("No device"); // Select explicitly in the application when more than one device is available. if (devices.payload.length !== 1) throw new Error('Select exactly one device before continuing'); const { connectId } = devices.payload[0]; if (!connectId) throw new Error("Missing connectId"); const state = await HardwareSDK.getDeviceState(connectId); if (!state.success) throw new Error(state.payload.error); const deviceId = state.payload.identity.deviceId; if (!deviceId) throw new Error("Device has no wallet identity");

适用场景

场景 1:获取地址

const res = await HardwareSDK.btcGetAddress(connectId, deviceId, { useEmptyPassphrase: true, path: "m/84'/0'/0'/0/0", coin: 'btc', showOnOneKey: true, }); if (!res.success) throw new Error(res.payload.error); // res.payload.address and res.payload.path are strings.

参数

  • path: 必填,BIP44 路径或数组;需与脚本 purpose 对应。
  • coin: 获取地址时可选,网络标识(如 btc / test)。
  • showOnOneKey?: 可选,是否在设备上展示并确认。

返回

type AddressResult = | { success: true; payload: { address: string; path: string } } | { success: false; payload: { error: string; code?: string | number } };

场景 2:签名交易

const { success, payload } = await HardwareSDK.btcSignTransaction(connectId, deviceId, { useEmptyPassphrase: true, coin: 'btc', inputs, outputs, refTxs, locktime: 0, }); if (!success) throw new Error(payload.error); // Verify payload.serializedTx and payload.signatures before broadcasting.

参数

  • coin: 必填,决定网络规则与前缀。
  • inputs: 必填,数组,字段包含(当前公共类型要求每个输入提供 amount):
    • address_n(路径,与脚本匹配)
    • prev_hash(UTXO txid,hex)
    • prev_index(UTXO vout)
    • amount(字符串 satoshi)
    • script_type(SPENDADDRESS / SPENDP2SHWITNESS / SPENDWITNESS / SPENDTAPROOT)
  • outputs: 必填,数组,字段包含:
    • 收款地址输出:address + amount + script_type(如 PAYTOADDRESS)
    • 找零输出:address_n + amount + script_type 对应脚本
  • refTxs: 必填数组,包含固件请求的完整前序交易;仅在签名流程不请求前序交易时可为空(需包含 hash、version、lock_time、inputs、bin_outputs 或 outputs,按前序交易实际结构填写)
  • locktime?: 可选,数字

返回

type SignResult = | { success: true; payload: { serializedTx: string; signatures: string[]; txid?: string } } | { success: false; payload: { error: string; code?: string | number } };

交互与状态

  • 接口以 Promise 结束;用户提示通过 UI_REQUEST 事件触发(解锁设备、确认输入/输出/找零/手续费)。
  • 同一设备请串行调用;签名前用 showOnOneKey 确认收款/找零路径。

示例

示例:P2SH-P2WPKH 交易(嵌套 SegWit)

以下是准备参数的片段。调用前须将示意 UTXO/输出替换为实际交易,并加载与之匹配的完整 refTxs。

import HardwareSDK from '@onekeyfe/hd-common-connect-sdk'; import type { BTCSignTransactionParams } from '@onekeyfe/hd-core'; const initialized = await HardwareSDK.init({ env: 'webusb', debug: false }); if (!initialized) throw new Error('SDK initialization failed'); const devices = await HardwareSDK.searchDevices(); if (!devices.success) throw new Error(devices.payload.error); if (!devices.payload.length) throw new Error("No device"); // Select explicitly in the application when more than one device is available. if (devices.payload.length !== 1) throw new Error('Select exactly one device before continuing'); const { connectId } = devices.payload[0]; if (!connectId) throw new Error("Missing connectId"); const state = await HardwareSDK.getDeviceState(connectId); if (!state.success) throw new Error(state.payload.error); const deviceId = state.payload.identity.deviceId; if (!deviceId) throw new Error("Device has no wallet identity"); const path49 = [(49 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, 0, 0]; const confirmedAddress = await HardwareSDK.btcGetAddress(connectId, deviceId, { path: path49, coin: 'btc', showOnOneKey: true, useEmptyPassphrase: true, }); if (!confirmedAddress.success) throw new Error(confirmedAddress.payload.error); const inputs: BTCSignTransactionParams['inputs'] = [ { address_n: path49, prev_index: 0, prev_hash: 'b035d89d4543ce5713c553d69431698116a822c57c03ddacf3f04b763d1999ac', amount: '3382047', script_type: 'SPENDP2SHWITNESS', }, ]; const outputs: BTCSignTransactionParams['outputs'] = [ { address_n: [(49 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, (0 | 0x80000000) >>> 0, 1, 1], amount: '3181747', script_type: 'PAYTOP2SHWITNESS', // change }, { address: '18WL2iZKmpDYWk1oFavJapdLALxwSjcSk2', amount: '200000', script_type: 'PAYTOADDRESS', // recipient }, ]; // Load complete previous transactions matching each requested prev_hash. declare const refTxs: BTCSignTransactionParams['refTxs']; const { success, payload } = await HardwareSDK.btcSignTransaction(connectId, deviceId, { useEmptyPassphrase: true, coin: 'btc', inputs, outputs, refTxs, locktime: 0, }); if (!success) throw new Error(payload.error); // Verify the signed transaction before submitting it through your Bitcoin client.

验签与排障

  • 路径 ↔ 脚本:
    • 44' → P2PKH
    • 49' → P2SH-P2WPKH
    • 84' → P2WPKH
    • 86' → P2TR
  • refTxs 必须包含固件请求的完整前序交易;缺失被请求的数据会被拒签。
  • 找零:确保 address_n 属于同一账户路径,防止用户混淆。
  • 设备确认:输入、输出、找零、手续费需与前端显示一致。
  • 验签:用 bitcoinjs-lib / @scure/btc-signer 重建交易,校验 txid 与 serializedTx。
  • 常见问题:
    • 输入过多:向用户报告限制;拆分或调整交易后,必须重新构造请求并让用户核对。
    • 手续费异常:停止当前签名;经用户确认后重新计算输出、找零与 dust 限制,不自动改写已确认的交易。
    • 拒签/超时:提供重试/取消,不要自动重播。

参见方法文档:btcSignTransaction · btcGetAddress。

最后更新于