-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathbase58.py
156 lines (125 loc) · 4.06 KB
/
base58.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Copyright (C) 2011 Sam Rushing
# Copyright (C) 2013-2014 The python-bitcoinlib developers
#
# This file is part of python-bitcoinlib.
#
# It is subject to the license terms in the LICENSE file found in the top-level
# directory of this distribution.
#
# No part of python-bitcoinlib, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in the
# LICENSE file.
"""Base58 encoding and decoding"""
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
_bchr = chr
_bord = ord
if sys.version > '3':
long = int
_bchr = lambda x: bytes([x])
_bord = lambda x: x
import binascii
import bitcoin.core
B58_DIGITS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
class Base58Error(Exception):
pass
class InvalidBase58Error(Base58Error):
"""Raised on generic invalid base58 data, such as bad characters.
Checksum failures raise Base58ChecksumError specifically.
"""
pass
def encode(b):
"""Encode bytes to a base58-encoded string"""
# Convert big-endian bytes to integer
n = int('0x0' + binascii.hexlify(b).decode('utf8'), 16)
# Divide that integer into bas58
res = []
while n > 0:
n, r = divmod(n, 58)
res.append(B58_DIGITS[r])
res = ''.join(res[::-1])
# Encode leading zeros as base58 zeros
czero = b'\x00'
if sys.version > '3':
# In Python3 indexing a bytes returns numbers, not characters.
czero = 0
pad = 0
for c in b:
if c == czero:
pad += 1
else:
break
return B58_DIGITS[0] * pad + res
def decode(s):
"""Decode a base58-encoding string, returning bytes"""
if not s:
return b''
# Convert the string to an integer
n = 0
for c in s:
n *= 58
if c not in B58_DIGITS:
raise InvalidBase58Error('Character %r is not a valid base58 character' % c)
digit = B58_DIGITS.index(c)
n += digit
# Convert the integer to bytes
h = '%x' % n
if len(h) % 2:
h = '0' + h
res = binascii.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
class Base58ChecksumError(Base58Error):
"""Raised on Base58 checksum errors"""
pass
class CBase58Data(bytes):
"""Base58-encoded data
Includes a version and checksum.
"""
def __new__(cls, s):
k = decode(s)
verbyte, data, check0 = k[0:1], k[1:-4], k[-4:]
check1 = bitcoin.core.Hash(verbyte + data)[:4]
if check0 != check1:
raise Base58ChecksumError('Checksum mismatch: expected %r, calculated %r' % (check0, check1))
return cls.from_bytes(data, _bord(verbyte[0]))
def __init__(self, s):
"""Initialize from base58-encoded string
Note: subclasses put your initialization routines here, but ignore the
argument - that's handled by __new__(), and .from_bytes() will call
__init__() with None in place of the string.
"""
@classmethod
def from_bytes(cls, data, nVersion):
"""Instantiate from data and nVersion"""
if not (0 <= nVersion <= 255):
raise ValueError('nVersion must be in range 0 to 255 inclusive; got %d' % nVersion)
self = bytes.__new__(cls, data)
self.nVersion = nVersion
return self
def to_bytes(self):
"""Convert to bytes instance
Note that it's the data represented that is converted; the checkum and
nVersion is not included.
"""
return b'' + self
def __str__(self):
"""Convert to string"""
vs = _bchr(self.nVersion) + self
check = bitcoin.core.Hash(vs)[0:4]
return encode(vs + check)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, str(self))
__all__ = (
'B58_DIGITS',
'Base58Error',
'InvalidBase58Error',
'encode',
'decode',
'Base58ChecksumError',
'CBase58Data',
)