Skip to content

Commit

Permalink
v3.1.0 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
jonte-z committed Jun 2, 2023
1 parent eafa989 commit 630e594
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 15 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 3.1.0 - 2023-06-01

### Added
- Add support for use of ED25519 Key to generate signatures

## 3.0.0 - 2023-05-23

### Changed
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Please find `examples` folder to check for more endpoints.

### Authentication

Binance supports HMAC and RSA API authentication.
Binance supports HMAC, RSA and ED25519 API authentication.

```python

Expand All @@ -85,12 +85,22 @@ print(client.account())
client = Client(api_key=api_key, private_key=private_key)
print(client.account())

# ED25519 Keys
api_key = ""
private_key = "./private_key.pem"
private_key_pass = "<password_if_applicable>"

with open(private_key, 'rb') as f:
private_key = f.read()

spot_client = Client(api_key=api_key, private_key=private_key, private_key_pass=private_key_pass)

# Encrypted RSA Key
client = Client(api_key=api_key, private_key=private_key, private_key_pass='password')
print(client.account())
```

Please find `examples/spot/trade/get_account.py` for more details.
Please find `examples/spot/wallet/account_snapshot.py` for more details on ED25519.
Please find `examples/spot/trade/get_account.py` for more details on RSA.

### Testnet

Expand Down
2 changes: 1 addition & 1 deletion binance/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.0.0"
__version__ = "3.1.0"
14 changes: 10 additions & 4 deletions binance/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from binance.lib.utils import cleanNoneValue
from binance.lib.utils import encoded_string
from binance.lib.utils import check_required_parameter
from binance.lib.authentication import hmac_hashing, rsa_signature
from binance.lib.authentication import hmac_hashing, rsa_signature, ed25519_signature


class API(object):
Expand Down Expand Up @@ -149,9 +149,15 @@ def _prepare_params(self, params):
return encoded_string(cleanNoneValue(params))

def _get_sign(self, payload):
if self.private_key:
return rsa_signature(self.private_key, payload, self.private_key_pass)
return hmac_hashing(self.api_secret, payload)
if self.private_key is not None:
try:
return ed25519_signature(
self.private_key, payload, self.private_key_pass
)
except ValueError:
return rsa_signature(self.private_key, payload, self.private_key_pass)
else:
return hmac_hashing(self.api_secret, payload)

def _dispatch_request(self, http_method):
return {
Expand Down
11 changes: 9 additions & 2 deletions binance/lib/authentication.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import hmac
import hashlib
from base64 import b64encode
from Crypto.PublicKey import RSA
from Crypto.PublicKey import RSA, ECC
from Crypto.Hash import SHA256
from Crypto.Signature import pkcs1_15
from Crypto.Signature import pkcs1_15, eddsa


def hmac_hashing(api_secret, payload):
Expand All @@ -16,3 +16,10 @@ def rsa_signature(private_key, payload, private_key_pass=None):
h = SHA256.new(payload.encode("utf-8"))
signature = pkcs1_15.new(private_key).sign(h)
return b64encode(signature)


def ed25519_signature(private_key, payload, private_key_pass=None):
private_key = ECC.import_key(private_key, passphrase=private_key_pass)
signer = eddsa.new(private_key, "rfc8032")
signature = signer.sign(payload.encode("utf-8"))
return b64encode(signature)
15 changes: 12 additions & 3 deletions examples/spot/wallet/account_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
import logging
from binance.spot import Spot as Client
from binance.lib.utils import config_logging
from examples.utils.prepare_env import get_api_key

config_logging(logging, logging.DEBUG)

api_key, api_secret = get_api_key()
api_key = "api_key"
private_key = "./private_key.txt"
private_key_pass = "private key password (if applicable)"

spot_client = Client(api_key, api_secret)
with open(private_key, "rb") as f:
private_key = f.read()

spot_client = Client(
api_key=api_key,
private_key=private_key,
private_key_pass=private_key_pass,
key_type="ed25519",
)
logging.info(spot_client.account_status())
2 changes: 1 addition & 1 deletion requirements/common.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
requests>=2.25.1
websocket-client>=1.5.0
pycryptodome>=3.15.0
pycryptodome>=3.15.0
63 changes: 62 additions & 1 deletion tests/lib/test_authentication.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from binance.lib.authentication import hmac_hashing, rsa_signature
from binance.lib.authentication import hmac_hashing, rsa_signature, ed25519_signature
import pytest


def test_hamc_hashing():
Expand Down Expand Up @@ -81,3 +82,63 @@ def test_encrypted_rsa_signature():
"utf-8"
)
sign.should.equal(expected_signature)


def test_invalid_rsa_secret_key():
invalid_rsa_secret_key = """-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6TAbBgkqhkiG9w0BBQMwDgQIeaAsdasdlju9u21u381273872138ydJMt/f5woCAggABIIEyI2jIbFTPo/YBmwH
N1QqzFgnMK5LJ0pKY5O71Z8EZIBVrXtEdcFzswbwRZajMpMyxFBjhrd2Y4kQvt9K
aQfwyJFQxs8L2hC1GRYf5ba6aNZHkWendNFvOET02UQwgDNdGdWCfcaoMCap0ZQ2
7VOyqbwtuRkM2LNlrtbu282FbwXe4wKljSNUIXTU3UaCmjOq5KIwBoRKJYlwn0qt
IzlyJKVurHX4cl/1mw55Qs+hRL1CdK/iYF5yqclbK96zq16SKZsaZZhHMf1oub4U
v5QjJiiQZc+WONFpBi2TybQ1qXF/6+70lDofY3+moUVcPgFHVsKEVQJtYuRZoGoc
QlXyyCRTyCIQSO41jhkRcJtjCpVCfvCwlob0cM59Hlfll9DdjKyAtaUsuA+cDMqd
ZE8HSjBGXuSwWthHSfblZ84LReHufmiIYGO3+n0dyjdLKcakYJ5V4QwxN2y4CwE7
TxszcZo7QYDRaWlz7Lx2gtXlS6UNhs5Ylt7lm/omkdg57zf9DO9P8Vhn0thXs5Ql
7QIUzhhg0kgnagwmy682AkV+O6F1wWw8qV0uUxABvo9VNuUF8aYBFr9iJn1fUg8Y
kjCXKns/S9l6Zhz3rSgjFzNdWmm+1TigjyqaVgK9Jdte/v9IG46R3q/rQDMSqIFn
zO25DfwOr8GvSgxN4Ervy/IwqoC94ptFCLfTJdL2n7IRWX9B+ai2RVSnBEXti4BR
nigKUkVR7+ynwA8KN5sf6Zc0apHIuylXnu4xeO0rehxhh920v05IjAPm9YIOP33/
UkHZWtXe2MooV4jmSiWMfAAgL8J26vML2xeGjhFZNQPM1/C3TB+UBxvKbD47EO6k
FgoVmpFZGTXbF1Rq9hyUpABOSDhPyVuQxW+Tmyjm8O0Oc7KABUP09DKneiNFFtO9
/B2u7FZ2ArfUzHesEJLWU+CtYVPdpbvtmd054tMV53j3cga2SQmg/yYWOQ7LyMjo
7FR04aBTq+BXGgU/fZryyHUb1fULy7YTCiMyvi2m+JrZ+TE7DSvbDiJVcZ52x++J
UpmID04q3wSRrOjci3yXUBvSa1yqxH8F5j3tv/nVM8x2s8ZLEgOHARS0CHZ6KRGD
TP3KqsOPoKognk712zbqJPWhx9HdAm9+B/5qWtUEOoeFXlzyzj0suVICg9rPNJm1
zx+STX5zTQ9oPNj6MFgZPSzIoW5Wb6vEdu7ANoANuStMp3E2sQf7q0gY5UkfnYyj
beTf+1t3k9ybAVZiT6yZ7T5KGeh040zSN2vpVKEEWzkGrL4wGs+aMpvtBEnVJYLl
medTIY6Z2PM/GFd8Ky8I+uTazXfvZUdilYCyZeIoO6Hyomy7TrnCzc/vjkhWtQrW
+Pu5GjcGziUXNpzHNS+7uIOOa4f6VpGB8m5QsGUT7nPvVQqvta5fgJ8+W9J5Ifp5
JqlyEAC7b7PFP9Rz65Do9AsbUbDStKMHl5CR/+CeOnzgfgeHCA8EroQ6WxMHFXec
GSsZ7VWSSlgOyIEcNMhiM9PKAbx65TbUcvb+KWAI5aUwmdjrKFqOKDloX+2fn9y8
qoOMy1yIoV7uYL+c4zugjzpgy58iBAiR1IVectxQY9lx1+d9tfjwK2Ne96hdzlLO
/zJyaPr5pCU/IAr6Rg==
---23123--END ENCRYPTED PRIVATE KEY-----"""
payload = "symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559"
with pytest.raises(ValueError):
rsa_signature(
invalid_rsa_secret_key, payload, private_key_pass="password"
).decode("utf-8")


def test_ed25519_signature():
ed25519_secret_key = """-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIE4rJ0goma1nbu1d8T1dp//0pe40jnf8tghwRhsSY4Bk
-----END PRIVATE KEY-----"""
payload = "type=SPOT&timestamp=1685686334211"
expected_signature = "E4nWIl3yUJgJFL6LoWImsrEwNegMiN9SN1FWKw+P3xXkJ2T/MtSq3Cg7fVnOGFWxTBX6vrTJJNoZnVtAgs1CAQ=="
sign = ed25519_signature(ed25519_secret_key, payload, private_key_pass=None).decode(
"utf-8"
)
sign.should.equal(expected_signature)


def test_ed25519_signature_invalid_format():
invalid_ed25519_secret_key = """-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK231297dsah3167bjsfdaVwBCIEIE4rJ0goma1nbu1d8T1dp//0pe40jnf8tghwRhsSY4Bk
--123321---END PRIVATE KEY-----"""
payload = "type=SPOT&timestamp=1685686334211"
with pytest.raises(ValueError):
ed25519_signature(
invalid_ed25519_secret_key, payload, private_key_pass=None
).decode("utf-8")

0 comments on commit 630e594

Please sign in to comment.