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

View File

@ -156,7 +156,7 @@ Miner.prototype.addTX = function addTX(tx) {
if (tx.height !== -1)
return;
if (!tx.verify())
if (!tx.verify(null, true))
return;
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 n, n1, n2, n3;
var res;
var pub, sig, type, subscript, hash;
var key, sig, type, subscript, hash;
var keys, i, j, key, m;
var succ;
var lock, threshold;
@ -679,11 +679,11 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
if (!tx || stack.length < 2)
return false;
pub = stack.pop();
key = stack.pop();
sig = stack.pop();
type = sig[sig.length - 1];
if (!constants.hashTypeByVal[type & 0x1f])
if (!script.isKey(key))
return false;
if (flags.strictder !== false) {
@ -694,10 +694,13 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
return false;
}
if (!constants.hashTypeByVal[type & 0x1f])
return false;
subscript = script.subscript(s, lastSep);
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 (!res)
return false;
@ -723,7 +726,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
keys = [];
for (i = 0; i < n; i++) {
key = stack.pop();
if (!(33 <= key.length && key.length <= 65))
if (!script.isKey(key))
return false;
keys.push(key);
@ -745,9 +748,6 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
sig = stack.pop();
type = sig[sig.length - 1];
if (!constants.hashTypeByVal[type & 0x1f])
return false;
if (flags.strictder !== false) {
if (!script.isValidSig(sig))
return false;
@ -756,6 +756,9 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
return false;
}
if (!constants.hashTypeByVal[type & 0x1f])
return false;
hash = tx.subscriptHash(index, subscript, type);
res = false;
@ -1123,11 +1126,12 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) {
if (s.length !== 1 || !Array.isArray(s[0]))
return false;
// res = script.isValidSig(s[0]);
res = 9 <= s[0].length && s[0].length <= 73;
if (!res)
if (!script.isSig(s[0]))
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)
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) {
var res;
s = script.subscript(s);
if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1]))
return false;
// res = script.isValidSig(s[0])
// && 33 <= s[1].length && s[1].length <= 65;
if (!script.isSig(s[0]))
return false;
res = 9 <= s[0].length && s[0].length <= 73
&& 33 <= s[1].length && s[1].length <= 65;
if (!res)
if (!script.isKey(s[1]))
return false;
if (key)
@ -1160,7 +1159,8 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) {
script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
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))
return false;
@ -1173,12 +1173,13 @@ script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
return false;
for (i = 1; i < s.length; i++) {
// res = script.isValidSig(s[i]);
res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res)
if (!script.isSig(s[i]))
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) {
o = script.redeem(pubs, 2, pubs.length);
return script.exec(s, o, tx, i);
@ -1199,9 +1200,7 @@ script.isScripthashInput = function isScripthashInput(s, redeem) {
return false;
for (i = 1; i < s.length - 1; i++) {
// res = script.isValidSig(s[i]);
res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res)
if (!script.isSig(s[i]))
return false;
}
@ -1320,6 +1319,13 @@ script.isCoinbase = function isCoinbase(s, block, strict) {
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) {
if (!utils.isBuffer(sig))
return false;
@ -1327,7 +1333,7 @@ script.isSig = function isSig(sig, allowZero) {
if (allowZero && sig.length === 0)
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

View File

@ -96,6 +96,9 @@ TXPool.prototype.add = function add(tx, noWrite) {
key = input.out.hash + '/' + input.out.index;
unspent = this._unspent[key];
if (!input.out.tx && this._all[input.out.hash])
input.out.tx = this._all[input.out.hash];
if (unspent) {
// Add TX to inputs and spend money
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)
// instead.
// 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))
continue;
}

View File

@ -157,10 +157,18 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) {
};
// 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
var s = input.out.tx.getSubscript(input.out.index);
var n, i, redeem;
s = input.out.tx.getSubscript(input.out.index);
// Already has a script template (at least)
if (input.script.length)
@ -218,16 +226,23 @@ TX.prototype.scriptInput = function scriptInput(input, pub) {
};
// Sign the now-built scriptSigs
TX.prototype.signInput = function signInput(input, key, type) {
var s, hash, signature;
TX.prototype.signInput = function signInput(index, key, type) {
var input, s, hash, signature;
var len, redeem, m, keys, pub, pubn, ki, totalSigs, i;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (!type)
type = 'all';
if (typeof type === 'string')
type = constants.hashType[type];
// Get the input
input = this.inputs[index];
assert(input);
// Get the previous output's subscript
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.
hash = this.subscriptHash(this.inputs.indexOf(input), redeem, type);
hash = this.subscriptHash(index, redeem, type);
// Sign the transaction with our one input
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
TX.prototype.scriptSig = function scriptSig(input, key, pub, type) {
TX.prototype.scriptSig = function scriptSig(index, key, pub, type) {
var input;
if (!utils.isBuffer(pub)) {
type = pub;
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
this.scriptInput(input, pub);
this.scriptInput(index, pub);
// Sign input
this.signInput(input, key, type);
this.signInput(index, key, type);
return input.script;
};
TX.prototype.output = function output(obj, value) {
var options;
var options, output;
if (obj instanceof bcoin.wallet)
obj = obj.getAddress();
@ -354,7 +378,7 @@ TX.prototype.output = function output(obj, value) {
options = obj;
}
var output = bcoin.output({
output = bcoin.output({
tx: this,
value: options.value,
script: options.script
@ -362,7 +386,7 @@ TX.prototype.output = function output(obj, value) {
this.outputs.push(output);
this.scriptOutput(output, options);
this.scriptOutput(this.outputs.length - 1, options);
return this;
};
@ -370,11 +394,19 @@ TX.prototype.output = function output(obj, value) {
// compat
TX.prototype.out = TX.prototype.output;
TX.prototype.scriptOutput = function scriptOutput(output, options) {
var script = output.script;
var keys, m, n, hash, locktime, flags;
TX.prototype.scriptOutput = function scriptOutput(index, options) {
var output, script, 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) {
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 verifyStr, hash;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (typeof type === 'string')
type = constants.hashType[type];
@ -508,7 +543,7 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
return constants.oneHash.slice();
copy.inputs.forEach(function(input, i) {
input.script = index === i ? s : [];
input.script = i === index ? s : [];
});
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) {
var stack, prev, push, res, redeem;
if (index !== undefined && index !== i)
if (index != null && index !== i)
return true;
if (!input.out.tx)
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);
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) {
var pub = this.getOwnPublicKey();
@ -364,7 +359,7 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
var outputs = tx.outputs.filter(function(output, i) {
var s = output.script;
if (index !== undefined && index !== i)
if (index != null && index !== i)
return false;
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 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;
// if (bcoin.script.isPubkeyInput(input.script, key, tx, i))
@ -416,7 +414,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
if (!input.out.tx)
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))
return true;
@ -456,12 +454,15 @@ Wallet.prototype.scriptInputs = function scriptInputs(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
if (!input.out.tx || !this.ownOutput(input.out.tx))
return false;
tx.scriptInput(input, pub);
tx.scriptInput(i, pub);
return true;
}, this);
@ -477,11 +478,14 @@ Wallet.prototype.signInputs = function signInputs(tx, type, 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))
return false;
tx.signInput(input, key, type);
tx.signInput(i, key, type);
return true;
}, this);
@ -499,12 +503,15 @@ Wallet.prototype.sign = function sign(tx, type, inputs) {
inputs = inputs || tx.inputs;
// 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
if (!input.out.tx || !this.ownOutput(input.out.tx))
return false;
tx.scriptSig(input, key, pub, type);
tx.scriptSig(i, key, pub, type);
return true;
}, this);
@ -591,7 +598,7 @@ Wallet.prototype.toAddress = function toAddress() {
Wallet.prototype.toJSON = function toJSON(encrypt) {
return {
v: 1,
v: 2,
type: 'wallet',
network: network.type,
encrypted: encrypt ? true : false,
@ -603,21 +610,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
? encrypt(this.getPrivateKey('base58'))
: this.getPrivateKey('base58'),
tx: this.tx.toJSON(),
ntx: this.tx.all().length,
hd: this.hd ? {
seed: this.hd.seed ? {
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,
xprivkey: this.hd
? (encrypt ? encrypt(this.hd.xprivkey) : this.hd.xprivkey)
: null,
multisig: this.n > 1 ? {
type: this.type,
keys: this.keys.map(utils.toBase58),
@ -628,9 +623,13 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
};
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');
if (json.network)
@ -639,11 +638,11 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt wallet');
if (json.encrypted)
json.priv = decrypt(json.priv);
if (priv) {
if (json.encrypted)
priv = decrypt(priv);
if (json.priv) {
key = bcoin.utils.fromBase58(json.priv);
key = bcoin.utils.fromBase58(priv);
assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
assert.equal(key[0], network.prefixes.privkey);
@ -657,20 +656,23 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
compressed = false;
}
} else {
pub = bcoin.utils.fromBase58(json.pub);
pub = bcoin.utils.fromBase58(pub);
compressed = pub[0] !== 0x04;
}
if (json.multisig)
json.multisig.keys = json.multisig.keys.map(utils.fromBase58);
if (multisig) {
multisig = {
type: multisig.type,
keys: multisig.keys.map(utils.fromBase58),
m: multisig.m,
n: multisig.n
};
}
if (json.hd) {
json.hd.privateKey = priv;
if (json.encrypted && json.hd.seed) {
json.hd.seed.mnemonic = decrypt(json.hd.seed.mnemonic);
json.hd.seed.passphrase = decrypt(json.hd.seed.passphrase);
}
priv = bcoin.hd.priv(json.hd);
if (xprivkey) {
if (json.encrypted)
xprivkey = decrypt(xprivkey);
priv = bcoin.hd.priv(xprivkey);
}
w = new Wallet({
@ -678,7 +680,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
priv: priv,
pub: pub,
compressed: compressed,
multisig: json.multisig
multisig: multisig
});
w.tx.fromJSON(json.tx);