a = [0, 1, 4, 9, 16, 25, 36, 49] print(a[:])
$ python3 test.py
[0, 1, 4, 9, 16, 25, 36, 49]
[:]の場合は始点から終点まで全てをスライスする。
随机应变 ABCD: Always Be Coding and … : хороший
a = [0, 1, 4, 9, 16, 25, 36, 49] print(a[:])
$ python3 test.py
[0, 1, 4, 9, 16, 25, 36, 49]
[:]の場合は始点から終点まで全てをスライスする。
eは秘密鍵、pは公開鍵
eG = P
ランダムな256ビットの数字k
k * G = R (x座標をr)
uG + vP = kG
kはランダムに選ばれる数字で、u, vは署名者が選ぶ
vP = (k – u)G
P = ((k-u)/v)G
eG = ((k-u)/v)G のため、 e = (k – v) / u
つまり、秘密鍵 e = (k – v) / u を満たす(u, v)の組み合わせであればどれでも良い
eは秘密鍵を知っている人のみわかる
目的は署名ハッシュと呼ぶ。署名ハッシュはzで表す。
uG + vPに当てはめると、 u = z/s, v = z/s
uG + vP = R(目的) = kG
uG + veP = kG
u + ve = k
z/s + re/s = k
(z + re)/s = k
s = (z + re)/k
k は256ビットを保証するためhash256を2回繰り返す
1. 署名(r, s)、署名対象のハッシュをz, 公開鍵をPとする
2. u = z/s, v = r/sを計算する
3. uG + vP = Rを計算する
4. Rのx座標がrと同じであれば署名は有効
ScriptPubKeyの形式がp2pkであるトランザクションアウトプット
ECDSAの署名を検証するには、メッセージz, 公開鍵P, 署名r, sが必要
p2pkでは、ビットコインは公開鍵に送られ、秘密鍵の所有者は署名を作成することによりビットコインをアンロックできる。
ScriptPubKeyはビットコインを秘密鍵の所有者の管理下に置く。
P は公開鍵(x,y) 256bit
e は秘密鍵
e.g.
ScriptPubKey
41 – length of pubkey
0411…a3 pubkey
ac – OP_CHECKSIG
ScriptSig
47 – length of signature
3044 .. 01 – signature
OP_CHECKSIG, pubkey + signature のコマンドセットにする
スタックの先頭エレメントがゼロ以外のときは、有効なscript sigとみなされる
stackにpubkey, signatureをpushし、OP_CHECKSIGで有効であればstackに1をpush, 有効でなければ0をpushする
0以外であれば処理を終了する
0の場合は、スクリプトは無効となり、トランザクション自体が無効になる
秘密鍵を知っている人だけが有効なScriptSigを作り出すことができる
各コマンドがオプコードかスタックにプッシュされるオプコードかを判定する
1~77まではエレメントでそれ以上(78以上)がオプコードとみなす
class Script:
def __init__(self, cmds=None):
if cmds is None:
self.cmds = []
else:
self.cmds = cmds
// omission
@classmethod
def parse(cls, s):
length = read_varint(s)
cmds = []
count = 0
while count < length:
current = s.read(1)
count += 1
current_byte = current[0]
if current_byte >= 1 and current_byte <= 75:
n = current_byte
cmds.append(s.read(n))
count += n
elif current_byte == 76:
data_length = little_endian_to_int(s.read(1))
count += data_length + 1
elif current_byte == 77:
data_length = little_endian_to_int(s.read(2))
cmds.append(s.read(data_length))
count += data_length + 2
else:
op_code = current_byte
cmds.append(op_code)
if count != length:
raise SyntaxError('parsing script failed')
return cls(cmds)
### シリアライズ
整数の場合はオプコード、それ以外はエレメント
class Script:
//
def raw_serialize(self):
result = b''
for cmd in self.cmds:
if type(cmd) == int:
result += into_to_little_endian(cmd, 1)
else:
length = len(cmd)
if length < 75:
result += int_to_little_endian(length, 1)
elif length > 75 and length < 0x100:
result += int_to_little_endian(76, 1)
result += int_to_little_endian(length, 1)
elif length >= 0x100 and length <= 520:
result += int_to_little_endian(77, 1)
result += int_to_little_endian(length, 2)
else:
raise ValueError('too long an cmd')
result += cmd
return result
def serialize(self):
result = self.raw_serialize()
total = len(result)
return encode_varint(total) + result
### Script Fieldの連結
scriptオブジェクトは評価が必要なコマンドのセットを表す
スクリプを評価するにあたり、ロックボックスのScriptPubKey(outputsの2番目)とアンロックするScriptSig(inputsの3番目)のフィールドを連結する必要がある。
ScriptPubKeyはPrevious Transaction, ScriptSigはCurrent Transaction
ScriptSigがScriptPubKeyをunlockする
ScriptSigからのコマンドはScriptPubKeyのコマンドの上に配置し、処理するコマンドがなくなるまで1つづつ処理される
class Script:
def __add__(self, other):
return Script(self.cmds + other.cmds)
標準スクリプト(Base58, Bech32)
p2pk, p2pkh, p2sh, p2wpkh, p2wsh
ウォレットは様々なタイプを解釈する方法を知っており、適切なScriptPubKeyを作成する
ScriptPubKeyとScriptSigはどちらも同じ方法でパースされる
バイトが0x01から0x4b(10進数の75)の間の場合(nと呼ぶ)、次のnバイトを1つのエレメントとして読み取る。その範囲の値ではないときは、そのバイトはオペレーションを表している
0x00 – OP_0
0x51 – OP_1
0x60 – OP_16
0x76 – OP_DUP
0x93 – OP_ADD
0xa9 – OP_HASH160
0xac – OP_CHECHSIG
0x4bより長いエレメントの場合は、特別なオプコードOP_PUSHDATA1(76-255), OP_PUSHDATA2(256-520), OP_PUSHDATA4がある。
エレメントはデータのことで、signature, pubkeyなど
オペレーションはデータに対して操作をする OP_CHECKSIG, OP_DUP, OP_HASH160
OP_DUPはスタックの先頭エレメントを複製し、新しく生成したエレメントにpush
重要なオペレーションはOP_CHECKSIG
stackから pubkey, signatureをpopし、その署名が公開鍵に対して有効であるか確認する。有効な場合、OP_CHECHSIGは1をスタックにpush
op_dup 118(10進数) -> 0x76(16進数)
def op_dup(stack):
if len(stack) < 1:
return False
stack.append(stack[-1])
return True
hash256 170(10進数) -> 0xaa(16進数)
def op_hash256(stack):
if len(stack) < 1:
return False
element = stack.pop()
stack.append(hash256(element))
return True
hash160だと同様に以下のようになる。
def op_hash160(stack):
if len(stack) < 1:
return False
element = stack.pop()
stack.append(hash160(element))
return True
class MyStack:
def __init__(self):
self.stack = []
def push(self, item):
self.stack.append(item)
def pop(self):
pass
mystack = MyStack()
mystack.push(0)
mystack.push(1)
mystack.push(2)
print(mystack.stack)
$ python3 test.py
[0, 1, 2]
pop
def pop(self):
result = self.stack[-1]
del self.stack[-1]
return result
print(mystack.pop())
print(mystack.pop())
取り出して削除なので、[-1]の後にdelを実行している。
上記と同じことをpop()と書くことができる。
def pop(self):
return self.stack.pop()
stackが0の場合があるので、例外処理も書かなければならない。
def pop(self):
if len(self.stack) == 0:
return None
return self.stack.pop()
トランザクションの手数料はinputsからoutputsを引いたもの
マイナーへの報酬となるトランザクション手数料のため、inputsはoutputsの合計以上の額にならなければならない。
class TxFetcher:
cache = {}
@classmethod
def get_url(cls, testnet=False):
if testnet:
return ''
else:
return ''
@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())
except 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 {}'.format(tx.id(), tx_id))
cls.cache[tx_id] = tx
cls.cache[tx_id].testnet = testnet
return cls.cache[tx_id]
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=test)
return tx.tx_outs[self.prev_index].script_pubkey
transaction idで特定し、前のトランザクションindexから対象outputのamout, script_pubkeyを取得している
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
class TxOut:
def serialize(self):
result = int_to_little_endian(self.amount, 8)
result += self.script_pubkey.serialize()
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 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
locktimeはトランザクションを遅延させる方法。600,000を指定した場合、ブロック600,001までブロックチェーンに入れることができない。500,000,000以上の場合はUnixのタイムスタンプ
シリアライズされた値は4バイトのリトルエンディアン
@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_outs = read_varint(s)
outputs = []
for _ in range(num_outputs):
outputs.append(TxOut.parse(s))
locktime = little_endian_to_int(s.read(4))
return cls(version, inputs, outputs, locktime, testnet=testnet)
以下の方法でトランザクションからscriptsig, scriptpubkey, amountなどをparseできるようになる。
from io import BytesIO from tx import Tx 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 main.py
304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a71601 035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937
OP_DUP OP_HASH160 ab0c0b2e98b1ab6dbf67d4750b0a56244948a879 OP_EQUALVERIFY OP_CHECKSIG
40000000