diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index d0785fac..8db84dd4 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -1,6 +1,7 @@ var bcoin = require('../bcoin'); var constants = bcoin.protocol.constants; var utils = bcoin.utils; +var assert = bcoin.utils.assert; var script = exports; script.decode = function decode(s) { @@ -129,14 +130,15 @@ script.verify = function verify(hash, sig, pub) { }; script.execute = function execute(s, stack, hasher) { - if (s.length > 10000) { + if (s.length > 10000) return false; - } for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; if (Array.isArray(o)) { stack.push(o); + } else if (typeof o === 'number' && o >= 1 && o <= 16) { + stack.push([o]); } else if (o === 'dup') { if (stack.length === 0) return false; @@ -247,21 +249,27 @@ script.execute = function execute(s, stack, hasher) { return true; }; -script.redemption = function(publicKeys, m, n) { - if (publicKeys.length !== m) { - throw new Error('wrong amount of pubkeys for redeem script'); - } - var mcode = constants.opcodes['1'] + (m - 1); - var ncode = constants.opcodes['1'] + (n - 1); - var redemption = []; - redemption.push(mcode); - publicKeys.forEach(function(pubkey) { - redemption.push(pubkey.length); - redemption = redemption.concat(pubkey); - }); - redemption.push(ncode); - redemption.push(constants.opcodes.checkmultisig); - return redemption; +script.multisig = function(keys, m, n) { + if (keys.length < m) + throw new Error('wrong amount of pubkeys for multisig script'); + + assert(m >= 1 && m <= n); + assert(n >= 1 && n <= 7); + + // Format: + // op_[m] [pubkey1-len] [pubkey1] ... op_[n] op_checkmultisig + + // Using pushdata ops for m and n: + // return [ [ m ] ].concat( + // keys, + // [ [ n ], 'checkmultisig' ] + // ); + + // Using OP_1-16 for m and n: + return [ m ].concat( + keys, + [ n, 'checkmultisig' ] + ); }; script.isPubkeyhash = function isPubkeyhash(s, hash) { @@ -301,6 +309,8 @@ script.isMultisig = function isMultisig(s, key) { return false; var m = s[0]; + if (typeof m === 'number' && m >= 1 && m <= 16) + m = [m]; if (!Array.isArray(m) || m.length !== 1) return false; m = m[0]; @@ -309,6 +319,8 @@ script.isMultisig = function isMultisig(s, key) { return false; var n = s[s.length - 2]; + if (typeof n === 'number' && n >= 1 && n <= 16) + n = [n]; if (!Array.isArray(n) || n.length !== 1) return false; n = n[0]; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 74d9cea3..c8bdf063 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -42,8 +42,6 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? +new Date() / 1000 : 0; - this.m = data.m || null; - this.n = data.n || null; this.change = data.change || null; this.fee = data.fee || 10000; this.dust = 5460; @@ -152,7 +150,6 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { - //input.script = [ constants.opcodes['0'], pub ]; input.script = [ [], pub ]; return; } @@ -160,13 +157,12 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // Multisig // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { - nsigs = nsigs || this.m; - if (!nsigs) - throw new Error('`nsigs` is required for multisig'); - //input.script = [ constants.opcodes['false'] ]; input.script = [ [] ]; - for (var i = 0; i < nsigs; i++) - //input.script[i + 1] = constants.opcodes['0']; + var m = s[0]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + for (var i = 0; i < m; i++) input.script[i + 1] = []; return; } @@ -174,11 +170,9 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // P2SH multisig // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { - //input.script = [ constants.opcodes['false'] ]; input.script = [ [] ]; var m = pub[0] - constants.opcodes['1'] + 1; for (var i = 0; i < m; i++) - //input.script[i + 1] = constants.opcodes['0']; input.script[i + 1] = []; // P2SH requires the redeem script after signatures input.script.push(pub); @@ -228,7 +222,6 @@ TX.prototype.signInput = function(input, key, type) { if (utils.isEqual(input.script[i], signature)) break; - //if (input.script[i] === constants.opcodes['0']) { if (input.script[i].length === 0) { input.script[i] = signature; break; @@ -283,26 +276,42 @@ TX.prototype.scriptOutput = function(options) { // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig var keys = options.keys || options.address; + if (keys === options.address) { keys = keys.map(function(address) { return bcoin.wallet.addr2hash(address, 'normal'); }); } + keys = keys.map(function(key) { if (typeof key === 'string') { return utils.toKeyArray(key); } return key; }); - script = [ - [ options.minSignatures || keys.length ] - ].concat( - keys, - [ [ keys.length ], 'checkmultisig' ] - ); - // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] - // in reality: - // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] + + // compat: + options.m = options.minSignatures || options.m; + var m = options.m || keys.length; + var n = options.n || keys.length; + + assert(m >= 1 && m <= n); + if (options.hash) + assert(n >= 1 && n <= 7); + else + assert(n >= 1 && n <= 3); + + script = bcoin.script.multisig(keys, m, n); + + // make it p2sh + if (options.hash) { + var hash = utils.ripesha(bcoin.script.encode(script)); + script = [ + 'hash160', + hash, + 'eq' + ]; + } } else if (bcoin.wallet.validateAddress(options.address, 'p2sh')) { // p2sh transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki @@ -413,8 +422,11 @@ TX.prototype.maxSize = function maxSize() { // Empty byte size += 1; // Signature + len - var m = s[0][0] || this.m; - assert(m >= 1 && m <= 7); + var m = s[0]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + assert(m >= 1 && m <= 3); size += 74 * m; return; } @@ -424,11 +436,18 @@ TX.prototype.maxSize = function maxSize() { var redeem, m, n; if (script) { redeem = script[script.length - 1]; - m = redeem[0] - constants.opcodes['1'] + 1; - n = redeem[redeem.length - 2] - constants.opcodes['1'] + 1; + m = redeem[0]; + n = redeem[redeem.length - 2]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + if (Array.isArray(n)) + n = n[0]; } else { - m = this.m; - n = this.n; + // May end up in a higher fee if we + // do not have the redeem script available. + m = 7; + n = 7; } assert(m >= 1 && m <= n); assert(n >= 1 && n <= 7); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 6781f5b7..afe2d1e4 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -149,11 +149,12 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { }; Wallet.prototype.getPublicKey = function getPublicKey(enc) { - var pub; - if (this.addressType === 'p2sh') - pub = this.getRedemption(); - else - pub = this.key.getPublic(this.compressed, 'array'); + var pub = this.key.getPublic(this.compressed, 'array'); + + if (this.addressType === 'p2sh') { + var keys = this.getPublicKeys(); + pub = bcoin.script.encode(bcoin.script.multisig(keys, m, n)); + } if (enc === 'base58') return utils.toBase58(pub); @@ -163,6 +164,15 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { return pub; }; +Wallet.prototype.getPublicKeys = function() { + var keys = this.sharedKeys.slice().map(utils.toKeyArray); + if (keys.length < this.m) { + var pub = this.key.getPublic(this.compressed, 'array'); + keys.push(pub); + } + return keys; +}; + Wallet.prototype.getHash = function getHash() { return utils.ripesha(this.getPublicKey()); }; @@ -328,16 +338,6 @@ Wallet.prototype.fill = function fill(tx, cb) { return tx; }; -// P2SH Multisig redeem script -Wallet.prototype.getRedemption = function() { - var sharedKeys = this.sharedKeys.slice().map(utils.toKeyArray); - if (sharedKeys.length < this.m) { - var pub = this.key.getPublic(this.compressed, 'array'); - sharedKeys.push(pub); - } - return bcoin.script.redemption(sharedKeys, m, n); -}; - Wallet.prototype.toJSON = function toJSON() { return { v: 1,