如何使用 Vanilla Javascript 在 Binance API 生成有效签名:{"code":-2014,"msg":"API-key format invalid."}

How to produce valid signature at Binance API using Vanilla Javascript: {"code":-2014,"msg":"API-key format invalid."}

我花了几个小时试图从端点获得正确的响应。为此,我需要发送一个由 SHA-256 函数生成的签名字符串,附加到我发送到服务器的查询字符串。

我尝试了不同的方法来获取该签名,如文档中所述:https://developers.binance.com/docs/binance-api/spot/index/#signed-trade-user_data-and-margin-endpoint-security但似乎没有任何效果。

我尝试了几种使用不同的库和函数生成有效签名的方法,但它们都不起作用(我什至尝试使用 Web Crypto API 文档)

我在拨打电话时遇到此错误:

{"code":-2014,"msg":"API-key format invalid."}

这是电话:

https://testnet.binance.vision/api/v3/account?timestamp=my_timestamp&signature=my_signature

我猜这是 Fetch 的问题,但在我的应用程序中的其他自定义函数中,它不会导致任何问题。

这是我的代码:

export async function getAccountInfo() {
    const apiSecret = pub.TESTNET_SECRETKEY; // Your secret key

    const timestamp = await serverTimestamp()
        .then(timestamp => {
            return timestamp;
        });

    let signature = sha256(apiSecret, timestamp);

    const testnet = 'https://testnet.binance.vision/api';

    // {{url}}/api/v3/account?timestamp={{timestamp}}&signature={{signature}}

    const fullUrl = testnet + '/v3/account?timestamp=' + timestamp + '&signature=' + signature;

    retrieveInformation(fullUrl);
}

我只是将不正确的时间戳字符串发送到散列函数,在这一行中:

let signature = sha256(apiSecret, timestamp); // Keep reading and you'll understand why.

香草 JS 解决方案

虽然我使用了依赖项,但我认为这仍然是一个完整有效的解决方案。

https://www.npmjs.com/package/jhash.js功能简单易用

问题

问题出在我发送到哈希函数的查询字符串中。

正如 Binance API 文档解释的那样,虽然含糊不清:

  • Endpoints use HMAC SHA256 signatures. The HMAC SHA256 signature is a keyed HMAC SHA256 operation. Use your secretKey as the key and totalParams as the value for the HMAC operation.
  • totalParams is defined as the query string concatenated with the request body.

最后一点让我很疑惑

现在,解决方案是将正确的字符串 (queryString) 发送到 sha256 函数中。 API 需要的是:

https://testnet.binance.vision/api/v3/account?timestamp=my_timestamp&signature=my_signature

timestamp= 子字符串是我的问题的解决方案。我不得不将那一小段代码发送到 hex_hmac_sha256 函数中,这是 Binance API.

所需的格式

完整的解决方案。

async function serverTimestamp() {
    const url = 'https://testnet.binance.vision/api/v3/time';
    const timeServer = await getJson(url);
    return timeServer.serverTime;
}

不是本地时间,必须在签名里面发送时间服务器。这是问题的解决方案。

export async function getAccountInfo() {
    const apiSecret = pub.TESTNET_SECRETKEY; // Your secret key

    const timestamp = await serverTimestamp()
        .then(timestamp => {
            return timestamp;
        });

    const queried_timestamp = 'timestamp=' + timestamp;

    // https://www.npmjs.com/package/jhash.js
    let signature = JHash.hex_hmac_sha256(apiSecret, queried_timestamp);
    // let signature = await sha256(apiSecret, queried_timestamp); // This one is not library dependant.

    const testnet = 'https://testnet.binance.vision/api';

    // {{url}}/api/v3/account?timestamp={{timestamp}}&signature={{signature}}
    const fullUrl = testnet + '/v3/account?timestamp=' + timestamp + '&signature=' + signature; // + '&recvWindow=60000';
    retrieveInformation(fullUrl);
}

请注意以下代码行,我将 URL 中包含的字符串作为查询字符串发送。

let signature = JHash.hex_hmac_sha256(apiSecret, queried_timestamp);
// This is the same line than the one I wrote above,
// but using another version of the function.

这是引导我走向正确方向的例子:https://developers.binance.com/docs/binance-api/spot/index/#example-1-as-a-request-body

正如您在官方文档示例中所见,他们回显了用于制作签名的完整查询字符串。

现在,您可能需要更好地理解问题的其他函数:

async function retrieveInformation(url = null) {
    const apiKey = pub.TESTNET_APIKEY; // Your ApiKey
    let httpHeaders = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'X-MBX-APIKEY': apiKey
    }

    let myHeaders = new Headers(httpHeaders);
    var requestOptions = {
        headers: myHeaders
    };
    console.log(url);
    console.log(requestOptions);
    const data = await getJson(url, requestOptions);
    console.log(data);
    return data;
}

data显示为如下JSON对象:

{
  "makerCommission": 15,
  "takerCommission": 15,
  "buyerCommission": 0,
  "sellerCommission": 0,
  "canTrade": true,
  "canWithdraw": true,
  "canDeposit": true,
  "updateTime": 123456789,
  "accountType": "SPOT",
  "balances": [
    {
      "asset": "BTC",
      "free": "4723846.89208129",
      "locked": "0.00000000"
    },
    {
      "asset": "LTC",
      "free": "4763368.68006011",
      "locked": "0.00000000"
    }
  ],
  "permissions": [
    "SPOT"
  ]
}

您可以在 API Binance 文档中看到相同的信息:https://developers.binance.com/docs/binance-api/spot/index/#account-information-user_data

我使用的其他功能(这只是对这个答案的奖励,你可能会发现它们很有用)

这是我使用的 fetch 函数:

async function getJson(url = null, requestOptions = null) {
    return fetch(url, requestOptions)
        .then((response) => {
            if (!response.ok) {
                throw Error(response.statusText);
            } else {
                const jsoned = response.json();
                return jsoned;
                // NOTE:
                //  response.json().then(data => {
                //      → do something with your data
                //  });
                //
            }
        })
        .catch(function (error) {
            console.log(error);
        });
}

这是我使用 SubtleCrypto Object (Crypto Web API) 上的一些 Mozilla 文档自行创建的 sha256 函数。它 returns 与来自依赖项的结果相同。

async function sha256(key, message) {
    
    // Step 1
    // encode as (utf-8) Uint8Array
    const msgUint8_key = new TextEncoder().encode(key);
    // encode as (utf-8) Uint8Array
    const msgUint8_message = new TextEncoder().encode(message);
    
    // Step 2
    const importedKey = await crypto.subtle.importKey('raw', msgUint8_key, {
        name: 'HMAC',
        hash: 'SHA-256'
    }, true, ['sign']);
    
    // Step 3
    const signedKey = await crypto.subtle.sign('HMAC', importedKey, msgUint8_message);
    // convert buffer to byte array
    const hashArray = Array.from(new Uint8Array(signedKey));
    // convert bytes to hex string    
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

对于那些正在为最后一个功能寻找更 100% 香草解决方案的人:

  • Are there any SHA-256 javascript implementations that are generally considered trustworthy?
  • JavaScript SHA-256 demo