ブロックヘッダーの取得とヘッダーレスポンス

どのノードも初めてネットワークに接続する際、取得して検証すべき最も重要なデータはブロックヘッダーである。
ブロックヘッダーをダウンロード数ルト、複数のノードに完全なブロック非同期に要求してブロックのダウンロードを並列化できる
ブロックヘッダーを取得するコマンドはgetheadersと呼ばれる

7f110100: Protocol version, 4bytes, little-endian, 70015
01: Number of hashes, varint
a35b…00: Starting block, little-endian
0000…00: Ending block, little-endian

class GetHeaderMessage:
    command = b'getheaders'

    def __init__(self, version=70015, num_hashes=1,
        start_block=None, end_block=None):
        self.version = version
        self.num_hashes = num_hashes
        if start_block is None:
            raise RuntimeError('a start block is required')
        self.start_block = start_block
        if end_block is None:
            self.end_block = b'\x00' * 32
        else:
            self.end_block = end_block

    def serialize(self):
        result = int_to_little_endian(self.version, 4)
        result += encode_varint(self.num_hashes)
        result += self.start_block[::-1]
        result += self.end_block[::-1]
        return result

### ヘッダーレスポンス
ノードを作成し、ハンドシェイクした後、ヘッダーを要求できる

from io import BytesIO
from block import Block, GENESIS_BLOCK
from network import SimpleNode, GetHeadersMessage
node = SimpleNone('', testnet=False)
node.handshake()
genesis = Block.parse(BytesIO(GENESIS_BLOCK))
getheaders = GetHeadersMessage(start_block=genesis.hash())
node.send(getheaders)

## headrsメッセージ
02: Number of block headers
00…67: Block header
00: Number of transactions (always 0)

headersメッセージはvarintのヘッダー数(1~2000)
各ブロックヘッダーは80bytes

class HeadersMessage:
    command = b'headers'

    def __init__(self, blocks):
        self.blocks = blocks

    @classmethod
    def parse(cls, stream):
        num_headers = read_varint(stream)
        blocks = []
        for _ in range(num_headers):
            blocks.append(Block.parse(stream))
            num_txs = read_varint(stream)
            if num_txs != 0:
                raise RuntimeError('number of txs not 0')
        return cls(blocks)
from io import BytesIO
from network import SimpleNode, GetHeaderMessage, HeadersMessage
from block import Block, GENESIS_BLOCK, LOWEST_BITS
from helper import calculate_new_bits
previous = Block.parse(BytesIO(GENESIS_BLOCK))
first_epoch_timestamp = previous.timestamp
expected_bits = LOWEST_BITS
count = 1
node = SimpleNode('', testnet=False)
node.handshake()
for _ in range(19):
    getheaders = GetHeadersMessage(start_block=previous.hash())
    node.send(getheaders)
    headers = node.wait_for(HeadersMessage)
    for header in headers.blocks:
        if not header.check_pow():
            raise RuntimeError('bad PoW at block {}'.format(count))
        if header.prev_block != previous.hash():
            raise RuntimeError('discontinuous block at {}'.format(count))
        if count % 2016 == 0:
            time_diff = previous.timestamp - first_epoch_timestamp
            expected_bits = calculate_new_bits(previous.bits, time_diff)
            print(expected_bits.hex())
            first_epoch_timestamp = header.timestamp
        if header.bits != expected_bits:
            raise RuntimeError('bad bits at block {}'.format(count))
        previous = header
        count += 1

VerAckMessageは最小のネットワークメッセージ。AckはAcknoldgeの略称で確認の意味
VerAckMessage :The verack message acknowledges a previously-received version message, informing the connecting node that it can begin to send other messages.
※https://bitcoin-s.org/api/org/bitcoins/core/p2p/index.html

ネットワークハンドシェイクと接続

### ネットワークハンドシェイク
ネットワークハンドシェイクの方法
– AがBに接続するためにversionメッセージを送信
– Bがversionメッセージを受信し、verackメッセージで応答し、自身のversionメッセージを送信
※verackはボディー(ペイロード)が無く、メッセージ名だけのメッセージ
– Aはversionメッセージとverackメッセージを受信し、verackメッセージを送信
– Bはverackメッセージを受信し、通信を継続
ハンドシェイク後に通信できる
不正なトランザクションまたはブロックを送信した場合、通信が禁止または切断される可能性がある

### ネットワークへの接続

import socket
from network immport NetworkEnvelop, VersionMessage
host = ''
port = 18333
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect((host, port))
stream = socket.makefile('rb', None)
version = VersionMessage()
envelop = NetworkEnvelop(version.command, version.serialize())
socket.sendall(envelop.serialize())
while True:
    new_message = NetworkEnvelope.parse(stream)
    print(new_message)
class VerAckMessage:
    command = b'verack'

    def __init__(self):
        pass
    
    @classmethod
    def parse(cls, s):
        return cls()
    
    def serialize(self):
        return b''

SimpleNone

class SimpleNone:

    def __init__(self, host, port=None, testnet=False, logging=False):
        if port is None:
            if testnet:
                port = 18333
            else:
                port = 8333
        self.testnet = testnet
        self.logging = logging
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((host, port))
        self.stream = self.socket.makefile('rb', None)
    
    def send(self, message):
        envelope = NetworkEnvelop(
            message.command, message.serialize(), testnet=self.testnet)
        if self.logging:
            print('sending: {}'.format(envelop))
        self.socket.sendall(envelope.serialize())
    
    def read(self):
        envelop = NetworkEnvelop.parse(self.stream, testnet=self.testnet)
        if self.logging:
            print('receiving: {}'.format(envelop))
        return envelope

    def wait_for(self, *message_classes):
        command = None
        command_to_class = {m.command: m for m in message_classes}
        while command not in command_to_class.keys():
            envelope = self.read()
            command = envelop.command
            if command == VersionMessage.command:
                self.send(VerAckMessage())
            elif command == PingMessage.command:
                self.send(PongMessage(envelope.payload))
        return command_to_class[command].parse(envelope.stream())

他のNodeとのハンドシェイク

from network import SimpleNode, VersionMessage
node = SimpleNone('', testnet=True)
version = VersionMessage()
node.send(version)
verack_received = False
version_received = False
while not verack_received and not version_received:
    message = node.wait_for(VersionMessage, VerArckMessage)
    if message.command == VerArckMessage.command:
        verack_received = True
    else:
        version_received = True
        node.sed(VerAckMessage())

handshake

   def handshake(self):
        version = VersionMessage()
        node.send(version)
        self.wait_for(VerAckMessage)

ペイロード

ペイロードとはメッセージを送受信するデータ本体

Protocol version: 4bytes, little-endian
Network services of sender: 8bytes, little-endian
Timestamp, 8bytes, little-endian
Network service of receiver: 8bytes, little-endian
Network address of receiver, 16bytes, IPv4

Network port of receiver: 2bytes
Network services of sender: 8bytes, little-endian
Network address of sender: 16bytes, IPv4

Network port of sender: 2bytes
Nonce: 8bytes, used for communicating response
User agent
Optional flag of relay, based on BIP37(ブルームフィルターで使用)

VersionMessageクラス

class VersionMessage:
    command = b'version'

    def __init__(self, version=70015, services=0, timestamp=None,
                receiver_services=0,
                receiver_ip=b'\x00\x00\x00\x00', receiver_port=8333,
                sender_services=0,
                sender_ip=b'\x00\x00\x00\x00', sender_port=8333,
                nonce=None, user_agent=b'/programmingbitcoin:0.1/',
                latest_block=0, relay=False):
        self.version = version
        self.service = service
        if timestamp is None:
            self.timestamp = int(time.time())
        else:
            self.timestamp = timestamp
        self.receiver_services = receiver_services
        self.receiver_ip = receiver_ip
        self.receiver_port = receiver_port
        self.sender_services = sender_services
        self.sender_ip = sender_ip
        self.sender_port = sender_port
        if nonce is None:
            self.nonce = int_to_little_endian(randint(0, 2**64), 8)
        else:
            self.nonce = nonce
        self.user_agent = user_agent
        self.latest_block = latest_block
        self.relay

serialize

    def serialize(self):
        result = int_to_little_endian(self.version, 4)
        result += int_to_little_endian(self.services, 8)
        result += int_to_little_endian(self.timestamp, 8)
        result += int_to_little_endian(self.receiver_services, 8)
        result += b'\x00' * 10 + b'\xff\xff' + self.receiver_ip
        result += self.receiver_port.to_bytes(2, 'big')
        result += int_to_little_endian(self.sender_services, 8)
        result += b'\x00' * 10 + b'\xff\xff' + self.sender_ip
        result += self.sender_port.to_bytes(2, 'big')
        result += self.nonce
        result += encode_varint(len(self.user_agent))
        result += self.user_agent
        result += int_to_little_endian(self.latest_block, 4)
        if self.relay:
            result += b'\x01'
        else:
            result += b'\x00'
        return result

p2p

ネットワークプロトコルを使用してブロックヘッダーを要求、受信、検証している
最初の4bytesは常に同じでネットワークマジックと呼ばれる(通信が中断した場合などに再開の目印になる)
Litecoinノードとbitcoinでは異なるマジックバイト
L bitcoin: 0b110907(testnet)
L bitcoin: f9beb4d9(mainnet)

ネットワークメッセージ
f9beb4d9 – network magic
76657273696f6e00000000 – command
65000000 payload length, 4bytes, little-endian
5f1a69d2 – payload checksum, first 4 bytes of hash256 of payload
7211…01 – payload

bitcoin Protocol documentation
https://en.bitcoin.it/wiki/Protocol_documentation

NETWORK_MAGIC = b'\xf9\xbe\xb4\xd9'
TESTNET_NETWORK_MAGIC = b'\x0b\x11\x09\x07'

class NetworkEnvelop:

    def __init__(self, command, payload, testnet=False):
        self.command = command
        self.palyad = payload
        if testnet:
            self.magic = TESTNET_NETWORK_MAGIC
        else:
            self.magic = NETWORK_MAGIC 

    def __repr__(self):
        return '{}: {}'.format(
            self.command.decode('ascii'),
            self.payload.hex(),
        )

parse

    @classmethod
    def parse(cls, s, testnet=False):
        magic = s.read(4)
        if magic == b'':
            raise IOError('Connection reset!')
        if testnet:
            expected_magic = TESTNET_NETWORK_MAGIC
        else:
            expected_magic = NETWORK_MAGIC
        if magic != expected_magic
            raise SyntaxError('magic is not right {} vs {}'.format(magic.hex(), expected_magic.hex()))
        command = s.read(12)
        command = command.strip(b'\x00')
        payload_length =  little_endian_to_int(s.read(4))
        checksum = s.read(4)
        payload = s.read(payload_length)
        calculated_checksum = hash256(payload)[:4]
        if calculated_checksum != checksum:
            raise IOError('checksum does not match')
        return cls(command, payload, testnet=testnet)

parse sample

from network import NetworkEnvelope
from io import BytesIO
message_hex = 'f9beb4d976657261636b000000000000000000005df6e0e2'
stream = BytesIO(bytes.fromhex(message_hex))
envelope = NetworkEnvelope.parse(stream)
print(envelope.command)
print(envelope.payload)

serialize

    def serialize(self):
        result = self.magic
        result += self.command + b'\x00' * (12 - len(self.command))
        result += int_to_little_endian(len(self.payload), 4)
        result += hash256(self.payload)[:4]
        result += self.payload
        return result

Proof of Work

from helper import hash256
block_id = hash256(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b4a7c5\
3b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c31\
57f961db38fd8b25be1e77a759e93c0118a4ffd71d'))[::-1]
print('{}'.format(block_id.hex()).zfill(64))

$ python3 test.py
0000000000000000007e9e4c586439b0cdbe13b1370bdd9435d76a644d047523

proof of workはビットコイン全てのブロックヘッダーのハッシュが一定のターゲットを下回らなければならないという要件
ターゲットはビットフィールドから計算される256ビットの数値
exponent(最後の1バイト)とcoefficient(残りの3バイト)
target = coefficient * 256^(exponent-3)

from helper import little_endian_to_int
bits = bytes.fromhex('e93c0118')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)
print('{:x}'.format(target).zfill(64))

$ python3 test.py
0000000000000000013ce9000000000000000000000000000000000000000000

有効なProof-of-workはブロックヘッダーハッシュでリトルエンディアン整数として解釈した時にターゲットを下回った時。

proof = little_endian_to_int(hash256(bytes.fromhex('020000208ec39428b17323\
fa0ddec8e887b4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d3957\
6821155e9c9e3f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
print(proof < target)
def bits_to_target(bits):
    exponent = bits[-1]
    coefficient = little_endian_to_int(bit[:-1])
    return coefficient * 256**(exponent - 3)

### difficulty
difficulty = 0xffff * 256 ** (0x1d – 3) / target

from helper import little_endian_to_int, hash256
bits = bytes.fromhex('e93c0118')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)
difficulty = 0xffff * 256**(0x1d - 3) / target
print(difficulty)
    def target(self):
        return bits_to_target(self.bits)

    def difficulty(self):
        lowest = 0xffff * 256**(0x1d - 3)
        return lowest / self.target

### Proof-of-Workが十分であることの確認
Proof-of-Workはブロックヘッダーのhash256を計算し、これをリトルエンディアン整数として解釈することで求める
これがターゲットより小さい場合、Proof-of-workは有効

    def check_pow(self):
        sha = hash256(self.serialize())
        proof = little_endian_to_int(sha)
        return proof < self.target

### ディフィカルティ調整(difficulty adjustment period)
2016ブロックごとに変更
new_target = previous_target * time_differential/(2週間)

from io import BytesIO
from block import Block
from helper import TWO_WEEKS
last_block = Block.parse(BytesIO(bytes.fromhex('00000020fdf740b0e49cf75bb3\
d5168fb3586f7613dcc5cd89675b0100000000000000002e37b144c0baced07eb7e7b64da916cd\
3121f2427005551aeb0ec6a6402ac7d7f0e4235954d801187f5da9f5')))
first_block = Block.parse(BytesIO(bytes.fromhex('000000201ecd89664fd205a37\
566e694269ed76e425803003628ab010000000000000000bfcade29d080d9aae8fd461254b0418\
05ae442749f2a40100440fc0e3d5868e55019345954d80118a1721b2e')))
time_differential = last_block.timestamp - first_block.timestamp
if time_differential > TWO_WEEKS * 4:
    time_differential = TWO_WEEKS * 4
if time_differential < TWO_WEEKS // 4:
    time_differential = TWO_WEEKS // 4
new_target = last_block.target() * time_differential // TWO_WEEKS
print('{:x}'.format(new_target).zfill(64))

$ python3 test.py
0000000000000000007615000000000000000000000000000000000000000000

最後の2015ブロックを見つけるのに8週間以上かかる場合は、difficultyを減らしすぎないようにする
最後の2015ブロックを見つけるのに3.5日より短い場合は、difficultyを増やしすぎないようにする

def target_to_bits(target):
    raw_bytes = target.to_bytes(32, 'big')
    raw_bytes = raw_bytes.lstrip(b'\x00')
    if raw_bytes[0] > 0x7f:
        exponent = len(raw_bytes) + 1
        coefficient = b'\x00' + raw_bytes[:2]
    else:
        exponent = len(raw_bytes)
        coefficient = raw_bytes[:3]
    new_bits = coefficient[::-1] + bytes([exponent])
    return new_bits

ディフィカルティ調整期間の最初のblock idと最後のblock id
Block 471744
000000203471101bbda3fe307664b3283a9ef0e97d9a38a7eacd88000000000000000000
10c8aba8479bbaa5e0848152fd3c2289ca50e1c3e58c9a4faaafbdf5803c5448ddb84559
7e8b0118e43a81d3

Block 473759
02000020f1472d9db4b563c35f97c428ac903f23b7fc055d1cfc26000000000000000000
b3f449fcbe1bc4cfbcb8283a0d2c037f961a3fdf2b8bedc144973735eea707e126425859
7e8b0118e5f00474

これから、新しいbitを計算する

from io import BytesIO
from block import Block
from helper import TWO_WEEKS, target_to_bits
last_block = Block.parse(BytesIO(bytes.fromhex('000000203471101bbda3fe307664b3283a9ef0e97d9a38a7eacd88000000000000000000\
10c8aba8479bbaa5e0848152fd3c2289ca50e1c3e58c9a4faaafbdf5803c5448ddb84559\
7e8b0118e43a81d3')))
first_block = Block.parse(BytesIO(bytes.fromhex('02000020f1472d9db4b563c35f97c428ac903f23b7fc055d1cfc26000000000000000000\
b3f449fcbe1bc4cfbcb8283a0d2c037f961a3fdf2b8bedc144973735eea707e126425859\
7e8b0118e5f00474')))
time_differential = last_block.timestamp - first_block.timestamp
if time_differential > TWO_WEEKS * 4:
    time_differential = TWO_WEEKS * 4
if time_differential < TWO_WEEKS // 4:
    time_differential = TWO_WEEKS // 4
new_target = last_block.target() * time_differential // TWO_WEEKS
new_bits = target_to_bits(new_target)
print(new_bits.hex())

$ python3 test.py
80df6217

def calculate_new_bits(previous_bits, time_differential):
    if time_differential > TWO_WEEKS * 4:
        time_differential = TWO_WEEKS * 4
    if time_differential < TWO_WEEKS // 4:
        time_differential = TWO_WEEKS // 4
    new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS
    return target_to_bits(new_target)

btcのブロック

### coinbase transaction
01000000 – version
01 – # of inputs
000…00 – previous tx hash
ffffffff – previous tx index
5e0…00 ScriptSig
ffffffff – sequence
01 – # of outputs
faf20b58…00 – output amount
1976…ac – p2pkh ScriptPubKey
00000000 – locktime

coinbase transactionのインプットは必ず1つになる。
その一つのインプットの前のトランザクションIDは32バイトの00
トランザクションインデックスはffffffff 出なければならない

### Txクラスのis_coinbaseメソッド

class Tx:
  //
      def is_coinbase(self):
        if len(self.tx_ins) != 1:
            return False
        first_input = self.tx_ins[0]
        if first_input.prev_tx != b'\x00' * 32:
            return False
        if first_input.prev_index != 0xffffffff:
            return False
        return True

### Coinbase TxのScriptSig
ScriptSigはマイニングした人によって設定される
2バイト以上、100バイト以下の制限がある

from io import BytesIO
from script import Script
stream = BytesIO(bytes.fromhex('4d04ffff001d0104455468652054696d6573203033\
2f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64\
206261696c6f757420666f722062616e6b73'))
s = Script.parse(stream)
print(s.cmds[2])

$ python3 test.py
b’The Times 03/Jan/2009 Chancellor on brink of second bailout for banks’

### BIP0034
Coinbase transactionのScriptSigの先頭要素を規定する
トランザクションIDの重複を防ぐため、BIP0034が作成されている
BIP0034はソフトフォークルールで、マイニングされたブロックの高さをコインベースのScriptSigの先頭要素に追加する
リトルエンディアン整数として解釈され、ブロックの高さと同じ

コインベーストランザクションから高さをパースする方法

from io import BytesIO
from script import Script
from helper import little_endian_to_int
stream = BytesIO(bytes.fromhex('5e03d71b07254d696e656420627920416e74506f6f\
6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d94028\
24ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00'))
script_sig = Script.parse(stream)
print(little_endian_to_int(script_sig.cmds[0]))
    def coinbase_height(self):
        if not self.is_coinbase():
            return None
        element = self.tx_ins[0].script_sig.cmds[0]
        return little_endian_to_int(element)

ScriptSigの先頭cmdsにCoinbase ブロックヘッダーの高さが入ってるのね。

### ブロックヘッダー
ブロックヘッダーは以下で構成されている
– Version、Previous Block, Merkle Root, Timestamp, Bits, Nonce
02000020 – version
8ec3…00 – previous block
5b07…be – merkle root
1e77a759 – timestamp
e93c0118 – bits
a4ffd71d – nonce

ブロックIDはヘッダーのhash256のリトルエンディアンの16進数表記

version, prev_block, merkle_root, timestampはlittle_endian, bitsとnonceはそのまま。

    @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)
        return cls(version, prev_block, merkle_root, timestamp, bits, nonce)

    def serialize(self):
        result = int_to_little_endian(self.version, 4)
        result += self.prev_block[::-1]
        result += self.merkle_root[::-1]
        result += int_to_little_endian(self.timestamp, 4)
        result += self.bits
        result += self.nonce
        return result

    def hash(self):
        s = self.serialize()
        sha = hash256(s)
        return sha[::-1]

### ブロックのバージョンについて
バージョン2は、コインベース・トランザクションにブロック高を指定するBIP0034にソフトウェアが対応していることを意味する
バージョン3はDERエンコーディングを強制するBIP0066に対応していることを意味する
バージョン4はOP_CHECKLOCKTIMEVERIFYを規定するBIP0065に対応していることを意味する

BIP0009は4バイトヘッダーのうち、先頭3ビットを001に固定してマイナーがBIP0009に対応していることを示す

from io import BytesIO
from block import Block
b = Block.parse(BytesIO(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b\
4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3\
f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
print('BIP9: {}'.format(b.version >> 29 == 0b001))
print('BIP91: {}'.format(b.version >> 4 & 1 == 1))
print('BIP9: {}'.format(b.version >> 1 & 1 == 1))

$ python3 test.py
BIP9: True
BIP91: False
BIP9: True

上記をclassに含める。if ~ return trueではなく、そのままreturnと書いてしまって良い
[class]
def bip9(self):
return self.version >> 29 == 0b001

def bip91(self):
return self.version >> 4 & 1 == 1

def bip141(self):
return self.version >> 1 & 1 == 1
[/code]

### 前のブロック
全てのブロックは前のブロックを指している必要がある

### マークルルート
全てのトランザクションを32バイトハッシュにエンコードする

### timestamp
unix形式の4bytes。Unixタイムスタンプは1970年1月1日からの秒数

### bits
BitsはそのブロックのProof of Workに必要な値をエンコードするフィールド

### Nonce(number used only once)
一度だけ使われる数値の略

bitcoinのブロック

### ブロックの構造と識別子
Block Size: 4bytes
Block Header: 80
Transaction Counter: 1~9 (ブロックに含まれているトランザクション数)
Transactions: 可変(トランザクションのリスト)

### ブロックヘッダの構造
Version: 4bytes
Previous Block Hash: 32
Merkle Root: 32
Timestamp: 4
Difficulty Target: 4
Nonce: 4

ブロックヘッダだけ保持するノードをSPV(Simplified Payment Verification)ノードという
ブルームフィルタとは、「このトランザクションを確認できるマークルパスをrequest」という要求を粗い要求に転換することで、どのトランザクションに関心があるかを特定しにくくするもの(BIP0037)

### 通常のトランザクションとcoinbaseトランザクション
通常のトランザクション
Transaction Hash: 32bytes
Output Index: 4
Unlockking Script Size: 1~9
Unlocking Script: 可変

coinbaseトランザクション
Transaction Hash: 32(他のトランザクションを参照しないため、全てのビットが「0」固定)
Output Index: 4 全てのビットが1固定
Coinbase Data Size: 1~9 (coinbase data sizeは2~100bytes)
Coinbase Data: 可変 (Extra NonceやMining Tagに使われる)
Sequence Number: 4(0x00000000)

### Segregated Witness
Segregated Witnessはトランザクションの署名をトランザクションインプットから切り離し別の領域に移す仕様で、2017年8月にアクティベートされた
サイズの大きい署名部分をトランザクションから切り離してwitnessに格納する
Lightning Netowrokとは、最初のトランザクションと最後のトランザクションのみをビットコインネットワークのブロックに記録して、その間のトランザクションはライトニングネットワーク内でのみ記録する

– SegWitのトランザクションの構造
Version no: 4bytes
Marker: 1 ★追加
Flag: 1
Input Counter: 1~9 (インプットの数)
Inputのリスト: 可変
Output Counter: 1~9 (アウトプットの数)
Outputのリスト: 可変
Script Witness: 可変 (witnessの要素の数とwitnessが設定される)
Locktime:4

### ブロックサイズの計算方法
block wight = ベースサイズ(全トランザクションのバイト数) * 3 + トータルサイズ(witnessを含むSegwit構造での全トランザクションバイト数)

P2WPKHでは
Locking Script: OP_0

[bitcoin基礎技術] ECDSA署名と検証

m: 署名対象のメッセージ
Q = dG : Q公開鍵、dは秘密鍵、Gはsecp256k1ベースポイント
n: secp256k1の位数

### ECDSA署名の生成
1. ハッシュ関数にメッセージmを渡し、ハッシュ値m’を取得する(m’=H(m))
2. [1, n-1]の範囲からkを選択し、楕円曲線上の点Rを k*Gで求める (R = kG)
3. R= rx, ry とする。Rのx座標rxを位数nで割った余剰を求める r = rx mod n
4. s = (m'(ハッシュしたメッセージ) + d(秘密鍵)*r(Rのx座標)) / k(任意の点) mod n
5. (r, s)が m に対する署名となる

### ECDSA署名の検証
1. P = (px, py)とする
P = m’/s * G + r / s * Q (公開鍵)(mod n)
この時、Px = r(mod n) となるとき、署名検証に成功

パラメータkを生成
$ openssl ecparam -genkey -name secp256k1 -out k.pem; ls k.pem
$ openssl ec -in k.pem -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32
read EC key
writing EC key
a891fce013c907a3ce5e157d1edc474bbdf7efc0c133b41cad14aca3566c914c
$ k=a891fce013c907a3ce5e157d1edc474bbdf7efc0c133b41cad14aca3566c914c

Rを生成
$ openssl ec -in k.pem -pubout -outform DER | tail -c 65 | xxd -p -c 65
read EC key
writing EC key
042ce7c2a48e71ad11e4445b3901d4afe715c0298ed1504cb4ce47b426d83bfaa7bb4665d4c92b9484c4b0ce5cfcabe089e1e2a7c8e32a3c6b5f2d2c0ba301fb97
$ R=042ce7c2a48e71ad11e4445b3901d4afe715c0298ed1504cb4ce47b426d83bfaa7bb4665d4c92b9484c4b0ce5cfcabe089e1e2a7c8e32a3c6b5f2d2c0ba301fb97

Rからrxを取り出し、変数rxに格納
$ rx=`echo $R | cut -c3-66`; echo $rx
2ce7c2a48e71ad11e4445b3901d4afe715c0298ed1504cb4ce47b426d83bfaa7

メッセージのハッシュ値を生成し、変数mhに格納
$ mh=`cat message.txt | xxd -r -p | openssl dgst -sha256 | cut -c10-`; echo $mh
stdin)= 45f83d17e10b34fca01eb8f4454dac34a777d9404a464e732cf4abf2c0da94c4

公開鍵をQに格納
$ Q=$pubKey; echo $Q
047a2b8a7974ed6f4b7817b0f354c6c81ceacb210e53e093eadcd4d4ece0bb4d60a0864e802eb58b813183cd4bae108588e062ec61e6bff36f68ad530ee11cff15

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def multiply_inv(a, m):
    if a < 0:
        a = a % m
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('multiply modular inverse does not exist')
    else:
        return x % m

[bitcoin基礎技術] デジタル署名

<送信者>
– データをハッシュ関数でハッシュ化
– 出力されたハッシュ値を秘密鍵で暗号化し、署名を作成
– データと署名をセットにして受信者に送信

<受信者>
– 受け取ったデータを送信者と同じハッシュ関数でハッシュ化してハッシュ値Aを得る
– 受け取った署名を送信者の公開鍵で復号してハッシュ値Bを得る
– A B比較して一致していることを確認

### デジタル署名の流れ
1. 送信者のメッセージを作成
$ echo secret > message.txt; cat message.txt
secret

2. 秘密鍵でハッシュ値に署名し、message.sigに出力
$ openssl dgst -sha256 -sign secp256k1-private.pem message.txt > message.sig; ls message.sig
message.sig

3. 生成した公開鍵で署名を検証
$ openssl dgst -sha256 -verify secp256k1-public.pem -signature message.sig message.txt
Verified OK

### メッセージの改ざん検出
$ openssl ecparam -genkey -name secp256k1 -out secp256k1-private-evil.pem; ls secp256k1-private-evil.pem
secp256k1-private-evil.pem
$ echo “tampered secret” > tampered_message.txt ; cat tampered_message.txt
tampered secret
$ openssl dgst -sha256 -sign secp256k1-private-evil.pem tampered_message.txt > tampered_message.sig ; ls tampered_message.sig
tampered_message.sig
$ openssl dgst -sha256 -verify secp256k1-public.pem -signature tampered_message.sig tampered_message.txt
Verification failure

[bitcoin基礎技術] 公開鍵暗号

パラメータファイルの生成
$ openssl ecparam -name secp256k1 -out secp256k1.pem

secp256k1.pem

-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----

$ openssl ecparam -in secp256k1.pem -text -noout
EC-Parameters: (256 bit)
ASN1 OID: secp256k1

パラメータの確認
$ openssl ecparam -in secp256k1.pem -text -param_enc explicit -noout
EC-Parameters: (256 bit)
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:fe:ff:
ff:fc:2f
A: 0
B: 7 (0x7)
Generator (uncompressed):
04:79:be:66:7e:f9:dc:bb:ac:55:a0:62:95:ce:87:
0b:07:02:9b:fc:db:2d:ce:28:d9:59:f2:81:5b:16:
f8:17:98:48:3a:da:77:26:a3:c4:65:5d:a4:fb:fc:
0e:11:08:a8:fd:17:b4:48:a6:85:54:19:9c:47:d0:
8f:fb:10:d4:b8
Order:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:fe:ba:ae:dc:e6:af:48:a0:3b:bf:d2:5e:8c:d0:
36:41:41
Cofactor: 1 (0x1)

秘密鍵を生成
$ openssl ecparam -in secp256k1.pem -genkey -noout -out secp256k1-private.pem
$ cat secp256k1-private.pem
—–BEGIN EC PRIVATE KEY—–
MHQCAQEEIAqV2IN8gUr9qXpsJyhpYA4KQfXioS/oiDMh1dgPcncAoAcGBSuBBAAK
oUQDQgAEeiuKeXTtb0t4F7DzVMbIHOrLIQ5T4JPq3NTU7OC7TWCghk6ALrWLgTGD
zUuuEIWI4GLsYea/829orVMO4Rz/FQ==
—–END EC PRIVATE KEY—–

秘密鍵を16進数表記で出力
$ openssl ec -in secp256k1-private.pem -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32
read EC key
writing EC key
0a95d8837c814afda97a6c272869600e0a41f5e2a12fe8883321d5d80f727700

秘密鍵を変数に格納
$ privKey=0a95d8837c814afda97a6c272869600e0a41f5e2a12fe8883321d5d80f727700

公開鍵を生成
$ openssl ec -in secp256k1-private.pem -pubout -out secp256k1-public.pem; cat secp256k1-public.pem
read EC key
writing EC key
—–BEGIN PUBLIC KEY—–
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeiuKeXTtb0t4F7DzVMbIHOrLIQ5T4JPq
3NTU7OC7TWCghk6ALrWLgTGDzUuuEIWI4GLsYea/829orVMO4Rz/FQ==

公開鍵を16進数で出力
$ openssl ec -in secp256k1-private.pem -pubout -outform DER | tail -c 65 | xxd -p -c 65
read EC key
writing EC key
047a2b8a7974ed6f4b7817b0f354c6c81ceacb210e53e093eadcd4d4ece0bb4d60a0864e802eb58b813183cd4bae108588e062ec61e6bff36f68ad530ee11cff15

秘密鍵はスカラーですが、公開鍵は座標x, y
$ pubKey=047a2b8a7974ed6f4b7817b0f354c6c81ceacb210e53e093eadcd4d4ece0bb4d60a0864e802eb58b813183cd4bae108588e062ec61e6bff36f68ad530ee11cff15
$ prefix=`echo $pubKey | cut -c1-2`; echo “prefix= $prefix”
prefix= 04
$ x=`echo $pubKey | cut -c3-66` ; echo “x = $x”
x = 7a2b8a7974ed6f4b7817b0f354c6c81ceacb210e53e093eadcd4d4ece0bb4d60
$ y=`echo $pubKey | cut -c67-130`; echo “y= $y”
y= a0864e802eb58b813183cd4bae108588e062ec61e6bff36f68ad530ee11cff15