mirror of
https://github.com/bitcoin/bips.git
synced 2026-03-23 16:05:41 +00:00
328: Add reference implementation
This commit is contained in:
176
bip-0328/_base58.py
Normal file
176
bip-0328/_base58.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Base 58 conversion utilities
|
||||
****************************
|
||||
"""
|
||||
|
||||
#
|
||||
# base58.py
|
||||
# Original source: git://github.com/joric/brutus.git
|
||||
# which was forked from git://github.com/samrushing/caesure.git
|
||||
#
|
||||
# Distributed under the MIT/X11 software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
from typing import List
|
||||
|
||||
from _common import hash256
|
||||
|
||||
|
||||
b58_digits: str = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
||||
def encode(b: bytes) -> str:
|
||||
"""
|
||||
Encode bytes to a base58-encoded string
|
||||
|
||||
:param b: Bytes to encode
|
||||
:return: Base58 encoded string of ``b``
|
||||
"""
|
||||
|
||||
# Convert big-endian bytes to integer
|
||||
n: int = int('0x0' + hexlify(b).decode('utf8'), 16)
|
||||
|
||||
# Divide that integer into base58
|
||||
temp: List[str] = []
|
||||
while n > 0:
|
||||
n, r = divmod(n, 58)
|
||||
temp.append(b58_digits[r])
|
||||
res: str = ''.join(temp[::-1])
|
||||
|
||||
# Encode leading zeros as base58 zeros
|
||||
czero: int = 0
|
||||
pad: int = 0
|
||||
for c in b:
|
||||
if c == czero:
|
||||
pad += 1
|
||||
else:
|
||||
break
|
||||
return b58_digits[0] * pad + res
|
||||
|
||||
def decode(s: str) -> bytes:
|
||||
"""
|
||||
Decode a base58-encoding string, returning bytes
|
||||
|
||||
:param s: Base48 string to decode
|
||||
:return: Bytes encoded by ``s``
|
||||
"""
|
||||
if not s:
|
||||
return b''
|
||||
|
||||
# Convert the string to an integer
|
||||
n: int = 0
|
||||
for c in s:
|
||||
n *= 58
|
||||
if c not in b58_digits:
|
||||
raise Exception('Character %r is not a valid base58 character' % c)
|
||||
digit = b58_digits.index(c)
|
||||
n += digit
|
||||
|
||||
# Convert the integer to bytes
|
||||
h: str = '%x' % n
|
||||
if len(h) % 2:
|
||||
h = '0' + h
|
||||
res = unhexlify(h.encode('utf8'))
|
||||
|
||||
# Add padding back.
|
||||
pad = 0
|
||||
for c in s[:-1]:
|
||||
if c == b58_digits[0]:
|
||||
pad += 1
|
||||
else:
|
||||
break
|
||||
return b'\x00' * pad + res
|
||||
|
||||
def decode_check(s: str) -> bytes:
|
||||
"""
|
||||
Decode a Base58Check encoded string, returning bytes
|
||||
|
||||
:param s: Base58 string to decode
|
||||
:return: Bytes encoded by ``s``
|
||||
"""
|
||||
data = decode(s)
|
||||
payload = data[:-4]
|
||||
checksum = data[-4:]
|
||||
calc_checksum = hash256(payload)
|
||||
if checksum != calc_checksum[:4]:
|
||||
raise ValueError("Invalid checksum")
|
||||
return payload
|
||||
|
||||
def encode_check(b: bytes) -> str:
|
||||
checksum = hash256(b)[0:4]
|
||||
data = b + checksum
|
||||
return encode(data)
|
||||
|
||||
def get_xpub_fingerprint(s: str) -> bytes:
|
||||
"""
|
||||
Get the parent fingerprint from an extended public key
|
||||
|
||||
:param s: The extended pubkey
|
||||
:return: The parent fingerprint bytes
|
||||
"""
|
||||
data = decode(s)
|
||||
fingerprint = data[5:9]
|
||||
return fingerprint
|
||||
|
||||
def get_xpub_fingerprint_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the parent fingerprint as a hex string from an extended public key
|
||||
|
||||
:param s: The extended pubkey
|
||||
:return: The parent fingerprint as a hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
fingerprint = data[5:9]
|
||||
return hexlify(fingerprint).decode()
|
||||
|
||||
def to_address(b: bytes, version: bytes) -> str:
|
||||
"""
|
||||
Base58 Check Encode the data with the version number.
|
||||
Used to encode legacy style addresses.
|
||||
|
||||
:param b: The data to encode
|
||||
:param version: The version number to encode with
|
||||
:return: The Base58 Check Encoded string
|
||||
"""
|
||||
data = version + b
|
||||
checksum = hash256(data)[0:4]
|
||||
data += checksum
|
||||
return encode(data)
|
||||
|
||||
def xpub_to_pub_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the public key as a string from the extended public key.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The pubkey hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
pubkey = data[-37:-4]
|
||||
return hexlify(pubkey).decode()
|
||||
|
||||
|
||||
def xpub_to_xonly_pub_hex(xpub: str) -> str:
|
||||
"""
|
||||
Get the public key as a string from the extended public key.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The pubkey hex string
|
||||
"""
|
||||
data = decode(xpub)
|
||||
pubkey = data[-36:-4]
|
||||
return hexlify(pubkey).decode()
|
||||
|
||||
|
||||
def xpub_main_2_test(xpub: str) -> str:
|
||||
"""
|
||||
Convert an extended pubkey from mainnet version to testnet version.
|
||||
|
||||
:param xpub: The extended pubkey
|
||||
:return: The extended pubkey re-encoded using testnet version bytes
|
||||
"""
|
||||
data = decode(xpub)
|
||||
test_data = b'\x04\x35\x87\xCF' + data[4:-4]
|
||||
checksum = hash256(test_data)[0:4]
|
||||
return encode(test_data + checksum)
|
||||
Reference in New Issue
Block a user