签名机制
目录
签名概述
为什么需要签名
核心问题:
如何证明你有权花费某个 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:
- 硬件钱包生成和存储私钥
- 交易数据发送到硬件钱包
- 硬件钱包在设备内部签署
- 返回签名给主机
- 主机广播完整交易
