[Bitcoin] DER署名

– Signatureを圧縮する
– 署名をシリアライズする標準をDERフォーマットと呼ぶ

1. 0x30バイトで開始
2. 署名の残りの長さをエンコード
3. マーカーバイト0x02追加
4. rをビッグエンディアン整数としてエンコード
5. マーカーバイト0x02を追加
6. sをビッグエンディアン整数としてエンコード

class Signature:

	def __init__(self, r, s):
		self.r = r
		self.s = s

	def __repr__(self):
		return 'Signature({:x},{:x})'.format(self.r, self.s)

	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 = byte([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
r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
sig = Signature(r, s)
print(sig.der().hex())

$ python3 app.py
3045022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60221008ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec

### Base58

def encode_base58(s):
    count = 0
    for c in s:
        if c == 0:
            count += 1
        else:
            break
    num = int.from_bytes(s, 'big')
    prefix = '1' * count
    result = ''
    while num > 0: 
        num, mod = divmod(num, 58)
        result = BASE58_ALPHABET[mod] + result
    return prefix + result

h = '7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d'
print(encode_base58(bytes.fromhex(h)))
h = 'eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c'
print(encode_base58(bytes.fromhex(h)))
h = 'c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6'
print(encode_base58(bytes.fromhex(h)))

### アドレス形式
1. メインネットアドレスは先頭を0x00, テストネットは0x6f
2. SECフォーマットを取り出し、sha256とripemd160操作を行う
3. 1のプレフィックスと2のハッシュ操作の結果を結合
4. 3の結果にhash256を行い先頭の4バイトを取得
5. 3と4を結合させてBase58でエンコード

def encode_base58_checksum(b):
	return encode_base58(b + hash256(b)[:4])

def hash160(s):
	return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() 

class S256Point:

	def hash160(self, compressed=True):
		return hash160(self.sec(compressed))

	def address(self, compressed=True, testnet=False):
		h160 = self.hash160(compressed)
		if testnet:
			prefix = b'\x6f'
		else:
			prefix = b'\x00'
		return encode_base58_checksum(prefix + h160)
priv = PrivateKey(5002)
print(priv.point.address(compressed=False, testnet=True))
priv = PrivateKey(2020**5)
print(priv.point.address(compressed=True, testnet=True))
priv = PrivateKey(0x12345deadbeef)
print(priv.point.address(compressed=True, testnet=False))

### WIF(ウォレットインポート形式)
1. メインネット用の秘密鍵は0x80、テストネット用は0xefプレフィックス
2. 秘密鍵を32バイトのビッグエンディアンでエンコード
3. SECフォーマットが圧縮形式の場合、末尾に0x01
4. 123を結合
5. 4の結果にhash256を実行し、先頭の4バイトを取得
6. 4と5を結合させてBase58でエンコード

	def wif(self, compressed=True, testnet=False):
		secret_bytes = self.secret.to_bytes(32, 'big')
		if testnet:
			prefix = b'\xef'
		else:
			prefix = b'\x80'
		if compressed:
			suffix = b'\x01'
		else:
			suffix = b''
		return encode_base58_checksum(prefix + secret_bytes + suffix)
priv = PrivateKey(5003)
print(priv.wif(compressed=True, testnet=True))
priv = PrivateKey(2021**5)
print(priv.wif(compressed=False, testnet=True))
priv = PrivateKey(0x54321deadbeef)
print(priv.wif(compressed=True, testnet=False))

### リトルエンディアン

def little_endian_to_int(b):
    return int.from_bytes(b, 'little')

def int_to_little_endian(n, length):
    return n.to_bytes(length, 'little')

テストネットアドレスの作成

passphrase = b'info@hpscript.com my secret'
secret = little_endian_to_int(hash256(passphrase))
priv = PrivateKey(secret)
print(priv.point.address(testnet=True))

テストネットのfaucetサイト
https://testnet-faucet.mempool.co/

大分入ってきましたね