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.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();
});

View File

@ -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)

View File

@ -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
*/

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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);

View File

@ -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;
};

View File

@ -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)

View File

@ -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;
};

View File

@ -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,

View File

@ -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;