@binance-chain/bsc-ledger-bridge-keyring
Advanced tools
Comparing version 0.6.2 to 0.6.3
290
index.js
const { EventEmitter } = require('events') | ||
const HDKey = require('hdkey') | ||
const ethUtil = require('ethereumjs-util') | ||
const sigUtil = require('eth-sig-util') | ||
const { TransactionFactory } = require('@ethereumjs/tx') | ||
// const sigUtil = require('eth-sig-util') | ||
// const { TransactionFactory } = require('@ethereumjs/tx') | ||
@@ -56,6 +56,2 @@ const pathBase = 'm' | ||
this.accountDetails = opts.accountDetails || {} | ||
if (!opts.accountDetails) { | ||
this._migrateAccountDetails(opts) | ||
} | ||
this.implementFullBIP44 = opts.implementFullBIP44 || false | ||
@@ -70,29 +66,2 @@ | ||
_migrateAccountDetails (opts) { | ||
if (this._isLedgerLiveHdPath() && opts.accountIndexes) { | ||
for (const account of Object.keys(opts.accountIndexes)) { | ||
this.accountDetails[account] = { | ||
bip44: true, | ||
hdPath: this._getPathForIndex(opts.accountIndexes[account]), | ||
} | ||
} | ||
} | ||
// try to migrate non-LedgerLive accounts too | ||
if (!this._isLedgerLiveHdPath()) { | ||
this.accounts | ||
.filter((account) => !Object.keys(this.accountDetails).includes(ethUtil.toChecksumAddress(account))) | ||
.forEach((account) => { | ||
try { | ||
this.accountDetails[ethUtil.toChecksumAddress(account)] = { | ||
bip44: false, | ||
hdPath: this._pathFromAddress(account), | ||
} | ||
} catch (e) { | ||
console.log(`failed to migrate account ${account}`) | ||
} | ||
}) | ||
} | ||
} | ||
isUnlocked () { | ||
@@ -138,35 +107,2 @@ return Boolean(this.hdk && this.hdk.publicKey) | ||
addAccounts (n = 1) { | ||
return new Promise((resolve, reject) => { | ||
this.unlock() | ||
.then(async (_) => { | ||
const from = this.unlockedAccount | ||
const to = from + n | ||
for (let i = from; i < to; i++) { | ||
const path = this._getPathForIndex(i) | ||
let address | ||
if (this._isLedgerLiveHdPath()) { | ||
address = await this.unlock(path) | ||
} else { | ||
address = this._addressFromIndex(pathBase, i) | ||
} | ||
this.accountDetails[ethUtil.toChecksumAddress(address)] = { | ||
// TODO: consider renaming this property, as the current name is misleading | ||
// It's currently used to represent whether an account uses the Ledger Live path. | ||
bip44: this._isLedgerLiveHdPath(), | ||
hdPath: path, | ||
} | ||
if (!this.accounts.includes(address)) { | ||
this.accounts.push(address) | ||
} | ||
this.page = 0 | ||
} | ||
resolve(this.accounts) | ||
}) | ||
.catch(reject) | ||
}) | ||
} | ||
getFirstPage () { | ||
@@ -185,14 +121,2 @@ this.page = 0 | ||
getAccounts () { | ||
return Promise.resolve(this.accounts.slice()) | ||
} | ||
removeAccount (address) { | ||
if (!this.accounts.map((a) => a.toLowerCase()).includes(address.toLowerCase())) { | ||
throw new Error(`Address ${address} not found in this keyring`) | ||
} | ||
this.accounts = this.accounts.filter((a) => a.toLowerCase() !== address.toLowerCase()) | ||
delete this.accountDetails[ethUtil.toChecksumAddress(address)] | ||
} | ||
updateTransportMethod (useLedgerLive = false) { | ||
@@ -224,55 +148,19 @@ return new Promise((resolve, reject) => { | ||
// tx is an instance of the ethereumjs-transaction class. | ||
signTransaction (address, tx) { | ||
// transactions built with older versions of ethereumjs-tx have a | ||
// getChainId method that newer versions do not. Older versions are mutable | ||
// while newer versions default to being immutable. Expected shape and type | ||
// of data for v, r and s differ (Buffer (old) vs BN (new)) | ||
if (typeof tx.getChainId === 'function') { | ||
// In this version of ethereumjs-tx we must add the chainId in hex format | ||
// to the initial v value. The chainId must be included in the serialized | ||
// transaction which is only communicated to ethereumjs-tx in this | ||
// value. In newer versions the chainId is communicated via the 'Common' | ||
// object. | ||
tx.v = ethUtil.bufferToHex(tx.getChainId()) | ||
tx.r = '0x00' | ||
tx.s = '0x00' | ||
return this._signTransaction(address, tx, tx.to, (payload) => { | ||
tx.v = Buffer.from(payload.v, 'hex') | ||
tx.r = Buffer.from(payload.r, 'hex') | ||
tx.s = Buffer.from(payload.s, 'hex') | ||
return tx | ||
}) | ||
} | ||
// For transactions created by newer versions of @ethereumjs/tx | ||
// Note: https://github.com/ethereumjs/ethereumjs-monorepo/issues/1188 | ||
// It is not strictly necessary to do this additional setting of the v | ||
// value. We should be able to get the correct v value in serialization | ||
// if the above issue is resolved. Until then this must be set before | ||
// calling .serialize(). Note we are creating a temporarily mutable object | ||
// forfeiting the benefit of immutability until this happens. We do still | ||
// return a Transaction that is frozen if the originally provided | ||
// transaction was also frozen. | ||
const unfrozenTx = TransactionFactory.fromTxData(tx.toJSON(), { common: tx.common, freeze: false }) | ||
unfrozenTx.v = new ethUtil.BN(ethUtil.addHexPrefix(tx.common.chainId()), 'hex') | ||
return this._signTransaction(address, unfrozenTx, tx.to.buf, (payload) => { | ||
// Because tx will be immutable, first get a plain javascript object that | ||
// represents the transaction. Using txData here as it aligns with the | ||
// nomenclature of ethereumjs/tx. | ||
const txData = tx.toJSON() | ||
// The fromTxData utility expects v,r and s to be hex prefixed | ||
txData.v = ethUtil.addHexPrefix(payload.v) | ||
txData.r = ethUtil.addHexPrefix(payload.r) | ||
txData.s = ethUtil.addHexPrefix(payload.s) | ||
// Adopt the 'common' option from the original transaction and set the | ||
// returned object to be frozen if the original is frozen. | ||
return TransactionFactory.fromTxData(txData, { common: tx.common, freeze: Object.isFrozen(tx) }) | ||
}) | ||
} | ||
signTransaction (address, tx, accountIndex) { | ||
return new Promise((resolve, reject) => { | ||
this.unlock().then((_) => { | ||
tx.v = ethUtil.bufferToHex(tx.getChainId()) | ||
tx.r = '0x00' | ||
tx.s = '0x00' | ||
_signTransaction (address, tx, toAddress, handleSigning) { | ||
return new Promise((resolve, reject) => { | ||
this.unlockAccountByAddress(address) | ||
.then((hdPath) => { | ||
this._sendMessage({ | ||
let hdPath | ||
if (this._isLedgerLiveHdPath()) { | ||
const index = accountIndex | ||
hdPath = this._getPathForIndex(index) | ||
} else { | ||
hdPath = this._toLedgerPath(this._pathFromAddress(address)) | ||
} | ||
this._sendMessage( | ||
{ | ||
action: 'ledger-sign-transaction', | ||
@@ -282,3 +170,3 @@ params: { | ||
hdPath, | ||
to: ethUtil.bufferToHex(toAddress).toLowerCase(), | ||
to: ethUtil.bufferToHex(tx.to).toLowerCase(), | ||
}, | ||
@@ -288,134 +176,23 @@ }, | ||
if (success) { | ||
tx.v = Buffer.from(payload.v, 'hex') | ||
tx.r = Buffer.from(payload.r, 'hex') | ||
tx.s = Buffer.from(payload.s, 'hex') | ||
const newOrMutatedTx = handleSigning(payload) | ||
const valid = newOrMutatedTx.verifySignature() | ||
if (valid) { | ||
resolve(newOrMutatedTx) | ||
} else { | ||
reject(new Error('Ledger: The transaction signature is not valid')) | ||
} | ||
resolve(tx) | ||
} else { | ||
reject(new Error(payload.error || 'Ledger: Unknown error while signing transaction')) | ||
reject( | ||
new Error( | ||
payload.error || | ||
'Ledger: Unknown error while signing transaction', | ||
), | ||
) | ||
} | ||
}) | ||
}) | ||
.catch(reject) | ||
}) | ||
} | ||
signMessage (withAccount, data) { | ||
return this.signPersonalMessage(withAccount, data) | ||
} | ||
// For personal_sign, we need to prefix the message: | ||
signPersonalMessage (withAccount, message) { | ||
return new Promise((resolve, reject) => { | ||
this.unlockAccountByAddress(withAccount) | ||
.then((hdPath) => { | ||
this._sendMessage({ | ||
action: 'ledger-sign-personal-message', | ||
params: { | ||
hdPath, | ||
message: ethUtil.stripHexPrefix(message), | ||
}, | ||
}, | ||
({ success, payload }) => { | ||
if (success) { | ||
let v = payload.v - 27 | ||
v = v.toString(16) | ||
if (v.length < 2) { | ||
v = `0${v}` | ||
} | ||
const signature = `0x${payload.r}${payload.s}${v}` | ||
const addressSignedWith = sigUtil.recoverPersonalSignature({ data: message, sig: signature }) | ||
if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { | ||
reject(new Error('Ledger: The signature doesnt match the right address')) | ||
} | ||
resolve(signature) | ||
} else { | ||
reject(new Error(payload.error || 'Ledger: Unknown error while signing message')) | ||
} | ||
}) | ||
}) | ||
.catch(reject) | ||
) | ||
}).catch((err) => { | ||
throw err | ||
}) | ||
}) | ||
} | ||
async unlockAccountByAddress (address) { | ||
const checksummedAddress = ethUtil.toChecksumAddress(address) | ||
if (!Object.keys(this.accountDetails).includes(checksummedAddress)) { | ||
throw new Error(`Ledger: Account for address '${checksummedAddress}' not found`) | ||
} | ||
const { hdPath } = this.accountDetails[checksummedAddress] | ||
const unlockedAddress = await this.unlock(hdPath) | ||
// unlock resolves to the address for the given hdPath as reported by the ledger device | ||
// if that address is not the requested address, then this account belongs to a different device or seed | ||
if (unlockedAddress.toLowerCase() !== address.toLowerCase()) { | ||
throw new Error(`Ledger: Account ${address} does not belong to the connected device`) | ||
} | ||
return hdPath | ||
} | ||
async signTypedData (withAccount, data, options = {}) { | ||
const isV4 = options.version === 'V4' | ||
if (!isV4) { | ||
throw new Error('Ledger: Only version 4 of typed data signing is supported') | ||
} | ||
const { | ||
domain, | ||
types, | ||
primaryType, | ||
message, | ||
} = sigUtil.TypedDataUtils.sanitizeData(data) | ||
const domainSeparatorHex = sigUtil.TypedDataUtils.hashStruct('EIP712Domain', domain, types, isV4).toString('hex') | ||
const hashStructMessageHex = sigUtil.TypedDataUtils.hashStruct(primaryType, message, types, isV4).toString('hex') | ||
const hdPath = await this.unlockAccountByAddress(withAccount) | ||
const { success, payload } = await new Promise((resolve) => { | ||
this._sendMessage({ | ||
action: 'ledger-sign-typed-data', | ||
params: { | ||
hdPath, | ||
domainSeparatorHex, | ||
hashStructMessageHex, | ||
}, | ||
}, | ||
(result) => resolve(result)) | ||
}) | ||
if (success) { | ||
let v = payload.v - 27 | ||
v = v.toString(16) | ||
if (v.length < 2) { | ||
v = `0${v}` | ||
} | ||
const signature = `0x${payload.r}${payload.s}${v}` | ||
const addressSignedWith = sigUtil.recoverTypedSignature_v4({ | ||
data, | ||
sig: signature, | ||
}) | ||
if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { | ||
throw new Error('Ledger: The signature doesnt match the right address') | ||
} | ||
return signature | ||
} | ||
throw new Error(payload.error || 'Ledger: Unknown error while signing message') | ||
} | ||
exportAccount () { | ||
throw new Error('Not supported on this device') | ||
} | ||
forgetDevice () { | ||
this.accounts = [] | ||
this.page = 0 | ||
this.unlockedAccount = 0 | ||
this.paths = {} | ||
this.accountDetails = {} | ||
this.hdk = new HDKey() | ||
} | ||
/* PRIVATE METHODS */ | ||
@@ -488,2 +265,3 @@ | ||
} | ||
return accounts | ||
@@ -490,0 +268,0 @@ } |
{ | ||
"name": "@binance-chain/bsc-ledger-bridge-keyring", | ||
"version": "0.6.2", | ||
"version": "0.6.3", | ||
"description": "A MetaMask compatible keyring, for ledger hardware wallets", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
0
15715
329