三、脚本原理
比特币系统的脚本系统设计得非常简洁,稍有编程基础的人很容易理解。与用堆栈(stack)实现一个简单的计算器的方式非常类似。 比特币的脚本系统借助堆栈进行运算。验证交易时,脚本系统依次读取每个指令,并对堆栈进行操作。所有指令结束后,检查堆栈中的残留数据,如果均为TRUE,则交易有效,否则交易无效。
堆栈(stack)是一种实现后进先出(LIFO, Last In First Out)方式的数据结构,使用两种基本操作:压入(push)和弹出(pop),具体实现时通常还会有另一个常用操作:检视(peek)。最上面的元素称为栈顶(top)。 打个比方:往一个有一定高度的箱子里放书(箱子的宽度只能容纳一本书),放入书的动作是push,取出书的动作是pop。最后放进的书一定是最先被取出的。拿起最上面的一本检视一下再放回原处称为peek。
脚本系统规定,指令(或运算符)均为1个字节,也就是说,最多只能有256种指令。 协议规定了执行每一个指令(OP)时要对堆栈(S)进行什么操作。比如: 假设S中依次push了如下下数据:(a, b, c, d, e) OP_ADD:加法指令。从S中依次pop出两个数据,这两个数据分别为e, d。计算f = e + d. 然后将f 压入S。此时S中的元素为(a, b, c, f) OP_DUP:复制栈顶数据。从S中peek出一个数据,此时数据为f, 复制一份f并压入堆栈。此时S中的元素为(a,b,c,f,f) OP_PUSHDATA1: 压入数据。将紧随OP指令后面的1个字节的数据(比如g)压入堆栈。此时S中的元素为(a,b,c,f,f, g)。 脚本系统协议中的每种指令名称及其用法请参考:
https://en.bitcoin.it/wiki/Script 附件中有一个github的链接,附有一个比特币脚本解释器的示例(C语言),没经过严格测试,仅供参考。 下面用一个P2PKH的交易来说明一下脚本系统是如何运作的,数据取自第一节中所引用的交易。 21
输入脚本(签名脚本):48304502203fe5f04a013512a4773414b25edc8c7915473dd5cf87bc73d28e1aaffdb4d14f02210
0e16156d526d1498f2cf5eb02d53e02f7fd5cf1dfdd25e4b032fdc5c59c9fd27b01210203635e5c
184951e14fcfecc83b15960594f4fceec729e09a4a517b0a03a7f4b9
每个数字的含义如下:
从区块链上找到该UTXO所对应的输出脚本(赎回脚本)为: (注意:不是这一笔交易中的输出脚本)
OP_DUP OP_HASH160 44524fa542897f46a9a0cccc27ccb91ba822b4b6 OP_EQUALVERIFY OP_CHECKSIG
76 a9 14 44524fa542897f46a9a0cccc27ccb91ba822b4b6 88 ac
注:指令中,凡是小于0x4b的数字均为PUSHDATA指令,这个数字同时代表打算压入堆栈的字节数。
验证一笔交易时,首先要读取输入脚本中的内容。
题外话:在早期脚本协议的约定中,输入脚本中只允许OP_PUSHDATA指令,这给后期推行P2SH方式制造了很大的麻烦,2012年的时候不得不冒着硬分叉的风险,在取得多数共识后强行引入了新协议,这样我们才有机会使用多重签名或智能合约这种模式。
输入脚本(签名脚本):script = 48 30 45 02 20 3f e5 f0 4a 01 35 12 a4 77 34 14 b2 5e dc 8c 79 15 47 3d d5 cf 87 bc 73 d2 8e 1a af fd b4 d1 4f 02 21 00 e1 61 56 d5 26 d1 49 8f 2c f5 eb 02 d5 3e 02 f7 fd 5c f1 df dd 25 e4 b0 32 fd c5 c5 9c 9f d2 7b 01 21 02 03 63 5e 5c 18 49 51 e1 4f cf ec c8 3b 15 96 05 94 f4 fc ee c7 29 e0 9a 4a 51 7b 0a 03 a7 f4 b9
流程:
令p = script;
op = *p; (op = 0×48),这是PUSHDATA指令,p++, 将后面的72个字节数据压入堆栈S, p +=72.
op = *p; 此时,op = 0×21,这也是PUSHDATA指令,p++, 将后面的33个字节数据压入堆栈S, p +=33.
此时p已移动至脚本末尾,输入脚本读取完毕,
接下来读取输出脚本(赎回脚本): script = 76 a9 14 72 32 ca 33 e0 79 74 05 a5 12 fa 87 29 34 cd 92 2c 81 29 65 88 ac
流程:
令p = script;
op = *p; (op = 0×76),这是OP_DUP指令,复制一份栈顶元素至堆栈S。p++;
op = *p; (op = 0xa9),这也是OP_HASH160指令,从S中pop出一个数据,进行hash160运算,将运算结果压回S。p++;
op = *p; (op = 0×14),这是PUSHDATA指令,p++, 将后面的20个字节数据压入堆栈S, p +=20;
op = *p; (op = 0×88),这也是OP_EQUALVERIFY指令,从S中依次pop出两个数据数据,比较这两个数据,如果不同,在交易验证失败。否则,p++;
op = *p; (op = 0xac),这也是OP_CHECKSIG指令,从S中依次pop出两个数据数据,根据[sig]数据末尾附加的类型(0×01)对原始交易数据进行哈系运算,M=hash256(tx_raw),检查ECDSA_checksig(M, [sig], [pubkey])的返回值,若结果为1,则将OP_TRUE压入堆栈,否则压入OP_FALSE.
此时p已移动至脚本末尾,全部脚本解析完毕。
检查堆栈中的元素,若为TRUE,则交易有效,否则交易无效。
(接上表)
交易验证成功,