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.
- hive_nectar-0.2.9.dist-info/METADATA +194 -0
- hive_nectar-0.2.9.dist-info/RECORD +87 -0
- hive_nectar-0.2.9.dist-info/WHEEL +4 -0
- hive_nectar-0.2.9.dist-info/entry_points.txt +2 -0
- hive_nectar-0.2.9.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +37 -0
- nectar/account.py +5076 -0
- nectar/amount.py +553 -0
- nectar/asciichart.py +303 -0
- nectar/asset.py +122 -0
- nectar/block.py +574 -0
- nectar/blockchain.py +1242 -0
- nectar/blockchaininstance.py +2590 -0
- nectar/blockchainobject.py +263 -0
- nectar/cli.py +5937 -0
- nectar/comment.py +1552 -0
- nectar/community.py +854 -0
- nectar/constants.py +95 -0
- nectar/discussions.py +1437 -0
- nectar/exceptions.py +152 -0
- nectar/haf.py +381 -0
- nectar/hive.py +630 -0
- nectar/imageuploader.py +114 -0
- nectar/instance.py +113 -0
- nectar/market.py +876 -0
- nectar/memo.py +542 -0
- nectar/message.py +379 -0
- nectar/nodelist.py +309 -0
- nectar/price.py +603 -0
- nectar/profile.py +74 -0
- nectar/py.typed +0 -0
- nectar/rc.py +333 -0
- nectar/snapshot.py +1024 -0
- nectar/storage.py +62 -0
- nectar/transactionbuilder.py +659 -0
- nectar/utils.py +630 -0
- nectar/version.py +3 -0
- nectar/vote.py +722 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +728 -0
- nectarapi/__init__.py +12 -0
- nectarapi/exceptions.py +126 -0
- nectarapi/graphenerpc.py +596 -0
- nectarapi/node.py +194 -0
- nectarapi/noderpc.py +79 -0
- nectarapi/openapi.py +107 -0
- nectarapi/py.typed +0 -0
- nectarapi/rpcutils.py +98 -0
- nectarapi/version.py +3 -0
- nectarbase/__init__.py +15 -0
- nectarbase/ledgertransactions.py +106 -0
- nectarbase/memo.py +242 -0
- nectarbase/objects.py +521 -0
- nectarbase/objecttypes.py +21 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1357 -0
- nectarbase/py.typed +0 -0
- nectarbase/signedtransactions.py +89 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +3 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +1121 -0
- nectargraphenebase/aes.py +49 -0
- nectargraphenebase/base58.py +197 -0
- nectargraphenebase/bip32.py +575 -0
- nectargraphenebase/bip38.py +110 -0
- nectargraphenebase/chains.py +15 -0
- nectargraphenebase/dictionary.py +2 -0
- nectargraphenebase/ecdsasig.py +309 -0
- nectargraphenebase/objects.py +130 -0
- nectargraphenebase/objecttypes.py +8 -0
- nectargraphenebase/operationids.py +5 -0
- nectargraphenebase/operations.py +25 -0
- nectargraphenebase/prefix.py +13 -0
- nectargraphenebase/py.typed +0 -0
- nectargraphenebase/signedtransactions.py +221 -0
- nectargraphenebase/types.py +557 -0
- nectargraphenebase/unsignedtransactions.py +288 -0
- nectargraphenebase/version.py +3 -0
- nectarstorage/__init__.py +57 -0
- nectarstorage/base.py +317 -0
- nectarstorage/exceptions.py +15 -0
- nectarstorage/interfaces.py +244 -0
- nectarstorage/masterpassword.py +237 -0
- nectarstorage/py.typed +0 -0
- nectarstorage/ram.py +27 -0
- 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
|
+
}
|