需要了解的背景知识
首先要了解比特币的两种脚本类型:
P2PKH(pay-to-public key-hash)和P2SH(pay-to-scrip-hash)
这部分可以在 <<精通比特币>>书中找到介绍,同时P2SH这个标准来源于比特币扩展协议BIP16,我之前翻译过这个协议,有兴趣可以看下:
http://blog.csdn.net/pony_maggie/article/details/77577121
另外需要了解下比特币脚本的执行原理,可以参考我以前写的一篇博客:
http://blog.csdn.net/pony_maggie/article/details/73656597
开始分析代码
#输入测试数据
inputs = [{
'output': 'cd6219ea108119dc62fce09698b649efde56eca7ce223a3315e8b431f6280ce7:0',
'value': 158000
}]
#输出测试数据
outputs = [
[{'address': addr0, 'value': 1000}, {'address': addr1, 'value': 2000}]
]
for outs in outputs:
mktx(inputs, outs)
inputs和outputs是测试数据,要用于比特币交易的输入和输出。
直接看mktx函数。函数创建一笔比特币交易的数据结构。用字典表示,序列化后返回。先看下它返回的结果:
0100000001e70c28f631b4e815333a22cea7ec56deef49b69896e0fc62dc198110ea1962cd0000000000ffffffff02e8030000000000001976a914d99f84267d1f90f3e870a5e9d2399918140be61d88acd00700000000000017a9140136d001619faba572df2ef3d193a57ad29122d98700000000
看着不太好理解,因为这是序列化之后的值,序列化之前是字符串形式的字典,可读性好,如下:
{'locktime': 0, 'version': 1, 'outs': [{'value': 1000, 'script': '76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac'}, {'value': 2000, 'script': 'a9140136d001619faba572df2ef3d193a57ad29122d987'}], 'ins': [{'sequence': 4294967295L, 'outpoint': {'index': 0, 'hash': 'cd6219ea108119dc62fce09698b649efde56eca7ce223a3315e8b431f6280ce7'}, 'script': ''}]}
根据比特币的协议, 比特币的交易(tx)结构是这样的,
tx消息描述一笔比特币交易
字段尺寸 | 描述 | 数据类型 | 说明 |
---|---|---|---|
4 | version | uint32_t | 交易数据格式版本 |
1+ | tx_in | var_int | 交易的输入数 |
41+ | tx_in | tx_in[] | 对前一输出的引用 |
1+ | tx_out count | var_int | 交易的输出数 |
8+ | tx_out | tx_out[] | 交易输出或比特币去向列表 |
4 | lock_time | uint32_t | 锁定交易的期限或block数目。如果为0则交易一直被锁定。未锁定的交易不可包含在block中,并可以在过期前修改(目前bitcon不允许更改交易,所以没有用) |
和上面的结果对应下,除了输入数和输出数都可以对应上,两个数目之所以没有是因为可以根据list长度自动计算。
进入mktxs内部剥茧抽丝,来看看究竟是如何组装一笔交易的。
def mktx(*args):
# [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
ins, outs = [], []
for arg in args:
if isinstance(arg, list):
for a in arg:
(ins if is_inp(a) else outs).append(a)
else:
(ins if is_inp(arg) else outs).append(arg)
# 字典表示交易对象,初始化locktime等字段
txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
for i in ins:
if isinstance(i, dict) and "outpoint" in i:
txobj["ins"].append(i)
else:
if isinstance(i, dict) and "output" in i:
i = i["output"]
txobj["ins"].append({
"outpoint": {"hash": i[:64], "index": int(i[65:])},
"script": "",
"sequence": 4294967295
})
for o in outs:
if isinstance(o, string_or_bytes_types):
addr = o[:o.find(':')]
val = int(o[o.find(':')+1:])
o = {}
if re.match('^[0-9a-fA-F]*$', addr):
o["script"] = addr
else:
o["address"] = addr
o["value"] = val
outobj = {}
if "address" in o:
outobj["script"] = address_to_script(o["address"])
elif "script" in o:
outobj["script"] = o["script"]
else:
raise Exception("Could not find 'address' or 'script' in output.")
outobj["value"] = o["value"]
txobj["outs"].append(outobj)
return serialize(txobj);
函数不长,前面提到一个tx里包含多个tx_in和tx_out,函数首先初始化一个tx对象,
txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
然后主要的工作就是组装tx_in和tx_out了。内容来源于我们的测试数据,作为参数传递过来。
tx_in和tx_out数据结构如下:
tx_out的构成:
字段尺寸 | 描述 | 数据类型 | 说明 |
---|---|---|---|
8 | value | uint64_t | 交易的比特币数量(单位是0.00000001) |
1+ | pk_script | var_int | pk_script的长度 |
? | pk_script | uchar[] | Usually contains the public key as a Bitcoin script setting up conditions to claim this output |
tx_in的构成:
字段尺寸 | 描述 | 数据类型 | 说明 |
---|---|---|---|
36 | previous_output | outpoint | 对前一输出的引用 |
1+ | script length | var_int | signature script 的长度 |
? | signature script | uchar[] | 用于确认交易授权的计算脚本 |
4 | sequence | uint32_t | 发送者定义的交易版本,用于在交易被写入block之前更改交易 |
outpoint是对前一输出的引用。
OutPoint结构的构成:
字段尺寸 | 描述 | 数据类型 | 说明 |
---|---|---|---|
32 | hash | char[32] | 引用的交易的散列 |
4 | index | uint32_t | 指定输出的索引,第一笔输出的索引是0,以此类推 |
我打印的txobj['ins']和txobj['outs']如下:
txobj[ins]:[{'sequence': 4294967295L, 'outpoint': {'index': 0, 'hash': 'cd6219ea108119dc62fce09698b649efde56eca7ce223a3315e8b431f6280ce7'}, 'script': ''}]
txobj[outs]:[{'value': 1000, 'script': '76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac'}, {'value': 2000, 'script': 'a9140136d001619faba572df2ef3d193a57ad29122d987'}]
里面有个函数address_to_script,需要说明下。
def address_to_script(addr):
if addr[0] == '3' or addr[0] == '2':
return mk_scripthash_script(addr)
else:
return mk_pubkey_script(addr)
从名字可以看出,该函数的功能时把地址转换为脚本,到底是什么意思呢?我们一步步来分析。
if判断部分,比特币地址中'3'或者'2'开头的是P2SH 地址(Pay-to-Script-Hash), 其它的地址按照P2PKH(Pay-to-Public-Key-Hash)方式处理。
P2PKH是比特币网络中最常用的脚本形式,我们就以它为例进入mk_pubkey_script函数中看下:
def mk_pubkey_script(addr):
# Keep the auxiliary functions around for altcoins' sake
return '76a914' + b58check_to_hex(addr) + '88ac'
就一行实现。b58check_to_hex是base58解码操作。我们知道比特币地址是这样计算的:
A = RIPEMD160(SHA256(K))
A就是比特币地址,但是A并不是我们通常看到的比特币地址,我们平时看到那个是为了增加可读性经过base58编码的。
函数中的b58check_to_hex(addr)其实就是解码转回A。为啥要转成A呢? 接续看,
76 a9 88 ac 是比特币脚本的四个指令对应的16进制编码,分别表示
0x76:OP_DUP(复制栈顶元素)
0xa9:OP_HASH160(栈顶项进行两次HASH,先用SHA-256,
再用RIPEMD-160)0x88:OP_EQUALVERIFY(与OP_EQUAL 一样,如结果为0,之后运行
OP_VERIFY)0xac:OP_CHECKSIG (交易所用的签名必须是哈希值和公钥的
有效签名,如果为真,则返回1)
所以,mk_pubkey_script返回的脚本是:
OP_DUP OP_HASH160 0x14 A OP_EQUALVERIFY OP_CHECKSIG
如果你了解比特币脚本的运行机制,这个看起来就很眼熟了。没错这就是比特币UTXO中的锁定脚本。而根据锁定脚本的标准,A的位置放入的是16进制的比特币地址,也就是base58编码之前的值。
看下实际运行的结果。比如这里的示例,传递的参数是:
1Lqgj1ThNfwLgHMp5qJUerYsuUEm8vHmVG
address_to_script输出:
76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac