diff --git a/js/models/wallet.js b/js/models/wallet.js new file mode 100644 index 000000000..02714fb1c --- /dev/null +++ b/js/models/wallet.js @@ -0,0 +1,95 @@ +'use strict'; +var imports = require('soop').imports(); +var bitcore = require('bitcore'); +var BIP32 = bitcore.BIP32; + +var Storage = imports.Storage || require('./Storage'); + +/* + * This follow Electrum convetion, as described on + * https://bitcointalk.org/index.php?topic=274182.0 + */ + +var PUBLIC_BRANCH = 'm/0/'; +var CHANGE_BRANCH = 'm/1/'; + +function Wallet(opts) { + opts = opts || {}; + + this.network = opts.network === 'livenet' ? + bitcore.networks.livenet : bitcore.networks.testnet; + + this.neededCosigners = opts.neededCosigners || 3; + this.totalCosigners = opts.totalCosigners || 5; + + this.dirty = 1; + this.cosignersBIP = []; + this.bip32 = new BIP32(opts.bytes || this.network.name); +} + + + +Wallet.read = function (BIP38password) { +}; + +Wallet.prototype.registeredCosigners = function () { + + // 1 is self. + return 1 + this.cosignersBIP.length; +}; + +Wallet.prototype.getExtendedPrivKey = function (BIP38password) { + if (!this.bip32) + throw new Error('no priv key defined on the wallet'); + + return this.bip32.extended_private_key_string(); +}; + + +Wallet.prototype.getExtendedPubKey = function () { + return this.bip32.extended_public_key_string(); +}; + + +// should receive an array also? +Wallet.prototype.addCosignerExtendedPubKey = function (newEpk) { + + if (this.haveAllNeededPubKeys()) + throw new Error('already have all needed key:' + this.totalCosigners); + + if (this.getExtendedPubKey() === newEpk) + throw new Error('already have that key (self kehy)'); + + + this.cosignersBIP.forEach(function(b){ + if (b.getExtendedPubKey() === newEpk) + throw new Error('already have that key'); + }); + + this.cosignersBIP.push(new Wallet({bytes:newEpk, network: this.network.name } )); +}; + + +Wallet.prototype.haveAllNeededPubKeys = function () { + return this.registeredCosigners() === this.totalCosigners; +}; + + +Wallet.prototype.getChangeAddress = function (index) { + + //index can be 0, 1, 2, etc. + if (! this.haveAllNeededPubKeys() ) + throw new Error('cosigners pub key missing'); +}; + + +Wallet.prototype.store = function () { +}; + + +// Input: Bitcore's Transaction, sign with ownPK +// return partially signed or fully signed tx +Wallet.prototype.signTx = function (tx, BIP38password) { +}; + +module.exports = require('soop')(Wallet); diff --git a/package.json b/package.json index bac19d810..7d2565c1e 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,16 @@ "url": "https://github.com/bitpay/cosign/issues" }, "homepage": "https://github.com/bitpay/cosign", + "dependencies": { + "bitcore": "0.1.8", + "soop": "0.1.5" + }, "devDependencies": { "grunt-cli": "~0.1.13", "karma": "~0.12.1", "karma-chrome-launcher": "~0.1.2", "mocha": "~1.18.2", - "karma-mocha": "~0.1.3" + "karma-mocha": "~0.1.3", + "chai": "~1.9.0" } } diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 000000000..ec648f252 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +-R spec diff --git a/test/test.wallet.js b/test/test.wallet.js new file mode 100644 index 000000000..5834860e4 --- /dev/null +++ b/test/test.wallet.js @@ -0,0 +1,84 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var bitcore = bitcore || require('../node_modules/bitcore'); + +var cosign = cosign || {}; + +var fakeStorage = {}; // TODO + +var Wallet = cosign.Wallet || require('soop').load('../js/models/Wallet', {Storage: fakeStorage}); + + +var config = { + network:'livenet', +}; + +describe('Wallet model', function() { + + it('should create an instance (livenet)', function () { + var w = new Wallet({ + network: config.network + }); + should.exist(w); + w.network.name.should.equal('livenet'); + }); + it('should create an instance (testnet)', function () { + var w2 = new Wallet(); + should.exist(w2); + w2.network.name.should.equal('testnet'); + }); + + it('should throw master priv key', function () { + var w2 = new Wallet(config); + should.exist(w2); + + w2.getExtendedPrivKey.bind().should.throw(); + }); + + + it('should create an master priv key', function () { + var w2 = new Wallet(config); + should.exist(w2); + should.exist(w2.getExtendedPrivKey()); + }); + + + it('should create an master pub key', function () { + var w2 = new Wallet(config); + should.exist(w2); + should.exist(w2.getExtendedPubKey()); + }); + + it('should fail to generate shared pub keys', function () { + var w2 = new Wallet(config); + should.exist(w2); + w2.getChangeAddress.bind(0).should.throw(); + + w2.registeredCosigners().should.equal(1); + w2.haveAllNeededPubKeys().should.equal(false); + }); + + it('should add and check when adding shared pub keys', function () { + var w = new Wallet(config); + should.exist(w); + + var cosigners = []; + for(var i=0; i<4; i++) { + var c = new Wallet(config); + w.haveAllNeededPubKeys().should.equal(false); + + w.addCosignerExtendedPubKey(c.getExtendedPubKey()); + cosigners.push(c); + } + w.haveAllNeededPubKeys().should.equal(true); + w.addCosignerExtendedPubKey.bind(w.getExtendedPubKey()).should.throw(); + w.addCosignerExtendedPubKey.bind(cosigners[0].getExtendedPubKey()).should.throw(); + w.addCosignerExtendedPubKey.bind((new Wallet(config)).getExtendedPubKey()).should.throw(); + }); + + +}); + +