比特币族系的交易构造

比特币及相关币种使用的学习笔记(二)

比特币交易


交易

完成一次比特币交易分三步,首先我们要构造一个交易,这涉及到给交易签名;然后将它广播到比特币网络; 最终我们要等待某个矿工将它打包。它才算成为了被确认的交易,变成了区块链记录的一部分,接下来如果 一切顺利,一个个后继区块基于它被计算出来后,它也就越来越可靠的成为可信记录的一部分。一般来说, 我们在主网络(Main Chain)上采用的信任策略不会像 regtest 环境这么保守,6 次确认(即有6个以 上区块的后继链被挖出),我们就可以相信它是非常可靠的了。

为了构造交易,我们先从用户地址开始。

#  为了省力气,先定一个 cli 封装

def cli(*args):
    cmd = ["bitcoin-cli", "-regtest", "-rpcport=8432", "-rpcuser=bitcoin", "-rpcpassword=bitcoin"]
    cmd += list(args)
    process = command(*cmd)
    process.wait()
    return process.stdout.read().strip()


addr = cli("getnewaddress")
print(addr)

……

简单交易

现在我们向构造出来的这个新地址发送一笔钱。通过最简操作对比特币交易有个初步了解。

txid = cli("sendtoaddress", addr, "10.00")
print(txid)

这步操作得到了一个交易 hash 。 这个 sendtoaddress 操作封装了很多细节。它从 UTXO 中选择 足够多的金额,生成交易,其中扣除交易费和 output 后,还设置了找零。


tx = cli("getrawtransaction", txid, "1")
print(tx)



{
  "hex": "0200000001abc593e33f10678f302af06a12871c32a5252c115de0f3ee61505be88aaf3d270000000049483045022100f4984d39a301a3bcd394e46b7fa135be076987a6490ff430a96ede6d0968dfa80220287b93d4d0cb43fd8c977ace3d00d35e9563f5ebc9e2551dc0e4945ca4586ad901feffffff0200ca9a3b000000001976a914646731518e9bcc7b28f8a46f4e484b735d56575b88ac00196bee000000001976a914ba750b1ea99c17395c97e933d9329a407eeb985588ac81000000",
  "txid": "ff9b7b3e511e4cbcaf2c264896b128211564a6ee033bed983955afbefd3b59c9",
  "hash": "ff9b7b3e511e4cbcaf2c264896b128211564a6ee033bed983955afbefd3b59c9",
  "size": 192,
  "vsize": 192,
  "version": 2,
  "locktime": 129,
  "vin": [
    {
      "txid": "273daf8ae85b5061eef3e05d112c25a5321c87126af02a308f67103fe393c5ab",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100f4984d39a301a3bcd394e46b7fa135be076987a6490ff430a96ede6d0968dfa80220287b93d4d0cb43fd8c977ace3d00d35e9563f5ebc9e2551dc0e4945ca4586ad9[ALL]",
        "hex": "483045022100f4984d39a301a3bcd394e46b7fa135be076987a6490ff430a96ede6d0968dfa80220287b93d4d0cb43fd8c977ace3d00d35e9563f5ebc9e2551dc0e4945ca4586ad901"
      },
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 10.00000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 646731518e9bcc7b28f8a46f4e484b735d56575b OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914646731518e9bcc7b28f8a46f4e484b735d56575b88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mpfqVBs8W1NbMqPk2oV16R6MLQTFuypaK5"
        ]
      }
    }, 
    {
      "value": 39.99996160,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 ba750b1ea99c17395c97e933d9329a407eeb9855 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914ba750b1ea99c17395c97e933d9329a407eeb985588ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mxWrC49MfPdu41gW83BNi7TmhQqKCX1Do5"
        ]
      }
    }
  ]
}

当然我们可以,也应该可以更精细的构造一个交易。

构造确定交易

下面我们利用 json 库获取 unspend 列表,然后从中拿出一条用来构造交易。

import json

result = json.loads(cli("listunspent"))
utxo = result[-6]
print(utxo)

我们还需要一个新地址:

addr0 = cli("getnewaddress")
print(addr0)

n3pzWqJHW4y1BCdPKv4VnB3Qtnq5X745wa

现在定义 vin 和 vout 列表:

vin = json.dumps([{"txid":utxo["txid"], "vout":utxo["vout"]}])
vout = json.dumps({addr0:49.9999})
print(vin)
print(vout)

[{"txid": "526b12b007aaa7ed48939488109603d9925b3b6ff8931d90c104972b521ad1e3", "vout": 0}]
{"n3pzWqJHW4y1BCdPKv4VnB3Qtnq5X745wa": 49.9999}

然后生成 rawtransaction :

rawtx = cli("createrawtransaction", vin, vout)
print(rawtx)


0200000001e3d11a522b9704c1901d93f86f3b5b92d903961088949348eda7aa07b0126b520000000000ffffffff01f0ca052a010000001976a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac00000000

当然简单归简单,这个接口不能设置找零,如果只是想转 inputs 中的一小部分, 剩余部分会都变成交易费。这应该是个很大的问题。我们可以将这个交易解码,一看便知:

tx = cli("decoderawtransaction", rawtx)
print(tx)



{
  "txid": "aab9113d365f3693336f5a7696042d28b799964827fa824cfd0e510d9c609fd0",
  "hash": "aab9113d365f3693336f5a7696042d28b799964827fa824cfd0e510d9c609fd0",
  "size": 85,
  "vsize": 85,
  "version": 2,
  "locktime": 0,
  "vin": [
    {
      "txid": "526b12b007aaa7ed48939488109603d9925b3b6ff8931d90c104972b521ad1e3",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 49.99990000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f4bc1ba5d157600b5338bca440903b513be1f6a2 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "n3pzWqJHW4y1BCdPKv4VnB3Qtnq5X745wa"
        ]
      }
    }
  ]
}

后面我们再讨论更复杂和完备的交易构造,现在先接着交易构造流程讨论如何给它签名:

signed = json.loads(cli("signrawtransaction", rawtx))
print(signed)

{'hex': '0200000001e3d11a522b9704c1901d93f86f3b5b92d903961088949348eda7aa07b0126b520000000049483045022100b16f6fc8fc6003031e833375f15bf6525102c7e052be59bf1010baaa9729f1530220720bdf21fd50a4ff4751689e6ef59fcb1e32f04f4dcfa208a99765c2f5cf4fff01ffffffff01f0ca052a010000001976a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac00000000', 'complete': True}

到此为止,我们终于构造了一个交易,现在我们把它广播出去:

new_txid = cli("sendrawtransaction", signed["hex"])
print(new_txid)


……

result = cli("generate", "1")
print(result)


[
"65139873e571227c9068b727fe50b6f3e8520a6dbc150bd603c28a9e3d168184"
]

片刻之后,我们得到了第一个手工打包的交易,现在用 getrawmempool 查看节点,应该会看到 mempool 已经被清空。 它们都进入了最新的区块。

构造更复杂的交易

像上一次交易一样,我们先找到两个可用的 utxo :

import json
result = json.loads(cli("listunspent"))
utxo0 = result[-6]
utxo1 = result[5]
print(utxo0)
print(utxo1)


{'txid': '26b6962e76f60847a7e6952e93e7b1bedefeb55ab81d96bd52891b5e5a0b0be0', 'vout': 0, 'address': 'mx17ravt8fiSJNHJrES2HiXxzTpfGnwW2e', 'scriptPubKey': '2102b331518051765afca186494922039763b0a0ffed41be17f26788724186fbf993ac', 'amount': 50.0, 'confirmations': 178, 'spendable': True, 'solvable': True}
{'txid': '5a78f2b9ee063c3e8f8ae2126587339e0e8a55ab3a740cf7ede2c4cf4e7b8908', 'vout': 0, 'address': 'n3pzWqJHW4y1BCdPKv4VnB3Qtnq5X745wa', 'account': '', 'scriptPubKey': '76a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac', 'amount': 49.9999, 'confirmations': 2, 'spendable': True, 'solvable': True}

然后生成两个收款地址:

addr0 = cli("getnewaddress")
addr1 = cli("getnewaddress")
print(addr0)
print(addr1)


mwkfrosPqs4FbkuJyNehMY1eiDoYpFkGPR
mkxaaPvfpbGsM9BFdhUMdmXFppqAoa2dTd

现在我们取出这两个 utxo 的地址所对应的私钥:

priv0 = cli("dumpprivkey", utxo0["address"])
print(priv0)
priv1 = cli("dumpprivkey", utxo1["address"])
print(priv1)

cVEkKgsphy8Txkg44Tvd8snhWdbB9c47DKQuZbhdM7Pa4gX47dbc
cNDqbGYYHK8sJ3WJNbMQcq1t2o8mxGdJvEQWHsEjrroyRxjzCMbT

我们现在构造交易的 vout 和 vin :

vin = [{"txid":utxo0["txid"], "vout":utxo0["vout"]}, {"txid":utxo1["txid"], "vout":utxo1["vout"]}]
vout = {utxo0["address"]:79.9999, utxo1["address"]:10}

现在构造出新交易:

rawtx = cli("createrawtransaction", json.dumps(vin), json.dumps(vout))
print(rawtx)

0200000002e00b0b5a5e1b8952bd961db85ab5fedebeb1e7932e95e6a74708f6762e96b6260000000000ffffffff08897b4ecfc4e2edf70c743aab558a0e9e33876512e28a8f3e3c06eeb9f2785a0000000000ffffffff02f028d6dc010000001976a914b4d5a3f7ee367fb12fd7592e907f286fb6c71e2d88ac00ca9a3b000000001976a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac00000000

现在做第一次签名:

signed0 = json.loads(cli("signrawtransaction", rawtx, "[]", json.dumps([priv0])))
print(signed0)


{'hex': '0200000002e00b0b5a5e1b8952bd961db85ab5fedebeb1e7932e95e6a74708f6762e96b6260000000049483045022100b77c54e298ed208729e661ed6bc46a0c3b73d9cb9b4dd567212ba0f2ef9f7cde02202e3dd46433c2e941a6dda53d9e7c734e957097f3fad5b55a41993f7008e7c16501ffffffff08897b4ecfc4e2edf70c743aab558a0e9e33876512e28a8f3e3c06eeb9f2785a0000000000ffffffff02f028d6dc010000001976a914b4d5a3f7ee367fb12fd7592e907f286fb6c71e2d88ac00ca9a3b000000001976a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac00000000', 'complete': False, 'errors': [{'txid': '5a78f2b9ee063c3e8f8ae2126587339e0e8a55ab3a740cf7ede2c4cf4e7b8908', 'vout': 0, 'scriptSig': '', 'sequence': 4294967295, 'error': 'Operation not valid with the current stack size'}]}

用同样的方式,我们以第二个私钥给第一次签名结果再签一次:

signed1 = json.loads(cli("signrawtransaction", signed0["hex"], "[]", json.dumps([priv1])))
print(signed1)

{'hex': '0200000002e00b0b5a5e1b8952bd961db85ab5fedebeb1e7932e95e6a74708f6762e96b6260000000049483045022100b77c54e298ed208729e661ed6bc46a0c3b73d9cb9b4dd567212ba0f2ef9f7cde02202e3dd46433c2e941a6dda53d9e7c734e957097f3fad5b55a41993f7008e7c16501ffffffff08897b4ecfc4e2edf70c743aab558a0e9e33876512e28a8f3e3c06eeb9f2785a000000006a473044022011c23a8bdd0278ed73c312b7a84008df877241da1ca40617f9ea1fd5f6fae6ad02201bd3095ded770eac3bb25c93dc6f0c26eba037f6332f455e14d0e33889671daa012102dfaabd6c406491fd337c7aedc923c984f8b53aedf016fc470dbe60f931b3ce11ffffffff02f028d6dc010000001976a914b4d5a3f7ee367fb12fd7592e907f286fb6c71e2d88ac00ca9a3b000000001976a914f4bc1ba5d157600b5338bca440903b513be1f6a288ac00000000', 'complete': True}



现在,我们可以将这个签名后的交易如前面章节中的简单交易一样,广播并确认:

new_txid = cli("sendrawtransaction", signed1["hex"])
print(new_txid)
result = cli("generate", "1")
print(result)


[
"7c29bac610910872cc791697a0293836235ee299c270b2b224ee3bc3a78160b2"
]

接下来我们讨论一下更复杂的场景,比如离线签名。