理解比特币脚本语言
世界在变化,开发人员并非完美。任何软件的第一次迭代总会存在一些缺陷或不足,需要在一段时间后进行调整。出于这两个原因,拥有(版本)变更管理机制非常重要。在中央服务的 API 的情况下,通常会有一个 API 版本和一个弃用计划。
但在分布式系统中该怎么办呢?如何保持系统的灵活性和安全性呢?
比特币对这个问题有一个优雅的解决方案:比特币脚本!阅读完本文后,你将了解它。让我们开始吧!
新的比特币是通过每添加一个区块到区块链中而挖掘出来的,直到大约在 2140 年。谁可以访问哪些比特币是通过跟踪未花费的交易输出(UTXO)来确定的
访问比特币区块浏览器 ,选择一个区块 ,我们可以看到该区块中有 2,738 笔交易。查看其中一笔交易并显示原始十六进制转储:
010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5403df550a1b4d696e656420627920416e74506f6f6c373138fd004702c13ba070fabe6d6d6d9986fd3aedb9f8b00fab3c74c2e1d399fd0e6bc1d964aa57a08f78ac2d648002000000000000001eb50000c1377c00ffffffff04653c3a2a000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9ede0721e21cc17367eb4fcccf94c7e6ebc54757a693515f809307d4d6a31e0c8500000000000000000266a24b9e11b6d6eab9aaf2ccdc3cb05fb15989c512b84c8ef5f498559cd4d66e6104de8f9cdb100000000000000002b6a2952534b424c4f434b3a93320d7a461a95b953bd18e40aa031b5470d6258ccd82011999a5b290031529c0120000000000000000000000000000000000000000000000000000000000000000000000000000
这种格式的结构如下:
由 Martin Thoma 提供的图片
本文感兴趣的两个字段是“解锁脚本”和“锁定脚本”。当添加新交易时,需要提供一个与引用的 UTXO(TX-ID 和 TX-Index)匹配的解锁脚本。输出锁定脚本是你期望将来他人使用该输出时执行的操作。在非常直接的意义上,你定义了密钥。
这两个脚本字段包含比特币脚本。它以一个字节的操作代码(OP_CODE
)开头。这只是与一个操作相关联的数字。
让我们以这笔交易作为下面示例。首先,我将十六进制交易解码为:
{
"version_number": 1,
"marker": null,
"flag": null,
"tx_ins": [
{
"tx_id": "00f645c7443e367330410e526b152fc799c71dafd4971d8ed6ee37babbd581bb",
"tx_index": 10,
"script": "483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94",
"sequence": 4294967295
}
],
"tx_outs": [
{
"satoshi": 16939,
"script": "a914c29b367fe...bd1b29ef47a687"
},
"... more outs..."
],
"witness": {
"stack_items": [
[]
]
},
"locktime": 0
}
有趣的部分是二进制解锁脚本以十六进制表示给出了第一个(也是唯一的)输入。
你可以使用以下脚本(在 Ubuntu 20.04 上)将二进制比特币脚本解码为 ASM(汇编语言):
# 下载并提取比特币核心
$ wget [https://bitcoin.org/bin/bitcoin-core-0.20.0/bitcoin-0.20.0-x86_64-linux-gnu.tar.gz](https://bitcoin.org/bin/bitcoin-core-0.20.0/bitcoin-0.20.0-x86_64-linux-gnu.tar.gz)
$ tar -xvf bitcoin-0.20.0-x86_64-linux-gnu.tar.gz
$ cd bitcoin-0.20.0/bin/
# 运行守护程序,但不下载区块
$ ./bitcoind -daemon -connect=0.0.0.0
# 最后!解码一个比特币脚本十六进制编码的程序
$ ./bitcoin-cli decodescript "483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94"
{
"asm": "3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001 04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94",
"type": "nonstandard",
"p2sh": "3QEAyVJgyTXivsKQopPiGngaPwFWmzdHLL",
"segwit": {
"asm": "0 64c118d91f54d62936c29915c5cb61d39dc3353b005aca55528a714b607800de",
"hex": "002064c118d91f54d62936c29915c5cb61d39dc3353b005aca55528a714b607800de",
"reqSigs": 1,
"type": "witness_v0_scripthash",
"addresses": [
"bc1qvnq33kgl2ntzjdkzny2utjmp6wwuxdfmqpdv542j3fc5kcrcqr0qxaa2gw"
],
"p2sh-segwit": "3EVPaAaNmZtuP4acv63USoRkSXPez4dX4z"
}
}
这是一个典型的解锁脚本(标记的“asm”
键标记)。第一个字符串是脚本签名。第二个是公钥。
接下来,我们从交易中获取锁定脚本(第 10 个索引):
$ bitcoin-cli decodescript "**76a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac**"
{
**"asm": "OP_DUP OP_HASH160 7ddb236e7877d5040e2a59e4be544c65934e573a OP_EQUALVERIFY OP_CHECKSIG"**,
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"1CUTyyxgbKvtCdoYmceQJCZLXCde5akiX2"
],
"p2sh": "3Jp8er9Srb9LMkJqk8jPeWafjDztYGLyLn",
"segwit": {
"asm": "0 7ddb236e7877d5040e2a59e4be544c65934e573a",
"hex": "00147ddb236e7877d5040e2a59e4be544c65934e573a",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bc1q0hdjxmncwl2sgr32t8jtu4zvvkf5u4e64ucrwj"
],
"p2sh-segwit": "37e78P9jyHEZ7dp2as7w817BUPgEEUQDD2"
}
}
asm
对应的值是一个非常典型的锁定脚本。
为了执行两者,我们首先放置解锁脚本,然后是锁定脚本:
3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001
04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94
OP_DUP
OP_HASH160
7ddb236e7877d5040e2a59e4be544c65934e573a
OP_EQUALVERIFY
OP_CHECKSIG
这是像每个程序一样从上到下执行的。比特币脚本是一种基于堆栈的编程语言。这意味着你没有变量,而是将所有内容放在一个堆栈上。
你肯定想要执行这个。有一个在线工具 ,但似乎有错误 。相反,我建议使用 btcdeb。安装后,你可以运行:
$ btcc 3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001 04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94 OP_DUP OP_HASH160 7ddb236e7877d5040e2a59e4be544c65934e573a OP_EQUALVERIFY OP_CHECKSIG
483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e9476a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac
$ btcdeb 483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e9476a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac
btcdeb 0.4.21 -- type `btcdeb -h` for start up options
LOG: sign segwit taproot
notice: btcdeb has gotten quieter; use --verbose if necessary (this message is temporary)
7 op script loaded. type `help` for usage information
script | stack
---------------------+--------
3045022100b7393ff... |
04c4b7a7f7bb2c899... |
OP_DUP |
OP_HASH160 |
7ddb236e7877d5040... |
OP_EQUALVERIFY |
OP_CHECKSIG |
btcdeb> step
3045022...
行表示你将此值放入堆栈。04c4b...
行放在其上。然后执行 OP_DUP
。它需要一个输入:04c4b...
。这个被复制。所以 04c4b...
被放在堆栈上两次。现在我们有:
script | stack
-------------------+-------------
OP_HASH160 | 04c4b...
7ddb23... | 04c4b...
OP_EQUALVERIFY | 3045022...
OP_CHECKSIG |
OP_HASH160
接受一个参数(04c4b...
)并对其应用哈希函数。然后你有:
script | stack
-------------------+------------
7ddb23... | 7ddb23...
OP_EQUALVERIFY | 04c4b...
OP_CHECKSIG | 3045022...
接下来,我们额外将 7ddb23...
放在堆栈上。OP_EQUALVERIFY
获取堆栈上的两个 7ddb23...
。因为它们相同,它会评估为 True
并继续执行。否则,它会中断并返回 False
。最后,OP_CHECKSIG
比较剩下的两个值:公钥和脚本签名。如果它们匹配,那么交易就被解锁了!
由于你可以在比特币脚本中放入一点数据,因此你可以在区块链上放置消息。你只需要确保脚本仍然评估为 True
。一个有趣的例子是第一笔比特币交易 。它包含输入脚本:
04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73
现在我们需要做两个小修改:
$ ./bitcoin-cli decodescript "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
{
"asm": "486604799 4 5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73",
"type": "nonstandard",
"p2sh": "3FimpcNzLCfeJB3zxDAExME4w6BLYryx4z",
"segwit": {
"asm": "0 c02688beb2fb8f93780d095ec5ce1a0213fafe72422744f618ab9286e3026020",
"hex": "0020c02688beb2fb8f93780d095ec5ce1a0213fafe72422744f618ab9286e3026020",
"reqSigs": 1,
"type": "witness_v0_scripthash",
"addresses": [
"bc1qcqng304jlw8ex7qdp90vtns6qgfl4lnjggn5fasc4wfgdcczvqsqzq3rpk"
],
"p2sh-segwit": "3Nh7AqMGnHaY2x5Lts4BhV3hrU8ANceMmT"
}
}
$ python
>>> from binascii import unhexlify
>>> unhexlify("5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73")
b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
顺便说一句,还有其他隐藏消息的方法(例如看看这些公钥 (“WeRe fine, 8chaN poSt Fake”)。如果你查看此交易脚本 ,你将在输出索引为 2 的地方找到:“Do not be overcome by evil, but overcome evil with good — Romans 12:21”
比特币脚本是一种用于锁定和解锁交易的基于堆栈的编程语言。这是一种聪明的方法来改变管理交易,因为它允许修改核心安全功能:交易的“批准过程”。它是一种非常简单的基于堆栈的语言,可以防止脚本本身引入安全问题。
衷心感谢我的朋友 Rene Pickhardt 帮助我理解这个话题:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!