segwit wallet support. segwit chain improvements.
This commit is contained in:
parent
bd868cda7a
commit
3fcc7d5b07
@ -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();
|
||||
});
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
399
lib/bcoin/mtx.js
399
lib/bcoin/mtx.js
@ -67,6 +67,7 @@ MTX.prototype.clone = function clone() {
|
||||
|
||||
tx.inputs = tx.inputs.map(function(input) {
|
||||
input.script = input.script.slice();
|
||||
input.witness = input.witness.slice();
|
||||
return input;
|
||||
});
|
||||
|
||||
@ -129,8 +130,8 @@ MTX.prototype.addInput = function addInput(options, index) {
|
||||
return this;
|
||||
};
|
||||
|
||||
MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) {
|
||||
var input, s, n, i;
|
||||
MTX.prototype.scriptInput = function scriptInput(index, addr) {
|
||||
var input, prev, n, i, redeemScript, witnessScript, vector, dummy;
|
||||
|
||||
if (typeof index !== 'number')
|
||||
index = this.inputs.indexOf(index);
|
||||
@ -139,102 +140,173 @@ MTX.prototype.scriptInput = function scriptInput(index, publicKey, redeem) {
|
||||
input = this.inputs[index];
|
||||
assert(input);
|
||||
|
||||
// Already has a script template (at least)
|
||||
// if (input.script.length)
|
||||
// return;
|
||||
|
||||
// We should have previous outputs by now.
|
||||
assert(input.output);
|
||||
|
||||
// Get the previous output's subscript
|
||||
s = input.output.script;
|
||||
// Optimization: Don't bother with any below
|
||||
// calculation if the output is already templated.
|
||||
// Just say this is "our" output.
|
||||
if (input.script.length || input.witness.length)
|
||||
return true;
|
||||
|
||||
// P2SH
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
if (!redeem)
|
||||
// Optimization: test output against the
|
||||
// address map to avoid unnecessary calculation.
|
||||
// A hash table lookup may be faster than all
|
||||
// the nonsense below.
|
||||
if (!addr.ownOutput(input.output))
|
||||
return false;
|
||||
|
||||
// Get the previous output's script
|
||||
prev = input.output.script;
|
||||
|
||||
// This is easily the hardest part about building a transaction
|
||||
// with segwit: figuring out where the redeem script and witness
|
||||
// redeem scripts go.
|
||||
if (bcoin.script.isScripthash(prev)) {
|
||||
if (addr.program && utils.isEqual(prev[1], addr.programHash)) {
|
||||
// Witness program nested in regular P2SH.
|
||||
redeemScript = addr.program;
|
||||
vector = input.witness;
|
||||
dummy = new Buffer([]);
|
||||
assert(addr.program[0] === 0, 'Non-zero version passed to address.');
|
||||
if (addr.program.length === 34) {
|
||||
// P2WSH nested within pay-to-scripthash
|
||||
// (it had to be this complicated, didn't it?)
|
||||
witnessScript = addr.script;
|
||||
prev = bcoin.script.decode(addr.script);
|
||||
} else if (addr.program.length === 22) {
|
||||
// P2WPKH nested within pay-to-scripthash.
|
||||
prev = bcoin.script.createPubkeyhash(addr.keyHash);
|
||||
} else {
|
||||
assert(false, 'Unknown program data length passed to address.');
|
||||
}
|
||||
} else if (addr.script && utils.isEqual(prev[1], addr.scriptHash160)) {
|
||||
// Regular P2SH.
|
||||
redeemScript = addr.script;
|
||||
prev = bcoin.script.decode(addr.script);
|
||||
vector = input.script;
|
||||
dummy = 0;
|
||||
} else {
|
||||
return false;
|
||||
s = bcoin.script.decode(redeem);
|
||||
}
|
||||
} else if (bcoin.script.isWitnessProgram(prev)) {
|
||||
// Witness program.
|
||||
vector = input.witness;
|
||||
dummy = new Buffer([]);
|
||||
|
||||
if (prev[0] !== 0)
|
||||
return false;
|
||||
|
||||
if (prev[1].length === 32) {
|
||||
// Bare P2WPSH.
|
||||
if (!addr.script || !utils.isEqual(prev[1], addr.scriptHash256))
|
||||
return false;
|
||||
|
||||
witnessScript = addr.script;
|
||||
prev = bcoin.script.decode(addr.script);
|
||||
} else if (prev[1].length === 20) {
|
||||
// Bare P2WPKH.
|
||||
if (!utils.isEqual(prev[1], addr.keyHash))
|
||||
return false;
|
||||
|
||||
prev = bcoin.script.createPubkeyhash(prev[1]);
|
||||
} else {
|
||||
// Bare... who knows?
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
redeem = null;
|
||||
// Wow, a normal output! Praise be to Jengus and Gord.
|
||||
vector = input.script;
|
||||
dummy = 0;
|
||||
}
|
||||
|
||||
if (bcoin.script.isPubkey(s)) {
|
||||
if (bcoin.script.isPubkey(prev)) {
|
||||
// P2PK
|
||||
if (!utils.isEqual(s[0], publicKey))
|
||||
if (!utils.isEqual(prev[0], addr.publicKey))
|
||||
return false;
|
||||
|
||||
// Already has a script template (at least)
|
||||
if (input.script.length)
|
||||
if (vector.length)
|
||||
return true;
|
||||
input.script = [0];
|
||||
} else if (bcoin.script.isPubkeyhash(s)) {
|
||||
|
||||
vector[0] = dummy;
|
||||
} else if (bcoin.script.isPubkeyhash(prev)) {
|
||||
// P2PKH
|
||||
if (!utils.isEqual(s[2], bcoin.address.hash160(publicKey)))
|
||||
if (!utils.isEqual(prev[2], addr.keyHash))
|
||||
return false;
|
||||
|
||||
// Already has a script template (at least)
|
||||
if (input.script.length)
|
||||
if (vector.length)
|
||||
return true;
|
||||
input.script = [0, publicKey];
|
||||
} else if (bcoin.script.isMultisig(s)) {
|
||||
|
||||
vector[0] = dummy;
|
||||
vector[1] = addr.publicKey;
|
||||
} else if (bcoin.script.isMultisig(prev)) {
|
||||
// Multisig
|
||||
for (i = 0; i < s.length; i++) {
|
||||
if (utils.isEqual(s[i], publicKey))
|
||||
for (i = 0; i < prev.length; i++) {
|
||||
if (utils.isEqual(prev[i], addr.publicKey))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i === s.length)
|
||||
if (i === prev.length)
|
||||
return false;
|
||||
|
||||
// Already has a script template (at least)
|
||||
if (input.script.length)
|
||||
if (vector.length)
|
||||
return true;
|
||||
|
||||
// Technically we should create m signature slots,
|
||||
// but we create n signature slots so we can order
|
||||
// the signatures properly.
|
||||
input.script = [0];
|
||||
vector[0] = dummy;
|
||||
|
||||
// Grab `n` value (number of keys).
|
||||
n = s[s.length - 2];
|
||||
n = prev[prev.length - 2];
|
||||
|
||||
// Fill script with `n` signature slots.
|
||||
for (i = 0; i < n; i++)
|
||||
input.script[i + 1] = 0;
|
||||
vector[i + 1] = dummy;
|
||||
} else {
|
||||
for (i = 0; i < s.length; i++) {
|
||||
if (utils.isEqual(s[i], publicKey))
|
||||
for (i = 0; i < prev.length; i++) {
|
||||
if (utils.isEqual(prev[i], addr.publicKey))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i === s.length)
|
||||
if (i === prev.length)
|
||||
return false;
|
||||
|
||||
// Already has a script template (at least)
|
||||
if (input.script.length)
|
||||
if (vector.length)
|
||||
return true;
|
||||
|
||||
// Likely a non-standard scripthash multisig
|
||||
// input. Determine n value by counting keys.
|
||||
// Also, only allow nonstandard types for
|
||||
// scripthash.
|
||||
if (redeem) {
|
||||
input.script = [0];
|
||||
// Fill script with `n` signature slots.
|
||||
for (i = 0; i < s.length; i++) {
|
||||
if (bcoin.script.isKey(s[i]))
|
||||
input.script.push(0);
|
||||
}
|
||||
vector[0] = dummy;
|
||||
|
||||
// Fill script with `n` signature slots.
|
||||
for (i = 0; i < prev.length; i++) {
|
||||
if (bcoin.script.isKey(prev[i]))
|
||||
vector[i + 1] = dummy;
|
||||
}
|
||||
}
|
||||
|
||||
// P2SH requires the redeem script after signatures
|
||||
if (redeem)
|
||||
input.script.push(redeem);
|
||||
// P2SH requires the redeem
|
||||
// script after signatures.
|
||||
if (redeemScript)
|
||||
input.script.push(redeemScript);
|
||||
|
||||
// P2WSH requires the witness
|
||||
// script after signatures.
|
||||
if (witnessScript)
|
||||
input.witness.push(witnessScript);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
MTX.prototype.createSignature = function createSignature(index, key, type) {
|
||||
var input, s, hash, signature;
|
||||
MTX.prototype.createSignature = function createSignature(index, prev, key, type) {
|
||||
var prev, hash, signature;
|
||||
|
||||
if (typeof index !== 'number')
|
||||
index = this.inputs.indexOf(index);
|
||||
@ -245,38 +317,22 @@ MTX.prototype.createSignature = function createSignature(index, key, type) {
|
||||
if (typeof type === 'string')
|
||||
type = constants.hashType[type];
|
||||
|
||||
// Get the input
|
||||
input = this.inputs[index];
|
||||
assert(input);
|
||||
|
||||
// We should have previous outputs by now.
|
||||
assert(input.output);
|
||||
|
||||
// Get the previous output's subscript
|
||||
s = input.output.script;
|
||||
|
||||
// We need to grab the redeem script when
|
||||
// signing p2sh transactions.
|
||||
if (bcoin.script.isScripthash(s))
|
||||
s = bcoin.script.getRedeem(input.script);
|
||||
|
||||
// Get the hash of the current tx, minus the other
|
||||
// inputs, plus the sighash type.
|
||||
hash = this.signatureHash(index, s, type);
|
||||
hash = this.signatureHash(index, prev, type);
|
||||
|
||||
// Sign the transaction with our one input
|
||||
signature = bcoin.script.sign(hash, key, type);
|
||||
|
||||
// Something is broken if this doesn't work:
|
||||
assert(bcoin.script.checksig(hash, signature, key));
|
||||
// assert(bcoin.script.checksig(hash, signature, key), 'BUG: Verify failed.');
|
||||
|
||||
return signature;
|
||||
};
|
||||
|
||||
// Sign the now-built scriptSigs
|
||||
MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
var input, s, signature, ki, signatures, i;
|
||||
var len, m, n, keys, publicKey, keyHash;
|
||||
MTX.prototype.signInput = function signInput(index, addr, type) {
|
||||
var input, prev, signature, ki, signatures, i;
|
||||
var len, m, n, keys, vector, dummy;
|
||||
|
||||
if (typeof index !== 'number')
|
||||
index = this.inputs.indexOf(index);
|
||||
@ -288,87 +344,103 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
// We should have previous outputs by now.
|
||||
assert(input.output);
|
||||
|
||||
// Create our signature.
|
||||
signature = this.createSignature(index, key, type);
|
||||
|
||||
// Get the previous output's subscript
|
||||
s = input.output.script;
|
||||
prev = input.output.script;
|
||||
|
||||
// Script length, needed for multisig
|
||||
len = input.script.length;
|
||||
vector = input.script;
|
||||
len = vector.length;
|
||||
dummy = 0;
|
||||
|
||||
// We need to grab the redeem script when
|
||||
// signing p2sh transactions.
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
s = bcoin.script.getRedeem(input.script);
|
||||
// Decrement `len` to avoid the redeem script
|
||||
len--;
|
||||
if (bcoin.script.isScripthash(prev)) {
|
||||
prev = bcoin.script.getRedeem(input.script);
|
||||
len = vector.length - 1;
|
||||
}
|
||||
|
||||
// Get pubkey.
|
||||
publicKey = key.getPublicKey();
|
||||
// If the output script is a witness program,
|
||||
// we have to switch the vector to the witness
|
||||
// and potentially alter the length. Note that
|
||||
// witnesses are stack items, so the `dummy`
|
||||
// _has_ to be an empty buffer (what OP_0
|
||||
// pushes onto the stack).
|
||||
if (bcoin.script.isWitnessScripthash(prev)) {
|
||||
prev = bcoin.script.getRedeem(input.witness);
|
||||
vector = input.witness;
|
||||
len = vector.length - 1;
|
||||
dummy = new Buffer([]);
|
||||
} else if (bcoin.script.isWitnessPubkeyhash(prev)) {
|
||||
prev = bcoin.script.createPubkeyhash(prev[1]);
|
||||
vector = input.witness;
|
||||
len = vector.length;
|
||||
dummy = new Buffer([]);
|
||||
}
|
||||
|
||||
// Create our signature.
|
||||
signature = this.createSignature(index, prev, addr.key, type);
|
||||
|
||||
// Add signatures.
|
||||
if (bcoin.script.isPubkey(s)) {
|
||||
if (bcoin.script.isPubkey(prev)) {
|
||||
// P2PK
|
||||
|
||||
// Already signed.
|
||||
if (bcoin.script.isSignature(input.script[0]))
|
||||
if (bcoin.script.isSignature(vector[0]))
|
||||
return true;
|
||||
|
||||
// Make sure the pubkey is ours.
|
||||
if (!utils.isEqual(publicKey, s[0]))
|
||||
if (!utils.isEqual(addr.publicKey, prev[0]))
|
||||
return false;
|
||||
|
||||
input.script[0] = signature;
|
||||
vector[0] = signature;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
if (bcoin.script.isPubkeyhash(prev)) {
|
||||
// P2PKH
|
||||
|
||||
// Already signed.
|
||||
if (bcoin.script.isSignature(input.script[0]))
|
||||
if (bcoin.script.isSignature(vector[0]))
|
||||
return true;
|
||||
|
||||
// Make sure the pubkey hash is ours.
|
||||
keyHash = bcoin.address.hash160(publicKey);
|
||||
if (!utils.isEqual(keyHash, s[2]))
|
||||
if (!utils.isEqual(addr.keyHash, prev[2]))
|
||||
return false;
|
||||
|
||||
input.script[0] = signature;
|
||||
vector[0] = signature;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bcoin.script.isMultisig(s)) {
|
||||
if (bcoin.script.isMultisig(prev)) {
|
||||
// Multisig
|
||||
|
||||
// Grab the redeem script's keys to figure
|
||||
// out where our key should go.
|
||||
keys = s.slice(1, -2);
|
||||
keys = prev.slice(1, -2);
|
||||
|
||||
// Grab `m` value (number of sigs required).
|
||||
m = s[0];
|
||||
m = prev[0];
|
||||
|
||||
// Grab `n` value (number of keys).
|
||||
n = s[s.length - 2];
|
||||
n = prev[prev.length - 2];
|
||||
} else {
|
||||
// Only allow non-standard signing for
|
||||
// scripthash.
|
||||
if (len !== input.script.length - 1)
|
||||
if (len !== vector.length - 1)
|
||||
return false;
|
||||
|
||||
keys = [];
|
||||
|
||||
for (i = 0; i < s.length; i++) {
|
||||
if (bcoin.script.isKey(s[i]))
|
||||
keys.push(s[i]);
|
||||
for (i = 0; i < prev.length; i++) {
|
||||
if (bcoin.script.isKey(prev[i]))
|
||||
keys.push(prev[i]);
|
||||
}
|
||||
|
||||
// We don't know what m is, so
|
||||
// we can never finalize the signatures.
|
||||
m = keys.length;
|
||||
n = keys.length;
|
||||
m = n;
|
||||
}
|
||||
|
||||
// Something is very wrong here. Abort.
|
||||
@ -378,7 +450,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
// Count the number of current signatures.
|
||||
signatures = 0;
|
||||
for (i = 1; i < len; i++) {
|
||||
if (bcoin.script.isSignature(input.script[i]))
|
||||
if (bcoin.script.isSignature(vector[i]))
|
||||
signatures++;
|
||||
}
|
||||
|
||||
@ -391,14 +463,14 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
// or by `m`. Add some signature slots for
|
||||
// us to use.
|
||||
while (len - 1 < n) {
|
||||
input.script.splice(len, 0, 0);
|
||||
vector.splice(len, 0, dummy);
|
||||
len++;
|
||||
}
|
||||
|
||||
// Find the key index so we can place
|
||||
// the signature in the same index.
|
||||
for (ki = 0; ki < keys.length; ki++) {
|
||||
if (utils.isEqual(publicKey, keys[ki]))
|
||||
if (utils.isEqual(addr.publicKey, keys[ki]))
|
||||
break;
|
||||
}
|
||||
|
||||
@ -417,8 +489,8 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
// and increment the total number of
|
||||
// signatures.
|
||||
if (ki < len && signatures < m) {
|
||||
if (input.script[ki] === 0) {
|
||||
input.script[ki] = signature;
|
||||
if (bcoin.script.isZero(vector[ki])) {
|
||||
vector[ki] = signature;
|
||||
signatures++;
|
||||
}
|
||||
}
|
||||
@ -427,8 +499,8 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
if (signatures >= m) {
|
||||
// Remove empty slots left over.
|
||||
for (i = len - 1; i >= 1; i--) {
|
||||
if (input.script[i] === 0) {
|
||||
input.script.splice(i, 1);
|
||||
if (bcoin.script.isZero(vector[i])) {
|
||||
vector.splice(i, 1);
|
||||
len--;
|
||||
}
|
||||
}
|
||||
@ -438,7 +510,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
// with implementations that potentially handle
|
||||
// signature slots differently.
|
||||
while (signatures > m) {
|
||||
input.script.splice(len - 1, 1);
|
||||
vector.splice(len - 1, 1);
|
||||
signatures--;
|
||||
len--;
|
||||
}
|
||||
@ -451,8 +523,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
|
||||
return signatures === m;
|
||||
};
|
||||
|
||||
MTX.prototype.sign = function sign(index, key, redeem, type) {
|
||||
var publicKey = key.getPublicKey();
|
||||
MTX.prototype.sign = function sign(index, addr, type) {
|
||||
var input;
|
||||
|
||||
if (index && typeof index === 'object')
|
||||
@ -462,104 +533,16 @@ MTX.prototype.sign = function sign(index, key, redeem, type) {
|
||||
assert(input);
|
||||
|
||||
// Build script for input
|
||||
if (!this.scriptInput(index, publicKey, redeem))
|
||||
if (!this.scriptInput(index, addr))
|
||||
return false;
|
||||
|
||||
// Sign input
|
||||
if (!this.signInput(index, key, type))
|
||||
if (!this.signInput(index, addr, type))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
MTX.prototype.isSigned = function isSigned(index, required) {
|
||||
var i, input, s, len, m, j, total;
|
||||
|
||||
if (this._signed)
|
||||
return true;
|
||||
|
||||
if (index && typeof index === 'object')
|
||||
index = this.inputs.indexOf(index);
|
||||
|
||||
if (index != null)
|
||||
assert(this.inputs[index]);
|
||||
|
||||
for (i = 0; i < this.inputs.length; i++) {
|
||||
input = this.inputs[i];
|
||||
|
||||
if (index != null && i !== index)
|
||||
continue;
|
||||
|
||||
// We can't check for signatures unless
|
||||
// we have the previous output.
|
||||
assert(input.output);
|
||||
|
||||
// Get the prevout's subscript
|
||||
s = input.output.script;
|
||||
|
||||
// Script length, needed for multisig
|
||||
len = input.script.length;
|
||||
|
||||
// Grab the redeem script if P2SH
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
s = bcoin.script.getRedeem(input.script);
|
||||
// Decrement `len` to avoid the redeem script
|
||||
len--;
|
||||
}
|
||||
|
||||
// Check for signatures.
|
||||
// P2PK
|
||||
if (bcoin.script.isPubkey(s)) {
|
||||
if (!bcoin.script.isSignature(input.script[0]))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// P2PK
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
if (!bcoin.script.isSignature(input.script[0]))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multisig
|
||||
if (bcoin.script.isMultisig(s)) {
|
||||
// Grab `m` value (number of required sigs).
|
||||
m = s[0];
|
||||
if (Buffer.isBuffer(m))
|
||||
m = m[0] || 0;
|
||||
|
||||
// Ensure all members are signatures.
|
||||
for (j = 1; j < len; j++) {
|
||||
if (!bcoin.script.isSignature(input.script[j]))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure we have the correct number
|
||||
// of required signatures.
|
||||
if (len - 1 !== m)
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (required == null)
|
||||
continue;
|
||||
|
||||
// Unknown
|
||||
total = 0;
|
||||
for (j = 0; j < input.script.length; j++) {
|
||||
if (bcoin.script.isSignatureEncoding(input.script[j]))
|
||||
total++;
|
||||
}
|
||||
|
||||
if (total !== required)
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._signed = true;
|
||||
};
|
||||
|
||||
MTX.prototype.addOutput = function addOutput(obj, value) {
|
||||
var options, output;
|
||||
|
||||
@ -667,7 +650,7 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) {
|
||||
|
||||
MTX.prototype.maxSize = function maxSize(maxM, maxN) {
|
||||
var copy = this.clone();
|
||||
var i, j, input, total, size, s, m, n;
|
||||
var i, j, input, total, size, prev, m, n;
|
||||
|
||||
// Create copy with 0-script inputs
|
||||
for (i = 0; i < copy.inputs.length; i++)
|
||||
@ -683,38 +666,38 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
|
||||
assert(input.output);
|
||||
|
||||
// Get the previous output's subscript
|
||||
s = input.output.script;
|
||||
prev = input.output.script;
|
||||
|
||||
// If we have access to the redeem script,
|
||||
// we can use it to calculate size much easier.
|
||||
if (this.inputs[i].script.length && bcoin.script.isScripthash(s)) {
|
||||
s = bcoin.script.getRedeem(this.inputs[i].script);
|
||||
if (this.inputs[i].script.length && bcoin.script.isScripthash(prev)) {
|
||||
prev = bcoin.script.getRedeem(this.inputs[i].script);
|
||||
// Need to add the redeem script size
|
||||
// here since it will be ignored by
|
||||
// the isMultisig clause.
|
||||
// OP_PUSHDATA2 [redeem]
|
||||
size += 3 + bcoin.script.getSize(s);
|
||||
size += 3 + bcoin.script.getSize(prev);
|
||||
}
|
||||
|
||||
if (bcoin.script.isPubkey(s)) {
|
||||
if (bcoin.script.isPubkey(prev)) {
|
||||
// P2PK
|
||||
// OP_PUSHDATA0 [signature]
|
||||
size += 1 + 73;
|
||||
} else if (bcoin.script.isPubkeyhash(s)) {
|
||||
} else if (bcoin.script.isPubkeyhash(prev)) {
|
||||
// P2PKH
|
||||
// OP_PUSHDATA0 [signature]
|
||||
size += 1 + 73;
|
||||
// OP_PUSHDATA0 [key]
|
||||
size += 1 + 33;
|
||||
} else if (bcoin.script.isMultisig(s)) {
|
||||
} else if (bcoin.script.isMultisig(prev)) {
|
||||
// Bare Multisig
|
||||
// Get the previous m value:
|
||||
m = s[0];
|
||||
m = prev[0];
|
||||
// OP_0
|
||||
size += 1;
|
||||
// OP_PUSHDATA0 [signature] ...
|
||||
size += (1 + 73) * m;
|
||||
} else if (bcoin.script.isScripthash(s)) {
|
||||
} else if (bcoin.script.isScripthash(prev)) {
|
||||
// P2SH Multisig
|
||||
// This technically won't work well for other
|
||||
// kinds of P2SH. It will also over-estimate
|
||||
@ -744,8 +727,8 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
|
||||
size += 1;
|
||||
} else {
|
||||
// OP_PUSHDATA0 [signature]
|
||||
for (j = 0; j < s.length; j++) {
|
||||
if (bcoin.script.isKey(s[j]))
|
||||
for (j = 0; j < prev.length; j++) {
|
||||
if (bcoin.script.isKey(prev[j]))
|
||||
size += 1 + 73;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user