Skip to content

Commit e507e94

Browse files
authored
feat(client): add RSA/ECDSA support (#683)
* feat(client): add RSA/ECDSA support * rm import * fix linting * fix msg * fix test
1 parent 396357b commit e507e94

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,20 @@ const client = Binance({
5959
This library works in both browsers and Node.js environments:
6060

6161

62+
### RSA/ECDSA support
6263

64+
This library supports RSA and ED25519 keys out of the box. The usage is straightforward, just provide `privateKey` instead of `apiSecret`.
6365

66+
```js
67+
68+
const apiKey = "ZymCbCxu1LiYIW8IcYSbXQOaAtHaeW3ioCU5qFf5QvUyTfP1runCaY8AwzCaoOaq"
69+
const privateKey = "-----BEGIN PRIVATE KEY-----\ndMC4CAfAwafYDK2cwaCIEIa+Ax8dMK50wcIcD0Zdf2jaCDoRdaoc7KaadRUh+aLdt\n-----END PRIVATE KEY-----"
70+
const client = Binance({
71+
privateKey,
72+
apiKey,
73+
})
74+
75+
```
6476

6577
### Proxy Support (Node.js only)
6678

@@ -261,6 +273,7 @@ Following examples will use the `await` form, which requires some configuration
261273
| ----------- | -------- | -------- | -------------------------------------------- |
262274
| apiKey | String | false | Required when making private calls |
263275
| apiSecret | String | false | Required when making private calls |
276+
| privateKey | String | false | Required when using RSA/Ed25519 calls |
264277
| getTime | Function | false | Time generator, defaults to () => Date.now() |
265278
| httpBase | String | false | Changes the default endpoint |
266279
| httpFutures | String | false | Changes the default endpoint |

src/http-client.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import zip from 'lodash.zipobject'
22
import JSONbig from 'json-bigint'
3-
import { createHmacSignature } from './signature'
3+
import { createHmacSignature, createAsymmetricSignature } from './signature'
44

55
// Robust environment detection for Node.js vs Browser
66
const isNode = (() => {
@@ -230,10 +230,21 @@ const keyCall =
230230
* @returns {object} The api response
231231
*/
232232
const privateCall =
233-
({ apiKey, apiSecret, proxy, endpoints, getTime = defaultGetTime, pubCall, testnet }) =>
233+
({
234+
apiKey,
235+
apiSecret,
236+
privateKey,
237+
proxy,
238+
endpoints,
239+
getTime = defaultGetTime,
240+
pubCall,
241+
testnet,
242+
}) =>
234243
(path, data = {}, method = 'GET', noData, noExtra) => {
235-
if (!apiKey || !apiSecret) {
236-
throw new Error('You need to pass an API key and secret to make authenticated calls.')
244+
if (!apiKey || (!apiSecret && !privateKey)) {
245+
throw new Error(
246+
'You need to pass an API key and secret/privateKey to make authenticated calls.',
247+
)
237248
}
238249

239250
return (
@@ -250,10 +261,22 @@ const privateCall =
250261
const dataToSign = queryString.substr(1)
251262

252263
// Create signature (async in browser, sync in Node.js)
253-
return createHmacSignature(dataToSign, apiSecret).then(signature => ({
254-
timestamp,
255-
signature,
256-
}))
264+
if (apiSecret) {
265+
return createHmacSignature(dataToSign, apiSecret).then(signature => ({
266+
timestamp,
267+
signature,
268+
}))
269+
} else if (privateKey) {
270+
const sig = createAsymmetricSignature(dataToSign, privateKey)
271+
// .then(signature => ({
272+
// timestamp,
273+
// signature,
274+
// }))
275+
return {
276+
timestamp,
277+
signature: sig,
278+
}
279+
}
257280
})
258281
.then(({ timestamp, signature }) => {
259282
const newData = noExtra ? data : { ...data, timestamp, signature }

src/signature.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,21 @@ export const createHmacSignature = async (data, secret) => {
5959
.join('')
6060
/* eslint-enable no-undef */
6161
}
62+
63+
export const createAsymmetricSignature = (data, privateKey) => {
64+
// Handles RSA and ECDASA (Ed25519) private keys
65+
const privateKeyObj = { key: privateKey }
66+
const keyObject = nodeCrypto.createPrivateKey(privateKeyObj)
67+
68+
let signature = ''
69+
70+
if (privateKey.length > 120) {
71+
// RSA key
72+
signature = nodeCrypto.sign('RSA-SHA256', Buffer.from(data), keyObject).toString('base64')
73+
// if (encode) signature = encodeURIComponent(signature);
74+
} else {
75+
// Ed25519 key
76+
signature = nodeCrypto.sign(null, Buffer.from(data), keyObject).toString('base64')
77+
}
78+
return signature
79+
}

test/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,21 @@ test('[REST] Signed call without creds', async t => {
120120
try {
121121
await client.accountInfo()
122122
} catch (e) {
123-
t.is(e.message, 'You need to pass an API key and secret to make authenticated calls.')
123+
t.is(
124+
e.message,
125+
'You need to pass an API key and secret/privateKey to make authenticated calls.',
126+
)
124127
}
125128
})
126129

127130
test('[REST] Signed call without creds - attempt getting tradeFee', async t => {
128131
try {
129132
await client.tradeFee()
130133
} catch (e) {
131-
t.is(e.message, 'You need to pass an API key and secret to make authenticated calls.')
134+
t.is(
135+
e.message,
136+
'You need to pass an API key and secret/privateKey to make authenticated calls.',
137+
)
132138
}
133139
})
134140

0 commit comments

Comments
 (0)