diff --git a/README.md b/README.md index a7ff3ad4..bffddd79 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,20 @@ const client = Binance({ This library works in both browsers and Node.js environments: +### RSA/ECDSA support +This library supports RSA and ED25519 keys out of the box. The usage is straightforward, just provide `privateKey` instead of `apiSecret`. +```js + +const apiKey = "ZymCbCxu1LiYIW8IcYSbXQOaAtHaeW3ioCU5qFf5QvUyTfP1runCaY8AwzCaoOaq" +const privateKey = "-----BEGIN PRIVATE KEY-----\ndMC4CAfAwafYDK2cwaCIEIa+Ax8dMK50wcIcD0Zdf2jaCDoRdaoc7KaadRUh+aLdt\n-----END PRIVATE KEY-----" +const client = Binance({ + privateKey, + apiKey, +}) + +``` ### Proxy Support (Node.js only) @@ -261,6 +273,7 @@ Following examples will use the `await` form, which requires some configuration | ----------- | -------- | -------- | -------------------------------------------- | | apiKey | String | false | Required when making private calls | | apiSecret | String | false | Required when making private calls | +| privateKey | String | false | Required when using RSA/Ed25519 calls | | getTime | Function | false | Time generator, defaults to () => Date.now() | | httpBase | String | false | Changes the default endpoint | | httpFutures | String | false | Changes the default endpoint | diff --git a/src/http-client.js b/src/http-client.js index 3e26237d..9602caa5 100644 --- a/src/http-client.js +++ b/src/http-client.js @@ -1,6 +1,6 @@ import zip from 'lodash.zipobject' import JSONbig from 'json-bigint' -import { createHmacSignature } from './signature' +import { createHmacSignature, createAsymmetricSignature } from './signature' // Robust environment detection for Node.js vs Browser const isNode = (() => { @@ -230,10 +230,21 @@ const keyCall = * @returns {object} The api response */ const privateCall = - ({ apiKey, apiSecret, proxy, endpoints, getTime = defaultGetTime, pubCall, testnet }) => + ({ + apiKey, + apiSecret, + privateKey, + proxy, + endpoints, + getTime = defaultGetTime, + pubCall, + testnet, + }) => (path, data = {}, method = 'GET', noData, noExtra) => { - if (!apiKey || !apiSecret) { - throw new Error('You need to pass an API key and secret to make authenticated calls.') + if (!apiKey || (!apiSecret && !privateKey)) { + throw new Error( + 'You need to pass an API key and secret/privateKey to make authenticated calls.', + ) } return ( @@ -250,10 +261,22 @@ const privateCall = const dataToSign = queryString.substr(1) // Create signature (async in browser, sync in Node.js) - return createHmacSignature(dataToSign, apiSecret).then(signature => ({ - timestamp, - signature, - })) + if (apiSecret) { + return createHmacSignature(dataToSign, apiSecret).then(signature => ({ + timestamp, + signature, + })) + } else if (privateKey) { + const sig = createAsymmetricSignature(dataToSign, privateKey) + // .then(signature => ({ + // timestamp, + // signature, + // })) + return { + timestamp, + signature: sig, + } + } }) .then(({ timestamp, signature }) => { const newData = noExtra ? data : { ...data, timestamp, signature } diff --git a/src/signature.js b/src/signature.js index 76a34e37..2e43ee53 100644 --- a/src/signature.js +++ b/src/signature.js @@ -59,3 +59,21 @@ export const createHmacSignature = async (data, secret) => { .join('') /* eslint-enable no-undef */ } + +export const createAsymmetricSignature = (data, privateKey) => { + // Handles RSA and ECDASA (Ed25519) private keys + const privateKeyObj = { key: privateKey } + const keyObject = nodeCrypto.createPrivateKey(privateKeyObj) + + let signature = '' + + if (privateKey.length > 120) { + // RSA key + signature = nodeCrypto.sign('RSA-SHA256', Buffer.from(data), keyObject).toString('base64') + // if (encode) signature = encodeURIComponent(signature); + } else { + // Ed25519 key + signature = nodeCrypto.sign(null, Buffer.from(data), keyObject).toString('base64') + } + return signature +} diff --git a/test/index.js b/test/index.js index 3ab35f5e..6c71fe07 100644 --- a/test/index.js +++ b/test/index.js @@ -120,7 +120,10 @@ test('[REST] Signed call without creds', async t => { try { await client.accountInfo() } catch (e) { - t.is(e.message, 'You need to pass an API key and secret to make authenticated calls.') + t.is( + e.message, + 'You need to pass an API key and secret/privateKey to make authenticated calls.', + ) } }) @@ -128,7 +131,10 @@ test('[REST] Signed call without creds - attempt getting tradeFee', async t => { try { await client.tradeFee() } catch (e) { - t.is(e.message, 'You need to pass an API key and secret to make authenticated calls.') + t.is( + e.message, + 'You need to pass an API key and secret/privateKey to make authenticated calls.', + ) } })