Base58 decode

Base58decodeはBase58encodeの逆の処理を行う

import sys
import re

matrix=list('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz')

pattern=r"^1*"

for line in sys.stdin:
    target=line.strip('\n')
    match=re.match(pattern, target)
    ones=match.group()
    target=target.lstrip(ones)
    targetChars=list(target)
    q=0
    for c in targetChars:
        q=q*58+matrix.index(c)
    decodedHex=str(hex(q)).lstrip('0x')
    decodedHex=ones.replace('1','00') + decodedHex
    hexlen=len(decodedHex)
    if hexlen % 2 == 1:
        print('0'+decodedHex)
    else:
        print(decodedHex)

$ echo 18N | python3 base58decode.py
0001ab

Base58 encode

1. 対象データの先頭に0が続く場合、0以外になるまでそのバイトを除外(e.g. 最初の3バイトが全て0の場合は、先頭の3バイトを除外)
2. 1の結果を58で割る
3. 剰余をBase58に変換
4. 変換された剰余を連結し、1で除外した0の数だけ先頭に1を連結

import sys
import re

matrix = list('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz')

pattern=r"^(00)*"

for line in sys.stdin:
    target = line.rstrip('\n')
    match = re.match(pattern, target)
    zeros=match.group()
    target = target.lstrip(zeros)
    q = int(target, 16)
    r = 0
    b58str = ''
    while q > 0:
        q, r = divmod(q, 58)
        b58str = matrix[r] + b58str

    print(zeros.replace('00', '1') + b58str)

$ echo 0001ab | python3 base58encode.py
18N

[bitcoin] p2sh-p2wsh

p2sh-p2wshは、p2sh-p2wpkhと同様にp2wshに後方互換性を持たせる方法

BIP0141以前
01000000 – version
01 – # of inputs
7082…54 – previous tx hash
00000000 – previous tx index
2322…4c – ScriptSig
ffffffff – sequence
01 – # of outputs
603e…00 – output amount
1600…22 ScriptPubKey
00000000 – locktime

BIP0141以降
01000000 – version
00 – Segwit marker
01 – Segwit flag
01 – # of inputs
7082…54 – previous tx hash
01000000 – previous tx index
2322…4c – ScriptSig
ffffffff – sequence
01 – # of outputs
603e…00 – output amount
1600…22 ScriptPubKey
0400…ae – witness
00000000 – locktime

OP_0, 32-byte hash
WitnessScriptはスクリプトコマンドとして解釈されコマンドセットに配置される
OP_0, signature1, signature2, OP_2, pubkey1, pubkey2, pubkey3, OP_3, OP_CHECKMULTISIG

[bitcoin] Pay-to-witness-script-hash(p2wsh)

BIP0141以前
01000000 – version
01 – # of inputs
593a…98 – previous tx hash
00000000 – previous tx index
00 – ScriptSig
ffffffff – sequence
01 – # of outputs
806f…00 …output amount
1976…ac ScriptPubKey
00000000 – locktime

BIP0141以降
01000000 – version
00 – Segwit marker
01 – Segwit flag
01 – # of inputs
593a…98 – previous tx hash
00000000 – previous tx index
00 – ScriptSig
ffffffff – sequence
01 – # of outputs
806f…00 …output amount
1976…ac ScriptPubKey
0400…ac – witness
00000000 – locktime

p2wshスクリプトのScriptPubKeyは OP_0 <32 byte hash>
scriptSigはp2wpkhと同様に空

p2wshのwitnessフィールドは2of3のmulti sig
04 – Number of witness elements
00 – OP_0
47 – Length of
3044…01 –
48 – Length of
3045…01 –
69 – Length of witnessScript
5221…ae – WitnessScript

WitnessScriptはsha256でハッシュすると、ScriptPubKeyにある32bytes hashと一致する
=> Lightning Network用の双方向ペイメントチャネル作成にマリアビリティがないマルチシグトランザクション

[bitcoin] p2sh-p2wpkh

p2wpkhはBIP0173で定義されているBech32というアドレス形式を使用する
p2shでp2wpkhをラップする… Segwitスクリプトがp2shのRedeemScriptでネストされる

BIP0141以前
01000000 – version
01 – # of inputs
712e…2f – previous tx hash
00000000 – previous tx index
1715…96 – ScriptSig
feffffff – sequence
02 – # of outputs
3236…00 output amount
1976…ac ScriptPubKey
075a0700 – locktime

BIP0141以降
01000000 – version
00 – Segwit marker
01 – Segwit flag
01 – # of inputs
712e…2f – previous tx hash
00000000 – previous tx index
1716…96 – ScriptSig
feffffff – sequence
02 – # of outputs
3236…00 output amount
1976…ac ScriptPubKey
0248…61 – witness
075a0700 – locktime

p2wpkhとの違いは、ScriptSigが空ではないこと, RedeemScriptがある

    @classmethod
    def parse(cls, s, testnet=False):
        s.read(4)
        if s.read(1) == b'\x00':
            parse_method = cls.parse_segwit
        else:
            parse_method = cls.parse_legacy
        s.seek(-5, 1)
        return parse_method(s, testnet=testnet)
    
    @classmethod
    def parse_legacy(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_outputs = 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, segwit=False)

    @classmethod
    def parse_segwit(cls, s, testnet=False):
        version = little_endian_to_int(s.read(4))
        marker = s.read(2)
        if marker != b'\x00\x01':
            raise RuntimeError('Not a segwit transaction {}'.format(marker))
        num_inputs = read_varint(s)
        inputs = []
        for _ in range(num_inputs):
            inputs.append(TxIn.parse(s))
        num_outputs = read_varint(s)
        outputs = []
        for _ in range(num_outputs):
            outputs.append(TxOut.parse(s))
        for tx_in in inputs:
            num_items = read_varint(s)
            items = []
            for _ in range(num_items):
                item_len = read_varint(s)
                if item_len == 0:
                    items.append(0)
                else:
                    items.append(s.read(item_len))
            tx_in.witness = items
        locktime = little_endian_to_int(s.read(4))
        return cls(version, inputs, outputs, locktime, testnet=testnet, segwit=True)

    def serialize(self):
        if self.segwit:
            return self.serialize_segwit()
        else:
            return self.serialize_legacy()

    def serialize_legacy(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   

    def serialize_segwit(self):
        result = int_to_little_endian(self.version, 4)
        result += b'\x00\x01'
        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()
        for tx_in in self.tx_ins:
            result += int_to_little_endian(len(tx_in.witness), 1)
            for item in tx_in.witness:
                if type(item) == int:
                    result += int_to_little_endian(item, 1)
                else:
                    result += encode_varint(len(item)) + item
        result += int_to_little_endian(self.locktime, 4)
        return result

 //

    def sig_hash_bip143(self, input_index, redeem_script=None, witness_script=None):
        tx_in = self.tx_ins[input_index]
        s = int_to_little_endian(self.version, 4)
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if witness_script:
            script_code = witness_script.serialize()
        elif redeem_script:
            script_code = p2pkh_script(redeem_script.cmds[1]).serialize()
        else:
            script_code = p2pkh_script(tx_in.script_pubkey(self.testnet).cmds[1]).serialize()
        s += script_code
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(SIGHASH_ALL, 4)
        return int.from_bytes(hash256(s), 'big')

[bitcoin] Segwit p2wpkh

SegwitはSegregated Witnessの略語
Segwitには多数の変更が取り入れられている
– ブロックサイズの増加
– トランザクションマリアビリティの解消
– 明確なアップグレードパスのためのSegwitバージョン管理
– 二次ハッシュの修正
– オフラインウォレット手数料計算のセキュリティ

Segwitトランザクションの最も基本タイプはPay to witness pubkey hash

### p2wpkh
BIP0141, BIP0143で定義されたスクリプトの一つ
p2wpkhは、p2pkhと似ているが、ScriptSigのデータがwitnessフィールドに移動した
マリアビリティとは、取引の内容を変更せずにトランザクションIDを変更できてしまう問題

01000000 – version
00 – Segwit marker
01 – Segwit flag
01 – # of inputs
15e1…56 – previous tx hash
01000000 – previous tx index
00 – ScriptSig
ffffffff – sequence
01 – # of outputs
00b4…output amount
1976…ac ScriptPubKey
0248…ac – witness
00000000 – locktime

marker, flag, witnessのフィールドがある
トランザクションIDの計算に前者のシリアライズが用いられる
witnessフィールドには署名と公開鍵の要素がある
p2wpkhのscriptPubKeyは OP_0 20byte hash

p2wpkhの場合、スクリプトシーケンス OP_0 <20-byte hash>が検出されると、witnessフィールドから公開鍵と署名、20bytesハッシュがp2pkhと同様のシーケンスでコマンドに追加される。
stackの処理はp2pkhとほぼ同じ

[bitcoin] ブルームフィルターの読み込み

SPVはブルームフィルターを作成したら、フィルターの詳細をフルノードに伝える必要がある。
SPVはversionメッセージのオプションにあるリレーフラグを0にする。
その後、フルノードにブルームフィルターを伝える。設定コマンドはfilterload

0a4…0940: bit field, variable field
05000000: Hash count, 4bytes, little endian
63000000: Tweak, 4bytes, little-endian
00: Matched item flag

    def filterload(self, flag=1):
        payload = encode_varint(self.size)
        payload += self.filter_bytes()
        payload += little_endian_to_int(self.function_count, 4)
        payload += little_endian_to_int(self.tweak, 4)
        payload += little_endian_to_int(flag, 1)
        return GenericMessage(b'filterload', payload)

### マークルブロックの取得
getdataでブルームフィルターを通過するトランザクションを要求する
02: Number of data items(varint)
03000000: Type of data item(tx, block, filtered block, compact block), little endian
30…00: Hash identifier

class GetDataMessage:
    command = b'getdata'

    def __int__(self):
        self.data = []

    def add_data(self, data_type, identifier):
        self.data.append((data_type, identifier))

    def serialize(self, identifier):
        result = encode_varint(len(self.data))
        for data_type, identifier in self.data:
            result += int_to_little_endian(data_type, 4)
            result += identifier[::-1]
        return result

[bitcoin] BIP0037ブルームフィルター

Bitcoinで使用されるハッシュ関数はmurmur3と呼ばれ、暗号学的に安全ではないが高速
以下のように定義される
i * 0xfba4c795 + tweak

murmur3

def murmur3(data, seed=0):
    c1 = 0xcc9e2d51
    c2 = 0x1b873593
    length = len(data)
    h1 = seed
    roundedEnd = (length & 0xfffffffc)
    for i in range(0, roundedEnd, 4):
        k1 = (data[i] & 0xff) | ((data[i + 1] & 0xff) << 8) | \
            ((data[i + 2] & 0xff) << 16) | (data[i + 3] << 24)
        k1 *= c1
        k1 = (k1 << 15) | ((k1 & 0xffffffff) >> 17)
        k1 *= c2
        h1 ^= k1
        h1 = (h1 << 13) | ((h1 & 0xffffffff) >> 19)
        h1 = h1 * 5 +  0xe6546b64
    k1 = 0
    val = length & 0x03
    if val == 3:
        k1 = (data[roundedEnd + 2] & 0xff) << 16
    if val in [2, 3]:
        k1 |= (data[roundedEnd + 1] & 0xff) << 8
    if val in [1, 2, 3]:
        k1 |= data[roundedEnd] & 0xff
        k1 *= c1
        k1 = (k1 << 15) | ((k1 & 0xffffffff) >> 17)
        k1 *= c2
        h1 ^= k1
    h1 ^= length
    h1 ^= ((h1 & 0xffffffff) >> 16)
    h1 *= 0x85ebca6b
    h1 ^= ((h1 & 0xffffffff) >> 13)
    h1 *= 0xc2b2ae35
    h1 ^= ((h1 & 0xffffffff) >> 16)
    return h1 & 0xffffffff

BIP37_CONSTANT = 0xfba4c795

from bloomfileter import BloomFilter, BIP37_CONSTANT
from helper import bit_field_to_bytes, murmur3

field_size = 10
function_count = 5
tweak = 99
items = (b'Hello World', b'Goodbye!')
bit_field_size = field_size * 8
bit_field = [0] * bit_field_size
for item in items:
    for i in range(function_count):
        seed = i * BIP37_CONSTANT + tweak
        h = murmur3(item, seed=seed)
        bit = h % bit_field_size
        bit_field[bit] = 1
print(bit_field_to_bytes(bit_field).hex())
    def add(self, item):
        for i in range(self.function_count):
            seed = i * BIP37_CONSTANT + self.tweak
            h = murmur3(item, seed=seed)
            bit = h % (self.size * 8)
            self.bit_field[bit] = 1

[bitcoin] ブルームフィルター

対象となるすべてのトランザクションのスーパーセットを作成するのに十分な情報を、軽量クライアントSPVがフルノードに伝える。このスーパーセットを作成するのに、ブルームフィルターを使用する

### ブルームフィルターとは?
対象となり得るすべてのトランザクションを取り出すフィルター
フルノードはブルームフィルターを介してトランザクションを取り扱い、通過するトランザクションに関し、merkleblockコマンドを送信する
=> ハッシュ関数を使用して決定的に数を取得し、モジュロを使用してトランザクションを各入れ物に整理する

集合内の任意のデータに使用可能なコンピュータサイエンスにおける構造
L ビッグエンディアンの整数として解釈し、bit_field_sizeのモジュるを取る

from helper import hash256
bit_field_size = 10
bit_field = [0] * bit_field_size
h = hash256(b'hello world')
bit = int.from_bytes(h, 'big') % bit_field_size
bit_field[bit] = 1
print(bit_field)

$ python3 test.py
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

ブルームフィルターの構成要素は
– ビットフィールドのサイズ
– 使用されるハッシュ関数
– 対象の格納先を示すビットフィールド

複数のアイテムを持つブルームフィルター

from helper import hash256
bit_field_size = 10
bit_field = [0] * bit_field_size
for item in (b'hello world', b'goodbye'):
    h = hash256(item)
    bit = int.from_bytes(h, 'big') % bit_field_size
    bit_field[bit] = 1
print(bit_field)

ハッシュ関数をh160にすると

from helper import hash256, hash160
bit_field_size = 10
bit_field = [0] * bit_field_size
for item in (b'hello world', b'goodbye'):
    h = hash160(item)
    bit = int.from_bytes(h, 'big') % bit_field_size
    bit_field[bit] = 1
print(bit_field)

$ python3 test.py
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0]

複数のブルームフィルターをビットフィールドを大幅に短縮できる

from helper import hash256, hash160
bit_field_size = 10
bit_field = [0] * bit_field_size
for item in (b'hello world', b'goodbye'):
    for hash_function in (hash256, hash160):
        h = hash_function(item)
        bit = int.from_bytes(h, 'big') % bit_field_size
        bit_field[bit] = 1
print(bit_field)

$ python3 test.py
[1, 1, 1, 0, 0, 0, 0, 0, 0, 1]

[bitcoin] merkleblock

merkleblockの最初の6つ(version, previous block, merkle root, timestamp, bit, nonce)はブロックヘッダーと全く同じ
number of total transaction, number of hashes, hashes, flag bitsが異なる

merkleblockのparse

    @classmethod
    def parse(cls, s):
        version = little_endian_to_int(s.read(4))
        prev_block = s.read(32)[::-1]
        merkle_root = s.read(32)[::-1]
        timestamp = little_endian_to_int(s.read(4))
        bits = s.read(4)
        nonce = s.read(4)
        total = little_endian_to_int(s.read(4))
        num_hashes = read_varint(s)
        hashes = []
        for _ in range(num_hashes):
            hashes.append(s.read(32)[::-1])
        flags_length = read_varint(s)
        flags = s.read(flag_length)
        return cls(version, prev_block, merkle_root, timestamp, bits, nonce, total, hashes, flags)

### フラグビットとハッシュの使用
1. ノード値がハッシュフィールドで指定されている場合、フラグビットは0
2. ノードが内部ノーで、軽量クイライアンとによって計算されている場合、フラッグビットは1
3. ノードがリーフノードで、対象のトランザクションである場合、フラグは1でノード値もハッシュフィールドで指定される