hive-nectar 0.2.9__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.
Files changed (87) hide show
  1. hive_nectar-0.2.9.dist-info/METADATA +194 -0
  2. hive_nectar-0.2.9.dist-info/RECORD +87 -0
  3. hive_nectar-0.2.9.dist-info/WHEEL +4 -0
  4. hive_nectar-0.2.9.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.2.9.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +37 -0
  7. nectar/account.py +5076 -0
  8. nectar/amount.py +553 -0
  9. nectar/asciichart.py +303 -0
  10. nectar/asset.py +122 -0
  11. nectar/block.py +574 -0
  12. nectar/blockchain.py +1242 -0
  13. nectar/blockchaininstance.py +2590 -0
  14. nectar/blockchainobject.py +263 -0
  15. nectar/cli.py +5937 -0
  16. nectar/comment.py +1552 -0
  17. nectar/community.py +854 -0
  18. nectar/constants.py +95 -0
  19. nectar/discussions.py +1437 -0
  20. nectar/exceptions.py +152 -0
  21. nectar/haf.py +381 -0
  22. nectar/hive.py +630 -0
  23. nectar/imageuploader.py +114 -0
  24. nectar/instance.py +113 -0
  25. nectar/market.py +876 -0
  26. nectar/memo.py +542 -0
  27. nectar/message.py +379 -0
  28. nectar/nodelist.py +309 -0
  29. nectar/price.py +603 -0
  30. nectar/profile.py +74 -0
  31. nectar/py.typed +0 -0
  32. nectar/rc.py +333 -0
  33. nectar/snapshot.py +1024 -0
  34. nectar/storage.py +62 -0
  35. nectar/transactionbuilder.py +659 -0
  36. nectar/utils.py +630 -0
  37. nectar/version.py +3 -0
  38. nectar/vote.py +722 -0
  39. nectar/wallet.py +472 -0
  40. nectar/witness.py +728 -0
  41. nectarapi/__init__.py +12 -0
  42. nectarapi/exceptions.py +126 -0
  43. nectarapi/graphenerpc.py +596 -0
  44. nectarapi/node.py +194 -0
  45. nectarapi/noderpc.py +79 -0
  46. nectarapi/openapi.py +107 -0
  47. nectarapi/py.typed +0 -0
  48. nectarapi/rpcutils.py +98 -0
  49. nectarapi/version.py +3 -0
  50. nectarbase/__init__.py +15 -0
  51. nectarbase/ledgertransactions.py +106 -0
  52. nectarbase/memo.py +242 -0
  53. nectarbase/objects.py +521 -0
  54. nectarbase/objecttypes.py +21 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1357 -0
  57. nectarbase/py.typed +0 -0
  58. nectarbase/signedtransactions.py +89 -0
  59. nectarbase/transactions.py +11 -0
  60. nectarbase/version.py +3 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +1121 -0
  63. nectargraphenebase/aes.py +49 -0
  64. nectargraphenebase/base58.py +197 -0
  65. nectargraphenebase/bip32.py +575 -0
  66. nectargraphenebase/bip38.py +110 -0
  67. nectargraphenebase/chains.py +15 -0
  68. nectargraphenebase/dictionary.py +2 -0
  69. nectargraphenebase/ecdsasig.py +309 -0
  70. nectargraphenebase/objects.py +130 -0
  71. nectargraphenebase/objecttypes.py +8 -0
  72. nectargraphenebase/operationids.py +5 -0
  73. nectargraphenebase/operations.py +25 -0
  74. nectargraphenebase/prefix.py +13 -0
  75. nectargraphenebase/py.typed +0 -0
  76. nectargraphenebase/signedtransactions.py +221 -0
  77. nectargraphenebase/types.py +557 -0
  78. nectargraphenebase/unsignedtransactions.py +288 -0
  79. nectargraphenebase/version.py +3 -0
  80. nectarstorage/__init__.py +57 -0
  81. nectarstorage/base.py +317 -0
  82. nectarstorage/exceptions.py +15 -0
  83. nectarstorage/interfaces.py +244 -0
  84. nectarstorage/masterpassword.py +237 -0
  85. nectarstorage/py.typed +0 -0
  86. nectarstorage/ram.py +27 -0
  87. nectarstorage/sqlite.py +343 -0
@@ -0,0 +1,1121 @@
1
+ import binascii
2
+ import bisect
3
+ import hashlib
4
+ import itertools
5
+ import os
6
+ import re
7
+ import unicodedata
8
+ from binascii import hexlify, unhexlify
9
+ from typing import Any, List, Optional, Tuple, Union
10
+
11
+ import ecdsa
12
+ from ecdsa.numbertheory import square_root_mod_prime
13
+ from ecdsa.util import number_to_string
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
+
21
+ PBKDF2_ROUNDS = 2048
22
+
23
+
24
+ # secp256k1 curve parameters for pure Python implementation
25
+ SECP256K1_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
26
+ SECP256K1_A = 0
27
+ SECP256K1_B = 7
28
+ SECP256K1_GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
29
+ SECP256K1_GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
30
+ SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
31
+
32
+
33
+ def _mod_inverse(a: int, m: int) -> int:
34
+ """
35
+ Return the modular inverse of a modulo m using the extended Euclidean algorithm.
36
+
37
+ Given integers a and m, compute x such that (a * x) % m == 1. The result is normalized
38
+ to the range [0, m-1]. If m == 1 the function returns 0.
39
+
40
+ Parameters:
41
+ a (int): The value to invert modulo m.
42
+ m (int): The modulus.
43
+
44
+ Returns:
45
+ int: The modular inverse of a modulo m, or 0 when m == 1.
46
+ """
47
+ m0, y, x = m, 0, 1
48
+ if m == 1:
49
+ return 0
50
+ while a > 1:
51
+ q = a // m
52
+ m, a = a % m, m
53
+ y, x = x - q * y, y
54
+ if x < 0:
55
+ x += m0
56
+ return x
57
+
58
+
59
+ def _is_on_curve(
60
+ x: int, y: int, p: int = SECP256K1_P, a: int = SECP256K1_A, b: int = SECP256K1_B
61
+ ) -> bool:
62
+ """Check if point (x, y) is on the secp256k1 curve: y² = x³ + a*x + b"""
63
+ left_side = (y * y) % p
64
+ right_side = (x * x * x + a * x + b) % p
65
+ return left_side == right_side
66
+
67
+
68
+ def _point_add(
69
+ p1: Optional[Tuple[int, int]], p2: Optional[Tuple[int, int]], p: int = SECP256K1_P
70
+ ) -> Optional[Tuple[int, int]]:
71
+ """
72
+ Add two points on an elliptic curve over a prime field.
73
+
74
+ Both p1 and p2 are either None (the point at infinity) or 2-tuples (x, y) of integers
75
+ interpreted modulo p. Returns a 2-tuple (x, y) representing the sum (mod p), or None
76
+ for the point at infinity. Uses the short Weierstrass group law (handles point
77
+ doubling and addition, including the inverse/vertical-tangent cases).
78
+
79
+ Parameters:
80
+ p1 (tuple|None): First point as (x, y) or None for infinity.
81
+ p2 (tuple|None): Second point as (x, y) or None for infinity.
82
+ p (int): Prime modulus of the field (defaults to SECP256K1_P).
83
+
84
+ Returns:
85
+ tuple|None: Resulting point (x, y) modulo p, or None if the result is the
86
+ point at infinity.
87
+ """
88
+ if p1 is None:
89
+ return p2
90
+ if p2 is None:
91
+ return p1
92
+
93
+ x1, y1 = p1
94
+ x2, y2 = p2
95
+
96
+ # P + (-P) = O
97
+ if x1 == x2 and (y1 + y2) % p == 0:
98
+ return None # Point at infinity
99
+
100
+ if x1 == x2:
101
+ # Point doubling - for secp256k1: s = (3*x1^2) / (2*y1)
102
+ if y1 % p == 0:
103
+ return None # vertical tangent => infinity
104
+ numerator = (3 * x1 * x1) % p
105
+ denominator = (2 * y1) % p
106
+ s = (numerator * _mod_inverse(denominator, p)) % p
107
+ else:
108
+ # Point addition
109
+ denom = (x2 - x1) % p
110
+ s = ((y2 - y1) % p) * _mod_inverse(denom, p) % p
111
+
112
+ x3 = (s * s - x1 - x2) % p
113
+ y3 = (s * (x1 - x3) - y1) % p
114
+
115
+ return (x3, y3)
116
+
117
+
118
+ def _scalar_mult(
119
+ k: int, point: Optional[Tuple[int, int]], p: int = SECP256K1_P
120
+ ) -> Optional[Tuple[int, int]]:
121
+ """
122
+ Compute k * point on the secp256k1 curve using the binary (double-and-add) method.
123
+
124
+ Parameters:
125
+ k (int): Non-negative integer scalar.
126
+ point (tuple|None): Elliptic-curve point as (x, y) or None to represent the point at infinity.
127
+ p (int, optional): Prime modulus of the field (defaults to SECP256K1_P).
128
+
129
+ Returns:
130
+ tuple|None: The resulting point (x, y) after scalar multiplication, or None for the point at infinity.
131
+ """
132
+ if k == 0:
133
+ return None # Point at infinity
134
+ if k == 1:
135
+ return point
136
+
137
+ result = None
138
+ current = point
139
+
140
+ while k > 0:
141
+ if k & 1:
142
+ result = _point_add(result, current, p)
143
+ current = _point_add(current, current, p)
144
+ k >>= 1
145
+
146
+ return result
147
+
148
+
149
+ def _point_to_compressed(point: Tuple[int, int]) -> bytes:
150
+ """
151
+ Return the 33-byte SEC compressed encoding of an EC point on secp256k1.
152
+
153
+ The input `point` must be a tuple (x, y) of integers representing coordinates on the curve.
154
+ The result is a 33-byte bytes object: a 1-byte prefix 0x02 (even y) or 0x03 (odd y)
155
+ followed by the 32-byte big-endian x coordinate.
156
+
157
+ Raises:
158
+ ValueError: If `point` is None (the point at infinity cannot be compressed).
159
+ """
160
+ if point is None:
161
+ raise ValueError("Cannot compress point at infinity")
162
+
163
+ x, y = point
164
+ prefix = 0x02 if y % 2 == 0 else 0x03
165
+ return prefix.to_bytes(1, "big") + x.to_bytes(32, "big")
166
+
167
+
168
+ def _compressed_to_point(compressed: bytes) -> Tuple[int, int]:
169
+ """
170
+ Convert a 33-byte SEC compressed public key to an (x, y) point on the secp256k1 curve.
171
+
172
+ Parameters:
173
+ compressed (bytes): 33-byte compressed point (prefix 0x02 or 0x03 followed by 32-byte big-endian x).
174
+
175
+ Returns:
176
+ tuple[int, int]: The affine coordinates (x, y) of the corresponding point.
177
+
178
+ Raises:
179
+ ValueError: If the input length is not 33 bytes, the prefix is not 0x02/0x03, or the recovered point is not on the secp256k1 curve.
180
+ """
181
+ if len(compressed) != 33:
182
+ raise ValueError("Invalid compressed point length")
183
+
184
+ prefix = compressed[0]
185
+ x = int.from_bytes(compressed[1:], "big")
186
+
187
+ if prefix not in (0x02, 0x03):
188
+ raise ValueError("Invalid compressed point prefix")
189
+
190
+ # Calculate y from x using curve equation: y^2 = x^3 + a*x + b
191
+ y_squared = (x * x * x + SECP256K1_A * x + SECP256K1_B) % SECP256K1_P
192
+
193
+ # Find square root mod p
194
+ y = pow(y_squared, (SECP256K1_P + 1) // 4, SECP256K1_P)
195
+
196
+ # Choose the correct y based on parity
197
+ if (prefix == 0x02 and y % 2 != 0) or (prefix == 0x03 and y % 2 == 0):
198
+ y = SECP256K1_P - y
199
+
200
+ if not _is_on_curve(x, y):
201
+ raise ValueError("Point not on curve")
202
+
203
+ return (x, y)
204
+
205
+
206
+ # From <https://stackoverflow.com/questions/212358/binary-search-bisection-in-python/2233940#2233940>
207
+ def binary_search(a: List[Any], x: Any, lo: int = 0, hi: Optional[int] = None) -> int:
208
+ """
209
+ Locate the index of x in sorted sequence a using binary search.
210
+
211
+ Performs a binary search on the sorted sequence `a` and returns the lowest index i in [lo, hi)
212
+ such that a[i] == x. If x is not present in that slice, returns -1.
213
+
214
+ Parameters:
215
+ a (Sequence): Sorted sequence (ascending) to search.
216
+ x: Value to locate.
217
+ lo (int, optional): Lower bound (inclusive) index to search from. Defaults to 0.
218
+ hi (int, optional): Upper bound (exclusive) index to search to. Defaults to len(a).
219
+
220
+ Returns:
221
+ int: Index of the first matching element in [lo, hi), or -1 if not found.
222
+ """
223
+ hi = hi if hi is not None else len(a) # hi defaults to len(a)
224
+ pos = bisect.bisect_left(a, x, lo, hi) # find insertion position
225
+ return pos if pos != hi and a[pos] == x else -1 # don't walk off the end
226
+
227
+
228
+ class PasswordKey(Prefix):
229
+ """This class derives a private key given the account name, the
230
+ role and a password. It leverages the technology of Brainkeys
231
+ and allows people to have a secure private key by providing a
232
+ passphrase only.
233
+ """
234
+
235
+ def __init__(
236
+ self,
237
+ account: Optional[str],
238
+ password: str,
239
+ role: str = "active",
240
+ prefix: Optional[str] = None,
241
+ ) -> None:
242
+ self.set_prefix(prefix)
243
+ self.account = account
244
+ self.role = role
245
+ self.password = password
246
+
247
+ def normalize(self, seed: str) -> str:
248
+ """Correct formating with single whitespace syntax and no trailing space"""
249
+ return " ".join(re.compile("[\t\n\v\f\r ]+").split(seed))
250
+
251
+ def get_private(self) -> "PrivateKey":
252
+ """Derive private key from the account, the role and the password"""
253
+ if self.account is None and self.role is None:
254
+ seed = self.password
255
+ elif self.account == "" and self.role == "":
256
+ seed = self.password
257
+ else:
258
+ seed = self.normalize(f"{self.account or ''}{self.role or ''}{self.password}")
259
+ return PrivateKey(
260
+ hexlify(hashlib.sha256(seed.encode()).digest()).decode("ascii"), prefix=self.prefix
261
+ )
262
+
263
+ def get_public(self) -> "PublicKey":
264
+ return self.get_private().pubkey
265
+
266
+ def get_private_key(self) -> "PrivateKey":
267
+ return self.get_private()
268
+
269
+ def get_public_key(self) -> "PublicKey":
270
+ return self.get_public()
271
+
272
+
273
+ class BrainKey(Prefix):
274
+ """Brainkey implementation similar to the graphene-ui web-wallet.
275
+
276
+ :param str brainkey: Brain Key
277
+ :param int sequence: Sequence number for consecutive keys
278
+
279
+ Keys in Graphene are derived from a seed brain key which is a string of
280
+ 16 words out of a predefined dictionary with 49744 words. It is a
281
+ simple single-chain key derivation scheme that is not compatible with
282
+ BIP44 but easy to use.
283
+
284
+ Given the brain key, a private key is derived as::
285
+
286
+ privkey = SHA256(SHA512(brainkey + " " + sequence))
287
+
288
+ Incrementing the sequence number yields a new key that can be
289
+ regenerated given the brain key.
290
+
291
+ """
292
+
293
+ def __init__(
294
+ self, brainkey: Optional[str] = None, sequence: int = 0, prefix: Optional[str] = None
295
+ ) -> None:
296
+ self.set_prefix(prefix)
297
+ if not brainkey:
298
+ self.brainkey = self.suggest()
299
+ else:
300
+ self.brainkey = self.normalize(brainkey).strip()
301
+ self.sequence = sequence
302
+
303
+ def __next__(self) -> "BrainKey":
304
+ """Get the next private key (sequence number increment) for
305
+ iterators
306
+ """
307
+ return self.next_sequence()
308
+
309
+ def next_sequence(self) -> "BrainKey":
310
+ """Increment the sequence number by 1"""
311
+ self.sequence += 1
312
+ return self
313
+
314
+ def normalize(self, brainkey: str) -> str:
315
+ """Correct formating with single whitespace syntax and no trailing space"""
316
+ return " ".join(re.compile("[\t\n\v\f\r ]+").split(brainkey))
317
+
318
+ def get_brainkey(self) -> str:
319
+ """Return brain key of this instance"""
320
+ return self.normalize(self.brainkey)
321
+
322
+ def get_private(self) -> "PrivateKey":
323
+ """Derive private key from the brain key and the current sequence
324
+ number
325
+ """
326
+ encoded = "%s %d" % (self.brainkey, self.sequence)
327
+ a = bytes(encoded, "ascii")
328
+ s = hashlib.sha256(hashlib.sha512(a).digest()).digest()
329
+ return PrivateKey(hexlify(s).decode("ascii"), prefix=self.prefix)
330
+
331
+ def get_blind_private(self) -> "PrivateKey":
332
+ """Derive private key from the brain key (and no sequence number)"""
333
+ a = bytes(self.brainkey, "ascii")
334
+ return PrivateKey(hashlib.sha256(a).hexdigest(), prefix=self.prefix)
335
+
336
+ def get_public(self) -> "PublicKey":
337
+ return self.get_private().pubkey
338
+
339
+ def get_private_key(self) -> "PrivateKey":
340
+ return self.get_private()
341
+
342
+ def get_public_key(self) -> "PublicKey":
343
+ return self.get_public()
344
+
345
+ def suggest(self, word_count: int = 16) -> str:
346
+ """Suggest a new random brain key. Randomness is provided by the
347
+ operating system using ``os.urandom()``.
348
+ """
349
+ brainkey: List[str] = [""] * word_count
350
+ dict_lines = BrainKeyDictionary.split(",")
351
+ if not len(dict_lines) == 49744:
352
+ raise AssertionError()
353
+ for j in range(0, word_count):
354
+ urand = os.urandom(2)
355
+ num = int.from_bytes(urand, byteorder="little")
356
+ rndMult = num / 2**16 # returns float between 0..1 (inclusive)
357
+ wIdx = int(round(len(dict_lines) * rndMult))
358
+ brainkey[j] = dict_lines[wIdx]
359
+ return " ".join(brainkey).upper()
360
+
361
+
362
+ # From https://github.com/trezor/python-mnemonic/blob/master/mnemonic/mnemonic.py
363
+ #
364
+ # Copyright (c) 2013 Pavol Rusnak
365
+ # Copyright (c) 2017 mruddy
366
+ class Mnemonic:
367
+ """BIP39 mnemoric implementation"""
368
+
369
+ def __init__(self) -> None:
370
+ self.wordlist = MnemonicDictionary.split(",")
371
+ self.radix = 2048
372
+
373
+ def generate(self, strength: int = 128) -> str:
374
+ """Generates a word list based on the given strength
375
+
376
+ :param int strength: initial entropy strength, must be one of [128, 160, 192, 224, 256]
377
+
378
+ """
379
+ if strength not in [128, 160, 192, 224, 256]:
380
+ raise ValueError(
381
+ "Strength should be one of the following: [128, 160, 192, 224, 256], but it is not (%d)."
382
+ % strength
383
+ )
384
+ return self.to_mnemonic(os.urandom(strength // 8))
385
+
386
+ # Adapted from <http://tinyurl.com/oxmn476>
387
+ def to_entropy(self, words: Union[str, List[str]]) -> bytes:
388
+ if not isinstance(words, list):
389
+ words = words.split(" ")
390
+ if len(words) not in [12, 15, 18, 21, 24]:
391
+ raise ValueError(
392
+ "Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)."
393
+ % len(words)
394
+ )
395
+ # Look up all the words in the list and construct the
396
+ # concatenation of the original entropy and the checksum.
397
+ concatLenBits = len(words) * 11
398
+ concatBits = [False] * concatLenBits
399
+ wordindex = 0
400
+ use_binary_search = True
401
+ for word in words:
402
+ # Find the words index in the wordlist
403
+ ndx = (
404
+ binary_search(self.wordlist, word)
405
+ if use_binary_search
406
+ else self.wordlist.index(word)
407
+ )
408
+ if ndx < 0:
409
+ raise LookupError('Unable to find "%s" in word list.' % word)
410
+ # Set the next 11 bits to the value of the index.
411
+ for ii in range(11):
412
+ concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0
413
+ wordindex += 1
414
+ checksumLengthBits = concatLenBits // 33
415
+ entropyLengthBits = concatLenBits - checksumLengthBits
416
+ # Extract original entropy as bytes.
417
+ entropy = bytearray(entropyLengthBits // 8)
418
+ for ii in range(len(entropy)):
419
+ for jj in range(8):
420
+ if concatBits[(ii * 8) + jj]:
421
+ entropy[ii] |= 1 << (7 - jj)
422
+ # Take the digest of the entropy.
423
+ hashBytes = hashlib.sha256(entropy).digest()
424
+ hashBits = list(
425
+ itertools.chain.from_iterable(
426
+ [c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes
427
+ )
428
+ )
429
+ # Check all the checksum bits.
430
+ for i in range(checksumLengthBits):
431
+ if concatBits[entropyLengthBits + i] != hashBits[i]:
432
+ raise ValueError("Failed checksum.")
433
+ return bytes(entropy)
434
+
435
+ def to_mnemonic(self, data: bytes) -> str:
436
+ if len(data) not in [16, 20, 24, 28, 32]:
437
+ raise ValueError(
438
+ "Data length should be one of the following: [16, 20, 24, 28, 32], but it is not (%d)."
439
+ % len(data)
440
+ )
441
+ h = hashlib.sha256(data).hexdigest()
442
+ b = (
443
+ bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
444
+ + bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
445
+ )
446
+ result = []
447
+ for i in range(len(b) // 11):
448
+ idx = int(b[i * 11 : (i + 1) * 11], 2)
449
+ result.append(self.wordlist[idx])
450
+
451
+ result_phrase = " ".join(result)
452
+ return result_phrase
453
+
454
+ def check(self, mnemonic: Union[str, List[str]]) -> bool:
455
+ """Checks the mnemonic word list is valid
456
+ :param list mnemonic: mnemonic word list with lenght of 12, 15, 18, 21, 24
457
+ :returns: True, when valid
458
+ """
459
+ mnemonic_str = " ".join(mnemonic) if isinstance(mnemonic, list) else mnemonic
460
+ mnemonic = self.normalize_string(mnemonic_str).split(" ")
461
+ # list of valid mnemonic lengths
462
+ if len(mnemonic) not in [12, 15, 18, 21, 24]:
463
+ return False
464
+ try:
465
+ idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
466
+ b = "".join(idx)
467
+ except ValueError:
468
+ return False
469
+ l = len(b) # noqa: E741
470
+ d = b[: l // 33 * 32]
471
+ h = b[-l // 33 :]
472
+ nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip("L").zfill(l // 33 * 8))
473
+ nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
474
+ return h == nh
475
+
476
+ def check_word(self, word: str) -> bool:
477
+ return word in self.wordlist
478
+
479
+ def expand_word(self, prefix: str) -> str:
480
+ """Expands a word when sufficient chars are given
481
+
482
+ :param str prefix: first chars of a valid dict word
483
+
484
+ """
485
+ if prefix in self.wordlist:
486
+ return prefix
487
+ else:
488
+ matches = [word for word in self.wordlist if word.startswith(prefix)]
489
+ if len(matches) == 1: # matched exactly one word in the wordlist
490
+ return matches[0]
491
+ else:
492
+ # exact match not found.
493
+ # this is not a validation routine, just return the input
494
+ return prefix
495
+
496
+ def expand(self, mnemonic: str) -> str:
497
+ """Expands all words given in a list"""
498
+ return " ".join(map(self.expand_word, mnemonic.split(" ")))
499
+
500
+ @classmethod
501
+ def normalize_string(cls, txt: str) -> str:
502
+ """Normalizes strings"""
503
+ return unicodedata.normalize("NFKD", txt)
504
+
505
+ @classmethod
506
+ def to_seed(cls, mnemonic: Union[str, List[str]], passphrase: str = "") -> bytes:
507
+ """Returns a seed based on bip39
508
+
509
+ :param str mnemonic: string containing a valid mnemonic word list
510
+ :param str passphrase: optional, passphrase can be set to modify the returned seed.
511
+
512
+ """
513
+ mnemonic = cls.normalize_string(mnemonic)
514
+ passphrase = cls.normalize_string(passphrase)
515
+ passphrase = "mnemonic" + passphrase
516
+ mnemonic = mnemonic.encode("utf-8")
517
+ passphrase = passphrase.encode("utf-8")
518
+ stretched = hashlib.pbkdf2_hmac("sha512", mnemonic, passphrase, PBKDF2_ROUNDS)
519
+ return stretched[:64]
520
+
521
+
522
+ class MnemonicKey(Prefix):
523
+ """This class derives a private key from a BIP39 mnemoric implementation"""
524
+
525
+ def __init__(
526
+ self,
527
+ word_list: Optional[Union[str, List[str]]] = None,
528
+ passphrase: str = "",
529
+ account_sequence: int = 0,
530
+ key_sequence: int = 0,
531
+ prefix: Optional[str] = None,
532
+ ) -> None:
533
+ self.set_prefix(prefix)
534
+ if word_list is not None:
535
+ self.set_mnemonic(word_list, passphrase=passphrase)
536
+ else:
537
+ self.seed = None
538
+ self.account_sequence = account_sequence
539
+ self.key_sequence = key_sequence
540
+ self.prefix = prefix or ""
541
+ self.path = "m/48'/13'/0'/%d'/%d'" % (self.account_sequence, self.key_sequence)
542
+
543
+ def set_mnemonic(self, word_list: Union[str, List[str]], passphrase: str = "") -> None:
544
+ mnemonic = Mnemonic()
545
+ if not mnemonic.check(word_list):
546
+ raise ValueError("Word list is not valid!")
547
+ self.seed = mnemonic.to_seed(word_list, passphrase=passphrase)
548
+
549
+ def generate_mnemonic(self, passphrase: str = "", strength: int = 256) -> str:
550
+ mnemonic = Mnemonic()
551
+ word_list = mnemonic.generate(strength=strength)
552
+ self.seed = mnemonic.to_seed(word_list, passphrase=passphrase)
553
+ return word_list
554
+
555
+ def set_path_BIP32(self, path: str) -> None:
556
+ self.path = path
557
+
558
+ def set_path_BIP44(
559
+ self,
560
+ account_sequence: int = 0,
561
+ chain_sequence: int = 0,
562
+ key_sequence: int = 0,
563
+ hardened_address: bool = True,
564
+ ) -> None:
565
+ if account_sequence < 0:
566
+ raise ValueError("account_sequence must be >= 0")
567
+ if key_sequence < 0:
568
+ raise ValueError("key_sequence must be >= 0")
569
+ if chain_sequence < 0:
570
+ raise ValueError("chain_sequence must be >= 0")
571
+ self.account_sequence = account_sequence
572
+ self.key_sequence = key_sequence
573
+ if hardened_address:
574
+ self.path = "m/44'/0'/%d'/%d/%d'" % (
575
+ self.account_sequence,
576
+ chain_sequence,
577
+ self.key_sequence,
578
+ )
579
+ else:
580
+ self.path = "m/44'/0'/%d'/%d/%d" % (
581
+ self.account_sequence,
582
+ chain_sequence,
583
+ self.key_sequence,
584
+ )
585
+
586
+ def set_path_BIP48(
587
+ self,
588
+ network_index: int = 13,
589
+ role: Union[str, int] = "owner",
590
+ account_sequence: int = 0,
591
+ key_sequence: int = 0,
592
+ ) -> None:
593
+ if account_sequence < 0:
594
+ raise ValueError("account_sequence must be >= 0")
595
+ if key_sequence < 0:
596
+ raise ValueError("key_sequence must be >= 0")
597
+ if network_index < 0:
598
+ raise ValueError("network_index must be >= 0")
599
+ if isinstance(role, str) and role not in ["owner", "active", "posting", "memo"]:
600
+ raise ValueError("Wrong role!")
601
+ elif isinstance(role, int) and role < 0:
602
+ raise ValueError("role must be >= 0")
603
+ if role == "owner":
604
+ role = 0
605
+ elif role == "active":
606
+ role = 1
607
+ elif role == "posting":
608
+ role = 4
609
+ elif role == "memo":
610
+ role = 3
611
+
612
+ self.account_sequence = account_sequence
613
+ self.key_sequence = key_sequence
614
+ self.path = "m/48'/%d'/%d'/%d'/%d'" % (
615
+ network_index,
616
+ role,
617
+ self.account_sequence,
618
+ self.key_sequence,
619
+ )
620
+
621
+ def next_account_sequence(self) -> "MnemonicKey":
622
+ """Increment the account sequence number by 1"""
623
+ self.account_sequence += 1
624
+ return self
625
+
626
+ def next_sequence(self) -> "MnemonicKey":
627
+ """Increment the key sequence number by 1"""
628
+ self.key_sequence += 1
629
+ return self
630
+
631
+ def set_path(self, path: str) -> None:
632
+ self.path = path
633
+
634
+ def get_path(self) -> str:
635
+ return self.path
636
+
637
+ def get_private(self) -> "PrivateKey":
638
+ """Derive private key from the account_sequence, the role and the key_sequence"""
639
+ if self.seed is None:
640
+ raise ValueError("seed is None, set or generate a mnemonic first")
641
+ key = BIP32Key.fromEntropy(self.seed)
642
+ path_result = parse_path(self.get_path(), as_bytes=False)
643
+ for n in path_result:
644
+ key = key.ChildKey(n)
645
+ if key is None:
646
+ raise ValueError(f"Failed to derive child key for path index: {n}")
647
+
648
+ return PrivateKey(key.WalletImportFormat(), prefix=self.prefix)
649
+
650
+ def get_public(self) -> "PublicKey":
651
+ return self.get_private().pubkey
652
+
653
+ def get_private_key(self) -> "PrivateKey":
654
+ return self.get_private()
655
+
656
+ def get_public_key(self) -> "PublicKey":
657
+ return self.get_public()
658
+
659
+
660
+ class Address(Prefix):
661
+ """Address class
662
+
663
+ This class serves as an address representation for Public Keys.
664
+
665
+ :param str address: Base58 encoded address (defaults to ``None``)
666
+ :param str prefix: Network prefix (defaults to ``STM``)
667
+
668
+ Example::
669
+
670
+ Address("STMFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi")
671
+
672
+ """
673
+
674
+ def __init__(self, address: str, prefix: Optional[str] = None) -> None:
675
+ self.set_prefix(prefix)
676
+ self._address = Base58(address, prefix=self.prefix)
677
+
678
+ @classmethod
679
+ def from_pubkey(
680
+ cls,
681
+ pubkey: Union[str, "PublicKey"],
682
+ compressed: bool = True,
683
+ version: int = 56,
684
+ prefix: Optional[str] = None,
685
+ ) -> "Address":
686
+ """Load an address provided by the public key.
687
+ Version: 56 => PTS
688
+ """
689
+ # Ensure this is a public key
690
+ pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
691
+ if compressed:
692
+ pubkey_plain = pubkey.compressed()
693
+ else:
694
+ pubkey_plain = pubkey.uncompressed()
695
+ sha = hashlib.sha256(unhexlify(pubkey_plain)).hexdigest()
696
+ rep = hexlify(ripemd160(sha)).decode("ascii")
697
+ s = ("%.2x" % version) + rep
698
+ result = s + hexlify(doublesha256(s)[:4]).decode("ascii")
699
+ result = hexlify(ripemd160(result)).decode("ascii")
700
+ return cls(result, prefix=pubkey.prefix)
701
+
702
+ @classmethod
703
+ def derivesha256address(
704
+ cls, pubkey: Union[str, "PublicKey"], compressed: bool = True, prefix: Optional[str] = None
705
+ ) -> "Address":
706
+ """Derive address using ``RIPEMD160(SHA256(x))``"""
707
+ pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
708
+ if compressed:
709
+ pubkey_plain = pubkey.compressed()
710
+ else:
711
+ pubkey_plain = pubkey.uncompressed()
712
+ pkbin = unhexlify(repr(pubkey_plain))
713
+ result = hexlify(hashlib.sha256(pkbin).digest())
714
+ result = hexlify(ripemd160(result)).decode("ascii")
715
+ return cls(result, prefix=pubkey.prefix)
716
+
717
+ @classmethod
718
+ def derivesha512address(
719
+ cls, pubkey: Union[str, "PublicKey"], compressed: bool = True, prefix: Optional[str] = None
720
+ ) -> "Address":
721
+ """Derive address using ``RIPEMD160(SHA512(x))``"""
722
+ pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
723
+ if compressed:
724
+ pubkey_plain = pubkey.compressed()
725
+ else:
726
+ pubkey_plain = pubkey.uncompressed()
727
+ pkbin = unhexlify(repr(pubkey_plain))
728
+ result = hexlify(hashlib.sha512(pkbin).digest())
729
+ result = hexlify(ripemd160(result)).decode("ascii")
730
+ return cls(result, prefix=pubkey.prefix)
731
+
732
+ def __repr__(self) -> str:
733
+ """Gives the hex representation of the ``GrapheneBase58CheckEncoded``
734
+ Graphene address.
735
+ """
736
+ return repr(self._address)
737
+
738
+ def __str__(self) -> str:
739
+ """Returns the readable Graphene address. This call is equivalent to
740
+ ``format(Address, "STM")``
741
+ """
742
+ return format(self._address, self.prefix)
743
+
744
+ def __format__(self, _format: str) -> str:
745
+ """May be issued to get valid "MUSE", "PLAY" or any other Graphene compatible
746
+ address with corresponding prefix.
747
+ """
748
+ return format(self._address, _format)
749
+
750
+ def __bytes__(self) -> bytes:
751
+ """Returns the raw content of the ``Base58CheckEncoded`` address"""
752
+ return bytes(self._address)
753
+
754
+
755
+ class GrapheneAddress(Address):
756
+ """Graphene Addresses are different. Hence we have a different class"""
757
+
758
+ @classmethod
759
+ def from_pubkey(
760
+ cls,
761
+ pubkey: Union[str, "PublicKey"],
762
+ compressed: bool = True,
763
+ prefix: Optional[str] = None,
764
+ ) -> "GrapheneAddress":
765
+ # Ensure this is a public key
766
+ pubkey = PublicKey(pubkey, prefix=prefix or Prefix.prefix)
767
+ if compressed:
768
+ pubkey_plain = pubkey.compressed()
769
+ else:
770
+ pubkey_plain = pubkey.uncompressed()
771
+
772
+ """ Derive address using ``RIPEMD160(SHA512(x))`` """
773
+ addressbin = ripemd160(hashlib.sha512(unhexlify(pubkey_plain)).hexdigest())
774
+ result = Base58(hexlify(addressbin).decode("ascii"))
775
+ return cls(result, prefix=pubkey.prefix)
776
+
777
+
778
+ class PublicKey(Prefix):
779
+ """This class deals with Public Keys and inherits ``Address``.
780
+
781
+ :param str pk: Base58 encoded public key
782
+ :param str prefix: Network prefix (defaults to ``STM``)
783
+
784
+ Example::
785
+
786
+ PublicKey("STM6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL")
787
+
788
+ .. note:: By default, graphene-based networks deal with **compressed**
789
+ public keys. If an **uncompressed** key is required, the
790
+ method :func:`unCompressed` can be used::
791
+
792
+ PublicKey("xxxxx").unCompressed()
793
+
794
+ """
795
+
796
+ def __init__(self, pk: Union[str, "PublicKey"], prefix: Optional[str] = None) -> None:
797
+ """Init PublicKey
798
+ :param str pk: Base58 encoded public key
799
+ :param str prefix: Network prefix (defaults to ``STM``)
800
+ """
801
+ self.set_prefix(prefix)
802
+ if isinstance(pk, PublicKey):
803
+ pk = format(pk, self.prefix)
804
+
805
+ if str(pk).startswith("04"):
806
+ # We only ever deal with compressed keys, so let's make it
807
+ # compressed
808
+ order = ecdsa.SECP256k1.order
809
+ p = ecdsa.VerifyingKey.from_string(
810
+ unhexlify(pk[2:]), curve=ecdsa.SECP256k1
811
+ ).pubkey.point
812
+ x_str = number_to_string(int(p.x()), order)
813
+ pk = hexlify(chr(2 + (int(p.y()) & 1)).encode("ascii") + x_str).decode("ascii")
814
+
815
+ self._pk = Base58(pk, prefix=self.prefix)
816
+
817
+ @property
818
+ def pubkey(self) -> str:
819
+ return repr(self._pk)
820
+
821
+ def get_public_key(self) -> str:
822
+ """Returns the pubkey"""
823
+ return self.pubkey
824
+
825
+ @property
826
+ def compressed_key(self) -> "PublicKey":
827
+ return PublicKey(self.compressed())
828
+
829
+ def __str__(self) -> str:
830
+ """Return the string representation of the public key"""
831
+ return self.prefix + str(self._pk)
832
+
833
+ def __repr__(self) -> str:
834
+ """Return the string representation of the public key"""
835
+ return str(self)
836
+
837
+ def __format__(self, _format: str) -> str:
838
+ """Format the public key with the given prefix"""
839
+ return _format + str(self._pk)
840
+
841
+ def __bytes__(self) -> bytes:
842
+ """Return the bytes representation of the public key"""
843
+ return bytes(self._pk)
844
+
845
+ def _derive_y_from_x(self, x: int, is_even: bool) -> int:
846
+ """Derive y point from x point"""
847
+ curve = ecdsa.SECP256k1.curve
848
+ # The curve equation over F_p is:
849
+ # y^2 = x^3 + ax + b
850
+ a, b, p = curve.a(), curve.b(), curve.p()
851
+ alpha = (pow(x, 3, p) + a * x + b) % p
852
+ beta = square_root_mod_prime(alpha, p)
853
+ if (beta % 2) == is_even:
854
+ beta = p - beta
855
+ return beta
856
+
857
+ def compressed(self) -> str:
858
+ """Derive compressed public key"""
859
+ return repr(self._pk)
860
+
861
+ def uncompressed(self) -> str:
862
+ """Derive uncompressed key"""
863
+ public_key = repr(self._pk)
864
+ prefix = public_key[0:2]
865
+ if prefix == "04":
866
+ return public_key
867
+ if not (prefix == "02" or prefix == "03"):
868
+ raise AssertionError()
869
+ x = int(public_key[2:], 16)
870
+ y = self._derive_y_from_x(x, (prefix == "02"))
871
+ key = "04" + "%064x" % x + "%064x" % y
872
+ return key
873
+
874
+ def point(self) -> Any:
875
+ """Return the point for the public key"""
876
+ string = unhexlify(self.uncompressed())
877
+ return ecdsa.VerifyingKey.from_string(string[1:], curve=ecdsa.SECP256k1).pubkey.point
878
+
879
+ def child(self, offset256: bytes) -> "PublicKey":
880
+ """Derive new public key from this key and a sha256 "offset" """
881
+ a = bytes(self) + offset256
882
+ s = hashlib.sha256(a).digest()
883
+ return self.add(s)
884
+
885
+ def add(self, digest256: bytes) -> "PublicKey":
886
+ """
887
+ Return a new PublicKey obtained by adding a 32-byte tweak (interpreted as a big-endian scalar) times the curve generator to this public key.
888
+
889
+ Parameters:
890
+ digest256 (bytes): A 32-byte SHA-256 digest used as the tweak scalar (big-endian). Must be length 32, non-zero, and less than the curve order.
891
+
892
+ Returns:
893
+ PublicKey: A new PublicKey instance representing (tweak * G) + current_public_key, preserving this key's prefix.
894
+
895
+ Raises:
896
+ ValueError: If `digest256` is not bytes, not 32 bytes long, is zero, is >= curve order, or if intermediate point multiplication/addition results in the point at infinity.
897
+ """
898
+ # Validate tweak
899
+ if not isinstance(digest256, (bytes, bytearray)):
900
+ raise ValueError("Tweak must be bytes")
901
+ if len(digest256) != 32:
902
+ raise ValueError("Tweak must be exactly 32 bytes")
903
+
904
+ tweak = int.from_bytes(digest256, "big")
905
+ if tweak == 0:
906
+ raise ValueError("Tweak cannot be zero")
907
+ if tweak >= SECP256K1_N:
908
+ raise ValueError("Tweak must be less than curve order")
909
+
910
+ # Convert current public key to point
911
+ current_compressed = bytes(self)
912
+ current_point = _compressed_to_point(current_compressed)
913
+
914
+ # Compute G*tweak (scalar multiplication of generator)
915
+ generator_point = (SECP256K1_GX, SECP256K1_GY)
916
+ tweak_point = _scalar_mult(tweak, generator_point)
917
+
918
+ if tweak_point is None:
919
+ raise ValueError("Tweak multiplication resulted in point at infinity")
920
+
921
+ # Add points: result = tweak_point + current_point
922
+ result_point = _point_add(tweak_point, current_point)
923
+
924
+ if result_point is None:
925
+ raise ValueError("Point addition resulted in point at infinity")
926
+
927
+ # Convert back to compressed format
928
+ result_compressed = _point_to_compressed(result_point)
929
+
930
+ # Create new PublicKey with same prefix
931
+ return PublicKey(hexlify(result_compressed).decode("ascii"), prefix=self.prefix)
932
+
933
+ @classmethod
934
+ def from_privkey(
935
+ cls, privkey: Union[str, "PrivateKey"], prefix: Optional[str] = None
936
+ ) -> "PublicKey":
937
+ """
938
+ Derive a compressed public key from a private key and return a PublicKey instance.
939
+
940
+ Parameters:
941
+ privkey: The private key material to derive from — accepts a WIF/hex string or a PrivateKey instance.
942
+ prefix (optional): Network/key prefix to use for the resulting PublicKey; if omitted the module default prefix is used.
943
+
944
+ Returns:
945
+ PublicKey: A PublicKey (compressed form) constructed from the derived public key bytes.
946
+
947
+ Raises:
948
+ ImportError: If the `ecdsa` library is not available.
949
+ """
950
+ privkey = PrivateKey(privkey, prefix=prefix or Prefix.prefix)
951
+ secret = unhexlify(repr(privkey))
952
+
953
+ order = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).curve.generator.order()
954
+ p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point
955
+ x_str = number_to_string(int(p.x()), order)
956
+ compressed = hexlify(chr(2 + (int(p.y()) & 1)).encode("ascii") + x_str).decode("ascii")
957
+ return cls(compressed, prefix=prefix or Prefix.prefix)
958
+
959
+ def unCompressed(self) -> str:
960
+ """Alias for self.uncompressed() - LEGACY"""
961
+ return self.uncompressed()
962
+
963
+ @property
964
+ def address(self) -> GrapheneAddress:
965
+ """Obtain a GrapheneAddress from a public key"""
966
+ return GrapheneAddress.from_pubkey(str(self), prefix=self.prefix)
967
+
968
+
969
+ class PrivateKey(Prefix):
970
+ """Derives the compressed and uncompressed public keys and
971
+ constructs two instances of :class:`PublicKey`:
972
+
973
+ :param str wif: Base58check-encoded wif key
974
+ :param str prefix: Network prefix (defaults to ``STM``)
975
+
976
+ Example::
977
+
978
+ PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd")
979
+
980
+ Compressed vs. Uncompressed:
981
+
982
+ * ``PrivateKey("w-i-f").pubkey``:
983
+ Instance of :class:`PublicKey` using compressed key.
984
+ * ``PrivateKey("w-i-f").pubkey.address``:
985
+ Instance of :class:`Address` using compressed key.
986
+ * ``PrivateKey("w-i-f").uncompressed``:
987
+ Instance of :class:`PublicKey` using uncompressed key.
988
+ * ``PrivateKey("w-i-f").uncompressed.address``:
989
+ Instance of :class:`Address` using uncompressed key.
990
+
991
+ """
992
+
993
+ def __init__(
994
+ self, wif: Optional[Union[str, "PrivateKey", Base58]] = None, prefix: Optional[str] = None
995
+ ) -> None:
996
+ self.set_prefix(prefix)
997
+ if wif is None:
998
+ import os
999
+
1000
+ self._wif = Base58(hexlify(os.urandom(32)).decode("ascii"))
1001
+ elif isinstance(wif, PrivateKey):
1002
+ self._wif = wif._wif
1003
+ elif isinstance(wif, Base58):
1004
+ self._wif = wif
1005
+ else:
1006
+ self._wif = Base58(wif)
1007
+
1008
+ assert len(repr(self._wif)) == 64
1009
+
1010
+ @property
1011
+ def bitcoin(self) -> PublicKey:
1012
+ return BitcoinPublicKey.from_privkey(self)
1013
+
1014
+ @property
1015
+ def address(self) -> Address:
1016
+ return Address.from_pubkey(self.pubkey, prefix=self.prefix)
1017
+
1018
+ @property
1019
+ def pubkey(self) -> PublicKey:
1020
+ return self.compressed
1021
+
1022
+ def get_public_key(self) -> PublicKey:
1023
+ """Legacy: Returns the pubkey"""
1024
+ return self.pubkey
1025
+
1026
+ @property
1027
+ def compressed(self) -> PublicKey:
1028
+ return PublicKey.from_privkey(self, prefix=self.prefix)
1029
+
1030
+ @property
1031
+ def uncompressed(self) -> PublicKey:
1032
+ return PublicKey(self.pubkey.uncompressed(), prefix=self.prefix)
1033
+
1034
+ def get_secret(self) -> bytes:
1035
+ """Get sha256 digest of the wif key."""
1036
+ return hashlib.sha256(bytes(self)).digest()
1037
+
1038
+ def derive_private_key(self, sequence: int) -> "PrivateKey":
1039
+ """Derive new private key from this private key and an arbitrary
1040
+ sequence number
1041
+ """
1042
+ encoded = "%s %d" % (str(self), sequence)
1043
+ a = bytes(encoded, "ascii")
1044
+ s = hashlib.sha256(hashlib.sha512(a).digest()).digest()
1045
+ return PrivateKey(hexlify(s).decode("ascii"), prefix=self.pubkey.prefix)
1046
+
1047
+ def child(self, offset256: bytes) -> "PrivateKey":
1048
+ """Derive new private key from this key and a sha256 "offset" """
1049
+ a = bytes(self.pubkey) + offset256
1050
+ s = hashlib.sha256(a).digest()
1051
+ return self.derive_from_seed(s)
1052
+
1053
+ def derive_from_seed(self, offset: bytes) -> "PrivateKey":
1054
+ """
1055
+ Derive a new PrivateKey by adding a 32-byte integer offset to this key's seed modulo the secp256k1 order.
1056
+
1057
+ Parameters:
1058
+ offset (bytes): A 32-byte SHA-256 digest interpreted as a big-endian integer offset to add to this key's secret.
1059
+
1060
+ Returns:
1061
+ PrivateKey: A new PrivateKey created from (seed + offset) mod SECP256K1_N, preserving this key's prefix.
1062
+ """
1063
+ seed = int(hexlify(bytes(self)).decode("ascii"), 16)
1064
+ z = int(hexlify(offset).decode("ascii"), 16)
1065
+ order = SECP256K1_N
1066
+ secexp = (seed + z) % order
1067
+ secret = "%0x" % secexp
1068
+ if len(secret) < 64: # left-pad with zeroes
1069
+ secret = ("0" * (64 - len(secret))) + secret
1070
+ return PrivateKey(secret, prefix=self.pubkey.prefix)
1071
+
1072
+ def __format__(self, _format: str) -> str:
1073
+ """Formats the instance of:doc:`Base58 <base58>` according to
1074
+ ``_format``
1075
+ """
1076
+ return format(self._wif, _format)
1077
+
1078
+ def __repr__(self) -> str:
1079
+ """Gives the hex representation of the Graphene private key."""
1080
+ return repr(self._wif)
1081
+
1082
+ def __str__(self) -> str:
1083
+ """Returns the readable (uncompressed wif format) Graphene private key. This
1084
+ call is equivalent to ``format(PrivateKey, "WIF")``
1085
+ """
1086
+ return format(self._wif, "WIF")
1087
+
1088
+ def __bytes__(self) -> bytes:
1089
+ """Returns the raw private key"""
1090
+ return bytes(self._wif)
1091
+
1092
+
1093
+ class BitcoinAddress(Address):
1094
+ @classmethod
1095
+ def from_pubkey(
1096
+ cls,
1097
+ pubkey: Union[str, PublicKey],
1098
+ compressed: bool = False,
1099
+ ) -> "BitcoinAddress":
1100
+ # Ensure this is a public key
1101
+ pubkey = PublicKey(pubkey)
1102
+ if compressed:
1103
+ pubkey = pubkey.compressed()
1104
+ else:
1105
+ pubkey = pubkey.uncompressed()
1106
+
1107
+ """ Derive address using ``RIPEMD160(SHA256(x))`` """
1108
+ addressbin = ripemd160(hexlify(hashlib.sha256(unhexlify(pubkey)).digest()))
1109
+ return cls(hexlify(addressbin).decode("ascii"))
1110
+
1111
+ def __str__(self) -> str:
1112
+ """Returns the readable Graphene address. This call is equivalent to
1113
+ ``format(Address, "GPH")``
1114
+ """
1115
+ return format(self._address, "BTC")
1116
+
1117
+
1118
+ class BitcoinPublicKey(PublicKey):
1119
+ @property
1120
+ def address(self) -> BitcoinAddress:
1121
+ return BitcoinAddress.from_pubkey(repr(self))