Repository URL to install this package:
|
Version:
1.14 ▾
|
python-stdnum
/
bitcoin.py
|
|---|
# bitcoin.py - functions for handling Bitcoin addresses
#
# Copyright (C) 2018 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Bitcoin address.
A Bitcoin address is an identifier that is used as destination in a Bitcoin
transaction. It is based on a hash of the public portion of a keypair.
There are currently three address formats in use:
* P2PKH: pay to pubkey hash
* P2SH: pay to script hash
* Bech32
More information:
* https://en.bitcoin.it/wiki/Address
>>> validate('1NEDqZPvTWRaoho48qXuLLsrYomMXPABfD')
'1NEDqZPvTWRaoho48qXuLLsrYomMXPABfD'
>>> validate('BC1QARDV855YJNGSPVXUTTQ897AQCA3LXJU2Y69JCE')
'bc1qardv855yjngspvxuttq897aqca3lxju2y69jce'
>>> validate('1NEDqZPvTWRaoho48qXuLLsrYomMXPABfX')
Traceback (most recent call last):
...
InvalidChecksum: ...
"""
import hashlib
import struct
from functools import reduce
from stdnum.exceptions import *
from stdnum.util import clean
def compact(number):
"""Convert the number to the minimal representation. This strips the
number of any valid separators and removes surrounding whitespace."""
number = clean(number, ' ').strip()
if number[:3].lower() == 'bc1':
number = number.lower()
return number
# Base58 encoding character set as used in Bitcoin addresses
_base58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def b58decode(s):
"""Decode a Base58 encoded string to a bytestring."""
value = reduce(lambda a, c: a * 58 + _base58_alphabet.index(c), s, 0)
result = b''
while value >= 256:
value, mod = divmod(value, 256)
result = struct.pack('B', mod) + result
result = struct.pack('B', value) + result
return struct.pack('B', 0) * (len(s) - len(s.lstrip('1'))) + result
# Bech32 character set as used in Bitcoin addresses
_bech32_alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
# The Bech32 generator tests and values for checksum calculation
_bech32_generator = (
(1 << 0, 0x3b6a57b2), (1 << 1, 0x26508e6d), (1 << 2, 0x1ea119fa),
(1 << 3, 0x3d4233dd), (1 << 4, 0x2a1462b3))
def bech32_checksum(values):
"""Calculate the Bech32 checksum."""
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 | value
for t, v in _bech32_generator:
chk ^= v if top & t else 0
return chk
def b32decode(data):
"""Decode a list of Base32 values to a bytestring."""
acc, bits = 0, 0
result = b''
for value in data:
acc = ((acc << 5) | value) & 0xfff
bits += 5
if bits >= 8:
bits -= 8
result = result + struct.pack('B', (acc >> bits) & 0xff)
if bits >= 5 or acc & ((1 << bits) - 1):
raise InvalidComponent()
return result
def _expand_hrp(hrp):
"""Convert the human-readable part to format for checksum calculation."""
return [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp]
def validate(number):
"""Check if the number provided is valid. This checks the length and
check digit."""
number = compact(number)
if number.startswith('1') or number.startswith('3'):
# P2PKH (pay to pubkey hash) or P2SH (pay to script hash) address
if not all(x in _base58_alphabet for x in number):
raise InvalidFormat()
address = b58decode(number)
if len(address) != 25:
raise InvalidLength()
if hashlib.sha256(hashlib.sha256(address[:-4]).digest()).digest()[:4] != address[-4:]:
raise InvalidChecksum()
elif number.startswith('bc1'):
# Bech32 type address
if not all(x in _bech32_alphabet for x in number[3:]):
raise InvalidFormat()
if len(number) < 11 or len(number) > 90:
raise InvalidLength()
data = [_bech32_alphabet.index(x) for x in number[3:]]
if bech32_checksum(_expand_hrp('bc') + data) != 1:
raise InvalidChecksum()
witness_version = data[0]
witness_program = b32decode(data[1:-6])
if witness_version > 16:
raise InvalidComponent()
if len(witness_program) < 2 or len(witness_program) > 40:
raise InvalidLength()
if witness_version == 0 and len(witness_program) not in (20, 32):
raise InvalidLength()
else:
raise InvalidComponent()
return number
def is_valid(number):
"""Check if the number provided is valid. This checks the length and
check digit."""
try:
return bool(validate(number))
except ValidationError:
return False