Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |
Expand Down
39 changes: 31 additions & 8 deletions src/http-client.js
Original file line number Diff line number Diff line change
@@ -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 = (() => {
Expand Down Expand Up @@ -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 (
Expand All @@ -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 }
Expand Down
18 changes: 18 additions & 0 deletions src/signature.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 8 additions & 2 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,21 @@ 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.',
)
}
})

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.',
)
}
})

Expand Down