トランザクションとはエンティティから別のエンティティに移動する事
– トランザクションコンポーネント
バージョン(トランザクション追加機能)
インプット(どのビットコインを使うか)
アウトプット(どこにビットコインを移動するか)
ロックタイム(いつトランザクションが有効になるか)
### バージョン
class Tx: def __init__(self, version, tx_ins, tx_outs, locktime, testnet=False): self.version = version self.tx_ins = tx_ins self.tx_outs = tx_outs self.locktime = locktime self.testnet = testnet 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): return self.hash().hex() def hash(self): return hash256(self.serialize())[::-1] @classmethod def parse(cls, s, testnet=False): version = little_endian_to_int(s.read(4)) return cls(version, None, None, None, testnet=testnet)
### インプット
前のトランザクションのアウトプットを示す
L 前に受け取ったビットコインへの参照
L 支払う本人のビットコインである証明
variable integer = varintを使用する
def read_varint(s): i = s.read(1)[0] if i == 0xfd: return little_endian_to_int(s.read(2)) elif i == 0xfe: return little_endian_to_int(s.read(4)) elif i == 0xff: return little_endian_to_int(s.read(8)) else: return i def encode_varint(i): if i < 0xfd: return bytes([i]) elif i < 0x10000: return b'\xfd' + int_to_little_endian(i, 2) elif i < 0x100000000: return b'\xfe' + int_to_little_endian(i, 4) elif i < 0x10000000000000000: return b'\xff' + int_to_little_endian(i, 8) else: raise ValueError('integer too large: {}'.format(i))
インプットフィールド
– 前のトランザクションID, トランザクションインデックス、ScriptSig, シーケンス
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, )
script_hex = ('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\ cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\ e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\ a') stream = BytesIO(bytes.fromhex(script_hex)) script_sig = Script.parse(stream) print(script_sig)
### アウトプット
ビットコインの送信先を定義
varint形式のアウトプット数で開始
フィールドには額とscriptpubkeyがある
TxOut
class tx: // 省略 @classmethod def parse(cls, s, testnet=False): version = little_endian_to_int(s.read(4)) num_inputs = read_varint(s) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(s)) num_outputs = read_variant(s) for _ in range(num_outputs): outputs.append(TxOut.parse(s)) return cls(version, inputs, outpute, None, testnet=testnet) 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): amount = little_endian_to_int(s.read(8)) script_pubkey = Script.parse(s) return cls(amount, script_pubkey)
### ロックタイム
@classmethod def parse(cls, s, testnet=False): version = little_endian_to_int(s.read(4)) num_inputs = read_varint(s) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(s)) num_outputs = read_variant(s) outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(s)) locktime = little_endian_to_int(s.read(4)) return cls(version, inputs, outpute, locktime, testnet=testnet)
hex_transaction = '010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600' stream = BytesIO(bytes.fromhex(hex_transaction)) tx_obj = Tx.parse(stream) print(tx_obj.tx_ins[1].script_sig) print(tx_obj.tx_outs[0].script_pubkey) print(tx_obj.tx_outs[1].amount)
$ python3 app.py
304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a71601 035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937
OP_DUP OP_HASH160 ab0c0b2e98b1ab6dbf67d4750b0a56244948a879 OP_EQUALVERIFY OP_CHECKSIG
40000000
### シリアライズ
class tx def serialize(self): 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 class TxIn def serialize(self): result = self.prev_tx[::-1] result += int_to_little_endian(self.prev_index, 4) result += self.script_sig.serialize() result += int_to_little_endian(self.sequence, 4) return result class TxOut def serialize(self): result = int_to_little_endian(self.amount, 8) result += self.script_pubkey.serialize() return result
### トランザクション手数料
マイナーがトランザクションに取り込む手数料がトランザクション手数料となる
class TxIn: def fetch_tx(self, testnet=False): return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet) def value(self, testnet=False) tx = self.fetch_tx(testnet=testnet) return tx.tx_outs[self.prev_index].amount def script_pubkey(self, testnet=False): tx = self.fetch_tx(testnet=testnet) return tx.tx_outs[self.prev_index].script_pubkey class TxFetcher: cache = {} @classmethod def get_url(cls, testnet=False) if testnet: return 'http://testnet.programmingbitcoin.com' else: return 'http://mainnet.programmingbitcoin.com' @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()) ecept ValueError: raise ValueError('unexpected response: {}'.format(response.text)) if raw[4] == 0: raw = raw[:4] + raw[6:] tx = Tx.parse(BytesIO(raw), testnet=testnet) tx.locktime = little_endian_to_int(raw[-4:]) else: tx = Tx.parse(BytesIO(raw), testnet=testnet) if tx.id() != tx_id: raise ValueError('not the same id: {} vs {}'.foramt(tx.id(), tx_id)) clc.cache[tx_id] = tx cls.cache[tx_id].testnet = testnet return cls.cache[tx_id] class Tx: def fee(self, testnet=False): input_sum, output_sum = 0, 0 for tx_in in self.tx_ins: input_sum += tx_in.value(testnet=testnet) for tx_out in self.tx_outs: output_suum += tx_out.amount return input_sum - output_sum