1
|
|
|
""" |
2
|
|
|
Description: |
3
|
|
|
|
4
|
|
|
Wallet Class for signing, generating, and interacting with a Tradehub account. |
5
|
|
|
This client is called as part of the Authenticated Client and the Demex Client. |
6
|
|
|
This class is designed to generate mnemonics, convert mnemonics, private keys, public keys, and addresses. |
7
|
|
|
|
8
|
|
|
Usage:: |
9
|
|
|
|
10
|
|
|
from tradehub.wallet import Wallet |
11
|
|
|
""" |
12
|
|
|
|
13
|
|
|
import base64 |
14
|
|
|
import bech32 |
15
|
|
|
import ecdsa |
16
|
|
|
import hashlib |
17
|
|
|
import hdwallets |
18
|
|
|
import mnemonic |
19
|
|
|
|
20
|
|
|
from tradehub.utils import sort_and_stringify_json |
21
|
|
|
|
22
|
|
|
|
23
|
|
|
class Wallet(object): |
24
|
|
|
""" |
25
|
|
|
This class allows the user to interact with the Demex and Tradehub wallet functions. |
26
|
|
|
Execution of this function is as follows:: |
27
|
|
|
|
28
|
|
|
Wallet(mnemonic='lorem ipsum dolor consectetur adipiscing eiusmod tempor incididunt labore magna', |
29
|
|
|
network='mainnet') |
30
|
|
|
""" |
31
|
|
|
|
32
|
|
|
_DEFAULT_DERIVATION_PATH = "m/44'/118'/0'/0/0" |
33
|
|
|
|
34
|
|
|
def __init__(self, mnemonic: str = None, network: str = "testnet"): |
35
|
|
|
""" |
36
|
|
|
:param mnemonic: The 12 or 24 word seed required to access your wallet and trade on Demex |
37
|
|
|
:type mnemonic: str |
38
|
|
|
:param network: The network you want to interact with. Accepts "testnet" or "mainnet". |
39
|
|
|
:type network: str |
40
|
|
|
""" |
41
|
|
|
self.DEFAULT_BECH32_PREFIX_DICT = { |
42
|
|
|
"main": "swth", |
43
|
|
|
"mainnet": "swth", |
44
|
|
|
"test": "tswth", |
45
|
|
|
"testnet": "tswth", |
46
|
|
|
} |
47
|
|
|
self.DEFAULT_BECH32_PREFIX = self.DEFAULT_BECH32_PREFIX_DICT[network.lower()] |
48
|
|
|
|
49
|
|
|
if mnemonic and len(mnemonic.split()) in [12, 24]: |
50
|
|
|
self._mnemonic = mnemonic |
51
|
|
|
else: |
52
|
|
|
self._mnemonic = self.generate_12_word_wallet() |
53
|
|
|
|
54
|
|
|
self._private_key = self.mnemonic_to_private_key(mnemonic_phrase=self._mnemonic) |
55
|
|
|
self.public_key = self.private_key_to_public_key(private_key=self._private_key) |
56
|
|
|
self.base64_public_key = base64.b64encode(self.public_key).decode("utf-8") |
57
|
|
|
self.address = self.private_key_to_address(private_key=self._private_key) |
58
|
|
|
|
59
|
|
|
def generate_12_word_wallet(self): |
60
|
|
|
""" |
61
|
|
|
Function to generate a 12 word mnemonic. |
62
|
|
|
|
63
|
|
|
Execution of this function is as follows:: |
64
|
|
|
|
65
|
|
|
generate_12_word_wallet() |
66
|
|
|
|
67
|
|
|
The expected return result for this function is as follows:: |
68
|
|
|
|
69
|
|
|
venture consider cool fury front middle junk person suit assist garbage category |
70
|
|
|
|
71
|
|
|
:return: 12 word String that should be highly protected if storing any funds. |
72
|
|
|
""" |
73
|
|
|
return mnemonic.Mnemonic(language="english").generate(strength=128) |
74
|
|
|
|
75
|
|
|
def generate_24_word_wallet(self): |
76
|
|
|
""" |
77
|
|
|
Function to generate a 24 word mnemonic. |
78
|
|
|
|
79
|
|
|
Execution of this function is as follows:: |
80
|
|
|
|
81
|
|
|
generate_24_word_wallet() |
82
|
|
|
|
83
|
|
|
The expected return result for this function is as follows:: |
84
|
|
|
|
85
|
|
|
refuse flag merge fiction choose dream frown gauge need fabric once pizza actual armed reopen couple family fury reopen slush blue try focus minute |
86
|
|
|
|
87
|
|
|
:return: 24 word String that should be highly protected if storing any funds. |
88
|
|
|
""" |
89
|
|
|
return mnemonic.Mnemonic(language="english").generate(strength=256) |
90
|
|
|
|
91
|
|
|
def mnemonic_to_private_key(self, mnemonic_phrase: str = None, wallet_path: str = _DEFAULT_DERIVATION_PATH) -> bytes: |
92
|
|
|
""" |
93
|
|
|
Function to find the private key based on the mnemonic passed to the function. |
94
|
|
|
|
95
|
|
|
Execution of this function is as follows:: |
96
|
|
|
|
97
|
|
|
mnemonic_to_private_key(mnemonic_phrase='venture consider cool fury front middle junk person suit assist garbage category', |
98
|
|
|
wallet_path=_DEFAULT_DERIVATION_PATH) |
99
|
|
|
|
100
|
|
|
The expected return result for this function is as follows:: |
101
|
|
|
|
102
|
|
|
b'\x15\xcf\xdd\xdf\xead\x88\xd2y!\xdb\xb61\xa6\x98\xeeQm\x05\xed\x8d%43!\n\xccS\xcbsf\x90' |
103
|
|
|
|
104
|
|
|
:param mnemonic_phrase: String mnemonic phrase for a tradehub wallet. |
105
|
|
|
:param wallet_path: String Derivation path to generated the private key from the mnemonic. |
106
|
|
|
:return: Byte value that is equal to the private key. |
107
|
|
|
""" |
108
|
|
|
mnemonic_bytes = mnemonic.Mnemonic.to_seed(mnemonic_phrase, passphrase="") |
109
|
|
|
hd_wallet = hdwallets.BIP32.from_seed(mnemonic_bytes) |
110
|
|
|
self._private_key = hd_wallet.get_privkey_from_path(wallet_path) |
111
|
|
|
return self._private_key |
112
|
|
|
|
113
|
|
|
def private_key_to_public_key(self, private_key: bytes = None) -> bytes: |
114
|
|
|
""" |
115
|
|
|
Function to find the public key based on the private key passed to the function. |
116
|
|
|
|
117
|
|
|
Execution of this function is as follows:: |
118
|
|
|
|
119
|
|
|
private_key_to_public_key(private_key=b'\x15\xcf\xdd\xdf\xead\x88\xd2y!\xdb\xb61\xa6\x98\xeeQm\x05\xed\x8d%43!\n\xccS\xcbsf\x90') |
120
|
|
|
|
121
|
|
|
The expected return result for this function is as follows:: |
122
|
|
|
|
123
|
|
|
b'\x02o\x1f\xfbL\x96\xe8\x1e\xb0\x12V\x80\xc7t\xfc\xb40R\xaeu\xf3{\xf6\xd7m]\xd1\xa9\x91\xa8\xe0Df' |
124
|
|
|
|
125
|
|
|
:param private_key: Byte representation of the wallets private key. |
126
|
|
|
:return: Binary value that is equal to the public key. |
127
|
|
|
""" |
128
|
|
|
privkey_obj = ecdsa.SigningKey.from_string(self._private_key, curve=ecdsa.SECP256k1) |
129
|
|
|
self.public_key_obj = privkey_obj.get_verifying_key() |
130
|
|
|
self.public_key = self.public_key_obj.to_string("compressed") |
131
|
|
|
return self.public_key |
132
|
|
|
|
133
|
|
|
def public_key_to_address(self, public_key: bytes = None, hrp: str = None) -> str: |
134
|
|
|
""" |
135
|
|
|
Function to find the readable address from the public key. |
136
|
|
|
|
137
|
|
|
Execution of this function is as follows:: |
138
|
|
|
|
139
|
|
|
public_key_to_address(public_key=b'\x02o\x1f\xfbL\x96\xe8\x1e\xb0\x12V\x80\xc7t\xfc\xb40R\xaeu\xf3{\xf6\xd7m]\xd1\xa9\x91\xa8\xe0Df', |
140
|
|
|
hrp=None) |
141
|
|
|
|
142
|
|
|
The expected return result for this function is as follows:: |
143
|
|
|
|
144
|
|
|
tswth1upcgussnx4p3jegwj3x2fccwlajwckkzgstrp8 |
145
|
|
|
|
146
|
|
|
:param public_key: Byte representation of the wallets public key. |
147
|
|
|
:return: String and human readable Tradehub address. |
148
|
|
|
""" |
149
|
|
|
if hrp is None: |
150
|
|
|
hrp = self.DEFAULT_BECH32_PREFIX |
151
|
|
|
s = hashlib.new("sha256", public_key).digest() |
152
|
|
|
r = hashlib.new("ripemd160", s).digest() |
153
|
|
|
five_bit_r = bech32.convertbits(r, 8, 5) |
154
|
|
|
assert five_bit_r is not None, "Unsuccessful bech32.convertbits call" |
155
|
|
|
return bech32.bech32_encode(hrp, five_bit_r) |
156
|
|
|
|
157
|
|
|
def private_key_to_address(self, private_key: bytes = None, hrp: str = None) -> str: |
158
|
|
|
""" |
159
|
|
|
Function to find the readable address from the private key. |
160
|
|
|
|
161
|
|
|
Execution of this function is as follows:: |
162
|
|
|
|
163
|
|
|
public_key_to_address(public_key=b'\x15\xcf\xdd\xdf\xead\x88\xd2y!\xdb\xb61\xa6\x98\xeeQm\x05\xed\x8d%43!\n\xccS\xcbsf\x90', |
164
|
|
|
hrp=None) |
165
|
|
|
|
166
|
|
|
The expected return result for this function is as follows:: |
167
|
|
|
|
168
|
|
|
tswth1upcgussnx4p3jegwj3x2fccwlajwckkzgstrp8 |
169
|
|
|
|
170
|
|
|
:param private_key: Byte representation of the wallets private key. |
171
|
|
|
:return: String and human readable Tradehub address. |
172
|
|
|
""" |
173
|
|
|
if hrp is None: |
174
|
|
|
hrp = self.DEFAULT_BECH32_PREFIX |
175
|
|
|
public_key = self.private_key_to_public_key(private_key) |
176
|
|
|
return self.public_key_to_address(public_key=public_key, hrp=hrp) |
177
|
|
|
|
178
|
|
|
def _sign(self, message: dict) -> str: |
179
|
|
|
""" |
180
|
|
|
Function to find the readable address from the public key. |
181
|
|
|
|
182
|
|
|
Execution of this function is as follows:: |
183
|
|
|
|
184
|
|
|
_sign(message={'message': 'This is a Tradehub test.'}) |
185
|
|
|
|
186
|
|
|
The expected return result for this function is as follows:: |
187
|
|
|
|
188
|
|
|
XP4s5fCY6UKh/dvscYsDylhYeD64WOTYohEU3QuuUygPCiQF9uFS9mHgwLaTFfFL41mdTRZclxq6CETr8bcZ5w== |
189
|
|
|
|
190
|
|
|
:param message: Dictionary message to sign. |
191
|
|
|
:return: Signed message. |
192
|
|
|
""" |
193
|
|
|
message_str = sort_and_stringify_json(message=message) |
194
|
|
|
message_bytes = message_str.encode("utf-8") |
195
|
|
|
|
196
|
|
|
private_key = ecdsa.SigningKey.from_string(self._private_key, curve=ecdsa.SECP256k1) |
197
|
|
|
signature_compact = private_key.sign_deterministic( |
198
|
|
|
message_bytes, |
199
|
|
|
hashfunc=hashlib.sha256, |
200
|
|
|
sigencode=ecdsa.util.sigencode_string_canonize |
201
|
|
|
) |
202
|
|
|
|
203
|
|
|
signature_base64_str = base64.b64encode(signature_compact).decode("utf-8") |
204
|
|
|
return signature_base64_str |
205
|
|
|
|