diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index 0f74008b..177160a9 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -42,16 +42,11 @@ function Address(options) { this.keys = []; this.m = options.m || 1; this.n = options.n || 1; - this.redeem = null; if (this.n > 1) this.type = 'multisig'; assert(this.type === 'pubkeyhash' || this.type === 'multisig'); - this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash'; - - if (network.address.prefixes[this.prefixType] == null) - throw new Error('Unknown prefix: ' + this.prefixType); if (this.m < 1 || this.m > this.n) throw new Error('m ranges between 1 and n'); @@ -61,9 +56,6 @@ function Address(options) { (options.keys || []).forEach(function(key) { this.addKey(key); }, this); - - if (options.redeem || options.script) - this.setRedeem(options.redeem || options.script); } utils.inherits(Address, EventEmitter); @@ -100,22 +92,7 @@ Address.prototype.getBalance = function getBalance() { return this._wallet.getBalance(this); }; -Address.prototype.setRedeem = function setRedeem(redeem) { - var old = this.getScriptAddress(); - - if (!Buffer.isBuffer(redeem)) - redeem = bcoin.script.encode(redeem); - - this.type = 'multisig'; - this.prefixType = 'scripthash'; - this.redeem = redeem; - this.emit('update script', old, this.getScriptAddress()); -}; - Address.prototype.addKey = function addKey(key) { - var old = this.getScriptAddress(); - var cur; - key = utils.ensureBuffer(key); var has = this.keys.some(function(k) { @@ -128,22 +105,9 @@ Address.prototype.addKey = function addKey(key) { this.keys.push(key); this.keys = utils.sortKeys(this.keys); - - delete this._scriptAddress; - delete this._scriptHash; - delete this._script; - this.getScriptAddress(); - - cur = this.getScriptAddress(); - - if (old !== cur) - this.emit('update script', old, cur); }; Address.prototype.removeKey = function removeKey(key) { - var old = this.getScriptAddress(); - var cur; - key = utils.ensureBuffer(key); var index = this.keys.map(function(k, i) { @@ -158,16 +122,6 @@ Address.prototype.removeKey = function removeKey(key) { this.keys.splice(index, 1); this.keys = utils.sortKeys(this.keys); - - delete this._scriptAddress; - delete this._scriptHash; - delete this._script; - this.getScriptAddress(); - - cur = this.getScriptAddress(); - - if (old !== cur) - this.emit('update script', old, cur); }; Address.prototype.getPrivateKey = function getPrivateKey(enc) { @@ -177,65 +131,122 @@ Address.prototype.getPrivateKey = function getPrivateKey(enc) { Address.prototype.getScript = function getScript() { var redeem; - if (this.prefixType !== 'scripthash') + if (this.type !== 'multisig') return; if (this._script) return this._script; - if (this.redeem) { - redeem = this.redeem; - assert(Buffer.isBuffer(redeem)); - } else if (this.keys.length < this.n) { - redeem = bcoin.script.createPubkeyhash(this.getKeyHash()); - redeem = bcoin.script.encode(redeem); - } else { - redeem = bcoin.script.createMultisig(this.keys, this.m, this.n); - redeem = bcoin.script.encode(redeem); - } + assert(this.keys.length === this.n, 'Not all keys have been added.'); - if (redeem.length > 520) - throw new Error('Redeem script too large (520 byte limit).'); + redeem = bcoin.script.createMultisig(this.keys, this.m, this.n); + redeem = bcoin.script.encode(redeem); + + if (this.options.program) { + if (redeem.length > 10000) + throw new Error('Redeem script too large (10000 byte limit).'); + } else { + if (redeem.length > 520) + throw new Error('Redeem script too large (520 byte limit).'); + } this._script = redeem; return this._script; }; -Address.prototype.getScriptHash = function getScriptHash() { - if (this.prefixType !== 'scripthash') +Address.prototype.getProgram = function getProgram() { + var program; + + if (!this.options.program) return; - if (this._scriptHash) - return this._scriptHash; + if (this._program) + return this._program; - this._scriptHash = Address.hash160(this.getScript()); + if (this.type === 'pubkeyhash') { + program = bcoin.script.createWitnessProgram( + 0, Address.hash160(this.getPublicKey())); + } else if (this.type === 'multisig') { + program = bcoin.script.createWitnessProgram( + 0, utils.sha256(this.getScript())); + } - return this._scriptHash; + assert(program); + + this._program = bcoin.script.encode(program); + + return this._program; +}; + +Address.prototype.getProgramHash = function getProgramHash() { + if (!this.options.program) + return; + + if (this._programHash) + return this._programHash; + + this._programHash = Address.hash160(this.getProgram()); + + return this._programHash; +}; + +Address.prototype.getProgramAddress = function getProgramAddress() { + if (!this.options.program) + return; + + if (this._programAddress) + return this._programAddress; + + this._programAddress = Address.compileHash(this.getProgramHash(), 'scripthash'); + + return this._programAddress; +}; + +Address.prototype.getScriptHash = function getScriptHash() { + return this.getScriptHash160(); +}; + +Address.prototype.getScriptHash160 = function getScriptHash256() { + if (this.type !== 'multisig') + return; + + if (this._scriptHash160) + return this._scriptHash160; + + this._scriptHash160 = Address.hash160(this.getScript()); + + return this._scriptHash160; +}; + +Address.prototype.getScriptHash256 = function getScriptHash256() { + if (this.type !== 'multisig') + return; + + if (this._scriptHash256) + return this._scriptHash256; + + this._scriptHash256 = Address.sha256(this.getScript()); + + return this._scriptHash256; }; Address.prototype.getScriptAddress = function getScriptAddress() { - if (this.prefixType !== 'scripthash') + if (this.type !== 'multisig') return; if (this._scriptAddress) return this._scriptAddress; - this._scriptAddress = Address.compileHash(this.getScriptHash(), this.prefixType); + if (this.options.program) + this._scriptAddress = Address.compileHash(this.getScriptHash256(), 'witnessscripthash'); + else + this._scriptAddress = Address.compileHash(this.getScriptHash160(), 'scripthash'); return this._scriptAddress; }; Address.prototype.getPublicKey = function getPublicKey(enc) { - if (!enc) { - if (this._pub) - return this._pub; - - this._pub = this.key.getPublicKey(); - - return this._pub; - } - return this.key.getPublicKey(enc); }; @@ -252,19 +263,22 @@ Address.prototype.getKeyAddress = function getKeyAddress() { if (this._address) return this._address; - this._address = Address.compileHash(this.getKeyHash(), 'pubkeyhash'); + if (this.options.program) + this._address = Address.compileHash(this.getKeyHash(), 'witnesspubkeyhash'); + else + this._address = Address.compileHash(this.getKeyHash(), 'pubkeyhash'); return this._address; }; Address.prototype.getHash = function getHash() { - if (this.prefixType === 'scripthash') + if (this.type === 'multisig') return this.getScriptHash(); return this.getKeyHash(); }; Address.prototype.getAddress = function getAddress() { - if (this.prefixType === 'scripthash') + if (this.type === 'multisig') return this.getScriptAddress(); return this.getKeyAddress(); }; @@ -277,9 +291,12 @@ Address.prototype._getAddressMap = function _getAddressMap() { this.addressMap[this.getKeyAddress()] = true; - if (this.prefixType === 'scripthash') + if (this.type === 'multisig') this.addressMap[this.getScriptAddress()] = true; + if (this.options.program) + this.addressMap[this.getProgramAddress()] = true; + return this.addressMap; }; @@ -351,7 +368,7 @@ Address.prototype.scriptInputs = function scriptInputs(tx, index) { if (!self.ownOutput(input.output)) return total; - if (tx.scriptInput(i, publicKey, redeem)) + if (tx.scriptInput(i, self)) total++; return total; @@ -379,7 +396,7 @@ Address.prototype.signInputs = function signInputs(tx, type, index) { if (!self.ownOutput(input.output)) return total; - if (tx.signInput(i, key, type)) + if (tx.signInput(i, self, type)) total++; return total; @@ -409,7 +426,7 @@ Address.prototype.sign = function sign(tx, type, index) { if (!self.ownOutput(input.output)) return total; - if (tx.sign(i, key, redeem, type)) + if (tx.sign(i, self, type)) total++; return total; @@ -424,10 +441,30 @@ Address.prototype.__defineGetter__('scriptHash', function() { return this.getScriptHash(); }); +Address.prototype.__defineGetter__('scriptHash160', function() { + return this.getScriptHash160(); +}); + +Address.prototype.__defineGetter__('scriptHash256', function() { + return this.getScriptHash256(); +}); + Address.prototype.__defineGetter__('scriptAddress', function() { return this.getScriptAddress(); }); +Address.prototype.__defineGetter__('program', function() { + return this.getProgram(); +}); + +Address.prototype.__defineGetter__('programHash', function() { + return this.getProgramHash(); +}); + +Address.prototype.__defineGetter__('programAddress', function() { + return this.getProgramAddress(); +}); + Address.prototype.__defineGetter__('privateKey', function() { return this.getPrivateKey(); }); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index d27c1372..aabf97e7 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -455,7 +455,7 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) Chain.prototype._verify = function _verify(block, prev) { var flags = constants.flags.MANDATORY_VERIFY_FLAGS; var height, ts, i, tx, cb, coinbaseHeight, medianTime; - var locktimeMedian, coinbase, commitment, segwit; + var locktimeMedian, segwit; if (!block.verify()) return flags; @@ -575,11 +575,16 @@ Chain.prototype._verify = function _verify(block, prev) { } } - if (segwit) { - if (block._witness && block.commitmentHash !== block.getCommitmentHash()) { + if (block.version >= 5 && segwit) { + if (block.commitmentHash !== block.getCommitmentHash()) { utils.debug('Block failed witnessroot test: %s', block.rhash); return false; } + } else { + if (block.hasWitness()) { + utils.debug('Unexpected witness data found: %s', block.rhash); + return false; + } } // Get timestamp for tx.isFinal(). @@ -655,11 +660,11 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac var i, j, input, hash; var sigops = 0; - utils.print(height); - utils.print(block.commitmentHash ? utils.revHex(block.commitmentHash) : null); - utils.print(utils.revHex(block.getCommitmentHash() || '00')); - utils.print(block.txs[0]); - utils.print(block.txs[1]); + // utils.print(height); + // utils.print(block.commitmentHash ? utils.revHex(block.commitmentHash) : null); + // utils.print(utils.revHex(block.getCommitmentHash() || '00')); + // utils.print(block.txs[0]); + // utils.print(block.txs[1]); if (err) return callback(err); @@ -704,6 +709,9 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac throw new Error('BUG: Bad inputs in historical data!'); return callback(null, false); } + + if (input.output.getType() === 'scripthash') + utils.debug(tx); } if (!scriptCheck) diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 5a98b35a..486523c7 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -207,6 +207,9 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { if (tx.isCoinbase()) return callback(new Error('What?')); + if (!this.checkTX(tx, peer)) + return callback(new Error('TX failed checkTX.')); + assert(tx.ts === 0); this._lockTX(tx); @@ -219,6 +222,9 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { if (err) return callback(err); + // Do this in the future. + // tx = self.fillCoin(tx); + if (!tx.hasPrevout()) { return callback(new Error('Previous outputs not found.')); peer.reject({ @@ -465,6 +471,67 @@ Mempool.prototype.unlock = function unlock() { this.locked = false; }; +Mempool.prototype.checkTX = function checkTX(tx, peer) { + var i, input, output, size; + var total = new bn(0); + var uniq = {}; + + if (tx.inputs.length === 0) + return this.reject(peer, tx, 'bad-txns-vin-empty'); + + if (tx.outputs.length === 0) + return this.reject(peer, tx, 'bad-txns-vout-empty'); + + if (tx.getSize() > constants.block.maxSize) + return this.reject(peer, tx, 'bad-txns-oversize'); + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + if (output.value.cmpn(0) < 0) + return this.reject(peer, tx, 'bad-txns-vout-negative'); + if (output.value.cmp(constants.maxMoney) > 0) + return this.reject(peer, tx, 'bad-txns-vout-toolarge'); + total.iadd(output.value); + if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney)) + return this.reject(peer, tx, 'bad-txns-txouttotal-toolarge'); + } + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + if (uniq[input.out.hash]) + return this.reject(peer, tx, 'bad-txns-inputs-duplicate'); + uniq[input.out.hash] = true; + } + + if (tx.isCoinbase()) { + size = bcoin.script.getSize(tx.inputs[0].script); + if (size < 2 || size > 100) + return this.reject(peer, tx, 'bad-cb-length'); + } else { + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + if (+input.out.hash === 0) + return this.reject(peer, tx, 'bad-txns-prevout-null'); + } + } + + return true; +}; + +Mempool.prototype.reject = function reject(peer, obj, reason) { + return false; + + if (!peer) + return false; + + peer.reject({ + reason: reason, + data: obj.hash ? obj.hash() : [] + }); + + return false; +}; + /** * Expose */ diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 5a44e741..61c097ef 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -67,6 +67,7 @@ MTX.prototype.clone = function clone() { tx.inputs = tx.inputs.map(function(input) { input.script = input.script.slice(); + input.witness = input.witness.slice(); return input; }); @@ -129,8 +130,8 @@ MTX.prototype.addInput = function addInput(options, index) { return this; }; -MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) { - var input, s, n, i; +MTX.prototype.scriptInput = function scriptInput(index, addr) { + var input, prev, n, i, redeemScript, witnessScript, vector, dummy; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -139,102 +140,173 @@ MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) { input = this.inputs[index]; assert(input); - // Already has a script template (at least) - // if (input.script.length) - // return; - // We should have previous outputs by now. assert(input.output); - // Get the previous output's subscript - s = input.output.script; + // Optimization: Don't bother with any below + // calculation if the output is already templated. + // Just say this is "our" output. + if (input.script.length || input.witness.length) + return true; - // P2SH - if (bcoin.script.isScripthash(s)) { - if (!redeem) + // Optimization: test output against the + // address map to avoid unnecessary calculation. + // A hash table lookup may be faster than all + // the nonsense below. + if (!addr.ownOutput(input.output)) + return false; + + // Get the previous output's script + prev = input.output.script; + + // This is easily the hardest part about building a transaction + // with segwit: figuring out where the redeem script and witness + // redeem scripts go. + if (bcoin.script.isScripthash(prev)) { + if (addr.program && utils.isEqual(prev[1], addr.programHash)) { + // Witness program nested in regular P2SH. + redeemScript = addr.program; + vector = input.witness; + dummy = new Buffer([]); + assert(addr.program[0] === 0, 'Non-zero version passed to address.'); + if (addr.program.length === 34) { + // P2WSH nested within pay-to-scripthash + // (it had to be this complicated, didn't it?) + witnessScript = addr.script; + prev = bcoin.script.decode(addr.script); + } else if (addr.program.length === 22) { + // P2WPKH nested within pay-to-scripthash. + prev = bcoin.script.createPubkeyhash(addr.keyHash); + } else { + assert(false, 'Unknown program data length passed to address.'); + } + } else if (addr.script && utils.isEqual(prev[1], addr.scriptHash160)) { + // Regular P2SH. + redeemScript = addr.script; + prev = bcoin.script.decode(addr.script); + vector = input.script; + dummy = 0; + } else { return false; - s = bcoin.script.decode(redeem); + } + } else if (bcoin.script.isWitnessProgram(prev)) { + // Witness program. + vector = input.witness; + dummy = new Buffer([]); + + if (prev[0] !== 0) + return false; + + if (prev[1].length === 32) { + // Bare P2WPSH. + if (!addr.script || !utils.isEqual(prev[1], addr.scriptHash256)) + return false; + + witnessScript = addr.script; + prev = bcoin.script.decode(addr.script); + } else if (prev[1].length === 20) { + // Bare P2WPKH. + if (!utils.isEqual(prev[1], addr.keyHash)) + return false; + + prev = bcoin.script.createPubkeyhash(prev[1]); + } else { + // Bare... who knows? + return false; + } } else { - redeem = null; + // Wow, a normal output! Praise be to Jengus and Gord. + vector = input.script; + dummy = 0; } - if (bcoin.script.isPubkey(s)) { + if (bcoin.script.isPubkey(prev)) { // P2PK - if (!utils.isEqual(s[0], publicKey)) + if (!utils.isEqual(prev[0], addr.publicKey)) return false; + // Already has a script template (at least) - if (input.script.length) + if (vector.length) return true; - input.script = [0]; - } else if (bcoin.script.isPubkeyhash(s)) { + + vector[0] = dummy; + } else if (bcoin.script.isPubkeyhash(prev)) { // P2PKH - if (!utils.isEqual(s[2], bcoin.address.hash160(publicKey))) + if (!utils.isEqual(prev[2], addr.keyHash)) return false; + // Already has a script template (at least) - if (input.script.length) + if (vector.length) return true; - input.script = [0, publicKey]; - } else if (bcoin.script.isMultisig(s)) { + + vector[0] = dummy; + vector[1] = addr.publicKey; + } else if (bcoin.script.isMultisig(prev)) { // Multisig - for (i = 0; i < s.length; i++) { - if (utils.isEqual(s[i], publicKey)) + for (i = 0; i < prev.length; i++) { + if (utils.isEqual(prev[i], addr.publicKey)) break; } - if (i === s.length) + if (i === prev.length) return false; // Already has a script template (at least) - if (input.script.length) + if (vector.length) return true; // Technically we should create m signature slots, // but we create n signature slots so we can order // the signatures properly. - input.script = [0]; + vector[0] = dummy; // Grab `n` value (number of keys). - n = s[s.length - 2]; + n = prev[prev.length - 2]; // Fill script with `n` signature slots. for (i = 0; i < n; i++) - input.script[i + 1] = 0; + vector[i + 1] = dummy; } else { - for (i = 0; i < s.length; i++) { - if (utils.isEqual(s[i], publicKey)) + for (i = 0; i < prev.length; i++) { + if (utils.isEqual(prev[i], addr.publicKey)) break; } - if (i === s.length) + if (i === prev.length) return false; // Already has a script template (at least) - if (input.script.length) + if (vector.length) return true; // Likely a non-standard scripthash multisig // input. Determine n value by counting keys. // Also, only allow nonstandard types for // scripthash. - if (redeem) { - input.script = [0]; - // Fill script with `n` signature slots. - for (i = 0; i < s.length; i++) { - if (bcoin.script.isKey(s[i])) - input.script.push(0); - } + vector[0] = dummy; + + // Fill script with `n` signature slots. + for (i = 0; i < prev.length; i++) { + if (bcoin.script.isKey(prev[i])) + vector[i + 1] = dummy; } } - // P2SH requires the redeem script after signatures - if (redeem) - input.script.push(redeem); + // P2SH requires the redeem + // script after signatures. + if (redeemScript) + input.script.push(redeemScript); + + // P2WSH requires the witness + // script after signatures. + if (witnessScript) + input.witness.push(witnessScript); return true; }; -MTX.prototype.createSignature = function createSignature(index, key, type) { - var input, s, hash, signature; +MTX.prototype.createSignature = function createSignature(index, prev, key, type) { + var prev, hash, signature; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -245,38 +317,22 @@ MTX.prototype.createSignature = function createSignature(index, key, type) { if (typeof type === 'string') type = constants.hashType[type]; - // Get the input - input = this.inputs[index]; - assert(input); - - // We should have previous outputs by now. - assert(input.output); - - // Get the previous output's subscript - s = input.output.script; - - // We need to grab the redeem script when - // signing p2sh transactions. - if (bcoin.script.isScripthash(s)) - s = bcoin.script.getRedeem(input.script); - // Get the hash of the current tx, minus the other // inputs, plus the sighash type. - hash = this.signatureHash(index, s, type); + hash = this.signatureHash(index, prev, type); // Sign the transaction with our one input signature = bcoin.script.sign(hash, key, type); // Something is broken if this doesn't work: - assert(bcoin.script.checksig(hash, signature, key)); + // assert(bcoin.script.checksig(hash, signature, key), 'BUG: Verify failed.'); return signature; }; -// Sign the now-built scriptSigs -MTX.prototype.signInput = function signInput(index, key, type) { - var input, s, signature, ki, signatures, i; - var len, m, n, keys, publicKey, keyHash; +MTX.prototype.signInput = function signInput(index, addr, type) { + var input, prev, signature, ki, signatures, i; + var len, m, n, keys, vector, dummy; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -288,87 +344,103 @@ MTX.prototype.signInput = function signInput(index, key, type) { // We should have previous outputs by now. assert(input.output); - // Create our signature. - signature = this.createSignature(index, key, type); - // Get the previous output's subscript - s = input.output.script; + prev = input.output.script; - // Script length, needed for multisig - len = input.script.length; + vector = input.script; + len = vector.length; + dummy = 0; // We need to grab the redeem script when // signing p2sh transactions. - if (bcoin.script.isScripthash(s)) { - s = bcoin.script.getRedeem(input.script); - // Decrement `len` to avoid the redeem script - len--; + if (bcoin.script.isScripthash(prev)) { + prev = bcoin.script.getRedeem(input.script); + len = vector.length - 1; } - // Get pubkey. - publicKey = key.getPublicKey(); + // If the output script is a witness program, + // we have to switch the vector to the witness + // and potentially alter the length. Note that + // witnesses are stack items, so the `dummy` + // _has_ to be an empty buffer (what OP_0 + // pushes onto the stack). + if (bcoin.script.isWitnessScripthash(prev)) { + prev = bcoin.script.getRedeem(input.witness); + vector = input.witness; + len = vector.length - 1; + dummy = new Buffer([]); + } else if (bcoin.script.isWitnessPubkeyhash(prev)) { + prev = bcoin.script.createPubkeyhash(prev[1]); + vector = input.witness; + len = vector.length; + dummy = new Buffer([]); + } + + // Create our signature. + signature = this.createSignature(index, prev, addr.key, type); // Add signatures. - if (bcoin.script.isPubkey(s)) { + if (bcoin.script.isPubkey(prev)) { // P2PK // Already signed. - if (bcoin.script.isSignature(input.script[0])) + if (bcoin.script.isSignature(vector[0])) return true; // Make sure the pubkey is ours. - if (!utils.isEqual(publicKey, s[0])) + if (!utils.isEqual(addr.publicKey, prev[0])) return false; - input.script[0] = signature; + vector[0] = signature; return true; } - if (bcoin.script.isPubkeyhash(s)) { + if (bcoin.script.isPubkeyhash(prev)) { // P2PKH // Already signed. - if (bcoin.script.isSignature(input.script[0])) + if (bcoin.script.isSignature(vector[0])) return true; // Make sure the pubkey hash is ours. - keyHash = bcoin.address.hash160(publicKey); - if (!utils.isEqual(keyHash, s[2])) + if (!utils.isEqual(addr.keyHash, prev[2])) return false; - input.script[0] = signature; + vector[0] = signature; return true; } - if (bcoin.script.isMultisig(s)) { + if (bcoin.script.isMultisig(prev)) { // Multisig // Grab the redeem script's keys to figure // out where our key should go. - keys = s.slice(1, -2); + keys = prev.slice(1, -2); // Grab `m` value (number of sigs required). - m = s[0]; + m = prev[0]; // Grab `n` value (number of keys). - n = s[s.length - 2]; + n = prev[prev.length - 2]; } else { // Only allow non-standard signing for // scripthash. - if (len !== input.script.length - 1) + if (len !== vector.length - 1) return false; keys = []; - for (i = 0; i < s.length; i++) { - if (bcoin.script.isKey(s[i])) - keys.push(s[i]); + for (i = 0; i < prev.length; i++) { + if (bcoin.script.isKey(prev[i])) + keys.push(prev[i]); } + // We don't know what m is, so + // we can never finalize the signatures. + m = keys.length; n = keys.length; - m = n; } // Something is very wrong here. Abort. @@ -378,7 +450,7 @@ MTX.prototype.signInput = function signInput(index, key, type) { // Count the number of current signatures. signatures = 0; for (i = 1; i < len; i++) { - if (bcoin.script.isSignature(input.script[i])) + if (bcoin.script.isSignature(vector[i])) signatures++; } @@ -391,14 +463,14 @@ MTX.prototype.signInput = function signInput(index, key, type) { // or by `m`. Add some signature slots for // us to use. while (len - 1 < n) { - input.script.splice(len, 0, 0); + vector.splice(len, 0, dummy); len++; } // Find the key index so we can place // the signature in the same index. for (ki = 0; ki < keys.length; ki++) { - if (utils.isEqual(publicKey, keys[ki])) + if (utils.isEqual(addr.publicKey, keys[ki])) break; } @@ -417,8 +489,8 @@ MTX.prototype.signInput = function signInput(index, key, type) { // and increment the total number of // signatures. if (ki < len && signatures < m) { - if (input.script[ki] === 0) { - input.script[ki] = signature; + if (bcoin.script.isZero(vector[ki])) { + vector[ki] = signature; signatures++; } } @@ -427,8 +499,8 @@ MTX.prototype.signInput = function signInput(index, key, type) { if (signatures >= m) { // Remove empty slots left over. for (i = len - 1; i >= 1; i--) { - if (input.script[i] === 0) { - input.script.splice(i, 1); + if (bcoin.script.isZero(vector[i])) { + vector.splice(i, 1); len--; } } @@ -438,7 +510,7 @@ MTX.prototype.signInput = function signInput(index, key, type) { // with implementations that potentially handle // signature slots differently. while (signatures > m) { - input.script.splice(len - 1, 1); + vector.splice(len - 1, 1); signatures--; len--; } @@ -451,8 +523,7 @@ MTX.prototype.signInput = function signInput(index, key, type) { return signatures === m; }; -MTX.prototype.sign = function sign(index, key, redeem, type) { - var publicKey = key.getPublicKey(); +MTX.prototype.sign = function sign(index, addr, type) { var input; if (index && typeof index === 'object') @@ -462,104 +533,16 @@ MTX.prototype.sign = function sign(index, key, redeem, type) { assert(input); // Build script for input - if (!this.scriptInput(index, publicKey, redeem)) + if (!this.scriptInput(index, addr)) return false; // Sign input - if (!this.signInput(index, key, type)) + if (!this.signInput(index, addr, type)) return false; return true; }; -MTX.prototype.isSigned = function isSigned(index, required) { - var i, input, s, len, m, j, total; - - if (this._signed) - return true; - - if (index && typeof index === 'object') - index = this.inputs.indexOf(index); - - if (index != null) - assert(this.inputs[index]); - - for (i = 0; i < this.inputs.length; i++) { - input = this.inputs[i]; - - if (index != null && i !== index) - continue; - - // We can't check for signatures unless - // we have the previous output. - assert(input.output); - - // Get the prevout's subscript - s = input.output.script; - - // Script length, needed for multisig - len = input.script.length; - - // Grab the redeem script if P2SH - if (bcoin.script.isScripthash(s)) { - s = bcoin.script.getRedeem(input.script); - // Decrement `len` to avoid the redeem script - len--; - } - - // Check for signatures. - // P2PK - if (bcoin.script.isPubkey(s)) { - if (!bcoin.script.isSignature(input.script[0])) - return false; - continue; - } - - // P2PK - if (bcoin.script.isPubkeyhash(s)) { - if (!bcoin.script.isSignature(input.script[0])) - return false; - continue; - } - - // Multisig - if (bcoin.script.isMultisig(s)) { - // Grab `m` value (number of required sigs). - m = s[0]; - if (Buffer.isBuffer(m)) - m = m[0] || 0; - - // Ensure all members are signatures. - for (j = 1; j < len; j++) { - if (!bcoin.script.isSignature(input.script[j])) - return false; - } - - // Ensure we have the correct number - // of required signatures. - if (len - 1 !== m) - return false; - - continue; - } - - if (required == null) - continue; - - // Unknown - total = 0; - for (j = 0; j < input.script.length; j++) { - if (bcoin.script.isSignatureEncoding(input.script[j])) - total++; - } - - if (total !== required) - return false; - } - - return this._signed = true; -}; - MTX.prototype.addOutput = function addOutput(obj, value) { var options, output; @@ -667,7 +650,7 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) { MTX.prototype.maxSize = function maxSize(maxM, maxN) { var copy = this.clone(); - var i, j, input, total, size, s, m, n; + var i, j, input, total, size, prev, m, n; // Create copy with 0-script inputs for (i = 0; i < copy.inputs.length; i++) @@ -683,38 +666,38 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { assert(input.output); // Get the previous output's subscript - s = input.output.script; + prev = input.output.script; // If we have access to the redeem script, // we can use it to calculate size much easier. - if (this.inputs[i].script.length && bcoin.script.isScripthash(s)) { - s = bcoin.script.getRedeem(this.inputs[i].script); + if (this.inputs[i].script.length && bcoin.script.isScripthash(prev)) { + prev = bcoin.script.getRedeem(this.inputs[i].script); // Need to add the redeem script size // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - size += 3 + bcoin.script.getSize(s); + size += 3 + bcoin.script.getSize(prev); } - if (bcoin.script.isPubkey(s)) { + if (bcoin.script.isPubkey(prev)) { // P2PK // OP_PUSHDATA0 [signature] size += 1 + 73; - } else if (bcoin.script.isPubkeyhash(s)) { + } else if (bcoin.script.isPubkeyhash(prev)) { // P2PKH // OP_PUSHDATA0 [signature] size += 1 + 73; // OP_PUSHDATA0 [key] size += 1 + 33; - } else if (bcoin.script.isMultisig(s)) { + } else if (bcoin.script.isMultisig(prev)) { // Bare Multisig // Get the previous m value: - m = s[0]; + m = prev[0]; // OP_0 size += 1; // OP_PUSHDATA0 [signature] ... size += (1 + 73) * m; - } else if (bcoin.script.isScripthash(s)) { + } else if (bcoin.script.isScripthash(prev)) { // P2SH Multisig // This technically won't work well for other // kinds of P2SH. It will also over-estimate @@ -744,8 +727,8 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { size += 1; } else { // OP_PUSHDATA0 [signature] - for (j = 0; j < s.length; j++) { - if (bcoin.script.isKey(s[j])) + for (j = 0; j < prev.length; j++) { + if (bcoin.script.isKey(prev[j])) size += 1 + 73; } } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 536632fc..0535851d 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -1260,13 +1260,21 @@ Pool.prototype.removeWallet = function removeWallet(wallet) { }; Pool.prototype.watchAddress = function watchAddress(address) { - if (address.type === 'scripthash') { + if (address.script) { // For the redeem script hash in outputs: - this.watch(address.getScriptHash()); + this.watch(address.getScriptHash160()); // For the redeem script in inputs: this.watch(address.getScript()); } + if (address.program) { + // For programs inside P2SH + this.watch(address.getProgramHash()); + // For witness scripthash + if (address.script) + this.watch(address.getScriptHash256()); + } + // For the pubkey hash in outputs: this.watch(address.getKeyHash()); // For the pubkey in inputs: @@ -1274,13 +1282,21 @@ Pool.prototype.watchAddress = function watchAddress(address) { }; Pool.prototype.unwatchAddress = function unwatchAddress(address) { - if (address.type === 'scripthash') { + if (addres.script) { // For the redeem script hash in p2sh outputs: - this.unwatch(address.getScriptHash()); + this.unwatch(address.getScriptHash160()); // For the redeem script in p2sh inputs: this.unwatch(address.getScript()); } + if (address.program) { + // For programs inside P2SH + this.unwatch(address.getProgramHash()); + // For witness scripthash + if (address.script) + this.unwatch(address.getScriptHash256()); + } + // For the pubkey hash in p2pk/multisig outputs: this.unwatch(address.getKeyHash()); // For the pubkey in p2pkh inputs: diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index c84ade76..12cc936a 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -583,10 +583,16 @@ Framer._block = function _block(block, witness) { var off = 0; var txSize = 0; var txs = []; + var hasWitness; var i, tx, p; for (i = 0; i < block.txs.length; i++) { - tx = witness && block.txs[i].hasWitness() + if (witness) { + hasWitness = block.txs[i].hasWitness + ? block.txs[i].hasWitness() + : block.txs[i]._witness; + } + tx = hasWitness ? Framer.witnessTX(block.txs[i]) : Framer.tx(block.txs[i]); txs.push(tx); diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index eb7bbab9..ce36b23e 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -206,18 +206,19 @@ script.encode = function encode(s) { // Witnesses aren't scripts, but we still // want to convert [0] to OP_0, [0xff] to 1negate, etc. script.decodeWitness = function decodeWitness(witness) { - var chunk, i, op; var script = []; + var chunk, i; for (i = 0; i < witness.length; i++) { chunk = witness[i]; - op = chunk; - if (chunk.length === 1) { - if (chunk[0] === 0xff) - op = '1negate'; - else if (chunk[0] >= 0 && chunk <= 16) + if (chunk.length <= 1) { + if (chunk.length === 0) + op = new Buffer([]); + else if (chunk[0] >= 1 && chunk <= 16) op = chunk[0]; + else if (chunk[0] === 0xff) + op = '1negate'; } script.push(op); @@ -227,20 +228,22 @@ script.decodeWitness = function decodeWitness(witness) { }; script.encodeWitness = function encodeWitness(script) { - var chunk, i, chunk; var witness = []; + var chunk, i; for (i = 0; i < script.length; i++) { chunk = script[i]; - if (chunk === '1negate') + if (chunk === 0) + chunk = new Buffer([]); + else if (chunk >= 1 && chunk <= 16) + chunk = new Buffer([chunk]); + else if (chunk === '1negate') chunk = new Buffer([0xff]); - else if (chunk >= 0 && chunk <= 16) - chunk = new Buffer([op]); - assert(Buffer.isBuffer(op)); + assert(Buffer.isBuffer(chunk)); - witness.push(op); + witness.push(chunk); } return witness; @@ -455,29 +458,24 @@ script.verifyProgram = function verifyProgram(witness, output, tx, i, flags) { } for (j = 0; j < stack.length; j++) { - if (stack[j].length > constants.script.maxSize) { - throw new Eror('max size'); + if (stack[j].length > constants.script.maxSize) return false; - } } - utils.print(script.format(stack)); + utils.debug(bcoin.script.format(stack)); + utils.debug(bcoin.script.format(redeem)); res = script.execute(redeem, stack, tx, i, flags); - utils.print(script.format(redeem)); - utils.print(script.format(stack)); + utils.debug(bcoin.script.format(stack)); + // Verify the script did not fail as well as the stack values - if (!res || stack.length === 0 || !script.bool(stack.pop())) { - throw new Error('script failed'); + if (!res || stack.length === 0 || !script.bool(stack.pop())) return false; - } // Witnesses always require cleanstack - if (stack.length !== 0) { - throw new Error('cleanstack'); + if (stack.length !== 0) return false; - } return true; }; @@ -1135,6 +1133,10 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { } res = succ >= m; + + if (!res) + utils.debug('checkmultisig failed: succ: %d, m: %d', succ, m); + if (o === 'checkmultisigverify') { if (!res) return false; @@ -2246,6 +2248,13 @@ script.isDummy = function isDummy(data) { return data.length === 0; }; +script.isZero = function isZero(data) { + if (data === 0) + return true; + + return script.isDummy(data); +}; + script.isData = function isData(data) { if (!Buffer.isBuffer(data)) return false; @@ -2492,70 +2501,31 @@ script.isPushOnly = function isPushOnly(s) { script.getSigops = function getSigops(s, accurate) { var i, op; - var n = 0; + var total = 0; var lastOp = -1; for (i = 0; i < s.length; i++) { op = s[i]; + if (Buffer.isBuffer(op)) continue; + if (constants.opcodes[op] == null) return 0; + if (op === 'checksig' || op === 'checksigverify') { - n++; + total++; } else if (op === 'checkmultisig' || op === 'checkmultisigverify') { - if (accurate && lastOp >= 1 && lastOp <= 16) { - n += lastOp; - } else { - n += constants.script.maxPubkeysPerMultisig; - } + if (accurate && lastOp >= 1 && lastOp <= 16) + total += lastOp; + else + total += constants.script.maxPubkeysPerMultisig; } + lastOp = op; } - return n; -}; - -script.getScripthashSigops = function getScripthashSigops(s, prev) { - if (prev) { - if (!script.isScripthash(prev)) - return 0; - } else { - if (!script.isScripthashInput(s)) - return 0; - } - - if (!script.isPushOnly(s)) - return 0; - - s = script.getRedeem(s); - - // Need to do this too - // if (script.isWitnessProgram(s)) { - // call witness sigops - // } - - return script.getSigops(s, true); -}; - -script.getWitnessSigops = function getWitnessSigops(s) { - if (script.isWitnessPubkeyhash(s)) - return 1; - return 0; -}; - -script.getWitnessScripthashSigops = function getWitnessScripthashSigops(witness, prev) { - var redeem; - - if (!prev) - return 0; - - if (!script.isWitnessScripthash(prev)) - return 0; - - redeem = script.getRedeem(witness); - - return script.getSigops(s, true); + return total; }; script.getArgs = function getArgs(s) { @@ -2581,5 +2551,11 @@ script.getArgs = function getArgs(s) { if (script.isNulldata(s)) return -1; + if (script.isWitnessScripthash(s)) + return 1; + + if (script.isWitnessPubkeyhash(s)) + return 2; + return -1; }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 9e24aa48..00bcf874 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -93,6 +93,7 @@ TX.prototype.clone = function clone() { delete tx._witness; delete tx._raw; delete tx._size; + delete tx._offset; delete tx._cost; delete tx._hash; delete tx._whash; @@ -120,7 +121,6 @@ TX.prototype.witnessHash = function witnessHash(enc) { : new Buffer(constants.zeroHash); } - // if (!this._witness) if (!this.hasWitness()) return this.hash(enc); @@ -160,9 +160,13 @@ TX.prototype.renderWitness = function renderWitness() { if (this._raw) { if (this._witness) return this._raw; + // We probably shouldn't even render it + // as a witness tx if it doesn't have a witness. return bcoin.protocol.framer.witnessTX(this); } + // We probably shouldn't even render it + // as a witness tx if it doesn't have a witness. if (!this.hasWitness()) return bcoin.protocol.framer.witnessTX(this); @@ -526,37 +530,68 @@ TX.prototype.isFinal = function isFinal(height, ts) { }; TX.prototype.getSigops = function getSigops(scriptHash, accurate) { - var n = 0; + var total = 0; + this.inputs.forEach(function(input) { var prev; - n += bcoin.script.getSigops(input.script, accurate); + + if (!input.output) + return; + + prev = input.output.script; + + total += bcoin.script.getSigops(input.script, accurate); + if (scriptHash && !this.isCoinbase()) { - prev = input.output ? input.output.script : null; - n += bcoin.script.getScripthashSigops(input.script, prev); + if (!bcoin.script.isScripthash(prev)) + return; + + if (!bcoin.script.isPushOnly(input.script)) + return; + + prev = bcoin.script.getRedeem(input.script); + + total += bcoin.script.getSigops(prev, true); } }, this); + this.outputs.forEach(function(output) { - n += bcoin.script.getSigops(output.script, accurate); + total += bcoin.script.getSigops(output.script, accurate); }, this); - return n; + + return total; }; TX.prototype.getSigopsCost = function getSigopsCost(scriptHash, accurate) { - var n = 0; + var cost = this.getSigops(scriptHash, accurate) * 4; + this.inputs.forEach(function(input) { var prev; - n += bcoin.script.getSigops(input.script, accurate) * 4; - if (scriptHash && !this.isCoinbase()) { - prev = input.output ? input.output.script : null; - n += bcoin.script.getScripthashSigops(input.script, prev) * 4; + + if (!input.output) + return; + + prev = input.output.script; + + if (bcoin.script.isScripthash(prev)) + prev = bcoin.script.getRedeem(input.script); + + if (bcoin.script.isWitnessScripthash(prev)) { + prev = bcoin.script.getRedeem(input.witness); + cost += bcoin.script.getSigops(prev, true); + } else { + cost += 0; } - n += bcoin.script.getWitnessScripthashSigops(input.witness, prev); }, this); + this.outputs.forEach(function(output) { - n += bcoin.script.getSigops(output.script, accurate) * 4; - n += bcoin.script.getWitnessSigops(output.script); + if (bcoin.script.isWitnessPubkeyhash(output.script)) + cost += 1; + else + cost += 0; }, this); - return n; + + return cost; }; TX.prototype.isStandard = function isStandard(flags) { @@ -639,8 +674,16 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { stack = []; + // Bitcoind doesn't do this, but it's possible someone + // could DoS us by sending ridiculous txs to the mempool + // if we don't put this here. + if (!bcoin.script.isPushOnly(input.script)) + return false; + res = bcoin.script.execute(input.script, stack, this, i, flags); + // TODO: Segwit here. + if (!res) return false; @@ -649,15 +692,17 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { if (stack.length === 0) return false; - redeem = bcoin.script.getRedeem(stack); + redeem = stack.pop(); - if (!redeem) + if (!Buffer.isBuffer(redeem)) return false; // Not accurate? - if (bcoin.script.getSize(redeem) > 520) + if (redeem.length > 520) return false; + redeem = bcoin.script.decode(redeem); + // Also consider scripthash "unknown"? if (bcoin.script.getType(redeem) === 'unknown') { if (bcoin.script.getSigops(redeem, true) > maxSigops) diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 8b790a77..6d7bca13 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1667,3 +1667,17 @@ utils.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) { return hash; }; + +utils.indexOf = function indexOf(arr, buf) { + var i; + + assert(Array.isArray(arr)); + assert(Buffer.isBuffer(buf)); + + for (i = 0; i < arr.length; i++) { + if (utils.isEqual(arr[i], buf)) + return i; + } + + return -1; +}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index d4d8e72f..f91bb0fe 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -51,6 +51,7 @@ function Wallet(options) { this.labelMap = {}; this.change = []; this.receive = []; + this.program = options.program || false; this.accountIndex = options.accountIndex || 0; this.receiveDepth = options.receiveDepth || 1; @@ -70,10 +71,6 @@ function Wallet(options) { this.type = 'multisig'; assert(this.type === 'pubkeyhash' || this.type === 'multisig'); - this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash'; - - if (network.address.prefixes[this.prefixType] == null) - throw new Error('Unknown prefix: ' + this.prefixType); if (this.m < 1 || this.m > this.n) throw new Error('m ranges between 1 and n'); @@ -360,6 +357,7 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { index: data.index, path: data.path, type: this.type, + program: this.program, m: this.m, n: this.n, keys: [], @@ -376,9 +374,12 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { this.addressMap[address.getKeyAddress()] = data.path; - if (this.prefixType === 'scripthash') + if (this.type === 'multisig') this.addressMap[address.getScriptAddress()] = data.path; + if (this.program) + this.addressMap[address.getProgramAddress()] = data.path; + this.emit('add address', address); return address; @@ -467,10 +468,30 @@ Wallet.prototype.getScriptHash = function getScriptHash() { return this.receiveAddress.getScriptHash(); }; +Wallet.prototype.getScriptHash160 = function getScriptHash160() { + return this.receiveAddress.getScriptHash160(); +}; + +Wallet.prototype.getScriptHash256 = function getScriptHash256() { + return this.receiveAddress.getScriptHash160(); +}; + Wallet.prototype.getScriptAddress = function getScriptAddress() { return this.receiveAddress.getScriptAddress(); }; +Wallet.prototype.getProgram = function getProgram() { + return this.receiveAddress.getProgram(); +}; + +Wallet.prototype.getProgramHash = function getProgramHash() { + return this.receiveAddress.getProgramHash(); +}; + +Wallet.prototype.getProgramAddress = function getProgramAddress() { + return this.receiveAddress.getProgramAddress(); +}; + Wallet.prototype.getPublicKey = function getPublicKey(enc) { return this.receiveAddress.getPublicKey(enc); }; @@ -851,6 +872,7 @@ Wallet.prototype.toJSON = function toJSON(noPool) { type: this.type, m: this.m, n: this.n, + program: this.program, derivation: this.derivation, copayBIP45: this.copayBIP45, accountIndex: this.accountIndex, @@ -882,6 +904,7 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) { type: json.type, m: json.m, n: json.n, + program: json.program, derivation: json.derivation, copayBIP45: json.copayBIP45, accountIndex: json.accountIndex, diff --git a/test/wallet-test.js b/test/wallet-test.js index ee4592b2..2030ecfb 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -38,14 +38,21 @@ describe('Wallet', function() { assert(!bcoin.address.validate('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc')); }); - it('should sign/verify TX', function() { - var w = bcoin.wallet(); + function p2pkh(program, bullshitNesting) { + var flags = bcoin.protocol.constants.flags.STANDARD_VERIFY_FLAGS; + + if (program) + flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS; + + var w = bcoin.wallet({ program: program }); // Input transcation var src = bcoin.mtx({ outputs: [{ value: 5460 * 2, - address: w.getAddress() + address: bullshitNesting + ? w.getProgramAddress() + : w.getAddress() }, { value: 5460 * 2, address: bcoin.address.compileData(new Buffer([])) @@ -61,7 +68,19 @@ describe('Wallet', function() { .addOutput(w.getAddress(), 5460); w.sign(tx); - assert(tx.verify()); + assert(tx.verify(null, true, flags)); + } + + it('should sign/verify pubkeyhash tx', function() { + p2pkh(false, false); + }); + + it('should sign/verify witnesspubkeyhash tx', function() { + p2pkh(true, false); + }); + + it('should sign/verify witnesspubkeyhash tx with bullshit nesting', function() { + p2pkh(true, true); }); it('should multisign/verify TX', function() { @@ -269,9 +288,15 @@ describe('Wallet', function() { cb(); }); - it('should verify 2-of-3 p2sh tx', function(cb) { + function multisig(program, bullshitNesting, cb) { + var flags = bcoin.protocol.constants.flags.STANDARD_VERIFY_FLAGS; + + if (program) + flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS; + // Create 3 2-of-3 wallets with our pubkeys as "shared keys" var w1 = bcoin.wallet({ + program: program, derivation: 'bip44', type: 'multisig', m: 2, @@ -279,6 +304,7 @@ describe('Wallet', function() { }); var w2 = bcoin.wallet({ + program: program, derivation: 'bip44', type: 'multisig', m: 2, @@ -286,6 +312,7 @@ describe('Wallet', function() { }); var w3 = bcoin.wallet({ + program: program, derivation: 'bip44', type: 'multisig', m: 2, @@ -309,11 +336,22 @@ describe('Wallet', function() { assert.equal(w2.getAddress(), addr); assert.equal(w3.getAddress(), addr); + var paddr = w1.getProgramAddress(); + assert.equal(w1.getProgramAddress(), paddr); + assert.equal(w2.getProgramAddress(), paddr); + assert.equal(w3.getProgramAddress(), paddr); + // Add a shared unspent transaction to our wallets var utx = bcoin.mtx(); - utx.addOutput({ address: addr, value: 5460 * 10 }); + if (bullshitNesting) + utx.addOutput({ address: paddr, value: 5460 * 10 }); + else + utx.addOutput({ address: addr, value: 5460 * 10 }); + utx.addInput(dummyInput); + assert(w1.ownOutput(utx.outputs[0])); + // Simulate a confirmation utx.ps = 0; utx.ts = 1; @@ -337,17 +375,15 @@ describe('Wallet', function() { // Create a tx requiring 2 signatures var send = bcoin.mtx(); send.addOutput({ address: receive.getAddress(), value: 5460 }); - assert(!send.verify()); + assert(!send.verify(null, true, flags)); var result = w1.fill(send, { m: w1.m, n: w1.n }); assert(result); w1.sign(send); - // console.log(bcoin.script.format(send.inputs[0])); - - assert(!send.verify()); + assert(!send.verify(null, true, flags)); w2.sign(send); - assert(send.verify()); + assert(send.verify(null, true, flags)); assert.equal(w1.changeDepth, 1); var change = w1.changeAddress.getAddress(); @@ -374,8 +410,12 @@ describe('Wallet', function() { assert.equal(w2.changeAddress.getAddress(), change); assert.equal(w3.changeAddress.getAddress(), change); - send.inputs[0].script[2] = 0; - assert(!send.verify(null, true)); + if (program) + send.inputs[0].witness[2] = new Buffer([]); + else + send.inputs[0].script[2] = 0; + + assert(!send.verify(null, true, flags)); assert.equal(send.getFee().toNumber(), 10000); w3 = bcoin.wallet.fromJSON(w3.toJSON()); @@ -385,6 +425,18 @@ describe('Wallet', function() { assert.equal(w3.changeAddress.getAddress(), change); cb(); + } + + it('should verify 2-of-3 scripthash tx', function(cb) { + multisig(false, false, cb); + }); + + it('should verify 2-of-3 witnessscripthash tx', function(cb) { + multisig(true, false, cb); + }); + + it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', function(cb) { + multisig(true, true, cb); }); var coinbase = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff2c027156266a24aa21a9edb1e139795984903d6629ddedf3763fb9bc582fd68a46b1f8c7c57f9fbcc7fc900101ffffffff02887d102a0100000023210290dd626747729e1cc445cb9a11cfb7e78ea896db9f5c335e6730491d9ee7474dac0000000000000000266a24aa21a9edb1e139795984903d6629ddedf3763fb9bc582fd68a46b1f8c7c57f9fbcc7fc900120000000000000000000000000000000000000000000000000000000000000000000000000'; @@ -404,11 +456,8 @@ describe('Wallet', function() { }] }); src.addInput(dummyInput); - console.log(src.toJSON()); var t = bcoin.protocol.parser.parseWitnessTX(new Buffer(coinbase, 'hex')); - utils.print(t); var t = new bcoin.tx(bcoin.protocol.parser.parseWitnessTX(new Buffer(w2pkh, 'hex'))); - utils.print(t); delete t._raw; delete t._hash; delete t._whash;