public key ring, symmetric for all holders

This commit is contained in:
Matias Alejo Garcia 2014-04-04 17:16:10 -03:00
commit 7492599552
2 changed files with 57 additions and 63 deletions

View file

@ -1,4 +1,7 @@
'use strict'; 'use strict';
var imports = require('soop').imports(); var imports = require('soop').imports();
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var BIP32 = bitcore.BIP32; var BIP32 = bitcore.BIP32;
@ -25,7 +28,6 @@ var storage = Storage.default();
var PUBLIC_BRANCH = 'm/0/'; var PUBLIC_BRANCH = 'm/0/';
var CHANGE_BRANCH = 'm/1/'; var CHANGE_BRANCH = 'm/1/';
function PublicKeyRing(opts) { function PublicKeyRing(opts) {
opts = opts || {}; opts = opts || {};
@ -38,8 +40,7 @@ function PublicKeyRing(opts) {
this.id = opts.id || PublicKeyRing.getRandomId(); this.id = opts.id || PublicKeyRing.getRandomId();
this.dirty = 1; this.dirty = 1;
this.copayersWallets = []; this.copayersBIP32 = [];
this.bip32 = new BIP32(opts.bytes || this.network.name);
this.changeAddressIndex=0; this.changeAddressIndex=0;
this.addressIndex=0; this.addressIndex=0;
@ -83,8 +84,10 @@ PublicKeyRing.read = function (id, passphrase) {
w.requiredCopayers = data.neededCopayers; w.requiredCopayers = data.neededCopayers;
w.totalCopayers = data.totalCopayers; w.totalCopayers = data.totalCopayers;
w.copayersWallets = data.copayersExtPubKeys.map( function (pk) {
return new PublicKeyRing({bytes:pk, network: w.network.name}); // this.bip32 = ;
w.copayersBIP32 = data.copayersExtPubKeys.map( function (pk) {
return new BIP32(pk);
}); });
w.dirty = 0; w.dirty = 0;
@ -98,12 +101,13 @@ PublicKeyRing.prototype.serialize = function () {
network: this.network.name, network: this.network.name,
requiredCopayers: this.neededCopayers, requiredCopayers: this.neededCopayers,
totalCopayers: this.totalCopayers, totalCopayers: this.totalCopayers,
copayersExtPubKeys: this.copayersWallets.map( function (b) { copayersExtPubKeys: this.copayersBIP32.map( function (b) {
return b.getMasterExtendedPubKey(); return b.extendedPublicKeyString();
}), }),
}); });
}; };
PublicKeyRing.prototype.store = function (passphrase) { PublicKeyRing.prototype.store = function (passphrase) {
if (!this.id) if (!this.id)
@ -116,16 +120,9 @@ PublicKeyRing.prototype.store = function (passphrase) {
}; };
PublicKeyRing.prototype.registeredCopayers = function () { PublicKeyRing.prototype.registeredCopayers = function () {
if (! this.copayersWallets) return 1; return this.copayersBIP32.length;
// 1 is self.
return 1 + this.copayersWallets.length;
}; };
PublicKeyRing.prototype.getMasterExtendedPubKey = function () {
return this.bip32.extendedPublicKeyString();
};
PublicKeyRing.prototype.haveAllRequiredPubKeys = function () { PublicKeyRing.prototype.haveAllRequiredPubKeys = function () {
@ -139,62 +136,69 @@ PublicKeyRing.prototype._checkKeys = function() {
}; };
// should receive an array also? PublicKeyRing.prototype._newExtendedPublicKey = function () {
PublicKeyRing.prototype.addCopayerExtendedPubKey = function (newEpk) { return new BIP32(this.network.name)
.extendedPublicKeyString();
};
PublicKeyRing.prototype.addCopayer = function (newEpk) {
if (this.haveAllRequiredPubKeys()) if (this.haveAllRequiredPubKeys())
throw new Error('already have all required key:' + this.totalCopayers); throw new Error('already have all required key:' + this.totalCopayers);
if (this.getMasterExtendedPubKey() === newEpk) if (!newEpk) {
throw new Error('already have that key (self key)'); newEpk = this._newExtendedPublicKey();
}
this.copayersBIP32.forEach(function(b){
this.copayersWallets.forEach(function(b){ if (b.extendedPublicKeyString() === newEpk)
if (b.getMasterExtendedPubKey() === newEpk)
throw new Error('already have that key'); throw new Error('already have that key');
}); });
this.copayersWallets.push(new PublicKeyRing({bytes:newEpk, network: this.network.name } )); this.copayersBIP32.push(new BIP32(newEpk));
this.dirty = 1; this.dirty = 1;
return newEpk;
}; };
PublicKeyRing.prototype.getPubKey = function (index,isChange) {
var path = (isChange ? CHANGE_BRANCH : PUBLIC_BRANCH) + index;
var bip32 = this.bip32.derive(path);
var pub = bip32.eckey.public;
return pub;
};
PublicKeyRing.prototype.getCopayersPubKeys = function (index, isChange) { PublicKeyRing.prototype.getCopayersPubKeys = function (index, isChange) {
this._checkKeys(); this._checkKeys();
var pubKeys = []; var pubKeys = [];
var l = this.copayersWallets.length; var l = this.copayersBIP32.length;
for(var i=0; i<l; i++) { for(var i=0; i<l; i++) {
pubKeys[i] = this.copayersWallets[i].getPubKey(index, isChange); var path = (isChange ? CHANGE_BRANCH : PUBLIC_BRANCH) + index;
var bip32 = this.copayersBIP32[i].derive(path);
pubKeys[i] = bip32.eckey.public;
} }
return pubKeys; return pubKeys;
}; };
PublicKeyRing.prototype.getAddress = function (index, isChange) { PublicKeyRing.prototype._checkIndexRange = function (index, isChange) {
if ( (isChange && index > this.changeAddressIndex) ||
if ( (isChange && index > this.changeAddressIndex) (!isChange && index > this.addressIndex)) {
|| (!isChange && index > this.addressIndex)) {
log('Out of bounds at getAddress: Index %d isChange: %d', index, isChange); log('Out of bounds at getAddress: Index %d isChange: %d', index, isChange);
throw new Error('index out of bound'); throw new Error('index out of bound');
} }
};
PublicKeyRing.prototype.getRedeemScript = function (index, isChange) {
this._checkIndexRange(index, isChange);
var pubKeys = this.getCopayersPubKeys(); var pubKeys = this.getCopayersPubKeys();
var version = this.network.addressScript;
var script = Script.createMultisig(this.requiredCopayers, pubKeys); var script = Script.createMultisig(this.requiredCopayers, pubKeys);
var buf = script.buffer; return script;
var hash = coinUtil.sha256ripe160(buf); };
PublicKeyRing.prototype.getAddress = function (index, isChange) {
this._checkIndexRange(index, isChange);
var script = this.getRedeemScript(index,isChange);
var hash = coinUtil.sha256ripe160(script.getBuffer());
var version = this.network.addressScript;
var addr = new Address(version, hash); var addr = new Address(version, hash);
var addrStr = addr.as('base58'); return addr.as('base58');
return addrStr;
}; };
//generate a new address, update index. //generate a new address, update index.

View file

@ -16,24 +16,20 @@ var config = {
network:'livenet', network:'livenet',
}; };
var createW = function (network, bytes) { var createW = function (network) {
var config = { var config = {
network: network || 'livenet', network: network || 'livenet',
}; };
if (bytes) config.bytes = bytes;
var w = new PublicKeyRing(config); var w = new PublicKeyRing(config);
should.exist(w); should.exist(w);
var copayers = []; var copayers = [];
for(var i=0; i<4; i++) { for(var i=0; i<5; i++) {
delete config['bytes'];
var c = new PublicKeyRing(config);
w.haveAllRequiredPubKeys().should.equal(false); w.haveAllRequiredPubKeys().should.equal(false);
var newEpk = w.addCopayer();
w.addCopayerExtendedPubKey(c.getMasterExtendedPubKey()); copayers.push(newEpk);
copayers.push(c);
} }
return {w:w, copayers: copayers}; return {w:w, copayers: copayers};
@ -54,17 +50,11 @@ describe('PublicKeyRing model', function() {
w2.network.name.should.equal('testnet'); w2.network.name.should.equal('testnet');
}); });
it('should create an master pub key', function () {
var w2 = new PublicKeyRing(config);
should.exist(w2);
should.exist(w2.getMasterExtendedPubKey());
});
it('should fail to generate shared pub keys wo extended key', function () { it('should fail to generate shared pub keys wo extended key', function () {
var w2 = new PublicKeyRing(config); var w2 = new PublicKeyRing(config);
should.exist(w2); should.exist(w2);
w2.registeredCopayers().should.equal(1); w2.registeredCopayers().should.equal(0);
w2.haveAllRequiredPubKeys().should.equal(false); w2.haveAllRequiredPubKeys().should.equal(false);
w2.getAddress.bind(false).should.throw(); w2.getAddress.bind(false).should.throw();
@ -76,9 +66,9 @@ describe('PublicKeyRing model', function() {
var copayers = k.copayers; var copayers = k.copayers;
w.haveAllRequiredPubKeys().should.equal(true); w.haveAllRequiredPubKeys().should.equal(true);
w.addCopayerExtendedPubKey.bind(w.getMasterExtendedPubKey()).should.throw(); w.addCopayer.bind().should.throw();
w.addCopayerExtendedPubKey.bind(copayers[0].getMasterExtendedPubKey()).should.throw(); for(var i =0; i<5; i++)
w.addCopayerExtendedPubKey.bind((new PublicKeyRing(config)).getMasterExtendedPubKey()).should.throw(); w.addCopayer.bind(copayers[i]).should.throw();
}); });
it('show be able to store and retrieve', function () { it('show be able to store and retrieve', function () {
@ -93,9 +83,9 @@ describe('PublicKeyRing model', function() {
var w2 = PublicKeyRing.read(ID); var w2 = PublicKeyRing.read(ID);
w2.haveAllRequiredPubKeys().should.equal(true); w2.haveAllRequiredPubKeys().should.equal(true);
w2.addCopayerExtendedPubKey.bind(w.getMasterExtendedPubKey()).should.throw(); w2.addCopayer.bind().should.throw();
w2.addCopayerExtendedPubKey.bind(copayers[0].getMasterExtendedPubKey()).should.throw(); for(var i =0; i<5; i++)
w2.addCopayerExtendedPubKey.bind((new PublicKeyRing(config)).getMasterExtendedPubKey()).should.throw(); w2.addCopayer.bind(copayers[i]).should.throw();
}); });