forked from genjix/timelock
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathbase58.py
143 lines (112 loc) · 3.74 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
#
# 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.
#
"""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
import sys
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))