mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
this should be ready to go
This commit is contained in:
parent
2355573340
commit
0b9b892c6b
@ -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 cleanErrorStack = require('./lib/cleanErrorStack')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const TrezorKeyring = require('eth-trezor-keyring')
|
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 {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// key mgmt
|
// key mgmt
|
||||||
const additionalKeyrings = [TrezorKeyring, LedgerKeyring]
|
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||||
this.keyringController = new KeyringController({
|
this.keyringController = new KeyringController({
|
||||||
keyringTypes: additionalKeyrings,
|
keyringTypes: additionalKeyrings,
|
||||||
initState: initState.KeyringController,
|
initState: initState.KeyringController,
|
||||||
@ -546,7 +546,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
keyringName = TrezorKeyring.type
|
keyringName = TrezorKeyring.type
|
||||||
break
|
break
|
||||||
case 'ledger':
|
case 'ledger':
|
||||||
keyringName = LedgerKeyring.type
|
keyringName = LedgerBridgeKeyring.type
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('MetamaskController:connectHardware - Unknown device')
|
throw new Error('MetamaskController:connectHardware - Unknown device')
|
||||||
|
56
package-lock.json
generated
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": {
|
"eth-lib": {
|
||||||
"version": "0.1.27",
|
"version": "0.1.27",
|
||||||
"resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz",
|
"resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz",
|
||||||
|
@ -111,6 +111,7 @@
|
|||||||
"eth-hd-keyring": "^1.2.2",
|
"eth-hd-keyring": "^1.2.2",
|
||||||
"eth-json-rpc-filters": "^1.2.6",
|
"eth-json-rpc-filters": "^1.2.6",
|
||||||
"eth-json-rpc-infura": "^3.0.0",
|
"eth-json-rpc-infura": "^3.0.0",
|
||||||
|
"eth-ledger-bridge-keyring": "github:brunobar79/eth-ledger-bridge-keyring",
|
||||||
"eth-method-registry": "^1.0.0",
|
"eth-method-registry": "^1.0.0",
|
||||||
"eth-phishing-detect": "^1.1.4",
|
"eth-phishing-detect": "^1.1.4",
|
||||||
"eth-query": "^2.1.2",
|
"eth-query": "^2.1.2",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user