网站首页 > 技术文章 正文
tx.py
from io import BytesIO
from unittest import TestCase
import json
import requests
from ecc import PrivateKey
from helper import (
encode_varint,
hash256,
int_to_little_endian,
little_endian_to_int,
read_varint,
SIGHASH_ALL,
)
from script import p2pkh_script, Script
class TxFetcher:
cache = {}
@classmethod
def get_url(cls, testnet=False):
if testnet:
return 'https://blockstream.info/testnet/api'
else:
return 'https://blockstream.info/api'
@classmethod
def fetch(cls, tx_id, testnet=False, fresh=False):
if fresh or (tx_id not in cls.cache):
url = '{}/tx/{}/hex'.format(cls.get_url(testnet), tx_id)
response = requests.get(url)
try:
raw = bytes.fromhex(response.text.strip())
except ValueError:
raise ValueError('unexpected response: {}'.format(response.text))
tx = Tx.parse(BytesIO(raw), testnet=testnet)
# make sure the tx we got matches to the hash we requested
if tx.segwit:
computed = tx.id()
else:
computed = hash256(raw)[::-1].hex()
if computed != tx_id:
raise RuntimeError('server lied: {} vs {}'.format(computed, tx_id))
cls.cache[tx_id] = tx
cls.cache[tx_id].testnet = testnet
return cls.cache[tx_id]
@classmethod
def load_cache(cls, filename):
disk_cache = json.loads(open(filename, 'r').read())
for k, raw_hex in disk_cache.items():
cls.cache[k] = Tx.parse(BytesIO(bytes.fromhex(raw_hex)))
@classmethod
def dump_cache(cls, filename):
with open(filename, 'w') as f:
to_dump = {k: tx.serialize().hex() for k, tx in cls.cache.items()}
s = json.dumps(to_dump, sort_keys=True, indent=4)
f.write(s)
# tag::source1[]
class Tx:
command = b'tx'
def __init__(self, version, tx_ins, tx_outs,
locktime, testnet=False, segwit=False):
self.version = version
self.tx_ins = tx_ins
self.tx_outs = tx_outs
self.locktime = locktime
self.testnet = testnet
self.segwit = segwit
self._hash_prevouts = None
self._hash_sequence = None
self._hash_outputs = None
# end::source1[]
def __repr__(self):
tx_ins = ''
for tx_in in self.tx_ins:
tx_ins += tx_in.__repr__() + '\n'
tx_outs = ''
for tx_out in self.tx_outs:
tx_outs += tx_out.__repr__() + '\n'
return 'tx: {}\nversion: {}\ntx_ins:\n{}tx_outs:\n{}locktime: {}'.format(
self.id(),
self.version,
tx_ins,
tx_outs,
self.locktime,
)
def id(self):
'''Human-readable hexadecimal of the transaction hash'''
return self.hash().hex()
# tag::source5[]
def hash(self):
'''Binary hash of the legacy serialization'''
return hash256(self.serialize_legacy())[::-1]
# end::source5[]
# tag::source2[]
@classmethod
def parse(cls, s, testnet=False):
s.read(4) # <1>
if s.read(1) == b'\x00': # <2>
parse_method = cls.parse_segwit
else:
parse_method = cls.parse_legacy
s.seek(-5, 1) # <3>
return parse_method(s, testnet=testnet)
@classmethod
def parse_legacy(cls, s, testnet=False):
version = little_endian_to_int(s.read(4)) # <4>
num_inputs = read_varint(s)
inputs = []
for _ in range(num_inputs):
inputs.append(TxIn.parse(s))
num_outputs = read_varint(s)
outputs = []
for _ in range(num_outputs):
outputs.append(TxOut.parse(s))
locktime = little_endian_to_int(s.read(4))
return cls(version, inputs, outputs, locktime,
testnet=testnet, segwit=False)
# end::source2[]
# tag::source3[]
@classmethod
def parse_segwit(cls, s, testnet=False):
version = little_endian_to_int(s.read(4))
marker = s.read(2)
if marker != b'\x00\x01': # <1>
raise RuntimeError('Not a segwit transaction {}'.format(marker))
num_inputs = read_varint(s)
inputs = []
for _ in range(num_inputs):
inputs.append(TxIn.parse(s))
num_outputs = read_varint(s)
outputs = []
for _ in range(num_outputs):
outputs.append(TxOut.parse(s))
for tx_in in inputs: # <2>
num_items = read_varint(s)
items = []
for _ in range(num_items):
item_len = read_varint(s)
if item_len == 0:
items.append(0)
else:
items.append(s.read(item_len))
tx_in.witness = items
locktime = little_endian_to_int(s.read(4))
return cls(version, inputs, outputs, locktime,
testnet=testnet, segwit=True)
# end::source3[]
# tag::source4[]
def serialize(self):
if self.segwit:
return self.serialize_segwit()
else:
return self.serialize_legacy()
def serialize_legacy(self): # <1>
result = int_to_little_endian(self.version, 4)
result += encode_varint(len(self.tx_ins))
for tx_in in self.tx_ins:
result += tx_in.serialize()
result += encode_varint(len(self.tx_outs))
for tx_out in self.tx_outs:
result += tx_out.serialize()
result += int_to_little_endian(self.locktime, 4)
return result
def serialize_segwit(self):
result = int_to_little_endian(self.version, 4)
result += b'\x00\x01' # <2>
result += encode_varint(len(self.tx_ins))
for tx_in in self.tx_ins:
result += tx_in.serialize()
result += encode_varint(len(self.tx_outs))
for tx_out in self.tx_outs:
result += tx_out.serialize()
for tx_in in self.tx_ins: # <3>
result += int_to_little_endian(len(tx_in.witness), 1)
for item in tx_in.witness:
if type(item) == int:
result += int_to_little_endian(item, 1)
else:
result += encode_varint(len(item)) + item
result += int_to_little_endian(self.locktime, 4)
return result
# end::source4[]
def fee(self):
'''Returns the fee of this transaction in satoshi'''
# initialize input sum and output sum
input_sum, output_sum = 0, 0
# use TxIn.value() to sum up the input amounts
for tx_in in self.tx_ins:
input_sum += tx_in.value(self.testnet)
# use TxOut.amount to sum up the output amounts
for tx_out in self.tx_outs:
output_sum += tx_out.amount
# fee is input sum - output sum
return input_sum - output_sum
def sig_hash(self, input_index, redeem_script=None):
'''Returns the integer representation of the hash that needs to get
signed for index input_index'''
# start the serialization with version
# use int_to_little_endian in 4 bytes
s = int_to_little_endian(self.version, 4)
# add how many inputs there are using encode_varint
s += encode_varint(len(self.tx_ins))
# loop through each input using enumerate, so we have the input index
for i, tx_in in enumerate(self.tx_ins):
# if the input index is the one we're signing
if i == input_index:
# if the RedeemScript was passed in, that's the ScriptSig
if redeem_script:
script_sig = redeem_script
# otherwise the previous tx's ScriptPubkey is the ScriptSig
else:
script_sig = tx_in.script_pubkey(self.testnet)
# Otherwise, the ScriptSig is empty
else:
script_sig = None
# add the serialization of the input with the ScriptSig we want
s += TxIn(
prev_tx=tx_in.prev_tx,
prev_index=tx_in.prev_index,
script_sig=script_sig,
sequence=tx_in.sequence,
).serialize()
# add how many outputs there are using encode_varint
s += encode_varint(len(self.tx_outs))
# add the serialization of each output
for tx_out in self.tx_outs:
s += tx_out.serialize()
# add the locktime using int_to_little_endian in 4 bytes
s += int_to_little_endian(self.locktime, 4)
# add SIGHASH_ALL using int_to_little_endian in 4 bytes
s += int_to_little_endian(SIGHASH_ALL, 4)
# hash256 the serialization
h256 = hash256(s)
# convert the result to an integer using int.from_bytes(x, 'big')
return int.from_bytes(h256, 'big')
def hash_prevouts(self):
if self._hash_prevouts is None:
all_prevouts = b''
all_sequence = b''
for tx_in in self.tx_ins:
all_prevouts += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
all_sequence += int_to_little_endian(tx_in.sequence, 4)
self._hash_prevouts = hash256(all_prevouts)
self._hash_sequence = hash256(all_sequence)
return self._hash_prevouts
def hash_sequence(self):
if self._hash_sequence is None:
self.hash_prevouts() # this should calculate self._hash_prevouts
return self._hash_sequence
def hash_outputs(self):
if self._hash_outputs is None:
all_outputs = b''
for tx_out in self.tx_outs:
all_outputs += tx_out.serialize()
self._hash_outputs = hash256(all_outputs)
return self._hash_outputs
def sig_hash_bip143(self, input_index, redeem_script=None, witness_script=None):
'''Returns the integer representation of the hash that needs to get
signed for index input_index'''
tx_in = self.tx_ins[input_index]
# per BIP143 spec
s = int_to_little_endian(self.version, 4)
s += self.hash_prevouts() + self.hash_sequence()
s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
if witness_script:
script_code = witness_script.serialize()
elif redeem_script:
script_code = p2pkh_script(redeem_script.cmds[1]).serialize()
else:
script_code = p2pkh_script(tx_in.script_pubkey(self.testnet).cmds[1]).serialize()
s += script_code
s += int_to_little_endian(tx_in.value(), 8)
s += int_to_little_endian(tx_in.sequence, 4)
s += self.hash_outputs()
s += int_to_little_endian(self.locktime, 4)
s += int_to_little_endian(SIGHASH_ALL, 4)
return int.from_bytes(hash256(s), 'big')
def verify_input(self, input_index):
'''Returns whether the input has a valid signature'''
# get the relevant input
tx_in = self.tx_ins[input_index]
# grab the previous ScriptPubKey
script_pubkey = tx_in.script_pubkey(testnet=self.testnet)
# check to see if the ScriptPubkey is a p2sh
if script_pubkey.is_p2sh_script_pubkey():
# the last cmd has to be the RedeemScript to trigger
cmd = tx_in.script_sig.cmds[-1]
# parse the RedeemScript
raw_redeem = int_to_little_endian(len(cmd), 1) + cmd
redeem_script = Script.parse(BytesIO(raw_redeem))
# the RedeemScript might be p2wpkh or p2wsh
if redeem_script.is_p2wpkh_script_pubkey():
z = self.sig_hash_bip143(input_index, redeem_script)
witness = tx_in.witness
elif redeem_script.is_p2wsh_script_pubkey():
cmd = tx_in.witness[-1]
raw_witness = encode_varint(len(cmd)) + cmd
witness_script = Script.parse(BytesIO(raw_witness))
z = self.sig_hash_bip143(input_index, witness_script=witness_script)
witness = tx_in.witness
else:
z = self.sig_hash(input_index, redeem_script)
witness = None
else:
# ScriptPubkey might be a p2wpkh or p2wsh
if script_pubkey.is_p2wpkh_script_pubkey():
z = self.sig_hash_bip143(input_index)
witness = tx_in.witness
elif script_pubkey.is_p2wsh_script_pubkey():
cmd = tx_in.witness[-1]
raw_witness = encode_varint(len(cmd)) + cmd
witness_script = Script.parse(BytesIO(raw_witness))
z = self.sig_hash_bip143(input_index, witness_script=witness_script)
witness = tx_in.witness
else:
z = self.sig_hash(input_index)
witness = None
# combine the current ScriptSig and the previous ScriptPubKey
combined = tx_in.script_sig + script_pubkey
# evaluate the combined script
return combined.evaluate(z, witness)
def verify(self):
'''Verify this transaction'''
# check that we're not creating money
if self.fee() < 0:
return False
# check that each input has a valid ScriptSig
for i in range(len(self.tx_ins)):
if not self.verify_input(i):
return False
return True
def sign_input(self, input_index, private_key):
'''Signs the input using the private key'''
# get the signature hash (z)
z = self.sig_hash(input_index)
# get der signature of z from private key
der = private_key.sign(z).der()
# append the SIGHASH_ALL to der (use SIGHASH_ALL.to_bytes(1, 'big'))
sig = der + SIGHASH_ALL.to_bytes(1, 'big')
# calculate the sec
sec = private_key.point.sec()
# initialize a new script with [sig, sec] as the cmds
script_sig = Script([sig, sec])
# change input's script_sig to new script
self.tx_ins[input_index].script_sig = script_sig
# return whether sig is valid using self.verify_input
return self.verify_input(input_index)
def is_coinbase(self):
'''Returns whether this transaction is a coinbase transaction or not'''
# check that there is exactly 1 input
if len(self.tx_ins) != 1:
return False
# grab the first input
first_input = self.tx_ins[0]
# check that first input prev_tx is b'\x00' * 32 bytes
if first_input.prev_tx != b'\x00' * 32:
return False
# check that first input prev_index is 0xffffffff
if first_input.prev_index != 0xffffffff:
return False
return True
def coinbase_height(self):
'''Returns the height of the block this coinbase transaction is in
Returns None if this transaction is not a coinbase transaction
'''
# if this is NOT a coinbase transaction, return None
if not self.is_coinbase():
return None
# grab the first cmd
first_cmd = self.tx_ins[0].script_sig.cmds[0]
# convert the cmd from little endian to int
return little_endian_to_int(first_cmd)
class TxIn:
def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff):
self.prev_tx = prev_tx
self.prev_index = prev_index
if script_sig is None:
self.script_sig = Script()
else:
self.script_sig = script_sig
self.sequence = sequence
def __repr__(self):
return '{}:{}'.format(
self.prev_tx.hex(),
self.prev_index,
)
@classmethod
def parse(cls, s):
'''Takes a byte stream and parses the tx_input at the start
return a TxIn object
'''
# prev_tx is 32 bytes, little endian
prev_tx = s.read(32)[::-1]
# prev_index is an integer in 4 bytes, little endian
prev_index = little_endian_to_int(s.read(4))
# use Script.parse to get the ScriptSig
script_sig = Script.parse(s)
# sequence is an integer in 4 bytes, little-endian
sequence = little_endian_to_int(s.read(4))
# return an instance of the class (see __init__ for args)
return cls(prev_tx, prev_index, script_sig, sequence)
def serialize(self):
'''Returns the byte serialization of the transaction input'''
# serialize prev_tx, little endian
result = self.prev_tx[::-1]
# serialize prev_index, 4 bytes, little endian
result += int_to_little_endian(self.prev_index, 4)
# serialize the script_sig
result += self.script_sig.serialize()
# serialize sequence, 4 bytes, little endian
result += int_to_little_endian(self.sequence, 4)
return result
def fetch_tx(self, testnet=False):
return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)
def value(self, testnet=False):
'''Get the outpoint value by looking up the tx hash
Returns the amount in satoshi
'''
# use self.fetch_tx to get the transaction
tx = self.fetch_tx(testnet=testnet)
# get the output at self.prev_index
# return the amount property
return tx.tx_outs[self.prev_index].amount
def script_pubkey(self, testnet=False):
'''Get the ScriptPubKey by looking up the tx hash
Returns a Script object
'''
# use self.fetch_tx to get the transaction
tx = self.fetch_tx(testnet=testnet)
# get the output at self.prev_index
# return the script_pubkey property
return tx.tx_outs[self.prev_index].script_pubkey
class TxOut:
def __init__(self, amount, script_pubkey):
self.amount = amount
self.script_pubkey = script_pubkey
def __repr__(self):
return '{}:{}'.format(self.amount, self.script_pubkey)
@classmethod
def parse(cls, s):
'''Takes a byte stream and parses the tx_output at the start
return a TxOut object
'''
# amount is an integer in 8 bytes, little endian
amount = little_endian_to_int(s.read(8))
# use Script.parse to get the ScriptPubKey
script_pubkey = Script.parse(s)
# return an instance of the class (see __init__ for args)
return cls(amount, script_pubkey)
def serialize(self):
'''Returns the byte serialization of the transaction output'''
# serialize amount, 8 bytes, little endian
result = int_to_little_endian(self.amount, 8)
# serialize the script_pubkey
result += self.script_pubkey.serialize()
return result
class TxTest(TestCase):
cache_file = '../tx.cache'
@classmethod
def setUpClass(cls):
# fill with cache so we don't have to be online to run these tests
TxFetcher.load_cache(cls.cache_file)
@classmethod
def tearDownClass(cls):
# write the cache to disk
TxFetcher.dump_cache(cls.cache_file)
def test_parse_version(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.version, 1)
def test_parse_inputs(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(len(tx.tx_ins), 1)
want = bytes.fromhex('d1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81')
self.assertEqual(tx.tx_ins[0].prev_tx, want)
self.assertEqual(tx.tx_ins[0].prev_index, 0)
want = bytes.fromhex('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a')
self.assertEqual(tx.tx_ins[0].script_sig.serialize(), want)
self.assertEqual(tx.tx_ins[0].sequence, 0xfffffffe)
def test_parse_outputs(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(len(tx.tx_outs), 2)
want = 32454049
self.assertEqual(tx.tx_outs[0].amount, want)
want = bytes.fromhex('1976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac')
self.assertEqual(tx.tx_outs[0].script_pubkey.serialize(), want)
want = 10011545
self.assertEqual(tx.tx_outs[1].amount, want)
want = bytes.fromhex('1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac')
self.assertEqual(tx.tx_outs[1].script_pubkey.serialize(), want)
def test_parse_locktime(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.locktime, 410393)
def test_serialize(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.serialize(), raw_tx)
def test_input_value(self):
tx_hash = 'd1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81'
index = 0
want = 42505594
tx_in = TxIn(bytes.fromhex(tx_hash), index)
self.assertEqual(tx_in.value(), want)
def test_input_pubkey(self):
tx_hash = 'd1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81'
index = 0
tx_in = TxIn(bytes.fromhex(tx_hash), index)
want = bytes.fromhex('1976a914a802fc56c704ce87c42d7c92eb75e7896bdc41ae88ac')
self.assertEqual(tx_in.script_pubkey().serialize(), want)
def test_fee(self):
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.fee(), 40000)
raw_tx = bytes.fromhex('010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.fee(), 140500)
def test_sig_hash(self):
tx = TxFetcher.fetch('452c629d67e41baec3ac6f04fe744b4b9617f8f859c63b3002f8684e7a4fee03')
want = int('27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6', 16)
self.assertEqual(tx.sig_hash(0), want)
def test_verify_p2pkh(self):
tx = TxFetcher.fetch('452c629d67e41baec3ac6f04fe744b4b9617f8f859c63b3002f8684e7a4fee03')
self.assertTrue(tx.verify())
tx = TxFetcher.fetch('5418099cc755cb9dd3ebc6cf1a7888ad53a1a3beb5a025bce89eb1bf7f1650a2', testnet=True)
self.assertTrue(tx.verify())
def test_verify_p2sh(self):
tx = TxFetcher.fetch('46df1a9484d0a81d03ce0ee543ab6e1a23ed06175c104a178268fad381216c2b')
self.assertTrue(tx.verify())
def test_verify_p2wpkh(self):
tx = TxFetcher.fetch('d869f854e1f8788bcff294cc83b280942a8c728de71eb709a2c29d10bfe21b7c', testnet=True)
self.assertTrue(tx.verify())
def test_verify_p2sh_p2wpkh(self):
tx = TxFetcher.fetch('c586389e5e4b3acb9d6c8be1c19ae8ab2795397633176f5a6442a261bbdefc3a')
self.assertTrue(tx.verify())
def test_verify_p2wsh(self):
tx = TxFetcher.fetch('78457666f82c28aa37b74b506745a7c7684dc7842a52a457b09f09446721e11c', testnet=True)
self.assertTrue(tx.verify())
def test_verify_p2sh_p2wsh(self):
tx = TxFetcher.fetch('954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88', testnet=True)
self.assertTrue(tx.verify())
def test_sign_input(self):
private_key = PrivateKey(secret=8675309)
stream = BytesIO(bytes.fromhex('010000000199a24308080ab26e6fb65c4eccfadf76749bb5bfa8cb08f291320b3c21e56f0d0d00000000ffffffff02408af701000000001976a914d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f88ac80969800000000001976a914507b27411ccf7f16f10297de6cef3f291623eddf88ac00000000'))
tx_obj = Tx.parse(stream, testnet=True)
self.assertTrue(tx_obj.sign_input(0, private_key))
want = '010000000199a24308080ab26e6fb65c4eccfadf76749bb5bfa8cb08f291320b3c21e56f0d0d0000006b4830450221008ed46aa2cf12d6d81065bfabe903670165b538f65ee9a3385e6327d80c66d3b502203124f804410527497329ec4715e18558082d489b218677bd029e7fa306a72236012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b67ffffffff02408af701000000001976a914d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f88ac80969800000000001976a914507b27411ccf7f16f10297de6cef3f291623eddf88ac00000000'
self.assertEqual(tx_obj.serialize().hex(), want)
def test_is_coinbase(self):
raw_tx = bytes.fromhex('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00ffffffff01faf20b58000000001976a914338c84849423992471bffb1a54a8d9b1d69dc28a88ac00000000')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertTrue(tx.is_coinbase())
def test_coinbase_height(self):
raw_tx = bytes.fromhex('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00ffffffff01faf20b58000000001976a914338c84849423992471bffb1a54a8d9b1d69dc28a88ac00000000')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertEqual(tx.coinbase_height(), 465879)
raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(raw_tx)
tx = Tx.parse(stream)
self.assertIsNone(tx.coinbase_height())
猜你喜欢
- 2024-09-09 序列化 Python 对象(序列化对象需要实现的接口)
- 2024-09-09 一篇文章读懂系列-2.二叉树及常见面试题
- 2024-09-09 Meta 如何将缓存一致性提高到 99.99999999
- 2024-09-09 自学Python笔记2(python0基础自学书)
- 2024-09-09 找到两个链表的第一个公共节点(找出两个链表的第一个公共节点)
- 2024-09-09 详解SkipList跳跃链表(跳表遍历)
- 2024-09-09 Python画豪华版圣诞树,带漂亮彩灯与文字背景
- 2024-09-09 零基础Python完全自学教程23:函数的返回值、作用域和匿名函数
- 2024-09-09 redis的内存满了之后,redis如何回收内存
- 2024-09-09 「python小游戏」据说这是一款还原度超高的小游戏,你感受下....
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)