收到“此请求的签名无效。”在 Binance websocket api 中,同时在 C++ 中使用 ed25519 密钥

问题描述 投票:0回答:1

您好,我正在尝试将订单发送到 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 + "&timestamp=" + 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++ encryption cryptography signature ed25519
1个回答
0
投票

C 代码本质上有两个错误:

  • 在Python参考代码中,签名是根据按字母顺序排列的参数生成的,其顺序如下:

    payload apiKey=...&price=...&quantity=...&side=...&symbol=...&timeInForce=...&timestamp=...&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 + 
    "&timestamp=" + 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=="}}
© www.soinside.com 2019 - 2024. All rights reserved.