Browse Source

refactor and fixes for Transaction, ScriptInterpreter, and Key

patch-2
Manuel Araoz 11 years ago
committed by MattFaus
parent
commit
ba92a6b1df
  1. 4
      Key.js
  2. 54
      ScriptInterpreter.js
  3. 223
      Transaction.js
  4. 80
      test/test.Transaction.js
  5. 134
      util/util.js

4
Key.js

@ -78,6 +78,10 @@ if (process.versions) {
return new Buffer(signature); return new Buffer(signature);
}; };
kSpec.prototype.verifySignature = function(hash, sig, callback) {
};
kSpec.prototype.verifySignatureSync = function(hash, sig) { kSpec.prototype.verifySignatureSync = function(hash, sig) {
var self = this; var self = this;

54
ScriptInterpreter.js

@ -7,6 +7,7 @@ var buffertools = imports.buffertools || require('buffertools');
var bignum = imports.bignum || require('bignum'); var bignum = imports.bignum || require('bignum');
var Util = imports.Util || require('./util/util'); var Util = imports.Util || require('./util/util');
var Script = require('./Script'); var Script = require('./Script');
var Key = require('./Key');
var SIGHASH_ALL = 1; var SIGHASH_ALL = 1;
var SIGHASH_NONE = 2; var SIGHASH_NONE = 2;
@ -61,7 +62,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
return; return;
} }
try {
// The execution bit is true if there are no "false" values in the // The execution bit is true if there are no "false" values in the
// execution stack. (A "false" value indicates that we're in the // execution stack. (A "false" value indicates that we're in the
// inactive branch of an if statement.) // inactive branch of an if statement.)
@ -411,13 +411,10 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
this.stackPop(); this.stackPop();
this.stackPop(); this.stackPop();
this.stack.push(new Buffer([value ? 1 : 0])); this.stack.push(new Buffer([value ? 1 : 0]));
console.log(script.toHumanReadable());
if (opcode === OP_EQUALVERIFY) { if (opcode === OP_EQUALVERIFY) {
if (value) { if (value) {
this.stackPop(); this.stackPop();
} else { } else {
console.log(v1);
console.log(v2);
throw new Error("OP_EQUALVERIFY negative"); throw new Error("OP_EQUALVERIFY negative");
} }
} }
@ -628,7 +625,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// Verify signature // Verify signature
checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) {
try {
var success; var success;
if (e) { if (e) {
@ -653,9 +649,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// Run next step // Run next step
executeStep.call(this, cb); executeStep.call(this, cb);
} catch (e) {
cb(e);
}
}.bind(this)); }.bind(this));
// Note that for asynchronous opcodes we have to return here to prevent // Note that for asynchronous opcodes we have to return here to prevent
@ -710,13 +703,11 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
checkMultiSigStep.call(this); checkMultiSigStep.call(this);
function checkMultiSigStep() { function checkMultiSigStep() {
try {
if (success && sigsCount > 0) { if (success && sigsCount > 0) {
var sig = sigs[isig]; var sig = sigs[isig];
var key = keys[ikey]; var key = keys[ikey];
checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) { checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) {
try {
if (!e && result) { if (!e && result) {
isig++; isig++;
sigsCount--; sigsCount--;
@ -732,9 +723,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
} }
checkMultiSigStep.call(this); checkMultiSigStep.call(this);
} catch (e) {
cb(e);
}
}.bind(this)); }.bind(this));
} else { } else {
this.stack.push(new Buffer([success ? 1 : 0])); this.stack.push(new Buffer([success ? 1 : 0]));
@ -749,9 +737,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// Run next step // Run next step
executeStep.call(this, cb); executeStep.call(this, cb);
} }
} catch (e) {
cb(e);
}
}; };
// Note that for asynchronous opcodes we have to return here to prevent // Note that for asynchronous opcodes we have to return here to prevent
@ -768,18 +753,13 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
} }
// Run next step // Run next step
if (pc % 100) { if (false && pc % 100) {
// V8 allows for much deeper stacks than Bitcoin's scripting language, // V8 allows for much deeper stacks than Bitcoin's scripting language,
// but just to be safe, we'll reset the stack every 100 steps // but just to be safe, we'll reset the stack every 100 steps
process.nextTick(executeStep.bind(this, cb)); process.nextTick(executeStep.bind(this, cb));
} else { } else {
executeStep.call(this, cb); executeStep.call(this, cb);
} }
} catch (e) {
log.debug("Script aborted: " +
(e.message ? e.message : e));
cb(e);
}
} }
}; };
@ -849,15 +829,15 @@ ScriptInterpreter.prototype.stackSwap = function stackSwap(a, b) {
* integer. Any longer Buffer is converted to a hex string. * integer. Any longer Buffer is converted to a hex string.
*/ */
ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() { ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() {
return this.stack.map(function(entry) { return this.stack.map(function(chunk) {
if (entry.length > 2) { if (chunk.length > 2) {
return buffertools.toHex(entry.slice(0)); return buffertools.toHex(chunk.slice(0));
} }
var num = bufferSMToInt(entry); var num = bufferSMToInt(chunk);
if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) {
return num.toNumber(); return num.toNumber();
} else { } else {
return buffertools.toHex(entry.slice(0)); return buffertools.toHex(chunk.slice(0));
} }
}); });
}; };
@ -904,12 +884,7 @@ ScriptInterpreter.verify =
} }
// Cast result to bool // Cast result to bool
try {
var result = si.getResult(); var result = si.getResult();
} catch (err) {
callback(err);
return;
}
callback(null, result); callback(null, result);
}); });
@ -992,8 +967,15 @@ ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey,
var that = this; var that = this;
this.eval(scriptSig, txTo, nIn, hashType, function(err) { this.eval(scriptSig, txTo, nIn, hashType, function(err) {
if (err) callback(err); if (err) callback(err);
else that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn, else {
var e = new Error('dummy');
var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
.replace(/^\s+at\s+/gm, '')
.replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
.split('\n');
that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
hashType, callback, siCopy); hashType, callback, siCopy);
}
}); });
}; };
@ -1013,17 +995,13 @@ var checkSig = ScriptInterpreter.checkSig =
} }
sig = sig.slice(0, sig.length - 1); sig = sig.slice(0, sig.length - 1);
try {
// Signature verification requires a special hash procedure // Signature verification requires a special hash procedure
var hash = tx.hashForSignature(scriptCode, n, hashType); var hash = tx.hashForSignature(scriptCode, n, hashType);
// Verify signature // Verify signature
var key = new Util.BitcoinKey(); var key = new Key();
key.public = pubkey; key.public = pubkey;
key.verifySignature(hash, sig, callback); key.verifySignature(hash, sig, callback);
} catch (err) {
callback(null, false);
}
}; };
ScriptInterpreter.prototype.isCanonicalSignature = function(sig) { ScriptInterpreter.prototype.isCanonicalSignature = function(sig) {

223
Transaction.js

@ -64,7 +64,7 @@ TransactionIn.prototype.getOutpointHash = function getOutpointHash() {
}; };
TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() { TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() {
return (this.o[32] ) + return (this.o[32]) +
(this.o[33] << 8) + (this.o[33] << 8) +
(this.o[34] << 16) + (this.o[34] << 16) +
(this.o[35] << 24); (this.o[35] << 24);
@ -106,14 +106,14 @@ function Transaction(data) {
this.hash = data.hash || null; this.hash = data.hash || null;
this.version = data.version; this.version = data.version;
this.lock_time = data.lock_time; this.lock_time = data.lock_time;
this.ins = Array.isArray(data.ins) ? data.ins.map(function (data) { this.ins = Array.isArray(data.ins) ? data.ins.map(function(data) {
var txin = new TransactionIn(); var txin = new TransactionIn();
txin.s = data.s; txin.s = data.s;
txin.q = data.q; txin.q = data.q;
txin.o = data.o; txin.o = data.o;
return txin; return txin;
}) : []; }) : [];
this.outs = Array.isArray(data.outs) ? data.outs.map(function (data) { this.outs = Array.isArray(data.outs) ? data.outs.map(function(data) {
var txout = new TransactionOut(); var txout = new TransactionOut();
txout.v = data.v; txout.v = data.v;
txout.s = data.s; txout.s = data.s;
@ -125,7 +125,7 @@ this.class = Transaction;
Transaction.In = TransactionIn; Transaction.In = TransactionIn;
Transaction.Out = TransactionOut; Transaction.Out = TransactionOut;
Transaction.prototype.isCoinBase = function () { Transaction.prototype.isCoinBase = function() {
return this.ins.length == 1 && this.ins[0].isCoinBase(); return this.ins.length == 1 && this.ins[0].isCoinBase();
}; };
@ -152,12 +152,12 @@ Transaction.prototype.serialize = function serialize() {
bufs.push(buf); bufs.push(buf);
bufs.push(util.varIntBuf(this.ins.length)); bufs.push(util.varIntBuf(this.ins.length));
this.ins.forEach(function (txin) { this.ins.forEach(function(txin) {
bufs.push(txin.serialize()); bufs.push(txin.serialize());
}); });
bufs.push(util.varIntBuf(this.outs.length)); bufs.push(util.varIntBuf(this.outs.length));
this.outs.forEach(function (txout) { this.outs.forEach(function(txout) {
bufs.push(txout.serialize()); bufs.push(txout.serialize());
}); });
@ -204,7 +204,7 @@ Transaction.prototype.inputs = function inputs() {
} }
return res; return res;
} };
/** /**
* Load and cache transaction inputs. * Load and cache transaction inputs.
@ -218,7 +218,7 @@ Transaction.prototype.inputs = function inputs() {
* @param {Function} callback Function to call on completion. * @param {Function} callback Function to call on completion.
*/ */
Transaction.prototype.cacheInputs = Transaction.prototype.cacheInputs =
function cacheInputs(blockChain, txStore, wait, callback) { function cacheInputs(blockChain, txStore, wait, callback) {
var self = this; var self = this;
var txCache = new TransactionInputsCache(this); var txCache = new TransactionInputsCache(this);
@ -254,8 +254,8 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
var txout = fromTxOuts[outIndex]; var txout = fromTxOuts[outIndex];
if (!txout) { if (!txout) {
throw new Error("Source output index "+outIndex+ throw new Error("Source output index " + outIndex +
" for input "+n+" out of bounds"); " for input " + n + " out of bounds");
} }
return txout; return txout;
@ -269,7 +269,7 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
throw new Error("Coinbase tx are invalid unless part of a block"); throw new Error("Coinbase tx are invalid unless part of a block");
} }
self.ins.forEach(function (txin, n) { self.ins.forEach(function(txin, n) {
var txout = getTxOut(txin, n); var txout = getTxOut(txin, n);
// TODO: Verify coinbase maturity // TODO: Verify coinbase maturity
@ -289,9 +289,9 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
if (!results[i]) { if (!results[i]) {
var txout = getTxOut(self.ins[i]); var txout = getTxOut(self.ins[i]);
log.debug('Script evaluated to false'); log.debug('Script evaluated to false');
log.debug('|- scriptSig', ""+self.ins[i].getScript()); log.debug('|- scriptSig', "" + self.ins[i].getScript());
log.debug('`- scriptPubKey', ""+txout.getScript()); log.debug('`- scriptPubKey', "" + txout.getScript());
throw new VerificationError('Script for input '+i+' evaluated to false'); throw new VerificationError('Script for input ' + i + ' evaluated to false');
} }
} }
@ -307,37 +307,34 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
function checkConflicts(err, count) { function checkConflicts(err, count) {
if (err) throw err; if (err) throw err;
self.outs.forEach(function (txout) { self.outs.forEach(function(txout) {
valueOut = valueOut.add(util.valueToBigInt(txout.v)); valueOut = valueOut.add(util.valueToBigInt(txout.v));
}); });
if (valueIn.cmp(valueOut) < 0) { if (valueIn.cmp(valueOut) < 0) {
var outValue = util.formatValue(valueOut); var outValue = util.formatValue(valueOut);
var inValue = util.formatValue(valueIn); var inValue = util.formatValue(valueIn);
throw new Error("Tx output value (BTC "+outValue+") "+ throw new Error("Tx output value (BTC " + outValue + ") " +
"exceeds input value (BTC "+inValue+")"); "exceeds input value (BTC " + inValue + ")");
} }
var fees = valueIn.sub(valueOut); var fees = valueIn.sub(valueOut);
if (count) { if (count) {
// Spent output detected, retrieve transaction that spends it // Spent output detected, retrieve transaction that spends it
blockChain.getConflictingTransactions(outpoints, function (err, results) { blockChain.getConflictingTransactions(outpoints, function(err, results) {
if (results.length) { if (results.length) {
if (buffertools.compare(results[0].getHash(), self.getHash()) === 0) { if (buffertools.compare(results[0].getHash(), self.getHash()) === 0) {
log.warn("Detected tx re-add (recoverable db corruption): " log.warn("Detected tx re-add (recoverable db corruption): " + util.formatHashAlt(results[0].getHash()));
+ util.formatHashAlt(results[0].getHash()));
// TODO: Needs to return an error for the memory pool case? // TODO: Needs to return an error for the memory pool case?
callback(null, fees); callback(null, fees);
} else { } else {
callback(new Error("At least one referenced output has" callback(new Error("At least one referenced output has" + " already been spent in tx " + util.formatHashAlt(results[0].getHash())));
+ " already been spent in tx "
+ util.formatHashAlt(results[0].getHash())));
} }
} else { } else {
callback(new Error("Outputs of this transaction are spent, but "+ callback(new Error("Outputs of this transaction are spent, but " +
"the transaction(s) that spend them are not "+ "the transaction(s) that spend them are not " +
"available. This probably means you need to "+ "available. This probably means you need to " +
"reset your database.")); "reset your database."));
} }
}); });
@ -352,13 +349,13 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
}; };
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) { Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) {
var valid = ScriptInterpreter.verifyFull( var scriptSig = this.ins[n].getScript();
this.ins[n].getScript(), return ScriptInterpreter.verifyFull(
scriptSig,
scriptPubKey, scriptPubKey,
this, n, 0, this, n, 0,
opts, opts,
callback); callback);
return valid;
}; };
/** /**
@ -375,7 +372,6 @@ Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
// Index any pubkeys affected by the outputs of this transaction // Index any pubkeys affected by the outputs of this transaction
for (var i = 0, l = this.outs.length; i < l; i++) { for (var i = 0, l = this.outs.length; i < l; i++) {
try {
var txout = this.outs[i]; var txout = this.outs[i];
var script = txout.getScript(); var script = txout.getScript();
@ -383,18 +379,11 @@ Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
if (outPubKey) { if (outPubKey) {
this.affects.push(outPubKey); this.affects.push(outPubKey);
} }
} catch (err) {
// It's not our job to validate, so we just ignore any errors and issue
// a very low level log message.
log.debug("Unable to determine affected pubkeys: " +
(err.stack ? err.stack : ""+err));
}
}; };
// Index any pubkeys affected by the inputs of this transaction // Index any pubkeys affected by the inputs of this transaction
var txIndex = txCache.txIndex; var txIndex = txCache.txIndex;
for (var i = 0, l = this.ins.length; i < l; i++) { for (var i = 0, l = this.ins.length; i < l; i++) {
try {
var txin = this.ins[i]; var txin = this.ins[i];
if (txin.isCoinBase()) continue; if (txin.isCoinBase()) continue;
@ -418,18 +407,12 @@ Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
if (outPubKey) { if (outPubKey) {
this.affects.push(outPubKey); this.affects.push(outPubKey);
} }
} catch (err) {
// It's not our job to validate, so we just ignore any errors and issue
// a very low level log message.
log.debug("Unable to determine affected pubkeys: " +
(err.stack ? err.stack : ""+err));
}
} }
} }
var affectedKeys = {}; var affectedKeys = {};
this.affects.forEach(function (pubKeyHash) { this.affects.forEach(function(pubKeyHash) {
affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash; affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash;
}); });
@ -443,25 +426,25 @@ var SIGHASH_NONE = 2;
var SIGHASH_SINGLE = 3; var SIGHASH_SINGLE = 3;
var SIGHASH_ANYONECANPAY = 80; var SIGHASH_ANYONECANPAY = 80;
Transaction.SIGHASH_ALL=SIGHASH_ALL; Transaction.SIGHASH_ALL = SIGHASH_ALL;
Transaction.SIGHASH_NONE=SIGHASH_NONE; Transaction.SIGHASH_NONE = SIGHASH_NONE;
Transaction.SIGHASH_SINGLE=SIGHASH_SINGLE; Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE;
Transaction.SIGHASH_ANYONECANPAY=SIGHASH_ANYONECANPAY; Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY;
Transaction.prototype.hashForSignature = Transaction.prototype.hashForSignature =
function hashForSignature(script, inIndex, hashType) { function hashForSignature(script, inIndex, hashType) {
if (+inIndex !== inIndex || if (+inIndex !== inIndex ||
inIndex < 0 || inIndex >= this.ins.length) { inIndex < 0 || inIndex >= this.ins.length) {
throw new Error("Input index '"+inIndex+"' invalid or out of bounds "+ throw new Error("Input index '" + inIndex + "' invalid or out of bounds " +
"("+this.ins.length+" inputs)"); "(" + this.ins.length + " inputs)");
} }
// Clone transaction // Clone transaction
var txTmp = new Transaction(); var txTmp = new Transaction();
this.ins.forEach(function (txin, i) { this.ins.forEach(function(txin, i) {
txTmp.ins.push(new TransactionIn(txin)); txTmp.ins.push(new TransactionIn(txin));
}); });
this.outs.forEach(function (txout) { this.outs.forEach(function(txout) {
txTmp.outs.push(new TransactionOut(txout)); txTmp.outs.push(new TransactionOut(txout));
}); });
txTmp.version = this.version; txTmp.version = this.version;
@ -565,7 +548,7 @@ Transaction.prototype.getStandardizedObject = function getStandardizedObject() {
var totalSize = 8; // version + lock_time var totalSize = 8; // version + lock_time
totalSize += util.getVarIntSize(this.ins.length); // tx_in count totalSize += util.getVarIntSize(this.ins.length); // tx_in count
var ins = this.ins.map(function (txin) { var ins = this.ins.map(function(txin) {
var txinObj = { var txinObj = {
prev_out: { prev_out: {
hash: buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'), hash: buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'),
@ -583,7 +566,7 @@ Transaction.prototype.getStandardizedObject = function getStandardizedObject() {
}); });
totalSize += util.getVarIntSize(this.outs.length); totalSize += util.getVarIntSize(this.outs.length);
var outs = this.outs.map(function (txout) { var outs = this.outs.map(function(txout) {
totalSize += util.getVarIntSize(txout.s.length) + totalSize += util.getVarIntSize(txout.s.length) +
txout.s.length + 8; // script_len + script + value txout.s.length + 8; // script_len + script + value
return { return {
@ -649,7 +632,7 @@ Transaction.prototype.fromObj = function fromObj(obj) {
this.outs = txobj.outs; this.outs = txobj.outs;
} }
Transaction.prototype.parse = function (parser) { Transaction.prototype.parse = function(parser) {
if (Buffer.isBuffer(parser)) { if (Buffer.isBuffer(parser)) {
this._buffer = parser; this._buffer = parser;
parser = new Parser(parser); parser = new Parser(parser);
@ -705,9 +688,9 @@ Transaction.prototype.parse = function (parser) {
* *
*/ */
Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed) { Transaction.selectUnspent = function(utxos, totalNeededAmount, allowUnconfirmed) {
var minConfirmationSteps = [6,1]; var minConfirmationSteps = [6, 1];
if (allowUnconfirmed) minConfirmationSteps.push(0); if (allowUnconfirmed) minConfirmationSteps.push(0);
var ret = []; var ret = [];
@ -719,25 +702,25 @@ Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed
do { do {
var minConfirmations = minConfirmationSteps.shift(); var minConfirmations = minConfirmationSteps.shift();
for(var i = 0; i<l; i++) { for (var i = 0; i < l; i++) {
var u = utxos[i]; var u = utxos[i];
var c = u.confirmations || 0; var c = u.confirmations || 0;
if ( c < minConfirmations || (maxConfirmations && c >=maxConfirmations) ) if (c < minConfirmations || (maxConfirmations && c >= maxConfirmations))
continue; continue;
var sat = u.amountSat || util.parseValue(u.amount); var sat = u.amountSat || util.parseValue(u.amount);
totalSat = totalSat.add(sat); totalSat = totalSat.add(sat);
ret.push(u); ret.push(u);
if(totalSat.cmp(totalNeededAmountSat) >= 0) { if (totalSat.cmp(totalNeededAmountSat) >= 0) {
fulfill = true; fulfill = true;
break; break;
} }
} }
maxConfirmations = minConfirmations; maxConfirmations = minConfirmations;
} while( !fulfill && minConfirmationSteps.length); } while (!fulfill && minConfirmationSteps.length);
//TODO(?): sort ret and check is some inputs can be avoided. //TODO(?): sort ret and check is some inputs can be avoided.
//If the initial utxos are sorted, this step would be necesary only if //If the initial utxos are sorted, this step would be necesary only if
@ -752,7 +735,7 @@ Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed
* Returns a scriptPubKey for the given address type * Returns a scriptPubKey for the given address type
*/ */
Transaction._scriptForAddress = function (addressString) { Transaction._scriptForAddress = function(addressString) {
var livenet = networks.livenet; var livenet = networks.livenet;
var testnet = networks.testnet; var testnet = networks.testnet;
@ -774,7 +757,7 @@ Transaction._sumOutputs = function(outs) {
var valueOutSat = bignum(0); var valueOutSat = bignum(0);
var l = outs.length; var l = outs.length;
for(var i=0;i<outs.length;i++) { for (var i = 0; i < outs.length; i++) {
var sat = outs[i].amountSat || util.parseValue(outs[i].amount); var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
valueOutSat = valueOutSat.add(sat); valueOutSat = valueOutSat.add(sat);
} }
@ -787,7 +770,7 @@ Transaction._sumOutputs = function(outs) {
* details on the input on .create * details on the input on .create
*/ */
Transaction.createWithFee = function (ins, outs, feeSat, opts) { Transaction.createWithFee = function(ins, outs, feeSat, opts) {
opts = opts || {}; opts = opts || {};
feeSat = feeSat || 0; feeSat = feeSat || 0;
@ -800,7 +783,7 @@ Transaction.createWithFee = function (ins, outs, feeSat, opts) {
var l = ins.length; var l = ins.length;
var valueInSat = bignum(0); var valueInSat = bignum(0);
for(var i=0; i<l; i++) { for (var i = 0; i < l; i++) {
valueInSat = valueInSat.add(util.parseValue(ins[i].amount)); valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
var txin = {}; var txin = {};
@ -821,14 +804,14 @@ Transaction.createWithFee = function (ins, outs, feeSat, opts) {
var valueOutSat = Transaction._sumOutputs(outs); var valueOutSat = Transaction._sumOutputs(outs);
valueOutSat = valueOutSat.add(feeSat); valueOutSat = valueOutSat.add(feeSat);
if (valueInSat.cmp(valueOutSat)<0) { if (valueInSat.cmp(valueOutSat) < 0) {
var inv = valueInSat.toString(); var inv = valueInSat.toString();
var ouv = valueOutSat.toString(); var ouv = valueOutSat.toString();
throw new Error('transaction input amount is less than outputs: ' + throw new Error('transaction input amount is less than outputs: ' +
inv + ' < '+ouv + ' [SAT]'); inv + ' < ' + ouv + ' [SAT]');
} }
for(var i=0;i<outs.length;i++) { for (var i = 0; i < outs.length; i++) {
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount); var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
var value = util.bigIntToValue(amountSat); var value = util.bigIntToValue(amountSat);
var script = Transaction._scriptForAddress(outs[i].address); var script = Transaction._scriptForAddress(outs[i].address);
@ -841,7 +824,7 @@ Transaction.createWithFee = function (ins, outs, feeSat, opts) {
// add remainder (without modifiying outs[]) // add remainder (without modifiying outs[])
var remainderSat = valueInSat.sub(valueOutSat); var remainderSat = valueInSat.sub(valueOutSat);
if (remainderSat.cmp(0)>0) { if (remainderSat.cmp(0) > 0) {
var remainderAddress = opts.remainderAddress || ins[0].address; var remainderAddress = opts.remainderAddress || ins[0].address;
var value = util.bigIntToValue(remainderSat); var value = util.bigIntToValue(remainderSat);
var script = Transaction._scriptForAddress(remainderAddress); var script = Transaction._scriptForAddress(remainderAddress);
@ -856,16 +839,16 @@ Transaction.createWithFee = function (ins, outs, feeSat, opts) {
return new Transaction(txobj); return new Transaction(txobj);
}; };
Transaction.prototype.calcSize = function () { Transaction.prototype.calcSize = function() {
var totalSize = 8; // version + lock_time var totalSize = 8; // version + lock_time
totalSize += util.getVarIntSize(this.ins.length); // tx_in count totalSize += util.getVarIntSize(this.ins.length); // tx_in count
this.ins.forEach(function (txin) { this.ins.forEach(function(txin) {
totalSize += 36 + util.getVarIntSize(txin.s.length) + totalSize += 36 + util.getVarIntSize(txin.s.length) +
txin.s.length + 4; // outpoint + script_len + script + sequence txin.s.length + 4; // outpoint + script_len + script + sequence
}); });
totalSize += util.getVarIntSize(this.outs.length); totalSize += util.getVarIntSize(this.outs.length);
this.outs.forEach(function (txout) { this.outs.forEach(function(txout) {
totalSize += util.getVarIntSize(txout.s.length) + totalSize += util.getVarIntSize(txout.s.length) +
txout.s.length + 8; // script_len + script + value txout.s.length + 8; // script_len + script + value
}); });
@ -881,12 +864,12 @@ Transaction.prototype.getSize = function getHash() {
}; };
Transaction.prototype.isComplete = function () { Transaction.prototype.isComplete = function() {
var l = this.ins.length; var l = this.ins.length;
var ret = true; var ret = true;
for (var i=0; i<l; i++) { for (var i = 0; i < l; i++) {
if ( buffertools.compare(this.ins[i].s,util.EMPTY_BUFFER)===0 ) { if (buffertools.compare(this.ins[i].s, util.EMPTY_BUFFER) === 0) {
ret = false; ret = false;
break; break;
} }
@ -909,7 +892,7 @@ Transaction.prototype.isComplete = function () {
* *
*/ */
Transaction.prototype.sign = function (selectedUtxos, keys, opts) { Transaction.prototype.sign = function(selectedUtxos, keys, opts) {
var self = this; var self = this;
var complete = false; var complete = false;
var m = keys.length; var m = keys.length;
@ -921,8 +904,8 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
var inputMap = []; var inputMap = [];
var l = selectedUtxos.length; var l = selectedUtxos.length;
for(var i=0; i<l; i++) { for (var i = 0; i < l; i++) {
inputMap[i]= { inputMap[i] = {
address: selectedUtxos[i].address, address: selectedUtxos[i].address,
scriptPubKey: selectedUtxos[i].scriptPubKey scriptPubKey: selectedUtxos[i].scriptPubKey
}; };
@ -932,18 +915,20 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
var walletKeyMap = {}; var walletKeyMap = {};
var l = keys.length; var l = keys.length;
var wk; var wk;
for(var i=0; i<l; i++) { for (var i = 0; i < l; i++) {
var k = keys[i]; var k = keys[i];
if (typeof k === 'string') { if (typeof k === 'string') {
var pk = new PrivateKey(k); var pk = new PrivateKey(k);
wk = new WalletKey({network: pk.network()}); wk = new WalletKey({
wk.fromObj({priv:k}); network: pk.network()
} });
else if (k instanceof WalletKey) { wk.fromObj({
priv: k
});
} else if (k instanceof WalletKey) {
wk = k; wk = k;
} } else {
else {
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects'); throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
} }
walletKeyMap[wk.storeObj().addr] = wk; walletKeyMap[wk.storeObj().addr] = wk;
@ -951,19 +936,19 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
var inputSigned = 0; var inputSigned = 0;
l = self.ins.length; l = self.ins.length;
for(var i=0;i<l;i++) { for (var i = 0; i < l; i++) {
var aIn = self.ins[i]; var aIn = self.ins[i];
var wk = walletKeyMap[inputMap[i].address]; var wk = walletKeyMap[inputMap[i].address];
if (typeof wk === 'undefined') { if (typeof wk === 'undefined') {
if ( buffertools.compare(aIn.s,util.EMPTY_BUFFER)!==0 ) if (buffertools.compare(aIn.s, util.EMPTY_BUFFER) !== 0)
inputSigned++; inputSigned++;
continue; continue;
} }
var scriptBuf = new Buffer(inputMap[i].scriptPubKey, 'hex'); var scriptBuf = new Buffer(inputMap[i].scriptPubKey, 'hex');
var s = new Script(scriptBuf); var s = new Script(scriptBuf);
if (s.classify() !== Script.TX_PUBKEYHASH) { if (s.classify() !== Script.TX_PUBKEYHASH) {
throw new Error('input:'+i+' script type:'+ s.getRawOutType() +' not supported yet'); throw new Error('input:' + i + ' script type:' + s.getRawOutType() + ' not supported yet');
} }
var txSigHash = self.hashForSignature(s, i, signhash); var txSigHash = self.hashForSignature(s, i, signhash);
@ -972,10 +957,10 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
var triesLeft = 10; var triesLeft = 10;
do { do {
sigRaw = wk.privKey.signSync(txSigHash); sigRaw = wk.privKey.signSync(txSigHash);
} while ( wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false && triesLeft-- ); } while (wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false && triesLeft--);
if (!triesLeft) { if (!triesLeft) {
log.debug('could not sign input:'+i +' verification failed'); log.debug('could not sign input:' + i + ' verification failed');
continue; continue;
} }
@ -1011,7 +996,7 @@ Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
* *
*/ */
Transaction.create = function (utxos, outs, opts) { Transaction.create = function(utxos, outs, opts) {
//starting size estimation //starting size estimation
var size = 500; var size = 500;
@ -1025,23 +1010,19 @@ Transaction.create = function (utxos, outs, opts) {
var selectedUtxos; var selectedUtxos;
do { do {
// based on https://en.bitcoin.it/wiki/Transaction_fees // based on https://en.bitcoin.it/wiki/Transaction_fees
maxSizeK = parseInt(size/1000) + 1; maxSizeK = parseInt(size / 1000) + 1;
var feeSat = givenFeeSat var feeSat = givenFeeSat ? givenFeeSat : maxSizeK * FEE_PER_1000B_SAT;
? givenFeeSat : maxSizeK * FEE_PER_1000B_SAT ;
var valueOutSat = Transaction var valueOutSat = Transaction
._sumOutputs(outs) ._sumOutputs(outs)
.add(feeSat); .add(feeSat);
selectedUtxos = Transaction selectedUtxos = Transaction
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed); .selectUnspent(utxos, valueOutSat / util.COIN, opts.allowUnconfirmed);
if (!selectedUtxos) { if (!selectedUtxos) {
throw new Error( throw new Error(
'the given UTXOs dont sum up the given outputs: ' 'the given UTXOs dont sum up the given outputs: ' + valueOutSat.toString() + ' (fee is ' + feeSat + ' )SAT'
+ valueOutSat.toString()
+ ' (fee is ' + feeSat
+ ' )SAT'
); );
} }
var tx = Transaction.createWithFee(selectedUtxos, outs, feeSat, { var tx = Transaction.createWithFee(selectedUtxos, outs, feeSat, {
@ -1050,9 +1031,12 @@ Transaction.create = function (utxos, outs, opts) {
}); });
size = tx.getSize(); size = tx.getSize();
} while (size > (maxSizeK+1)*1000 ); } while (size > (maxSizeK + 1) * 1000);
return {tx: tx, selectedUtxos: selectedUtxos}; return {
tx: tx,
selectedUtxos: selectedUtxos
};
}; };
@ -1119,21 +1103,20 @@ Transaction.create = function (utxos, outs, opts) {
* *
*/ */
Transaction.createAndSign = function (utxos, outs, keys, opts) { Transaction.createAndSign = function(utxos, outs, keys, opts) {
var ret = Transaction.create(utxos, outs, opts); var ret = Transaction.create(utxos, outs, opts);
ret.tx.sign(ret.selectedUtxos, keys); ret.tx.sign(ret.selectedUtxos, keys);
return ret; return ret;
}; };
var TransactionInputsCache = exports.TransactionInputsCache = var TransactionInputsCache = exports.TransactionInputsCache =
function TransactionInputsCache(tx) function TransactionInputsCache(tx) {
{
var txList = []; var txList = [];
var txList64 = []; var txList64 = [];
var reqOuts = {}; var reqOuts = {};
// Get list of transactions required for verification // Get list of transactions required for verification
tx.ins.forEach(function (txin) { tx.ins.forEach(function(txin) {
if (txin.isCoinBase()) return; if (txin.isCoinBase()) return;
var hash = txin.o.slice(0, 32); var hash = txin.o.slice(0, 32);
@ -1156,8 +1139,7 @@ function TransactionInputsCache(tx)
this.callbacks = []; this.callbacks = [];
}; };
TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback) TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback) {
{
var self = this; var self = this;
var complete = false; var complete = false;
@ -1167,7 +1149,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
} }
var missingTx = {}; var missingTx = {};
self.txList64.forEach(function (hash64) { self.txList64.forEach(function(hash64) {
missingTx[hash64] = true; missingTx[hash64] = true;
}); });
@ -1176,10 +1158,10 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
if (err) throw err; if (err) throw err;
// Index memory transactions // Index memory transactions
txs.forEach(function (tx) { txs.forEach(function(tx) {
var hash64 = tx.getHash().toString('base64'); var hash64 = tx.getHash().toString('base64');
var obj = {}; var obj = {};
Object.keys(self.requiredOuts[hash64]).forEach(function (o) { Object.keys(self.requiredOuts[hash64]).forEach(function(o) {
obj[+o] = tx.outs[+o]; obj[+o] = tx.outs[+o];
}); });
self.txIndex[hash64] = obj; self.txIndex[hash64] = obj;
@ -1206,7 +1188,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
// TODO: Major speedup should be possible if we load only the outs and not // TODO: Major speedup should be possible if we load only the outs and not
// whole transactions. // whole transactions.
var callback = this; var callback = this;
blockChain.getOutputsByHashes(self.txList, function (err, result) { blockChain.getOutputsByHashes(self.txList, function(err, result) {
callback(err, result); callback(err, result);
}); });
}, },
@ -1216,7 +1198,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
var missingTxDbg = ''; var missingTxDbg = '';
if (Object.keys(missingTx).length) { if (Object.keys(missingTx).length) {
missingTxDbg = Object.keys(missingTx).map(function (hash64) { missingTxDbg = Object.keys(missingTx).map(function(hash64) {
return util.formatHash(new Buffer(hash64, 'base64')); return util.formatHash(new Buffer(hash64, 'base64'));
}).join(','); }).join(',');
} }
@ -1224,11 +1206,10 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
if (wait && Object.keys(missingTx).length) { if (wait && Object.keys(missingTx).length) {
// TODO: This might no longer be needed now that saveTransactions uses // TODO: This might no longer be needed now that saveTransactions uses
// the safe=true option. // the safe=true option.
setTimeout(function () { setTimeout(function() {
var missingHashes = Object.keys(missingTx); var missingHashes = Object.keys(missingTx);
if (missingHashes.length) { if (missingHashes.length) {
self.callback(new Error('Missing inputs (timeout while searching): ' self.callback(new Error('Missing inputs (timeout while searching): ' + missingTxDbg));
+ missingTxDbg));
} else if (!complete) { } else if (!complete) {
self.callback(new Error('Callback failed to trigger')); self.callback(new Error('Callback failed to trigger'));
} }
@ -1243,8 +1224,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w
}; };
TransactionInputsCache.prototype.callback = function callback(err) TransactionInputsCache.prototype.callback = function callback(err) {
{
var args = Array.prototype.slice.apply(arguments); var args = Array.prototype.slice.apply(arguments);
// Empty the callback array first (because downstream functions could add new // Empty the callback array first (because downstream functions could add new
@ -1252,14 +1232,9 @@ TransactionInputsCache.prototype.callback = function callback(err)
var cbs = this.callbacks; var cbs = this.callbacks;
this.callbacks = []; this.callbacks = [];
try { cbs.forEach(function(cb) {
cbs.forEach(function (cb) {
cb.apply(null, args); cb.apply(null, args);
}); });
} catch (err) {
log.err("Callback error after connecting tx inputs: "+
(err.stack ? err.stack : err.toString()));
}
}; };
module.exports = require('soop')(Transaction); module.exports = require('soop')(Transaction);

80
test/test.Transaction.js

@ -24,17 +24,15 @@ function parse_test_transaction(entry) {
// Ignore comments // Ignore comments
if (entry.length !== 3) return; if (entry.length !== 3) return;
var inputs = []; var inputs = {};
entry[0].forEach(function(vin) { entry[0].forEach(function(vin) {
var hash = vin[0]; var hash = (vin[0]);
var index = vin[1]; var index = vin[1];
var scriptPubKey = Script.fromHumanReadable(vin[2]); var scriptPubKey = Script.fromHumanReadable(vin[2]);
inputs.push({ var mapKey = [hash, index];
'prev_tx_hash': hash, console.log('mapkey=' + mapKey);
'index': index, inputs[mapKey] = scriptPubKey;
'scriptPubKey': scriptPubKey
});
}); });
@ -341,53 +339,39 @@ describe('Transaction', function() {
* Bitcoin core transaction tests * Bitcoin core transaction tests
*/ */
// Verify that known valid transactions are intepretted correctly // Verify that known valid transactions are intepretted correctly
var cb = function(err, results) {
should.not.exist(err);
should.exist(results);
results.should.equal(true);
};
testdata.dataTxValid.forEach(function(datum) { testdata.dataTxValid.forEach(function(datum) {
var testTx = parse_test_transaction(datum); if (datum.length < 3) return;
if (!testTx) return; var raw = datum[1];
var verifyP2SH = datum[2]; var verifyP2SH = datum[2];
var transactionString = buffertools.toHex(
testTx.transaction.serialize());
it('valid tx=' + transactionString, function() { it('valid tx=' + raw, function() {
// Verify that all inputs are valid // Verify that all inputs are valid
testTx.inputs.forEach(function(input) { var testTx = parse_test_transaction(datum);
console.log(raw);
//buffertools.toHex(testTx.transaction.serialize()).should.equal(raw);
var inputs = testTx.transaction.inputs();
for (var i = 0; i < inputs.length; i++) {
console.log(' input number #########' + i);
var input = inputs[i];
buffertools.reverse(input[0]);
input[0] = buffertools.toHex(input[0]);
var mapKey = [input];
var scriptPubKey = testTx.inputs[mapKey];
if (!scriptPubKey) throw new Error('asdasdasdasd');
testTx.transaction.verifyInput( testTx.transaction.verifyInput(
input.index, i,
input.scriptPubKey, scriptPubKey, {
{ verifyP2SH: verifyP2SH, dontVerifyStrictEnc: true}, verifyP2SH: verifyP2SH,
function(err, results) { dontVerifyStrictEnc: true
// Exceptions raised inside this function will be handled },
// ...by this function, so ignore if that is the case cb);
if (err && err.constructor.name === 'AssertionError') return; }
should.not.exist(err);
should.exist(results);
results.should.equal(true);
});
});
}); });
}); });
// Verify that known invalid transactions are interpretted correctly
testdata.dataTxInvalid.forEach(function(datum) {
var testTx = parse_test_transaction(datum);
if (!testTx) return;
var transactionString = buffertools.toHex(
testTx.transaction.serialize());
it('valid tx=' + transactionString, function() {
// Verify that all inputs are invalid
testTx.inputs.forEach(function(input) {
testTx.transaction.verifyInput(input.index, input.scriptPubKey,
function(err, results) {
// Exceptions raised inside this function will be handled
// ...by this function, so ignore if that is the case
if (err && err.constructor.name === 'AssertionError') return;
// There should either be an error, or the results should be false.
(err !== null || (!err && results === false)).should.equal(true);
});
});
});
});
}); });

134
util/util.js

@ -1,27 +1,24 @@
var crypto = require('crypto'); var crypto = require('crypto');
var bignum = require('bignum'); var bignum = require('bignum');
var Binary = require('binary'); var Binary = require('binary');
var Put = require('bufferput'); var Put = require('bufferput');
var buffertools = require('buffertools'); var buffertools = require('buffertools');
var browser; var browser;
if (!process.versions) { var inBrowser = !process.versions;
// browser version if (inBrowser) {
browser = require('../browser/vendor-bundle.js'); browser = require('../browser/vendor-bundle.js');
} }
var sha256 = exports.sha256 = function (data) { var sha256 = exports.sha256 = function(data) {
return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary');
}; };
var ripe160 = exports.ripe160 = function (data) { var ripe160 = exports.ripe160 = function(data) {
if (!Buffer.isBuffer(data)) { if (!Buffer.isBuffer(data)) {
throw new Error('arg should be a buffer'); throw new Error('arg should be a buffer');
} }
if (inBrowser) {
if (!process.versions) { var w = new browser.crypto31.lib.WordArray.init(browser.Crypto.util.bytesToWords(data), data.length);
var w = new browser.crypto31.lib.WordArray.init(Crypto.util.bytesToWords(data), data.length);
var wordArray = browser.crypto31.RIPEMD160(w); var wordArray = browser.crypto31.RIPEMD160(w);
var words = wordArray.words; var words = wordArray.words;
var answer = []; var answer = [];
@ -33,22 +30,22 @@ var ripe160 = exports.ripe160 = function (data) {
return new Buffer(crypto.createHash('rmd160').update(data).digest('binary'), 'binary'); return new Buffer(crypto.createHash('rmd160').update(data).digest('binary'), 'binary');
}; };
var sha1 = exports.sha1 = function (data) { var sha1 = exports.sha1 = function(data) {
return new Buffer(crypto.createHash('sha1').update(data).digest('binary'), 'binary'); return new Buffer(crypto.createHash('sha1').update(data).digest('binary'), 'binary');
}; };
var twoSha256 = exports.twoSha256 = function (data) { var twoSha256 = exports.twoSha256 = function(data) {
return sha256(sha256(data)); return sha256(sha256(data));
}; };
var sha256ripe160 = exports.sha256ripe160 = function (data) { var sha256ripe160 = exports.sha256ripe160 = function(data) {
return ripe160(sha256(data)); return ripe160(sha256(data));
}; };
/** /**
* Format a block hash like the official client does. * Format a block hash like the official client does.
*/ */
var formatHash = exports.formatHash = function (hash) { var formatHash = exports.formatHash = function(hash) {
var hashEnd = new Buffer(10); var hashEnd = new Buffer(10);
hash.copy(hashEnd, 0, 22, 32); hash.copy(hashEnd, 0, 22, 32);
return buffertools.reverse(hashEnd).toString('hex'); return buffertools.reverse(hashEnd).toString('hex');
@ -57,7 +54,7 @@ var formatHash = exports.formatHash = function (hash) {
/** /**
* Display the whole hash, as hex, in correct endian order. * Display the whole hash, as hex, in correct endian order.
*/ */
var formatHashFull = exports.formatHashFull = function (hash) { var formatHashFull = exports.formatHashFull = function(hash) {
var copy = new Buffer(hash.length); var copy = new Buffer(hash.length);
hash.copy(copy); hash.copy(copy);
var hex = buffertools.toHex(buffertools.reverse(copy)); var hex = buffertools.toHex(buffertools.reverse(copy));
@ -69,13 +66,13 @@ var formatHashFull = exports.formatHashFull = function (hash) {
* *
* Formats a block hash by removing leading zeros and truncating to 10 characters. * Formats a block hash by removing leading zeros and truncating to 10 characters.
*/ */
var formatHashAlt = exports.formatHashAlt = function (hash) { var formatHashAlt = exports.formatHashAlt = function(hash) {
var hex = formatHashFull(hash); var hex = formatHashFull(hash);
hex = hex.replace(/^0*/, ''); hex = hex.replace(/^0*/, '');
return hex.substr(0, 10); return hex.substr(0, 10);
}; };
var formatBuffer = exports.formatBuffer = function (buffer, maxLen) { var formatBuffer = exports.formatBuffer = function(buffer, maxLen) {
// Calculate amount of bytes to display // Calculate amount of bytes to display
if (maxLen === null) { if (maxLen === null) {
maxLen = 10; maxLen = 10;
@ -96,41 +93,47 @@ var formatBuffer = exports.formatBuffer = function (buffer, maxLen) {
return output; return output;
}; };
var valueToBigInt = exports.valueToBigInt = function (valueBuffer) { var valueToBigInt = exports.valueToBigInt = function(valueBuffer) {
if (Buffer.isBuffer(valueBuffer)) { if (Buffer.isBuffer(valueBuffer)) {
return bignum.fromBuffer(valueBuffer, {endian: 'little', size: 8}); return bignum.fromBuffer(valueBuffer, {
endian: 'little',
size: 8
});
} else { } else {
return valueBuffer; return valueBuffer;
} }
}; };
var bigIntToValue = exports.bigIntToValue = function (valueBigInt) { var bigIntToValue = exports.bigIntToValue = function(valueBigInt) {
if (Buffer.isBuffer(valueBigInt)) { if (Buffer.isBuffer(valueBigInt)) {
return valueBigInt; return valueBigInt;
} else { } else {
return valueBigInt.toBuffer({endian: 'little', size: 8}); return valueBigInt.toBuffer({
endian: 'little',
size: 8
});
} }
}; };
var fitsInNBits = function(integer, n) { var fitsInNBits = function(integer, n) {
// TODO: make this efficient!!! // TODO: make this efficient!!!
return integer.toString(2).replace('-','').length < n; return integer.toString(2).replace('-', '').length < n;
}; };
exports.bytesNeededToStore = bytesNeededToStore = function(integer) { exports.bytesNeededToStore = bytesNeededToStore = function(integer) {
if (integer === 0) return 0; if (integer === 0) return 0;
return Math.ceil(((integer).toString(2).replace('-','').length + 1)/ 8); return Math.ceil(((integer).toString(2).replace('-', '').length + 1) / 8);
}; };
exports.negativeBuffer = negativeBuffer = function(b) { exports.negativeBuffer = negativeBuffer = function(b) {
// implement two-complement negative // implement two-complement negative
var c = new Buffer(b.length); var c = new Buffer(b.length);
// negate each byte // negate each byte
for (var i=0; i<b.length; i++){ for (var i = 0; i < b.length; i++) {
c[i] = ~b[i]; c[i] = ~b[i];
if (c[i] < 0) c[i] += 256; if (c[i] < 0) c[i] += 256;
} }
// add one // add one
for (var i=b.length - 1; i>=0; i--){ for (var i = b.length - 1; i >= 0; i--) {
c[i] += 1; c[i] += 1;
if (c[i] >= 256) c[i] -= 256; if (c[i] >= 256) c[i] -= 256;
if (c[i] !== 0) break; if (c[i] !== 0) break;
@ -149,9 +152,9 @@ exports.intToBuffer2C = function(integer) {
var buf = new Put(); var buf = new Put();
var s = integer.toString(16); var s = integer.toString(16);
var neg = s[0] === '-'; var neg = s[0] === '-';
s = s.replace('-',''); s = s.replace('-', '');
for (var i=0; i<size; i++) { for (var i = 0; i < size; i++) {
var si = s.substring(s.length - 2*(i+1), s.length - 2*(i)); var si = s.substring(s.length - 2 * (i + 1), s.length - 2 * (i));
if (si.lenght === 1) { if (si.lenght === 1) {
si = '0' + si; si = '0' + si;
} }
@ -234,49 +237,44 @@ exports.bufferSMToInt = function(v) {
var formatValue = exports.formatValue = function (valueBuffer) { var formatValue = exports.formatValue = function(valueBuffer) {
var value = valueToBigInt(valueBuffer).toString(); var value = valueToBigInt(valueBuffer).toString();
var integerPart = value.length > 8 ? value.substr(0, value.length-8) : '0'; var integerPart = value.length > 8 ? value.substr(0, value.length - 8) : '0';
var decimalPart = value.length > 8 ? value.substr(value.length-8) : value; var decimalPart = value.length > 8 ? value.substr(value.length - 8) : value;
while (decimalPart.length < 8) { while (decimalPart.length < 8) {
decimalPart = "0"+decimalPart; decimalPart = "0" + decimalPart;
} }
decimalPart = decimalPart.replace(/0*$/, ''); decimalPart = decimalPart.replace(/0*$/, '');
while (decimalPart.length < 2) { while (decimalPart.length < 2) {
decimalPart += "0"; decimalPart += "0";
} }
return integerPart+"."+decimalPart; return integerPart + "." + decimalPart;
}; };
var reFullVal = /^\s*(\d+)\.(\d+)/; var reFullVal = /^\s*(\d+)\.(\d+)/;
var reFracVal = /^\s*\.(\d+)/; var reFracVal = /^\s*\.(\d+)/;
var reWholeVal = /^\s*(\d+)/; var reWholeVal = /^\s*(\d+)/;
function padFrac(frac) function padFrac(frac) {
{ frac = frac.substr(0, 8); //truncate to 8 decimal places
frac=frac.substr(0,8); //truncate to 8 decimal places
while (frac.length < 8) while (frac.length < 8)
frac = frac + '0'; frac = frac + '0';
return frac; return frac;
} }
function parseFullValue(res) function parseFullValue(res) {
{
return bignum(res[1]).mul('100000000').add(padFrac(res[2])); return bignum(res[1]).mul('100000000').add(padFrac(res[2]));
} }
function parseFracValue(res) function parseFracValue(res) {
{
return bignum(padFrac(res[1])); return bignum(padFrac(res[1]));
} }
function parseWholeValue(res) function parseWholeValue(res) {
{
return bignum(res[1]).mul('100000000'); return bignum(res[1]).mul('100000000');
} }
exports.parseValue = function parseValue(valueStr) exports.parseValue = function parseValue(valueStr) {
{
if (typeof valueStr !== 'string') if (typeof valueStr !== 'string')
valueStr = valueStr.toString(); valueStr = valueStr.toString();
@ -296,11 +294,11 @@ exports.parseValue = function parseValue(valueStr)
}; };
// Utility that synchronizes function calls based on a key // Utility that synchronizes function calls based on a key
var createSynchrotron = exports.createSynchrotron = function (fn) { var createSynchrotron = exports.createSynchrotron = function(fn) {
var table = {}; var table = {};
return function (key) { return function(key) {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var run = function () { var run = function() {
// Function fn() will call when it finishes // Function fn() will call when it finishes
args[0] = function next() { args[0] = function next() {
if (table[key]) { if (table[key]) {
@ -333,21 +331,23 @@ var createSynchrotron = exports.createSynchrotron = function (fn) {
* *
* @returns Buffer random nonce * @returns Buffer random nonce
*/ */
var generateNonce = exports.generateNonce = function () { var generateNonce = exports.generateNonce = function() {
var b32 = 0x100000000, ff = 0xff; var b32 = 0x100000000,
var b = new Buffer(8), i = 0; ff = 0xff;
var b = new Buffer(8),
i = 0;
// Generate eight random bytes // Generate eight random bytes
r = Math.random()*b32; r = Math.random() * b32;
b[i++] = r & ff; b[i++] = r & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
r = Math.random()*b32; r = Math.random() * b32;
b[i++] = r & ff; b[i++] = r & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
b[i++] = (r=r>>>8) & ff; b[i++] = (r = r >>> 8) & ff;
return b; return b;
}; };
@ -357,10 +357,10 @@ var generateNonce = exports.generateNonce = function () {
* *
* This function calculates the difficulty target given the difficulty bits. * This function calculates the difficulty target given the difficulty bits.
*/ */
var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) { var decodeDiffBits = exports.decodeDiffBits = function(diffBits, asBigInt) {
diffBits = +diffBits; diffBits = +diffBits;
var target = bignum(diffBits & 0xffffff); var target = bignum(diffBits & 0xffffff);
target = target.shiftLeft(8*((diffBits >>> 24) - 3)); target = target.shiftLeft(8 * ((diffBits >>> 24) - 3));
if (asBigInt) { if (asBigInt) {
return target; return target;
@ -370,7 +370,7 @@ var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) {
var diffBuf = target.toBuffer(); var diffBuf = target.toBuffer();
var targetBuf = new Buffer(32); var targetBuf = new Buffer(32);
buffertools.fill(targetBuf, 0); buffertools.fill(targetBuf, 0);
diffBuf.copy(targetBuf, 32-diffBuf.length); diffBuf.copy(targetBuf, 32 - diffBuf.length);
return targetBuf; return targetBuf;
}; };
@ -394,7 +394,7 @@ var encodeDiffBits = exports.encodeDiffBits = function encodeDiffBits(target) {
var compact = size << 24; var compact = size << 24;
if (size >= 1) compact |= mpiBuf[4] << 16; if (size >= 1) compact |= mpiBuf[4] << 16;
if (size >= 2) compact |= mpiBuf[5] << 8; if (size >= 2) compact |= mpiBuf[5] << 8;
if (size >= 3) compact |= mpiBuf[6] ; if (size >= 3) compact |= mpiBuf[6];
return compact; return compact;
}; };
@ -405,16 +405,20 @@ var encodeDiffBits = exports.encodeDiffBits = function encodeDiffBits(target) {
* This function calculates the maximum difficulty target divided by the given * This function calculates the maximum difficulty target divided by the given
* difficulty target. * difficulty target.
*/ */
var calcDifficulty = exports.calcDifficulty = function (target) { var calcDifficulty = exports.calcDifficulty = function(target) {
if (!Buffer.isBuffer(target)) { if (!Buffer.isBuffer(target)) {
target = decodeDiffBits(target); target = decodeDiffBits(target);
} }
var targetBigint = bignum.fromBuffer(target, {order: 'forward'}); var targetBigint = bignum.fromBuffer(target, {
var maxBigint = bignum.fromBuffer(MAX_TARGET, {order: 'forward'}); order: 'forward'
});
var maxBigint = bignum.fromBuffer(MAX_TARGET, {
order: 'forward'
});
return maxBigint.div(targetBigint).toNumber(); return maxBigint.div(targetBigint).toNumber();
}; };
var reverseBytes32 = exports.reverseBytes32 = function (data) { var reverseBytes32 = exports.reverseBytes32 = function(data) {
if (data.length % 4) { if (data.length % 4) {
throw new Error("Util.reverseBytes32(): Data length must be multiple of 4"); throw new Error("Util.reverseBytes32(): Data length must be multiple of 4");
} }

Loading…
Cancel
Save