比特币脚本迎来重要升级,Pieter Wuille正式公布Miniscript项目
比特币脚本的功能虽然有限,但它的重要性不言而喻,而其在今日迎来了重要升级,Bitcoin core协议维护者Pieter Wuille正式公布了 Miniscript 技术项目。
Pieter Wuille介绍道:
“简而言之,它是一种以结构化、可组合的方式编写(某些)比特币脚本的方法,其允许各种静态分析、通用签名和策略编写。以下内容,是Miniscript项目技术文档的译文(部分):想象一下,一家公司想用2-of-3多重签名策略来保护他们的冷存储资金。问题是,其中一位高管有一个自己的2FA双因子验证/多重签名/timelock设置。为什么整个设置不能成为多重签名“参与者”之一?
当前很多工作都集中在对区块链本身功能的扩展上,以支持更复杂的应用,但我觉得我们忘记了,以可访问、可组合、可分析的方式使用这些功能,在今天基本上是不可能的。
我希望Miniscript和PSBT这样的东西,可以减少软件之间的一些障碍。理想情况下,执行人员的2FA设置可以完美地与冷存储设置交互,计算必要的组合脚本,并且仍然能够签名。
该项目包括一个策略编译器,你可以知道在什么条件下输出应该是可使用的,及其相对概率是什么,它将为其找到最经济的Miniscript兼容脚本(仅限于一些转换)。
为了这个项目,我们已讨论了很长一段时间(包括今年早些时候在斯坦福区块链活动上)。直至我们对比特币共识及标准化规则进行了大量测试,我才对发布内容感到满意,而当下便是公布之时。
而从开始至今,我们重写整个设计近3次,Andrew Poelstra和sanket1729在持续讨论额外的可能性、问题及分析技术。
正如大家所知的,我对命名这件事并不擅长,所以我需要指出,这个项目与 Minisketch (https://github.com/sipa/minisketch)完全无关,Minisketch库是Greg Maxwell、tomatodread和我一起编写的,其目的是支持高效的基于集调节的交易中继(Erlay)。
它也和我们在 Taproot 上的研究工作无关。当然,对Miniscript的研究确实教会了我们一些关于脚本的知识,这些知识可以为将来对脚本的改进提供设计依据,并且Miniscript可以根据需要进行扩展。
如果需要的话,我会在Bitcoin Core中加入这部分内容(我相信这可能非常有用),但理想情况下,它会被包含在很多钱包技术中。Andrew Poelstra和sanket1729一直在为它开发一个Rust库:
https://github.com/apoelstra/rust-miniscript/
”
Miniscript介绍
Miniscript是一种以结构化方式编写比特币脚本子集,并支持分析、合成、通用签名等功能的语言。
比特币脚本是一种独特的基于栈的语言,其具有很多边界用例,旨在实现由签名、哈希锁(hash lock)和时间锁(time lock)的各种组合组成的支出条件。尽管比特币脚本的功能有限,但其仍然是非常重要的:
- 考虑一个支出条件的组合,找到最经济的脚本来实现它;
- 给定两个脚本,构建一个实现其支出条件组合的脚本(例如,一个多重签名,其中的一个“key”是另一个多重签名);
- 给定一个脚本,找出其允许的支出条件;
- 给定一个脚本并访问足够的私钥集,为它构造一个通用的令人满意的witness(验证内容);
- 给定一个脚本,能够预测花费一个输出的成本;
- 给定一个脚本,了解特定的资源限制(如操作限制)在花费时是否会受到影响;
目前,Miniscript实际上只为P2WSH和P2SH-P2WSH嵌入式脚本设计。它的大多数构造在p2sh中也可以正常工作,但一些(可选)安全属性依赖于隔离见证(Segwit)特定规则。此外,已实现的策略编译器假定了一个隔离见证(Segwit)特定成本模型。
Miniscript由Blockstream Research的Pieter Wuille、Andrew Poelstra和Sanket Kanjalkar设计和实现,但也有其他人参与讨论。
链接:
1、C++编译器:https://github.com/sipa/miniscript 2、Bitcoin Core兼容C++实现:https://github.com/sipa/miniscript/tree/master/bitcoin/script 3、Rust-miniscript实现:https://github.com/apoelstra/rust-miniscript 4、在斯坦福区块链活动上谈论的Miniscript内容:http://diyhpl.us/wiki/transcripts/stanford-blockchain-conference/2019/miniscript/ ( 中文版 )
MiniScript编译器策略
在这里,你可以看到Miniscript编译器的演示。根据下面的说明编写支出策略,并观察它如何影响构造的Miniscript。
策略
and(pk(A),or(pk(B),or(9@pk(C),older(1000))))
支持策略:
- pk(NAME): 需要名为NAME的公钥进行签名,名称可以是最多16个字符的任何字符串;
- after(NUM), older(NUM): 要求nlocktime/nsequence值至少为NUM,NUM不能为0;
- sha256(HEX), hash256(HEX): 要求显示64个字符HEX的预映射(preimage),特殊值H可用作HEX;
- ripemd160(HEX), hash160(HEX): 要求显示40个字符HEX的预映射(preimage),特殊值H可用作HEX;
- and(POL,POL): 要求满足这两个子策略;
- or([N@]POL,[N@]POL): 要求满足其中一个子策略。数字N表示每个子表达式的相对概率(因此9@,要比默认值高9倍);
- thresh(NUM,POL,POL,...): 要求满足以下子策略中的NUM(假设所有组合的可能性都相同);
Miniscript输出 :
and_v(or_c(c:pk(B),or_c(c:pk(C),v:older(1000))),c:pk(A))
支出成本分析
- 脚本(Script): 114 WU
- 输入(Input)Input:142.900000 WU
- 总计:256.900000 WU
OP_CHECKSIG OP_NOTIF
OP_CHECKSIG OP_NOTIF
OP_CHECKSEQUENCEVERIFY OP_VERIFY
OP_ENDIF
OP_ENDIF
OP_CHECKSIG
分析Miniscript
在这里,你可以分析Miniscript表达式的结构等等。
Miniscript
and_v(or_c(c:pk(B),or_c(c:pk(C),v:older(1000))),c:pk(A))
提供类型为“B”的良好miniscript表达式。
分析:
大小:114字节脚本
生成的脚本结构
OP_CHECKSIG OP_NOTIF
OP_CHECKSIG OP_NOTIF
OP_CHECKSIG OP_NOTIF
OP_CHECKSEQUENCEVERIFY OP_VERIFY
OP_ENDIF
OP_ENDIF
OP_CHECKSIG
Miniscript reference
翻译表
此表显示了所有Miniscript片段及其相关语义和比特币脚本。不改变其子表达式语义的片段称为wrapper(包装类)。普通片段使用“fragment(arguments,…)”表示法,而wrapper(包装类)使用前缀编写,前缀由冒号与其他片段分开。冒号将在后续wrapper(包装类)之间删除;例如vc:pk(key)是应用于给定密钥的pk片段的c:包装类的v:包装类;含义 | Miniscript 片段 | 比特币脚本 |
---|---|---|
false |
0
|
0 |
true |
1
|
1 |
check(key) |
pk(key)
|
<key> |
pk_h(key)
|
DUP HASH160 <HASH160(key)> EQUALVERFIFY | |
nSequence ≥ n (and compatible) |
older(n)
|
<n> CHECKSEQUENCEVERIFY |
nLockTime ≥ n (and compatilbe) |
after(n)
|
<n> CHECKLOCKTIMEVERIFY |
len(x) = 32 and SHA256(x) = h |
sha256(h)
|
SIZE <32> EQUALVERIFY SHA256 <h> EQUAL |
len(x) = 32 and HASH256(x) = h |
hash256(h)
|
SIZE <32> EQUALVERIFY HASH256 <h> EQUAL |
len(x) = 32 and RIPEMD160(x) = h |
ripemd160(h)
|
SIZE <32> EQUALVERIFY RIPEMD160 <h> EQUAL |
len(x) = 32 and HASH160(x) = h |
hash160(h)
|
SIZE <32> EQUALVERIFY HASH160 <h> EQUAL |
( X and Y ) or Z |
andor(
X
,
Y
,
Z
)
|
[X] NOTIF [Z] ELSE [Y] ENDIF |
X and Y |
and_v(
X
,
Y
)
|
[X] [Y] |
and_b(
X
,
Y
)
|
[X] [Y] BOOLAND | |
and_n(
X
,
Y
)
=
andor(
X
,
Y
,0)
|
[X] NOTIF 0 ELSE [Y] ENDIF | |
X or Z |
or_b(
X
,
Z
)
|
[X] [Z] BOOLOR |
or_c(
X
,
Z
)
|
[X] NOTIF [Z] ENDIF | |
or_d(
X
,
Z
)
|
[X] IFDUP NOTIF [Z] ENDIF | |
or_i(
X
,
Z
)
|
IF [X] ELSE [Z] ENDIF | |
X1 + ... + Xn = k |
thresh(k,
X1
,...,
Xn
)
|
[X1] [X2] ADD ... [Xn] ADD ... <k> EQUAL |
check(key1) + ... + check(keyn) = k |
thresh_m(k,key1,...,keyn)
|
<k> <key1> ... <keyn> <n> CHECKMULTISIG |
X (identities) |
a:
X
|
TOALTSTACK [X] FROMALTSTACK |
s:
X
|
SWAP [X] | |
c:
X
|
[X] CHECKSIG | |
t:
X
=
and_v(
X
,1)
|
[X] 1 | |
d:
X
|
DUP IF [X] ENDIF | |
v:
X
|
[X]
VERIFY
(or
VERIFY version of last opcode in [X] ) |
|
j:
X
|
SIZE 0NOTEQUAL IF [X] ENDIF | |
n:
X
|
[X] 0NOTEQUAL | |
l:
X
=
or_i(0,
X
)
|
IF 0 ELSE [X] ENDIF | |
u:
X
=
or_i(
X
,0)
|
IF [X] ELSE 0 ENDIF |
and_n
片段和
t:
、
l:
以及
u:
包装类(wrapper)对于其他Miniscript而言是语法糖,如上表所示。在下面的内容中,它们将不再被包括在内,因为它们的属性可通过查看它们的扩展来派生。
正确性属性
并非每个Miniscript表达式都可以彼此组合。有些通过在栈中放入“true”或“false”值来返回结果,另一些只能中止或继续。有些需要使用完全已知数量的参数的子表达式,而另一些则需要具有非零顶部栈元素的子表达式来满足。为了对所有这些属性进行建模,我们为Miniscript定义了一个正确性类型系统。每个ministcript表达式都有四种基本类型之一:
- " B " 基本表达式:这些表达式从栈顶获取输入。当满足时,它们将最多4字节的非零值推送到栈上。当不满足时,它们将一个精确的0推送到栈上(如果不满足而不中止是完全可能的)。此类型用于大多数表达式,并且是顶级表达式所必需的。示例是 older(n) = CHECKSEQUENCEVERIFY;
-
"
V
" 验证表达式:像"B"表达式一样,它们从栈顶获取输入。然而一旦满足,它们就可继续工作而无需推送任何东西。它们在不中止的情况下,是无法不满足的。一个"V"表达式可使用"B" 表达式上的v: wrapper获得,或者通过使用
and_v
,or_i
,or_c
,或andor
组合其他"V" 表达式获得。例子有vc:pk(key) = CHECKSIGVERIFY; - " K " Key表达式:它们也同样从栈顶获取输入,但不会直接验证条件,而是将公钥推送到栈上,对于该栈,仍需要签名来满足表达式。可使用c:wrapper(CHECKSIG)将 "K"表达式转换为"B"表达式。例如pk_h(key) = DUP HASH160 <Hash160(key)> EQUALVERIFY;
- " W "包装表达式:它们从栈顶获取输入,并将非零(满足时)或零(不满足时)推送到栈顶部或one below。例如,一个3输入"W"表达式会把栈"A B C D E F"转换成"A B F 0" 或"A B 0 F"(如果不满足),而如果满足,则转换成 "A B F n"或"A B n F"(n为非零值)。每个"W"表达式要么是s:B (SWAP B)要么是a:B (TOALTSTACK B FROMALTSTACK)。例子有sc:pk(key) = SWAP CHECKSIG;
- “z”zero arg:此表达式始终只消耗0个栈元素;
- "o" One-arg: 此表达式始终只消耗一个栈元素;
- "n"非零:此表达式始终使用至少一个栈元素,此表达式的dissatisfaction,要求顶部输入栈元素为零;
- "d" 不满足:此表达式的不满足可无条件构造。这意味着dissatisfaction不能包括任何签名或哈希预映射preimage,也不能依赖于满足的时间锁;
- "u"单位:当满足时,此表达式将在栈上精确放置一个1(而不是任何非零值);
资源限制
不同类型的比特币脚本有不同的资源限制,无论是通过共识还是标准。其中一些会影响其他有效的Miniscripts:- 超过10000字节的脚本因共识而无效(bare, P2SH, P2WSH, P2SH-P2WSH);
- 超过520字节的脚本因共识无效(P2SH);
-
脚本满足操作,其中非push操作码总数加上参与所有已执行
thresh_ms
的key数大于201,因共识而无效(bare, P2SH, P2WSH, P2SH-P2WSH); - 除c:pk(key)(P2PK)、c:pk_h(key)(P2PKH)和thresh_m(k,...)之外n=3的任何内容在标准性无效(bare);
- 超过3600字节的脚本因标准无效(P2WSH, P2SH-P2WSH);
- 序列化脚本签名(scriptSig)超过1650字节的脚本,因标准无效(P2SH);
- 由100多个栈元素组成的witness脚本,因标准无效(P2WSH, P2SH-P2WSH);
or_b(X,Y)
,其中x和y都需要执行大量的thresh_ms来satisfaction。在这种情况下,满足x或y中的一个不会超过操作限制,而满足两者则都会超过。因为这两者都不需要满足,所以限制并不能阻止satisfaction。
安全属性
上面的类型系统保证对应的比特币脚本是:- 共识和标准性完成:假设不违反上一节中列出的资源限制,对于语义允许的每一组满足条件,可构建一个通过比特币共识规则和通用标准性规则的witness;
- 共识健全:除非满足支出条件,否则无法构建对脚本有效的共识witness。由于标准性规则只允许共识有效满足的一个子集,因此此属性还意味着标准的健全性。
为了使这些属性不仅适用于脚本,而且适用于整个交易,witness必须提交与验证相关的所有数据。实际上,这意味着不需要任何数字签名就可满足其条件的脚本是不安全的。例如,如果一个输出可通过简单地传递某个
nLockTime
(Miniscript中的
after(n)
片段)来使用,但没有任何数字签名,攻击者就可以修改支出交易中的
nLockTime
字段。
更多内容,读者可访问:http://bitcoin.sipa.be/miniscript/