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
nectar/memo.py ADDED
@@ -0,0 +1,542 @@
1
+ import os
2
+ import random
3
+ import struct
4
+ from binascii import hexlify, unhexlify
5
+ from typing import Any, Dict, Optional, Tuple, Union
6
+
7
+ from nectar.instance import shared_blockchain_instance
8
+ from nectar.version import version as __version__
9
+ from nectarbase import memo as BtsMemo
10
+ from nectargraphenebase.account import PrivateKey, PublicKey
11
+ from nectargraphenebase.base58 import base58decode, base58encode
12
+
13
+ from .account import Account
14
+ from .exceptions import MissingKeyError
15
+
16
+
17
+ class Memo:
18
+ """Deals with Memos that are attached to a transfer
19
+
20
+ :param Account from_account: Account that has sent the memo
21
+ :param Account to_account: Account that has received the memo
22
+ :param Hive blockchain_instance: Hive instance
23
+
24
+ A memo is encrypted with a shared secret derived from a private key of
25
+ the sender and a public key of the receiver. Due to the underlying
26
+ mathematics, the same shared secret can be derived by the private key
27
+ of the receiver and the public key of the sender. The encrypted message
28
+ is perturbed by a nonce that is part of the transmitted message.
29
+
30
+ .. code-block:: python
31
+
32
+ from nectar.memo import Memo
33
+ m = Memo("thecrazygm", "hive-nectar")
34
+ m.unlock_wallet("secret")
35
+ enc = (m.encrypt("test"))
36
+ print(enc)
37
+ >> {'message': '#DTpKcbxWqsETCRfjYGk9feERFa5nVBF8FaHfWPwUjyHBTgNhXGh4mN5TTG41nLhUcHtXfu7Hy3AwLrtWvo1ERUyAZaJjaEZNhHyoeDnrHdWChrzbccbANQmazgwjyxzEL', 'from': 'STM6MQBLaX9Q15CK3prXoWK4C6EqtsL7C4rqq1h6BQjxvfk9tuT3N', 'to': 'STM6sRudsxWpTZWxnpRkCDVD51RteiJnvJYCt5LiZAbVLfM1hJCQC'}
38
+ print(m.decrypt(enc))
39
+ >> foobar
40
+
41
+ To decrypt a memo, simply use
42
+
43
+ .. code-block:: python
44
+
45
+ from nectar.memo import Memo
46
+ m = Memo()
47
+ m.unlock_wallet("secret")
48
+ print(m.decrypt(op_data["memo"]))
49
+
50
+ if ``op_data`` being the payload of a transfer operation.
51
+
52
+ Memo Keys
53
+
54
+ In Hive, memos are AES-256 encrypted with a shared secret between sender and
55
+ receiver. It is derived from the memo private key of the sender and the memo
56
+ public key of the receiver.
57
+
58
+ In order for the receiver to decode the memo, the shared secret has to be
59
+ derived from the receiver's private key and the senders public key.
60
+
61
+ The memo public key is part of the account and can be retrieved with the
62
+ `get_account` call:
63
+
64
+ .. code-block:: js
65
+
66
+ get_account <accountname>
67
+ {
68
+ [...]
69
+ "options": {
70
+ "memo_key": "GPH5TPTziKkLexhVKsQKtSpo4bAv5RnB8oXcG4sMHEwCcTf3r7dqE",
71
+ [...]
72
+ },
73
+ [...]
74
+ }
75
+
76
+ while the memo private key can be dumped with `dump_private_keys`
77
+
78
+ Memo Message
79
+
80
+ The take the following form:
81
+
82
+ .. code-block:: js
83
+
84
+ {
85
+ "from": "GPH5mgup8evDqMnT86L7scVebRYDC2fwAWmygPEUL43LjstQegYCC",
86
+ "to": "GPH5Ar4j53kFWuEZQ9XhxbAja4YXMPJ2EnUg5QcrdeMFYUNMMNJbe",
87
+ "nonce": "13043867485137706821",
88
+ "message": "d55524c37320920844ca83bb20c8d008"
89
+ }
90
+
91
+ The fields `from` and `to` contain the memo public key of sender and receiver.
92
+ The `nonce` is a random integer that is used for the seed of the AES encryption
93
+ of the message.
94
+
95
+ Encrypting a memo
96
+
97
+ The high level memo class makes use of the nectar wallet to obtain keys
98
+ for the corresponding accounts.
99
+
100
+ .. code-block:: python
101
+
102
+ from nectar.memo import Memo
103
+ from nectar.account import Account
104
+
105
+ memoObj = Memo(
106
+ from_account=Account(from_account),
107
+ to_account=Account(to_account)
108
+ )
109
+ encrypted_memo = memoObj.encrypt(memo)
110
+
111
+ Decoding of a received memo
112
+
113
+ .. code-block:: python
114
+
115
+ from getpass import getpass
116
+ from nectar.block import Block
117
+ from nectar.memo import Memo
118
+
119
+ # Obtain a transfer from the blockchain
120
+ block = Block(23755086) # block
121
+ transaction = block["transactions"][3] # transactions
122
+ op = transaction["operations"][0] # operation
123
+ op_id = op[0] # operation type
124
+ op_data = op[1] # operation payload
125
+
126
+ # Instantiate Memo for decoding
127
+ memo = Memo()
128
+
129
+ # Unlock wallet
130
+ memo.unlock_wallet(getpass())
131
+
132
+ # Decode memo
133
+ # Raises exception if required keys not available in the wallet
134
+ print(memo.decrypt(op_data["transfer"]))
135
+
136
+ """
137
+
138
+ def __init__(
139
+ self,
140
+ from_account: Optional[Union[str, Account, PrivateKey]] = None,
141
+ to_account: Optional[Union[str, Account, PublicKey]] = None,
142
+ blockchain_instance: Optional[Any] = None,
143
+ **kwargs: Any,
144
+ ) -> None:
145
+ """
146
+ Initialize a Memo helper that resolves sender/recipient identifiers into Account/Key objects.
147
+
148
+ If `from_account`/`to_account` are provided as strings shorter than 51 characters they are treated as account names and resolved to Account(...) using the selected blockchain instance. Strings with length >= 51 are treated as raw keys and converted to PrivateKey (for `from_account`) or PublicKey (for `to_account`). If an input is omitted, the corresponding attribute is set to None.
149
+
150
+ Also sets self.blockchain to the provided blockchain_instance or, if None, the shared blockchain instance.
151
+
152
+ Parameters:
153
+ from_account (str|Account|PrivateKey|None): Sender identity — an account name (resolved to Account) or a private key string (resolved to PrivateKey). If already an Account/PrivateKey object, it will be assigned as-is by calling the appropriate constructor above.
154
+ to_account (str|Account|PublicKey|None): Recipient identity — an account name (resolved to Account) or a public key string (resolved to PublicKey).
155
+ blockchain_instance (optional): Blockchain client/instance to use for Account resolution; if omitted the shared blockchain instance is used.
156
+
157
+ Attributes set:
158
+ self.blockchain: blockchain instance used for key/account resolution.
159
+ self.from_account: Account or PrivateKey instance (or None).
160
+ self.to_account: Account or PublicKey instance (or None).
161
+ """
162
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
163
+
164
+ # Handle to_account
165
+ if to_account:
166
+ if isinstance(to_account, str):
167
+ if len(to_account) < 51:
168
+ self.to_account = Account(to_account, blockchain_instance=self.blockchain)
169
+ else:
170
+ self.to_account = PublicKey(to_account)
171
+ elif isinstance(to_account, Account):
172
+ self.to_account = to_account
173
+ elif isinstance(to_account, PublicKey):
174
+ self.to_account = to_account
175
+ else:
176
+ self.to_account = None
177
+ else:
178
+ self.to_account = None
179
+
180
+ # Handle from_account
181
+ if from_account:
182
+ if isinstance(from_account, str):
183
+ if len(from_account) < 51:
184
+ self.from_account = Account(from_account, blockchain_instance=self.blockchain)
185
+ else:
186
+ self.from_account = PrivateKey(from_account)
187
+ elif isinstance(from_account, Account):
188
+ self.from_account = from_account
189
+ elif isinstance(from_account, PrivateKey):
190
+ self.from_account = from_account
191
+ else:
192
+ self.from_account = None
193
+ else:
194
+ self.from_account = None
195
+
196
+ def unlock_wallet(self, *args: Any, **kwargs: Any) -> None:
197
+ """Unlock the library internal wallet"""
198
+ self.blockchain.wallet.unlock(*args, **kwargs)
199
+
200
+ def encrypt(
201
+ self,
202
+ memo: str,
203
+ bts_encrypt: bool = False,
204
+ return_enc_memo_only: bool = False,
205
+ nonce: Optional[str] = None,
206
+ ) -> Optional[Union[str, Dict[str, Any]]]:
207
+ """Encrypt a memo
208
+
209
+ :param str memo: clear text memo message
210
+ :param bool return_enc_memo_only: When True, only the encoded memo is returned
211
+ :param str nonce: when not set, a random string is generated and used
212
+ :returns: encrypted memo
213
+ :rtype: dict
214
+ """
215
+ if not memo:
216
+ return None
217
+ if nonce is None:
218
+ nonce = str(random.getrandbits(64))
219
+ if isinstance(self.from_account, Account):
220
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(
221
+ self.from_account["memo_key"]
222
+ )
223
+ memo_wif = PrivateKey(memo_wif)
224
+ else:
225
+ memo_wif = self.from_account
226
+ if isinstance(self.to_account, Account):
227
+ pubkey = self.to_account["memo_key"]
228
+ else:
229
+ pubkey = self.to_account
230
+ if not memo_wif:
231
+ if isinstance(self.from_account, Account):
232
+ raise MissingKeyError("Memo key for %s missing!" % self.from_account["name"])
233
+ else:
234
+ raise MissingKeyError("Memo key missing!")
235
+
236
+ if not hasattr(self, "chain_prefix"):
237
+ self.chain_prefix = self.blockchain.prefix
238
+
239
+ if bts_encrypt:
240
+ # Convert nonce to int for encode_memo_bts
241
+ nonce_int = int(nonce) if nonce else 0
242
+ enc = BtsMemo.encode_memo_bts(
243
+ PrivateKey(memo_wif),
244
+ PublicKey(str(pubkey), prefix=self.chain_prefix),
245
+ nonce_int,
246
+ memo,
247
+ )
248
+
249
+ return {
250
+ "message": enc,
251
+ "nonce": nonce,
252
+ "from": str(PrivateKey(memo_wif).pubkey),
253
+ "to": str(pubkey),
254
+ }
255
+ else:
256
+ enc = BtsMemo.encode_memo(
257
+ PrivateKey(memo_wif),
258
+ PublicKey(str(pubkey), prefix=self.chain_prefix),
259
+ int(nonce),
260
+ memo,
261
+ prefix=self.chain_prefix,
262
+ )
263
+ if return_enc_memo_only:
264
+ return enc
265
+ return {"message": enc, "from": str(PrivateKey(memo_wif).pubkey), "to": str(pubkey)}
266
+
267
+ def encrypt_binary(self, infile, outfile, buffer_size=2048, nonce=None):
268
+ """Encrypt a binary file
269
+
270
+ :param str infile: input file name
271
+ :param str outfile: output file name
272
+ :param int buffer_size: write buffer size
273
+ :param str nonce: when not set, a random string is generated and used
274
+ """
275
+ if not os.path.exists(infile):
276
+ raise ValueError("%s does not exists!" % infile)
277
+
278
+ if nonce is None:
279
+ nonce = str(random.getrandbits(64))
280
+ if isinstance(self.from_account, Account):
281
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(
282
+ self.from_account["memo_key"]
283
+ )
284
+ else:
285
+ memo_wif = self.from_account
286
+ if isinstance(self.to_account, Account):
287
+ pubkey = self.to_account["memo_key"]
288
+ else:
289
+ pubkey = self.to_account
290
+ if not memo_wif:
291
+ if isinstance(self.from_account, Account):
292
+ raise MissingKeyError("Memo key for %s missing!" % self.from_account["name"])
293
+ else:
294
+ raise MissingKeyError("Memo key missing!")
295
+
296
+ if not hasattr(self, "chain_prefix"):
297
+ self.chain_prefix = self.blockchain.prefix
298
+
299
+ file_size = os.path.getsize(infile)
300
+ priv = PrivateKey(memo_wif)
301
+ pub = PublicKey(str(pubkey), prefix=self.chain_prefix)
302
+ enc = BtsMemo.encode_memo(
303
+ priv, pub, int(nonce), "nectar/%s" % __version__, prefix=self.chain_prefix
304
+ )
305
+ enc = unhexlify(base58decode(enc[1:]))
306
+ shared_secret = BtsMemo.get_shared_secret(priv, pub)
307
+ aes, check = BtsMemo.init_aes2(shared_secret, int(nonce))
308
+ with open(outfile, "wb") as fout:
309
+ fout.write(struct.pack("<Q", len(enc)))
310
+ fout.write(enc)
311
+ fout.write(struct.pack("<Q", file_size))
312
+ with open(infile, "rb") as fin:
313
+ while True:
314
+ data = fin.read(buffer_size)
315
+ n = len(data)
316
+ if n == 0:
317
+ break
318
+ elif n % 16 != 0:
319
+ data += b" " * (16 - n % 16) # <- padded with spaces
320
+ encd = aes.encrypt(data)
321
+ fout.write(encd)
322
+
323
+ def extract_decrypt_memo_data(self, memo: str) -> Tuple[Any, Any, Any]:
324
+ """Returns information about an encrypted memo"""
325
+ from_key, to_key, nonce, check, cipher = BtsMemo.extract_memo_data(memo)
326
+ return from_key, to_key, nonce
327
+
328
+ def decrypt(self, memo: Union[str, Dict[str, Any]]) -> Optional[str]:
329
+ """
330
+ Decrypt a memo message produced for a transfer.
331
+
332
+ Accepts either a raw memo string or a transfer-style dict with keys "from", "to", and "memo" or "message". If provided, the memo dict may also contain a "nonce". The function will locate an appropriate private memo key from the local wallet (or use a provided PrivateKey), derive the shared secret with the counterparty public key, and return the decrypted plaintext.
333
+
334
+ Parameters:
335
+ memo (str or dict): Encrypted memo as a string, or a dict in transfer form:
336
+ {"from": <account|key>, "to": <account|key>, "memo"/"message": <str>, "nonce"?: <int|str>}.
337
+ - "from"/"to" entries may be account names, account dicts, PublicKey/PrivateKey objects, or omitted.
338
+
339
+ Returns:
340
+ str: Decrypted memo plaintext, or None if `memo` is falsy.
341
+
342
+ Raises:
343
+ MissingKeyError: If no installed private memo key can be found for decrypting the message.
344
+ """
345
+ if not memo:
346
+ return None
347
+ memo_wif = None
348
+ # We first try to decode assuming we received the memo
349
+ if (
350
+ isinstance(memo, dict)
351
+ and "to" in memo
352
+ and "from" in memo
353
+ and ("memo" in memo or "message" in memo)
354
+ ):
355
+ memo_to = Account(memo["to"], blockchain_instance=self.blockchain)
356
+ memo_from = Account(memo["from"], blockchain_instance=self.blockchain)
357
+ message = memo.get("memo") or memo.get("message")
358
+ else:
359
+ memo_to = self.to_account
360
+ memo_from = self.from_account
361
+ message = memo
362
+ if isinstance(memo, dict) and "nonce" in memo:
363
+ nonce = memo.get("nonce")
364
+ else:
365
+ nonce = ""
366
+
367
+ if memo_to is None or memo_from is None:
368
+ if message is None:
369
+ return None
370
+ # Ensure message is a string for extract_memo_data
371
+ if isinstance(message, dict):
372
+ message = str(message.get("memo") or message.get("message") or "")
373
+ elif not isinstance(message, str):
374
+ message = str(message)
375
+ from_key, to_key, nonce, check, cipher = BtsMemo.extract_memo_data(message)
376
+ try:
377
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(to_key))
378
+ pubkey = from_key
379
+ except MissingKeyError:
380
+ try:
381
+ # if that failed, we assume that we have sent the memo
382
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(from_key))
383
+ pubkey = to_key
384
+ except MissingKeyError:
385
+ # if all fails, raise exception
386
+ raise MissingKeyError("Non of the required memo keys are installed!")
387
+ elif memo_to is not None and memo_from is not None and isinstance(memo_from, PrivateKey):
388
+ memo_wif = memo_from
389
+ pubkey = memo_to
390
+ elif memo_to is not None and memo_from is not None and isinstance(memo_to, PrivateKey):
391
+ memo_wif = memo_to
392
+ pubkey = memo_from
393
+ else:
394
+ try:
395
+ if isinstance(memo_to, Account):
396
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(memo_to["memo_key"])
397
+ else:
398
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(memo_to))
399
+ if isinstance(memo_from, Account):
400
+ pubkey = memo_from["memo_key"]
401
+ else:
402
+ pubkey = memo_from
403
+ except MissingKeyError:
404
+ try:
405
+ # if that failed, we assume that we have sent the memo
406
+ if isinstance(memo_from, Account):
407
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(
408
+ memo_from["memo_key"]
409
+ )
410
+ else:
411
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(memo_from))
412
+ if isinstance(memo_to, Account):
413
+ pubkey = memo_to["memo_key"]
414
+ else:
415
+ pubkey = memo_to
416
+ except MissingKeyError:
417
+ # if all fails, raise exception
418
+ raise MissingKeyError("Non of the required memo keys are installed!")
419
+
420
+ if not hasattr(self, "chain_prefix"):
421
+ self.chain_prefix = self.blockchain.prefix
422
+
423
+ # Ensure message is a string for decode functions
424
+ if isinstance(message, dict):
425
+ message = str(message.get("memo") or message.get("message") or "")
426
+ elif not isinstance(message, str):
427
+ message = str(message)
428
+
429
+ if message[0] == "#" or memo_to is None or memo_from is None:
430
+ return BtsMemo.decode_memo(PrivateKey(memo_wif), message)
431
+ else:
432
+ return BtsMemo.decode_memo_bts(
433
+ PrivateKey(memo_wif),
434
+ PublicKey(str(pubkey), prefix=self.chain_prefix),
435
+ int(nonce or 0),
436
+ message,
437
+ )
438
+
439
+ def decrypt_binary(self, infile: str, outfile: str, buffer_size: int = 2048) -> Dict[str, Any]:
440
+ """Decrypt a binary file
441
+
442
+ :param str infile: encrypted binary file
443
+ :param str outfile: output file name
444
+ :param int buffer_size: read buffer size
445
+ :returns: encrypted memo information
446
+ :rtype: dict
447
+ """
448
+ if not os.path.exists(infile):
449
+ raise ValueError("%s does not exists!" % infile)
450
+ if buffer_size % 16 != 0:
451
+ raise ValueError("buffer_size must be dividable by 16")
452
+ with open(infile, "rb") as fin:
453
+ memo_size = struct.unpack("<Q", fin.read(struct.calcsize("<Q")))[0]
454
+ memo = fin.read(memo_size)
455
+ orig_file_size = struct.unpack("<Q", fin.read(struct.calcsize("<Q")))[0]
456
+ memo = "#" + base58encode(hexlify(memo).decode("ascii"))
457
+ memo_to = self.to_account
458
+ memo_from = self.from_account
459
+ from_key, to_key, nonce, check, cipher = BtsMemo.extract_memo_data(memo)
460
+ if memo_to is None and memo_from is None:
461
+ try:
462
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(to_key))
463
+ pubkey = from_key
464
+ except MissingKeyError:
465
+ try:
466
+ # if that failed, we assume that we have sent the memo
467
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(from_key))
468
+ pubkey = to_key
469
+ except MissingKeyError:
470
+ # if all fails, raise exception
471
+ raise MissingKeyError("Non of the required memo keys are installed!")
472
+ elif memo_to is not None and memo_from is not None and isinstance(memo_from, PrivateKey):
473
+ memo_wif = memo_from
474
+ if isinstance(memo_to, Account):
475
+ pubkey = memo_to["memo_key"]
476
+ else:
477
+ pubkey = memo_to
478
+ elif memo_to is not None and memo_from is not None and isinstance(memo_to, PrivateKey):
479
+ memo_wif = memo_to
480
+ if isinstance(memo_from, Account):
481
+ pubkey = memo_from["memo_key"]
482
+ else:
483
+ pubkey = memo_from
484
+ else:
485
+ try:
486
+ if isinstance(memo_to, Account):
487
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(memo_to["memo_key"])
488
+ else:
489
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(memo_to))
490
+ if isinstance(memo_from, Account):
491
+ pubkey = memo_from["memo_key"]
492
+ else:
493
+ pubkey = memo_from
494
+ except MissingKeyError:
495
+ try:
496
+ # if that failed, we assume that we have sent the memo
497
+ if isinstance(memo_from, Account):
498
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(
499
+ memo_from["memo_key"]
500
+ )
501
+ else:
502
+ memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey(str(memo_from))
503
+ if isinstance(memo_to, Account):
504
+ pubkey = memo_to["memo_key"]
505
+ else:
506
+ pubkey = memo_to
507
+ except MissingKeyError:
508
+ # if all fails, raise exception
509
+ raise MissingKeyError("Non of the required memo keys are installed!")
510
+
511
+ if not hasattr(self, "chain_prefix"):
512
+ self.chain_prefix = self.blockchain.prefix
513
+ priv = PrivateKey(memo_wif)
514
+ pubkey = PublicKey(str(pubkey), prefix=self.chain_prefix)
515
+ nectar_version = BtsMemo.decode_memo(priv, memo)
516
+ shared_secret = BtsMemo.get_shared_secret(priv, pubkey)
517
+ # Init encryption
518
+ aes, checksum = BtsMemo.init_aes2(shared_secret, int(nonce))
519
+ with open(infile, "rb") as fin:
520
+ memo_size = struct.unpack("<Q", fin.read(struct.calcsize("<Q")))[0]
521
+ memo = fin.read(memo_size)
522
+ file_size = struct.unpack("<Q", fin.read(struct.calcsize("<Q")))[0]
523
+ with open(outfile, "wb") as fout:
524
+ while True:
525
+ data = fin.read(buffer_size)
526
+ n = len(data)
527
+ if n == 0:
528
+ break
529
+ decd = aes.decrypt(data)
530
+ n = len(decd)
531
+ if file_size > n:
532
+ fout.write(decd)
533
+ else:
534
+ fout.write(decd[:file_size]) # <- remove padding on last block
535
+ file_size -= n
536
+ return {
537
+ "file_size": orig_file_size,
538
+ "from_key": str(from_key),
539
+ "to_key": str(to_key),
540
+ "nonce": nonce,
541
+ "nectar_version": nectar_version,
542
+ }