aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Finlay <somniac@me.com>2016-03-26 09:57:40 +0800
committerDan Finlay <somniac@me.com>2016-03-26 09:57:40 +0800
commit9fbf40e7028a92fe582a8c554bdbea1dbbfe143b (patch)
tree63c5fd2a4e874a2928546630cb8f8d92e3236535
parent1ff518a94e966f5851efb5d60c4277d79f17d744 (diff)
parent742fd7caa8599f4044d9eccc941dbfd47af81c82 (diff)
downloadtangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar.gz
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar.bz2
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar.lz
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar.xz
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.tar.zst
tangerine-wallet-browser-9fbf40e7028a92fe582a8c554bdbea1dbbfe143b.zip
Merge pull request #73 from MetaMask/Bip44
Bip44-compliant HD Tree Generation
-rw-r--r--.babelrc1
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/lib/idStore.js126
-rw-r--r--package.json10
-rw-r--r--test/helper.js4
-rw-r--r--test/index.html29
-rw-r--r--test/spec/test.js11
-rw-r--r--test/unit/idStore-test.js81
8 files changed, 156 insertions, 108 deletions
diff --git a/.babelrc b/.babelrc
new file mode 100644
index 000000000..9d8d51656
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1 @@
+{ "presets": ["es2015"] }
diff --git a/app/manifest.json b/app/manifest.json
index 0777c0c8a..1ca0f55e9 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,6 +1,6 @@
{
"name": "__MSG_appName__",
- "version": "0.15.0",
+ "version": "1.0.0",
"manifest_version": 2,
"description": "__MSG_appDescription__",
"icons": {
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index c4547b07f..e3f2a841d 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -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){
diff --git a/package.json b/package.json
index 9f700e924..f482fd4f6 100644
--- a/package.json
+++ b/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"
diff --git a/test/helper.js b/test/helper.js
new file mode 100644
index 000000000..7746b90e0
--- /dev/null
+++ b/test/helper.js
@@ -0,0 +1,4 @@
+require('mocha-sinon')()
+var jsdom = require('mocha-jsdom')
+jsdom()
+
diff --git a/test/index.html b/test/index.html
deleted file mode 100644
index 6498d5fcc..000000000
--- a/test/index.html
+++ /dev/null
@@ -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>
diff --git a/test/spec/test.js b/test/spec/test.js
deleted file mode 100644
index 0fca0fb57..000000000
--- a/test/spec/test.js
+++ /dev/null
@@ -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 () {
-
- });
- });
- });
-})();
diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js
new file mode 100644
index 000000000..d3fabfe9d
--- /dev/null
+++ b/test/unit/idStore-test.js
@@ -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()
+ })
+ })
+ })
+})