Bitcoin 脚本系统
目录
Bitcoin 脚本概述
什么是 Bitcoin 脚本
Bitcoin 脚本是一种编程语言:
定位:
栈式、先进先出(FIFO)的语言
非图灵完备(无法形成无限循环)
面向交易验证设计
作用:
定义如何花费 UTXO
验证交易的合法性
例:
scriptPubKey:定义条件
scriptSig:提供解决方案设计原则
1. 安全性优先
防止任意代码执行
限制操作复杂度
2. 确定性
任何节点验证结果相同
无浮动性结果
3. 简洁性
使用栈式计算
便于验证
4. 透明性
所有规则公开
没有隐藏行为脚本基础
栈式机制
工作原理:
操作数入栈:
OP_1 OP_2 OP_ADD
步骤 1:OP_1 入栈 → 栈:[1]
步骤 2:OP_2 入栈 → 栈:[1, 2]
步骤 3:OP_ADD 执行 → 栈:[3]
结果:
栈顶值为 3
特点:
LIFO(后进先出)
操作基于栈顶元素脚本组成
两部分组成:
scriptPubKey(输出脚本):
锁定脚本
附加在 UTXO 上
定义如何使用这笔钱
scriptSig(输入脚本):
解锁脚本
提供满足条件的数据
尝试解锁前面的 UTXO
验证流程:
scriptSig + scriptPubKey = 有效
结果栈顶为 true = 花费成功操作码
基础操作码
数据操作:
OP_PUSH_DATA:
将数据推入栈
例:<signature> <pubkey>
OP_DUP:
复制栈顶
栈:[a] → [a, a]
OP_DROP:
删除栈顶
栈:[a, b] → [a]
OP_SWAP:
交换顶部两元素
栈:[a, b] → [b, a]
OP_OVER:
复制第二个元素到栈顶
栈:[a, b] → [a, b, a]算术操作码
OP_ADD:
弹出两个数,相加
栈:[a, b] → [a+b]
OP_SUB:
栈:[a, b] → [a-b]
OP_MUL:
栈:[a, b] → [a*b]
OP_DIV:
栈:[a, b] → [a/b]
OP_MOD:
栈:[a, b] → [a%b]
OP_EQUAL:
比较是否相等
栈:[a, b] → [true/false]
OP_LESSTHAN:
小于比较
栈:[a, b] → [a < b]密码学操作码
OP_HASH160:
栈:[data] → [RIPEMD160(SHA256(data))]
OP_SHA256:
栈:[data] → [SHA256(data)]
OP_RIPEMD160:
栈:[data] → [RIPEMD160(data)]
OP_CHECKSIG:
验证签名
栈:[pubkey, sig] → [true/false]
检查签名是否有效
OP_CHECKSIGVERIFY:
OP_CHECKSIG + OP_VERIFY
如果验证失败则终止脚本
OP_CHECKMULTISIG:
验证多重签名
栈:[pubkeys..., sigs..., n, m] → [true/false]控制流操作码
OP_IF:
条件分支
栈:[condition] → 根据值选择分支
OP_ELSE:
与 OP_IF 搭配
OP_ENDIF:
结束条件
OP_VERIFY:
验证栈顶值为 true
不为 true 则脚本失败
OP_RETURN:
立即脚本失败常见脚本类型
P2PKH(Pay to Public Key Hash)
最常见的脚本类型
scriptPubKey:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig:
<signature> <pubkey>
验证过程:
1. scriptSig 入栈
栈:[signature, pubkey]
2. scriptPubKey 开始执行
OP_DUP
栈:[signature, pubkey, pubkey]
3. OP_HASH160
栈:[signature, pubkey, hash160(pubkey)]
4. <pubKeyHash> 入栈
栈:[signature, pubkey, hash160(pubkey), targetHash]
5. OP_EQUALVERIFY
检查哈希相等
成功则栈:[signature, pubkey]
6. OP_CHECKSIG
验证签名
成功则栈:[true]
7. 栈顶为 true,脚本成功P2SH(Pay to Script Hash)
更灵活的脚本类型
允许复杂条件
scriptPubKey:
OP_HASH160 <scriptHash> OP_EQUAL
scriptSig:
<data> ... <redeemScript>
特点:
实际脚本(redeemScript)在解锁时提供
可以隐藏复杂逻辑
验证:
1. 检查 redeemScript 的哈希是否匹配
2. 执行 redeemScript 进行实际验证
用途:
多重签名
时间锁定
条件支付P2WPKH(Pay to Witness Public Key Hash)
SegWit 见证脚本
减少交易大小
scriptPubKey:
OP_0 <pubKeyHash>
特点:
使用见证字段而非 scriptSig
见证数据不计入 TXID 计算
交易大小减少约 25-30%
优势:
字节费用降低
隐私性更好P2WSH(Pay to Witness Script Hash)
P2SH 的 SegWit 版本
scriptPubKey:
OP_0 <scriptHash>
特点:
脚本哈希在见证字段中
支持复杂脚本
用途:
多重签名的 SegWit 实现
时间锁定的 SegWit 实现多重签名(Multisig)
需要多个签名来花费
脚本:
OP_M <pubkey1> <pubkey2> ... <pubkeyn> OP_N OP_CHECKMULTISIG
M-of-N 签名:
需要 M 个签名来自 N 个公钥
例:2-of-3
OP_2 <key1> <key2> <key3> OP_3 OP_CHECKMULTISIG
需要任意 2 个私钥的签名才能花费
scriptSig:
OP_0 <sig1> <sig2>
(OP_0 是修复历史 bug 的占位符)
应用:
共管账户
冷热钱包分离
企业管理脚本执行
执行流程
验证一个交易的步骤:
1. 遍历所有输入
对每个输入:
2. 获取对应的输出脚本
scriptPubKey(锁定脚本)
3. 获取当前输入的脚本
scriptSig(解锁脚本)
4. 执行 scriptSig
操作数被推入栈
5. 执行 scriptPubKey
使用栈中的操作数
6. 检查结果
栈顶必须为 true
脚本才能成功
失败条件:
- 栈为空
- 栈顶不是 true
- 脚本出错
- 操作限制超过操作限制
防止恶意脚本:
1. 脚本大小
最大 10,000 字节
2. 栈大小
最多 1,000 个元素
3. 执行步数
OP_CHECKMULTISIG 的 n 最多 20
4. 签名数据
单个签名最多 72 字节
5. 公钥数据
单个公钥最多 65 字节
设计原因:
防止 DDoS 攻击
保证验证速度脚本验证
签名验证过程
OP_CHECKSIG 的工作流程:
1. 从栈获取公钥和签名
pubkey, sig
2. 提取交易的消息哈希
对当前交易进行 SHA256(SHA256(...))
3. 解析签名
从 DER 编码中提取 r 和 s 值
4. 验证 ECDSA 签名
使用公钥、消息哈希、r 和 s
确认签名有效
5. 返回结果
签名有效:true
签名无效:false脚本有效性
脚本必须满足:
1. 语法正确
所有操作码有效
2. 操作数充足
每个操作码有足够操作数
3. 操作结果正确
数学运算结果正确
4. 栈状态
最终栈顶为 true
例:
OP_1 OP_1 OP_ADD
结果:[2](true)
有效
OP_1 OP_2 OP_EQUAL
结果:[false]
无效(1 ≠ 2)常见问题
Q1: 为什么 Bitcoin 脚本不是图灵完备的?
A:
- 防止无限循环
- 确保脚本总会终止
- 保证网络安全性
- 避免 DDoS 攻击
Q2: 能否在脚本中放任意代码?
A:
- 不能,有严格的操作码限制
- OP_CAT、OP_SUBSTR 等已禁用
- 操作复杂度受限
- 确保确定性验证
Q3: 为什么 multisig 中要有 OP_0?
A:
- 历史原因:OP_CHECKMULTISIG 的 bug
- 消耗一个额外的栈值
- 需要放置 OP_0 作为占位符
- 将来的脚本版本可能修复
Q4: scriptPubKey 和 scriptSig 的关系?
A:
- scriptPubKey:花费条件(锁)
- scriptSig:满足条件的数据(钥匙)
- 两个脚本顺序执行
- 类似:锁 + 钥匙 = 打开
Q5: 为什么某些操作码被禁用?
A:
- OP_CAT:可能导致栈溢出
- OP_SUBSTR:操作复杂
- OP_LSHIFT、OP_RSHIFT:性能问题
- 禁用是为了保护网络
