我只是在这一行中向哈希函数发送了错误的时间戳字符串:
let signature = sha256(apiSecret, timestamp); // Keep reading and you'll understand why.
原版JS解决方案
虽然我使用了依赖项,但我认为这仍然是一个完整的有效解决方案。
https://www.npmjs.com/package/jhash.js 功能比较简单直接上手。
问题
问题出在我发送到哈希函数的查询字符串中。
正如 Binance API 文档解释的那样,虽然晦涩难懂:
- 端点使用
HMAC SHA256 签名。 HMAC SHA256 签名是
键入HMAC SHA256 操作。使用您的 secretKey 作为键,totalParams 作为 HMAC 操作的值。
-
totalParams 定义为与请求正文连接的查询字符串。
最后一点让我很困惑。
现在,解决方案是将正确的字符串 (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 中包含的字符串作为 queryString 发送。
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
正如您在官方文档示例中看到的那样,他们回应了用于制作签名的完整 queryString(s)。
现在,您可能需要更好地理解问题的其他功能:
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 函数。它返回的结果与依赖项中的结果相同。
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 % Vanilla 解决方案来解决最后一个功能的人: