区块链

为什么比特币交易的每个输入都需要单独签名?

理解比特币交易中多输入签名的必要性,避免常见误解,并了解 P2PKH 签名的基本逻辑、SDK 使用方法以及签名保护的具体内容。

林知衡

林知衡

technical_editor

发布于 2026年6月18日3 分钟阅读

一句话理解

签名每个输入,就是为交易中每个被花费的 UTXO 提供满足其锁定脚本的解锁证据。一笔交易可以有多个输入,每个输入都在花费一个不同的旧输出,因此每个输入都必须独立证明自己有权花费对应的 UTXO。

为什么比特币交易的每个输入都需要单独签名? 文章封面

为什么不是只签一次?

新手常以为一笔交易只需一个签名,但实际情况取决于输入的数量。如果交易有 3 个输入:

TEXT
1input 0 -> 花掉 UTXO A
2input 1 -> 花掉 UTXO B
3input 2 -> 花掉 UTXO C

那么每个输入都必须满足其引用的 UTXO 的锁定条件。这些 UTXO 可能属于同一私钥,也可能属于不同私钥,甚至可能使用不同的脚本类型。因此,签名并非“给整笔交易盖个章”那么简单,而是每个输入都要完成对应的解锁逻辑。

P2PKH 的签名逻辑

普通 P2PKH 输出的锁定脚本大致如下:

你要提供:

  1. 一个公钥。
  2. 一个签名。

并且:

  1. 公钥哈希要等于输出锁定的哈希。
  2. 签名要能被这个公钥验证。

花费这个输出时,输入的解锁脚本通常包含 <signature> <public key>。执行时,解锁脚本与上一笔输出的锁定脚本组合验证,如果结果为真,该输入才算有效。

用 SDK 创建签名模板

在官方低层示例中,P2PKH 输入使用 unlockingScriptTemplate: new P2PKH().unlock(privateKey)。完整片段类似:

TypeScript
1tx.addInput({
2 sourceTransaction: Transaction.fromHex(sourceTxHex),
3 sourceOutputIndex: 0,
4 unlockingScriptTemplate: new P2PKH().unlock(privateKey)
5})

这一步并非立刻生成最终签名,而是告知 SDK:该输入应使用该私钥按 P2PKH 模板解锁。之后调用 await tx.sign(),SDK 会为需要签名的输入生成对应的解锁脚本。

签名保护了什么?

签名的目的不仅是证明“我知道一个私钥”,它还要将当前交易的关键内容绑定起来,防止他人修改交易后复用你的签名。例如,如果签名不覆盖输出,攻击者可能将收款地址改为自己的地址;如果签名不覆盖金额或脚本上下文,验证就可能被绕过。比特币交易签名有具体的 sighash 规则,入门阶段不必深入所有细节,但需要知道:签名与当前交易结构相关,不能脱离交易上下文单独理解。

多输入交易的签名顺序

构造多输入交易时,通常按以下顺序操作:

  1. 选输入
  2. 建输出
  3. 添加找零
  4. 计算手续费
  5. 签名每个输入
  6. 验证交易
  7. 序列化或广播

如果签名后又修改输出,签名可能失效,因为签名通常承诺了交易内容。不要将签名视为中途可随意修改交易的步骤。

钱包签名和低层私钥签名

在 BSV 技术栈中,阶段 7 使用 WalletClient,签名由钱包管理,应用不直接接触私钥;阶段 8 学习低层签名是为了理解机制。真实用户应用仍应优先使用钱包接口,让私钥留在钱包内。

两者区别:

  • WalletClient 签名:私钥在钱包里,应用请求钱包创建或签名 action,适合用户应用。
  • 低层私钥签名:代码直接持有私钥,开发者手动构造交易并签名,适合学习、测试、专用后端或底层工具。

低层签名有助于理解交易,但不要将“把用户私钥放进应用代码”当作生产模式。

验证签名

签名后,可用 SDK 的验证方法检查交易结构是否正确:

TypeScript
1await tx.sign()
2const ok = await tx.verify()
3console.log(ok)

验证失败的常见原因包括:

  • 私钥和上一笔输出的地址不匹配。
  • 源交易或输出索引错误。
  • 金额或脚本信息错误。
  • 签名后又修改了交易内容。
  • 手续费或找零导致结构不符合预期。

验证通过并不保证广播一定成功,还涉及网络策略和服务状态,但验证是广播前的重要检查。

新手常见误解

  • 一笔交易不一定只有一个签名,每个输入都可能需要自己的解锁证据。
  • 签名不是直接签 txid,而是按特定规则绑定交易内容。
  • P2PKH 解锁需要签名和公钥,而不只是地址。
  • 签名后不要随意修改交易,修改输出、金额或脚本可能使签名失效。
  • 私钥不能出现在前端代码、日志、截图或教程仓库中。示例 WIF 必须是占位符或测试专用。

参考来源

推荐文章