天天看點

pybitcointools源碼分析之比特币交易資料結構

需要了解的背景知識

首先要了解比特币的兩種腳本類型:

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': 
   }]

#輸出測試資料
outputs = [
            [{'address': addr0, 'value': }, {'address': addr1, 'value': }]
]

for outs in outputs:
    mktx(inputs, outs)
           

inputs和outputs是測試資料,要用于比特币交易的輸入和輸出。

直接看mktx函數。函數建立一筆比特币交易的資料結構。用字典表示,序列化後傳回。先看下它傳回的結果:

e70c28f631b4e815333a22cea7ec56deef49b69896e0fc62dc198110ea1962cd0000000000ffffffff02e8030000000000001976a914d99f84267d1f90f3e870a5e9d2399918140be61d88acd00700000000000017a9140136d001619faba572df2ef3d193a57ad29122d98700000000
           

看着不太好了解,因為這是序列化之後的值,序列化之前是字元串形式的字典,可讀性好,如下:

{'locktime': , 'version': , 'outs': [{'value': , 'script': '76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac'}, {'value': , 'script': 'a9140136d001619faba572df2ef3d193a57ad29122d987'}], 'ins': [{'sequence': L, 'outpoint': {'index': , '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": , "version": , "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[:], "index": int(i[:])},
                "script": "",
                "sequence": 
            })


    for o in outs:
        if isinstance(o, string_or_bytes_types):
            addr = o[:o.find(':')]
            val = int(o[o.find(':')+:])
            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對象,

然後主要的工作就是組裝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': L, 'outpoint': {'index': , 'hash': 'cd6219ea108119dc62fce09698b649efde56eca7ce223a3315e8b431f6280ce7'}, 'script': ''}]


txobj[outs]:[{'value': , 'script': '76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac'}, {'value': , 'script': 'a9140136d001619faba572df2ef3d193a57ad29122d987'}]
           

裡面有個函數address_to_script,需要說明下。

def address_to_script(addr):
    if addr[] == '3' or addr[] == '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就是比特币位址,但是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 x14 A OP_EQUALVERIFY  OP_CHECKSIG
           

如果你了解比特币腳本的運作機制,這個看起來就很眼熟了。沒錯這就是比特币UTXO中的鎖定腳本。而根據鎖定腳本的标準,A的位置放入的是16進制的比特币位址,也就是base58編碼之前的值。

看下實際運作的結果。比如這裡的示例,傳遞的參數是:

qgj1ThNfwLgHMp5qJUerYsuUEm8vHmVG
           

address_to_script輸出:

76a914d99f84267d1f90f3e870a5e9d2399918140be61d88ac
           

繼續閱讀