this should be ready to go

feature/default_network_editable
brunobar79 6 years ago
parent 2355573340
commit 0b9b892c6b
  1. 320
      app/scripts/eth-ledger-keyring-listener.js
  2. 6
      app/scripts/metamask-controller.js
  3. 56
      package-lock.json
  4. 1
      package.json

@ -1,320 +0,0 @@
const {EventEmitter} = require('events')
const HDKey = require('hdkey')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const Transaction = require('ethereumjs-tx')
const hdPathString = `44'/60'/0'`
const type = 'Ledger Hardware'
const BRIDGE_URL = 'https://localhost:3000'
const pathBase = 'm'
const MAX_INDEX = 1000
class LedgerKeyring extends EventEmitter {
constructor (opts = {}) {
super()
this.type = type
this.page = 0
this.perPage = 5
this.unlockedAccount = 0
this.hdk = new HDKey()
this.paths = {}
this.iframe = null
this.setupIframe()
this.deserialize(opts)
}
setupIframe () {
this.iframe = document.createElement('iframe')
this.iframe.src = BRIDGE_URL
document.head.appendChild(this.iframe)
}
sendMessage (msg, cb) {
this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*')
window.addEventListener('message', ({ origin, data }) => {
if (origin !== BRIDGE_URL) return false
if (data && data.action && data.action === `${msg.action}-reply`) {
cb(data)
}
})
}
serialize () {
return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts})
}
deserialize (opts = {}) {
this.hdPath = opts.hdPath || hdPathString
this.unlocked = opts.unlocked || false
this.accounts = opts.accounts || []
return Promise.resolve()
}
isUnlocked () {
return this.unlocked
}
setAccountToUnlock (index) {
this.unlockedAccount = parseInt(index, 10)
}
unlock () {
if (this.isUnlocked()) return Promise.resolve('already unlocked')
return new Promise((resolve, reject) => {
this.sendMessage({
action: 'ledger-unlock',
params: {
hdPath: this.hdPath,
},
},
({success, payload}) => {
if (success) {
this.hdk.publicKey = new Buffer(payload.publicKey, 'hex')
this.hdk.chainCode = new Buffer(payload.chainCode, 'hex')
resolve('just unlocked')
} else {
reject(payload.error || 'Unknown error')
}
})
})
}
addAccounts (n = 1) {
return new Promise((resolve, reject) => {
this.unlock()
.then(_ => {
const from = this.unlockedAccount
const to = from + n
this.accounts = []
for (let i = from; i < to; i++) {
const address = this._addressFromIndex(pathBase, i)
this.accounts.push(address)
this.page = 0
}
resolve(this.accounts)
})
.catch(e => {
reject(e)
})
})
}
getFirstPage () {
this.page = 0
return this.__getPage(1)
}
getNextPage () {
return this.__getPage(1)
}
getPreviousPage () {
return this.__getPage(-1)
}
__getPage (increment) {
this.page += increment
if (this.page <= 0) { this.page = 1 }
return new Promise((resolve, reject) => {
this.unlock()
.then(_ => {
const from = (this.page - 1) * this.perPage
const to = from + this.perPage
const accounts = []
for (let i = from; i < to; i++) {
const address = this._addressFromIndex(pathBase, i)
accounts.push({
address: address,
balance: null,
index: i,
})
this.paths[ethUtil.toChecksumAddress(address)] = i
}
resolve(accounts)
})
.catch(e => {
reject(e)
})
})
}
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())
}
// tx is an instance of the ethereumjs-transaction class.
async signTransaction (address, tx) {
return new Promise((resolve, reject) => {
this.unlock()
.then(_ => {
const newTx = new Transaction({
from: this._normalize(address),
to: this._normalize(tx.to),
value: this._normalize(tx.value),
data: this._normalize(tx.data),
chainId: tx._chainId,
nonce: this._normalize(tx.nonce),
gasLimit: this._normalize(tx.gasLimit),
gasPrice: this._normalize(tx.gasPrice),
v: ethUtil.bufferToHex(tx.getChainId()),
r: '0x00',
s: '0x00',
})
this.sendMessage({
action: 'ledger-sign-transaction',
params: {
tx: newTx.serialize().toString('hex'),
hdPath: this._pathFromAddress(address),
},
},
({success, payload}) => {
if (success) {
newTx.v = Buffer.from(payload.v, 'hex')
newTx.r = Buffer.from(payload.r, 'hex')
newTx.s = Buffer.from(payload.s, 'hex')
const valid = newTx.verifySignature()
if (valid) {
resolve(newTx)
} else {
reject('The transaction signature is not valid')
}
} else {
reject(payload)
}
})
})
})
}
async signMessage (withAccount, data) {
throw new Error('Not supported on this device')
}
// For personal_sign, we need to prefix the message:
async signPersonalMessage (withAccount, message) {
const humanReadableMsg = this._toAscii(message)
const bufferMsg = Buffer.from(humanReadableMsg).toString('hex')
return new Promise((resolve, reject) => {
this.unlock()
.then(_ => {
this.sendMessage({
action: 'ledger-sign-personal-message',
params: {
hdPath: this._pathFromAddress(withAccount),
message: bufferMsg,
},
},
({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('signature doesnt match the right address')
}
resolve(signature)
} else {
reject(payload)
}
})
})
})
}
async signTypedData (withAccount, typedData) {
throw new Error('Not supported on this device')
}
async exportAccount (address) {
throw new Error('Not supported on this device')
}
forgetDevice () {
this.accounts = []
this.unlocked = false
this.page = 0
this.unlockedAccount = 0
this.paths = {}
}
/* PRIVATE METHODS */
_padLeftEven (hex) {
return hex.length % 2 !== 0 ? `0${hex}` : hex
}
_normalize (buf) {
return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase())
}
_addressFromIndex (pathBase, i) {
const dkey = this.hdk.derive(`${pathBase}/${i}`)
const address = ethUtil
.publicToAddress(dkey.publicKey, true)
.toString('hex')
return ethUtil.toChecksumAddress(address)
}
_pathFromAddress (address) {
const checksummedAddress = ethUtil.toChecksumAddress(address)
let index = this.paths[checksummedAddress]
if (typeof index === 'undefined') {
for (let i = 0; i < MAX_INDEX; i++) {
if (checksummedAddress === this._addressFromIndex(pathBase, i)) {
index = i
break
}
}
}
if (typeof index === 'undefined') {
throw new Error('Unknown address')
}
return `${this.hdPath}/${index}`
}
_toAscii (hex) {
let str = ''
let i = 0; const l = hex.length
if (hex.substring(0, 2) === '0x') {
i = 2
}
for (; i < l; i += 2) {
const code = parseInt(hex.substr(i, 2), 16)
str += String.fromCharCode(code)
}
return str
}
}
LedgerKeyring.type = type
module.exports = LedgerKeyring

@ -49,7 +49,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const cleanErrorStack = require('./lib/cleanErrorStack')
const log = require('loglevel')
const TrezorKeyring = require('eth-trezor-keyring')
const LedgerKeyring = require('./eth-ledger-keyring-listener')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
module.exports = class MetamaskController extends EventEmitter {
@ -128,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter {
})
// key mgmt
const additionalKeyrings = [TrezorKeyring, LedgerKeyring]
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
this.keyringController = new KeyringController({
keyringTypes: additionalKeyrings,
initState: initState.KeyringController,
@ -546,7 +546,7 @@ module.exports = class MetamaskController extends EventEmitter {
keyringName = TrezorKeyring.type
break
case 'ledger':
keyringName = LedgerKeyring.type
keyringName = LedgerBridgeKeyring.type
break
default:
throw new Error('MetamaskController:connectHardware - Unknown device')

56
package-lock.json generated

@ -8458,6 +8458,62 @@
}
}
},
"eth-ledger-bridge-keyring": {
"version": "github:brunobar79/eth-ledger-bridge-keyring#f8f05925519a34e2d5ee3083ca95960fa70ddd11",
"from": "github:brunobar79/eth-ledger-bridge-keyring",
"requires": {
"eth-sig-util": "^1.4.2",
"ethereumjs-tx": "^1.3.4",
"ethereumjs-util": "^5.1.5",
"events": "^2.0.0",
"hdkey": "0.8.0"
},
"dependencies": {
"ethereum-common": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz",
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
},
"ethereumjs-tx": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz",
"integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==",
"requires": {
"ethereum-common": "^0.0.18",
"ethereumjs-util": "^5.0.0"
}
},
"ethereumjs-util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
"ethjs-util": "^0.1.3",
"keccak": "^1.0.2",
"rlp": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
}
},
"events": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
"integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg=="
},
"hdkey": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/hdkey/-/hdkey-0.8.0.tgz",
"integrity": "sha512-oYsdlK22eobT68N5faWI3776f6tOLyqxLLYwxMx+TP0rkWzuCs0oiOm2VbLWcxdpHFP4LtiRR8udaIX8VkEaZQ==",
"requires": {
"coinstring": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
}
}
}
},
"eth-lib": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz",

@ -111,6 +111,7 @@
"eth-hd-keyring": "^1.2.2",
"eth-json-rpc-filters": "^1.2.6",
"eth-json-rpc-infura": "^3.0.0",
"eth-ledger-bridge-keyring": "github:brunobar79/eth-ledger-bridge-keyring",
"eth-method-registry": "^1.0.0",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",

Loading…
Cancel
Save