Merge pull request #482 from maraoz/fix/BIP-NNNN-compat
change BIP32 structure to match our new BIP
This commit is contained in:
commit
7acf5f270b
10 changed files with 130 additions and 63 deletions
1
copay.js
1
copay.js
|
|
@ -4,6 +4,7 @@ module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing');
|
||||||
module.exports.TxProposals = require('./js/models/core/TxProposals');
|
module.exports.TxProposals = require('./js/models/core/TxProposals');
|
||||||
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
|
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
|
||||||
module.exports.Passphrase = require('./js/models/core/Passphrase');
|
module.exports.Passphrase = require('./js/models/core/Passphrase');
|
||||||
|
module.exports.Structure = require('./js/models/core/Structure');
|
||||||
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
var imports = require('soop').imports();
|
var imports = require('soop').imports();
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var HK = bitcore.HierarchicalKey;
|
var HK = bitcore.HierarchicalKey;
|
||||||
var WalletKey = bitcore.WalletKey;
|
var WalletKey = bitcore.WalletKey;
|
||||||
var networks = bitcore.networks;
|
var networks = bitcore.networks;
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var PublicKeyRing = require('./PublicKeyRing');
|
var Structure = require('./Structure');
|
||||||
|
|
||||||
function PrivateKey(opts) {
|
function PrivateKey(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
@ -20,13 +20,20 @@ function PrivateKey(opts) {
|
||||||
|
|
||||||
PrivateKey.prototype.getId = function() {
|
PrivateKey.prototype.getId = function() {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
var path = PublicKeyRing.ID_BRANCH;
|
var path = Structure.IdFullBranch;
|
||||||
var bip32 = this.bip.derive(path);
|
var idhk = this.bip.derive(path);
|
||||||
this.id= bip32.eckey.public.toString('hex');
|
this.id= idhk.eckey.public.toString('hex');
|
||||||
}
|
}
|
||||||
return this.id;
|
return this.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PrivateKey.prototype.deriveBIP45Branch = function() {
|
||||||
|
if (!this.bip45Branch) {
|
||||||
|
this.bip45Branch = this.bip.derive(Structure.BIP45_PUBLIC_PREFIX);
|
||||||
|
}
|
||||||
|
return this.bip45Branch;
|
||||||
|
}
|
||||||
|
|
||||||
PrivateKey.fromObj = function(obj) {
|
PrivateKey.fromObj = function(obj) {
|
||||||
return new PrivateKey(obj);
|
return new PrivateKey(obj);
|
||||||
};
|
};
|
||||||
|
|
@ -55,13 +62,11 @@ PrivateKey.prototype._getHK = function(path) {
|
||||||
};
|
};
|
||||||
|
|
||||||
PrivateKey.prototype.get = function(index,isChange) {
|
PrivateKey.prototype.get = function(index,isChange) {
|
||||||
var path = PublicKeyRing.Branch(index, isChange);
|
var path = Structure.FullBranch(index, isChange);
|
||||||
var pk = this.privateKeyCache[path];
|
var pk = this.privateKeyCache[path];
|
||||||
if (!pk) {
|
if (!pk) {
|
||||||
var derivedHK = this._getHK(path);
|
var derivedHK = this._getHK(path);
|
||||||
pk = this.privateKeyCache[path] = derivedHK.eckey.private.toString('hex');
|
pk = this.privateKeyCache[path] = derivedHK.eckey.private.toString('hex');
|
||||||
} else {
|
|
||||||
//console.log('cache hit!');
|
|
||||||
}
|
}
|
||||||
var wk = new WalletKey({network: this.network});
|
var wk = new WalletKey({network: this.network});
|
||||||
wk.fromObj({priv: pk});
|
wk.fromObj({priv: pk});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
var imports = require('soop').imports();
|
var imports = require('soop').imports();
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var HK = bitcore.HierarchicalKey;
|
var HK = bitcore.HierarchicalKey;
|
||||||
|
var PrivateKey = require('./PrivateKey');
|
||||||
|
var Structure = require('./Structure');
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
var coinUtil = bitcore.util;
|
var coinUtil = bitcore.util;
|
||||||
|
|
@ -36,22 +38,6 @@ function PublicKeyRing(opts) {
|
||||||
this.copayerIds = [];
|
this.copayerIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This follow Electrum convetion, as described in
|
|
||||||
* https://bitcointalk.org/index.php?topic=274182.0
|
|
||||||
*
|
|
||||||
* We should probably adopt the next standard once it's ready, as discussed in:
|
|
||||||
* http://sourceforge.net/p/bitcoin/mailman/message/32148600/
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
PublicKeyRing.Branch = function (index, isChange) {
|
|
||||||
// first 0 is for future use: could be copayerId.
|
|
||||||
return 'm/0/'+(isChange?1:0)+'/'+index;
|
|
||||||
};
|
|
||||||
|
|
||||||
PublicKeyRing.ID_BRANCH = 'm/100/0/0';
|
|
||||||
|
|
||||||
PublicKeyRing.fromObj = function (data) {
|
PublicKeyRing.fromObj = function (data) {
|
||||||
if (data instanceof PublicKeyRing) {
|
if (data instanceof PublicKeyRing) {
|
||||||
throw new Error('bad data format: Did you use .toObj()?');
|
throw new Error('bad data format: Did you use .toObj()?');
|
||||||
|
|
@ -109,13 +95,13 @@ PublicKeyRing.prototype._checkKeys = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype._newExtendedPublicKey = function () {
|
PublicKeyRing.prototype._newExtendedPublicKey = function () {
|
||||||
return new HK(this.network.name)
|
return new PrivateKey({networkName: this.network.name})
|
||||||
|
.deriveBIP45Branch()
|
||||||
.extendedPublicKeyString();
|
.extendedPublicKeyString();
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype._updateBip = function (index) {
|
PublicKeyRing.prototype._updateBip = function (index) {
|
||||||
var path = PublicKeyRing.ID_BRANCH;
|
var hk = this.copayersHK[index].derive(Structure.IdBranch);
|
||||||
var hk = this.copayersHK[index].derive(path);
|
|
||||||
this.copayerIds[index]= hk.eckey.public.toString('hex');
|
this.copayerIds[index]= hk.eckey.public.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -123,41 +109,41 @@ PublicKeyRing.prototype._setNicknameForIndex = function (index, nickname) {
|
||||||
this.nicknameFor[this.copayerIds[index]] = nickname;
|
this.nicknameFor[this.copayerIds[index]] = nickname;
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype.nicknameForIndex = function (index) {
|
PublicKeyRing.prototype.nicknameForIndex = function(index) {
|
||||||
return this.nicknameFor[this.copayerIds[index]];
|
return this.nicknameFor[this.copayerIds[index]];
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype.nicknameForCopayer = function (copayerId) {
|
PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) {
|
||||||
return this.nicknameFor[copayerId];
|
return this.nicknameFor[copayerId];
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype.addCopayer = function (newEpk, nickname) {
|
PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
||||||
if (this.isComplete())
|
if (this.isComplete())
|
||||||
throw new Error('already have all required key:' + this.totalCopayers);
|
throw new Error('PKR already has all required key:' + this.totalCopayers);
|
||||||
|
|
||||||
|
this.copayersHK.forEach(function(b){
|
||||||
|
if (b.extendedPublicKeyString() === newEpk)
|
||||||
|
throw new Error('PKR already has that key');
|
||||||
|
});
|
||||||
|
|
||||||
if (!newEpk) {
|
if (!newEpk) {
|
||||||
newEpk = this._newExtendedPublicKey();
|
newEpk = this._newExtendedPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.copayersHK.forEach(function(b){
|
var i = this.copayersHK.length;
|
||||||
if (b.extendedPublicKeyString() === newEpk)
|
|
||||||
throw new Error('already have that key');
|
|
||||||
});
|
|
||||||
|
|
||||||
var i=this.copayersHK.length;
|
|
||||||
var bip = new HK(newEpk);
|
var bip = new HK(newEpk);
|
||||||
this.copayersHK.push(bip);
|
this.copayersHK.push(bip);
|
||||||
this._updateBip(i);
|
this._updateBip(i);
|
||||||
if (nickname) {
|
if (nickname) {
|
||||||
this._setNicknameForIndex(i,nickname);
|
this._setNicknameForIndex(i, nickname);
|
||||||
}
|
}
|
||||||
return newEpk;
|
return newEpk;
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyRing.prototype.getPubKeys = function (index, isChange) {
|
PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
|
||||||
this._checkKeys();
|
this._checkKeys();
|
||||||
|
|
||||||
var path = PublicKeyRing.Branch(index, isChange);
|
var path = Structure.Branch(index, isChange);
|
||||||
var pubKeys = this.publicKeysCache[path];
|
var pubKeys = this.publicKeysCache[path];
|
||||||
if (!pubKeys) {
|
if (!pubKeys) {
|
||||||
pubKeys = [];
|
pubKeys = [];
|
||||||
|
|
|
||||||
43
js/models/core/Structure.js
Normal file
43
js/models/core/Structure.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
var imports = require('soop').imports();
|
||||||
|
|
||||||
|
function Structure() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki
|
||||||
|
* m / purpose' / cosigner_index / change / address_index
|
||||||
|
*/
|
||||||
|
var PURPOSE = 45;
|
||||||
|
var MAX_NON_HARDENED = 0x80000000 - 1;
|
||||||
|
|
||||||
|
var SHARED_INDEX = MAX_NON_HARDENED - 0;
|
||||||
|
var ID_INDEX = MAX_NON_HARDENED - 1;
|
||||||
|
|
||||||
|
var BIP45_PUBLIC_PREFIX = 'm/'+ PURPOSE+'\'';
|
||||||
|
Structure.BIP45_PUBLIC_PREFIX = BIP45_PUBLIC_PREFIX;
|
||||||
|
|
||||||
|
Structure.Branch = function(address_index, isChange, cosigner_index) {
|
||||||
|
var ret = 'm/'+
|
||||||
|
(typeof cosigner_index !== 'undefined'? cosigner_index: SHARED_INDEX)+'/'+
|
||||||
|
(isChange?1:0)+'/'+
|
||||||
|
address_index;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
Structure.FullBranch = function(address_index, isChange, cosigner_index) {
|
||||||
|
var sub = Structure.Branch(address_index, isChange, cosigner_index);
|
||||||
|
sub = sub.substring(2);
|
||||||
|
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||||
|
};
|
||||||
|
Structure.IdFullBranch = Structure.FullBranch(0, 0, ID_INDEX);
|
||||||
|
Structure.IdBranch = Structure.Branch(0, 0, ID_INDEX);
|
||||||
|
Structure.PURPOSE = PURPOSE;
|
||||||
|
Structure.MAX_NON_HARDENED = MAX_NON_HARDENED;
|
||||||
|
Structure.SHARED_INDEX = SHARED_INDEX;
|
||||||
|
Structure.ID_INDEX = ID_INDEX;
|
||||||
|
|
||||||
|
module.exports = require('soop')(Structure);
|
||||||
|
|
@ -628,7 +628,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, utxos, opts) {
|
||||||
|
|
||||||
var signRet;
|
var signRet;
|
||||||
if (priv) {
|
if (priv) {
|
||||||
b.sign(priv.getAll(pkr.addressIndex, pkr.changeAddressIndex));
|
var signed = b.sign(priv.getAll(pkr.addressIndex, pkr.changeAddressIndex));
|
||||||
}
|
}
|
||||||
var myId = this.getMyCopayerId();
|
var myId = this.getMyCopayerId();
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,6 @@ WalletFactory.prototype.fromObj = function(obj) {
|
||||||
console.log('## Decrypting'); //TODO
|
console.log('## Decrypting'); //TODO
|
||||||
var w = Wallet.fromObj(obj, this.storage, this.network, this.blockchain);
|
var w = Wallet.fromObj(obj, this.storage, this.network, this.blockchain);
|
||||||
w.verbose = this.verbose;
|
w.verbose = this.verbose;
|
||||||
// JIC: Add our key
|
|
||||||
try {
|
|
||||||
w.publicKeyRing.addCopayer(
|
|
||||||
w.privateKey.getExtendedPublicKeyString()
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// No really an error, just to be sure.
|
|
||||||
}
|
|
||||||
this.log('### WALLET OPENED:', w.id);
|
this.log('### WALLET OPENED:', w.id);
|
||||||
return w;
|
return w;
|
||||||
};
|
};
|
||||||
|
|
@ -107,7 +99,9 @@ WalletFactory.prototype.create = function(opts) {
|
||||||
requiredCopayers: requiredCopayers,
|
requiredCopayers: requiredCopayers,
|
||||||
totalCopayers: totalCopayers,
|
totalCopayers: totalCopayers,
|
||||||
});
|
});
|
||||||
opts.publicKeyRing.addCopayer(opts.privateKey.getExtendedPublicKeyString(), opts.nickname);
|
opts.publicKeyRing.addCopayer(
|
||||||
|
opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
|
||||||
|
opts.nickname);
|
||||||
this.log('\t### PublicKeyRing Initialized');
|
this.log('\t### PublicKeyRing Initialized');
|
||||||
|
|
||||||
opts.txProposals = opts.txProposals || new TxProposals({
|
opts.txProposals = opts.txProposals || new TxProposals({
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ describe('PrivateKey model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should derive priv keys', function () {
|
it('should derive priv keys', function () {
|
||||||
var w = new PrivateKey(config);
|
var pk = new PrivateKey(config);
|
||||||
for(var j=0; j<2; j++) {
|
for(var j=0; j<2; j++) {
|
||||||
for(var i=0; i<3; i++) {
|
for(var i=0; i<3; i++) {
|
||||||
var wk = w.get(i,j);
|
var wk = pk.get(i,j);
|
||||||
should.exist(wk);
|
should.exist(wk);
|
||||||
var o=wk.storeObj();
|
var o=wk.storeObj();
|
||||||
should.exist(o);
|
should.exist(o);
|
||||||
|
|
|
||||||
37
test/test.Structure.js
Normal file
37
test/test.Structure.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var chai = chai || require('chai');
|
||||||
|
var should = chai.should();
|
||||||
|
var bitcore = bitcore || require('bitcore');
|
||||||
|
var copay = copay || require('../copay');
|
||||||
|
var Structure = require('../js/models/core/Structure');
|
||||||
|
|
||||||
|
describe('Structure model', function() {
|
||||||
|
it('should have the correct constants', function () {
|
||||||
|
Structure.MAX_NON_HARDENED.should.equal(Math.pow(2,31) - 1);
|
||||||
|
Structure.SHARED_INDEX.should.equal(Structure.MAX_NON_HARDENED);
|
||||||
|
Structure.ID_INDEX.should.equal(Structure.SHARED_INDEX - 1);
|
||||||
|
Structure.IdFullBranch.should.equal('m/45\'/2147483646/0/0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the correct branches', function () {
|
||||||
|
// shared branch (no cosigner index specified)
|
||||||
|
Structure.FullBranch(0,false).should.equal('m/45\'/2147483647/0/0');
|
||||||
|
|
||||||
|
// copayer 0, address 0, external address (receiving)
|
||||||
|
Structure.FullBranch(0,false,0).should.equal('m/45\'/0/0/0');
|
||||||
|
|
||||||
|
// copayer 0, address 10, external address (receiving)
|
||||||
|
Structure.FullBranch(0,false,10).should.equal('m/45\'/10/0/0');
|
||||||
|
|
||||||
|
// copayer 0, address 0, internal address (change)
|
||||||
|
Structure.FullBranch(0,true,0).should.equal('m/45\'/0/1/0');
|
||||||
|
|
||||||
|
// copayer 0, address 10, internal address (change)
|
||||||
|
Structure.FullBranch(10,true,0).should.equal('m/45\'/0/1/10');
|
||||||
|
|
||||||
|
// copayer 7, address 10, internal address (change)
|
||||||
|
Structure.FullBranch(10,true,7).should.equal('m/45\'/7/1/10');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -42,7 +42,7 @@ var createPKR = function (bip32s) {
|
||||||
for(var i=0; i<5; i++) {
|
for(var i=0; i<5; i++) {
|
||||||
if (bip32s) {
|
if (bip32s) {
|
||||||
var b=bip32s[i];
|
var b=bip32s[i];
|
||||||
w.addCopayer(b?b.getExtendedPublicKeyString():null);
|
w.addCopayer(b?b.deriveBIP45Branch().extendedPublicKeyString():null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
w.addCopayer();
|
w.addCopayer();
|
||||||
|
|
@ -72,7 +72,7 @@ describe('TxProposals model', function() {
|
||||||
var priv = new PrivateKey(config);
|
var priv = new PrivateKey(config);
|
||||||
var priv2 = new PrivateKey(config);
|
var priv2 = new PrivateKey(config);
|
||||||
var priv3 = new PrivateKey(config);
|
var priv3 = new PrivateKey(config);
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
var isChange=0;
|
var isChange=0;
|
||||||
var index=0;
|
var index=0;
|
||||||
var pkr = createPKR([priv, priv2, priv3]);
|
var pkr = createPKR([priv, priv2, priv3]);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ describe('Wallet model', function() {
|
||||||
requiredCopayers: c.requiredCopayers,
|
requiredCopayers: c.requiredCopayers,
|
||||||
totalCopayers: c.totalCopayers,
|
totalCopayers: c.totalCopayers,
|
||||||
});
|
});
|
||||||
c.publicKeyRing.addCopayer(c.privateKey.getExtendedPublicKeyString());
|
var copayerEPK = c.privateKey.deriveBIP45Branch().extendedPublicKeyString()
|
||||||
|
c.publicKeyRing.addCopayer(copayerEPK);
|
||||||
|
|
||||||
c.txProposals = new copay.TxProposals({
|
c.txProposals = new copay.TxProposals({
|
||||||
networkName: c.networkName,
|
networkName: c.networkName,
|
||||||
|
|
@ -102,10 +103,10 @@ describe('Wallet model', function() {
|
||||||
for(var i=0; i<4; i++) {
|
for(var i=0; i<4; i++) {
|
||||||
if (privateKeys) {
|
if (privateKeys) {
|
||||||
var k=privateKeys[i];
|
var k=privateKeys[i];
|
||||||
pkr.addCopayer(k?k.getExtendedPublicKeyString():null);
|
pkr.addCopayer(k?k.deriveBIP45Branch().extendedPublicKeyString():null);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
pkr.addCopayer();
|
pkr.addCopayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pkr.generateAddress(true);
|
pkr.generateAddress(true);
|
||||||
pkr.generateAddress(true);
|
pkr.generateAddress(true);
|
||||||
|
|
@ -125,19 +126,19 @@ describe('Wallet model', function() {
|
||||||
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
|
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
|
||||||
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
|
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
|
||||||
|
|
||||||
w.createTxSync(
|
var ntxid = w.createTxSync(
|
||||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||||
'123456789',
|
'123456789',
|
||||||
unspentTest
|
unspentTest
|
||||||
);
|
);
|
||||||
|
|
||||||
var t = w.txProposals;
|
var t = w.txProposals;
|
||||||
var k = Object.keys(t.txps)[0];
|
var txp = t.txps[ntxid];
|
||||||
var tx = t.txps[k].builder.build();
|
var tx = txp.builder.build();
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
tx.isComplete().should.equal(false);
|
tx.isComplete().should.equal(false);
|
||||||
Object.keys(t.txps[k].signedBy).length.should.equal(1);
|
Object.keys(txp.seenBy).length.should.equal(1);
|
||||||
Object.keys(t.txps[k].seenBy).length.should.equal(1);
|
Object.keys(txp.signedBy).length.should.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#addressIsOwn', function () {
|
it('#addressIsOwn', function () {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue