enumerate()関数を利用することで、for文内のループ処理にインデックス番号を付与できる
ary = ['ドル', 'ユーロ', 'ポンド', 'リラ']
for i, d in enumerate(ary):
print('index:' + str(i) + ' 通貨:' + d)
$ python3 test.py
index:0 通貨:ドル
index:1 通貨:ユーロ
index:2 通貨:ポンド
index:3 通貨:リラ
随机应变 ABCD: Always Be Coding and … : хороший
enumerate()関数を利用することで、for文内のループ処理にインデックス番号を付与できる
ary = ['ドル', 'ユーロ', 'ポンド', 'リラ']
for i, d in enumerate(ary):
print('index:' + str(i) + ' 通貨:' + d)
$ python3 test.py
index:0 通貨:ドル
index:1 通貨:ユーロ
index:2 通貨:ポンド
index:3 通貨:リラ
トランザクションは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)
ScriptPubKey
55 OP_5
93 OP_ADD
59 OP_9
87 OP_EQUAL
ScriptSig
54 OP_4
76 OP_DUP
76 OP_DUP
95 OP_MUL
93 OP_ADD
56 OP_6
87 OP_EQUAL
52
from script import Script script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87]) script_sig = Script([0x52]) combined_script = script_sig + script_pubkey print(combined_script.evaluate(0))
OP_CODEが異なるような気が…
OP_MULはビットコインネットワークで無効化されている
6e OP_2DUP
87 OP_EQUAL
91 OP_NOT
69 OP_VERIFY
a7 OP_SHA1
7c OP_SWAP
a7 OP_SHA1
87 OP_EQUAL
from script import Script script_pubkey = Script([0x6e, 0x87, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87]) c1 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\ 32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\ 203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\ 74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\ fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\ fe017f46dc93a6b67e013b029aaa1db2560b45ca67d688c7f84b8c4c791fe02b3df614f86db169\ 0901c56b45c1530afedfb76038e972722fe7ad728f0e4904e046c230570fe9d41398abe12ef5bc\ 942be33542a4802d98b5d70f2a332ec37fac3514e74ddc0f2cc1a874cd0c78305a215664613097\ 89606bd0bf3f98cda8044629a1' c2 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\ 32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\ 203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\ 74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\ fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\ fe017346dc9166b67e118f029ab621b2560ff9ca67cca8c7f85ba84c79030c2b3de218f86db3a9\ 0901d5df45c14f26fedfb3dc38e96ac22fe7bd728f0e45bce046d23c570feb141398bb552ef5a0\ a82be331fea48037b8b5d71f0e332edf93ac3500eb4ddc0decc1a864790c782c76215660dd3097\ 91d06bd0af3f98cda4bc4629b1' collision1 = bytes.fromhex(c1) collision2 = bytes.fromhex(c2) script_sig = Script([collision1, collision2]) combined_script = script_sig + script_pubkey print(combined_script.evaluate(0))
secp256k1の公開鍵は圧縮で33バイト、非圧縮で65バイト(130文字)で長い
UTXOセットが大きくなる
ScriptPubKeyフィールドに公開鍵が存在する
### p2pkhで解決
– アドレスが短くなる
– sha256とripemd160
=> hash160と呼ぶ 160ビット
p2pkhは短くて安全性が高い
### ScriptPubKey(output)
76 OP_DUB
a9 OP_HASH160
14 Length of
bc3b…da
88 OP_EQUALVERIFY
ac OC_CHECKSIG
### ScriptSig(input)
48 Length of signature
30..01 signature
21 Length of pubkey
0349…8a pubkey
### 構成 OP_DUPはエレメントの先頭を複製するため、pubkeyが複製される OP_HASH160でpubkeyをhash160(sha256, ripemd160)実行、20バイトのハッシュを生成する OP_EQUALVERIFYで、生成されたhashとhash値が正しいか確認 OP_CHECKSIGはP2PKと同じ ### P2PKH
– ScriptPubKey(output)
OP_DUB, OP_HASH160, HASH, OP_EQUALVERIFY, OP_CHECKSIG
– ScriptSig(input)
def op_dup(stack):
if len(stack) < 1:
return False
stack.append(stack[-1])
return True
def op_hash160(stack):
if len(stack) < 1:
return False
element = stack.pop()
stack.append(hash160(element))
return True
def op_equalverify(stack):
return op_equal(stack) and op_verify(stack)
def op_equal(stack):
if len(stack) < 2:
return False
element1 = stack.pop()
element2 = stack.pop()
if element1 == element2:
stack.append(encode_num(1))
else:
stack.append(encode_num(0))
return True
def op_verify(stack):
if len(stack) < 1:
return False
element = stack.pop()
if decode_num(element) == 0:
return False
return True
def op_checksig(stack, z):
if len(stack) < 1:
return False
sec_pubkey = stack.pop()
der_signature = stack.pop()[:-1]
try:
point = S256Point.parse(sec_pubkey)
sig = Signature.parse(der_signature)
except (ValueError, SyntaxError) as e:
return False
if point.verify(z, sig):
stack.append(encode_num(1))
else:
stack.append(encode_num(0))
return True
– ScriptPubKeyが短くなる
– ripemd160,sha256も計算する
OP_CHECKSIG
The entire transaction’s outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise.
from ecc import (
S256Point,
Signature,
)
def op_checksig(stack, z):
if len(stack) < 1:
return False
sec_pubkey = stack.pop()
der_signature = stack.pop()[:-1]
try:
point = S256Point.parse(sec_pubkey)
sig = Signature.parse(der_signature)
except (ValueError, SyntaxError) as e:
return False
if point.verify(z, sig):
stack.append(encode_num(1))
else:
stack.append(encode_num(0))
return True
class S256Point(Point):
@classmethod
def parse(self, sec_bin):
if sec_bin[0] == 4:
x = int.from_bytes(sec_bin[1:33], 'big')
y = int.from_bytes(sec_bin[33:65], 'big')
return S256Point(x=x, y=y)
is_even = sec_bin[0] == 2
x = S256Field(int.from_bytes(sec_bin[1:], 'big'))
alpha = x**3 + S256Field(B)
beta = alpha.sqrt()
if beta.num % 2 == 0:
even_beta = beta
odd_beta = S256Field(P - beta.num)
else:
even_beta = S256Field(P - beta.num)
odd_beta = beta
if is_even:
return S256Point(x, even_beta)
else:
return S256Point(x, odd_beta)
class Signature:
@classmethod
def parse(cls, signature_bin):
s = BytesIO(signature_bin)
compound = s.read(1)[0]
if compound != 0x30:
raise SyntaxError("Bad Signature")
length = s.read(1)[0]
if length + 2 != len(signature_bin):
raise SyntaxError("Bad Signature Length")
marker = s.read(1)[0]
if marker != 0x02:
raise SyntaxError("Bad Signature")
rlength = s.read(1)[0]
r = int.from_bytes(s.read(rlength), 'big')
marker = s.read(1)[0]
if marker != 0x02:
raise SyntaxError("Bad Signature")
slength = s.read(1)[0]
s = int.from_bytes(s.read(slength), 'big')
if len(signature_bin) != 6 + rlength + slength:
raise SyntaxError("Signature too long")
return cls(r, s)
class S256Point(Point):
def verify(self, z, sig):
s_inv = pow(sig.s, N - 2, N)
u = z * s_inv % N
v = sig.r * s_inv % N
total = u * G + v * self
return total.x.num == sig.r
bitcoinの署名をシリアライズする標準をDERフォーマット(Distinguished Encoding rules)と呼ぶ
1. 0x30(48)bytesで開始
2. 署名の残りの長さをエンコードし、通常は0x44または0x45を追加
3. マーカーバイト 0x02を追加
4. rをビックエンディアンの整数としてエンコード
ただしrの先頭バイトが0x80以上のときは、先頭に0x00を付加。長さをrの先頭に付加
5. マーカーバイト0x02を追加
6. sをビッグエンディアンの整数としてエンコード
ただしrの先頭バイトが0x80以上のときは、先頭に0x00を付加。長さをrの先頭に付加
30 – Marker
45 – Length of Sig
02 – Marker for r value
21 – r value length
00ed…8f – r value
02 – Marker for s value
20 – s value length
7a98..ed – s value
def der(self):
rbin = self.r.to_bytes(32, byteorder='big')
rbin = rbin.lstrip(b'\x00')
if rbin[0] & 0x80:
rbin = b'\x00' + rbin
result = bytes([2, len(rbin)]) + rbin
sbin = self.s.to_bytes(32, byteorder='big')
sbin = sbin.lstrip(b'\x00')
if sbin[0] & 0x80:
sbin = b'\x00' + sbin
result += bytes([2, len(sbin)]) + sbin
return bytes([0x30, len(result)]) + result
署名ハッシュz
s = (z + r(Temporary PrivateKey * Gのx座標)*G)/Temporary PrivateKey mod P
署名は、署名ハッシュと kの値が決まれば、k*Gのx軸座標であるrと、(z + r*e)/k の sが決まる。
def encode_num(num):
if num == 0:
return b''
abs_num = abs(num)
negative = num < 0
result = bytearray()
while abs_num:
result.append(abs_num & 0xff)
abs_num >>= 8
if result[-1] & 0x80:
if negative:
result.append(0x80)
else:
result.append(0)
elif negative:
result[-1] != 0x80
return bytes(result)
def decode_num(element):
if element == b'':
return 0
big_endian = element[::-1]
if big_endian[0] & 0x80:
negative = True
result = big_endian[0] & 0x7f
else:
negative = False
result = big_endian[0]
for c in big_endian[1:]:
result <<= 8
result += c
if negative:
return -result
else:
return result
def op_0(stack):
stack.append(encode_num(0))
return True
bytearray() method returns a bytearray object which is an array of given bytes.
prime = [2, 3, 5, 7] print(bytearray(prime))
$ python3 test.py
bytearray(b’\x02\x03\x05\x07′)
0 <= x < 256 の整数
num = -20 print(abs(num))
num = -30.33 などにしても結果は同じ