sig_hashとverify_input

txをシリアライズする際に、redeem_script=None としてp2pkh か p2shかを分ける

    def sig_hash(self, input_index, redeem_script=None):
        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:
                if redeem_script:
                    script_sig = redeem_script
                else:
                    script_sig = tx_in.script_pubkey(self.testnet)
            else:
                script_sig = None    
            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()
        s += encode_varint(len(self.tx_outs))
        for tx_out in self.tx_outs:
            s += tx_out.serialize()
        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)
        if script_pubkey.is_p2sh_script_pubkey():
            cmd = tx_in.script_sig.cmds[-1]
            raw_redeem = encode_varint(len(cmd)) + cmd
            redeem_script = Script.parse(BytesIO(raw_redeem))
        else:
            redeem_script = None
        z = self.sig_hash(input_index, redeem_script)
        combined = tx_in.script_sig + script_pubkey
        return combined.evaluate(z)
    def is_p2pkh_script_pubkey(self):
        return len(self.cmds) == 5 and self.cmds[0] == 0x76 \
            and self.cmds[1] == 0xa9 \
            and type(self.cmds[2]) == bytes and len(self.cmds[2]) == 20 \
            and self.cmds[3] == 0x88 and self.cmds[4] == 0xac

    def is_p2sh_script_pubkey(self):
        return len(self.cmds) == 3 and self.cmds[0] == 0xa9 \
            and type(self.cmds[1]) == bytes and len(self.cmds[1]) == 20 \
            adn self.cmds[2] == 0x87

p2shの署名検証

p2pkhは1つの署名と1つの公開鍵のみだが、p2shは幾つかの公開鍵(RedeemScript内のSECフォーマット)と、それ以下の数の署名(ScriptSig内、DERフォーマット)がある。署名は公開鍵と同じ順番である必要がある。
特定の署名と公開鍵を取得したら、署名が有効かを判断するために必要なのは署名ハッシュ、すなわちzのみである。

### 署名ハッシュの取得方法
1. 全てのScriptSig(output)を空にする => 00
2. 署名対象となるp2shのインプットのScriptSigをRedeemScriptに置き換える
 L RedeemScriptを空のScriptSigの場所に配置する。※ScriptPubKeyでないことがp2pkhと異なる
3. 4バイトのハッシュタイプを末尾に追加する。p2pkhと同じ。SIGHASH_ALLに対応する整数は1であり、4バイトのリトルエンディアンでエンコードする必要がある
4. SEC公開鍵、DER署名はScriptSig(output)から

from ecc import S256Point, Signature
from helper import hash256
modified_tx = bytes.fromhex('0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a38\
5aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036\
b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98be\
c453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa0\
5de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b9\
5abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2\
788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687000000000\
1000000')
h256 = hash256(modified_tx)
z = int.from_bytes(h256, 'big')
sec = bytes.fromhex('022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff\
1295d21cfdb70')
der = bytes.fromhex('3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a\
8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4\
ee942a89937')
point = S256Point.parse(sec)
sig = Signature.parse(der)
print(point.verify(z, sig))

$ python3 test.py
True

from io import BytesIO
from ecc import S256Point, Signature
from helper import hash256, int_to_little_endian, encode_varint
from script import Script
from tx import Tx, TxIn, SIGHASH_ALL
hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6\
bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8\
fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4e\
e942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef\
53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022\
01475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103\
b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04\
d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f40090000\
0000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976\
a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da\
1574e6b3c192ecfb52cc8984ee7b6c568700000000'
hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4b\
b71'
hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8ee\
f53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402\
2'
hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca88\
30bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7f\
bdbd4bb7152ae'
sec = bytes.fromhex(hex_sec)
der = bytes.fromhex(hex_der)
redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script)))
stream = BytesIO(bytes.fromhex(hex_tx))
tx_obj = Tx.parse(stream)
s = int_to_little_endian(tx_obj.version, 4)
s += encode_varint(len(tx_obj.tx_ins))
i = tx_obj.tx_ins[0]
s += TxIn(i.prev_tx, i.prev_index, redeem_script, i.sequence).serialize()
s += encode_varint(len(tx_obj.tx_outs))
for tx_out in tx_obj.tx_outs:
    s += tx_out.serialize()
s += int_to_little_endian(tx_obj.locktime, 4)
s += int_to_little_endian(SIGHASH_ALL, 4)
z = int.from_bytes(hash256(s), 'big')
point = S256Point.parse(sec)
sig = Signature.parse(der)
print(point.verify(z, sig))

$ python3 test.py
True

p2shのコーディング

OP_HASH160, hash160, OP_EQUALの処理を追加する

            else:
                stack.append(cmd)
                if len(cmds) == 3 and cmds[0] == 0xa9 \
                    and type(cmds[1]) == bytes and len(cmds[1]) == 20 \
                    and cmds[2] == 0x87:
                    cmds.pop()
                    if not op_hash160(stack):
                        return False
                    stack.append(h160)
                    if not op_equal(stack):
                        return False
                    if not op_verify(stack):
                        LOGGER.info('bad p2sh h160')
                        return False
                    redeem_script = encode_varint(len(cmd)) + cmd
                    stream = ByteIO(redeem_script)
                    cmds.extend(Script.parse(stream).cmds)

p2shの特徴は、RedeemScriptが長くともOP_PUSHDATA2の最大長520byteである点
複雑なロジックを定義するスクリプトにすることができる。

### p2shのアドレス
p2shアドレスを計算するには、p2pkhアドレスの計算方法と類似したプロセスを用いる
hash160の前にプレフィックスバイトを、末尾にチェックサムを追加する
メインネットのp2shはプレフィックスバイトに0x05、Base58のアドレスは3から始まる
テストネットのp2shのプレフィックスバイトに0xc4を使用するため、アドレスは2から始まる

from helper import encode_base58_checksum
h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
print(encode_base58_checksum(b'\x05' + h160))

$ python3 test.py
3CLoMMyuoDQTPRD3XYZtCvgvkadrAdvdXh

p2pkhとp2shのアドレス

def h160_to_p2pkh_address(h160, testnet=False):
    if testnet:
        prefix = b'\x6f'
    else:
        prefix = b'\x00'
    return encode_base58_checksum(prefix + h160)

def h160_to_p2sh_address(h160, testnet=False):
    if testnet:
        prefix = b'\xc4'
    else:
        prefix = b'\x05'
    return encode_base58_checksum(prefix + h160)

p2sh(pay to script hash)

マルチシグ、複数署名の解法
複数の秘密鍵から単一の集約署名であるシェノア署名が一般的になると思われる

### ベアマルチシグ
オプコードのOP_CHECKMULTISIG (0xae)を理解する必要がある。
OP_CHECKMULTISIGは、スタックから多数の要素をポップし、必要な数の署名がトランザクションインプットに対して有効か否かを返す
トランザクションアウトプットは長いScriptPubKeyのまま

ベアマルチシグのScriptPubKey
51 OP_1
41 Length of pubkey
04fc..3d pubkey1
21 Length of pubkey2
0202..00 pubkey2
52 OP_2
ae OP_CHECKMULTISIG

ベアマルチシグのScriptSig
00 OP_0
48 Length of signature1
3045…01 signature1

pubkeyやsignatureの数が多くなればなるほどバイト数が多くなる
pubkey, signatureのm, nは1~20の数値を指定できる

### SCRIPTの例
OP_0, signature1, signature2, …, signature m, OP_m, pubkey1, … pubkey n, OP_n, OP_CHECKMULTISIG
OP_CHECKMULTISIGは、m + n + 3個の要素をポップし、n個の公開鍵のリストのうち、m個の署名が有効である場合は1をスタックにプッシュ、有効でない場合は0
OP_CHECKMULTISIGは m + n + 2この要素をプップするはずが、m + n + 3でポップしているため、OP_0が追加されている。

def op_checkmultisig(stack, z):
    if len(stack) < 1:
        return False
    n = decode_num(stack.pop())
    if len(stack) < n + 1:
        return False
    sec_pubkeys = []
    for _ in range(n):
        sec_pubkeys.append(stack.pop())
    m = decode_num(stack.pop())
    if len(stack) < m + 1:
        return False
    der_signatures = []
    for _ in range(m):
        der_signatures.append(stack.pop()[:-1])
    stack.pop()
    try:
        points = [S256Point.parse(sec) for sec in sec_pubkeys]
        sigs = [Signature.parse(der) for der in der_signatures]
        for sig in sigs:
            if len(points) == 0:
                LOGGER.info("signatures no good or not in right order")
                return False
            success = False
            while points:
                point = points.pop(0)
                if point.verify(z, sig):
                    success = True
                    break
                if not success:
                    return False
        stack.append(encode_num(1))
    except (ValueError, SyntaxError):
        return False
    return True

### ベアマルチシグの問題
m of nの署名を要求して単一障害を回避するが、問題がある
– ScriptPubKeyが長くなる
– outputsが通常のp2pkhアウトプットの5~20倍になる
これらの問題を軽減するためにPay to script hash(p2sh)が誕生している
L Scriptコマンドのハッシュを取得し、後でハッシュ化する前のScriptコマンドを明らかにする

### 特別なルールを実行するPay-to-script-hash
RedeemScript, OH_HASH160, hash, OP_EQUAL
スタックが1で終わる場合、RedeemScriptがパースされ、Scriptコマンドセットに追加 BIP160で導入された
2-of-2マルチシグのScriptPubKeyがあるとする これをp2shに変換する作業をスクリプトのハッシュを取得してスクリプトを引き換えたいときに利用しやすいように保持する
52 OP_2
21 Length of pubkey1
02…db70 pubkey1
21 Length of pubkey2
03…bb71 pubkey2
52 OP_2
ae OP_CHECKMULTISIG

Pay to script hash(p2sh)のScriptPubKey
a9 OP_HASH160
14 Length of Hash
74d6…56 hash
87 OP_EQUAL

これまでのScriptPubKeyに相当するもののhash160 アンロック時に明らかにしなければならないRedeemScriptのハッシュに資金をロックする
RedeemScriptは公開だけでなくアンロックにも必要で、ブロックチェーン上にはないため、p2shアドレスの作成者が保管しておかなければならない

2-of-2マルチシグのScriptSig
00 OP_0
48 Length of signature1
3045…3701 signature1
48 Length of signature2
3045…2201 signature
47 Length of RedeemScript
5221…ae RedeemScript

### 連結のScript
ScriptPubKey: OP_HASH160, hash, OP_EQUAL
ScriptSig: OP_0, signature1, signature2, RedeemScript
Script: OP_0, signature1, signature2, RedeemScript, OP_HASH160, hash, OP_EQUAL

特別なルールを実行するp2shのパターン: RedeemScript, OP_HASH160, hash, OP_EQUAL
RedeemScriptはScriptコマンドにセット
h160した結果がScriptPubKeyにあるh160の値と等しくなるRedeemScriptを明らかにすると、RedeemScriptはScriptPubKeyの代わりとして機能する
資金をロックするスクリプトをハッシュして、スクリプト自体の代わりにブロックチェーンに入れる

OP_0, signature1, signature2, RedeemScript, OP_HASH160, hash, OP_EQUAL
OP_HASH160でRedeemScriptをhashにするので、
OP_0, signature1, signature2, hash, OP_EQUALとなる。
hashが等しい場合は結果は有効とみなされる

52 OP_2
21 Length of pubkey1
02…db70 pubkey1
21 Length of pubkey2
03…bb71 pubkey2
52 OP_2
ae OP_CHECKMULTISIG

テストネットでのtransaction作成

まずアドレスの作成

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))

mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk

「testnet bitcoin faucet」で検索して出てきた以下のサイトからテストネットのコインを受け取ります。
https://coinfaucet.eu/en/btc-testnet/

We sent 0.01947729 bitcoins to address
mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk

tx: 7c671e8faaab0569acee26d76ae158adcd8f4806ec08c480d596e2aa78095026

FEES 0.00017 BTC

—-

1つのUTXOの60%を指定したアドレス「mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv」に送付する
残額から手数料を引いた額をお釣り用のアドレスに送る必要があるため、もう一つ、お釣り用の bitcoin testnetのアドレスを作ります。hpscript blog
mmEjxM1RvKquR6FvUNibQe8ah4FFkphrdZ

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('7c671e8faaab0569acee26d76ae158adcd8f4806ec08c480d596e2aa78095026')
prev_index = 1
target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
target_amount = 0.01
change_address = 'mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk'
change_amount = 0.009
secret = 21059908846034780412291472495725985072617958337763972172781872669894507067750
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(target_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())

TxOutは配列で持っておき、amountとscript_pubkeyのセットをoutsごとにappendしていく。
$ python3 main.py
True
010000000126500978aae296d580c408ec06488fcdad58e16ad726eeac6905abaa8f1e677c010000006a473044022010b3ba160737a3a84a06cb431acc7b72393947c686af9fab83e6e5169dc4b58d02200233d9a6b4b3c93ddfbfb730fdc1b15563f2822a09b335f976f0f0abf7cc4c1701210284d4b1384aa13140d99f36a4143adf74c8d487dd13acc6d17def7081c2034cd2ffffffff0240420f00000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac40420f00000000001976a9148e813b482fc151baaf0c6b254aa02d10ee49ad5888ac00000000

### 2つのinputs, 1つのoutputsの場合
同じアドレスでfaucetからbitcoinを取得しようとすると、7hour後となる。

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.serialize().hex())

$ python3 main.py
True
01000000022f2afe57bde0822c793604baae834f2cd26155bf1c0d37480212c107e75cd011010000006a47304402204cc5fe11b2b025f8fc9f6073b5e3942883bbba266b71751068badeb8f11f0364022070178363f5dea4149581a4b9b9dbad91ec1fd990e3fa14f9de3ccb421fa5b269012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b67ffffffff153db0202de27e7944c7fd651ec1d0fab1f1aaed4b0da60d9a1b06bd771ff6510100000000ffffffff01d0754100000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac00000000

inputsが複数の場合は、tx_ins[]にappendしていく。

トランザクションへの署名

署名ハッシュzを取得する方法がわかっている
ScriptPubKey内の20バイトのハッシュ(hash160)の元となる公開鍵に対応する秘密鍵を知っている場合、zに署名してDER署名を生成できる

from ecc import PrivateKey
from helper import SIGHASH_ALL
z = tranzaction.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())

関数上では、input_indexは0固定ではなく、input_indexで指定する。
der署名のscript_sigはprivate_keyとzから作成する。

    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)

Transactionの作成

インプットがp2pkhのScriptPubKeyによってロックされたトランザクションの作成

1. ビットコインをどこに移動させるか
2. どのようなUTXOを使うことができるか
3. このトランザクションをどれくらい速くブロックチェーンに入れたいか

ビットコインではアドレス(公開鍵)はプライバシーやセキュリティの問題から再利用するべきではない。

### 手数料の算定
手数料はバイトサイズに基づき算定
300byteより600byteの方が高くなる
ブロックに空きがあるときは1bytesあたり1satoshi

### トランザクションの作成
アドレスを取得し、そこから20バイトのハッシュを取得する
数値を得たら、ビッグエンディアンバイトに変換
最初のバイトはネットワークプレフィックス、最後の4バイトはチェックサム、中間の20バイトが20バイトハッシュ(hash160)

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]

この20バイトのハッシュをScriptPubKeyに変換する
script.py

def p2pkh_script(h160):
    return Script([0x76, 0xa9, h160, 0x88, 0xac])

0x76: OP_DUP
0xa9: OP_HASH160
h160 は20バイトのエレメント(引数)
0x88: OP_EQUALVERIFY
0xac: OP_CHECKSIG

### トランザクションの作成

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)

$ python3 test.py
tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11
version: 1
tx_ins:
0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13
tx_outs:
locktime: 33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVERIFY OP_CHECKSIG
10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVERIFY OP_CHECKSIG

transaction全体の検証

verify_inputのinput_indexとは、tx_inの何番目かを指している。iはlen(self.tx_ins)で求める。

class Tx:

    // 

    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
[//code]

lenは配列の場合は、配列の数を表示

ary = ['ドル', 'ユーロ', 'ポンド', 'リラ']
print(len(ary))

$ python3 test.py
4

フルノードでは最大sigops、ScriptSigサイズ等、より多くの項目を検証している

署名の確認 SIGHASH_ALLによる署名zの算出

トランザクションは1インプットにつき1つの署名を持つ。マルチシグの場合は、署名が複数
ECDSAアルゴリズには、公開鍵P、署名ハッシュz、署名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))

OP_CHECKSIGが実行された場合、SEC公開鍵とDER署名がスタックにあるので簡単に取得できる。
難しいのは署名ハッシュを取得する部分
=> 署名前にトランザクションを変更し、インプットごとに異なる署名ハッシュを計算する

### ScriptSig(inputs)を空にする
インプットが1つの場合や複数の場合でも署名チェック時にScriptSigを空にする => 00
インプットが指しているScriptPubKeyを取り出して、空になっているScriptSigを入れる
=> インプットのScriptSigを前のScriptPubKeyに置き換える
4バイトのハッシュタイプを末尾に追加
署名の範囲は、他の全てのインプット、アウトプットを含めるSIGHASH_ALL、特定の1つのアウトプットを含めるSIGHASH_SINGLE、アウトプットは範囲外とするSIGHASH_NONE
=> 殆どのトランザクションはSIGHASH_ALLで署名されている。滅多に使われないSIGHASH_ANYONECANPAYというハッシュタイプもある
SIGHASH_ALLに対応する整数は1であり、リトルエンディアンで4バイトでエンコードする必要がある
ハッシュタイプ(SIGHASH_ALL)を末尾01000000付加

0100000001813f79011acb80925dfe69b3def355fe914\
bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\
bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\
e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\
c1943060001000000

zを算出する

from helper import hash256
modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914\
bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\
bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\
e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\
c1943060001000000')
h256 = hash256(modified_tx)
z = int.from_bytes(h256, 'big')
print(hex(z))

$ python3 test.py
0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6

Txクラスの sig_hashメソッド

    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.serialize()
        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)

トランザクションの作成と検証

### トランザクションの検証
ノードがトランザクションを受信すると、各トランザクションがネットワークのルールにしたがっている事を確認

確認事項
– トランザクションのインプットを過去に支払っていないこと
– インプットの合計額がアウトプットの合計額以上になっていること
– ScriptSigがScriptPubKeyのアンロックに成功していること

### inputの支払い状況の確認
インプットが過去に使われていないかを確認するにはUTXOセットを検索する
=> そのトランザクションが検証テストに合格すれば、UTXOセットからトランザクションのインプットを削除する
=> 軽量クライアントはフルノードの情報を信用する

### inputとoutputの合計額の確認
inputには額を表すフィールドがないため、ブロックチェーンを検索する必要がある

    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_sum += 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)