比特币地址转换

Posted by tianhaoo on December 9, 2021 本文阅读量

16进制数字与wif格式私钥互转

from base58 import b58encode, b58decode
from binascii import hexlify, unhexlify
from hashlib import sha256
import unittest


def encode_private_key_as_wif(private_key):
  """
  秘密鍵 (16 進数文字列) を WIF 形式に変換する。
  """

  # 先頭の 0x80 は秘密鍵をエンコードする際の version byte を表す。
  key = '80' + private_key
  tmp = key

  # SHA-256 関数を 2 回適用する。
  tmp = sha256(unhexlify(tmp)).hexdigest()
  tmp = sha256(unhexlify(tmp)).hexdigest()

  # 末尾の 8 バイトをチェックサムとする。
  checksum = tmp[0:8]

  wif_private_key = b58encode(unhexlify(key + checksum))

  return wif_private_key


def decode_wif_private_key(wif_private_key):
  """
  秘密鍵 (WIF 形式) を 16 進数文字列に変換する。
  """
  tmp = hexlify(b58decode(wif_private_key)).decode('utf-8')
  # version byte とチェックサムを取り除く。
  private_key = tmp[2:-8]

  # b58decode の代わりに b58decode_check を使う場合
  # tmp = hexlify(b58decode_check(wif_private_key)).decode('utf-8')
  # private_key = tmp[2:]

  return private_key


def verify_checksum(wif_private_key):
  """
  秘密鍵のチェックサムを検証する。
  """
  b58decoded = hexlify(b58decode(wif_private_key)).decode('utf-8')
  tmp = b58decoded[:-8]
  tmp = sha256(unhexlify(tmp)).hexdigest()
  tmp = sha256(unhexlify(tmp)).hexdigest()

  return b58decoded[-8:] == tmp[:8]


class WIFTest(unittest.TestCase):
  """
  WIF 形式に関する関数の動作を検証するための単体テスト。
  検証のための値は https://en.bitcoin.it/wiki/Wallet_import_format から借用している。
  """

  def test_encode_private_key_as_wif(self):
    private_key = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d'
    wif_private_key = encode_private_key_as_wif(private_key)

    self.assertEqual('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ', wif_private_key)

  def test_decode_wif_private_key(self):
    wif_private_key = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'
    private_key = decode_wif_private_key(wif_private_key)

    self.assertEqual('0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d', private_key)

  def test_verify_checksum(self):
    valid_wif_private_key = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'
    invalid_wif_private_key = 'SHueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'

    self.assertTrue(verify_checksum(valid_wif_private_key))
    self.assertFalse(verify_checksum(invalid_wif_private_key))


if __name__ == '__main__':
  unittest.main()

私钥转比特币地址

import bitcoin

msgs = [
    '所有的「压缩」,都表示公钥坐标转换为公钥值时的压缩',
    '压缩密钥不是把密钥压缩,而是指仅用来生成压缩公钥的密钥',
    '压缩地址也不是把地址压缩,而是用压缩公钥生成的地址'
]
print('='*80)
print('\n'.join(m.center(60, ' ') for m in msgs))
print('='*80)

# 生成一个随机的密钥
while True:
    # 生成一个用十六进制表示的长 256 位的私钥(str类型)
    private_key = bitcoin.random_key()
    # 解码为十进制的整形密钥
    decoded_private_key = bitcoin.decode_privkey(private_key, 'hex')
    if 0 < decoded_private_key < bitcoin.N:
        break

print(f'密钥(十六进制):{private_key} (长 256 位)')
print(f'密钥(十进制):{decoded_private_key} (0 到 1.158*10**77 之间)')

# 用 WIF 格式编码密钥
wif_encoded_private_key = bitcoin.encode_privkey(decoded_private_key, 'wif')
print(f'密钥(WIF):{wif_encoded_private_key} (5 开头,长 51 字符)')

# 用 01 标识的压缩密钥
compressed_private_key = private_key + '01'
print(f'压缩密钥(十六进制):{compressed_private_key} (01 结尾,长 264 位)')

# 生成 WIF的压缩格式
wif_compressed_private_key = bitcoin.encode_privkey(
    bitcoin.decode_privkey(compressed_private_key, 'hex'), 'wif')
print(f'压缩密钥(WIF):{wif_compressed_private_key} (L/K 开头)')

# 计算公钥坐标 K = k * G
public_key = bitcoin.fast_multiply(bitcoin.G, decoded_private_key)
print(f'公钥(坐标):{public_key}')
# 转十六也可用 bitcoin.encode(xxx, 16)
print(f'公钥(坐标的十六进制):{tuple(hex(i) for i in public_key)}')

# 计算公钥
hex_encoded_public_key = bitcoin.encode_pubkey(public_key, 'hex')
print(f'公钥(十六进制):{hex_encoded_public_key} (04 x y)')

# 计算压缩公钥
# if public_key[1] % 2 == 0:  # 两种方式均可
if public_key[1] & 1 == 0:
    compressed_prefix = '02'
else:
    compressed_prefix = '03'
# 转十六也可用 bitcoin.encode(xxx, 16)
hex_compressed_public_key = compressed_prefix + hex(public_key[0])[2:]
print(f'压缩公钥(十六进制){hex_compressed_public_key} '
      '(02 开头代表 y 是偶数,03 开头代表 y 是奇数)')

# 计算地址
# 传入公钥坐标对象/十六进制公钥值,输出同样的地址
# 传入压缩公钥值,输出与⬆️不同的地址
print(f'地址(b58check):{bitcoin.pubkey_to_address(public_key)} (1 开头)')
print(type(hex_compressed_public_key))
print('压缩地址(b58check):'
      f'{bitcoin.pubkey_to_address(hex_compressed_public_key)} (1 开头)')



以太坊地址生成过程如下

1、生成 256 位随机数作为私钥。

2、将私钥转化为 secp256k1 非压缩格式的公钥,即 512 位的公钥。

3、使用散列算法 Keccak256 计算公钥的哈希值,转化为十六进制字符串。

4、取十六进制字符串的后 40 个字母,开头加上 0x 作为地址。

#!/usr/bin/env python3

# pip install ecdsa
# pip install pysha3

from ecdsa import SigningKey, SECP256k1
import sha3


def checksum_encode(addr_str):  # Takes a hex (string) address as input
    keccak = sha3.keccak_256()
    out = ''
    addr = addr_str.lower().replace('0x', '')
    keccak.update(addr.encode('ascii'))
    hash_addr = keccak.hexdigest()
    for i, c in enumerate(addr):
        if int(hash_addr[i], 16) >= 8:
            out += c.upper()
        else:
            out += c
    return '0x' + out


keccak = sha3.keccak_256()

priv = SigningKey.generate(curve=SECP256k1)
pub = priv.get_verifying_key().to_string()

keccak.update(pub)
address = keccak.hexdigest()[24:]


def test(addrstr):
    assert (addrstr == checksum_encode(addrstr))


test('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed')
test('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359')
test('0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB')
test('0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb')
test('0x7aA3a964CC5B0a76550F549FC30923e5c14EDA84')

print("Private key:", priv.to_string().hex())
print("Public key: ", pub.hex())
print("Address:    ", checksum_encode(address))