tx/script refactor and improvements.

This commit is contained in:
Christopher Jeffrey 2016-01-09 19:02:19 -08:00
parent 0658444eb5
commit 0b7ca266ee
6 changed files with 146 additions and 97 deletions

View File

@ -152,8 +152,6 @@ function HDPrivateKey(options) {
data = options; data = options;
} }
this.master = options.master || this;
data = this._normalize(data, network.prefixes.xprivkey); data = this._normalize(data, network.prefixes.xprivkey);
this.data = data; this.data = data;
@ -347,7 +345,6 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
return new HDPrivateKey({ return new HDPrivateKey({
version: null, version: null,
master: this.master,
depth: new bn(this.depth).toNumber() + 1, depth: new bn(this.depth).toNumber() + 1,
parentFingerPrint: this.fingerPrint, parentFingerPrint: this.fingerPrint,
childIndex: index, childIndex: index,

View File

@ -156,7 +156,7 @@ Miner.prototype.addTX = function addTX(tx) {
if (tx.height !== -1) if (tx.height !== -1)
return; return;
if (!tx.verify()) if (!tx.verify(null, true))
return; return;
if (tx.isCoinbase()) if (tx.isCoinbase())

View File

@ -248,7 +248,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
var v, v1, v2, v3, v4; var v, v1, v2, v3, v4;
var n, n1, n2, n3; var n, n1, n2, n3;
var res; var res;
var pub, sig, type, subscript, hash; var key, sig, type, subscript, hash;
var keys, i, j, key, m; var keys, i, j, key, m;
var succ; var succ;
var lock, threshold; var lock, threshold;
@ -679,11 +679,11 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
if (!tx || stack.length < 2) if (!tx || stack.length < 2)
return false; return false;
pub = stack.pop(); key = stack.pop();
sig = stack.pop(); sig = stack.pop();
type = sig[sig.length - 1]; type = sig[sig.length - 1];
if (!constants.hashTypeByVal[type & 0x1f]) if (!script.isKey(key))
return false; return false;
if (flags.strictder !== false) { if (flags.strictder !== false) {
@ -694,10 +694,13 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
return false; return false;
} }
if (!constants.hashTypeByVal[type & 0x1f])
return false;
subscript = script.subscript(s, lastSep); subscript = script.subscript(s, lastSep);
hash = tx.subscriptHash(index, subscript, type); hash = tx.subscriptHash(index, subscript, type);
res = script.verify(hash, sig.slice(0, -1), pub); res = script.verify(hash, sig.slice(0, -1), key);
if (o === 'checksigverify') { if (o === 'checksigverify') {
if (!res) if (!res)
return false; return false;
@ -723,7 +726,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
keys = []; keys = [];
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
key = stack.pop(); key = stack.pop();
if (!(33 <= key.length && key.length <= 65)) if (!script.isKey(key))
return false; return false;
keys.push(key); keys.push(key);
@ -745,9 +748,6 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
sig = stack.pop(); sig = stack.pop();
type = sig[sig.length - 1]; type = sig[sig.length - 1];
if (!constants.hashTypeByVal[type & 0x1f])
return false;
if (flags.strictder !== false) { if (flags.strictder !== false) {
if (!script.isValidSig(sig)) if (!script.isValidSig(sig))
return false; return false;
@ -756,6 +756,9 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
return false; return false;
} }
if (!constants.hashTypeByVal[type & 0x1f])
return false;
hash = tx.subscriptHash(index, subscript, type); hash = tx.subscriptHash(index, subscript, type);
res = false; res = false;
@ -1123,11 +1126,12 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) {
if (s.length !== 1 || !Array.isArray(s[0])) if (s.length !== 1 || !Array.isArray(s[0]))
return false; return false;
// res = script.isValidSig(s[0]); if (!script.isSig(s[0]))
res = 9 <= s[0].length && s[0].length <= 73;
if (!res)
return false; return false;
// Execute the script against our key's
// checksig script to see if this is our input.
// This will only work if the script verifies.
if (key) if (key)
return script.exec(s, [key, 'checksig'], tx, i); return script.exec(s, [key, 'checksig'], tx, i);
@ -1135,20 +1139,15 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) {
}; };
script.isPubkeyhashInput = function isPubkeyhashInput(s, key) { script.isPubkeyhashInput = function isPubkeyhashInput(s, key) {
var res;
s = script.subscript(s); s = script.subscript(s);
if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1])) if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1]))
return false; return false;
// res = script.isValidSig(s[0]) if (!script.isSig(s[0]))
// && 33 <= s[1].length && s[1].length <= 65; return false;
res = 9 <= s[0].length && s[0].length <= 73 if (!script.isKey(s[1]))
&& 33 <= s[1].length && s[1].length <= 65;
if (!res)
return false; return false;
if (key) if (key)
@ -1160,7 +1159,8 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) {
script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
var i, res, o; var i, res, o;
// We need to rule out scripthash because it may look like multisig // We need to rule out scripthash
// because it may look like multisig
if (script.isScripthashInput(s)) if (script.isScripthashInput(s))
return false; return false;
@ -1173,12 +1173,13 @@ script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
return false; return false;
for (i = 1; i < s.length; i++) { for (i = 1; i < s.length; i++) {
// res = script.isValidSig(s[i]); if (!script.isSig(s[i]))
res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res)
return false; return false;
} }
// Execute the script against our pubkeys'
// redeem script to see if this is our input.
// This will only work if the script verifies.
if (pubs && pubs.length >= 2) { if (pubs && pubs.length >= 2) {
o = script.redeem(pubs, 2, pubs.length); o = script.redeem(pubs, 2, pubs.length);
return script.exec(s, o, tx, i); return script.exec(s, o, tx, i);
@ -1199,9 +1200,7 @@ script.isScripthashInput = function isScripthashInput(s, redeem) {
return false; return false;
for (i = 1; i < s.length - 1; i++) { for (i = 1; i < s.length - 1; i++) {
// res = script.isValidSig(s[i]); if (!script.isSig(s[i]))
res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res)
return false; return false;
} }
@ -1320,6 +1319,13 @@ script.isCoinbase = function isCoinbase(s, block, strict) {
return coinbase; return coinbase;
}; };
script.isKey = function isKey(key) {
if (!utils.isBuffer(key))
return false;
return key.length >= 33 && key.length <= 65;
};
script.isSig = function isSig(sig, allowZero) { script.isSig = function isSig(sig, allowZero) {
if (!utils.isBuffer(sig)) if (!utils.isBuffer(sig))
return false; return false;
@ -1327,7 +1333,7 @@ script.isSig = function isSig(sig, allowZero) {
if (allowZero && sig.length === 0) if (allowZero && sig.length === 0)
return true; return true;
return 9 <= sig.length && sig.length <= 73; return sig.length >= 9 && sig.length <= 73;
}; };
// https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki

View File

@ -96,6 +96,9 @@ TXPool.prototype.add = function add(tx, noWrite) {
key = input.out.hash + '/' + input.out.index; key = input.out.hash + '/' + input.out.index;
unspent = this._unspent[key]; unspent = this._unspent[key];
if (!input.out.tx && this._all[input.out.hash])
input.out.tx = this._all[input.out.hash];
if (unspent) { if (unspent) {
// Add TX to inputs and spend money // Add TX to inputs and spend money
index = tx._inputIndex(unspent.tx.hash('hex'), unspent.index); index = tx._inputIndex(unspent.tx.hash('hex'), unspent.index);
@ -121,7 +124,7 @@ TXPool.prototype.add = function add(tx, noWrite) {
// we could in theory use ownInput here (and down below) // we could in theory use ownInput here (and down below)
// instead. // instead.
// if (this._wallet.ownInput(input.out.tx, input.out.index)) // if (this._wallet.ownInput(input.out.tx, input.out.index))
if (tx.inputs[i].out.tx) { if (input.out.tx) {
if (!this._wallet.ownOutput(input.out.tx, input.out.index)) if (!this._wallet.ownOutput(input.out.tx, input.out.index))
continue; continue;
} }

View File

@ -157,10 +157,18 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) {
}; };
// Build the scriptSigs for inputs, excluding the signatures // Build the scriptSigs for inputs, excluding the signatures
TX.prototype.scriptInput = function scriptInput(input, pub) { TX.prototype.scriptInput = function scriptInput(index, pub) {
var input, s, n, i, redeem;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input
input = this.inputs[index];
assert(input);
// Get the previous output's subscript // Get the previous output's subscript
var s = input.out.tx.getSubscript(input.out.index); s = input.out.tx.getSubscript(input.out.index);
var n, i, redeem;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (input.script.length)
@ -218,16 +226,23 @@ TX.prototype.scriptInput = function scriptInput(input, pub) {
}; };
// Sign the now-built scriptSigs // Sign the now-built scriptSigs
TX.prototype.signInput = function signInput(input, key, type) { TX.prototype.signInput = function signInput(index, key, type) {
var s, hash, signature; var input, s, hash, signature;
var len, redeem, m, keys, pub, pubn, ki, totalSigs, i; var len, redeem, m, keys, pub, pubn, ki, totalSigs, i;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (!type) if (!type)
type = 'all'; type = 'all';
if (typeof type === 'string') if (typeof type === 'string')
type = constants.hashType[type]; type = constants.hashType[type];
// Get the input
input = this.inputs[index];
assert(input);
// Get the previous output's subscript // Get the previous output's subscript
s = input.out.tx.getSubscript(input.out.index); s = input.out.tx.getSubscript(input.out.index);
@ -239,7 +254,7 @@ TX.prototype.signInput = function signInput(input, key, type) {
} }
// Get the hash of the current tx, minus the other inputs, plus the sighash. // Get the hash of the current tx, minus the other inputs, plus the sighash.
hash = this.subscriptHash(this.inputs.indexOf(input), redeem, type); hash = this.subscriptHash(index, redeem, type);
// Sign the transaction with our one input // Sign the transaction with our one input
signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); signature = bcoin.ecdsa.sign(hash, key.priv).toDER();
@ -324,23 +339,32 @@ TX.prototype.signInput = function signInput(input, key, type) {
}; };
// Build the scriptSig and sign it // Build the scriptSig and sign it
TX.prototype.scriptSig = function scriptSig(input, key, pub, type) { TX.prototype.scriptSig = function scriptSig(index, key, pub, type) {
var input;
if (!utils.isBuffer(pub)) { if (!utils.isBuffer(pub)) {
type = pub; type = pub;
pub = key.getPublic(true, 'array'); pub = key.getPublic(true, 'array');
} }
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input
input = this.inputs[index];
assert(input);
// Build script for input // Build script for input
this.scriptInput(input, pub); this.scriptInput(index, pub);
// Sign input // Sign input
this.signInput(input, key, type); this.signInput(index, key, type);
return input.script; return input.script;
}; };
TX.prototype.output = function output(obj, value) { TX.prototype.output = function output(obj, value) {
var options; var options, output;
if (obj instanceof bcoin.wallet) if (obj instanceof bcoin.wallet)
obj = obj.getAddress(); obj = obj.getAddress();
@ -354,7 +378,7 @@ TX.prototype.output = function output(obj, value) {
options = obj; options = obj;
} }
var output = bcoin.output({ output = bcoin.output({
tx: this, tx: this,
value: options.value, value: options.value,
script: options.script script: options.script
@ -362,7 +386,7 @@ TX.prototype.output = function output(obj, value) {
this.outputs.push(output); this.outputs.push(output);
this.scriptOutput(output, options); this.scriptOutput(this.outputs.length - 1, options);
return this; return this;
}; };
@ -370,11 +394,19 @@ TX.prototype.output = function output(obj, value) {
// compat // compat
TX.prototype.out = TX.prototype.output; TX.prototype.out = TX.prototype.output;
TX.prototype.scriptOutput = function scriptOutput(output, options) { TX.prototype.scriptOutput = function scriptOutput(index, options) {
var script = output.script; var output, script, keys, m, n, hash, locktime, flags;
var keys, m, n, hash, locktime, flags;
options = options || output; if (typeof index !== 'number')
index = this.outputs.indexOf(index);
output = this.outputs[index];
assert(output);
if (!options)
options = output;
script = output.script;
if (options instanceof bcoin.output) { if (options instanceof bcoin.output) {
options = Object.keys(options).reduce(function(out, key) { options = Object.keys(options).reduce(function(out, key) {
@ -499,6 +531,9 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
var copy = this.clone(); var copy = this.clone();
var verifyStr, hash; var verifyStr, hash;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (typeof type === 'string') if (typeof type === 'string')
type = constants.hashType[type]; type = constants.hashType[type];
@ -508,7 +543,7 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
return constants.oneHash.slice(); return constants.oneHash.slice();
copy.inputs.forEach(function(input, i) { copy.inputs.forEach(function(input, i) {
input.script = index === i ? s : []; input.script = i === index ? s : [];
}); });
if ((type & 0x1f) === constants.hashType.all) { if ((type & 0x1f) === constants.hashType.all) {
@ -564,12 +599,18 @@ TX.prototype.verify = function verify(index, force, flags) {
return this.inputs.every(function(input, i) { return this.inputs.every(function(input, i) {
var stack, prev, push, res, redeem; var stack, prev, push, res, redeem;
if (index !== undefined && index !== i) if (index != null && index !== i)
return true; return true;
if (!input.out.tx) if (!input.out.tx)
return false; return false;
assert.equal(input.out.tx.hash('hex'), input.out.hash);
// Transaction cannot reference itself
if (input.out.tx.hash('hex') === this.hash('hex'))
return false;
assert(input.out.tx.outputs.length > input.out.index); assert(input.out.tx.outputs.length > input.out.index);
stack = []; stack = [];

View File

@ -125,11 +125,6 @@ Wallet.prototype._init = function init() {
}); });
}; };
// If we want to use p2sh addresses for the prefix:
// Wallet.prototype.__defineGetter__('prefix', function() {
// return 'bt/' + this.getFullAddress() + '/';
// });
Wallet.prototype.multisig = function multisig(options) { Wallet.prototype.multisig = function multisig(options) {
var pub = this.getOwnPublicKey(); var pub = this.getOwnPublicKey();
@ -364,7 +359,7 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
var outputs = tx.outputs.filter(function(output, i) { var outputs = tx.outputs.filter(function(output, i) {
var s = output.script; var s = output.script;
if (index !== undefined && index !== i) if (index != null && index !== i)
return false; return false;
if (bcoin.script.isPubkey(s, key)) if (bcoin.script.isPubkey(s, key))
@ -398,7 +393,10 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
var inputs = tx.inputs.filter(function(input, i) { var inputs = tx.inputs.filter(function(input, i) {
var s; var s;
if (index !== undefined && index !== i) if (!input.out.tx && this.tx._all[input.out.hash])
input.out.tx = this.tx._all[input.out.hash];
if (index != null && index !== i)
return false; return false;
// if (bcoin.script.isPubkeyInput(input.script, key, tx, i)) // if (bcoin.script.isPubkeyInput(input.script, key, tx, i))
@ -416,7 +414,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
if (!input.out.tx) if (!input.out.tx)
return false; return false;
s = input.out.tx.outputs[input.out.index].script; s = input.out.tx.getSubscript(input.out.index);
if (bcoin.script.isPubkey(s, key)) if (bcoin.script.isPubkey(s, key))
return true; return true;
@ -456,12 +454,15 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) {
inputs = inputs || tx.inputs; inputs = inputs || tx.inputs;
inputs = inputs.filter(function(input) { inputs = inputs.filter(function(input, i) {
if (!input.out.tx && this.tx._all[input.out.hash])
input.out.tx = this.tx._all[input.out.hash];
// Filter inputs that this wallet own // Filter inputs that this wallet own
if (!input.out.tx || !this.ownOutput(input.out.tx)) if (!input.out.tx || !this.ownOutput(input.out.tx))
return false; return false;
tx.scriptInput(input, pub); tx.scriptInput(i, pub);
return true; return true;
}, this); }, this);
@ -477,11 +478,14 @@ Wallet.prototype.signInputs = function signInputs(tx, type, inputs) {
inputs = inputs || tx.inputs; inputs = inputs || tx.inputs;
inputs = inputs.filter(function(input) { inputs = inputs.filter(function(input, i) {
if (!input.out.tx && this.tx._all[input.out.hash])
input.out.tx = this.tx._all[input.out.hash];
if (!input.out.tx || !this.ownOutput(input.out.tx)) if (!input.out.tx || !this.ownOutput(input.out.tx))
return false; return false;
tx.signInput(input, key, type); tx.signInput(i, key, type);
return true; return true;
}, this); }, this);
@ -499,12 +503,15 @@ Wallet.prototype.sign = function sign(tx, type, inputs) {
inputs = inputs || tx.inputs; inputs = inputs || tx.inputs;
// Add signature script to each input // Add signature script to each input
inputs = inputs.filter(function(input) { inputs = inputs.filter(function(input, i) {
if (!input.out.tx && this.tx._all[input.out.hash])
input.out.tx = this.tx._all[input.out.hash];
// Filter inputs that this wallet own // Filter inputs that this wallet own
if (!input.out.tx || !this.ownOutput(input.out.tx)) if (!input.out.tx || !this.ownOutput(input.out.tx))
return false; return false;
tx.scriptSig(input, key, pub, type); tx.scriptSig(i, key, pub, type);
return true; return true;
}, this); }, this);
@ -591,7 +598,7 @@ Wallet.prototype.toAddress = function toAddress() {
Wallet.prototype.toJSON = function toJSON(encrypt) { Wallet.prototype.toJSON = function toJSON(encrypt) {
return { return {
v: 1, v: 2,
type: 'wallet', type: 'wallet',
network: network.type, network: network.type,
encrypted: encrypt ? true : false, encrypted: encrypt ? true : false,
@ -603,21 +610,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
? encrypt(this.getPrivateKey('base58')) ? encrypt(this.getPrivateKey('base58'))
: this.getPrivateKey('base58'), : this.getPrivateKey('base58'),
tx: this.tx.toJSON(), tx: this.tx.toJSON(),
ntx: this.tx.all().length, xprivkey: this.hd
hd: this.hd ? { ? (encrypt ? encrypt(this.hd.xprivkey) : this.hd.xprivkey)
seed: this.hd.seed ? { : null,
mnemonic: encrypt
? encrypt(this.hd.seed.mnemonic)
: this.hd.seed.mnemonic,
passphrase: encrypt
? encrypt(this.hd.seed.passphrase)
: this.hd.seed.passphrase
} : undefined,
depth: new bn(this.hd.data.depth).toNumber(),
parentFingerPrint: utils.toHex(this.hd.data.parentFingerPrint),
childIndex: new bn(this.hd.data.childIndex).toNumber(),
chainCode: utils.toHex(this.hd.data.chainCode)
} : undefined,
multisig: this.n > 1 ? { multisig: this.n > 1 ? {
type: this.type, type: this.type,
keys: this.keys.map(utils.toBase58), keys: this.keys.map(utils.toBase58),
@ -628,9 +623,13 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
}; };
Wallet.fromJSON = function fromJSON(json, decrypt) { Wallet.fromJSON = function fromJSON(json, decrypt) {
var priv, pub, compressed, key, w; var compressed, key, w;
var priv = json.priv;
var pub = json.pub;
var xprivkey = json.xprivkey;
var multisig = json.multisig;
assert.equal(json.v, 1); assert.equal(json.v, 2);
assert.equal(json.type, 'wallet'); assert.equal(json.type, 'wallet');
if (json.network) if (json.network)
@ -639,11 +638,11 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
if (json.encrypted && !decrypt) if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt wallet'); throw new Error('Cannot decrypt wallet');
if (json.encrypted) if (priv) {
json.priv = decrypt(json.priv); if (json.encrypted)
priv = decrypt(priv);
if (json.priv) { key = bcoin.utils.fromBase58(priv);
key = bcoin.utils.fromBase58(json.priv);
assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
assert.equal(key[0], network.prefixes.privkey); assert.equal(key[0], network.prefixes.privkey);
@ -657,20 +656,23 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
compressed = false; compressed = false;
} }
} else { } else {
pub = bcoin.utils.fromBase58(json.pub); pub = bcoin.utils.fromBase58(pub);
compressed = pub[0] !== 0x04; compressed = pub[0] !== 0x04;
} }
if (json.multisig) if (multisig) {
json.multisig.keys = json.multisig.keys.map(utils.fromBase58); multisig = {
type: multisig.type,
keys: multisig.keys.map(utils.fromBase58),
m: multisig.m,
n: multisig.n
};
}
if (json.hd) { if (xprivkey) {
json.hd.privateKey = priv; if (json.encrypted)
if (json.encrypted && json.hd.seed) { xprivkey = decrypt(xprivkey);
json.hd.seed.mnemonic = decrypt(json.hd.seed.mnemonic); priv = bcoin.hd.priv(xprivkey);
json.hd.seed.passphrase = decrypt(json.hd.seed.passphrase);
}
priv = bcoin.hd.priv(json.hd);
} }
w = new Wallet({ w = new Wallet({
@ -678,7 +680,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
priv: priv, priv: priv,
pub: pub, pub: pub,
compressed: compressed, compressed: compressed,
multisig: json.multisig multisig: multisig
}); });
w.tx.fromJSON(json.tx); w.tx.fromJSON(json.tx);