|
|
@ -1,11 +1,16 @@ |
|
|
|
const extension = require('extensionizer') |
|
|
|
const extension = require('extensionizer') |
|
|
|
const {EventEmitter} = require('events') |
|
|
|
const {EventEmitter} = require('events') |
|
|
|
|
|
|
|
const HDKey = require('hdkey') |
|
|
|
|
|
|
|
const ethUtil = require('ethereumjs-util') |
|
|
|
|
|
|
|
const sigUtil = require('eth-sig-util') |
|
|
|
|
|
|
|
const Transaction = require('ethereumjs-tx') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger
|
|
|
|
// HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger
|
|
|
|
const hdPathString = `m/44'/60'/0'` |
|
|
|
const hdPathString = `m/44'/60'/0'` |
|
|
|
const type = 'Ledger Hardware Keyring' |
|
|
|
const type = 'Ledger Hardware Keyring' |
|
|
|
const ORIGIN = 'http://localhost:9000' |
|
|
|
const ORIGIN = 'https://localhost:3000' |
|
|
|
|
|
|
|
const pathBase = 'm' |
|
|
|
|
|
|
|
|
|
|
|
class LedgerKeyring extends EventEmitter { |
|
|
|
class LedgerKeyring extends EventEmitter { |
|
|
|
constructor (opts = {}) { |
|
|
|
constructor (opts = {}) { |
|
|
@ -14,6 +19,7 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
this.page = 0 |
|
|
|
this.page = 0 |
|
|
|
this.perPage = 5 |
|
|
|
this.perPage = 5 |
|
|
|
this.unlockedAccount = 0 |
|
|
|
this.unlockedAccount = 0 |
|
|
|
|
|
|
|
this.hdk = new HDKey() |
|
|
|
this.paths = {} |
|
|
|
this.paths = {} |
|
|
|
this.iframe = null |
|
|
|
this.iframe = null |
|
|
|
this.setupIframe() |
|
|
|
this.setupIframe() |
|
|
@ -26,10 +32,6 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
console.log('Injecting ledger iframe') |
|
|
|
console.log('Injecting ledger iframe') |
|
|
|
document.head.appendChild(this.iframe) |
|
|
|
document.head.appendChild(this.iframe) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
Passing messages from iframe to background script |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') |
|
|
|
console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
@ -78,32 +80,39 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
}, |
|
|
|
}, |
|
|
|
({action, success, payload}) => {
|
|
|
|
({action, success, payload}) => {
|
|
|
|
if (success) { |
|
|
|
if (success) { |
|
|
|
resolve(payload) |
|
|
|
this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') |
|
|
|
|
|
|
|
this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') |
|
|
|
|
|
|
|
resolve('just unlocked') |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
reject(payload) |
|
|
|
reject(payload.error || 'Unknown error') |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async addAccounts (n = 1) { |
|
|
|
setAccountToUnlock (index) { |
|
|
|
|
|
|
|
this.unlockedAccount = parseInt(index, 10) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addAccounts (n = 1) { |
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
this.unlock() |
|
|
|
this.unlock() |
|
|
|
.then(_ => { |
|
|
|
.then(_ => { |
|
|
|
this.sendMessage({ |
|
|
|
const from = this.unlockedAccount |
|
|
|
action: 'ledger-add-account', |
|
|
|
const to = from + n |
|
|
|
params: { |
|
|
|
this.accounts = [] |
|
|
|
n, |
|
|
|
|
|
|
|
}, |
|
|
|
for (let i = from; i < to; i++) { |
|
|
|
}, |
|
|
|
const address = this._addressFromIndex(pathBase, i) |
|
|
|
({action, success, payload}) => { |
|
|
|
this.accounts.push(address) |
|
|
|
if (success) { |
|
|
|
this.page = 0 |
|
|
|
resolve(payload) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
reject(payload) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
resolve(this.accounts) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(e => { |
|
|
|
|
|
|
|
reject(e) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -129,20 +138,27 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
this.unlock() |
|
|
|
this.unlock() |
|
|
|
.then(_ => { |
|
|
|
.then(_ => { |
|
|
|
this.sendMessage({ |
|
|
|
|
|
|
|
action: 'ledger-get-page', |
|
|
|
const from = (this.page - 1) * this.perPage |
|
|
|
params: { |
|
|
|
const to = from + this.perPage |
|
|
|
page: this.page, |
|
|
|
|
|
|
|
}, |
|
|
|
const accounts = [] |
|
|
|
}, |
|
|
|
|
|
|
|
({action, success, payload}) => { |
|
|
|
for (let i = from; i < to; i++) { |
|
|
|
if (success) { |
|
|
|
const address = this._addressFromIndex(pathBase, i) |
|
|
|
resolve(payload) |
|
|
|
accounts.push({ |
|
|
|
} else { |
|
|
|
address: address, |
|
|
|
reject(payload) |
|
|
|
balance: null, |
|
|
|
}
|
|
|
|
index: i, |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
this.paths[ethUtil.toChecksumAddress(address)] = i |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
resolve(accounts) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(e => { |
|
|
|
|
|
|
|
reject(e) |
|
|
|
|
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -157,6 +173,7 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) |
|
|
|
this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// tx is an instance of the ethereumjs-transaction class.
|
|
|
|
// tx is an instance of the ethereumjs-transaction class.
|
|
|
|
async signTransaction (address, tx) { |
|
|
|
async signTransaction (address, tx) { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
@ -224,6 +241,56 @@ class LedgerKeyring extends EventEmitter { |
|
|
|
this.unlockedAccount = 0 |
|
|
|
this.unlockedAccount = 0 |
|
|
|
this.paths = {} |
|
|
|
this.paths = {} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* PRIVATE METHODS */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_padLeftEven (hex) { |
|
|
|
|
|
|
|
return hex.length % 2 !== 0 ? `0${hex}` : hex |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_normalize (buf) { |
|
|
|
|
|
|
|
return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).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 |
|
|
|
LedgerKeyring.type = type |
|
|
|