Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client): rsa and eddsa encoding #1481

Merged
merged 5 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion binance/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.parse import urlencode
import time
import aiohttp
import yarl

from binance.enums import HistoricalKlinesType
from binance.exceptions import (
Expand Down Expand Up @@ -103,8 +104,14 @@ async def _request(
):
kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs)

if method == 'get':
# url encode the query string
if 'params' in kwargs:
uri = f"{uri}?{kwargs['params']}"
kwargs.pop('params')

async with getattr(self.session, method)(
uri, proxy=self.https_proxy, **kwargs
yarl.URL(uri, encoded=True), proxy=self.https_proxy, **kwargs
) as response:
self.response = response
return await self._handle_response(response)
Expand Down
22 changes: 18 additions & 4 deletions binance/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from Crypto.PublicKey import RSA, ECC
from Crypto.Hash import SHA256
from Crypto.Signature import pkcs1_15, eddsa
import urllib.parse as _urlencode
from operator import itemgetter
from urllib.parse import urlencode

Expand Down Expand Up @@ -59,6 +60,8 @@ class BaseClient:

REQUEST_TIMEOUT: float = 10

REQUEST_RECVWINDOW: int = 10000 # 10 seconds

SYMBOL_TYPE_SPOT = "SPOT"

ORDER_STATUS_NEW = "NEW"
Expand Down Expand Up @@ -300,13 +303,21 @@ def _rsa_signature(self, query_string: str):
assert self.PRIVATE_KEY
h = SHA256.new(query_string.encode("utf-8"))
signature = pkcs1_15.new(self.PRIVATE_KEY).sign(h) # type: ignore
return b64encode(signature).decode()
res = b64encode(signature).decode()
# return self.encode_uri_component(res)
return res

@staticmethod
def encode_uri_component(uri, safe="~()*!.'"):
return _urlencode.quote(uri, safe=safe)

def _ed25519_signature(self, query_string: str):
assert self.PRIVATE_KEY
return b64encode(
res = b64encode(
eddsa.new(self.PRIVATE_KEY, "rfc8032").sign(query_string.encode())
).decode() # type: ignore
# return self.encode_uri_component(res)
return res

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

def _generate_signature(self, data: Dict) -> str:
def _generate_signature(self, data: Dict, uri_encode=True) -> str:
sig_func = self._hmac_signature
if self.PRIVATE_KEY:
if self._is_rsa:
sig_func = self._rsa_signature
else:
sig_func = self._ed25519_signature
query_string = "&".join([f"{d[0]}={d[1]}" for d in self._order_params(data)])
return sig_func(query_string)
res = sig_func(query_string)
return self.encode_uri_component(res) if uri_encode else res

def _sign_ws_params(self, params, signature_func):
if "signature" in params:
Expand Down Expand Up @@ -445,6 +457,8 @@ def _get_request_kwargs(
kwargs["data"]["timestamp"] = int(
time.time() * 1000 + self.timestamp_offset
)
if self.REQUEST_RECVWINDOW:
kwargs["data"]["recvWindow"] = self.REQUEST_RECVWINDOW
kwargs["data"]["signature"] = self._generate_signature(kwargs["data"])

# sort get and post params to match signature order
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cryptography.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_encryption():
private_key_pass=case["password"],
ping=False,
)
signature = client._generate_signature(data)
signature = client._generate_signature(data, False)
assert signature == case["expected_signature"], (
f"Test failed: {case['description']}"
)
Loading