您好,我正在尝试将订单发送到 Binance websocket api
wss://ws-api.binance.com:443/ws-api/v3
。我正在使用 ed25519 密钥来签署我的订单 json 数据。当我发送请求时,我收到:
{"id":"my_new_order","status":400,"error":{"code":-1022,"msg":"Signature for this request is not valid."},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000,"count":3}]}
我什至尝试过按字母顺序对 json 参数进行排序,但仍然不起作用。 但我尝试使用他们的文档中给出的示例python代码并且它有效。
我正在使用钠库来生成签名。
std::string privateKeyData = "MY_PRIVATE_KEY";
if (privateKeyData.empty()) {
return 1;
}
// Set up the request parameters
std::string symbol = "BTCUSDT";
std::string side = "BUY";
std::string type = "LIMIT";
std::string timeInForce = "IOC";
std::string quantity = "0.001";
std::string price = "50000";
// Timestamp the request
std::string timestampStr = timestamp();
// Construct the message to sign
std::string message = "apiKey=" + API_KEY + "&symbol=" + symbol +
"&side=" + side + "&type=" + type + "&timeInForce=" + timeInForce +
"&quantity=" + quantity + "&price=" + price + "×tamp=" + timestampStr;
// Load the private key for signing
unsigned char privateKey[crypto_sign_ed25519_SECRETKEYBYTES];
if (crypto_sign_ed25519_sk_to_pk(privateKey, reinterpret_cast<const unsigned char*>(privateKeyData.data())) != 0) {
std::cerr << "Error: Failed to load private key." << std::endl;
return 1;
}
// Sign the message
unsigned char signature[crypto_sign_BYTES];
unsigned long long signatureLength;
if (crypto_sign_detached(signature, &signatureLength,
reinterpret_cast<const unsigned char*>(message.data()), message.size(),
privateKey) != 0) {
std::cerr << "Error: Failed to sign message." << std::endl;
return 1;
}
// Encode the signature as base64
char encodedSignature[crypto_sign_BYTES * 2 + 1];
if (sodium_bin2base64(encodedSignature, sizeof(encodedSignature), signature, signatureLength, sodium_base64_VARIANT_ORIGINAL) == nullptr) {
std::cerr << "Error: Failed to encode signature." << std::endl;
return 1;
}
// Add the signature to the request parameters
std::cout << "Signature: " << encodedSignature << std::endl;
std::string base64Signature(encodedSignature);
// Construct the JSON string manually
std::string jsonRequest = "{"
"\"id\": \"my_new_order\","
"\"method\": \"order.place\","
"\"params\": {"
"\"apiKey\": \"" + API_KEY + "\","
"\"price\": \"" + price + "\","
"\"quantity\": \"" + quantity + "\","
"\"side\": \"" + side + "\","
"\"symbol\": \"" + symbol + "\","
"\"timeInForce\": \"" + timeInForce + "\","
"\"timestamp\": " + timestampStr + ","
"\"type\": \"" + type + "\","
"\"signature\": \"" + base64Signature + "\""
"}"
"}";
// Print the JSON string
std::cout << "JSON Request:\n" << jsonRequest << std::endl;
形成的 JSON 请求如下所示:
{"id": "my_new_order","method": "order.place","params": {"apiKey": "MY_API_KEY","price": "50000","quantity": "0.001","side": "BUY","symbol": "BTCUSDT","timeInForce": "IOC","timestamp": 1713859232000,"type": "LIMIT","signature": "GENERATED_SIGNATURE"}}
对此我得到了上面提到的回复。
为了比较,Python 请求负载如下所示:
{'id': 'my_new_order', 'method': 'order.place', 'params': {'apiKey': 'MY_API_KEY','price': '50000', 'quantity': '0.001', 'symbol': 'BTCUSDT', 'side': 'BUY', 'timeInForce': 'IOC', 'type': 'LIMIT', 'timestamp': 1713859985702, 'signature': 'GENERATED_SIGNATURE'}}
除了使用钠之外的任何解决方案也是受欢迎的。
C 代码本质上有两个错误:
在Python参考代码中,签名是根据按字母顺序排列的参数生成的,其顺序如下:
payload apiKey=...&price=...&quantity=...&side=...&symbol=...&timeInForce=...×tamp=...&type=...
在 C 代码中,应用了不同的顺序。必须更改它,使其与 Python 代码中的顺序相对应。
Python 和 C 代码使用不同的键约定。在 C 代码中,使用典型的 Libsodium 约定,其中密钥(64 字节)是种子(32 字节)和公钥(32 字节)s 的串联。 这里,而在Python代码中,种子被称为私钥并且单独使用,s。 这里。原则上,种子就足够了,因为公钥可以从种子中派生,但 Libsodium 约定的性能更高,因为在签名过程中可以省略这种派生。
正如 Botje 在他们的评论中已经指出的那样,在 C 代码中仅使用种子。这是错误的,并且在签名期间表现得好像应用了不正确/不一致的公钥。不正确的公钥反过来会反映在正确的 r 和不正确的 s 中(请记住,Ed25519 签名由两部分组成,r(32 字节)和 s(32 字节),它们是连接在一起的。而在 r 中,只有涉及私钥,s 中还涉及公钥)。
然而,由于第一个错误,这种特征行为是不可观察的,这会导致通常不同的签名(即也是不正确的 r)。
这里的修复是签名时必须使用密钥而不是种子。 Libsodium 提供了
crypto_sign_seed_keypair()
函数,用于从种子中导出秘密密钥和公钥。
C 代码(为了简单起见,没有错误处理):
// "seed" is called "privateKey" in the Python wrapper/port
unsigned char seed[32] =
{
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f
};
// Derive the secret key (sk)
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
crypto_sign_seed_keypair(pk, sk, seed);
// Set up the request parameters
std::string symbol = "BTCUSDT";
std::string side = "BUY";
std::string type = "LIMIT";
std::string timeInForce = "IOC";
std::string quantity = "0.001";
std::string price = "50000";
// Timestamp the request
std::string timestampStr = "1713978544129"; // timestamp(); // use a fixed timestamp for testing!
std::string API_KEY = "put your own API Key here";
// Fix 1: Use an alphabetical order (see sorted() in Python)
std::string message = "apiKey=" + API_KEY + "&price=" + price + "&quantity=" + quantity +
"&side=" + side + "&symbol=" + symbol + "&timeInForce=" + timeInForce +
"×tamp=" + timestampStr + "&type=" + type;
// Sign the alphabetical ordered data
unsigned char signature[crypto_sign_BYTES];
unsigned long long signatureLength;
char encodedSignature[sodium_base64_ENCODED_LEN(crypto_sign_BYTES, sodium_base64_VARIANT_ORIGINAL)];
crypto_sign_detached(signature, &signatureLength, (unsigned char*)message.data(), message.size(), sk); // Fix 2: Apply the secret key
sodium_bin2base64(encodedSignature, sizeof(encodedSignature), signature, signatureLength, sodium_base64_VARIANT_ORIGINAL);
// Construct the JSON string manually (using Python formatting and ordering for easier comparison)
std::string jsonRequest = "{"
"\"id\": \"my_new_order\", "
"\"method\": \"order.place\", "
"\"params\": {"
"\"apiKey\": \"" + API_KEY + "\", "
"\"symbol\": \"" + symbol + "\", "
"\"side\": \"" + side + "\", "
"\"type\": \"" + type + "\", "
"\"timeInForce\": \"" + timeInForce + "\", "
"\"quantity\": \"" + quantity + "\", "
"\"price\": \"" + price + "\", "
"\"timestamp\": " + timestampStr + ", "
"\"signature\": \"" + encodedSignature + "\""
"}"
"}";
// Print the JSON string
std::cout << "JSON Request:\n" << jsonRequest << std::endl; // JSON Request: {"id": "my_new_order", "method" : "order.place", "params" : {"apiKey": "put your own API Key here", "symbol" : "BTCUSDT", "side" : "BUY", "type" : "LIMIT", "timeInForce" : "IOC", "quantity" : "0.001", "price" : "50000", "timestamp" : 1713978544129, "signature" : "7Rh8bRx0/VT8riNSC2POpR1+8p0gjXhL7mkOL/nfmhFQ6QbGCXRE6PZQ2cq+c6ZNPBXPc52jOIJ2bId6YbyIBw=="}}
Python参考代码(来自这里):
import base64
import json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# Set up authentication
API_KEY='put your own API Key here'
# Load the private key.
# In this example the key is expected to be stored without encryption,
# but we recommend using a strong password for improved security.
private_key = Ed25519PrivateKey.from_private_bytes(bytes.fromhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"));
# alternatively, the key can be imported as PEM:
#data = b"""-----BEGIN PRIVATE KEY-----
#MC4CAQAwBQYDK2VwBCIEIICBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6f
#-----END PRIVATE KEY-----"""
#private_key = load_pem_private_key(data, password=None)
# Set up the request parameters
params = {
'apiKey': API_KEY,
'symbol': 'BTCUSDT',
'side': 'BUY',
'type': 'LIMIT',
'timeInForce': 'IOC',
'quantity': '0.001',
'price': '50000'
}
# Timestamp the request
#timestamp = int(time.time() * 1000) # UNIX timestamp in milliseconds
timestamp = 1713978544129 # use a fixed timestamp for testing!
params['timestamp'] = timestamp
# Sign the request
payload = '&'.join([f'{param}={value}' for param, value in sorted(params.items())])
signature = base64.b64encode(private_key.sign(payload.encode('ASCII')))
params['signature'] = signature.decode('ASCII')
# Send the request
request = {
'id': 'my_new_order',
'method': 'order.place',
'params': params
}
print(json.dumps(request)) # {"id": "my_new_order", "method": "order.place", "params": {"apiKey": "put your own API Key here", "symbol": "BTCUSDT", "side": "BUY", "type": "LIMIT", "timeInForce": "IOC", "quantity": "0.001", "price": "50000", "timestamp": 1713978544129, "signature": "7Rh8bRx0/VT8riNSC2POpR1+8p0gjXhL7mkOL/nfmhFQ6QbGCXRE6PZQ2cq+c6ZNPBXPc52jOIJ2bId6YbyIBw=="}}