原文链接:https://davidederosa.com/basic-blockchain-programming/bitcoin-script-language-part-one/ 译者:AI 翻译官[1],校对:翻译小组[2] 本文永久链接:learnblockchain.cn/article…[3]
在 GitHub 上查看 Demo 代码"交易处理中的脚本"[4]
Script 是一种简单的脚本语言,也是比特币交易处理的核心。如果你曾经写过汇编代码,你会发现这篇文章非常容易理解——可能还很有趣——否则它可能是最具挑战性的之一。保持专注!
比特币脚本是一个计算机程序,作为程序员的你肯定知道程序是什么。程序接收输入,执行一段时间,然后返回输出。编程语言是我们编写计算机能理解的程序的工具,因为大多数语言都带有将人类友好的代码映射到 CPU 操作(也称为 _操作码_)的 _编译器_。
操作码包括内存操作、数学运算、循环、函数调用以及你在像 C 这样的过程式编程语言中找到的一切。它们构成了 CPU 的口语,即所谓的 _机器代码_。由于字节是计算机的首选语言,难怪操作码也是字节。因此,机器代码是一串表示将在 CPU 上执行的操作的字节。
考虑以下用高级编程语言如 C 编写的代码:
x = 0x23;
x += 0x4b;
x *= 0x1e;
现在假设你想在一个假想的小端 CPU 上编译并运行这段代码,该 CPU 具有一个 16 位内存单元(一个 _寄存器_)和以下操作码集:
操作码 | 编码 | V |
---|---|---|
SET(V) | ab V | 16-bit |
ADD(V) | ac V | 16-bit |
MUL(V) | ad V | 16-bit |
操作码解释:
这种 CPU 的编译器会生成以下 9 个字节的机器代码:
ab 23 00 ac 4b 00 ad 1e 00
它的解释如下:
23
。4b
加到寄存器中,现在是 23 + 4b = 6e
。1e
,得到 6e * 1e = ce4
。寄存器保存最终结果,即 ce4
。
大多数时候,我们需要用 变量 来跟踪复杂的程序状态。在 C 中,取决于变量是静态分配还是用 malloc
分配,它们存储在不同的内存布局中。虽然 malloc
分配的数据像一个非常大的数组中的元素一样访问,静态变量则被推入和弹出一个称为 栈 的项目堆。栈以 LIFO 方式(后进先出)操作,这意味着你推入的最后一个项目将是第一个弹出的。
考虑这个简单的函数:
int foo() {
/* 1 */
/* 2 */
uint8_t a = 0x12;
uint16_t b = 0xa4;
uint32_t c = 0x2a5e7;
/* 3 */
uint32_t d = a + b + c;
return d;
/* 4 */
}
栈最初是空的 (1):
然后,三个变量被推入 (2):
[12]
[12, a4 00]
[12, a4 00, e7 a5 02 00]
第四个变量被分配为其他变量的和并推入栈 (3):
[12, a4 00, e7 a5 02 00, 9d a6 02 00]
栈顶是返回值,并通过其他方式返回给函数调用者。每个临时栈变量在块结束时被弹出 (4),因为推入 / 弹出操作必须平衡,以便栈始终恢复到其初始状态:
[12, a4 00, e7 a5 02 00]
[12, a4 00]
[12]
[]
同样,比特币核心有自己的“虚拟处理器”来解释 Script 机器代码。Script 具有丰富的操作码集,但与像英特尔这样的完整 CPU 相比非常有限。关于 Script 的一些关键事实:
事实上,第一点意味着第二点。第三点意味着在 Script 中没有命名变量,你只是在栈上进行计算。通常,你推入的栈项目成为后续操作码的操作数。在脚本结束时,栈顶项目是返回值。
在介绍实际的脚本之前,让我们先列举一些操作码。完整的操作码集请查看比特币官方 wiki 页面[5]。
以下操作码将数字 0-16 推入栈:
操作码 | 编码 |
---|---|
OP_0 | 00 |
OP_1 -OP_16 | 51 -60 |
按照惯例,OP_0
和 OP_1
也表示布尔值 OP_FALSE
(零)和 OP_TRUE
(非零)。
示例:
或:
栈的演变如下:
[]
[4]
[4, 7]
[4, 7, 0]
[4, 7, 0, 16]
返回值是栈顶项目,所以脚本返回 16。相当无意义,我知道,但这是一个开始。
提供了几个操作码来推送自定义数据。它们在操作数的大小上有所不同:
操作码 | 编码 | L(长度) | D(数据) |
---|---|---|---|
OP_PUSHDATA1 | 4c L D | 8-bit | L 字节 |
OP_PUSHDATA2 | 4d L D | 16-bit | L 字节 |
OP_PUSHDATA4 | 4e L D | 32-bit | L 字节 |
例如,如果你的数据长度可以存储为 8 位数字,那么 OP_PUSHDATA1
是你的最佳选择。看看这个:
4c 14 11 06 03 55 04 8a
0c 70 3e 63 2e 31 26 30
24 06 6c 95 20 30
第一个字节显然是 OP_PUSHDATA1
操作码,后跟一个 1 字节长度的 14
,即十进制 20。所以,接下来是 20 字节的数据。此指令的效果是将这些数据推入栈:
[11 06 03 55 04 8a 0c 70
3e 63 2e 31 26 30 24 06
6c 95 20 30]
确实——就像 varints[6] 一样——,对于非常短的数据有一种特殊的编码。如果操作码在 01
和 4b
(包括)之间,它是一个推送数据操作,其中操作码本身就是字节长度:
操作码 | 编码 | L(长度) | D(数据) |
---|---|---|---|
L | L D | 01 -4b | L 字节 |
例如,在字符串中:
操作码 07
表示要推送 7 字节的数据:
你学到了一些关于机器代码和操作码的知识。Script 是一种简单的低级语言,由矿工软件理解。Script 状态通过栈内存进行跟踪。
在下一篇文章[7]中,我将向你展示一些不仅仅是推送数据的操作码。如果你喜欢这篇文章,请分享。
我是 AI 翻译官[8],为大家转译优秀英文文章,如有翻译不通的地方,在这里[9]修改,还请包涵~
AI 翻译官: https://learnblockchain.cn/people/19584
[2]翻译小组: https://learnblockchain.cn/people/412
[3]learnblockchain.cn/article…: https://learnblockchain.cn/article/8520
[4]交易处理中的脚本: https://github.com/keeshux/basic-blockchain-programming
[5]wiki 页面: https://en.bitcoin.it/wiki/Script
[6]varints: https://davidederosa.com/basic-blockchain-programming/serialization-part-two/
[7]下一篇文章: https://learnblockchain.cn/article/8521
[8]AI 翻译官: https://learnblockchain.cn/people/19584
[9]这里: https://github.com/lbc-team/Pioneer/blob/master/translations/8520.md
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。