Skip to content

签名机制

目录

  1. 签名概述
  2. 交易签名过程
  3. SIGHASH 类型
  4. 签名脚本
  5. 多重签名
  6. 签名验证
  7. 实际例子
  8. 常见问题

签名概述

为什么需要签名

核心问题:
  如何证明你有权花费某个 UTXO?

解决方案:
  使用持有的私钥进行数字签名
  任何人都可用公钥验证

过程:
  1. 签署者:用私钥签署消息
  2. 验证者:用公钥验证签名真伪
  3. 结果:只有私钥持有者能创建有效签名

签名的作用

1. 身份认证
   证明交易由私钥持有者发起

2. 不可否认性
   签署者不能否认发送过此交易

3. 完整性保护
   签名涵盖交易数据
   任何改动都使签名失效

4. 防双花
   只有有效签名的交易才能花费 UTXO

交易签名过程

签名步骤

第 1 步:序列化交易

构造要签名的交易数据:

不签名的交易 = 
  版本号 + 输入计数 + 所有输入 + 输出计数 + 所有输出 + 锁定时间

其中,要签名的输入的脚本部分替换为:
  前一交易的锁定脚本(需要满足的条件)

目的:
  确保签名包含整个交易数据
  防止交易被篡改

第 2 步:计算消息哈希

计算交易的哈希值:

message_hash = SHA256(SHA256(serialized_transaction))

这是要被签名的消息。

比特币规范:
  使用双重 SHA-256
  结果是 256 比特(32 字节)

第 3 步:使用私钥签名

使用 ECDSA 签名:

signature = ECDSA_Sign(message_hash, private_key)

输出:
  签名 = (r, s),每个 256 比特

DER 编码:
  将 (r, s) 编码为 DER 格式
  通常 71-72 字节

SIGHASH 标志:
  附加 SIGHASH 类型字节(通常 0x01)

第 4 步:构造签名脚本

对于 P2PKH(最常见):

签名脚本 = <signature> <public_key>

示例:
  <72 字节签名> <33 字节压缩公钥>
  
  完整脚本:
    47              (71 字节长)
    30 44 ...       (DER 编码的签名)
    21              (33 字节长)
    02 79 be ...    (压缩公钥)

对于其他类型可能不同

SIGHASH 类型

SIGHASH 概念

SIGHASH 类型指定:
  签名涵盖交易的哪些部分

作用:
  允许灵活的交易签名策略
  支持多方交易编辑

在 DER 签名后附加:
  0x01:SIGHASH_ALL(最常见)
  0x02:SIGHASH_NONE
  0x03:SIGHASH_SINGLE
  0x80:SIGHASH_ANYONECANPAY(可与以上组合)

SIGHASH_ALL (0x01)

最常见的方式

覆盖范围:
  ✓ 所有输入
  ✓ 所有输出
  ✓ 锁定时间

含义:
  签署整个交易
  任何改动都使签名失效

使用场景:
  标准交易
  安全的一次性支付

示例:
  Alice 签署支付给 Bob 的交易
  签名后,交易不能被修改

SIGHASH_NONE (0x02)

较少使用

覆盖范围:
  ✓ 所有输入
  ✗ 不覆盖输出
  ✓ 锁定时间

含义:
  签署输入,但不锁定输出
  其他人可以改变输出内容

使用场景:
  委托情况
  筹款(输出可被修改)

示例:
  Alice 签署"花费这些 UTXO"
  Bob 可以决定输出如何使用

SIGHASH_SINGLE (0x03)

特殊用途

覆盖范围:
  ✓ 所有输入
  ✓ 对应索引的输出
  ✗ 其他输出
  ✓ 锁定时间

含义:
  每个输入签署对应索引的输出
  输入 0 对应输出 0,等等

使用场景:
  原子交换
  支付渠道

示例:
  Alice(输入 0)签署输出给 Bob 的输出 0
  Charlie(输入 1)签署输出给 Dave 的输出 1
  双方同时提交输入,原子执行

SIGHASH_ANYONECANPAY (0x80)

与其他类型组合

组合方式:
  0x81:SIGHASH_ALL | ANYONECANPAY
  0x82:SIGHASH_NONE | ANYONECANPAY
  0x83:SIGHASH_SINGLE | ANYONECANPAY

含义:
  只覆盖当前输入
  其他输入可被添加或移除

使用场景:
  多方共同签署
  灵活的交易构造

示例:
  多个人想共同支付一笔款项
  每人独立签署自己的输入
  可以自由添加/移除参与者

签名脚本

P2PKH 签名脚本

标准脚本:
  <signature> <public_key>

执行过程(验证时):
  1. 栈:[]
  2. push 签名:[签名]
  3. push 公钥:[签名, 公钥]
  4. OP_DUP:[签名, 公钥, 公钥]
  5. OP_HASH160:[签名, 公钥, HASH160(公钥)]
  6. push <hash>:[签名, 公钥, HASH160(公钥), 预期hash]
  7. OP_EQUALVERIFY:[签名, 公钥](如果相等)
  8. OP_CHECKSIG:[] 或 1/0(验证成功/失败)

多重签名脚本

m-of-n 多重签名

锁定脚本:
  OP_m <pubkey1> ... <pubkeyn> OP_n OP_CHECKMULTISIG

示例(2-of-3):
  OP_2 <pub1> <pub2> <pub3> OP_3 OP_CHECKMULTISIG

解锁脚本:
  OP_0 <sig1> <sig2>

执行验证:
  需要 n 个公钥中的 m 个有效签名

多重签名

多重签名概念

定义:
  m-of-n:需要 n 个人中的 m 个签署才能花费

安全性:
  增加所需的密钥数量
  防止单点故障

应用场景:
  公司资金:需要多个部门主管
  冷钱包:多个钥保管者
  托管:第三方见证

示例:
  2-of-3 多签
    Alice、Bob、Charlie 各有一把密钥
    需要任意两人同意才能转账
    即使一人丢失密钥,仍可操作

多重签名的构造

步骤 1:生成密钥
  key1 = random_256_bits()
  key2 = random_256_bits()
  key3 = random_256_bits()

步骤 2:计算公钥
  pub1 = key1 × G
  pub2 = key2 × G
  pub3 = key3 × G

步骤 3:构造锁定脚本(P2SH)
  script = OP_2 pub1 pub2 pub3 OP_3 OP_CHECKMULTISIG
  
  script_hash = RIPEMD160(SHA256(script))

步骤 4:地址
  address = Base58Check(0x05 + script_hash)
  地址以 3 开头(P2SH)

步骤 5:花费时(需要 2 个签名)
  解锁脚本:
    OP_0 <sig1> <sig2>
    
  完整脚本(验证时):
    <sig1> <sig2> OP_2 pub1 pub2 pub3 OP_3 OP_CHECKMULTISIG

多重签名的费用

费用计算:
  多重签名脚本更长
  → 交易更大
  → 费用更高

大小估算:
  2-of-3 多签:约 370 字节
  P2PKH 交易:约 226 字节
  
  差异:64% 更大
  费用:64% 更高

优化(使用 P2SH):
  P2SH 隐藏脚本
  未花费时只需支付短的 script_hash
  花费时才需要完整脚本
  通常平衡成本

签名验证

验证过程

步骤 1:提取签名和公钥
  从交易的签名脚本中提取

步骤 2:构造消息
  重新序列化交易(使用锁定脚本替代签名脚本)
  计算 SHA256(SHA256(tx))

步骤 3:验证签名
  使用 ECDSA 验证:
  ECDSA_Verify(message_hash, signature, public_key)

步骤 4:检查输出
  如果 ECDSA 验证成功 → 签名有效
  如果验证失败 → 交易无效,被拒绝

验证的安全性

节点验证要点:

1. 签名必须有效
   ✓ ECDSA 验证通过

2. 公钥必须与锁定脚本匹配
   ✓ Hash160(public_key) == 锁定脚本中的值

3. UTXO 必须存在
   ✓ 前一交易必须在区块链上
   ✓ 输出索引必须有效

4. UTXO 必须未被花费
   ✓ 检查 UTXO 集合

5. 输入总和 >= 输出总和 + 费用
   ✓ 防止凭空创造比特币

所有检查都通过 → 交易有效

实际例子

完整签名例子

场景:Alice 发送 1 BTC 给 Bob

Alice 的私钥:d = 0xe9873d...
Alice 的公钥:Q = 0x0279be...
Alice 的地址:1A1z7a...
UTXO:5 BTC 来自交易 abc123..., 输出 0

步骤 1:构造交易(不含签名脚本)
  版本:01 00 00 00
  输入数:01
  前一 TXID:abc123...(32 字节小端序)
  Vout:00 00 00 00
  脚本长度:19
  脚本:(锁定脚本替代位置)
  序列:ff ff ff ff
  输出数:02
  输出 1:Bob 的地址,1 BTC
  输出 2:找零 3.99 BTC
  锁定时间:00 00 00 00

步骤 2:计算消息哈希
  serialized = 上面序列化的交易
  message_hash = SHA256(SHA256(serialized))
  = 0xabcd1234...(32 字节)

步骤 3:使用 ECDSA 签名
  signature = ECDSA_Sign(message_hash, d)
  = (r, s)
  
  DER 编码:
  30 44 02 20 [r] 02 20 [s]

步骤 4:构造签名脚本
  script_sig = <signature> <public_key>
             = 47 [DER签名] 21 [公钥]

步骤 5:创建完整交易
  原始交易 + 签名脚本

步骤 6:广播
  发送到比特币网络
  矿工验证并打包

步骤 7:验证(矿工/节点)
  重构消息哈希
  ECDSA_Verify(hash, sig, pubkey)
  ✅ 成功 → 交易有效

常见问题

Q1: 如果签名被泄露会怎样?

A:

  • 单个交易的签名被泄露不会直接导致私钥恢复
  • 但多个交易的签名泄露可能允许恢复私钥
  • 更重要的是:签名后的交易是不可改的,泄露无害

Q2: 可以签署一半的交易吗?

A:

  • 可以,使用 SIGHASH_ALL | ANYONECANPAY
  • 其他人可以添加他们的输入
  • 但要谨慎,可能存在安全风险

Q3: 签名过程中为什么需要 k(随机数)?

A:

  • k 提供随机性
  • 相同消息产生不同的签名
  • 防止签名分析攻击

Q4: 为什么每笔交易需要单独签名?

A:

  • 每笔交易都是独立的 UTXO 消费
  • 需要证明对该具体 UTXO 的控制权
  • 允许灵活的交易构造

Q5: 硬件钱包如何签署交易?

A:

  • 硬件钱包生成和存储私钥
  • 交易数据发送到硬件钱包
  • 硬件钱包在设备内部签署
  • 返回签名给主机
  • 主机广播完整交易