トランザクションは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)