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
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/interfaces.py
|
|
2
|
+
from collections.abc import MutableMapping
|
|
3
|
+
from typing import Any, Iterator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StoreInterface(MutableMapping):
|
|
7
|
+
"""The store interface is the most general store that we can have.
|
|
8
|
+
|
|
9
|
+
It behaves like a dictionary but allows returning None for missing keys and
|
|
10
|
+
keeps a `defaults` mapping that can supply fallback values.
|
|
11
|
+
|
|
12
|
+
.. note:: This class defines ``defaults`` that are used to return
|
|
13
|
+
reasonable defaults for the library.
|
|
14
|
+
|
|
15
|
+
.. warning:: If you are trying to obtain a value for a key that does
|
|
16
|
+
**not** exist in the store, the library will **NOT** raise but
|
|
17
|
+
return a ``None`` value. This represents the biggest difference to
|
|
18
|
+
a regular ``dict`` class.
|
|
19
|
+
|
|
20
|
+
Methods that need to be implemented:
|
|
21
|
+
|
|
22
|
+
* ``def setdefault(cls, key, value)``
|
|
23
|
+
* ``def __init__(self, *args, **kwargs)``
|
|
24
|
+
* ``def __setitem__(self, key, value)``
|
|
25
|
+
* ``def __getitem__(self, key)``
|
|
26
|
+
* ``def __iter__(self)``
|
|
27
|
+
* ``def __len__(self)``
|
|
28
|
+
* ``def __contains__(self, key)``
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
.. note:: Configuration and Key classes are subclasses of this to allow
|
|
32
|
+
storing keys separate from configuration.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
defaults = {}
|
|
37
|
+
|
|
38
|
+
def setdefault(self, key, value=None):
|
|
39
|
+
"""Allows to define default values on this store instance."""
|
|
40
|
+
if value is None and key in self.defaults:
|
|
41
|
+
return self.defaults[key]
|
|
42
|
+
if value is not None:
|
|
43
|
+
self.defaults[key] = value
|
|
44
|
+
return self._data.setdefault(key, value)
|
|
45
|
+
|
|
46
|
+
def __init__(self, *_args, **_kwargs):
|
|
47
|
+
self._data: dict[Any, Any] = {}
|
|
48
|
+
|
|
49
|
+
def __setitem__(self, key, value):
|
|
50
|
+
"""Sets an item in the store"""
|
|
51
|
+
self._data[key] = value
|
|
52
|
+
|
|
53
|
+
def __getitem__(self, key):
|
|
54
|
+
"""Gets an item from the store as if it was a dictionary
|
|
55
|
+
|
|
56
|
+
.. note:: Returns the value from the store or from defaults if
|
|
57
|
+
the key is found there. Raises ``KeyError`` if not found
|
|
58
|
+
in either.
|
|
59
|
+
"""
|
|
60
|
+
if key in self._data:
|
|
61
|
+
return self._data[key]
|
|
62
|
+
if key in self.defaults:
|
|
63
|
+
return self.defaults[key]
|
|
64
|
+
raise KeyError(key)
|
|
65
|
+
|
|
66
|
+
def __iter__(self) -> Iterator[Any]:
|
|
67
|
+
"""Iterates through the store"""
|
|
68
|
+
return iter(self._data)
|
|
69
|
+
|
|
70
|
+
def __len__(self) -> int:
|
|
71
|
+
"""return length of store"""
|
|
72
|
+
return len(self._data)
|
|
73
|
+
|
|
74
|
+
def __contains__(self, key) -> bool:
|
|
75
|
+
"""Tests if a key is contained in the store."""
|
|
76
|
+
return key in self._data
|
|
77
|
+
|
|
78
|
+
def __delitem__(self, key: Any) -> None:
|
|
79
|
+
if key not in self._data:
|
|
80
|
+
raise KeyError(key)
|
|
81
|
+
self._data.pop(key)
|
|
82
|
+
|
|
83
|
+
def items(self):
|
|
84
|
+
"""Returns all items off the store as tuples"""
|
|
85
|
+
return self._data.items()
|
|
86
|
+
|
|
87
|
+
def get(self, key, default=None):
|
|
88
|
+
"""Return the key if exists or a default value"""
|
|
89
|
+
return self._data.get(key, self.defaults.get(key, default))
|
|
90
|
+
|
|
91
|
+
# Specific for this library
|
|
92
|
+
def delete(self, key):
|
|
93
|
+
"""Delete a key from the store"""
|
|
94
|
+
raise NotImplementedError
|
|
95
|
+
|
|
96
|
+
def wipe(self):
|
|
97
|
+
"""Wipe the store"""
|
|
98
|
+
raise NotImplementedError
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class KeyInterface(StoreInterface):
|
|
102
|
+
"""The KeyInterface defines the interface for key storage.
|
|
103
|
+
|
|
104
|
+
.. note:: This class inherits
|
|
105
|
+
:class:`nectarstorage.interfaces.StoreInterface` and defines
|
|
106
|
+
additional key-specific methods.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def is_encrypted(self):
|
|
110
|
+
"""Returns True/False to indicate required use of unlock"""
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
# Interface to deal with encrypted keys
|
|
114
|
+
def getPublicKeys(self):
|
|
115
|
+
"""Returns the public keys stored in the database"""
|
|
116
|
+
raise NotImplementedError
|
|
117
|
+
|
|
118
|
+
def getPrivateKeyForPublicKey(self, pub):
|
|
119
|
+
"""Returns the (possibly encrypted) private key that
|
|
120
|
+
corresponds to a public key
|
|
121
|
+
|
|
122
|
+
:param str pub: Public key
|
|
123
|
+
|
|
124
|
+
The encryption scheme is BIP38
|
|
125
|
+
"""
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
def add(self, wif, pub=None):
|
|
129
|
+
"""Add a new public/private key pair (correspondence has to be checked elsewhere!)
|
|
130
|
+
|
|
131
|
+
:param str pub: Public key
|
|
132
|
+
:param str wif: Private key
|
|
133
|
+
"""
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
def delete(self, key):
|
|
137
|
+
"""Delete a pubkey/privatekey pair from the store
|
|
138
|
+
|
|
139
|
+
:param str key: Public key
|
|
140
|
+
"""
|
|
141
|
+
raise NotImplementedError
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class EncryptedKeyInterface(KeyInterface):
|
|
145
|
+
"""The EncryptedKeyInterface extends KeyInterface to work with encrypted
|
|
146
|
+
keys
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def is_encrypted(self):
|
|
150
|
+
"""Returns True/False to indicate required use of unlock"""
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
def unlock(self, password):
|
|
154
|
+
"""Tries to unlock the wallet if required
|
|
155
|
+
|
|
156
|
+
:param str password: Plain password
|
|
157
|
+
"""
|
|
158
|
+
raise NotImplementedError
|
|
159
|
+
|
|
160
|
+
def locked(self):
|
|
161
|
+
"""is the wallet locked?"""
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
def lock(self):
|
|
165
|
+
"""Lock the wallet again"""
|
|
166
|
+
raise NotImplementedError
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ConfigInterface(StoreInterface):
|
|
170
|
+
"""The BaseKeyStore defines the interface for key storage
|
|
171
|
+
|
|
172
|
+
.. note:: This class inherits
|
|
173
|
+
:class:`nectarstorage.interfaces.StoreInterface` and defines
|
|
174
|
+
**no** additional configuration-specific methods.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TokenInterface(StoreInterface):
|
|
181
|
+
"""The TokenInterface defines the interface for token storage.
|
|
182
|
+
|
|
183
|
+
.. note:: This class inherits
|
|
184
|
+
:class:`nectarstorage.interfaces.StoreInterface` and defines
|
|
185
|
+
additional key-specific methods.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def is_encrypted(self):
|
|
189
|
+
"""Returns True/False to indicate required use of unlock"""
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
# Interface to deal with tokens
|
|
193
|
+
def getPublicNames(self):
|
|
194
|
+
"""Returns the public token names stored in the database"""
|
|
195
|
+
raise NotImplementedError
|
|
196
|
+
|
|
197
|
+
def getPrivateKeyForPublicKey(self, pub):
|
|
198
|
+
"""Returns the (possibly encrypted) token that corresponds to a name
|
|
199
|
+
|
|
200
|
+
:param str pub: Public key
|
|
201
|
+
|
|
202
|
+
The encryption scheme is BIP38
|
|
203
|
+
"""
|
|
204
|
+
raise NotImplementedError
|
|
205
|
+
|
|
206
|
+
def add(self, token, name=None):
|
|
207
|
+
"""Add a new token entry (correspondence has to be checked elsewhere!)
|
|
208
|
+
|
|
209
|
+
:param str name: Public identifier
|
|
210
|
+
:param str token: Token value
|
|
211
|
+
"""
|
|
212
|
+
raise NotImplementedError
|
|
213
|
+
|
|
214
|
+
def delete(self, key):
|
|
215
|
+
"""Delete a token entry from the store
|
|
216
|
+
|
|
217
|
+
:param str key: Public identifier
|
|
218
|
+
"""
|
|
219
|
+
raise NotImplementedError
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class EncryptedTokenInterface(TokenInterface):
|
|
223
|
+
"""The EncryptedKeyInterface extends KeyInterface to work with encrypted
|
|
224
|
+
tokens
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def is_encrypted(self):
|
|
228
|
+
"""Returns True/False to indicate required use of unlock"""
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def unlock(self, password):
|
|
232
|
+
"""Tries to unlock the wallet if required
|
|
233
|
+
|
|
234
|
+
:param str password: Plain password
|
|
235
|
+
"""
|
|
236
|
+
raise NotImplementedError
|
|
237
|
+
|
|
238
|
+
def locked(self):
|
|
239
|
+
"""is the wallet locked?"""
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
def lock(self):
|
|
243
|
+
"""Lock the wallet again"""
|
|
244
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/masterpassword.py
|
|
2
|
+
import hashlib
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import warnings
|
|
6
|
+
from binascii import hexlify
|
|
7
|
+
|
|
8
|
+
from nectargraphenebase import bip38
|
|
9
|
+
from nectargraphenebase.aes import AESCipher
|
|
10
|
+
|
|
11
|
+
from .exceptions import WalletLocked, WrongMasterPasswordException
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MasterPassword:
|
|
17
|
+
"""The keys are encrypted with a Masterpassword that is stored in
|
|
18
|
+
the configurationStore. It has a checksum to verify correctness
|
|
19
|
+
of the password
|
|
20
|
+
The encrypted private keys in `keys` are encrypted with a random
|
|
21
|
+
**masterkey/masterpassword** that is stored in the configuration
|
|
22
|
+
encrypted by the user-provided password.
|
|
23
|
+
|
|
24
|
+
:param ConfigStore config: Configuration store to get access to the
|
|
25
|
+
encrypted master password
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, config=None, **kwargs):
|
|
29
|
+
if config is None:
|
|
30
|
+
raise ValueError("If using encrypted store, a config store is required!")
|
|
31
|
+
self.config = config
|
|
32
|
+
self.password = None
|
|
33
|
+
self.decrypted_master = None
|
|
34
|
+
self.config_key = "encrypted_master_password"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def masterkey(self):
|
|
38
|
+
"""Contains the **decrypted** master key"""
|
|
39
|
+
return self.decrypted_master
|
|
40
|
+
|
|
41
|
+
def has_masterpassword(self):
|
|
42
|
+
"""Tells us if the config store knows an encrypted masterpassword"""
|
|
43
|
+
return self.config_key in self.config
|
|
44
|
+
|
|
45
|
+
def locked(self):
|
|
46
|
+
"""Is the store locked. E.g. Is a valid password known that can be
|
|
47
|
+
used to decrypt the master key?
|
|
48
|
+
"""
|
|
49
|
+
return not self.unlocked()
|
|
50
|
+
|
|
51
|
+
def unlocked(self):
|
|
52
|
+
"""Is the store unlocked so that I can decrypt the content?"""
|
|
53
|
+
if self.password is not None:
|
|
54
|
+
return bool(self.password)
|
|
55
|
+
else:
|
|
56
|
+
password_storage = self.config["password_storage"]
|
|
57
|
+
KEYRING_AVAILABLE = False
|
|
58
|
+
if password_storage == "keyring":
|
|
59
|
+
try:
|
|
60
|
+
import keyring # type: ignore[import-not-found]
|
|
61
|
+
|
|
62
|
+
if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring):
|
|
63
|
+
KEYRING_AVAILABLE = True
|
|
64
|
+
else:
|
|
65
|
+
KEYRING_AVAILABLE = False
|
|
66
|
+
except ImportError:
|
|
67
|
+
KEYRING_AVAILABLE = False
|
|
68
|
+
if (
|
|
69
|
+
"UNLOCK" in os.environ
|
|
70
|
+
and os.environ["UNLOCK"]
|
|
71
|
+
and self.config_key in self.config
|
|
72
|
+
and self.config[self.config_key]
|
|
73
|
+
and password_storage == "environment"
|
|
74
|
+
):
|
|
75
|
+
log.debug("Trying to use environmental variable to unlock wallet")
|
|
76
|
+
self.unlock(os.environ.get("UNLOCK"))
|
|
77
|
+
return bool(self.password)
|
|
78
|
+
elif (
|
|
79
|
+
password_storage == "keyring"
|
|
80
|
+
and KEYRING_AVAILABLE
|
|
81
|
+
and self.config_key in self.config
|
|
82
|
+
and self.config[self.config_key]
|
|
83
|
+
):
|
|
84
|
+
log.debug("Trying to use keyring to unlock wallet")
|
|
85
|
+
pwd = keyring.get_password("nectar", "wallet")
|
|
86
|
+
self.unlock(pwd)
|
|
87
|
+
return bool(self.password)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def lock(self):
|
|
91
|
+
"""Lock the store so that we can no longer decrypt the content of the
|
|
92
|
+
store
|
|
93
|
+
"""
|
|
94
|
+
self.password = None
|
|
95
|
+
self.decrypted_master = None
|
|
96
|
+
|
|
97
|
+
def unlock(self, password):
|
|
98
|
+
"""The password is used to encrypt this masterpassword. To
|
|
99
|
+
decrypt the keys stored in the keys database, one must use
|
|
100
|
+
BIP38, decrypt the masterpassword from the configuration
|
|
101
|
+
store with the user password, and use the decrypted
|
|
102
|
+
masterpassword to decrypt the BIP38 encrypted private keys
|
|
103
|
+
from the keys storage!
|
|
104
|
+
|
|
105
|
+
:param str password: Password to use for en-/de-cryption
|
|
106
|
+
"""
|
|
107
|
+
self.password = password
|
|
108
|
+
if self.config_key in self.config and self.config[self.config_key]:
|
|
109
|
+
self._decrypt_masterpassword()
|
|
110
|
+
else:
|
|
111
|
+
self._new_masterpassword(password)
|
|
112
|
+
self._save_encrypted_masterpassword()
|
|
113
|
+
|
|
114
|
+
def wipe_masterpassword(self):
|
|
115
|
+
"""Removes the encrypted masterpassword from config storage"""
|
|
116
|
+
if self.config_key in self.config and self.config[self.config_key]:
|
|
117
|
+
self.config[self.config_key] = None
|
|
118
|
+
|
|
119
|
+
def _decrypt_masterpassword(self):
|
|
120
|
+
"""Decrypt the encrypted masterkey"""
|
|
121
|
+
aes = AESCipher(self.password)
|
|
122
|
+
checksum, encrypted_master = self.config[self.config_key].split("$")
|
|
123
|
+
try:
|
|
124
|
+
decrypted_master = aes.decrypt(encrypted_master)
|
|
125
|
+
except Exception:
|
|
126
|
+
self._raise_wrongmasterpassexception()
|
|
127
|
+
if checksum != self._derive_checksum(decrypted_master):
|
|
128
|
+
self._raise_wrongmasterpassexception()
|
|
129
|
+
self.decrypted_master = decrypted_master
|
|
130
|
+
|
|
131
|
+
def _raise_wrongmasterpassexception(self):
|
|
132
|
+
self.password = None
|
|
133
|
+
raise WrongMasterPasswordException
|
|
134
|
+
|
|
135
|
+
def _save_encrypted_masterpassword(self):
|
|
136
|
+
self.config[self.config_key] = self._get_encrypted_masterpassword()
|
|
137
|
+
|
|
138
|
+
def _new_masterpassword(self, password):
|
|
139
|
+
"""Generate a new random masterkey, encrypt it with the password and
|
|
140
|
+
store it in the store.
|
|
141
|
+
|
|
142
|
+
:param str password: Password to use for en-/de-cryption
|
|
143
|
+
"""
|
|
144
|
+
# make sure to not overwrite an existing key
|
|
145
|
+
if self.config_key in self.config and self.config[self.config_key]:
|
|
146
|
+
raise Exception("Storage already has a masterpassword!")
|
|
147
|
+
|
|
148
|
+
self.decrypted_master = hexlify(os.urandom(32)).decode("ascii")
|
|
149
|
+
|
|
150
|
+
# Encrypt and save master
|
|
151
|
+
self.password = password
|
|
152
|
+
self._save_encrypted_masterpassword()
|
|
153
|
+
return self.masterkey
|
|
154
|
+
|
|
155
|
+
def _derive_checksum(self, s):
|
|
156
|
+
"""Derive the checksum
|
|
157
|
+
|
|
158
|
+
:param str s: Random string for which to derive the checksum
|
|
159
|
+
"""
|
|
160
|
+
checksum = hashlib.sha256(bytes(s, "ascii")).hexdigest()
|
|
161
|
+
return checksum[:4]
|
|
162
|
+
|
|
163
|
+
def _get_encrypted_masterpassword(self):
|
|
164
|
+
"""Obtain the encrypted masterkey
|
|
165
|
+
|
|
166
|
+
.. note:: The encrypted masterkey is checksummed, so that we can
|
|
167
|
+
figure out that a provided password is correct or not. The
|
|
168
|
+
checksum is only 4 bytes long!
|
|
169
|
+
"""
|
|
170
|
+
if not self.unlocked():
|
|
171
|
+
raise WalletLocked
|
|
172
|
+
aes = AESCipher(self.password)
|
|
173
|
+
return "{}${}".format(self._derive_checksum(self.masterkey), aes.encrypt(self.masterkey))
|
|
174
|
+
|
|
175
|
+
def change_password(self, newpassword):
|
|
176
|
+
"""Change the password that allows to decrypt the master key"""
|
|
177
|
+
if not self.unlocked():
|
|
178
|
+
raise WalletLocked
|
|
179
|
+
self.password = newpassword
|
|
180
|
+
self._save_encrypted_masterpassword()
|
|
181
|
+
|
|
182
|
+
def decrypt(self, wif):
|
|
183
|
+
"""Decrypt the content according to BIP38
|
|
184
|
+
|
|
185
|
+
:param str wif: Encrypted key
|
|
186
|
+
"""
|
|
187
|
+
if not self.unlocked():
|
|
188
|
+
raise WalletLocked
|
|
189
|
+
return format(bip38.decrypt(wif, self.masterkey), "wif")
|
|
190
|
+
|
|
191
|
+
def deriveChecksum(self, s):
|
|
192
|
+
"""Derive the checksum"""
|
|
193
|
+
checksum = hashlib.sha256(bytes(s, "ascii")).hexdigest()
|
|
194
|
+
return checksum[:4]
|
|
195
|
+
|
|
196
|
+
def encrypt_text(self, txt):
|
|
197
|
+
"""Encrypt the content according to BIP38
|
|
198
|
+
|
|
199
|
+
:param str wif: Unencrypted key
|
|
200
|
+
"""
|
|
201
|
+
if not self.unlocked():
|
|
202
|
+
raise WalletLocked
|
|
203
|
+
aes = AESCipher(self.masterkey)
|
|
204
|
+
return "{}${}".format(self.deriveChecksum(txt), aes.encrypt(txt))
|
|
205
|
+
|
|
206
|
+
def decrypt_text(self, enctxt):
|
|
207
|
+
"""Decrypt the content according to BIP38
|
|
208
|
+
|
|
209
|
+
:param str wif: Encrypted key
|
|
210
|
+
"""
|
|
211
|
+
if not self.unlocked():
|
|
212
|
+
raise WalletLocked
|
|
213
|
+
aes = AESCipher(self.masterkey)
|
|
214
|
+
checksum, encrypted_text = enctxt.split("$")
|
|
215
|
+
try:
|
|
216
|
+
decrypted_text = aes.decrypt(encrypted_text)
|
|
217
|
+
except Exception:
|
|
218
|
+
raise WrongMasterPasswordException
|
|
219
|
+
if checksum != self.deriveChecksum(decrypted_text):
|
|
220
|
+
raise WrongMasterPasswordException
|
|
221
|
+
return decrypted_text
|
|
222
|
+
|
|
223
|
+
def encrypt(self, wif):
|
|
224
|
+
"""Encrypt the content according to BIP38
|
|
225
|
+
|
|
226
|
+
:param str wif: Unencrypted key
|
|
227
|
+
"""
|
|
228
|
+
if not self.unlocked():
|
|
229
|
+
raise WalletLocked
|
|
230
|
+
return format(bip38.encrypt(str(wif), self.masterkey), "encwif")
|
|
231
|
+
|
|
232
|
+
def changePassword(self, newpassword): # pragma: no cover
|
|
233
|
+
warnings.warn(
|
|
234
|
+
"changePassword will be replaced by change_password in the future",
|
|
235
|
+
PendingDeprecationWarning,
|
|
236
|
+
)
|
|
237
|
+
return self.change_password(newpassword)
|
nectarstorage/py.typed
ADDED
|
File without changes
|
nectarstorage/ram.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/ram.py
|
|
2
|
+
from .interfaces import StoreInterface
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InRamStore(StoreInterface):
|
|
6
|
+
"""The InRamStore inherits
|
|
7
|
+
:class:`nectarstorage.interfaces.StoreInterface` and extends it by two
|
|
8
|
+
further calls for wipe and delete.
|
|
9
|
+
|
|
10
|
+
The store is syntactically equivalent to a regular dictionary.
|
|
11
|
+
|
|
12
|
+
.. warning:: If you are trying to obtain a value for a key that does
|
|
13
|
+
**not** exist in the store, the library will **NOT** raise but
|
|
14
|
+
return a ``None`` value. This represents the biggest difference to
|
|
15
|
+
a regular ``dict`` class.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Specific for this library
|
|
19
|
+
def delete(self, key):
|
|
20
|
+
"""Delete a key from the store"""
|
|
21
|
+
self._data.pop(key, None)
|
|
22
|
+
|
|
23
|
+
def wipe(self):
|
|
24
|
+
"""Wipe the store"""
|
|
25
|
+
keys = list(self.keys()).copy()
|
|
26
|
+
for key in keys:
|
|
27
|
+
self.delete(key)
|