mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #73 from MetaMask/Bip44
Bip44-compliant HD Tree Generation
This commit is contained in:
commit
9fbf40e702
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"version": "0.15.0",
|
||||
"version": "1.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "__MSG_appDescription__",
|
||||
"icons": {
|
||||
|
@ -14,23 +14,24 @@ module.exports = IdentityStore
|
||||
|
||||
inherits(IdentityStore, EventEmitter)
|
||||
function IdentityStore(ethStore) {
|
||||
const self = this
|
||||
EventEmitter.call(self)
|
||||
EventEmitter.call(this)
|
||||
|
||||
// we just use the ethStore to auto-add accounts
|
||||
self._ethStore = ethStore
|
||||
this._ethStore = ethStore
|
||||
// lightwallet key store
|
||||
self._keyStore = null
|
||||
this._keyStore = null
|
||||
// lightwallet wrapper
|
||||
self._idmgmt = null
|
||||
this._idmgmt = null
|
||||
|
||||
self._currentState = {
|
||||
this.hdPathString = "m/44'/60'/0'/0"
|
||||
|
||||
this._currentState = {
|
||||
selectedAddress: null,
|
||||
identities: {},
|
||||
unconfTxs: {},
|
||||
}
|
||||
// not part of serilized metamask state - only kept in memory
|
||||
self._unconfTxCbs = {}
|
||||
this._unconfTxCbs = {}
|
||||
}
|
||||
|
||||
//
|
||||
@ -51,18 +52,17 @@ IdentityStore.prototype.createNewVault = function(password, entropy, cb){
|
||||
}
|
||||
|
||||
IdentityStore.prototype.recoverFromSeed = function(password, seed, cb){
|
||||
const self = this
|
||||
self._createIdmgmt(password, seed, null, function(err){
|
||||
this._createIdmgmt(password, seed, null, (err) => {
|
||||
if (err) return cb(err)
|
||||
self._loadIdentities()
|
||||
self._didUpdate()
|
||||
|
||||
this._loadIdentities()
|
||||
this._didUpdate()
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
IdentityStore.prototype.setStore = function(store){
|
||||
const self = this
|
||||
self._ethStore = store
|
||||
this._ethStore = store
|
||||
}
|
||||
|
||||
IdentityStore.prototype.clearSeedWordCache = function(cb) {
|
||||
@ -71,46 +71,40 @@ IdentityStore.prototype.clearSeedWordCache = function(cb) {
|
||||
}
|
||||
|
||||
IdentityStore.prototype.getState = function(){
|
||||
const self = this
|
||||
const cachedSeeds = window.localStorage['seedWords']
|
||||
return clone(extend(self._currentState, {
|
||||
return clone(extend(this._currentState, {
|
||||
isInitialized: !!window.localStorage['lightwallet'] && !cachedSeeds,
|
||||
isUnlocked: self._isUnlocked(),
|
||||
isUnlocked: this._isUnlocked(),
|
||||
seedWords: cachedSeeds,
|
||||
}))
|
||||
}
|
||||
|
||||
IdentityStore.prototype.getSelectedAddress = function(){
|
||||
const self = this
|
||||
return self._currentState.selectedAddress
|
||||
return this._currentState.selectedAddress
|
||||
}
|
||||
|
||||
IdentityStore.prototype.setSelectedAddress = function(address){
|
||||
const self = this
|
||||
self._currentState.selectedAddress = address
|
||||
self._didUpdate()
|
||||
this._currentState.selectedAddress = address
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
IdentityStore.prototype.setLocked = function(cb){
|
||||
const self = this
|
||||
delete self._keyStore
|
||||
delete self._idmgmt
|
||||
delete this._keyStore
|
||||
delete this._idmgmt
|
||||
cb()
|
||||
}
|
||||
|
||||
IdentityStore.prototype.submitPassword = function(password, cb){
|
||||
const self = this
|
||||
self._tryPassword(password, function(err){
|
||||
this._tryPassword(password, (err) => {
|
||||
if (err) return cb(err)
|
||||
// load identities before returning...
|
||||
self._loadIdentities()
|
||||
this._loadIdentities()
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
// comes from dapp via zero-client hooked-wallet provider
|
||||
IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){
|
||||
var self = this
|
||||
|
||||
// create txData obj with parameters and meta data
|
||||
var time = (new Date()).getTime()
|
||||
@ -121,56 +115,51 @@ IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){
|
||||
time: time,
|
||||
status: 'unconfirmed',
|
||||
}
|
||||
self._currentState.unconfTxs[txId] = txData
|
||||
this._currentState.unconfTxs[txId] = txData
|
||||
console.log('addUnconfirmedTransaction:', txData)
|
||||
|
||||
// keep the cb around for after approval (requires user interaction)
|
||||
self._unconfTxCbs[txId] = cb
|
||||
this._unconfTxCbs[txId] = cb
|
||||
|
||||
// signal update
|
||||
self._didUpdate()
|
||||
this._didUpdate()
|
||||
|
||||
return txId
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.approveTransaction = function(txId, cb){
|
||||
const self = this
|
||||
|
||||
var txData = self._currentState.unconfTxs[txId]
|
||||
var txData = this._currentState.unconfTxs[txId]
|
||||
var txParams = txData.txParams
|
||||
var approvalCb = self._unconfTxCbs[txId] || noop
|
||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
||||
|
||||
// accept tx
|
||||
cb()
|
||||
approvalCb(null, true)
|
||||
// clean up
|
||||
delete self._currentState.unconfTxs[txId]
|
||||
delete self._unconfTxCbs[txId]
|
||||
self._didUpdate()
|
||||
delete this._currentState.unconfTxs[txId]
|
||||
delete this._unconfTxCbs[txId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.cancelTransaction = function(txId){
|
||||
const self = this
|
||||
|
||||
var txData = self._currentState.unconfTxs[txId]
|
||||
var approvalCb = self._unconfTxCbs[txId] || noop
|
||||
var txData = this._currentState.unconfTxs[txId]
|
||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
||||
|
||||
// reject tx
|
||||
approvalCb(null, false)
|
||||
// clean up
|
||||
delete self._currentState.unconfTxs[txId]
|
||||
delete self._unconfTxCbs[txId]
|
||||
self._didUpdate()
|
||||
delete this._currentState.unconfTxs[txId]
|
||||
delete this._unconfTxCbs[txId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// performs the actual signing, no autofill of params
|
||||
IdentityStore.prototype.signTransaction = function(txParams, cb){
|
||||
const self = this
|
||||
try {
|
||||
console.log('signing tx...', txParams)
|
||||
var rawTx = self._idmgmt.signTx(txParams)
|
||||
var rawTx = this._idmgmt.signTx(txParams)
|
||||
cb(null, rawTx)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
@ -182,13 +171,11 @@ IdentityStore.prototype.signTransaction = function(txParams, cb){
|
||||
//
|
||||
|
||||
IdentityStore.prototype._didUpdate = function(){
|
||||
const self = this
|
||||
self.emit('update', self.getState())
|
||||
this.emit('update', this.getState())
|
||||
}
|
||||
|
||||
IdentityStore.prototype._isUnlocked = function(){
|
||||
const self = this
|
||||
var result = Boolean(self._keyStore) && Boolean(self._idmgmt)
|
||||
var result = Boolean(this._keyStore) && Boolean(this._idmgmt)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -198,22 +185,21 @@ IdentityStore.prototype._cacheSeedWordsUntilConfirmed = function(seedWords) {
|
||||
|
||||
// load identities from keyStoreet
|
||||
IdentityStore.prototype._loadIdentities = function(){
|
||||
const self = this
|
||||
if (!self._isUnlocked()) throw new Error('not unlocked')
|
||||
if (!this._isUnlocked()) throw new Error('not unlocked')
|
||||
// get addresses and normalize address hexString
|
||||
var addresses = self._keyStore.getAddresses().map(function(address){ return '0x'+address })
|
||||
addresses.forEach(function(address){
|
||||
var addresses = this._keyStore.getAddresses(this.hdPathString).map((address) => { return '0x'+address })
|
||||
addresses.forEach((address) => {
|
||||
// // add to ethStore
|
||||
self._ethStore.addAccount(address)
|
||||
this._ethStore.addAccount(address)
|
||||
// add to identities
|
||||
var identity = {
|
||||
name: 'Wally',
|
||||
img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd',
|
||||
address: address,
|
||||
}
|
||||
self._currentState.identities[address] = identity
|
||||
this._currentState.identities[address] = identity
|
||||
})
|
||||
self._didUpdate()
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
//
|
||||
@ -221,8 +207,7 @@ IdentityStore.prototype._loadIdentities = function(){
|
||||
//
|
||||
|
||||
IdentityStore.prototype._tryPassword = function(password, cb){
|
||||
const self = this
|
||||
self._createIdmgmt(password, null, null, cb)
|
||||
this._createIdmgmt(password, null, null, cb)
|
||||
}
|
||||
|
||||
IdentityStore.prototype._createIdmgmt = function(password, seed, entropy, cb){
|
||||
@ -232,7 +217,7 @@ IdentityStore.prototype._createIdmgmt = function(password, seed, entropy, cb){
|
||||
var serializedKeystore = window.localStorage['lightwallet']
|
||||
|
||||
if (seed) {
|
||||
this._restoreFromSeed(keyStore, seed, derivedKey)
|
||||
keyStore = this._restoreFromSeed(password, seed, derivedKey)
|
||||
|
||||
// returning user, recovering from localStorage
|
||||
} else if (serializedKeystore) {
|
||||
@ -249,17 +234,22 @@ IdentityStore.prototype._createIdmgmt = function(password, seed, entropy, cb){
|
||||
this._idmgmt = new IdManagement({
|
||||
keyStore: keyStore,
|
||||
derivedKey: derivedKey,
|
||||
hdPathSTring: this.hdPathString,
|
||||
})
|
||||
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
IdentityStore.prototype._restoreFromSeed = function(keyStore, seed, derivedKey) {
|
||||
keyStore = new LightwalletKeyStore(seed, derivedKey)
|
||||
IdentityStore.prototype._restoreFromSeed = function(password, seed, derivedKey) {
|
||||
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
|
||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'});
|
||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
||||
|
||||
keyStore.generateNewAddress(derivedKey, 3)
|
||||
window.localStorage['lightwallet'] = keyStore.serialize()
|
||||
console.log('restored from seed. saved to keystore localStorage')
|
||||
return keyStore
|
||||
}
|
||||
|
||||
IdentityStore.prototype._loadFromLocalStorage = function(serializedKeystore, derivedKey) {
|
||||
@ -268,19 +258,23 @@ IdentityStore.prototype._loadFromLocalStorage = function(serializedKeystore, der
|
||||
|
||||
IdentityStore.prototype._createFirstWallet = function(entropy, derivedKey) {
|
||||
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
|
||||
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey)
|
||||
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
|
||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'});
|
||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
||||
|
||||
keyStore.generateNewAddress(derivedKey, 3)
|
||||
window.localStorage['lightwallet'] = keyStore.serialize()
|
||||
console.log('saved to keystore localStorage')
|
||||
return keyStore
|
||||
}
|
||||
|
||||
function IdManagement( opts = { keyStore: null, derivedKey: null } ) {
|
||||
function IdManagement( opts = { keyStore: null, derivedKey: null, hdPathString: null } ) {
|
||||
this.keyStore = opts.keyStore
|
||||
this.derivedKey = opts.derivedKey
|
||||
this.hdPathString = opts.hdPathString
|
||||
|
||||
this.getAddresses = function(){
|
||||
return keyStore.getAddresses().map(function(address){ return '0x'+address })
|
||||
return keyStore.getAddresses(this.hdPathString).map(function(address){ return '0x'+address })
|
||||
}
|
||||
|
||||
this.signTx = function(txParams){
|
||||
|
10
package.json
10
package.json
@ -4,7 +4,8 @@
|
||||
"public": false,
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "gulp dev"
|
||||
"start": "gulp dev",
|
||||
"test": "mocha --compilers js:babel-register --recursive"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.5.2",
|
||||
@ -28,6 +29,8 @@
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.7.2",
|
||||
"browserify": "^13.0.0",
|
||||
"del": "^2.2.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
@ -35,8 +38,13 @@
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"jsdom": "^8.1.0",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"lodash.assign": "^4.0.6",
|
||||
"mocha": "^2.4.5",
|
||||
"mocha-jsdom": "^1.1.0",
|
||||
"mocha-sinon": "^1.1.5",
|
||||
"sinon": "^1.17.3",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.7.0"
|
||||
|
4
test/helper.js
Normal file
4
test/helper.js
Normal file
@ -0,0 +1,4 @@
|
||||
require('mocha-sinon')()
|
||||
var jsdom = require('mocha-jsdom')
|
||||
jsdom()
|
||||
|
@ -1,29 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mocha Spec Runner</title>
|
||||
<link rel="stylesheet" href="../bower_components/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../bower_components/mocha/mocha.js"></script>
|
||||
<script>mocha.setup('bdd');</script>
|
||||
<script src="../bower_components/chai/chai.js"></script>
|
||||
<script>
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
var should = chai.should();
|
||||
</script>
|
||||
<!-- bower:js -->
|
||||
<!-- endbower -->
|
||||
<!-- include source files here... -->
|
||||
<!-- include spec files here... -->
|
||||
<script src="spec/test.js"></script>
|
||||
<script>
|
||||
if (navigator.userAgent.indexOf('PhantomJS') === -1) {
|
||||
mocha.run();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('Give it some context', function () {
|
||||
describe('maybe a bit more context here', function () {
|
||||
it('should run here few assertions', function () {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
81
test/unit/idStore-test.js
Normal file
81
test/unit/idStore-test.js
Normal file
@ -0,0 +1,81 @@
|
||||
var assert = require('assert')
|
||||
var IdentityStore = require('../../app/scripts/lib/idStore')
|
||||
|
||||
describe('IdentityStore', function() {
|
||||
|
||||
describe('#createNewVault', function () {
|
||||
let idStore
|
||||
let password = 'password123'
|
||||
let entropy = 'entripppppyy duuude'
|
||||
let seedWords
|
||||
let accounts = []
|
||||
let originalKeystore
|
||||
|
||||
before(function(done) {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
idStore = new IdentityStore({
|
||||
addAccount(acct) { accounts.push(acct) },
|
||||
})
|
||||
|
||||
idStore.createNewVault(password, entropy, (err, seeds) => {
|
||||
seedWords = seeds
|
||||
originalKeystore = idStore._idmgmt.keyStore
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#recoverFromSeed', function() {
|
||||
let newAccounts = []
|
||||
|
||||
before(function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
idStore = new IdentityStore({
|
||||
addAccount(acct) { newAccounts.push(acct) },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected keystore', function (done) {
|
||||
|
||||
idStore.recoverFromSeed(password, seedWords, (err) => {
|
||||
assert.ifError(err)
|
||||
|
||||
let newKeystore = idStore._idmgmt.keyStore
|
||||
assert.equal(newAccounts[0], accounts[0])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#recoverFromSeed BIP44 compliance', function() {
|
||||
let seedWords = 'picnic injury awful upper eagle junk alert toss flower renew silly vague'
|
||||
let firstAccount = '0x5d8de92c205279c10e5669f797b853ccef4f739a'
|
||||
|
||||
let password = 'secret!'
|
||||
let accounts = []
|
||||
let idStore
|
||||
|
||||
before(function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
idStore = new IdentityStore({
|
||||
addAccount(acct) {
|
||||
accounts.push(acct)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected first account', function (done) {
|
||||
|
||||
idStore.recoverFromSeed(password, seedWords, (err) => {
|
||||
assert.ifError(err)
|
||||
|
||||
let newKeystore = idStore._idmgmt.keyStore
|
||||
assert.equal(accounts[0], firstAccount)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user