hive-nectar 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hive-nectar might be problematic. Click here for more details.
- hive_nectar-0.0.2.dist-info/METADATA +182 -0
- hive_nectar-0.0.2.dist-info/RECORD +86 -0
- hive_nectar-0.0.2.dist-info/WHEEL +4 -0
- hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
- hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +32 -0
- nectar/account.py +4371 -0
- nectar/amount.py +475 -0
- nectar/asciichart.py +270 -0
- nectar/asset.py +82 -0
- nectar/block.py +446 -0
- nectar/blockchain.py +1178 -0
- nectar/blockchaininstance.py +2284 -0
- nectar/blockchainobject.py +221 -0
- nectar/blurt.py +563 -0
- nectar/cli.py +6285 -0
- nectar/comment.py +1217 -0
- nectar/community.py +513 -0
- nectar/constants.py +111 -0
- nectar/conveyor.py +309 -0
- nectar/discussions.py +1709 -0
- nectar/exceptions.py +149 -0
- nectar/hive.py +546 -0
- nectar/hivesigner.py +420 -0
- nectar/imageuploader.py +72 -0
- nectar/instance.py +129 -0
- nectar/market.py +1013 -0
- nectar/memo.py +449 -0
- nectar/message.py +357 -0
- nectar/nodelist.py +444 -0
- nectar/price.py +557 -0
- nectar/profile.py +65 -0
- nectar/rc.py +308 -0
- nectar/snapshot.py +726 -0
- nectar/steem.py +582 -0
- nectar/storage.py +53 -0
- nectar/transactionbuilder.py +622 -0
- nectar/utils.py +545 -0
- nectar/version.py +2 -0
- nectar/vote.py +557 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +617 -0
- nectarapi/__init__.py +11 -0
- nectarapi/exceptions.py +123 -0
- nectarapi/graphenerpc.py +589 -0
- nectarapi/node.py +178 -0
- nectarapi/noderpc.py +229 -0
- nectarapi/rpcutils.py +97 -0
- nectarapi/version.py +2 -0
- nectarbase/__init__.py +14 -0
- nectarbase/ledgertransactions.py +75 -0
- nectarbase/memo.py +243 -0
- nectarbase/objects.py +429 -0
- nectarbase/objecttypes.py +22 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1297 -0
- nectarbase/signedtransactions.py +48 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +2 -0
- nectargrapheneapi/__init__.py +6 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +846 -0
- nectargraphenebase/aes.py +52 -0
- nectargraphenebase/base58.py +192 -0
- nectargraphenebase/bip32.py +494 -0
- nectargraphenebase/bip38.py +134 -0
- nectargraphenebase/chains.py +149 -0
- nectargraphenebase/dictionary.py +3 -0
- nectargraphenebase/ecdsasig.py +326 -0
- nectargraphenebase/objects.py +123 -0
- nectargraphenebase/objecttypes.py +6 -0
- nectargraphenebase/operationids.py +3 -0
- nectargraphenebase/operations.py +23 -0
- nectargraphenebase/prefix.py +11 -0
- nectargraphenebase/py23.py +38 -0
- nectargraphenebase/signedtransactions.py +201 -0
- nectargraphenebase/types.py +419 -0
- nectargraphenebase/unsignedtransactions.py +283 -0
- nectargraphenebase/version.py +2 -0
- nectarstorage/__init__.py +38 -0
- nectarstorage/base.py +306 -0
- nectarstorage/exceptions.py +16 -0
- nectarstorage/interfaces.py +237 -0
- nectarstorage/masterpassword.py +239 -0
- nectarstorage/ram.py +30 -0
- nectarstorage/sqlite.py +334 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import binascii
|
|
3
|
+
import bisect
|
|
4
|
+
import codecs
|
|
5
|
+
import hashlib
|
|
6
|
+
import itertools
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import unicodedata
|
|
11
|
+
from binascii import hexlify, unhexlify
|
|
12
|
+
|
|
13
|
+
import ecdsa
|
|
14
|
+
|
|
15
|
+
from .base58 import Base58, doublesha256, ripemd160
|
|
16
|
+
from .bip32 import BIP32Key, parse_path
|
|
17
|
+
from .dictionary import words as BrainKeyDictionary
|
|
18
|
+
from .dictionary import words_bip39 as MnemonicDictionary
|
|
19
|
+
from .prefix import Prefix
|
|
20
|
+
from .py23 import PY2, py23_bytes
|
|
21
|
+
|
|
22
|
+
PBKDF2_ROUNDS = 2048
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# From <https://stackoverflow.com/questions/212358/binary-search-bisection-in-python/2233940#2233940>
|
|
26
|
+
def binary_search(a, x, lo=0, hi=None): # can't use a to specify default for hi
|
|
27
|
+
hi = hi if hi is not None else len(a) # hi defaults to len(a)
|
|
28
|
+
pos = bisect.bisect_left(a, x, lo, hi) # find insertion position
|
|
29
|
+
return pos if pos != hi and a[pos] == x else -1 # don't walk off the end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PasswordKey(Prefix):
|
|
33
|
+
"""This class derives a private key given the account name, the
|
|
34
|
+
role and a password. It leverages the technology of Brainkeys
|
|
35
|
+
and allows people to have a secure private key by providing a
|
|
36
|
+
passphrase only.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, account, password, role="active", prefix=None):
|
|
40
|
+
self.set_prefix(prefix)
|
|
41
|
+
self.account = account
|
|
42
|
+
self.role = role
|
|
43
|
+
self.password = password
|
|
44
|
+
|
|
45
|
+
def normalize(self, seed):
|
|
46
|
+
"""Correct formating with single whitespace syntax and no trailing space"""
|
|
47
|
+
return " ".join(re.compile("[\t\n\v\f\r ]+").split(seed))
|
|
48
|
+
|
|
49
|
+
def get_private(self):
|
|
50
|
+
"""Derive private key from the account, the role and the password"""
|
|
51
|
+
if self.account is None and self.role is None:
|
|
52
|
+
seed = self.password
|
|
53
|
+
elif self.account == "" and self.role == "":
|
|
54
|
+
seed = self.password
|
|
55
|
+
else:
|
|
56
|
+
seed = self.account + self.role + self.password
|
|
57
|
+
seed = self.normalize(seed)
|
|
58
|
+
a = py23_bytes(seed, "utf8")
|
|
59
|
+
s = hashlib.sha256(a).digest()
|
|
60
|
+
return PrivateKey(hexlify(s).decode("ascii"), prefix=self.prefix)
|
|
61
|
+
|
|
62
|
+
def get_public(self):
|
|
63
|
+
return self.get_private().pubkey
|
|
64
|
+
|
|
65
|
+
def get_private_key(self):
|
|
66
|
+
return self.get_private()
|
|
67
|
+
|
|
68
|
+
def get_public_key(self):
|
|
69
|
+
return self.get_public()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BrainKey(Prefix):
|
|
73
|
+
"""Brainkey implementation similar to the graphene-ui web-wallet.
|
|
74
|
+
|
|
75
|
+
:param str brainkey: Brain Key
|
|
76
|
+
:param int sequence: Sequence number for consecutive keys
|
|
77
|
+
|
|
78
|
+
Keys in Graphene are derived from a seed brain key which is a string of
|
|
79
|
+
16 words out of a predefined dictionary with 49744 words. It is a
|
|
80
|
+
simple single-chain key derivation scheme that is not compatible with
|
|
81
|
+
BIP44 but easy to use.
|
|
82
|
+
|
|
83
|
+
Given the brain key, a private key is derived as::
|
|
84
|
+
|
|
85
|
+
privkey = SHA256(SHA512(brainkey + " " + sequence))
|
|
86
|
+
|
|
87
|
+
Incrementing the sequence number yields a new key that can be
|
|
88
|
+
regenerated given the brain key.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, brainkey=None, sequence=0, prefix=None):
|
|
93
|
+
self.set_prefix(prefix)
|
|
94
|
+
if not brainkey:
|
|
95
|
+
self.brainkey = self.suggest()
|
|
96
|
+
else:
|
|
97
|
+
self.brainkey = self.normalize(brainkey).strip()
|
|
98
|
+
self.sequence = sequence
|
|
99
|
+
|
|
100
|
+
def __next__(self):
|
|
101
|
+
"""Get the next private key (sequence number increment) for
|
|
102
|
+
iterators
|
|
103
|
+
"""
|
|
104
|
+
return self.next_sequence()
|
|
105
|
+
|
|
106
|
+
def next_sequence(self):
|
|
107
|
+
"""Increment the sequence number by 1"""
|
|
108
|
+
self.sequence += 1
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def normalize(self, brainkey):
|
|
112
|
+
"""Correct formating with single whitespace syntax and no trailing space"""
|
|
113
|
+
return " ".join(re.compile("[\t\n\v\f\r ]+").split(brainkey))
|
|
114
|
+
|
|
115
|
+
def get_brainkey(self):
|
|
116
|
+
"""Return brain key of this instance"""
|
|
117
|
+
return self.normalize(self.brainkey)
|
|
118
|
+
|
|
119
|
+
def get_private(self):
|
|
120
|
+
"""Derive private key from the brain key and the current sequence
|
|
121
|
+
number
|
|
122
|
+
"""
|
|
123
|
+
encoded = "%s %d" % (self.brainkey, self.sequence)
|
|
124
|
+
a = py23_bytes(encoded, "ascii")
|
|
125
|
+
s = hashlib.sha256(hashlib.sha512(a).digest()).digest()
|
|
126
|
+
return PrivateKey(hexlify(s).decode("ascii"), prefix=self.prefix)
|
|
127
|
+
|
|
128
|
+
def get_blind_private(self):
|
|
129
|
+
"""Derive private key from the brain key (and no sequence number)"""
|
|
130
|
+
a = py23_bytes(self.brainkey, "ascii")
|
|
131
|
+
return PrivateKey(hashlib.sha256(a).hexdigest(), prefix=self.prefix)
|
|
132
|
+
|
|
133
|
+
def get_public(self):
|
|
134
|
+
return self.get_private().pubkey
|
|
135
|
+
|
|
136
|
+
def get_private_key(self):
|
|
137
|
+
return self.get_private()
|
|
138
|
+
|
|
139
|
+
def get_public_key(self):
|
|
140
|
+
return self.get_public()
|
|
141
|
+
|
|
142
|
+
def suggest(self, word_count=16):
|
|
143
|
+
"""Suggest a new random brain key. Randomness is provided by the
|
|
144
|
+
operating system using ``os.urandom()``.
|
|
145
|
+
"""
|
|
146
|
+
brainkey = [None] * word_count
|
|
147
|
+
dict_lines = BrainKeyDictionary.split(",")
|
|
148
|
+
if not len(dict_lines) == 49744:
|
|
149
|
+
raise AssertionError()
|
|
150
|
+
for j in range(0, word_count):
|
|
151
|
+
urand = os.urandom(2)
|
|
152
|
+
if isinstance(urand, str):
|
|
153
|
+
urand = py23_bytes(urand, "ascii")
|
|
154
|
+
if PY2:
|
|
155
|
+
num = int(codecs.encode(urand[::-1], "hex"), 16)
|
|
156
|
+
else:
|
|
157
|
+
num = int.from_bytes(urand, byteorder="little")
|
|
158
|
+
rndMult = num / 2**16 # returns float between 0..1 (inclusive)
|
|
159
|
+
wIdx = int(round(len(dict_lines) * rndMult))
|
|
160
|
+
brainkey[j] = dict_lines[wIdx]
|
|
161
|
+
return " ".join(brainkey).upper()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# From https://github.com/trezor/python-mnemonic/blob/master/mnemonic/mnemonic.py
|
|
165
|
+
#
|
|
166
|
+
# Copyright (c) 2013 Pavol Rusnak
|
|
167
|
+
# Copyright (c) 2017 mruddy
|
|
168
|
+
class Mnemonic(object):
|
|
169
|
+
"""BIP39 mnemoric implementation"""
|
|
170
|
+
|
|
171
|
+
def __init__(self):
|
|
172
|
+
self.wordlist = MnemonicDictionary.split(",")
|
|
173
|
+
self.radix = 2048
|
|
174
|
+
|
|
175
|
+
def generate(self, strength=128):
|
|
176
|
+
"""Generates a word list based on the given strength
|
|
177
|
+
|
|
178
|
+
:param int strength: initial entropy strength, must be one of [128, 160, 192, 224, 256]
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
if strength not in [128, 160, 192, 224, 256]:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
"Strength should be one of the following [128, 160, 192, 224, 256], but it is not (%d)."
|
|
184
|
+
% strength
|
|
185
|
+
)
|
|
186
|
+
return self.to_mnemonic(os.urandom(strength // 8))
|
|
187
|
+
|
|
188
|
+
# Adapted from <http://tinyurl.com/oxmn476>
|
|
189
|
+
def to_entropy(self, words):
|
|
190
|
+
if not isinstance(words, list):
|
|
191
|
+
words = words.split(" ")
|
|
192
|
+
if len(words) not in [12, 15, 18, 21, 24]:
|
|
193
|
+
raise ValueError(
|
|
194
|
+
"Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)."
|
|
195
|
+
% len(words)
|
|
196
|
+
)
|
|
197
|
+
# Look up all the words in the list and construct the
|
|
198
|
+
# concatenation of the original entropy and the checksum.
|
|
199
|
+
concatLenBits = len(words) * 11
|
|
200
|
+
concatBits = [False] * concatLenBits
|
|
201
|
+
wordindex = 0
|
|
202
|
+
use_binary_search = True
|
|
203
|
+
for word in words:
|
|
204
|
+
# Find the words index in the wordlist
|
|
205
|
+
ndx = (
|
|
206
|
+
binary_search(self.wordlist, word)
|
|
207
|
+
if use_binary_search
|
|
208
|
+
else self.wordlist.index(word)
|
|
209
|
+
)
|
|
210
|
+
if ndx < 0:
|
|
211
|
+
raise LookupError('Unable to find "%s" in word list.' % word)
|
|
212
|
+
# Set the next 11 bits to the value of the index.
|
|
213
|
+
for ii in range(11):
|
|
214
|
+
concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0
|
|
215
|
+
wordindex += 1
|
|
216
|
+
checksumLengthBits = concatLenBits // 33
|
|
217
|
+
entropyLengthBits = concatLenBits - checksumLengthBits
|
|
218
|
+
# Extract original entropy as bytes.
|
|
219
|
+
entropy = bytearray(entropyLengthBits // 8)
|
|
220
|
+
for ii in range(len(entropy)):
|
|
221
|
+
for jj in range(8):
|
|
222
|
+
if concatBits[(ii * 8) + jj]:
|
|
223
|
+
entropy[ii] |= 1 << (7 - jj)
|
|
224
|
+
# Take the digest of the entropy.
|
|
225
|
+
hashBytes = hashlib.sha256(entropy).digest()
|
|
226
|
+
if sys.version < "3":
|
|
227
|
+
hashBits = list(
|
|
228
|
+
itertools.chain.from_iterable(
|
|
229
|
+
([ord(c) & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes)
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
hashBits = list(
|
|
234
|
+
itertools.chain.from_iterable(
|
|
235
|
+
([c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes)
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
# Check all the checksum bits.
|
|
239
|
+
for i in range(checksumLengthBits):
|
|
240
|
+
if concatBits[entropyLengthBits + i] != hashBits[i]:
|
|
241
|
+
raise ValueError("Failed checksum.")
|
|
242
|
+
return entropy
|
|
243
|
+
|
|
244
|
+
def to_mnemonic(self, data):
|
|
245
|
+
if len(data) not in [16, 20, 24, 28, 32]:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not (%d)."
|
|
248
|
+
% len(data)
|
|
249
|
+
)
|
|
250
|
+
h = hashlib.sha256(data).hexdigest()
|
|
251
|
+
b = (
|
|
252
|
+
bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
|
|
253
|
+
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
|
|
254
|
+
)
|
|
255
|
+
result = []
|
|
256
|
+
for i in range(len(b) // 11):
|
|
257
|
+
idx = int(b[i * 11 : (i + 1) * 11], 2)
|
|
258
|
+
result.append(self.wordlist[idx])
|
|
259
|
+
|
|
260
|
+
result_phrase = " ".join(result)
|
|
261
|
+
return result_phrase
|
|
262
|
+
|
|
263
|
+
def check(self, mnemonic):
|
|
264
|
+
"""Checks the mnemonic word list is valid
|
|
265
|
+
:param list mnemonic: mnemonic word list with lenght of 12, 15, 18, 21, 24
|
|
266
|
+
:returns: True, when valid
|
|
267
|
+
"""
|
|
268
|
+
mnemonic = self.normalize_string(mnemonic).split(" ")
|
|
269
|
+
# list of valid mnemonic lengths
|
|
270
|
+
if len(mnemonic) not in [12, 15, 18, 21, 24]:
|
|
271
|
+
return False
|
|
272
|
+
try:
|
|
273
|
+
idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
|
|
274
|
+
b = "".join(idx)
|
|
275
|
+
except ValueError:
|
|
276
|
+
return False
|
|
277
|
+
l = len(b) # noqa: E741
|
|
278
|
+
d = b[: l // 33 * 32]
|
|
279
|
+
h = b[-l // 33 :]
|
|
280
|
+
nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip("L").zfill(l // 33 * 8))
|
|
281
|
+
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
|
|
282
|
+
return h == nh
|
|
283
|
+
|
|
284
|
+
def check_word(self, word):
|
|
285
|
+
return word in self.wordlist
|
|
286
|
+
|
|
287
|
+
def expand_word(self, prefix):
|
|
288
|
+
"""Expands a word when sufficient chars are given
|
|
289
|
+
|
|
290
|
+
:param str prefix: first chars of a valid dict word
|
|
291
|
+
|
|
292
|
+
"""
|
|
293
|
+
if prefix in self.wordlist:
|
|
294
|
+
return prefix
|
|
295
|
+
else:
|
|
296
|
+
matches = [word for word in self.wordlist if word.startswith(prefix)]
|
|
297
|
+
if len(matches) == 1: # matched exactly one word in the wordlist
|
|
298
|
+
return matches[0]
|
|
299
|
+
else:
|
|
300
|
+
# exact match not found.
|
|
301
|
+
# this is not a validation routine, just return the input
|
|
302
|
+
return prefix
|
|
303
|
+
|
|
304
|
+
def expand(self, mnemonic):
|
|
305
|
+
"""Expands all words given in a list"""
|
|
306
|
+
return " ".join(map(self.expand_word, mnemonic.split(" ")))
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def normalize_string(cls, txt):
|
|
310
|
+
"""Normalizes strings"""
|
|
311
|
+
if isinstance(txt, str if sys.version < "3" else bytes):
|
|
312
|
+
utxt = txt.decode("utf8")
|
|
313
|
+
elif isinstance(txt, unicode if sys.version < "3" else str): # noqa: F821
|
|
314
|
+
utxt = txt
|
|
315
|
+
else:
|
|
316
|
+
raise TypeError("String value expected")
|
|
317
|
+
|
|
318
|
+
return unicodedata.normalize("NFKD", utxt)
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def to_seed(cls, mnemonic, passphrase=""):
|
|
322
|
+
"""Returns a seed based on bip39
|
|
323
|
+
|
|
324
|
+
:param str mnemonic: string containing a valid mnemonic word list
|
|
325
|
+
:param str passphrase: optional, passphrase can be set to modify the returned seed.
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
mnemonic = cls.normalize_string(mnemonic)
|
|
329
|
+
passphrase = cls.normalize_string(passphrase)
|
|
330
|
+
passphrase = "mnemonic" + passphrase
|
|
331
|
+
mnemonic = mnemonic.encode("utf-8")
|
|
332
|
+
passphrase = passphrase.encode("utf-8")
|
|
333
|
+
stretched = hashlib.pbkdf2_hmac("sha512", mnemonic, passphrase, PBKDF2_ROUNDS)
|
|
334
|
+
return stretched[:64]
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class MnemonicKey(Prefix):
|
|
338
|
+
"""This class derives a private key from a BIP39 mnemoric implementation"""
|
|
339
|
+
|
|
340
|
+
def __init__(
|
|
341
|
+
self, word_list=None, passphrase="", account_sequence=0, key_sequence=0, prefix=None
|
|
342
|
+
):
|
|
343
|
+
self.set_prefix(prefix)
|
|
344
|
+
if word_list is not None:
|
|
345
|
+
self.set_mnemonic(word_list, passphrase=passphrase)
|
|
346
|
+
else:
|
|
347
|
+
self.seed = None
|
|
348
|
+
self.account_sequence = account_sequence
|
|
349
|
+
self.key_sequence = key_sequence
|
|
350
|
+
self.prefix = prefix
|
|
351
|
+
self.path = "m/48'/13'/0'/%d'/%d'" % (self.account_sequence, self.key_sequence)
|
|
352
|
+
|
|
353
|
+
def set_mnemonic(self, word_list, passphrase=""):
|
|
354
|
+
mnemonic = Mnemonic()
|
|
355
|
+
if not mnemonic.check(word_list):
|
|
356
|
+
raise ValueError("Word list is not valid!")
|
|
357
|
+
self.seed = mnemonic.to_seed(word_list, passphrase=passphrase)
|
|
358
|
+
|
|
359
|
+
def generate_mnemonic(self, passphrase="", strength=256):
|
|
360
|
+
mnemonic = Mnemonic()
|
|
361
|
+
word_list = mnemonic.generate(strength=strength)
|
|
362
|
+
self.seed = mnemonic.to_seed(word_list, passphrase=passphrase)
|
|
363
|
+
return word_list
|
|
364
|
+
|
|
365
|
+
def set_path_BIP32(self, path):
|
|
366
|
+
self.path = path
|
|
367
|
+
|
|
368
|
+
def set_path_BIP44(
|
|
369
|
+
self, account_sequence=0, chain_sequence=0, key_sequence=0, hardened_address=True
|
|
370
|
+
):
|
|
371
|
+
if account_sequence < 0:
|
|
372
|
+
raise ValueError("account_sequence must be >= 0")
|
|
373
|
+
if key_sequence < 0:
|
|
374
|
+
raise ValueError("key_sequence must be >= 0")
|
|
375
|
+
if chain_sequence < 0:
|
|
376
|
+
raise ValueError("chain_sequence must be >= 0")
|
|
377
|
+
self.account_sequence = account_sequence
|
|
378
|
+
self.key_sequence = key_sequence
|
|
379
|
+
if hardened_address:
|
|
380
|
+
self.path = "m/44'/0'/%d'/%d/%d'" % (
|
|
381
|
+
self.account_sequence,
|
|
382
|
+
chain_sequence,
|
|
383
|
+
self.key_sequence,
|
|
384
|
+
)
|
|
385
|
+
else:
|
|
386
|
+
self.path = "m/44'/0'/%d'/%d/%d" % (
|
|
387
|
+
self.account_sequence,
|
|
388
|
+
chain_sequence,
|
|
389
|
+
self.key_sequence,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def set_path_BIP48(self, network_index=13, role="owner", account_sequence=0, key_sequence=0):
|
|
393
|
+
if account_sequence < 0:
|
|
394
|
+
raise ValueError("account_sequence must be >= 0")
|
|
395
|
+
if key_sequence < 0:
|
|
396
|
+
raise ValueError("key_sequence must be >= 0")
|
|
397
|
+
if network_index < 0:
|
|
398
|
+
raise ValueError("network_index must be >= 0")
|
|
399
|
+
if isinstance(role, str) and role not in ["owner", "active", "posting", "memo"]:
|
|
400
|
+
raise ValueError("Wrong role!")
|
|
401
|
+
elif isinstance(role, int) and role < 0:
|
|
402
|
+
raise ValueError("role must be >= 0")
|
|
403
|
+
if role == "owner":
|
|
404
|
+
role = 0
|
|
405
|
+
elif role == "active":
|
|
406
|
+
role = 1
|
|
407
|
+
elif role == "posting":
|
|
408
|
+
role = 4
|
|
409
|
+
elif role == "memo":
|
|
410
|
+
role = 3
|
|
411
|
+
|
|
412
|
+
self.account_sequence = account_sequence
|
|
413
|
+
self.key_sequence = key_sequence
|
|
414
|
+
self.path = "m/48'/%d'/%d'/%d'/%d'" % (
|
|
415
|
+
network_index,
|
|
416
|
+
role,
|
|
417
|
+
self.account_sequence,
|
|
418
|
+
self.key_sequence,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def next_account_sequence(self):
|
|
422
|
+
"""Increment the account sequence number by 1"""
|
|
423
|
+
self.account_sequence += 1
|
|
424
|
+
return self
|
|
425
|
+
|
|
426
|
+
def next_sequence(self):
|
|
427
|
+
"""Increment the key sequence number by 1"""
|
|
428
|
+
self.key_sequence += 1
|
|
429
|
+
return self
|
|
430
|
+
|
|
431
|
+
def set_path(self, path):
|
|
432
|
+
self.path = path
|
|
433
|
+
|
|
434
|
+
def get_path(self):
|
|
435
|
+
return self.path
|
|
436
|
+
|
|
437
|
+
def get_private(self):
|
|
438
|
+
"""Derive private key from the account_sequence, the role and the key_sequence"""
|
|
439
|
+
if self.seed is None:
|
|
440
|
+
raise ValueError("seed is None, set or generate a mnemnoric first")
|
|
441
|
+
key = BIP32Key.fromEntropy(self.seed)
|
|
442
|
+
for n in parse_path(self.get_path()):
|
|
443
|
+
key = key.ChildKey(n)
|
|
444
|
+
return PrivateKey(key.WalletImportFormat(), prefix=self.prefix)
|
|
445
|
+
|
|
446
|
+
def get_public(self):
|
|
447
|
+
return self.get_private().pubkey
|
|
448
|
+
|
|
449
|
+
def get_private_key(self):
|
|
450
|
+
return self.get_private()
|
|
451
|
+
|
|
452
|
+
def get_public_key(self):
|
|
453
|
+
return self.get_public()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class Address(Prefix):
|
|
457
|
+
"""Address class
|
|
458
|
+
|
|
459
|
+
This class serves as an address representation for Public Keys.
|
|
460
|
+
|
|
461
|
+
:param str address: Base58 encoded address (defaults to ``None``)
|
|
462
|
+
:param str prefix: Network prefix (defaults to ``STM``)
|
|
463
|
+
|
|
464
|
+
Example::
|
|
465
|
+
|
|
466
|
+
Address("STMFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi")
|
|
467
|
+
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
def __init__(self, address, prefix=None):
|
|
471
|
+
self.set_prefix(prefix)
|
|
472
|
+
self._address = Base58(address, prefix=self.prefix)
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
def from_pubkey(cls, pubkey, compressed=True, version=56, prefix=None):
|
|
476
|
+
"""Load an address provided by the public key.
|
|
477
|
+
Version: 56 => PTS
|
|
478
|
+
"""
|
|
479
|
+
# Ensure this is a public key
|
|
480
|
+
pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
|
|
481
|
+
if compressed:
|
|
482
|
+
pubkey_plain = pubkey.compressed()
|
|
483
|
+
else:
|
|
484
|
+
pubkey_plain = pubkey.uncompressed()
|
|
485
|
+
sha = hashlib.sha256(unhexlify(pubkey_plain)).hexdigest()
|
|
486
|
+
rep = hexlify(ripemd160(sha)).decode("ascii")
|
|
487
|
+
s = ("%.2x" % version) + rep
|
|
488
|
+
result = s + hexlify(doublesha256(s)[:4]).decode("ascii")
|
|
489
|
+
result = hexlify(ripemd160(result)).decode("ascii")
|
|
490
|
+
return cls(result, prefix=pubkey.prefix)
|
|
491
|
+
|
|
492
|
+
@classmethod
|
|
493
|
+
def derivesha256address(cls, pubkey, compressed=True, prefix=None):
|
|
494
|
+
"""Derive address using ``RIPEMD160(SHA256(x))``"""
|
|
495
|
+
pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
|
|
496
|
+
if compressed:
|
|
497
|
+
pubkey_plain = pubkey.compressed()
|
|
498
|
+
else:
|
|
499
|
+
pubkey_plain = pubkey.uncompressed()
|
|
500
|
+
pkbin = unhexlify(repr(pubkey_plain))
|
|
501
|
+
result = hexlify(hashlib.sha256(pkbin).digest())
|
|
502
|
+
result = hexlify(ripemd160(result)).decode("ascii")
|
|
503
|
+
return cls(result, prefix=pubkey.prefix)
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def derivesha512address(cls, pubkey, compressed=True, prefix=None):
|
|
507
|
+
"""Derive address using ``RIPEMD160(SHA512(x))``"""
|
|
508
|
+
pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
|
|
509
|
+
if compressed:
|
|
510
|
+
pubkey_plain = pubkey.compressed()
|
|
511
|
+
else:
|
|
512
|
+
pubkey_plain = pubkey.uncompressed()
|
|
513
|
+
pkbin = unhexlify(repr(pubkey_plain))
|
|
514
|
+
result = hexlify(hashlib.sha512(pkbin).digest())
|
|
515
|
+
result = hexlify(ripemd160(result)).decode("ascii")
|
|
516
|
+
return cls(result, prefix=pubkey.prefix)
|
|
517
|
+
|
|
518
|
+
def __repr__(self):
|
|
519
|
+
"""Gives the hex representation of the ``GrapheneBase58CheckEncoded``
|
|
520
|
+
Graphene address.
|
|
521
|
+
"""
|
|
522
|
+
return repr(self._address)
|
|
523
|
+
|
|
524
|
+
def __str__(self):
|
|
525
|
+
"""Returns the readable Graphene address. This call is equivalent to
|
|
526
|
+
``format(Address, "STM")``
|
|
527
|
+
"""
|
|
528
|
+
return format(self._address, self.prefix)
|
|
529
|
+
|
|
530
|
+
def __format__(self, _format):
|
|
531
|
+
"""May be issued to get valid "MUSE", "PLAY" or any other Graphene compatible
|
|
532
|
+
address with corresponding prefix.
|
|
533
|
+
"""
|
|
534
|
+
return format(self._address, _format)
|
|
535
|
+
|
|
536
|
+
def __bytes__(self):
|
|
537
|
+
"""Returns the raw content of the ``Base58CheckEncoded`` address"""
|
|
538
|
+
return py23_bytes(self._address)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class GrapheneAddress(Address):
|
|
542
|
+
"""Graphene Addresses are different. Hence we have a different class"""
|
|
543
|
+
|
|
544
|
+
@classmethod
|
|
545
|
+
def from_pubkey(cls, pubkey, compressed=True, version=56, prefix=None):
|
|
546
|
+
# Ensure this is a public key
|
|
547
|
+
pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
|
|
548
|
+
if compressed:
|
|
549
|
+
pubkey_plain = pubkey.compressed()
|
|
550
|
+
else:
|
|
551
|
+
pubkey_plain = pubkey.uncompressed()
|
|
552
|
+
|
|
553
|
+
""" Derive address using ``RIPEMD160(SHA512(x))`` """
|
|
554
|
+
addressbin = ripemd160(hashlib.sha512(unhexlify(pubkey_plain)).hexdigest())
|
|
555
|
+
result = Base58(hexlify(addressbin).decode("ascii"))
|
|
556
|
+
return cls(result, prefix=pubkey.prefix)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class PublicKey(Prefix):
|
|
560
|
+
"""This class deals with Public Keys and inherits ``Address``.
|
|
561
|
+
|
|
562
|
+
:param str pk: Base58 encoded public key
|
|
563
|
+
:param str prefix: Network prefix (defaults to ``STM``)
|
|
564
|
+
|
|
565
|
+
Example::
|
|
566
|
+
|
|
567
|
+
PublicKey("STM6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL")
|
|
568
|
+
|
|
569
|
+
.. note:: By default, graphene-based networks deal with **compressed**
|
|
570
|
+
public keys. If an **uncompressed** key is required, the
|
|
571
|
+
method :func:`unCompressed` can be used::
|
|
572
|
+
|
|
573
|
+
PublicKey("xxxxx").unCompressed()
|
|
574
|
+
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
def __init__(self, pk, prefix=None):
|
|
578
|
+
"""Init PublicKey
|
|
579
|
+
:param str pk: Base58 encoded public key
|
|
580
|
+
:param str prefix: Network prefix (defaults to ``STM``)
|
|
581
|
+
"""
|
|
582
|
+
self.set_prefix(prefix)
|
|
583
|
+
if isinstance(pk, PublicKey):
|
|
584
|
+
pk = format(pk, self.prefix)
|
|
585
|
+
|
|
586
|
+
if str(pk).startswith("04"):
|
|
587
|
+
# We only ever deal with compressed keys, so let's make it
|
|
588
|
+
# compressed
|
|
589
|
+
order = ecdsa.SECP256k1.order
|
|
590
|
+
p = ecdsa.VerifyingKey.from_string(
|
|
591
|
+
unhexlify(pk[2:]), curve=ecdsa.SECP256k1
|
|
592
|
+
).pubkey.point
|
|
593
|
+
x_str = ecdsa.util.number_to_string(p.x(), order)
|
|
594
|
+
pk = hexlify(chr(2 + (p.y() & 1)).encode("ascii") + x_str).decode("ascii")
|
|
595
|
+
|
|
596
|
+
self._pk = Base58(pk, prefix=self.prefix)
|
|
597
|
+
|
|
598
|
+
@property
|
|
599
|
+
def pubkey(self):
|
|
600
|
+
return self._pk
|
|
601
|
+
|
|
602
|
+
def get_public_key(self):
|
|
603
|
+
"""Returns the pubkey"""
|
|
604
|
+
return self.pubkey
|
|
605
|
+
|
|
606
|
+
@property
|
|
607
|
+
def compressed_key(self):
|
|
608
|
+
return PublicKey(self.compressed())
|
|
609
|
+
|
|
610
|
+
def _derive_y_from_x(self, x, is_even):
|
|
611
|
+
"""Derive y point from x point"""
|
|
612
|
+
curve = ecdsa.SECP256k1.curve
|
|
613
|
+
# The curve equation over F_p is:
|
|
614
|
+
# y^2 = x^3 + ax + b
|
|
615
|
+
a, b, p = curve.a(), curve.b(), curve.p()
|
|
616
|
+
alpha = (pow(x, 3, p) + a * x + b) % p
|
|
617
|
+
beta = ecdsa.numbertheory.square_root_mod_prime(alpha, p)
|
|
618
|
+
if (beta % 2) == is_even:
|
|
619
|
+
beta = p - beta
|
|
620
|
+
return beta
|
|
621
|
+
|
|
622
|
+
def compressed(self):
|
|
623
|
+
"""Derive compressed public key"""
|
|
624
|
+
return repr(self._pk)
|
|
625
|
+
|
|
626
|
+
def uncompressed(self):
|
|
627
|
+
"""Derive uncompressed key"""
|
|
628
|
+
public_key = repr(self._pk)
|
|
629
|
+
prefix = public_key[0:2]
|
|
630
|
+
if prefix == "04":
|
|
631
|
+
return public_key
|
|
632
|
+
if not (prefix == "02" or prefix == "03"):
|
|
633
|
+
raise AssertionError()
|
|
634
|
+
x = int(public_key[2:], 16)
|
|
635
|
+
y = self._derive_y_from_x(x, (prefix == "02"))
|
|
636
|
+
key = "04" + "%064x" % x + "%064x" % y
|
|
637
|
+
return key
|
|
638
|
+
|
|
639
|
+
def point(self):
|
|
640
|
+
"""Return the point for the public key"""
|
|
641
|
+
string = unhexlify(self.uncompressed())
|
|
642
|
+
return ecdsa.VerifyingKey.from_string(string[1:], curve=ecdsa.SECP256k1).pubkey.point
|
|
643
|
+
|
|
644
|
+
def child(self, offset256):
|
|
645
|
+
"""Derive new public key from this key and a sha256 "offset" """
|
|
646
|
+
a = bytes(self) + offset256
|
|
647
|
+
s = hashlib.sha256(a).digest()
|
|
648
|
+
return self.add(s)
|
|
649
|
+
|
|
650
|
+
def add(self, digest256):
|
|
651
|
+
"""Derive new public key from this key and a sha256 "digest" """
|
|
652
|
+
from .ecdsa import tweakaddPubkey
|
|
653
|
+
|
|
654
|
+
return tweakaddPubkey(self, digest256)
|
|
655
|
+
|
|
656
|
+
@classmethod
|
|
657
|
+
def from_privkey(cls, privkey, prefix=None):
|
|
658
|
+
"""Derive uncompressed public key"""
|
|
659
|
+
privkey = PrivateKey(privkey, prefix=prefix or Prefix.prefix)
|
|
660
|
+
secret = unhexlify(repr(privkey))
|
|
661
|
+
order = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).curve.generator.order()
|
|
662
|
+
p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point
|
|
663
|
+
x_str = ecdsa.util.number_to_string(p.x(), order)
|
|
664
|
+
# y_str = ecdsa.util.number_to_string(p.y(), order)
|
|
665
|
+
compressed = hexlify(chr(2 + (p.y() & 1)).encode("ascii") + x_str).decode("ascii")
|
|
666
|
+
# uncompressed = hexlify(
|
|
667
|
+
# chr(4).encode('ascii') + x_str + y_str).decode('ascii')
|
|
668
|
+
return cls(compressed, prefix=prefix or Prefix.prefix)
|
|
669
|
+
|
|
670
|
+
def __repr__(self):
|
|
671
|
+
"""Gives the hex representation of the Graphene public key."""
|
|
672
|
+
return repr(self._pk)
|
|
673
|
+
|
|
674
|
+
def __str__(self):
|
|
675
|
+
"""Returns the readable Graphene public key. This call is equivalent to
|
|
676
|
+
``format(PublicKey, "STM")``
|
|
677
|
+
"""
|
|
678
|
+
return format(self._pk, self.prefix)
|
|
679
|
+
|
|
680
|
+
def __format__(self, _format):
|
|
681
|
+
"""Formats the instance of:doc:`Base58 <base58>` according to ``_format``"""
|
|
682
|
+
return format(self._pk, _format)
|
|
683
|
+
|
|
684
|
+
def __bytes__(self):
|
|
685
|
+
"""Returns the raw public key (has length 33)"""
|
|
686
|
+
return py23_bytes(self._pk)
|
|
687
|
+
|
|
688
|
+
def __lt__(self, other):
|
|
689
|
+
"""For sorting of public keys (due to graphene),
|
|
690
|
+
we actually sort according to addresses
|
|
691
|
+
"""
|
|
692
|
+
assert isinstance(other, PublicKey)
|
|
693
|
+
return repr(self.address) < repr(other.address)
|
|
694
|
+
|
|
695
|
+
def unCompressed(self):
|
|
696
|
+
"""Alias for self.uncompressed() - LEGACY"""
|
|
697
|
+
return self.uncompressed()
|
|
698
|
+
|
|
699
|
+
@property
|
|
700
|
+
def address(self):
|
|
701
|
+
"""Obtain a GrapheneAddress from a public key"""
|
|
702
|
+
return GrapheneAddress.from_pubkey(repr(self), prefix=self.prefix)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class PrivateKey(Prefix):
|
|
706
|
+
"""Derives the compressed and uncompressed public keys and
|
|
707
|
+
constructs two instances of :class:`PublicKey`:
|
|
708
|
+
|
|
709
|
+
:param str wif: Base58check-encoded wif key
|
|
710
|
+
:param str prefix: Network prefix (defaults to ``STM``)
|
|
711
|
+
|
|
712
|
+
Example::
|
|
713
|
+
|
|
714
|
+
PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd")
|
|
715
|
+
|
|
716
|
+
Compressed vs. Uncompressed:
|
|
717
|
+
|
|
718
|
+
* ``PrivateKey("w-i-f").pubkey``:
|
|
719
|
+
Instance of :class:`PublicKey` using compressed key.
|
|
720
|
+
* ``PrivateKey("w-i-f").pubkey.address``:
|
|
721
|
+
Instance of :class:`Address` using compressed key.
|
|
722
|
+
* ``PrivateKey("w-i-f").uncompressed``:
|
|
723
|
+
Instance of :class:`PublicKey` using uncompressed key.
|
|
724
|
+
* ``PrivateKey("w-i-f").uncompressed.address``:
|
|
725
|
+
Instance of :class:`Address` using uncompressed key.
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
def __init__(self, wif=None, prefix=None):
|
|
730
|
+
self.set_prefix(prefix)
|
|
731
|
+
if wif is None:
|
|
732
|
+
import os
|
|
733
|
+
|
|
734
|
+
self._wif = Base58(hexlify(os.urandom(32)).decode("ascii"))
|
|
735
|
+
elif isinstance(wif, PrivateKey):
|
|
736
|
+
self._wif = wif._wif
|
|
737
|
+
elif isinstance(wif, Base58):
|
|
738
|
+
self._wif = wif
|
|
739
|
+
else:
|
|
740
|
+
self._wif = Base58(wif)
|
|
741
|
+
|
|
742
|
+
assert len(repr(self._wif)) == 64
|
|
743
|
+
|
|
744
|
+
@property
|
|
745
|
+
def bitcoin(self):
|
|
746
|
+
return BitcoinPublicKey.from_privkey(self)
|
|
747
|
+
|
|
748
|
+
@property
|
|
749
|
+
def address(self):
|
|
750
|
+
return Address.from_pubkey(self.pubkey, prefix=self.prefix)
|
|
751
|
+
|
|
752
|
+
@property
|
|
753
|
+
def pubkey(self):
|
|
754
|
+
return self.compressed
|
|
755
|
+
|
|
756
|
+
def get_public_key(self):
|
|
757
|
+
"""Legacy: Returns the pubkey"""
|
|
758
|
+
return self.pubkey
|
|
759
|
+
|
|
760
|
+
@property
|
|
761
|
+
def compressed(self):
|
|
762
|
+
return PublicKey.from_privkey(self, prefix=self.prefix)
|
|
763
|
+
|
|
764
|
+
@property
|
|
765
|
+
def uncompressed(self):
|
|
766
|
+
return PublicKey(self.pubkey.uncompressed(), prefix=self.prefix)
|
|
767
|
+
|
|
768
|
+
def get_secret(self):
|
|
769
|
+
"""Get sha256 digest of the wif key."""
|
|
770
|
+
return hashlib.sha256(bytes(self)).digest()
|
|
771
|
+
|
|
772
|
+
def derive_private_key(self, sequence):
|
|
773
|
+
"""Derive new private key from this private key and an arbitrary
|
|
774
|
+
sequence number
|
|
775
|
+
"""
|
|
776
|
+
encoded = "%s %d" % (str(self), sequence)
|
|
777
|
+
a = py23_bytes(encoded, "ascii")
|
|
778
|
+
s = hashlib.sha256(hashlib.sha512(a).digest()).digest()
|
|
779
|
+
return PrivateKey(hexlify(s).decode("ascii"), prefix=self.pubkey.prefix)
|
|
780
|
+
|
|
781
|
+
def child(self, offset256):
|
|
782
|
+
"""Derive new private key from this key and a sha256 "offset" """
|
|
783
|
+
a = py23_bytes(self.pubkey) + offset256
|
|
784
|
+
s = hashlib.sha256(a).digest()
|
|
785
|
+
return self.derive_from_seed(s)
|
|
786
|
+
|
|
787
|
+
def derive_from_seed(self, offset):
|
|
788
|
+
"""Derive private key using "generate_from_seed" method.
|
|
789
|
+
Here, the key itself serves as a `seed`, and `offset`
|
|
790
|
+
is expected to be a sha256 digest.
|
|
791
|
+
"""
|
|
792
|
+
seed = int(hexlify(py23_bytes(self)).decode("ascii"), 16)
|
|
793
|
+
z = int(hexlify(offset).decode("ascii"), 16)
|
|
794
|
+
order = ecdsa.SECP256k1.order
|
|
795
|
+
secexp = (seed + z) % order
|
|
796
|
+
secret = "%0x" % secexp
|
|
797
|
+
if len(secret) < 64: # left-pad with zeroes
|
|
798
|
+
secret = ("0" * (64 - len(secret))) + secret
|
|
799
|
+
return PrivateKey(secret, prefix=self.pubkey.prefix)
|
|
800
|
+
|
|
801
|
+
def __format__(self, _format):
|
|
802
|
+
"""Formats the instance of:doc:`Base58 <base58>` according to
|
|
803
|
+
``_format``
|
|
804
|
+
"""
|
|
805
|
+
return format(self._wif, _format)
|
|
806
|
+
|
|
807
|
+
def __repr__(self):
|
|
808
|
+
"""Gives the hex representation of the Graphene private key."""
|
|
809
|
+
return repr(self._wif)
|
|
810
|
+
|
|
811
|
+
def __str__(self):
|
|
812
|
+
"""Returns the readable (uncompressed wif format) Graphene private key. This
|
|
813
|
+
call is equivalent to ``format(PrivateKey, "WIF")``
|
|
814
|
+
"""
|
|
815
|
+
return format(self._wif, "WIF")
|
|
816
|
+
|
|
817
|
+
def __bytes__(self):
|
|
818
|
+
"""Returns the raw private key"""
|
|
819
|
+
return py23_bytes(self._wif)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
class BitcoinAddress(Address):
|
|
823
|
+
@classmethod
|
|
824
|
+
def from_pubkey(cls, pubkey, compressed=False, version=56, prefix=None):
|
|
825
|
+
# Ensure this is a public key
|
|
826
|
+
pubkey = PublicKey(pubkey)
|
|
827
|
+
if compressed:
|
|
828
|
+
pubkey = pubkey.compressed()
|
|
829
|
+
else:
|
|
830
|
+
pubkey = pubkey.uncompressed()
|
|
831
|
+
|
|
832
|
+
""" Derive address using ``RIPEMD160(SHA256(x))`` """
|
|
833
|
+
addressbin = ripemd160(hexlify(hashlib.sha256(unhexlify(pubkey)).digest()))
|
|
834
|
+
return cls(hexlify(addressbin).decode("ascii"))
|
|
835
|
+
|
|
836
|
+
def __str__(self):
|
|
837
|
+
"""Returns the readable Graphene address. This call is equivalent to
|
|
838
|
+
``format(Address, "GPH")``
|
|
839
|
+
"""
|
|
840
|
+
return format(self._address, "BTC")
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
class BitcoinPublicKey(PublicKey):
|
|
844
|
+
@property
|
|
845
|
+
def address(self):
|
|
846
|
+
return BitcoinAddress.from_pubkey(repr(self))
|