From c251def27cfb0c1134cbaf4355b37b9d0fa36f5c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 10 Dec 2015 17:43:48 -0800 Subject: [PATCH] wallet: multisig key handling. prefixes. to/fromJSON. --- lib/bcoin/wallet.js | 127 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 24 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 92fe137f..8aaca748 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -28,6 +28,7 @@ function Wallet(options, passphrase) { this.compressed = typeof options.compressed !== 'undefined' ? options.compressed : true; this.storage = options.storage; + this.label = options.label || ''; this.key = null; this.loaded = false; this.lastTs = 0; @@ -64,7 +65,7 @@ function Wallet(options, passphrase) { } this.type = 'pubkey'; - this.pubkeys = []; + this.keys = []; this.m = 1; this.n = 1; @@ -118,30 +119,25 @@ Wallet.prototype.multisig = function multisig(options) { var pub = this.getOwnPublicKey(); options.type = options.type || options.prefix; - options.keys = options.keys || options.pubkeys; + options.keys = options.keys || options.pubkeys || []; this.type = options.type || 'pubkey'; - this.pubkeys = (options.keys || []).map(utils.toKeyArray); + // this.keys = (options.keys || []).map(utils.toKeyArray); + this.keys = []; this.m = options.m || 1; this.n = options.n || 1; this.nmax = this.type === 'script' ? (this.compressed ? 15 : 7) : 3; - var hasOwn = this.pubkeys.some(function(key) { - return utils.isEqual(key, pub); - }); + this.addKey(pub); - if (!hasOwn) - this.pubkeys.push(pub); - - // Keys need to be in a predictable order. - this.pubkeys = this.pubkeys.sort(function(a, b) { - return new bn(a).cmp(new bn(b)) > 0; - }); + options.keys.forEach(function(key) { + this.addKey(key); + }, this); // Use p2sh multisig by default - if (!options.type && this.pubkeys.length > 1) + if (!options.type && this.keys.length > 1) this.type = 'script'; if (this.m < 1 || this.m > this.n) @@ -150,8 +146,49 @@ Wallet.prototype.multisig = function multisig(options) { if (this.n < 1 || this.n > this.nmax) throw new Error('n ranges between 1 and ' + this.nmax); - if (this.pubkeys.length < this.m) - throw new Error(this.m + ' public keys required'); + if (this.keys.length > this.n) + throw new Error('No more than ' + this.n + ' are necessary'); + + // if (this.keys.length < this.m) + // throw new Error(this.m + ' public keys required'); +}; + +Wallet.prototype.addKey = function addKey(key) { + key = utils.toKeyArray(key); + + var has = this.keys.some(function(k) { + return utils.isEqual(k, key); + }); + + if (has) + return; + + this.keys.push(key); + + // Keys need to be in a predictable order. + this.keys = this.keys.sort(function(a, b) { + return new bn(a).cmp(new bn(b)) > 0; + }); +}; + +Wallet.prototype.removeKey = function removeKey(key) { + key = utils.toKeyArray(key); + + var index = this.keys.map(function(key, i) { + return utils.isEqual(key, pub) ? i : null; + }).filter(function(i) { + return i !== null; + })[0]; + + if (index == null) + return; + + this.keys.splice(index, 1); + + // Keys need to be in a predictable order. + this.keys = this.keys.sort(function(a, b) { + return new bn(a).cmp(new bn(b)) > 0; + }); }; Wallet.prototype.derive = function derive() { @@ -223,7 +260,7 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { }; Wallet.prototype.getPublicKeys = function() { - var keys = this.pubkeys.slice().map(utils.toKeyArray); + var keys = this.keys.slice().map(utils.toKeyArray); // Keys need to be in a predictable order. keys = keys.sort(function(a, b) { @@ -267,7 +304,20 @@ Wallet.hash2addr = function hash2addr(hash, prefix) { return utils.toBase58(addr); }; +Wallet.__defineGetter__('prefixes', function() { + if (Wallet._prefixes) return Wallet._prefixes; + Wallet._prefixes = ['pubkey', 'script'].reduce(function(out, prefix) { + var ch = Wallet.hash2addr(bcoin.utils.ripesha([]), prefix)[0]; + out[ch] = prefix; + return out; + }, {}); + return Wallet._prefixes; +}); + Wallet.addr2hash = function addr2hash(addr, prefix) { + if (prefix == null && typeof addr === 'string') + prefix = Wallet.prefixes[addr[0]]; + if (!Array.isArray(addr)) addr = utils.fromBase58(addr); @@ -415,29 +465,52 @@ Wallet.prototype.fill = function fill(tx, cb) { return tx; }; -Wallet.prototype.toJSON = function toJSON() { +Wallet.prototype.toJSON = function toJSON(encrypt) { + encrypt = encrypt || function(msg) { + return msg; + }; return { v: 1, type: 'wallet', network: network.type, + encrypted: encrypt ? true : false, + label: this.label, + address: this.getAddress(), + balance: utils.toBTC(this.balance()), pub: this.getOwnPublicKey('base58'), - priv: this.getPrivateKey('base58'), + priv: encrypt + ? encrypt(this.getPrivateKey('base58')) + : this.getPrivateKey('base58'), tx: this.tx.toJSON(), - multisig: { + ntx: this.tx.all().length, + hd: this.hd ? { + // seed: this.hd.seed ? { + // mnemonic: this.hd.seed.mnemonic, + // passphrase: this.hd.seed.passphrase + // } : undefined, + depth: this.hd.data.depth, + parentFingerPrint: utils.toHex(this.hd.data.parentFingerPrint), + childIndex: this.hd.data.childIndex, + chainCode: utils.toHex(this.hd.data.chainCode) + } : undefined, + multisig: this.n > 1 ? { type: this.type, - keys: this.pubkeys.map(utils.toHex), + keys: this.keys.map(utils.toHex), m: this.m, n: this.n - } + } : undefined }; }; -Wallet.fromJSON = function fromJSON(json) { +Wallet.fromJSON = function fromJSON(json, decrypt) { assert.equal(json.v, 1); assert.equal(json.type, 'wallet'); if (json.network) assert.equal(json.network, network.type); + if (decrypt) + json.priv = decrypt(json.priv); + var priv; var pub; var compressed; @@ -461,10 +534,16 @@ Wallet.fromJSON = function fromJSON(json) { compressed = pub[0] !== 0x04; } - if (json.multisig && json.multisig.keys) + if (json.multisig) json.multisig.keys = json.multisig.keys.map(utils.toKeyArray); + if (json.hd) { + json.hd.privateKey = priv; + priv = new hd.priv(json.hd); + } + var w = new Wallet({ + label: json.label, priv: priv, pub: pub, compressed: compressed,