op_checksigの実装

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

DER署名

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

Python bytearray()

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 の整数

scriptのchecksigによる評価

evaluateにより、script_sigとscript_pubkeyの検証を行いたい
Scriptの第二引数である0xac(172)はOP_CHECKSIG

from script import Script
z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
sec = bytes.fromhex('04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e\
4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34')
sig = bytes.fromhex('3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f4048\
76325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fd\
dbdce6feab601')
script_pubkey = Script([sec, 0xac])
script_sig = Script([sig])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(z))
    def evaluate(self, z):
        cmds = self.cmds[:]
        stack = []
        altstack = []
        while len(cmds) > 0:
            cmd = cmds.pop(0)
            if type(cmd) == int:
                operation = OP_CODE_FUNCTIONS[cmd]
                if cmd in (99, 100):
                    if not operation(stack, cmds):
                        LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
                        return False
                elif cmd in (107, 108):
                    if not operation(stack, altstack):
                        LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
                        return False
                elif cmd in (172, 173, 174, 175):
                    if not operation(stack, z):
                        LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
                        return False
                else:
                    if not operation(stack):
                        LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
                        return False
            else:
                stack.append(cmd)
        if len(stack) == 0:
            return False
        if statck.pop() == b'':
            return False
        return True

99: op_if,
100: op_notif,

op_if: If the top stack value is not False, the statements are executed. The top stack value is removed.

def op_if(stack, items):
    if len(stack) < 1:
        return False
    true_items = []
    false_itmes = []
    current_array = true_items
    found = False
    num_endifs_needed = 1
    while len(items) > 0:
        item = items.pop(0)
        if item in (99, 100):
            num_endifs_needed += 1
            current_array.append(item)
        elif num_endifs_needed == 1 and item == 103:
            current_array = false_items
        elif item == 104:
            if num_endifs_needed == 1:
                found = True
                break
            else:
                num_endifs_neede -= 1
                current_array.append(item)
        else:
            current_array.append(item)
    if not found:
        return False
    element = stack.pop()
    if decode_num(element) == 0:
        items[:0] = false_items
    else:
        items[:0] = true_items
    return True

op_notif: If the top stack value is False, the statements are executed. The top stack value is removed.

def op_notif(stack, items):
    if len(stack) < 1:
        return False
    true_items = []
    false_items = []
    current_array = true_items
    found = False
    num_endifs_needed = 1
    while len(items) > 0:
        item = items.pop(0)
        if item in (99, 100):
            num_endifs_needed += 1
            current_array.append(item)
        elif num_endifs_needed == 1 and item == 103:
            current_array = false_items
        elif item == 104:
            if num_endifs_needed == 1:
                found = True
                break
            else:
                num_endifs_needed -= 1
                current_array.append(item)
        else:
            current_array.append(item)
    if not found:
        return False
    element = stack.pop()
    if decode_num(element) == 0:
        items[:0] = true_items
    else:
        items[:0] = false_items
    return True

—–
107: op_toaltstack,
108: op_fromaltstack,

op_toaltstack:

def op_toaltstack(stack, altstack):
    if len(stack) < 1:
        return False
    altstack.append(stack.pop())
    return True

op_fromaltstack

def op_fromaltstack(stack, altstack):
    if len(altstack) < 1:
        return False
    stack.append(alstack.pop())
    return True

172: op_checksig,
173: op_checksigverify,
174: op_checkmultisig,
175: op_checkmultisigverify,

0xac(172)のOP_CHECKSIGが非常に重要

Python 関数を数字(数字コード)で実行する方法

def OP_NOP():
    print("This is OP_NOP, 76")

def OP_IF():
    print("This is OP_IF, 99")

def OP_NOTIF():
    print("This is OP_NOTIF, 100")

OP_CODE = {
    76: OP_NOP,
    99: OP_IF,
    100: OP_NOTIF
}

func = OP_CODE[76]
func()

$ python3 test.py
This is OP_NOP, 76

bitcoinのOPcode一覧はこちら
https://en.bitcoin.it/wiki/Script

めちゃくちゃお洒落な書き方だな~

Python if not condition

a = True

if not a:
    print('a is false')

in String

a = ''

if not a:
    print('a is empty')
def mul(num):
    if type(num) == int:
        print(num * 2)
        return True
    else:
        return False

n = 'a'
if not mul(n):
    print('please input number')

if notは基本的にTrue or Falseを判定している

PythonのLogging

シンプルなログ出力はお馴染みのprint文

print("I am the simplest log.")

ファイル出力、メール送信、HTTP通信など動作ログをよりフレキシブルに出力したい場合にはloggingモジュールを使用してログを出力する。

### ログレベル
DEBUG, INFO, WARNING, ERROR, CRITICAL

出力

import logging 

logging.info("info log")
logging.warning("warning log")

$ python3 test.py
WARNING:root:warning log

info はコマンドラインに出力されない
loggerオブジェクトを取得してログを出力する

import logging 

logger = logging.getLogger("sample")
logger.info("info log")
logger.warning("warning log")

$ python3 test.py
warning log

import logging 

logger = logging.getLogger("sample")
logger.setLevel(logging.DEBUG)
logger.info("info log")
logger.warning("warning log")

### ハンドラ
StreamHandler, FileHandler, SMTPHandler, HTTPHandlerがある

import logging 

logger = logging.getLogger("sample")
logger.setLevel(logging.DEBUG)


st_handler = logging.StreamHandler()

logger.addHandler(st_handler)
logger.info("info log")
logger.warning("warning log")

handlerで指定することでログ出力をカスタマイズできる。