ノードは、トランザクションを受信すると、各トランザクションがネットワークのルールに従っている事を確認する
トランザクションの検証と呼ぶ
1. トランザクションのインプットを過去に支払っていないない事
2. インプットの合計額がアウトプットの合計額以上になっている事
3. ScriptSigが前のScriptPubKeyのアンロックに成功している事
### インプット支払い状況の確認
どのフルノードでも、UTXOセットを検索すると確認できる
### インプットの合計額とアウトプットの合計額の確認
手数料を求めるメソッド
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
手数料がマイナスでないかを確認する
from tx import Tx from io import BytesIO raw_tx = ('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf830\ 3c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\ cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\ e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\ afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88a\ c99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600') stream = BytesIO(bytes.fromhex(raw_tx)) transaction = Tx.parse(stream) print(transaction.fee() >= 0)
### 署名の確認
トランザクションの検証で最も難しい部分は、その署名を確認するプロセス
ECDSA署名アルゴリズムには、公開鍵P、署名ハッシュ、署名(r, s)が必要
from ecc import S256Point, Signature sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\ 213bf016b278a') der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\ cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\ c8e10615bed') z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6 point = S256Point.parse(sec) signature = Signature.parse(der) print(point.verify(z, signature))
インプットごとに異なる署名ハッシュを計算する
1. 全てのScriptSigを空にする
2. 署名されているインプットのScriptSigを前のScriptPubKeyに置き換える
3. ハッシュタイプを付加する
from helper import hash256 from ecc import S256Point, Signature modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914\ bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\ bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\ e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\ c1943060001000000') h256 = hash256(modified_tx) z = int.from_bytes(h256, 'big') print(hex(z)) sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\ 213bf016b278a') der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\ cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\ c8e10615bed') z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6 point = S256Point.parse(sec) signature = Signature.parse(der) print(point.verify(z, signature))
sig_hash, verify_input
def sig_hash(self, input_index): s = int_to_little_endian(self.version, 4) s += encode_varint(len(self.tx_ins)) for i, tx_in in enumerate(self.tx_ins): if i == input_index: s += TxIn( prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, script_sig=tx_in.script_pubkey(self.testnet), sequence=tx_in.sequence, ).serialize() else: s += TxIn( prev_tx = tx_in.prev_tx, prev_index=tx_in.prev_index, sequence=tx_in.sequence, ).serialize() s += encode_varint(len(self.tx_outs)) for tx_out in self.tx_outs: s += tx_out.serilize() s += int_to_little_endian(self.locktime, 4) s += int_to_little_endian(SIGHASH_ALL, 4) h256 = hash256(s) return int.from_bytes(h256, 'big') def verify_input(self, input_index): tx_in = self.tx_ins[input_index] script_pubkey = tx_in.script_pubkey(testnet=self.testnet) z = self.sig_hash(input_index) combined = tx_in.script_sig + script_pubkey return combined.evaluate(z)
トランザクション全体の検証
def verify(self): if self.fee() < 0: return False for i in range(len(self.tx_ins)): if not self.verify_input(i): return False return True
### トランザクションの作成
def decode_base58(s): num = 0 for c in s: num *= 58 num += BASE58_ALPHABET.index(c) combined = num.to_bytes(25, byteorder='big') checksum = combined[-4:] if hash256(combined[:-4])[:4] != checksum: raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4])) return combined[1:-4]
def p2pkh_script(h160): return Script([0x76, 0xa9, h160, 0x88, 0xac])
トランザクションの作成
from helper import decode_base58, SIGHASH_ALL from script import p2pkh_script, Script from tx import TxIn, TxOut, Tx prev_tx = bytes.fromhex('0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6\ eb20a080843a299') prev_index = 13 tx_in = TxIn(prev_tx, prev_index) tx_outs = [] change_amount = int(0.33*100000000) change_h160 = decode_base58('mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2') change_script = p2pkh_script(change_h160) change_output = TxOut(amount=change_amount, script_pubkey=change_script) target_amount = int(0.1*100000000) target_h160 = decode_base58('mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf') target_script = p2pkh_script(target_h160) target_output = TxOut(amount=target_amount, script_pubkey=target_script) tx_obj = Tx(1, [tx_in], [change_output, target_output], 0, True) print([tx_obj])
[tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11
version: 1
tx_ins:
0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13
tx_outs:
33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVERIFY OP_CHECKSIG
10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVERIFY OP_CHECKSIG
locktime: 0]
### トランザクションへの署名
from ecc import PrivateKey from helper import SIGHASH_ALL z = transaction.sig_hash(0) private_key = PrivateKey(secret=8675309) der = private_key.sign(z).der() sig = der + SIGHASH_ALL.to_bytes(1, 'big') sec = private_key.point.sec() script_sig = Script([sig, sec]) transaction.tx_ins[0].script_sig = script_sig print(transaction.serialize().hex())
sign_input
def sign_input(self, input_index, private_key): z = self.sig_hash(input_index) der = private_key.sign(z).der() sig = der + SIGHASH_ALL.to_bytes(1, 'big') sec = private_key.point.sec() self.tx_ins[input_index].script_sig = Script([sig, sec]) return self.verify_input(input_index)
### 自分のトランザクションを作成
from ecc import PrivateKey from helper import hash256, little_endian_to_int secret = little_endian_to_int(hash256(b'hpscript secret')) private_key = PrivateKey(secret) print(private_key.point.address(testnet=True))
$ python3 main.py
mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk
from ecc import PrivateKey from helper import decode_base58, SIGHASH_ALL from script import p2pkh_script, Script from tx import TxIn, TxOut, Tx prev_tx = bytes.fromhex('75a1c4bc671f55f626dda1074c7725991e6f68b8fcefcfca7\ b64405ca3b45f1c') prev_index = 1 target_address = 'miKegze5FQNCnGw6PKyqUbYUeBa4x2hFeM' target_amount = 0.01 change_address = 'mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk' change_amount = 0.009 secret = 98765309 priv = PrivateKey(secret=secret) tx_ins = [] tx_ins.append(TxIn(prev_tx, prev_index)) tx_outs = [] h160 = decode_base58(target_address) script_pubkey = p2pkh_script(h160) target_satoshis = int(target_amount*100000000) tx_outs.append(TxOut(target_satoshis, script_pubkey)) h160 = decode_base58(change_address) script_pubkey = p2pkh_script(h160) change_satoshis = int(change_amount*100000000) tx_outs.append(TxOut(change_satoshis, script_pubkey)) tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True) print(tx_obj.sign_input(0, priv)) print(tx_obj.serialize().hex())
from ecc import PrivateKey from helper import decode_base58, SIGHASH_ALL from script import p2pkh_script, Script from tx import TxIn, TxOut, Tx prev_tx_1 = bytes.fromhex('11d05ce707c1120248370d1cbf5561d22c4f83aeba04367\ 92c82e0bd57fe2a2f') prev_index_1 = 1 prev_tx_2 = bytes.fromhex('51f61f77bd061b9a0da60d4bedaaf1b1fad0c11e65fdc74\ 4797ee22d20b03d15') prev_index_2 = 1 target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv' target_amount = 0.0429 secret = 8675309 priv = PrivateKey(secret=secret) tx_ins = [] tx_ins.append(TxIn(prev_tx_1, prev_index_1)) tx_ins.append(TxIn(prev_tx_2, prev_index_2)) tx_outs = [] h160 = decode_base58(target_address) script_pubkey = p2pkh_script(h160) target_satoshis = int(target_amount*100000000) tx_outs.append(TxOut(target_satoshis, script_pubkey)) tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True) print(tx_obj.sign_input(0, priv)) print(tx_obj.sign_input(1, priv)) print(tx_obj.serialize().hex())
少しずつ見えてきましたね