Last active
October 10, 2024 03:21
-
-
Save gitzhou/c21f64af3f2980c4fbd6748bc326675d to your computer and use it in GitHub Desktop.
base58check demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from base_x import BaseX | |
class Base58: | |
b58 = BaseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') | |
@staticmethod | |
def encode(payload: bytes) -> str: | |
leading_zeros = 0 | |
for byte in payload: | |
if byte != 0: | |
break | |
leading_zeros += 1 | |
encoded = '' | |
if payload[leading_zeros:]: | |
num = int.from_bytes(payload, 'big') | |
encoded = Base58.b58.from_dec(num) | |
return '1' * leading_zeros + encoded | |
@staticmethod | |
def decode(encoded: str) -> bytes: | |
leading_zeros = 0 | |
for char in encoded: | |
if char != '1': | |
break | |
leading_zeros += 1 | |
num_b = b'' | |
if encoded[leading_zeros:]: | |
num = Base58.b58.to_dec(encoded) | |
num_b = num.to_bytes((num.bit_length() + 7) // 8, 'big') | |
return b'\x00' * leading_zeros + num_b | |
if __name__ == '__main__': | |
base58_cases = [ | |
(b'', ''), | |
(b'\x00', '1'), | |
(b'\x00\x00', '11'), | |
(b'1234567890', '3mJr7AoUCHxNqd'), | |
(b'hello world', 'StV1DL6CwTryKyV'), | |
(b'\x00\x00hello world', '11StV1DL6CwTryKyV'), | |
(b'\x01\x02\x03\x04\x05', '7bWpTW'), | |
] | |
for _payload, _encoded in base58_cases: | |
assert Base58.encode(_payload) == _encoded | |
assert Base58.decode(_encoded) == _payload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from base58 import Base58 | |
from hash import hash256 | |
class Base58Check: | |
CHECKSUM_BYTES = 4 | |
@staticmethod | |
def checksum(payload: bytes) -> bytes: | |
return hash256(payload)[:Base58Check.CHECKSUM_BYTES] | |
@staticmethod | |
def encode(payload: bytes) -> str: | |
checksum = Base58Check.checksum(payload) | |
return Base58.encode(payload + checksum) | |
@staticmethod | |
def decode(encoded: str) -> bytes: | |
decoded = Base58.decode(encoded) | |
if len(decoded) < Base58Check.CHECKSUM_BYTES: | |
raise ValueError('invalid base58check encoded string') | |
payload = decoded[:-Base58Check.CHECKSUM_BYTES] | |
checksum = decoded[-Base58Check.CHECKSUM_BYTES:] | |
if Base58Check.checksum(payload) != checksum: | |
raise ValueError('invalid checksum') | |
return payload | |
if __name__ == '__main__': | |
base58check_cases = [ | |
(b'', '3QJmnh'), | |
(b'\x00', '1Wh4bh'), | |
(b'\x00\x00', '112edB6q'), | |
(b'1234567890', 'K5zqBMZXFNFYSLJZTdx'), | |
(b'hello world', '3vQB7B6MrGQZaxCuFg4oh'), | |
(b'\x00\x00hello world', '113vQB7B6MrGQZaxCpfiiBh'), | |
(b'\x01\x02\x03\x04\x05', 'kA3B33GVuqT'), | |
] | |
for _payload, _encoded in base58check_cases: | |
assert Base58Check.encode(_payload) == _encoded | |
assert Base58Check.decode(_encoded) == _payload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BaseX: | |
def __init__(self, alphabet: str): | |
if len(alphabet) < 2: | |
raise ValueError('alphabet must have at least 2 characters') | |
self.alphabet = alphabet | |
def from_dec(self, num: int) -> str: | |
""" | |
Converts a decimal unsigned integer to base X of this instance | |
:param num: decimal number | |
:return: encoded number in base X of this instance | |
""" | |
if num < 0: | |
raise ValueError('num must be non-negative') | |
encoded = '' | |
while num > 0: | |
num, remaining = divmod(num, len(self.alphabet)) | |
encoded = self.alphabet[remaining] + encoded | |
return encoded or self.alphabet[0] | |
def to_dec(self, encoded: str) -> int: | |
""" | |
Convert an encoded number in base X of this instance | |
to the decimal unsigned integer | |
:param encoded: encoded number in base X of this instance | |
:return: decimal number | |
""" | |
if not encoded: | |
raise ValueError('encoded number must not be empty') | |
num = 0 | |
try: | |
for char in encoded: | |
num = num * len(self.alphabet) + self.alphabet.index(char) | |
except Exception: | |
raise ValueError(f'invalid base{len(self.alphabet)} encoded number') | |
return num | |
def from_x(self, num_s: str, base_x: 'BaseX') -> str: | |
""" | |
Convert an encoded number in base X of passed-in instance | |
to the encoded number in base X of this instance | |
:param num_s: encoded number in base X of passed-in instance | |
:param base_x: BaseX instance | |
""" | |
num = base_x.to_dec(num_s) | |
return self.from_dec(num) | |
def to_x(self, num_s: str, base_x: 'BaseX') -> str: | |
""" | |
Convert an encoded number in base X of this instance | |
to the encoded number in base X of passed-in instance | |
:param num_s: encoded number in base X of this instance | |
:param base_x: BaseX instance | |
:return: | |
""" | |
num = self.to_dec(num_s) | |
return base_x.from_dec(num) | |
import unittest | |
class TestBaseX(unittest.TestCase): | |
def test(self): | |
base5 = BaseX('01234') | |
base7 = BaseX('0123456') | |
base8 = BaseX('01234567') | |
base9 = BaseX('012345678') | |
# base8('123') to dec | |
assert base8.to_dec('123') == 83 | |
# dec 83 to base7 | |
assert base7.from_dec(83) == '146' | |
# base5('1324') to dec | |
assert base5.to_dec('1324') == 214 | |
# dec 214 to base9 | |
assert base9.from_dec(214) == '257' | |
# base5('1324') to base9 | |
assert base9.from_x('1324', base5) == '257' | |
assert base5.to_x('1324', base9) == '257' | |
# custom base10 | |
my_base10 = BaseX(')!@#$%^&*(') | |
assert my_base10.to_dec('!@#$%^&*()') == 1234567890 | |
def test_corner_cases(self): | |
base5 = BaseX('01234') | |
assert base5.from_dec(0) == '0' | |
assert base5.to_dec('0') == 0 | |
with self.assertRaises(ValueError) as context: | |
base5.from_dec(-1) | |
assert str(context.exception) == 'num must be non-negative' | |
with self.assertRaises(ValueError) as context: | |
base5.to_dec('') | |
assert str(context.exception) == 'encoded number must not be empty' | |
with self.assertRaises(ValueError) as context: | |
base5.to_dec('5') | |
assert str(context.exception) == 'invalid base5 encoded number' | |
if __name__ == '__main__': | |
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import hashlib | |
def sha256(payload: bytes) -> bytes: | |
return hashlib.sha256(payload).digest() | |
def hash256(payload: bytes) -> bytes: | |
return sha256(sha256(payload)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment