Génération des adresses Bitcoin
Adresse Bitcoin ?
Une adresse Bitcoin, ou simplement une adresse, est un identifiant de 26 à 35 caractères alphanumériques, commençant par le chiffre 1 ou 3, qui représente une destination possible pour un paiement bitcoin. Les adresses peuvent être générées gratuitement par n’importe quel utilisateur de Bitcoin. Par exemple, en utilisant Bitcoin Core, vous pouvez cliquer sur “New Address” et créer une adresse. Il est également possible d’obtenir une adresse Bitcoin en utilisant un compte auprès d’un service d’échange ou de portefeuille en ligne. [Source: bitcoin wiki]
Il y a actuellement trois formats d’adresses utilisés:
P2PKH
qui commence par le numéro 1, par exemple:1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2
- Type
P2SH
commençant par le chiffre 3, par exemple:3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
- Type
Bech32
commençant par bc1, par exemple:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
Pour voir la liste complètes des prefixes, veuillez visiter la documentation de Bitcoin
Alors comment créer une adresse Bitcoin ?
Voici une implémentation de l’algorithme de Base58
et du processus de génération des addresses Bitcoin en utilisantion Python3
:
base58.py:
# Base58 implementation using Python3
# Author: Chiheb Nexus - 2018
# License: GPLv3
#
import binascii
class Base58:
'''Base58 implementation'''
def __init__(self):
self.B58_DIGITS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def encode(self, var, n=0):
'''Encode Bytes/int/string to padded base58 encoded string'''
res, czero, pad = [], 0, 0
if isinstance(var, bytes):
# Convert bytes into hex then into an integer
n = int('0x0' + binascii.hexlify(var).decode('utf8'), 16)
elif isinstance(var, int):
# Convert integer into a bytes
n, var = var, bytes(str(var), 'utf8')
elif isinstance(var, str):
# convert string into an integer
n = int(''.join(map(str, (ord(k) for k in var))))
else:
raise Exception('Byte not valid: {0}'.format(n))
while n > 0:
n, r = divmod(n, 58)
res.append(self.B58_DIGITS[r])
res_final = ''.join(res[::-1])
for c in var:
if c == czero:
pad += 1
else:
break
return self.B58_DIGITS[0] * pad + res_final
def decode(self, s):
'''Decode padded base58 encoded string into bytes'''
if not s:
return b''
n = 0
for c in s:
n *= 58
if c not in self.B58_DIGITS:
raise Exception('Not valid base58: {0}'.format(c))
digit = self.B58_DIGITS.index(c)
n += digit
# Convert n into hex
h = '{:x}'.format(n)
if len(h) % 2:
h = '0' + h
res = binascii.unhexlify(h.encode('utf8'))
pad = 0
for c in s[:-1]:
if c == self.B58_DIGITS[0]:
pad += 1
else:
break
return b'\x00' * pad + res
btc_addresses.py:
# Générer des clés privées et publiques de Bitcoin
# Author: Chiheb Nexus - 2018
# License: GPLv3
#########
# Livre:
# http://chimera.labs.oreilly.com/books/1234000001802/ch04.html#_implementing_keys_and_addresses_in_python
# Liste des prefix de Bitcoin:
# https://en.bitcoin.it/wiki/List_of_address_prefixes
# Exemple: Private key => WIF:
# https://en.bitcoin.it/wiki/Wallet_import_format
# Exemple: WIF => Adresse publique:
# https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
#########
import os
import hashlib
import binascii
import ecdsa
from base58 import Base58
class BTCAddress:
def __init__(self):
self.base58 = Base58()
# 2 ^ 256 + 1
self.N = (1 << 256) + 1
def generate_privkey(self, bits=32):
'''Générer un random 32 bit seed'''
while True:
# Générer une séqunce de bits aléatoires
# https://docs.python.org/3/library/os.html#os.urandom
priv_hex = binascii.hexlify(os.urandom(bits)).decode('utf8')
priv_int = int(priv_hex, 16)
# une clé privée de bitcoin est de l'ordre 2^256 bit = 32 bytes
if 0 < priv_int < self.N:
return priv_hex
def priv_toWIF(self, key='', prefix='80'):
'''Générer un WIF depuis un seed'''
# remplacer le premier byte par le prefix
# enlever le char "L" et ajouter des 0 à gauche pour assurer la longueur
# https://www.tutorialspoint.com/python/string_zfill.htm
step1 = prefix + hex(int(key, 16))[2:].strip('L').zfill(64)
# double hash sha256
step2 = hashlib.sha256(binascii.unhexlify(step1)).hexdigest()
step3 = hashlib.sha256(binascii.unhexlify(step2)).hexdigest()
# step1 + 8 bits de step3 => convertir en entier
step4 = int(step1 + step3[:8] , 16)
# convertir l'entier de ste4 en base58
return self.base58.encode(step4)
def check_wif(self, wif='', privkey=''):
'''Valider un WIF généré'''
# décoder le WIF et le convertir en hexadécimal
b58 = self.base58.decode(wif).hex()
# enlever les 2 premiers bits et les 8 derniers bits
# et comparer avec la clé privée
assert b58[2:-8].upper() == privkey.upper()
def wif_to_priv(self, wif=''):
'''Avoir un seed depuis le WIF'''
# décoder le WIF et le convertir en hexadécimal
# enlever les 2 premiers bits et les 8 derniers bits
return self.base58.decode(wif).hex()[2:-8]
def priv_to_public(self, privkey='', compressed=False):
'''Avoir une clé publique depuis un seed'''
# voir ce lien: https://en.bitcoin.it/wiki/Protocol_documentation#Addresses
sk = ecdsa.SigningKey.from_string(binascii.unhexlify(privkey), curve=ecdsa.SECP256k1)
vk = sk.get_verifying_key()
# Les prefixes: https://en.bitcoin.it/wiki/List_of_address_prefixes
prefix = '04' if not compressed else ('03' if vk.pubkey.point.y() % 2 == 1 else '02')
return prefix + binascii.hexlify(vk.to_string()).decode()
def ripemd160(self, var):
'''Fonction de hash ripemd160'''
rmd = hashlib.new('ripemd160')
rmd.update(var)
return rmd
def make_pub_address(self, pubkey, prefix='00'):
'''Créer une addresse bitcoin depuis une clé publique'''
# appliquer ripemd160 sur la hash sha256 du byte array du clé publique
hash160 = self.ripemd160(hashlib.sha256(binascii.unhexlify(pubkey)).digest()).digest()
# ajouter le prefix en byte au byte du hash160
post_pub_addr = binascii.unhexlify(bytes(prefix.encode())) + hash160
# double hash sha256 et extraire les 4 derniers bits => checksum
checksum = hashlib.sha256(hashlib.sha256(post_pub_addr).digest()).digest()[:4]
# encoder en base58
return self.base58.encode(post_pub_addr + checksum)
test.py
# Tester l'implémentation de génération des addresses de bitcoin
# Author: Chiheb Nexus - 2018
# License: GPLv3
# l'exemple introduit par:
# https://en.bitcoin.it/wiki/Wallet_import_format
# et
# https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
# Aussi penser à valider les résultats avec le site: https://www.bitaddress.org
#
import unittest
from btc_addresses import BTCAddress
class TestBTCAddress(unittest.TestCase):
btc_addresses = BTCAddress()
privkey = '0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D'
wif_example = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
addr_example = "1GAehh7TsJAHuUAeKZcXf5CnwuGuGgyX2S"
def test_wif(self):
'''Tester le WIF généré avec le WIF de l'exemple'''
priv = self.btc_addresses.priv_toWIF(self.privkey)
self.assertEqual(priv, self.wif_example)
def test_addr(self):
'''Tester l'adresse générée avec l'adresse de l'exemple'''
pub = self.btc_addresses.priv_to_public(self.privkey)
addr = self.btc_addresses.make_pub_address(pub)
self.assertEqual(addr, self.addr_example)
# Test
if __name__ == '__main__':
unittest.main()
Output
┌─[Chiheb@NeXus]─[~/blog/tutos/generate_btc_addresses]
└──╼ [>] python3 test.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.112s
OK
Cheers :D !
Commentaires