-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathonboarding.py
More file actions
187 lines (159 loc) · 5.66 KB
/
onboarding.py
File metadata and controls
187 lines (159 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import base64
from web3 import Web3
from dydx3.constants import OFF_CHAIN_ONBOARDING_ACTION
from dydx3.constants import OFF_CHAIN_KEY_DERIVATION_ACTION
from dydx3.eth_signing import SignOnboardingAction
from dydx3.helpers.requests import request
from dydx3.starkex.helpers import private_key_to_public_key_pair_hex
class Onboarding(object):
def __init__(
self,
host,
eth_signer,
network_id,
default_address,
api_timeout,
stark_public_key=None,
stark_public_key_y_coordinate=None,
):
self.host = host
self.default_address = default_address
self.api_timeout = api_timeout
self.stark_public_key = stark_public_key
self.stark_public_key_y_coordinate = stark_public_key_y_coordinate
self.signer = SignOnboardingAction(eth_signer, network_id)
# ============ Request Helpers ============
def _post(
self,
endpoint,
data,
opt_ethereum_address,
):
ethereum_address = opt_ethereum_address or self.default_address
signature = self.signer.sign(
ethereum_address,
action=OFF_CHAIN_ONBOARDING_ACTION,
)
request_path = '/'.join(['/v3', endpoint])
return request(
self.host + request_path,
'post',
{
'DYDX-SIGNATURE': signature,
'DYDX-ETHEREUM-ADDRESS': ethereum_address,
},
data,
self.api_timeout,
)
# ============ Requests ============
def create_user(
self,
stark_public_key=None,
stark_public_key_y_coordinate=None,
ethereum_address=None,
referred_by_affiliate_link=None,
country=None,
):
'''
Onboard a user with an Ethereum address and STARK key.
By default, onboards using the STARK and/or API public keys
corresponding to private keys that the client was initialized with.
:param stark_public_key: optional
:type stark_public_key: str
:param stark_public_key_y_coordinate: optional
:type stark_public_key_y_coordinate: str
:param ethereum_address: optional
:type ethereum_address: str
:param referred_by_affiliate_link: optional
:type referred_by_affiliate_link: str
:param country optional
:type country: str (ISO 3166-1 Alpha-2)
:returns: { apiKey, user, account }
:raises: DydxAPIError
'''
stark_key = stark_public_key or self.stark_public_key
stark_key_y = (
stark_public_key_y_coordinate or self.stark_public_key_y_coordinate
)
if stark_key is None:
raise ValueError(
'STARK private key or public key is required'
)
if stark_key_y is None:
raise ValueError(
'STARK private key or public key y-coordinate is required'
)
return self._post(
'onboarding',
{
'starkKey': stark_key,
'starkKeyYCoordinate': stark_key_y,
'referredByAffiliateLink': referred_by_affiliate_link,
'country': country,
},
ethereum_address,
)
# ============ Key Derivation ============
def derive_stark_key(
self,
ethereum_address=None,
):
'''
Derive a STARK key pair deterministically from an Ethereum key.
This is the function used by the dYdX frontend to derive a user's
STARK key pair in a way that is recoverable. Programmatic traders may
optionally derive their STARK key pair in the same way.
:param ethereum_address: optional
:type ethereum_address: str
'''
signature = self.signer.sign(
ethereum_address or self.default_address,
action=OFF_CHAIN_KEY_DERIVATION_ACTION,
)
signature_int = int(signature, 16)
hashed_signature = Web3.solidity_keccak(['uint256'], [signature_int])
private_key_int = int(hashed_signature.hex(), 16) >> 5
private_key_hex = hex(private_key_int)
public_x, public_y = private_key_to_public_key_pair_hex(
private_key_hex,
)
return {
'public_key': public_x,
'public_key_y_coordinate': public_y,
'private_key': private_key_hex
}
def recover_default_api_key_credentials(
self,
ethereum_address=None,
):
'''
Derive API credentials deterministically from an Ethereum key.
This can be used to recover the default API key credentials, which are
the same set of credentials used in the dYdX frontend.
'''
signature = self.signer.sign(
ethereum_address or self.default_address,
action=OFF_CHAIN_ONBOARDING_ACTION,
)
r_hex = signature[2:66]
r_int = int(r_hex, 16)
hashed_r_bytes = bytes(Web3.solidity_keccak(['uint256'], [r_int]))
secret_bytes = hashed_r_bytes[:30]
s_hex = signature[66:130]
s_int = int(s_hex, 16)
hashed_s_bytes = bytes(Web3.solidity_keccak(['uint256'], [s_int]))
key_bytes = hashed_s_bytes[:16]
passphrase_bytes = hashed_s_bytes[16:31]
key_hex = key_bytes.hex()
key_uuid = '-'.join([
key_hex[:8],
key_hex[8:12],
key_hex[12:16],
key_hex[16:20],
key_hex[20:],
])
return {
'secret': base64.urlsafe_b64encode(secret_bytes).decode(),
'key': key_uuid,
'passphrase': base64.urlsafe_b64encode(passphrase_bytes).decode(),
}