diff --git a/CHANGELOG.md b/CHANGELOG.md index 8552335..a05de55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 3.0.0-rc.2 - 2023-05-03 + +### Added +- Support Ed25519 key signature + ## v3.0.0-rc1 - 2023-04-20 ### Added diff --git a/README.md b/README.md index c023003..e99e152 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ client.newOrder('BNBUSDT', 'BUY', 'LIMIT', { Please find `examples` folder to check for more endpoints. -## RSA Key based Authentication +## Key Pair Based Authentication ```javascript -const { Spot } = require('@binance/connector') +const { Spot, PrivateKeyAlgo } = require('@binance/connector') const apiKey = '' const apiSecret = '' // has no effect when RSA private key is provided @@ -65,10 +65,13 @@ const apiSecret = '' // has no effect when RSA private key is provided // load private key const privateKey = fs.readFileSync('/Users/john/ssl/private_key_encrypted.pem') const privateKeyPassphrase = 'password' +const privateKeyAlgo = PrivateKeyAlgo.RSA // for RSA key +const privateKeyAlgo = PrivateKeyAlgo.ED25519 // for Ed25519 key const client = new Spot(apiKey, apiSecret, { privateKey, privateKeyPassphrase // only used for encrypted key + privateKeyAlgo }) // Get account information diff --git a/examples/spot/trade/account.js b/examples/spot/trade/account.js index a3e0917..716bbfc 100644 --- a/examples/spot/trade/account.js +++ b/examples/spot/trade/account.js @@ -1,42 +1,17 @@ 'use strict' - -const fs = require('fs') const Spot = require('../../../src/spot') -let apiKey = '' -let apiSecret = '' -let client +const apiKey = '' +const apiSecret = '' -// baseURL is used to set which market you wish to send the request to. -// Please make sure the api key/secret is consistent with the baseURL, It won't work if you send request to production site with testnet api key. +// Please make sure the api key/secret is consistent with the baseURL. It won't work if you send request to production site with testnet api key. // https://api.binance.com is for production. // https://testnet.binance.vision is the spot testnet base url // timeout is another optional value in milliseconds, that how long the request is allowed to executed. -client = new Spot(apiKey, apiSecret, { baseURL: 'https://testnet.binance.vision', timeout: 1000 }) +const client = new Spot(apiKey, apiSecret, { baseURL: 'https://testnet.binance.vision', timeout: 1000 }) client.account() .then(response => client.logger.log(response.data)) .catch(error => client.logger.error(error.message)) - -// Below example shows how to sign the request with RSA key. You will have to a few steps: -// 1. Have your RSA keys ready. -// 2. Login to Binance.com to register your public key. -// 3. Save the API Key that is generated from UI, then you are ready to go. -apiKey = 'the_api_key' -apiSecret = '' // Not required for RSA signature. - -// load private key -const privateKey = fs.readFileSync('/Users/liangshi/ssl/private_key_encrypted.pem') - -client = new Spot(apiKey, apiSecret, { - baseURL: 'https://testnet.binance.vision', // This URL is for testnet; Remove it for production. - privateKey, - privateKeyPassphrase: 'private_key_password', // only used for encrypted key - timeout: 1000 -}) - -client.account() - .then(response => client.logger.log(response.data)) - .catch(error => client.logger.error(error)) diff --git a/examples/spot/trade/account_ed25519.js b/examples/spot/trade/account_ed25519.js new file mode 100644 index 0000000..4c3cfe6 --- /dev/null +++ b/examples/spot/trade/account_ed25519.js @@ -0,0 +1,29 @@ +'use strict' + +const fs = require('fs') +const Spot = require('../../../src/spot') +const PrivateKeyAlgo = require('../../../src/helpers/privateKeyAlgo') + +// Please make sure the api key/secret is consistent with the baseURL. It won't work if you send request to production site with testnet api key. +// https://api.binance.com is for production. +// https://testnet.binance.vision is the spot testnet base url + +// Below example shows how to sign the request with key. You will have to a few steps: +// 1. Have your Ed25519 key ready. +// 2. Login to Binance.com to register your public key. +// 3. Save the API Key that is generated from UI, then you are ready to go. +const apiKey = 'the api key' + +// load private key +const privateKey = fs.readFileSync('/Users/john/ed25519.pem') + +const client = new Spot(apiKey, null, { + baseURL: 'https://testnet.binance.vision', // This URL is for testnet; Remove it for production. + privateKey, + privateKeyAlgo: PrivateKeyAlgo.ED25519, // specific the key algo + timeout: 1000 +}) + +client.account() + .then(response => client.logger.log(response.data)) + .catch(error => client.logger.error(error)) diff --git a/examples/spot/trade/account_rsa.js b/examples/spot/trade/account_rsa.js new file mode 100644 index 0000000..02b9411 --- /dev/null +++ b/examples/spot/trade/account_rsa.js @@ -0,0 +1,29 @@ +'use strict' + +const fs = require('fs') +const Spot = require('../../../src/spot') + +// Please make sure the api key/secret is consistent with the baseURL. It won't work if you send request to production site with testnet api key. +// https://api.binance.com is for production. +// https://testnet.binance.vision is the spot testnet base url + +// Below example shows how to sign the request with RSA key. You will have to a few steps: +// 1. Have your RSA keys ready. +// 2. Login to Binance.com to register your public key. +// 3. Save the API Key that is generated from UI, then you are ready to go. +const apiKey = 'the api key' +const apiSecret = '' // Not required for RSA signature. + +// load private key +const privateKey = fs.readFileSync('/Users/john/private_key_encrypted.pem') + +const client = new Spot(apiKey, apiSecret, { + baseURL: 'https://testnet.binance.vision', // This URL is for testnet; Remove it for production. + privateKey, + privateKeyPassphrase: 'password', // only used for encrypted key + timeout: 1000 +}) + +client.account() + .then(response => client.logger.log(response.data)) + .catch(error => client.logger.error(error)) diff --git a/package-lock.json b/package-lock.json index 1d4db7e..dd44347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@binance/connector", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@binance/connector", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "license": "MIT", "dependencies": { "axios": "^1.3", diff --git a/package.json b/package.json index 1a8fb68..5f70252 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@binance/connector", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "This is a lightweight library that works as a connector to the Binance public API.", "main": "src/index.js", "scripts": { diff --git a/src/APIBase.js b/src/APIBase.js index c56899e..929d8bd 100644 --- a/src/APIBase.js +++ b/src/APIBase.js @@ -2,10 +2,12 @@ const crypto = require('crypto') const { removeEmptyValue, buildQueryString, createRequest, defaultLogger } = require('./helpers/utils') +const ConnectorClientError = require('./error/connectorClientError') +const PrivateKeyAlgo = require('./helpers/privateKeyAlgo') class APIBase { constructor (options) { - const { apiKey, apiSecret, baseURL, logger, timeout, proxy, httpsAgent, privateKey, privateKeyPassphrase, wsURL } = options + const { apiKey, apiSecret, baseURL, logger, timeout, proxy, httpsAgent, privateKey, privateKeyPassphrase, privateKeyAlgo, wsURL } = options this.apiKey = apiKey this.apiSecret = apiSecret @@ -17,6 +19,7 @@ class APIBase { this.logger = logger || defaultLogger this.privateKey = privateKey || '' this.privateKeyPassphrase = privateKeyPassphrase || '' + this.privateKeyAlgo = privateKeyAlgo || PrivateKeyAlgo.RSA this.wsURL = wsURL } @@ -49,13 +52,20 @@ class APIBase { .update(queryString) .digest('hex') } else { - signature = crypto - .createSign('RSA-SHA256') - .update(queryString) - .sign({ + if (this.privateKeyAlgo === PrivateKeyAlgo.RSA) { + signature = crypto.sign('RSA-SHA256', Buffer.from(queryString), { key: this.privateKey, passphrase: this.privateKeyPassphrase - }, 'base64') + }).toString('base64') + } else if (this.privateKeyAlgo === PrivateKeyAlgo.ED25519) { + signature = crypto.sign(null, Buffer.from(queryString), { + key: this.privateKey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST + }).toString('base64') + } else { + throw new ConnectorClientError("privateKeyAlgo must be either 'RSA' or 'ED25519'") + } signature = encodeURIComponent(signature) } diff --git a/src/error/connectorClientError.js b/src/error/connectorClientError.js new file mode 100644 index 0000000..1682da2 --- /dev/null +++ b/src/error/connectorClientError.js @@ -0,0 +1,12 @@ +'use strict' + +const Error = require('../error/error') + +class ConnectorClientError extends Error { + constructor (errorMessage) { + super(errorMessage) + this.name = 'ConnectorClientError' + } +} + +module.exports = ConnectorClientError diff --git a/src/helpers/privateKeyAlgo.js b/src/helpers/privateKeyAlgo.js new file mode 100644 index 0000000..0fbf845 --- /dev/null +++ b/src/helpers/privateKeyAlgo.js @@ -0,0 +1,6 @@ +const PrivateKeyAlgo = { + RSA: 'RSA', + ED25519: 'Ed25519' +} + +module.exports = PrivateKeyAlgo diff --git a/src/index.js b/src/index.js index 32ca39c..81ba3b4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ 'use strict' +module.exports.PrivateKeyAlgo = require('./helpers/privateKeyAlgo') module.exports.Spot = require('./spot') module.exports.WebsocketStream = require('./websocketStream') module.exports.WebsocketAPI = require('./websocketAPI')