Skip to content

Commit 81c2e10

Browse files
authored
fix(client): rsa and eddsa encoding (sammchardy#1481)
* fix(client): rsa and eddsa encoding * fix tests * properly url encode in the async client * protect code * add recvwindow
1 parent 4b8d1ed commit 81c2e10

File tree

3 files changed

+27
-6
lines changed

3 files changed

+27
-6
lines changed

binance/async_client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from urllib.parse import urlencode
55
import time
66
import aiohttp
7+
import yarl
78

89
from binance.enums import HistoricalKlinesType
910
from binance.exceptions import (
@@ -103,8 +104,14 @@ async def _request(
103104
):
104105
kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs)
105106

107+
if method == 'get':
108+
# url encode the query string
109+
if 'params' in kwargs:
110+
uri = f"{uri}?{kwargs['params']}"
111+
kwargs.pop('params')
112+
106113
async with getattr(self.session, method)(
107-
uri, proxy=self.https_proxy, **kwargs
114+
yarl.URL(uri, encoded=True), proxy=self.https_proxy, **kwargs
108115
) as response:
109116
self.response = response
110117
return await self._handle_response(response)

binance/base_client.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from Crypto.PublicKey import RSA, ECC
1111
from Crypto.Hash import SHA256
1212
from Crypto.Signature import pkcs1_15, eddsa
13+
import urllib.parse as _urlencode
1314
from operator import itemgetter
1415
from urllib.parse import urlencode
1516

@@ -59,6 +60,8 @@ class BaseClient:
5960

6061
REQUEST_TIMEOUT: float = 10
6162

63+
REQUEST_RECVWINDOW: int = 10000 # 10 seconds
64+
6265
SYMBOL_TYPE_SPOT = "SPOT"
6366

6467
ORDER_STATUS_NEW = "NEW"
@@ -300,13 +303,21 @@ def _rsa_signature(self, query_string: str):
300303
assert self.PRIVATE_KEY
301304
h = SHA256.new(query_string.encode("utf-8"))
302305
signature = pkcs1_15.new(self.PRIVATE_KEY).sign(h) # type: ignore
303-
return b64encode(signature).decode()
306+
res = b64encode(signature).decode()
307+
# return self.encode_uri_component(res)
308+
return res
309+
310+
@staticmethod
311+
def encode_uri_component(uri, safe="~()*!.'"):
312+
return _urlencode.quote(uri, safe=safe)
304313

305314
def _ed25519_signature(self, query_string: str):
306315
assert self.PRIVATE_KEY
307-
return b64encode(
316+
res = b64encode(
308317
eddsa.new(self.PRIVATE_KEY, "rfc8032").sign(query_string.encode())
309318
).decode() # type: ignore
319+
# return self.encode_uri_component(res)
320+
return res
310321

311322
def _hmac_signature(self, query_string: str) -> str:
312323
assert self.API_SECRET, "API Secret required for private endpoints"
@@ -317,15 +328,16 @@ def _hmac_signature(self, query_string: str) -> str:
317328
)
318329
return m.hexdigest()
319330

320-
def _generate_signature(self, data: Dict) -> str:
331+
def _generate_signature(self, data: Dict, uri_encode=True) -> str:
321332
sig_func = self._hmac_signature
322333
if self.PRIVATE_KEY:
323334
if self._is_rsa:
324335
sig_func = self._rsa_signature
325336
else:
326337
sig_func = self._ed25519_signature
327338
query_string = "&".join([f"{d[0]}={d[1]}" for d in self._order_params(data)])
328-
return sig_func(query_string)
339+
res = sig_func(query_string)
340+
return self.encode_uri_component(res) if uri_encode else res
329341

330342
def _sign_ws_params(self, params, signature_func):
331343
if "signature" in params:
@@ -445,6 +457,8 @@ def _get_request_kwargs(
445457
kwargs["data"]["timestamp"] = int(
446458
time.time() * 1000 + self.timestamp_offset
447459
)
460+
if self.REQUEST_RECVWINDOW:
461+
kwargs["data"]["recvWindow"] = self.REQUEST_RECVWINDOW
448462
kwargs["data"]["signature"] = self._generate_signature(kwargs["data"])
449463

450464
# sort get and post params to match signature order

tests/test_cryptography.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_encryption():
4646
private_key_pass=case["password"],
4747
ping=False,
4848
)
49-
signature = client._generate_signature(data)
49+
signature = client._generate_signature(data, False)
5050
assert signature == case["expected_signature"], (
5151
f"Test failed: {case['description']}"
5252
)

0 commit comments

Comments
 (0)