From f139ce2d91e40365182ee8d07351d1512d05d220 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Tue, 6 May 2014 14:58:23 +0400 Subject: [PATCH] wallet: multisig signing --- lib/bcoin.js | 1 + lib/bcoin/script.js | 72 +++++++++++++++++++++++++++++++++++++++----- lib/bcoin/tx-pool.js | 0 lib/bcoin/tx.js | 11 ++++++- lib/bcoin/utils.js | 3 +- lib/bcoin/wallet.js | 21 +++++++------ test/wallet-test.js | 27 +++++++++++++++++ 7 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 lib/bcoin/tx-pool.js diff --git a/lib/bcoin.js b/lib/bcoin.js index 9c5a8c94..6ef8958e 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -7,6 +7,7 @@ bcoin.bloom = require('./bcoin/bloom'); bcoin.protocol = require('./bcoin/protocol'); bcoin.script = require('./bcoin/script'); bcoin.tx = require('./bcoin/tx'); +bcoin.txPool = require('./bcoin/tx-pool'); bcoin.block = require('./bcoin/block'); bcoin.chain = require('./bcoin/chain'); bcoin.wallet = require('./bcoin/wallet'); diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index bf88b792..ab4d6117 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -112,8 +112,8 @@ script.subscript = function subscript(s) { }; script.execute = function execute(s, stack, tx) { - for (var i = 0; i < s.length; i++) { - var o = s[i]; + for (var pc = 0; pc < s.length; pc++) { + var o = s[pc]; if (Array.isArray(o)) { stack.push(o); } else if (o === 'dup') { @@ -148,13 +148,67 @@ script.execute = function execute(s, stack, tx) { if (type !== 1) return false; - var res = bcoin.ecdsa.verify(tx, sig, pub); + var res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), pub); if (o === 'checksigverify') { if (!res) return false; } else { stack.push(res ? [ 1 ] : []); } + } else if (o === 'checkmultisigverify' || o === 'checkmultisig') { + if (!tx || stack.length < 3) + return false; + + var n = stack.pop(); + if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3)) + return false; + n = n[0]; + + if (stack.length < n + 1) + return false; + + var keys = []; + for (var i = 0; i < n; i++) { + var key = stack.pop(); + if (!(33 <= key.length && key.length <= 65)) + return false; + + keys.push(key); + } + + var m = stack.pop(); + if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) + return false; + m = m[0]; + + if (stack.length < m + 1) + return false; + + // Get signatures + var succ = 0; + for (var i = 0, j = 0; i < m, j < n; i++) { + var sig = stack.pop(); + var type = sig[sig.length - 1]; + if (type !== 1) + return false; + + var res = false; + for (; !res && j < n; j++) + res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), keys[j]); + if (res) + succ++; + } + + // Extra value + stack.pop(); + + var res = succ >= m; + if (o === 'checkmultisigverify') { + if (!res) + return false; + } else { + stack.push(res ? [ 1 ] : []); + } } else { // Unknown operation return false; @@ -180,7 +234,7 @@ script.isPubkeyhash = function isPubkeyhash(s, hash) { return utils.isEqual(s[2], hash); else return s[2]; -} +}; script.isMultisig = function isMultisig(s, key) { if (s.length < 4) @@ -191,14 +245,18 @@ script.isMultisig = function isMultisig(s, key) { return false; m = m[0]; - if (m + 3 !== s.length || s[s.length - 1] !== 'checkmultisig') + if (s[s.length - 1] !== 'checkmultisig') return false; var n = s[s.length - 2]; if (!Array.isArray(n) || n.length !== 1) return false; + n = n[0]; - var keys = s.slice(1, m); + if (n + 3 !== s.length) + return false; + + var keys = s.slice(1, 1 + n); var isArray = keys.every(function(k) { return Array.isArray(k); }); @@ -211,4 +269,4 @@ script.isMultisig = function isMultisig(s, key) { return m === keys.filter(function(k) { return utils.isEqual(k, key); }).length; -} +}; diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js new file mode 100644 index 00000000..e69de29b diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 0139a556..aa7aeabc 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -98,8 +98,17 @@ TX.prototype.out = function out(output, value) { var script = output.script ? output.script.slice() : []; + // Multisig script if given addresses + if (Array.isArray(output.keys || output.address)) { + var keys = output.keys || output.address; + script = [ + [ output.minSignatures || keys.length ] + ].concat( + keys, + [ [ keys.length ], 'checkmultisig' ] + ); // Default script if given address - if (output.address) { + } else if (output.address) { script = [ 'dup', 'hash160', diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index db4f0836..3c4177c7 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -99,7 +99,8 @@ utils.fromBase58 = function fromBase58(str) { var res = new bn(0); for (var i = zeroes; i < str.length; i++) { var c = base58.indexOf(str[i]); - assert(c >= 0 && c < 58); + if (!(c >= 0 && c < 58)) + return []; q *= 58; w *= 58; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 71119db8..35a3becd 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -77,20 +77,20 @@ Wallet.addr2hash = function addr2hash(addr) { addr = utils.fromBase58(addr); if (addr.length !== 25) - return false; + return []; if (addr[0] !== 0) - return false; + return []; var chk = utils.checksum(addr.slice(0, -4)); if (utils.readU32(chk, 0) !== utils.readU32(addr, 21)) - return false; + return []; return addr.slice(1, -4); }; Wallet.prototype.validateAddress = function validateAddress(addr) { var p = Wallet.addr2hash(addr); - return !!p; + return p.length !== 0; }; Wallet.validateAddress = Wallet.prototype.validateAddress; @@ -128,14 +128,17 @@ Wallet.prototype.sign = function sign(tx, type) { // Add signature script to each input inputs.forEach(function(input, i) { var s = input.out.tx.getSubscript(input.out.index); - var hash = tx.subscriptHash(i, s, type); var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); + signature = signature.concat(bcoin.protocol.constants.hashType[type]); - input.script = [ - signature.concat(bcoin.protocol.constants.hashType[type]), - pub - ]; + if (bcoin.script.isPubkeyhash(s)) { + input.script = [ signature, pub ]; + return; + } + + // Multisig + input.script = [ [], signature ]; }, this); return inputs.length; diff --git a/test/wallet-test.js b/test/wallet-test.js index 1562d7b8..c14a8138 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -43,4 +43,31 @@ describe('Wallet', function() { w.sign(tx); assert(tx.verify()); }); + + it('should multisign/verify TX', function() { + var w = bcoin.wallet(); + + // Input transcation + var src = bcoin.tx({ + outputs: [{ + value: 5460 * 2, + minSignatures: 1, + address: [ w.getPublicKey(), w.getPublicKey().concat(1) ] + }, { + value: 5460 * 2, + address: w.getAddress() + 'x' + }] + }); + assert(w.own(src)); + assert.equal(w.own(src).reduce(function(acc, out) { + return acc.iadd(out.value); + }, new bn(0)).toString(10), 5460 * 2); + + var tx = bcoin.tx() + .input(src, 0) + .out(w.getAddress(), 5460); + + w.sign(tx); + assert(tx.verify()); + }); });