跳到主要内容

BTC Flow

这份指南围绕 UKey Wallet 的 BTC 地址确认与 UTXO 交易签名展开。比特币流程里最容易踩坑的是路径、脚本类型、前序交易和找零输出不匹配,因此这里重点讲这些信息如何交给设备审阅并完成校验。

目录

  1. 原理
  2. 部署
  3. 启动
  4. 适用面
  5. 交互状态
  6. 示范
  7. 末尾核对

原理

BTC 相关方法通过 SDK 入口发起,设备端会按照 UTXO 模型审阅输入、输出、找零和手续费。应用需要提供足够完整的交易上下文,设备才能确认用户看到的内容与最终交易一致。

  • HD 路径必须与脚本 purpose 对应,例如 44、49、84、86。
  • 每个输入都应能在 refTxs 中找到完整前序交易。
  • 找零输出必须使用正确路径和脚本类型,否则设备无法安全判断它是否属于用户。
  • 用户确认后,SDK 返回签名、序列化交易或交易 ID 等结果。

部署

npm i @ukeyfe/hardware-core @ukeyfe/hardware-common-connect-sdk

启动

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";
await ukeySdk.init({ env: "webusb", fetchConfig: true, debug: false });
const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

适用面

场景 1:读取地址

读取指定路径下的 BTC 地址。首次展示给用户、保存收款地址或配置找零路径时,建议开启设备端确认。

const path84 = "m/84'/0'/1'/0/0";
const opResult = await ukeySdk.btcGetAddress(connectId, deviceId, {
path: path84,
coin: "btc",
showOnUKey: true,
});
// 返回内容可参考:opResult.payload.address, publicKey?, path

入参清单

  • path / address_n: BIP44 风格路径,可用字符串或数字数组,需和脚本 purpose 对齐。
  • coin: 网络选择值,例如 btctest
  • showOnUKey?: 开启后会先在设备端展示地址并等待用户核对。

返回

Promise<{ success; payload: { address; path; publicKey?; chainCode? } }>;

场景 2:交易签名

签名交易前,请先确认输入、输出、找零和手续费都能由应用端独立计算。设备端展示内容应与前端最终广播的交易保持一致。

const { success, payload } = await ukeySdk.btcSignTransaction(
connectId,
deviceId,
{
coin: "btc",
inputs,
outputs,
refTxs,
locktime: 0,
},
);
// 返回结构大致为: { serializedTx, signatures?, txid? }

入参清单

  • coin: 用来选择 Bitcoin 网络规则和地址前缀。
  • inputs: 交易输入集合。需提供以下字段;SegWit/Taproot 必须带 amount,Legacy 也建议填写:
    • address_n(路径,与脚本匹配)
    • prev_hash(UTXO txid,hex)
    • prev_index(UTXO vout)
    • amount(字符串 satoshi)
    • script_typeSPENDADDRESS / SPENDP2SHWITNESS / SPENDWITNESS / SPENDTAPROOT
  • outputs: 交易输出集合,同时描述收款输出和找零输出:
    • 收款地址输出:address + amount + script_type(如 PAYTOADDRESS
    • 找零输出:address_n + amount + script_type 对应脚本
  • refTxs: 每个输入对应的完整前序交易;按原交易结构提供 hashversionlock_timeinputsbin_outputsoutputs
  • locktime?: 交易需要锁定时间时传入数字值。

返回

Promise<{
success;
payload: { serializedTx; signatures?: string[]; txid?: string };
}>;

交互状态

  • 方法通过 Promise 返回结果;解锁、打开 BTC App、确认输入/输出/找零/手续费等交互通过 UI_REQUEST 事件触发。
  • 同一台设备建议串行签名,避免多个请求同时等待用户确认。
  • 签名前可用 showOnUKey 让用户确认地址和路径,尤其是找零路径。

示范

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

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";

await ukeySdk.init({ env: "webusb", debug: false });

const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

const path49 = [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
0,
0,
];
await ukeySdk.btcGetAddress(connectId, deviceId, {
path: path49,
coin: "btc",
showOnUKey: true,
});

const inputs = [
{
address_n: path49,
prev_index: 0,
prev_hash:
"d29e7c4b1a0f3d5e6c7b8a99112233445566778899aabbccddeeff0012345678",
amount: "3471185",
script_type: "SPENDWITNESS",
},
];

const outputs = [
{
address_n: [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
1,
1,
],
amount: "3265985",
script_type: "PAYTOWITNESS", // 用作找零输出
},
{
address: "1FfmbHfnpaZjKFvyi1okTjJJusN455paPH",
amount: "205200",
script_type: "PAYTOADDRESS", // 对应收款方输出
},
];

const refTxs = [
{
hash: "d29e7c4b1a0f3d5e6c7b8a99112233445566778899aabbccddeeff0012345678",
version: 1,
lock_time: 0,
inputs: [
/* 这里填写前序交易的 inputs */
],
bin_outputs: [
/* 这里填写前序交易的 outputs */
],
},
];

const { success, payload } = await ukeySdk.btcSignTransaction(
connectId,
deviceId,
{
coin: "btc",
inputs,
outputs,
refTxs,
locktime: 0,
},
);

if (success) {
console.info("可广播的已签名交易:", payload.serializedTx); // 这时就能直接发出去了
}

末尾核对

核验项建议处理
路径说明与脚本44' 对应 P2PKH,49' 对应 P2SH-P2WPKH,84' 对应 P2WPKH,86' 对应 P2TR。
前序交易refTxs 要把每个输入的来源交易补全,否则设备会直接拒签。
找零地址找零返回值里的 address_n 应留在同一账户路径说明内,避免和收款地址混淆。
屏幕内容交易输入、返回值、找零和手续费摘要要和设备展示保持一致。
结果核对可以用 bitcoinjs-lib@scure/btc-signer 重建交易,再比对 txidserializedTx
常见卡点输入过多时先拆分批次,手续费异常时调整返回值与找零,用户拒签或超时则提供重试和取消。

继续查看具体 API:btcSignTransaction · btcGetAddress