segwit wallet support. segwit chain improvements.

This commit is contained in:
Christopher Jeffrey 2016-02-28 01:33:59 -08:00
parent bd868cda7a
commit 3fcc7d5b07
11 changed files with 642 additions and 418 deletions

View File

@ -42,16 +42,11 @@ function Address(options) {
this.keys = []; this.keys = [];
this.m = options.m || 1; this.m = options.m || 1;
this.n = options.n || 1; this.n = options.n || 1;
this.redeem = null;
if (this.n > 1) if (this.n > 1)
this.type = 'multisig'; this.type = 'multisig';
assert(this.type === 'pubkeyhash' || 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) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
@ -61,9 +56,6 @@ function Address(options) {
(options.keys || []).forEach(function(key) { (options.keys || []).forEach(function(key) {
this.addKey(key); this.addKey(key);
}, this); }, this);
if (options.redeem || options.script)
this.setRedeem(options.redeem || options.script);
} }
utils.inherits(Address, EventEmitter); utils.inherits(Address, EventEmitter);
@ -100,22 +92,7 @@ Address.prototype.getBalance = function getBalance() {
return this._wallet.getBalance(this); 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) { Address.prototype.addKey = function addKey(key) {
var old = this.getScriptAddress();
var cur;
key = utils.ensureBuffer(key); key = utils.ensureBuffer(key);
var has = this.keys.some(function(k) { var has = this.keys.some(function(k) {
@ -128,22 +105,9 @@ Address.prototype.addKey = function addKey(key) {
this.keys.push(key); this.keys.push(key);
this.keys = utils.sortKeys(this.keys); 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) { Address.prototype.removeKey = function removeKey(key) {
var old = this.getScriptAddress();
var cur;
key = utils.ensureBuffer(key); key = utils.ensureBuffer(key);
var index = this.keys.map(function(k, i) { 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.splice(index, 1);
this.keys = utils.sortKeys(this.keys); 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) { Address.prototype.getPrivateKey = function getPrivateKey(enc) {
@ -177,65 +131,122 @@ Address.prototype.getPrivateKey = function getPrivateKey(enc) {
Address.prototype.getScript = function getScript() { Address.prototype.getScript = function getScript() {
var redeem; var redeem;
if (this.prefixType !== 'scripthash') if (this.type !== 'multisig')
return; return;
if (this._script) if (this._script)
return this._script; return this._script;
if (this.redeem) { assert(this.keys.length === this.n, 'Not all keys have been added.');
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);
}
if (redeem.length > 520) redeem = bcoin.script.createMultisig(this.keys, this.m, this.n);
throw new Error('Redeem script too large (520 byte limit).'); 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; this._script = redeem;
return this._script; return this._script;
}; };
Address.prototype.getScriptHash = function getScriptHash() { Address.prototype.getProgram = function getProgram() {
if (this.prefixType !== 'scripthash') var program;
if (!this.options.program)
return; return;
if (this._scriptHash) if (this._program)
return this._scriptHash; 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() { Address.prototype.getScriptAddress = function getScriptAddress() {
if (this.prefixType !== 'scripthash') if (this.type !== 'multisig')
return; return;
if (this._scriptAddress) if (this._scriptAddress)
return 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; return this._scriptAddress;
}; };
Address.prototype.getPublicKey = function getPublicKey(enc) { 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); return this.key.getPublicKey(enc);
}; };
@ -252,19 +263,22 @@ Address.prototype.getKeyAddress = function getKeyAddress() {
if (this._address) if (this._address)
return 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; return this._address;
}; };
Address.prototype.getHash = function getHash() { Address.prototype.getHash = function getHash() {
if (this.prefixType === 'scripthash') if (this.type === 'multisig')
return this.getScriptHash(); return this.getScriptHash();
return this.getKeyHash(); return this.getKeyHash();
}; };
Address.prototype.getAddress = function getAddress() { Address.prototype.getAddress = function getAddress() {
if (this.prefixType === 'scripthash') if (this.type === 'multisig')
return this.getScriptAddress(); return this.getScriptAddress();
return this.getKeyAddress(); return this.getKeyAddress();
}; };
@ -277,9 +291,12 @@ Address.prototype._getAddressMap = function _getAddressMap() {
this.addressMap[this.getKeyAddress()] = true; this.addressMap[this.getKeyAddress()] = true;
if (this.prefixType === 'scripthash') if (this.type === 'multisig')
this.addressMap[this.getScriptAddress()] = true; this.addressMap[this.getScriptAddress()] = true;
if (this.options.program)
this.addressMap[this.getProgramAddress()] = true;
return this.addressMap; return this.addressMap;
}; };
@ -351,7 +368,7 @@ Address.prototype.scriptInputs = function scriptInputs(tx, index) {
if (!self.ownOutput(input.output)) if (!self.ownOutput(input.output))
return total; return total;
if (tx.scriptInput(i, publicKey, redeem)) if (tx.scriptInput(i, self))
total++; total++;
return total; return total;
@ -379,7 +396,7 @@ Address.prototype.signInputs = function signInputs(tx, type, index) {
if (!self.ownOutput(input.output)) if (!self.ownOutput(input.output))
return total; return total;
if (tx.signInput(i, key, type)) if (tx.signInput(i, self, type))
total++; total++;
return total; return total;
@ -409,7 +426,7 @@ Address.prototype.sign = function sign(tx, type, index) {
if (!self.ownOutput(input.output)) if (!self.ownOutput(input.output))
return total; return total;
if (tx.sign(i, key, redeem, type)) if (tx.sign(i, self, type))
total++; total++;
return total; return total;
@ -424,10 +441,30 @@ Address.prototype.__defineGetter__('scriptHash', function() {
return this.getScriptHash(); return this.getScriptHash();
}); });
Address.prototype.__defineGetter__('scriptHash160', function() {
return this.getScriptHash160();
});
Address.prototype.__defineGetter__('scriptHash256', function() {
return this.getScriptHash256();
});
Address.prototype.__defineGetter__('scriptAddress', function() { Address.prototype.__defineGetter__('scriptAddress', function() {
return this.getScriptAddress(); 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() { Address.prototype.__defineGetter__('privateKey', function() {
return this.getPrivateKey(); return this.getPrivateKey();
}); });

View File

@ -455,7 +455,7 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback)
Chain.prototype._verify = function _verify(block, prev) { Chain.prototype._verify = function _verify(block, prev) {
var flags = constants.flags.MANDATORY_VERIFY_FLAGS; var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
var height, ts, i, tx, cb, coinbaseHeight, medianTime; var height, ts, i, tx, cb, coinbaseHeight, medianTime;
var locktimeMedian, coinbase, commitment, segwit; var locktimeMedian, segwit;
if (!block.verify()) if (!block.verify())
return flags; return flags;
@ -575,11 +575,16 @@ Chain.prototype._verify = function _verify(block, prev) {
} }
} }
if (segwit) { if (block.version >= 5 && segwit) {
if (block._witness && block.commitmentHash !== block.getCommitmentHash()) { if (block.commitmentHash !== block.getCommitmentHash()) {
utils.debug('Block failed witnessroot test: %s', block.rhash); utils.debug('Block failed witnessroot test: %s', block.rhash);
return false; return false;
} }
} else {
if (block.hasWitness()) {
utils.debug('Unexpected witness data found: %s', block.rhash);
return false;
}
} }
// Get timestamp for tx.isFinal(). // Get timestamp for tx.isFinal().
@ -655,11 +660,11 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
var i, j, input, hash; var i, j, input, hash;
var sigops = 0; var sigops = 0;
utils.print(height); // utils.print(height);
utils.print(block.commitmentHash ? utils.revHex(block.commitmentHash) : null); // utils.print(block.commitmentHash ? utils.revHex(block.commitmentHash) : null);
utils.print(utils.revHex(block.getCommitmentHash() || '00')); // utils.print(utils.revHex(block.getCommitmentHash() || '00'));
utils.print(block.txs[0]); // utils.print(block.txs[0]);
utils.print(block.txs[1]); // utils.print(block.txs[1]);
if (err) if (err)
return callback(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!'); throw new Error('BUG: Bad inputs in historical data!');
return callback(null, false); return callback(null, false);
} }
if (input.output.getType() === 'scripthash')
utils.debug(tx);
} }
if (!scriptCheck) if (!scriptCheck)

View File

@ -207,6 +207,9 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
if (tx.isCoinbase()) if (tx.isCoinbase())
return callback(new Error('What?')); return callback(new Error('What?'));
if (!this.checkTX(tx, peer))
return callback(new Error('TX failed checkTX.'));
assert(tx.ts === 0); assert(tx.ts === 0);
this._lockTX(tx); this._lockTX(tx);
@ -219,6 +222,9 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
if (err) if (err)
return callback(err); return callback(err);
// Do this in the future.
// tx = self.fillCoin(tx);
if (!tx.hasPrevout()) { if (!tx.hasPrevout()) {
return callback(new Error('Previous outputs not found.')); return callback(new Error('Previous outputs not found.'));
peer.reject({ peer.reject({
@ -465,6 +471,67 @@ Mempool.prototype.unlock = function unlock() {
this.locked = false; 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 * Expose
*/ */

View File

@ -67,6 +67,7 @@ MTX.prototype.clone = function clone() {
tx.inputs = tx.inputs.map(function(input) { tx.inputs = tx.inputs.map(function(input) {
input.script = input.script.slice(); input.script = input.script.slice();
input.witness = input.witness.slice();
return input; return input;
}); });
@ -129,8 +130,8 @@ MTX.prototype.addInput = function addInput(options, index) {
return this; return this;
}; };
MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) { MTX.prototype.scriptInput = function scriptInput(index, addr) {
var input, s, n, i; var input, prev, n, i, redeemScript, witnessScript, vector, dummy;
if (typeof index !== 'number') if (typeof index !== 'number')
index = this.inputs.indexOf(index); index = this.inputs.indexOf(index);
@ -139,102 +140,173 @@ MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) {
input = this.inputs[index]; input = this.inputs[index];
assert(input); assert(input);
// Already has a script template (at least)
// if (input.script.length)
// return;
// We should have previous outputs by now. // We should have previous outputs by now.
assert(input.output); assert(input.output);
// Get the previous output's subscript // Optimization: Don't bother with any below
s = input.output.script; // calculation if the output is already templated.
// Just say this is "our" output.
if (input.script.length || input.witness.length)
return true;
// P2SH // Optimization: test output against the
if (bcoin.script.isScripthash(s)) { // address map to avoid unnecessary calculation.
if (!redeem) // 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; 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 { } 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 // P2PK
if (!utils.isEqual(s[0], publicKey)) if (!utils.isEqual(prev[0], addr.publicKey))
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (vector.length)
return true; return true;
input.script = [0];
} else if (bcoin.script.isPubkeyhash(s)) { vector[0] = dummy;
} else if (bcoin.script.isPubkeyhash(prev)) {
// P2PKH // P2PKH
if (!utils.isEqual(s[2], bcoin.address.hash160(publicKey))) if (!utils.isEqual(prev[2], addr.keyHash))
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (vector.length)
return true; 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 // Multisig
for (i = 0; i < s.length; i++) { for (i = 0; i < prev.length; i++) {
if (utils.isEqual(s[i], publicKey)) if (utils.isEqual(prev[i], addr.publicKey))
break; break;
} }
if (i === s.length) if (i === prev.length)
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (vector.length)
return true; return true;
// Technically we should create m signature slots, // Technically we should create m signature slots,
// but we create n signature slots so we can order // but we create n signature slots so we can order
// the signatures properly. // the signatures properly.
input.script = [0]; vector[0] = dummy;
// Grab `n` value (number of keys). // Grab `n` value (number of keys).
n = s[s.length - 2]; n = prev[prev.length - 2];
// Fill script with `n` signature slots. // Fill script with `n` signature slots.
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
input.script[i + 1] = 0; vector[i + 1] = dummy;
} else { } else {
for (i = 0; i < s.length; i++) { for (i = 0; i < prev.length; i++) {
if (utils.isEqual(s[i], publicKey)) if (utils.isEqual(prev[i], addr.publicKey))
break; break;
} }
if (i === s.length) if (i === prev.length)
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (vector.length)
return true; return true;
// Likely a non-standard scripthash multisig // Likely a non-standard scripthash multisig
// input. Determine n value by counting keys. // input. Determine n value by counting keys.
// Also, only allow nonstandard types for // Also, only allow nonstandard types for
// scripthash. // scripthash.
if (redeem) { vector[0] = dummy;
input.script = [0];
// Fill script with `n` signature slots. // Fill script with `n` signature slots.
for (i = 0; i < s.length; i++) { for (i = 0; i < prev.length; i++) {
if (bcoin.script.isKey(s[i])) if (bcoin.script.isKey(prev[i]))
input.script.push(0); vector[i + 1] = dummy;
}
} }
} }
// P2SH requires the redeem script after signatures // P2SH requires the redeem
if (redeem) // script after signatures.
input.script.push(redeem); if (redeemScript)
input.script.push(redeemScript);
// P2WSH requires the witness
// script after signatures.
if (witnessScript)
input.witness.push(witnessScript);
return true; return true;
}; };
MTX.prototype.createSignature = function createSignature(index, key, type) { MTX.prototype.createSignature = function createSignature(index, prev, key, type) {
var input, s, hash, signature; var prev, hash, signature;
if (typeof index !== 'number') if (typeof index !== 'number')
index = this.inputs.indexOf(index); index = this.inputs.indexOf(index);
@ -245,38 +317,22 @@ MTX.prototype.createSignature = function createSignature(index, key, type) {
if (typeof type === 'string') if (typeof type === 'string')
type = constants.hashType[type]; 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 // Get the hash of the current tx, minus the other
// inputs, plus the sighash type. // inputs, plus the sighash type.
hash = this.signatureHash(index, s, type); hash = this.signatureHash(index, prev, type);
// Sign the transaction with our one input // Sign the transaction with our one input
signature = bcoin.script.sign(hash, key, type); signature = bcoin.script.sign(hash, key, type);
// Something is broken if this doesn't work: // 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; return signature;
}; };
// Sign the now-built scriptSigs MTX.prototype.signInput = function signInput(index, addr, type) {
MTX.prototype.signInput = function signInput(index, key, type) { var input, prev, signature, ki, signatures, i;
var input, s, signature, ki, signatures, i; var len, m, n, keys, vector, dummy;
var len, m, n, keys, publicKey, keyHash;
if (typeof index !== 'number') if (typeof index !== 'number')
index = this.inputs.indexOf(index); index = this.inputs.indexOf(index);
@ -288,87 +344,103 @@ MTX.prototype.signInput = function signInput(index, key, type) {
// We should have previous outputs by now. // We should have previous outputs by now.
assert(input.output); assert(input.output);
// Create our signature.
signature = this.createSignature(index, key, type);
// Get the previous output's subscript // Get the previous output's subscript
s = input.output.script; prev = input.output.script;
// Script length, needed for multisig vector = input.script;
len = input.script.length; len = vector.length;
dummy = 0;
// We need to grab the redeem script when // We need to grab the redeem script when
// signing p2sh transactions. // signing p2sh transactions.
if (bcoin.script.isScripthash(s)) { if (bcoin.script.isScripthash(prev)) {
s = bcoin.script.getRedeem(input.script); prev = bcoin.script.getRedeem(input.script);
// Decrement `len` to avoid the redeem script len = vector.length - 1;
len--;
} }
// Get pubkey. // If the output script is a witness program,
publicKey = key.getPublicKey(); // 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. // Add signatures.
if (bcoin.script.isPubkey(s)) { if (bcoin.script.isPubkey(prev)) {
// P2PK // P2PK
// Already signed. // Already signed.
if (bcoin.script.isSignature(input.script[0])) if (bcoin.script.isSignature(vector[0]))
return true; return true;
// Make sure the pubkey is ours. // Make sure the pubkey is ours.
if (!utils.isEqual(publicKey, s[0])) if (!utils.isEqual(addr.publicKey, prev[0]))
return false; return false;
input.script[0] = signature; vector[0] = signature;
return true; return true;
} }
if (bcoin.script.isPubkeyhash(s)) { if (bcoin.script.isPubkeyhash(prev)) {
// P2PKH // P2PKH
// Already signed. // Already signed.
if (bcoin.script.isSignature(input.script[0])) if (bcoin.script.isSignature(vector[0]))
return true; return true;
// Make sure the pubkey hash is ours. // Make sure the pubkey hash is ours.
keyHash = bcoin.address.hash160(publicKey); if (!utils.isEqual(addr.keyHash, prev[2]))
if (!utils.isEqual(keyHash, s[2]))
return false; return false;
input.script[0] = signature; vector[0] = signature;
return true; return true;
} }
if (bcoin.script.isMultisig(s)) { if (bcoin.script.isMultisig(prev)) {
// Multisig // Multisig
// Grab the redeem script's keys to figure // Grab the redeem script's keys to figure
// out where our key should go. // out where our key should go.
keys = s.slice(1, -2); keys = prev.slice(1, -2);
// Grab `m` value (number of sigs required). // Grab `m` value (number of sigs required).
m = s[0]; m = prev[0];
// Grab `n` value (number of keys). // Grab `n` value (number of keys).
n = s[s.length - 2]; n = prev[prev.length - 2];
} else { } else {
// Only allow non-standard signing for // Only allow non-standard signing for
// scripthash. // scripthash.
if (len !== input.script.length - 1) if (len !== vector.length - 1)
return false; return false;
keys = []; keys = [];
for (i = 0; i < s.length; i++) { for (i = 0; i < prev.length; i++) {
if (bcoin.script.isKey(s[i])) if (bcoin.script.isKey(prev[i]))
keys.push(s[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; n = keys.length;
m = n;
} }
// Something is very wrong here. Abort. // Something is very wrong here. Abort.
@ -378,7 +450,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
// Count the number of current signatures. // Count the number of current signatures.
signatures = 0; signatures = 0;
for (i = 1; i < len; i++) { for (i = 1; i < len; i++) {
if (bcoin.script.isSignature(input.script[i])) if (bcoin.script.isSignature(vector[i]))
signatures++; signatures++;
} }
@ -391,14 +463,14 @@ MTX.prototype.signInput = function signInput(index, key, type) {
// or by `m`. Add some signature slots for // or by `m`. Add some signature slots for
// us to use. // us to use.
while (len - 1 < n) { while (len - 1 < n) {
input.script.splice(len, 0, 0); vector.splice(len, 0, dummy);
len++; len++;
} }
// Find the key index so we can place // Find the key index so we can place
// the signature in the same index. // the signature in the same index.
for (ki = 0; ki < keys.length; ki++) { for (ki = 0; ki < keys.length; ki++) {
if (utils.isEqual(publicKey, keys[ki])) if (utils.isEqual(addr.publicKey, keys[ki]))
break; break;
} }
@ -417,8 +489,8 @@ MTX.prototype.signInput = function signInput(index, key, type) {
// and increment the total number of // and increment the total number of
// signatures. // signatures.
if (ki < len && signatures < m) { if (ki < len && signatures < m) {
if (input.script[ki] === 0) { if (bcoin.script.isZero(vector[ki])) {
input.script[ki] = signature; vector[ki] = signature;
signatures++; signatures++;
} }
} }
@ -427,8 +499,8 @@ MTX.prototype.signInput = function signInput(index, key, type) {
if (signatures >= m) { if (signatures >= m) {
// Remove empty slots left over. // Remove empty slots left over.
for (i = len - 1; i >= 1; i--) { for (i = len - 1; i >= 1; i--) {
if (input.script[i] === 0) { if (bcoin.script.isZero(vector[i])) {
input.script.splice(i, 1); vector.splice(i, 1);
len--; len--;
} }
} }
@ -438,7 +510,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
// with implementations that potentially handle // with implementations that potentially handle
// signature slots differently. // signature slots differently.
while (signatures > m) { while (signatures > m) {
input.script.splice(len - 1, 1); vector.splice(len - 1, 1);
signatures--; signatures--;
len--; len--;
} }
@ -451,8 +523,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
return signatures === m; return signatures === m;
}; };
MTX.prototype.sign = function sign(index, key, redeem, type) { MTX.prototype.sign = function sign(index, addr, type) {
var publicKey = key.getPublicKey();
var input; var input;
if (index && typeof index === 'object') if (index && typeof index === 'object')
@ -462,104 +533,16 @@ MTX.prototype.sign = function sign(index, key, redeem, type) {
assert(input); assert(input);
// Build script for input // Build script for input
if (!this.scriptInput(index, publicKey, redeem)) if (!this.scriptInput(index, addr))
return false; return false;
// Sign input // Sign input
if (!this.signInput(index, key, type)) if (!this.signInput(index, addr, type))
return false; return false;
return true; 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) { MTX.prototype.addOutput = function addOutput(obj, value) {
var options, output; var options, output;
@ -667,7 +650,7 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) {
MTX.prototype.maxSize = function maxSize(maxM, maxN) { MTX.prototype.maxSize = function maxSize(maxM, maxN) {
var copy = this.clone(); 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 // Create copy with 0-script inputs
for (i = 0; i < copy.inputs.length; i++) for (i = 0; i < copy.inputs.length; i++)
@ -683,38 +666,38 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
assert(input.output); assert(input.output);
// Get the previous output's subscript // Get the previous output's subscript
s = input.output.script; prev = input.output.script;
// If we have access to the redeem script, // If we have access to the redeem script,
// we can use it to calculate size much easier. // we can use it to calculate size much easier.
if (this.inputs[i].script.length && bcoin.script.isScripthash(s)) { if (this.inputs[i].script.length && bcoin.script.isScripthash(prev)) {
s = bcoin.script.getRedeem(this.inputs[i].script); prev = bcoin.script.getRedeem(this.inputs[i].script);
// Need to add the redeem script size // Need to add the redeem script size
// here since it will be ignored by // here since it will be ignored by
// the isMultisig clause. // the isMultisig clause.
// OP_PUSHDATA2 [redeem] // 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 // P2PK
// OP_PUSHDATA0 [signature] // OP_PUSHDATA0 [signature]
size += 1 + 73; size += 1 + 73;
} else if (bcoin.script.isPubkeyhash(s)) { } else if (bcoin.script.isPubkeyhash(prev)) {
// P2PKH // P2PKH
// OP_PUSHDATA0 [signature] // OP_PUSHDATA0 [signature]
size += 1 + 73; size += 1 + 73;
// OP_PUSHDATA0 [key] // OP_PUSHDATA0 [key]
size += 1 + 33; size += 1 + 33;
} else if (bcoin.script.isMultisig(s)) { } else if (bcoin.script.isMultisig(prev)) {
// Bare Multisig // Bare Multisig
// Get the previous m value: // Get the previous m value:
m = s[0]; m = prev[0];
// OP_0 // OP_0
size += 1; size += 1;
// OP_PUSHDATA0 [signature] ... // OP_PUSHDATA0 [signature] ...
size += (1 + 73) * m; size += (1 + 73) * m;
} else if (bcoin.script.isScripthash(s)) { } else if (bcoin.script.isScripthash(prev)) {
// P2SH Multisig // P2SH Multisig
// This technically won't work well for other // This technically won't work well for other
// kinds of P2SH. It will also over-estimate // kinds of P2SH. It will also over-estimate
@ -744,8 +727,8 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
size += 1; size += 1;
} else { } else {
// OP_PUSHDATA0 [signature] // OP_PUSHDATA0 [signature]
for (j = 0; j < s.length; j++) { for (j = 0; j < prev.length; j++) {
if (bcoin.script.isKey(s[j])) if (bcoin.script.isKey(prev[j]))
size += 1 + 73; size += 1 + 73;
} }
} }

View File

@ -1260,13 +1260,21 @@ Pool.prototype.removeWallet = function removeWallet(wallet) {
}; };
Pool.prototype.watchAddress = function watchAddress(address) { Pool.prototype.watchAddress = function watchAddress(address) {
if (address.type === 'scripthash') { if (address.script) {
// For the redeem script hash in outputs: // For the redeem script hash in outputs:
this.watch(address.getScriptHash()); this.watch(address.getScriptHash160());
// For the redeem script in inputs: // For the redeem script in inputs:
this.watch(address.getScript()); 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: // For the pubkey hash in outputs:
this.watch(address.getKeyHash()); this.watch(address.getKeyHash());
// For the pubkey in inputs: // For the pubkey in inputs:
@ -1274,13 +1282,21 @@ Pool.prototype.watchAddress = function watchAddress(address) {
}; };
Pool.prototype.unwatchAddress = function unwatchAddress(address) { Pool.prototype.unwatchAddress = function unwatchAddress(address) {
if (address.type === 'scripthash') { if (addres.script) {
// For the redeem script hash in p2sh outputs: // For the redeem script hash in p2sh outputs:
this.unwatch(address.getScriptHash()); this.unwatch(address.getScriptHash160());
// For the redeem script in p2sh inputs: // For the redeem script in p2sh inputs:
this.unwatch(address.getScript()); 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: // For the pubkey hash in p2pk/multisig outputs:
this.unwatch(address.getKeyHash()); this.unwatch(address.getKeyHash());
// For the pubkey in p2pkh inputs: // For the pubkey in p2pkh inputs:

View File

@ -583,10 +583,16 @@ Framer._block = function _block(block, witness) {
var off = 0; var off = 0;
var txSize = 0; var txSize = 0;
var txs = []; var txs = [];
var hasWitness;
var i, tx, p; var i, tx, p;
for (i = 0; i < block.txs.length; i++) { 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.witnessTX(block.txs[i])
: Framer.tx(block.txs[i]); : Framer.tx(block.txs[i]);
txs.push(tx); txs.push(tx);

View File

@ -206,18 +206,19 @@ script.encode = function encode(s) {
// Witnesses aren't scripts, but we still // Witnesses aren't scripts, but we still
// want to convert [0] to OP_0, [0xff] to 1negate, etc. // want to convert [0] to OP_0, [0xff] to 1negate, etc.
script.decodeWitness = function decodeWitness(witness) { script.decodeWitness = function decodeWitness(witness) {
var chunk, i, op;
var script = []; var script = [];
var chunk, i;
for (i = 0; i < witness.length; i++) { for (i = 0; i < witness.length; i++) {
chunk = witness[i]; chunk = witness[i];
op = chunk;
if (chunk.length === 1) { if (chunk.length <= 1) {
if (chunk[0] === 0xff) if (chunk.length === 0)
op = '1negate'; op = new Buffer([]);
else if (chunk[0] >= 0 && chunk <= 16) else if (chunk[0] >= 1 && chunk <= 16)
op = chunk[0]; op = chunk[0];
else if (chunk[0] === 0xff)
op = '1negate';
} }
script.push(op); script.push(op);
@ -227,20 +228,22 @@ script.decodeWitness = function decodeWitness(witness) {
}; };
script.encodeWitness = function encodeWitness(script) { script.encodeWitness = function encodeWitness(script) {
var chunk, i, chunk;
var witness = []; var witness = [];
var chunk, i;
for (i = 0; i < script.length; i++) { for (i = 0; i < script.length; i++) {
chunk = script[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]); 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; return witness;
@ -455,29 +458,24 @@ script.verifyProgram = function verifyProgram(witness, output, tx, i, flags) {
} }
for (j = 0; j < stack.length; j++) { for (j = 0; j < stack.length; j++) {
if (stack[j].length > constants.script.maxSize) { if (stack[j].length > constants.script.maxSize)
throw new Eror('max size');
return false; 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); res = script.execute(redeem, stack, tx, i, flags);
utils.print(script.format(redeem)); utils.debug(bcoin.script.format(stack));
utils.print(script.format(stack));
// Verify the script did not fail as well as the stack values // Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !script.bool(stack.pop())) { if (!res || stack.length === 0 || !script.bool(stack.pop()))
throw new Error('script failed');
return false; return false;
}
// Witnesses always require cleanstack // Witnesses always require cleanstack
if (stack.length !== 0) { if (stack.length !== 0)
throw new Error('cleanstack');
return false; return false;
}
return true; return true;
}; };
@ -1135,6 +1133,10 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) {
} }
res = succ >= m; res = succ >= m;
if (!res)
utils.debug('checkmultisig failed: succ: %d, m: %d', succ, m);
if (o === 'checkmultisigverify') { if (o === 'checkmultisigverify') {
if (!res) if (!res)
return false; return false;
@ -2246,6 +2248,13 @@ script.isDummy = function isDummy(data) {
return data.length === 0; return data.length === 0;
}; };
script.isZero = function isZero(data) {
if (data === 0)
return true;
return script.isDummy(data);
};
script.isData = function isData(data) { script.isData = function isData(data) {
if (!Buffer.isBuffer(data)) if (!Buffer.isBuffer(data))
return false; return false;
@ -2492,70 +2501,31 @@ script.isPushOnly = function isPushOnly(s) {
script.getSigops = function getSigops(s, accurate) { script.getSigops = function getSigops(s, accurate) {
var i, op; var i, op;
var n = 0; var total = 0;
var lastOp = -1; var lastOp = -1;
for (i = 0; i < s.length; i++) { for (i = 0; i < s.length; i++) {
op = s[i]; op = s[i];
if (Buffer.isBuffer(op)) if (Buffer.isBuffer(op))
continue; continue;
if (constants.opcodes[op] == null) if (constants.opcodes[op] == null)
return 0; return 0;
if (op === 'checksig' || op === 'checksigverify') { if (op === 'checksig' || op === 'checksigverify') {
n++; total++;
} else if (op === 'checkmultisig' || op === 'checkmultisigverify') { } else if (op === 'checkmultisig' || op === 'checkmultisigverify') {
if (accurate && lastOp >= 1 && lastOp <= 16) { if (accurate && lastOp >= 1 && lastOp <= 16)
n += lastOp; total += lastOp;
} else { else
n += constants.script.maxPubkeysPerMultisig; total += constants.script.maxPubkeysPerMultisig;
}
} }
lastOp = op; lastOp = op;
} }
return n; return total;
};
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);
}; };
script.getArgs = function getArgs(s) { script.getArgs = function getArgs(s) {
@ -2581,5 +2551,11 @@ script.getArgs = function getArgs(s) {
if (script.isNulldata(s)) if (script.isNulldata(s))
return -1; return -1;
if (script.isWitnessScripthash(s))
return 1;
if (script.isWitnessPubkeyhash(s))
return 2;
return -1; return -1;
}; };

View File

@ -93,6 +93,7 @@ TX.prototype.clone = function clone() {
delete tx._witness; delete tx._witness;
delete tx._raw; delete tx._raw;
delete tx._size; delete tx._size;
delete tx._offset;
delete tx._cost; delete tx._cost;
delete tx._hash; delete tx._hash;
delete tx._whash; delete tx._whash;
@ -120,7 +121,6 @@ TX.prototype.witnessHash = function witnessHash(enc) {
: new Buffer(constants.zeroHash); : new Buffer(constants.zeroHash);
} }
// if (!this._witness)
if (!this.hasWitness()) if (!this.hasWitness())
return this.hash(enc); return this.hash(enc);
@ -160,9 +160,13 @@ TX.prototype.renderWitness = function renderWitness() {
if (this._raw) { if (this._raw) {
if (this._witness) if (this._witness)
return this._raw; 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); 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()) if (!this.hasWitness())
return bcoin.protocol.framer.witnessTX(this); return bcoin.protocol.framer.witnessTX(this);
@ -526,37 +530,68 @@ TX.prototype.isFinal = function isFinal(height, ts) {
}; };
TX.prototype.getSigops = function getSigops(scriptHash, accurate) { TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
var n = 0; var total = 0;
this.inputs.forEach(function(input) { this.inputs.forEach(function(input) {
var prev; 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()) { if (scriptHash && !this.isCoinbase()) {
prev = input.output ? input.output.script : null; if (!bcoin.script.isScripthash(prev))
n += bcoin.script.getScripthashSigops(input.script, prev); return;
if (!bcoin.script.isPushOnly(input.script))
return;
prev = bcoin.script.getRedeem(input.script);
total += bcoin.script.getSigops(prev, true);
} }
}, this); }, this);
this.outputs.forEach(function(output) { this.outputs.forEach(function(output) {
n += bcoin.script.getSigops(output.script, accurate); total += bcoin.script.getSigops(output.script, accurate);
}, this); }, this);
return n;
return total;
}; };
TX.prototype.getSigopsCost = function getSigopsCost(scriptHash, accurate) { TX.prototype.getSigopsCost = function getSigopsCost(scriptHash, accurate) {
var n = 0; var cost = this.getSigops(scriptHash, accurate) * 4;
this.inputs.forEach(function(input) { this.inputs.forEach(function(input) {
var prev; var prev;
n += bcoin.script.getSigops(input.script, accurate) * 4;
if (scriptHash && !this.isCoinbase()) { if (!input.output)
prev = input.output ? input.output.script : null; return;
n += bcoin.script.getScripthashSigops(input.script, prev) * 4;
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);
this.outputs.forEach(function(output) { this.outputs.forEach(function(output) {
n += bcoin.script.getSigops(output.script, accurate) * 4; if (bcoin.script.isWitnessPubkeyhash(output.script))
n += bcoin.script.getWitnessSigops(output.script); cost += 1;
else
cost += 0;
}, this); }, this);
return n;
return cost;
}; };
TX.prototype.isStandard = function isStandard(flags) { TX.prototype.isStandard = function isStandard(flags) {
@ -639,8 +674,16 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
stack = []; 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); res = bcoin.script.execute(input.script, stack, this, i, flags);
// TODO: Segwit here.
if (!res) if (!res)
return false; return false;
@ -649,15 +692,17 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
if (stack.length === 0) if (stack.length === 0)
return false; return false;
redeem = bcoin.script.getRedeem(stack); redeem = stack.pop();
if (!redeem) if (!Buffer.isBuffer(redeem))
return false; return false;
// Not accurate? // Not accurate?
if (bcoin.script.getSize(redeem) > 520) if (redeem.length > 520)
return false; return false;
redeem = bcoin.script.decode(redeem);
// Also consider scripthash "unknown"? // Also consider scripthash "unknown"?
if (bcoin.script.getType(redeem) === 'unknown') { if (bcoin.script.getType(redeem) === 'unknown') {
if (bcoin.script.getSigops(redeem, true) > maxSigops) if (bcoin.script.getSigops(redeem, true) > maxSigops)

View File

@ -1667,3 +1667,17 @@ utils.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) {
return hash; 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;
};

View File

@ -51,6 +51,7 @@ function Wallet(options) {
this.labelMap = {}; this.labelMap = {};
this.change = []; this.change = [];
this.receive = []; this.receive = [];
this.program = options.program || false;
this.accountIndex = options.accountIndex || 0; this.accountIndex = options.accountIndex || 0;
this.receiveDepth = options.receiveDepth || 1; this.receiveDepth = options.receiveDepth || 1;
@ -70,10 +71,6 @@ function Wallet(options) {
this.type = 'multisig'; this.type = 'multisig';
assert(this.type === 'pubkeyhash' || 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) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
@ -360,6 +357,7 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
index: data.index, index: data.index,
path: data.path, path: data.path,
type: this.type, type: this.type,
program: this.program,
m: this.m, m: this.m,
n: this.n, n: this.n,
keys: [], keys: [],
@ -376,9 +374,12 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
this.addressMap[address.getKeyAddress()] = data.path; this.addressMap[address.getKeyAddress()] = data.path;
if (this.prefixType === 'scripthash') if (this.type === 'multisig')
this.addressMap[address.getScriptAddress()] = data.path; this.addressMap[address.getScriptAddress()] = data.path;
if (this.program)
this.addressMap[address.getProgramAddress()] = data.path;
this.emit('add address', address); this.emit('add address', address);
return address; return address;
@ -467,10 +468,30 @@ Wallet.prototype.getScriptHash = function getScriptHash() {
return this.receiveAddress.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() { Wallet.prototype.getScriptAddress = function getScriptAddress() {
return this.receiveAddress.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) { Wallet.prototype.getPublicKey = function getPublicKey(enc) {
return this.receiveAddress.getPublicKey(enc); return this.receiveAddress.getPublicKey(enc);
}; };
@ -851,6 +872,7 @@ Wallet.prototype.toJSON = function toJSON(noPool) {
type: this.type, type: this.type,
m: this.m, m: this.m,
n: this.n, n: this.n,
program: this.program,
derivation: this.derivation, derivation: this.derivation,
copayBIP45: this.copayBIP45, copayBIP45: this.copayBIP45,
accountIndex: this.accountIndex, accountIndex: this.accountIndex,
@ -882,6 +904,7 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) {
type: json.type, type: json.type,
m: json.m, m: json.m,
n: json.n, n: json.n,
program: json.program,
derivation: json.derivation, derivation: json.derivation,
copayBIP45: json.copayBIP45, copayBIP45: json.copayBIP45,
accountIndex: json.accountIndex, accountIndex: json.accountIndex,

View File

@ -38,14 +38,21 @@ describe('Wallet', function() {
assert(!bcoin.address.validate('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc')); assert(!bcoin.address.validate('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc'));
}); });
it('should sign/verify TX', function() { function p2pkh(program, bullshitNesting) {
var w = bcoin.wallet(); 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 // Input transcation
var src = bcoin.mtx({ var src = bcoin.mtx({
outputs: [{ outputs: [{
value: 5460 * 2, value: 5460 * 2,
address: w.getAddress() address: bullshitNesting
? w.getProgramAddress()
: w.getAddress()
}, { }, {
value: 5460 * 2, value: 5460 * 2,
address: bcoin.address.compileData(new Buffer([])) address: bcoin.address.compileData(new Buffer([]))
@ -61,7 +68,19 @@ describe('Wallet', function() {
.addOutput(w.getAddress(), 5460); .addOutput(w.getAddress(), 5460);
w.sign(tx); 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() { it('should multisign/verify TX', function() {
@ -269,9 +288,15 @@ describe('Wallet', function() {
cb(); 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" // Create 3 2-of-3 wallets with our pubkeys as "shared keys"
var w1 = bcoin.wallet({ var w1 = bcoin.wallet({
program: program,
derivation: 'bip44', derivation: 'bip44',
type: 'multisig', type: 'multisig',
m: 2, m: 2,
@ -279,6 +304,7 @@ describe('Wallet', function() {
}); });
var w2 = bcoin.wallet({ var w2 = bcoin.wallet({
program: program,
derivation: 'bip44', derivation: 'bip44',
type: 'multisig', type: 'multisig',
m: 2, m: 2,
@ -286,6 +312,7 @@ describe('Wallet', function() {
}); });
var w3 = bcoin.wallet({ var w3 = bcoin.wallet({
program: program,
derivation: 'bip44', derivation: 'bip44',
type: 'multisig', type: 'multisig',
m: 2, m: 2,
@ -309,11 +336,22 @@ describe('Wallet', function() {
assert.equal(w2.getAddress(), addr); assert.equal(w2.getAddress(), addr);
assert.equal(w3.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 // Add a shared unspent transaction to our wallets
var utx = bcoin.mtx(); 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); utx.addInput(dummyInput);
assert(w1.ownOutput(utx.outputs[0]));
// Simulate a confirmation // Simulate a confirmation
utx.ps = 0; utx.ps = 0;
utx.ts = 1; utx.ts = 1;
@ -337,17 +375,15 @@ describe('Wallet', function() {
// Create a tx requiring 2 signatures // Create a tx requiring 2 signatures
var send = bcoin.mtx(); var send = bcoin.mtx();
send.addOutput({ address: receive.getAddress(), value: 5460 }); 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 }); var result = w1.fill(send, { m: w1.m, n: w1.n });
assert(result); assert(result);
w1.sign(send); w1.sign(send);
// console.log(bcoin.script.format(send.inputs[0])); assert(!send.verify(null, true, flags));
assert(!send.verify());
w2.sign(send); w2.sign(send);
assert(send.verify()); assert(send.verify(null, true, flags));
assert.equal(w1.changeDepth, 1); assert.equal(w1.changeDepth, 1);
var change = w1.changeAddress.getAddress(); var change = w1.changeAddress.getAddress();
@ -374,8 +410,12 @@ describe('Wallet', function() {
assert.equal(w2.changeAddress.getAddress(), change); assert.equal(w2.changeAddress.getAddress(), change);
assert.equal(w3.changeAddress.getAddress(), change); assert.equal(w3.changeAddress.getAddress(), change);
send.inputs[0].script[2] = 0; if (program)
assert(!send.verify(null, true)); 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); assert.equal(send.getFee().toNumber(), 10000);
w3 = bcoin.wallet.fromJSON(w3.toJSON()); w3 = bcoin.wallet.fromJSON(w3.toJSON());
@ -385,6 +425,18 @@ describe('Wallet', function() {
assert.equal(w3.changeAddress.getAddress(), change); assert.equal(w3.changeAddress.getAddress(), change);
cb(); 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'; var coinbase = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff2c027156266a24aa21a9edb1e139795984903d6629ddedf3763fb9bc582fd68a46b1f8c7c57f9fbcc7fc900101ffffffff02887d102a0100000023210290dd626747729e1cc445cb9a11cfb7e78ea896db9f5c335e6730491d9ee7474dac0000000000000000266a24aa21a9edb1e139795984903d6629ddedf3763fb9bc582fd68a46b1f8c7c57f9fbcc7fc900120000000000000000000000000000000000000000000000000000000000000000000000000';
@ -404,11 +456,8 @@ describe('Wallet', function() {
}] }]
}); });
src.addInput(dummyInput); src.addInput(dummyInput);
console.log(src.toJSON());
var t = bcoin.protocol.parser.parseWitnessTX(new Buffer(coinbase, 'hex')); 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'))); var t = new bcoin.tx(bcoin.protocol.parser.parseWitnessTX(new Buffer(w2pkh, 'hex')));
utils.print(t);
delete t._raw; delete t._raw;
delete t._hash; delete t._hash;
delete t._whash; delete t._whash;