bunch of interim (broken) stuff
This commit is contained in:
commit
56a20ef25d
|
@ -0,0 +1,2 @@
|
|||
build
|
||||
node_modules
|
|
@ -4,12 +4,12 @@ function ClassSpec(b) {
|
|||
var base58 = b.base58 || require('base58-native').base58Check;
|
||||
|
||||
// Constructor. Takes the following forms:
|
||||
// new BitcoinAddress();
|
||||
// new BitcoinAddress(<base58_address_string>)
|
||||
// new BitcoinAddress(<21-byte-buffer>)
|
||||
// new BitcoinAddress(<data>, <encoding>)
|
||||
// new BitcoinAddress(<version>, <20-byte-hash>)
|
||||
function BitcoinAddress(arg1, arg2) {
|
||||
// new Address();
|
||||
// new Address(<base58_address_string>)
|
||||
// new Address(<21-byte-buffer>)
|
||||
// new Address(<data>, <encoding>)
|
||||
// new Address(<version>, <20-byte-hash>)
|
||||
function Address(arg1, arg2) {
|
||||
if(typeof arg1 == 'number') {
|
||||
this.data = new Buffer(21);
|
||||
this.__proto__ = encodings['binary'];
|
||||
|
@ -26,7 +26,7 @@ function ClassSpec(b) {
|
|||
};
|
||||
|
||||
// get or set the bitcoin address version (the first byte of the address)
|
||||
BitcoinAddress.prototype.version = function(num) {
|
||||
Address.prototype.version = function(num) {
|
||||
if(num || (num === 0)) {
|
||||
this.doAsBinary(function() {this.data.writeUInt8(num, 0);});
|
||||
return num;
|
||||
|
@ -35,7 +35,7 @@ function ClassSpec(b) {
|
|||
};
|
||||
|
||||
// get or set the hash data (as a Buffer object)
|
||||
BitcoinAddress.prototype.hash = function(data) {
|
||||
Address.prototype.hash = function(data) {
|
||||
if(data) {
|
||||
this.doAsBinary(function() {data.copy(this.data,1);});
|
||||
return data;
|
||||
|
@ -44,7 +44,7 @@ function ClassSpec(b) {
|
|||
};
|
||||
|
||||
// get or set the encoding used (transforms data)
|
||||
BitcoinAddress.prototype.encoding = function(encoding) {
|
||||
Address.prototype.encoding = function(encoding) {
|
||||
if(encoding && (encoding != this._encoding)) {
|
||||
this.data = this.as(encoding);
|
||||
this.__proto__ = encodings[encoding];
|
||||
|
@ -53,28 +53,28 @@ function ClassSpec(b) {
|
|||
};
|
||||
|
||||
// answer a new instance having the given encoding
|
||||
BitcoinAddress.prototype.withEncoding = function(encoding) {
|
||||
return new BitcoinAddress(this.as(encoding), encoding);
|
||||
Address.prototype.withEncoding = function(encoding) {
|
||||
return new Address(this.as(encoding), encoding);
|
||||
};
|
||||
|
||||
// answer the data in the given encoding
|
||||
BitcoinAddress.prototype.as = function(encoding) {
|
||||
Address.prototype.as = function(encoding) {
|
||||
if(!encodings[encoding]) throw new Error('invalid encoding');
|
||||
return this.converters[encoding].call(this);
|
||||
};
|
||||
|
||||
// validate the address (basically just check that we have 21 bytes)
|
||||
BitcoinAddress.prototype.validate = function() {
|
||||
Address.prototype.validate = function() {
|
||||
this.withEncoding('binary').validate();
|
||||
};
|
||||
|
||||
// convert to a string (in base58 form)
|
||||
BitcoinAddress.prototype.toString = function() {
|
||||
Address.prototype.toString = function() {
|
||||
return this.as('base58');
|
||||
};
|
||||
|
||||
// utility
|
||||
BitcoinAddress.prototype.doAsBinary = function(callback) {
|
||||
Address.prototype.doAsBinary = function(callback) {
|
||||
var oldEncoding = this.encoding();
|
||||
this.encoding('binary');
|
||||
callback.apply(this);
|
||||
|
@ -82,11 +82,11 @@ function ClassSpec(b) {
|
|||
};
|
||||
|
||||
// Setup support for various address encodings. The object for
|
||||
// each encoding inherits from the BitcoinAddress prototype. This
|
||||
// each encoding inherits from the Address prototype. This
|
||||
// allows any encoding to override any method...changing the encoding
|
||||
// for an instance will change the encoding it inherits from. Note,
|
||||
// this will present some problems for anyone wanting to inherit from
|
||||
// BitcoinAddress (we'll deal with that when needed).
|
||||
// Address (we'll deal with that when needed).
|
||||
var encodings = {
|
||||
'binary': {
|
||||
converters: {
|
||||
|
@ -135,9 +135,9 @@ function ClassSpec(b) {
|
|||
if(!encodings[k].converters[k])
|
||||
encodings[k].converters[k] = function() {return this.data;};
|
||||
encodings[k]._encoding = k;
|
||||
encodings[k].__proto__ = BitcoinAddress.prototype;
|
||||
encodings[k].__proto__ = Address.prototype;
|
||||
};
|
||||
|
||||
return BitcoinAddress;
|
||||
return Address;
|
||||
};
|
||||
module.defineClass(ClassSpec);
|
|
@ -0,0 +1,569 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var Util = b.Util || require('../ext/util');
|
||||
var Debug1 = b.Debug1 || function() {};
|
||||
var Script = b.Script || require('./script').class();
|
||||
var Bignum = b.Bignum || require('bignum');
|
||||
var Put = b.Put || require('bufferput');
|
||||
var Step = b.Step || require('step');
|
||||
var Transaction = b.Transaction || require('./transaction');
|
||||
var TransactionIn = b.TransactionIn || require('./transactionIn');
|
||||
var TransactionOut = b.TransactionOut || require('./transactionOut');
|
||||
var COINBASE_OP = Transaction.COINBASE_OP;
|
||||
var VerificationError = b.VerificationError || require('../ext/error').VerificationError;
|
||||
var BlockRules = {
|
||||
maxTimeOffset: 2 * 60 * 60, // How far block timestamps can be into the future
|
||||
largestHash: Bignum(2).pow(256)
|
||||
};
|
||||
|
||||
function Block(data)
|
||||
{
|
||||
if ("object" !== typeof data) {
|
||||
data = {};
|
||||
}
|
||||
this.hash = data.hash || null;
|
||||
this.prev_hash = data.prev_hash || Util.NULL_HASH;
|
||||
this.merkle_root = data.merkle_root || Util.NULL_HASH;
|
||||
this.timestamp = data.timestamp || 0;
|
||||
this.bits = data.bits || 0;
|
||||
this.nonce = data.nonce || 0;
|
||||
this.version = data.version || 0;
|
||||
this.height = data.height || 0;
|
||||
this.size = data.size || 0;
|
||||
this.active = data.active || false;
|
||||
this.chainWork = data.chainWork || Util.EMPTY_BUFFER;
|
||||
this.txs = data.txs || [];
|
||||
};
|
||||
|
||||
Block.prototype.getHeader = function getHeader() {
|
||||
put = Put();
|
||||
put.word32le(this.version);
|
||||
put.put(this.prev_hash);
|
||||
put.put(this.merkle_root);
|
||||
put.word32le(this.timestamp);
|
||||
put.word32le(this.bits);
|
||||
put.word32le(this.nonce);
|
||||
return put.buffer();
|
||||
};
|
||||
|
||||
Block.prototype.calcHash = function calcHash() {
|
||||
var header = this.getHeader();
|
||||
|
||||
return Util.twoSha256(header);
|
||||
};
|
||||
|
||||
Block.prototype.checkHash = function checkHash() {
|
||||
if (!this.hash || !this.hash.length) return false;
|
||||
return this.calcHash().compare(this.hash) == 0;
|
||||
};
|
||||
|
||||
Block.prototype.getHash = function getHash() {
|
||||
if (!this.hash || !this.hash.length) this.hash = this.calcHash();
|
||||
|
||||
return this.hash;
|
||||
};
|
||||
|
||||
Block.prototype.checkProofOfWork = function checkProofOfWork() {
|
||||
var target = Util.decodeDiffBits(this.bits);
|
||||
|
||||
// TODO: Create a compare method in node-buffertools that uses the correct
|
||||
// endian so we don't have to reverse both buffers before comparing.
|
||||
this.hash.reverse();
|
||||
|
||||
if (this.hash.compare(target) > 0) {
|
||||
throw new VerificationError('Difficulty target not met');
|
||||
}
|
||||
|
||||
// Return the hash to its normal order
|
||||
this.hash.reverse();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the amount of work that went into this block.
|
||||
*
|
||||
* Work is defined as the average number of tries required to meet this
|
||||
* block's difficulty target. For example a target that is greater than 5%
|
||||
* of all possible hashes would mean that 20 "work" is required to meet it.
|
||||
*/
|
||||
Block.prototype.getWork = function getWork() {
|
||||
var target = Util.decodeDiffBits(this.bits, true);
|
||||
return BlockRules.largestHash.div(target.add(1));
|
||||
};
|
||||
|
||||
Block.prototype.checkTimestamp = function checkTimestamp() {
|
||||
var currentTime = new Date().getTime() / 1000;
|
||||
if (this.timestamp > currentTime + BlockRules.maxTimeOffset) {
|
||||
throw new VerificationError('Timestamp too far into the future');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Block.prototype.checkTransactions = function checkTransactions(txs) {
|
||||
if (!Array.isArray(txs) || txs.length <= 0) {
|
||||
throw new VerificationError('No transactions');
|
||||
}
|
||||
if (!txs[0].isCoinBase()) {
|
||||
throw new VerificationError('First tx must be coinbase');
|
||||
}
|
||||
for (var i = 1; i < txs.length; i++) {
|
||||
if (txs[i].isCoinBase()) {
|
||||
throw new VerificationError('Tx index '+i+' must not be coinbase');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build merkle tree.
|
||||
*
|
||||
* Ported from Java. Original code: BitcoinJ by Mike Hearn
|
||||
* Copyright (c) 2011 Google Inc.
|
||||
*/
|
||||
Block.prototype.getMerkleTree = function getMerkleTree(txs) {
|
||||
// The merkle hash is based on a tree of hashes calculated from the transactions:
|
||||
//
|
||||
// merkleHash
|
||||
// /\
|
||||
// / \
|
||||
// A B
|
||||
// / \ / \
|
||||
// tx1 tx2 tx3 tx4
|
||||
//
|
||||
// Basically transactions are hashed, then the hashes of the transactions are hashed
|
||||
// again and so on upwards into the tree. The point of this scheme is to allow for
|
||||
// disk space savings later on.
|
||||
//
|
||||
// This function is a direct translation of CBlock::BuildMerkleTree().
|
||||
|
||||
if (txs.length == 0) {
|
||||
return [Util.NULL_HASH.slice(0)];
|
||||
}
|
||||
|
||||
// Start by adding all the hashes of the transactions as leaves of the tree.
|
||||
var tree = txs.map(function (tx) {
|
||||
return tx instanceof Transaction ? tx.getHash() : tx;
|
||||
});
|
||||
|
||||
var j = 0;
|
||||
// Now step through each level ...
|
||||
for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) {
|
||||
// and for each leaf on that level ..
|
||||
for (var i = 0; i < size; i += 2) {
|
||||
var i2 = Math.min(i + 1, size - 1);
|
||||
var a = tree[j + i];
|
||||
var b = tree[j + i2];
|
||||
tree.push(Util.twoSha256(a.concat(b)));
|
||||
}
|
||||
j += size;
|
||||
}
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) {
|
||||
var tree = this.getMerkleTree(txs);
|
||||
return tree[tree.length - 1];
|
||||
};
|
||||
|
||||
Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) {
|
||||
if (!this.merkle_root || !this.merkle_root.length) {
|
||||
throw new VerificationError('No merkle root');
|
||||
}
|
||||
|
||||
if (this.calcMerkleRoot().compare(this.merkle_root) == 0) {
|
||||
throw new VerificationError('Merkle root incorrect');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Block.prototype.checkBlock = function checkBlock(txs) {
|
||||
if (!this.checkHash()) {
|
||||
throw new VerificationError("Block hash invalid");
|
||||
}
|
||||
this.checkProofOfWork();
|
||||
this.checkTimestamp();
|
||||
|
||||
if (txs) {
|
||||
this.checkTransactions(txs);
|
||||
if (!this.checkMerkleRoot(txs)) {
|
||||
throw new VerificationError("Merkle hash invalid");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Block.getBlockValue = function getBlockValue(height) {
|
||||
var subsidy = Bignum(50).mul(Util.COIN);
|
||||
subsidy = subsidy.div(Bignum(2).pow(Math.floor(height / 210000)));
|
||||
return subsidy;
|
||||
};
|
||||
|
||||
Block.prototype.getBlockValue = function getBlockValue() {
|
||||
return Block.getBlockValue(this.height);
|
||||
};
|
||||
|
||||
Block.prototype.toString = function toString() {
|
||||
return "<Block " + Util.formatHashAlt(this.hash) + " height="+this.height+">";
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes some properties based on information from the parent block.
|
||||
*/
|
||||
Block.prototype.attachTo = function attachTo(parent) {
|
||||
this.height = parent.height + 1;
|
||||
this.setChainWork(parent.getChainWork().add(this.getWork()));
|
||||
};
|
||||
|
||||
Block.prototype.setChainWork = function setChainWork(chainWork) {
|
||||
if (Buffer.isBuffer(chainWork)) {
|
||||
// Nothing to do
|
||||
} else if ("function" === typeof chainWork.toBuffer) { // duck-typing bignum
|
||||
chainWork = chainWork.toBuffer();
|
||||
} else {
|
||||
throw new Error("Block.setChainWork(): Invalid datatype");
|
||||
}
|
||||
|
||||
this.chainWork = chainWork;
|
||||
};
|
||||
|
||||
Block.prototype.getChainWork = function getChainWork() {
|
||||
return Bignum.fromBuffer(this.chainWork);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares the chainWork of two blocks.
|
||||
*/
|
||||
Block.prototype.moreWorkThan = function moreWorkThan(otherBlock) {
|
||||
return this.getChainWork().cmp(otherBlock.getChainWork()) > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the difficulty target for the next block after this one.
|
||||
*/
|
||||
Block.prototype.getNextWork =
|
||||
function getNextWork(blockChain, nextBlock, callback) {
|
||||
var self = this;
|
||||
|
||||
var powLimit = blockChain.getMinDiff();
|
||||
var powLimitTarget = Util.decodeDiffBits(powLimit, true);
|
||||
|
||||
var targetTimespan = blockChain.getTargetTimespan();
|
||||
var targetSpacing = blockChain.getTargetSpacing();
|
||||
var interval = targetTimespan / targetSpacing;
|
||||
|
||||
if (this.height == 0) {
|
||||
callback(null, this.bits);
|
||||
}
|
||||
|
||||
if ((this.height+1) % interval !== 0) {
|
||||
if (blockChain.isTestnet()) {
|
||||
// Special testnet difficulty rules
|
||||
var lastBlock = blockChain.getTopBlock();
|
||||
|
||||
// If the new block's timestamp is more than 2 * 10 minutes
|
||||
// then allow mining of a min-difficulty block.
|
||||
if (nextBlock.timestamp > this.timestamp + targetSpacing*2) {
|
||||
callback(null, powLimit);
|
||||
} else {
|
||||
// Return last non-"special-min-difficulty" block
|
||||
if (this.bits != powLimit) {
|
||||
// Current block is non-min-diff
|
||||
callback(null, this.bits);
|
||||
} else {
|
||||
// Recurse backwards until a non min-diff block is found.
|
||||
function lookForLastNonMinDiff(block, callback) {
|
||||
try {
|
||||
if (block.height > 0 &&
|
||||
block.height % interval !== 0 &&
|
||||
block.bits == powLimit) {
|
||||
blockChain.getBlockByHeight(
|
||||
block.height - 1,
|
||||
function (err, lastBlock) {
|
||||
try {
|
||||
if (err) throw err;
|
||||
lookForLastNonMinDiff(lastBlock, callback);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
callback(null, block.bits);
|
||||
}
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
lookForLastNonMinDiff(this, callback);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not adjustment interval, next block has same difficulty
|
||||
callback(null, this.bits);
|
||||
}
|
||||
} else {
|
||||
// Get the first block from the old difficulty period
|
||||
blockChain.getBlockByHeight(
|
||||
this.height - interval + 1,
|
||||
function (err, lastBlock) {
|
||||
try {
|
||||
if (err) throw err;
|
||||
|
||||
// Determine how long the difficulty period really took
|
||||
var actualTimespan = self.timestamp - lastBlock.timestamp;
|
||||
|
||||
// There are some limits to how much we will adjust the difficulty in
|
||||
// one step
|
||||
if (actualTimespan < targetTimespan/4) {
|
||||
actualTimespan = targetTimespan/4;
|
||||
}
|
||||
if (actualTimespan > targetTimespan*4) {
|
||||
actualTimespan = targetTimespan*4;
|
||||
}
|
||||
|
||||
var oldTarget = Util.decodeDiffBits(self.bits, true);
|
||||
var newTarget = oldTarget.mul(actualTimespan).div(targetTimespan);
|
||||
|
||||
if (newTarget.cmp(powLimitTarget) > 0) {
|
||||
newTarget = powLimitTarget;
|
||||
}
|
||||
|
||||
Debug1('Difficulty retarget (target='+targetTimespan +
|
||||
', actual='+actualTimespan+')');
|
||||
Debug1('Before: '+oldTarget.toBuffer().toString('hex'));
|
||||
Debug1('After: '+newTarget.toBuffer().toString('hex'));
|
||||
|
||||
callback(null, Util.encodeDiffBits(newTarget));
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var medianTimeSpan = 11;
|
||||
|
||||
Block.prototype.getMedianTimePast =
|
||||
function getMedianTimePast(blockChain, callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
Step(
|
||||
function getBlocks() {
|
||||
var heights = [];
|
||||
for (var i = 0, m = medianTimeSpan; i < m && (self.height - i) >= 0; i++) {
|
||||
heights.push(self.height - i);
|
||||
}
|
||||
blockChain.getBlocksByHeights(heights, this);
|
||||
},
|
||||
function calcMedian(err, blocks) {
|
||||
if (err) throw err;
|
||||
|
||||
var timestamps = blocks.map(function (block) {
|
||||
if (!block) {
|
||||
throw new Error("Prior block missing, cannot calculate median time");
|
||||
}
|
||||
|
||||
return +block.timestamp;
|
||||
});
|
||||
|
||||
// Sort timestamps
|
||||
timestamps = timestamps.sort();
|
||||
|
||||
// Return median timestamp
|
||||
this(null, timestamps[Math.floor(timestamps.length/2)]);
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Block.prototype.verifyChild =
|
||||
function verifyChild(blockChain, child, callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
Step(
|
||||
function getExpectedDifficulty() {
|
||||
self.getNextWork(blockChain, child, this);
|
||||
},
|
||||
function verifyExpectedDifficulty(err, nextWork) {
|
||||
if (err) throw err;
|
||||
|
||||
if (+child.bits !== +nextWork) {
|
||||
throw new VerificationError("Incorrect proof of work '"+child.bits+"',"+
|
||||
" should be '"+nextWork+"'.");
|
||||
}
|
||||
|
||||
this();
|
||||
},
|
||||
function getMinimumTimestamp(err) {
|
||||
if (err) throw err;
|
||||
|
||||
self.getMedianTimePast(blockChain, this);
|
||||
},
|
||||
function verifyTimestamp(err, medianTimePast) {
|
||||
if (err) throw err;
|
||||
|
||||
if (child.timestamp <= medianTimePast) {
|
||||
throw new VerificationError("Block's timestamp is too early");
|
||||
}
|
||||
|
||||
this();
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Block.prototype.createCoinbaseTx =
|
||||
function createCoinbaseTx(beneficiary)
|
||||
{
|
||||
var tx = new Transaction();
|
||||
tx.ins.push(new TransactionIn({
|
||||
s: Util.EMPTY_BUFFER,
|
||||
q: 0xffffffff,
|
||||
o: COINBASE_OP
|
||||
}));
|
||||
tx.outs.push(new TransactionOut({
|
||||
v: Util.bigIntToValue(this.getBlockValue()),
|
||||
s: Script.createPubKeyOut(beneficiary).getBuffer()
|
||||
}));
|
||||
return tx;
|
||||
};
|
||||
|
||||
Block.prototype.prepareNextBlock =
|
||||
function prepareNextBlock(blockChain, beneficiary, time, callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
var newBlock = new Block();
|
||||
Step(
|
||||
function getMedianTimePastStep() {
|
||||
self.getMedianTimePast(blockChain, this);
|
||||
},
|
||||
|
||||
function getNextWorkStep(err, medianTimePast) {
|
||||
if (err) throw err;
|
||||
|
||||
if (!time) {
|
||||
// TODO: Use getAdjustedTime for the second timestamp
|
||||
time = Math.max(medianTimePast+1,
|
||||
Math.floor(new Date().getTime() / 1000));
|
||||
}
|
||||
|
||||
self.getNextWork(blockChain, newBlock, this);
|
||||
},
|
||||
|
||||
function applyNextWorkStep(err, nextWork) {
|
||||
if (err) throw err;
|
||||
newBlock.bits = nextWork;
|
||||
this(null);
|
||||
},
|
||||
|
||||
function miscStep(err) {
|
||||
if (err) throw err;
|
||||
|
||||
newBlock.version = 1;
|
||||
newBlock.timestamp = time;
|
||||
newBlock.prev_hash = self.getHash().slice(0);
|
||||
newBlock.height = self.height+1;
|
||||
|
||||
// Create coinbase transaction
|
||||
var txs = [];
|
||||
|
||||
var tx = newBlock.createCoinbaseTx(beneficiary);
|
||||
txs.push(tx);
|
||||
|
||||
newBlock.merkle_root = newBlock.calcMerkleRoot(txs);
|
||||
|
||||
// Return reference to (unfinished) block
|
||||
this(null, {block: newBlock, txs: txs});
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Block.prototype.mineNextBlock =
|
||||
function mineNextBlock(blockChain, beneficiary, time, miner, callback)
|
||||
{
|
||||
this.prepareNextBlock(blockChain, beneficiary, time, function (err, data) {
|
||||
try {
|
||||
if (err) throw err;
|
||||
|
||||
var newBlock = data.block;
|
||||
var txs = data.txs;
|
||||
|
||||
newBlock.solve(miner, function (err, nonce) {
|
||||
newBlock.nonce = nonce;
|
||||
|
||||
// Make sure hash is cached
|
||||
newBlock.getHash();
|
||||
|
||||
callback(err, newBlock, txs);
|
||||
});
|
||||
|
||||
// Return reference to (unfinished) block
|
||||
return newBlock;
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Block.prototype.solve = function solve(miner, callback) {
|
||||
var header = this.getHeader();
|
||||
var target = Util.decodeDiffBits(this.bits);
|
||||
miner.solve(header, target, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object with the same field names as jgarzik's getblock patch.
|
||||
*/
|
||||
Block.prototype.getStandardizedObject =
|
||||
function getStandardizedObject(txs)
|
||||
{
|
||||
var block = {
|
||||
hash: Util.formatHashFull(this.getHash()),
|
||||
version: this.version,
|
||||
prev_block: Util.formatHashFull(this.prev_hash),
|
||||
mrkl_root: Util.formatHashFull(this.merkle_root),
|
||||
time: this.timestamp,
|
||||
bits: this.bits,
|
||||
nonce: this.nonce,
|
||||
height: this.height
|
||||
};
|
||||
|
||||
|
||||
if (txs) {
|
||||
var mrkl_tree = this.getMerkleTree(txs).map(function (buffer) {
|
||||
return Util.formatHashFull(buffer);
|
||||
});
|
||||
block.mrkl_root = mrkl_tree[mrkl_tree.length - 1];
|
||||
|
||||
block.n_tx = txs.length;
|
||||
var totalSize = 80; // Block header
|
||||
totalSize += Util.getVarIntSize(txs.length); // txn_count
|
||||
txs = txs.map(function (tx) {
|
||||
tx = tx.getStandardizedObject();
|
||||
totalSize += tx.size;
|
||||
return tx;
|
||||
});
|
||||
block.size = totalSize;
|
||||
block.tx = txs;
|
||||
|
||||
block.mrkl_tree = mrkl_tree;
|
||||
} else {
|
||||
block.size = this.size;
|
||||
}
|
||||
return block;
|
||||
};
|
||||
|
||||
return Block;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,589 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var config = b.config || require('./config');
|
||||
var network = b.network || require('./networks')[config.network];
|
||||
|
||||
var MAX_RECEIVE_BUFFER = 10000000;
|
||||
var PROTOCOL_VERSION = 70000;
|
||||
|
||||
var Binary = b.Binary || require('binary');
|
||||
var Put = b.Put || require('bufferput');
|
||||
var Buffers = b.Buffers || require('buffers');
|
||||
var noop = function() {};
|
||||
var log = b.log || require('bitpay/log');
|
||||
var Parser = b.Parser || require('./util/BinaryParser').class();
|
||||
var util = require('./util/util');
|
||||
var doubleSha256 = b.doubleSha256 || util.twoSha256;
|
||||
var nonce = util.generateNonce();
|
||||
|
||||
var Block = require('../model/block').class();
|
||||
|
||||
var BIP0031_VERSION = 60000;
|
||||
|
||||
function Connection(socket, peer) {
|
||||
Connection.super(this, arguments);
|
||||
this.socket = socket;
|
||||
this.peer = peer;
|
||||
|
||||
// A connection is considered "active" once we have received verack
|
||||
this.active = false;
|
||||
// The version incoming packages are interpreted as
|
||||
this.recvVer = 0;
|
||||
// The version outgoing packages are sent as
|
||||
this.sendVer = 0;
|
||||
// The (claimed) height of the remote peer's block chain
|
||||
this.bestHeight = 0;
|
||||
// Is this an inbound connection?
|
||||
this.inbound = !!socket.server;
|
||||
// Have we sent a getaddr on this connection?
|
||||
this.getaddr = false;
|
||||
|
||||
// Receive buffer
|
||||
this.buffers = new Buffers();
|
||||
|
||||
// Starting 20 Feb 2012, Version 0.2 is obsolete
|
||||
// This is the same behavior as the official client
|
||||
if (new Date().getTime() > 1329696000000) {
|
||||
this.recvVer = 209;
|
||||
this.sendVer = 209;
|
||||
}
|
||||
|
||||
this.setupHandlers();
|
||||
};
|
||||
Connection.superclass = b.superclass || require('events').EventEmitter;
|
||||
|
||||
Connection.prototype.setupHandlers = function () {
|
||||
this.socket.addListener('connect', this.handleConnect.bind(this));
|
||||
this.socket.addListener('error', this.handleError.bind(this));
|
||||
this.socket.addListener('end', this.handleDisconnect.bind(this));
|
||||
this.socket.addListener('data', (function (data) {
|
||||
var dumpLen = 35;
|
||||
log.debug('['+this.peer+'] '+
|
||||
'Recieved '+data.length+' bytes of data:');
|
||||
log.debug('... '+ data.slice(0, dumpLen > data.length ?
|
||||
data.length : dumpLen).toHex() +
|
||||
(data.length > dumpLen ? '...' : ''));
|
||||
}).bind(this));
|
||||
this.socket.addListener('data', this.handleData.bind(this));
|
||||
};
|
||||
|
||||
Connection.prototype.handleConnect = function () {
|
||||
if (!this.inbound) {
|
||||
this.sendVersion();
|
||||
}
|
||||
this.emit('connect', {
|
||||
conn: this,
|
||||
socket: this.socket,
|
||||
peer: this.peer
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype.handleError = function(err) {
|
||||
if (err.errno == 110 || err.errno == 'ETIMEDOUT') {
|
||||
log.info('connection timed out for '+this.peer);
|
||||
} else if (err.errno == 111 || err.errno == 'ECONNREFUSED') {
|
||||
log.info('connection refused for '+this.peer);
|
||||
} else {
|
||||
log.warn('connection with '+this.peer+' '+err.toString());
|
||||
}
|
||||
this.emit('error', {
|
||||
conn: this,
|
||||
socket: this.socket,
|
||||
peer: this.peer,
|
||||
err: err
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype.handleDisconnect = function () {
|
||||
this.emit('disconnect', {
|
||||
conn: this,
|
||||
socket: this.socket,
|
||||
peer: this.peer
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype.handleMessage = function(message) {
|
||||
if (!message) {
|
||||
// Parser was unable to make sense of the message, drop it
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (message.command) {
|
||||
case 'version':
|
||||
// Did we connect to ourself?
|
||||
if (nonce.compare(message.nonce) === 0) {
|
||||
this.socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.inbound) {
|
||||
this.sendVersion();
|
||||
}
|
||||
|
||||
if (message.version >= 209) {
|
||||
this.sendMessage('verack', new Buffer([]));
|
||||
}
|
||||
this.sendVer = Math.min(message.version, PROTOCOL_VERSION);
|
||||
if (message.version < 209) {
|
||||
this.recvVer = Math.min(message.version, PROTOCOL_VERSION);
|
||||
} else {
|
||||
// We won't start expecting a checksum until after we've received
|
||||
// the "verack" message.
|
||||
this.once('verack', (function () {
|
||||
this.recvVer = message.version;
|
||||
}).bind(this));
|
||||
}
|
||||
this.bestHeight = message.start_height;
|
||||
break;
|
||||
|
||||
case 'verack':
|
||||
this.recvVer = Math.min(message.version, PROTOCOL_VERSION);
|
||||
this.active = true;
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
if ("object" === typeof message.nonce) {
|
||||
this.sendPong(message.nonce);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.emit(message.command, {
|
||||
conn: this,
|
||||
socket: this.socket,
|
||||
peer: this.peer,
|
||||
message: message
|
||||
});
|
||||
} catch (e) {
|
||||
log.err('Error while handling message '+message.command+' from ' +
|
||||
this.peer + ':\n' +
|
||||
(e.stack ? e.stack : e.toString()));
|
||||
}
|
||||
};
|
||||
|
||||
Connection.prototype.sendPong = function (nonce) {
|
||||
this.sendMessage('pong', nonce);
|
||||
};
|
||||
|
||||
Connection.prototype.sendVersion = function () {
|
||||
var subversion = '/BitcoinX:0.1/';
|
||||
|
||||
var put = Put();
|
||||
put.word32le(PROTOCOL_VERSION); // version
|
||||
put.word64le(1); // services
|
||||
put.word64le(Math.round(new Date().getTime()/1000)); // timestamp
|
||||
put.pad(26); // addr_me
|
||||
put.pad(26); // addr_you
|
||||
put.put(nonce);
|
||||
put.varint(subversion.length);
|
||||
put.put(new Buffer(subversion, 'ascii'));
|
||||
put.word32le(0);
|
||||
|
||||
this.sendMessage('version', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendGetBlocks = function (starts, stop) {
|
||||
var put = Put();
|
||||
put.word32le(this.sendVer);
|
||||
|
||||
put.varint(starts.length);
|
||||
for (var i = 0; i < starts.length; i++) {
|
||||
if (starts[i].length != 32) {
|
||||
throw new Error('Invalid hash length');
|
||||
}
|
||||
|
||||
put.put(starts[i]);
|
||||
}
|
||||
|
||||
var stopBuffer = new Buffer(stop, 'binary');
|
||||
if (stopBuffer.length != 32) {
|
||||
throw new Error('Invalid hash length');
|
||||
}
|
||||
|
||||
put.put(stopBuffer);
|
||||
|
||||
this.sendMessage('getblocks', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendGetData = function (invs) {
|
||||
var put = Put();
|
||||
put.varint(invs.length);
|
||||
for (var i = 0; i < invs.length; i++) {
|
||||
put.word32le(invs[i].type);
|
||||
put.put(invs[i].hash);
|
||||
}
|
||||
this.sendMessage('getdata', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendGetAddr = function (invs) {
|
||||
var put = Put();
|
||||
this.sendMessage('getaddr', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendInv = function(data) {
|
||||
if(!Array.isArray(data)) data = [data];
|
||||
var put = Put();
|
||||
put.varint(data.length);
|
||||
data.forEach(function (value) {
|
||||
if (value instanceof Block) {
|
||||
// Block
|
||||
put.word32le(2); // MSG_BLOCK
|
||||
} else {
|
||||
// Transaction
|
||||
put.word32le(1); // MSG_TX
|
||||
}
|
||||
put.put(value.getHash());
|
||||
});
|
||||
this.sendMessage('inv', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendHeaders = function (headers) {
|
||||
var put = Put();
|
||||
put.varint(headers.length);
|
||||
headers.forEach(function (header) {
|
||||
put.put(header);
|
||||
|
||||
// Indicate 0 transactions
|
||||
put.word8(0);
|
||||
});
|
||||
this.sendMessage('headers', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendTx = function (tx) {
|
||||
this.sendMessage('tx', tx.serialize());
|
||||
};
|
||||
|
||||
Connection.prototype.sendBlock = function (block, txs) {
|
||||
var put = Put();
|
||||
|
||||
// Block header
|
||||
put.put(block.getHeader());
|
||||
|
||||
// List of transactions
|
||||
put.varint(txs.length);
|
||||
txs.forEach(function (tx) {
|
||||
put.put(tx.serialize());
|
||||
});
|
||||
|
||||
this.sendMessage('block', put.buffer());
|
||||
};
|
||||
|
||||
Connection.prototype.sendMessage = function (command, payload) {
|
||||
try {
|
||||
var magic = network.magic;
|
||||
var commandBuf = new Buffer(command, 'ascii');
|
||||
if (commandBuf.length > 12) throw 'Command name too long';
|
||||
|
||||
var checksum;
|
||||
if (this.sendVer >= 209) {
|
||||
checksum = doubleSha256(payload).slice(0, 4);
|
||||
} else {
|
||||
checksum = new Buffer([]);
|
||||
}
|
||||
|
||||
var message = Put(); // -- HEADER --
|
||||
message.put(magic); // magic bytes
|
||||
message.put(commandBuf); // command name
|
||||
message.pad(12 - commandBuf.length); // zero-padded
|
||||
message.word32le(payload.length); // payload length
|
||||
message.put(checksum); // checksum
|
||||
// -- BODY --
|
||||
message.put(payload); // payload data
|
||||
|
||||
var buffer = message.buffer();
|
||||
|
||||
log.debug('['+this.peer+'] '+
|
||||
"Sending message "+command+" ("+payload.length+" bytes)");
|
||||
|
||||
this.socket.write(buffer);
|
||||
} catch (err) {
|
||||
// TODO: We should catch this error one level higher in order to better
|
||||
// determine how to react to it. For now though, ignoring it will do.
|
||||
log.err("Error while sending message to peer "+this.peer+": "+
|
||||
(err.stack ? err.stack : err.toString()));
|
||||
}
|
||||
};
|
||||
|
||||
Connection.prototype.handleData = function (data) {
|
||||
this.buffers.push(data);
|
||||
|
||||
if (this.buffers.length > MAX_RECEIVE_BUFFER) {
|
||||
log.err("Peer "+this.peer+" exceeded maxreceivebuffer, disconnecting."+
|
||||
(err.stack ? err.stack : err.toString()));
|
||||
this.socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.processData();
|
||||
};
|
||||
|
||||
Connection.prototype.processData = function () {
|
||||
// If there are less than 20 bytes there can't be a message yet.
|
||||
if (this.buffers.length < 20) return;
|
||||
|
||||
var magic = network.magic;
|
||||
var i = 0;
|
||||
for (;;) {
|
||||
if (this.buffers.get(i ) === magic[0] &&
|
||||
this.buffers.get(i+1) === magic[1] &&
|
||||
this.buffers.get(i+2) === magic[2] &&
|
||||
this.buffers.get(i+3) === magic[3]) {
|
||||
if (i !== 0) {
|
||||
log.debug('['+this.peer+'] '+
|
||||
'Received '+i+
|
||||
' bytes of inter-message garbage: ');
|
||||
log.debug('... '+this.buffers.slice(0,i));
|
||||
|
||||
this.buffers.skip(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i > (this.buffers.length - 4)) {
|
||||
this.buffers.skip(i);
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
var payloadLen = (this.buffers.get(16) ) +
|
||||
(this.buffers.get(17) << 8) +
|
||||
(this.buffers.get(18) << 16) +
|
||||
(this.buffers.get(19) << 24);
|
||||
|
||||
var startPos = (this.recvVer >= 209) ? 24 : 20;
|
||||
var endPos = startPos + payloadLen;
|
||||
|
||||
if (this.buffers.length < endPos) return;
|
||||
|
||||
var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/,"");
|
||||
var payload = this.buffers.slice(startPos, endPos);
|
||||
var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null;
|
||||
|
||||
log.debug('['+this.peer+'] ' +
|
||||
"Received message " + command +
|
||||
" (" + payloadLen + " bytes)");
|
||||
|
||||
if (checksum !== null) {
|
||||
var checksumConfirm = doubleSha256(payload).slice(0, 4);
|
||||
if (checksumConfirm.compare(checksum) !== 0) {
|
||||
log.err('['+this.peer+'] '+
|
||||
'Checksum failed',
|
||||
{ cmd: command,
|
||||
expected: checksumConfirm.toString('hex'),
|
||||
actual: checksum.toString('hex') });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var message;
|
||||
try {
|
||||
message = this.parseMessage(command, payload);
|
||||
} catch (e) {
|
||||
log.err('Error while parsing message '+command+' from ' +
|
||||
this.peer + ':\n' +
|
||||
(e.stack ? e.stack : e.toString()));
|
||||
}
|
||||
|
||||
if (message) {
|
||||
this.handleMessage(message);
|
||||
}
|
||||
|
||||
this.buffers.skip(endPos);
|
||||
this.processData();
|
||||
};
|
||||
|
||||
Connection.prototype.parseMessage = function (command, payload) {
|
||||
var parser = new Parser(payload);
|
||||
|
||||
var data = {
|
||||
command: command
|
||||
};
|
||||
|
||||
var i;
|
||||
|
||||
switch (command) {
|
||||
case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version
|
||||
data.version = parser.word32le();
|
||||
data.services = parser.word64le();
|
||||
data.timestamp = parser.word64le();
|
||||
data.addr_me = parser.buffer(26);
|
||||
data.addr_you = parser.buffer(26);
|
||||
data.nonce = parser.buffer(8);
|
||||
data.subversion = Connection.parseVarStr(parser);
|
||||
data.start_height = parser.word32le();
|
||||
break;
|
||||
|
||||
case 'inv':
|
||||
case 'getdata':
|
||||
data.count = Connection.parseVarInt(parser);
|
||||
|
||||
data.invs = [];
|
||||
for (i = 0; i < data.count; i++) {
|
||||
data.invs.push({
|
||||
type: parser.word32le(),
|
||||
hash: parser.buffer(32)
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'block':
|
||||
data.version = parser.word32le();
|
||||
data.prev_hash = parser.buffer(32);
|
||||
data.merkle_root = parser.buffer(32);
|
||||
data.timestamp = parser.word32le();
|
||||
data.bits = parser.word32le();
|
||||
data.nonce = parser.word32le();
|
||||
|
||||
var txCount = Connection.parseVarInt(parser);
|
||||
|
||||
data.txs = [];
|
||||
for (i = 0; i < txCount; i++) {
|
||||
data.txs.push(Connection.parseTx(parser));
|
||||
}
|
||||
|
||||
data.size = payload.length;
|
||||
break;
|
||||
|
||||
case 'tx':
|
||||
var txData = Connection.parseTx(parser);
|
||||
return {
|
||||
command: command,
|
||||
version: txData.version,
|
||||
lock_time: txData.lock_time,
|
||||
ins: txData.ins,
|
||||
outs: txData.outs
|
||||
};
|
||||
|
||||
case 'getblocks':
|
||||
case 'getheaders':
|
||||
// parse out the version
|
||||
data.version = parser.word32le();
|
||||
|
||||
// TODO: Limit block locator size?
|
||||
// reference implementation limits to 500 results
|
||||
var startCount = Connection.parseVarInt(parser);
|
||||
|
||||
data.starts = [];
|
||||
for (i = 0; i < startCount; i++) {
|
||||
data.starts.push(parser.buffer(32));
|
||||
}
|
||||
data.stop = parser.buffer(32);
|
||||
break;
|
||||
|
||||
case 'addr':
|
||||
var addrCount = Connection.parseVarInt(parser);
|
||||
|
||||
// Enforce a maximum number of addresses per message
|
||||
if (addrCount > 1000) {
|
||||
addrCount = 1000;
|
||||
}
|
||||
|
||||
data.addrs = [];
|
||||
for (i = 0; i < addrCount; i++) {
|
||||
// TODO: Time actually depends on the version of the other peer (>=31402)
|
||||
data.addrs.push({
|
||||
time: parser.word32le(),
|
||||
services: parser.word64le(),
|
||||
ip: parser.buffer(16),
|
||||
port: parser.word16be()
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'alert':
|
||||
data.payload = Connection.parseVarStr(parser);
|
||||
data.signature = Connection.parseVarStr(parser);
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
if (this.recvVer > BIP0031_VERSION) {
|
||||
data.nonce = parser.buffer(8);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getaddr':
|
||||
case 'verack':
|
||||
// Empty message, nothing to parse
|
||||
break;
|
||||
|
||||
default:
|
||||
log.err('Connection.parseMessage(): Command not implemented',
|
||||
{cmd: command});
|
||||
|
||||
// This tells the calling function not to issue an event
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
Connection.parseVarInt = function (parser)
|
||||
{
|
||||
var firstByte = parser.word8();
|
||||
switch (firstByte) {
|
||||
case 0xFD:
|
||||
return parser.word16le();
|
||||
|
||||
case 0xFE:
|
||||
return parser.word32le();
|
||||
|
||||
case 0xFF:
|
||||
return parser.word64le();
|
||||
|
||||
default:
|
||||
return firstByte;
|
||||
}
|
||||
};
|
||||
|
||||
Connection.parseVarStr = function (parser) {
|
||||
var len = Connection.parseVarInt(parser);
|
||||
return parser.buffer(len);
|
||||
};
|
||||
|
||||
Connection.parseTx = function (parser) {
|
||||
if (Buffer.isBuffer(parser)) {
|
||||
parser = new Parser(parser);
|
||||
}
|
||||
|
||||
var data = {}, i, sLen, startPos = parser.pos;
|
||||
|
||||
data.version = parser.word32le();
|
||||
|
||||
var txinCount = Connection.parseVarInt(parser, 'tx_in_count');
|
||||
|
||||
data.ins = [];
|
||||
for (j = 0; j < txinCount; j++) {
|
||||
var txin = {};
|
||||
txin.o = parser.buffer(36); // outpoint
|
||||
sLen = Connection.parseVarInt(parser); // script_len
|
||||
txin.s = parser.buffer(sLen); // script
|
||||
txin.q = parser.word32le(); // sequence
|
||||
data.ins.push(txin);
|
||||
}
|
||||
|
||||
var txoutCount = Connection.parseVarInt(parser);
|
||||
|
||||
data.outs = [];
|
||||
for (j = 0; j < txoutCount; j++) {
|
||||
var txout = {};
|
||||
txout.v = parser.buffer(8); // value
|
||||
sLen = Connection.parseVarInt(parser); // script_len
|
||||
txout.s = parser.buffer(sLen); // script
|
||||
data.outs.push(txout);
|
||||
}
|
||||
|
||||
data.lock_time = parser.word32le();
|
||||
|
||||
var endPos = parser.pos;
|
||||
|
||||
data.buffer = parser.subject.slice(startPos, endPos);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return Connection;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,25 @@
|
|||
Copyright 2013 BitPay Inc.
|
||||
|
||||
Parts of this software are based on BitcoinJS
|
||||
Copyright (c) 2011 Stefan Thomas <justmoon@members.fsf.org>
|
||||
|
||||
Parts of this software are based on BitcoinJ
|
||||
Copyright (c) 2011 Google Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,161 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
function Opcode(num) {
|
||||
this.code = num;
|
||||
};
|
||||
|
||||
Opcode.prototype.toString = function () {
|
||||
return Opcode.reverseMap[this.code];
|
||||
};
|
||||
|
||||
Opcode.map = {
|
||||
// push value
|
||||
OP_FALSE : 0,
|
||||
OP_0 : 0,
|
||||
OP_PUSHDATA1 : 76,
|
||||
OP_PUSHDATA2 : 77,
|
||||
OP_PUSHDATA4 : 78,
|
||||
OP_1NEGATE : 79,
|
||||
OP_RESERVED : 80,
|
||||
OP_TRUE : 81,
|
||||
OP_1 : 81,
|
||||
OP_2 : 82,
|
||||
OP_3 : 83,
|
||||
OP_4 : 84,
|
||||
OP_5 : 85,
|
||||
OP_6 : 86,
|
||||
OP_7 : 87,
|
||||
OP_8 : 88,
|
||||
OP_9 : 89,
|
||||
OP_10 : 90,
|
||||
OP_11 : 91,
|
||||
OP_12 : 92,
|
||||
OP_13 : 93,
|
||||
OP_14 : 94,
|
||||
OP_15 : 95,
|
||||
OP_16 : 96,
|
||||
|
||||
// control
|
||||
OP_NOP : 97,
|
||||
OP_VER : 98,
|
||||
OP_IF : 99,
|
||||
OP_NOTIF : 100,
|
||||
OP_VERIF : 101,
|
||||
OP_VERNOTIF : 102,
|
||||
OP_ELSE : 103,
|
||||
OP_ENDIF : 104,
|
||||
OP_VERIFY : 105,
|
||||
OP_RETURN : 106,
|
||||
|
||||
// stack ops
|
||||
OP_TOALTSTACK : 107,
|
||||
OP_FROMALTSTACK : 108,
|
||||
OP_2DROP : 109,
|
||||
OP_2DUP : 110,
|
||||
OP_3DUP : 111,
|
||||
OP_2OVER : 112,
|
||||
OP_2ROT : 113,
|
||||
OP_2SWAP : 114,
|
||||
OP_IFDUP : 115,
|
||||
OP_DEPTH : 116,
|
||||
OP_DROP : 117,
|
||||
OP_DUP : 118,
|
||||
OP_NIP : 119,
|
||||
OP_OVER : 120,
|
||||
OP_PICK : 121,
|
||||
OP_ROLL : 122,
|
||||
OP_ROT : 123,
|
||||
OP_SWAP : 124,
|
||||
OP_TUCK : 125,
|
||||
|
||||
// splice ops
|
||||
OP_CAT : 126,
|
||||
OP_SUBSTR : 127,
|
||||
OP_LEFT : 128,
|
||||
OP_RIGHT : 129,
|
||||
OP_SIZE : 130,
|
||||
|
||||
// bit logic
|
||||
OP_INVERT : 131,
|
||||
OP_AND : 132,
|
||||
OP_OR : 133,
|
||||
OP_XOR : 134,
|
||||
OP_EQUAL : 135,
|
||||
OP_EQUALVERIFY : 136,
|
||||
OP_RESERVED1 : 137,
|
||||
OP_RESERVED2 : 138,
|
||||
|
||||
// numeric
|
||||
OP_1ADD : 139,
|
||||
OP_1SUB : 140,
|
||||
OP_2MUL : 141,
|
||||
OP_2DIV : 142,
|
||||
OP_NEGATE : 143,
|
||||
OP_ABS : 144,
|
||||
OP_NOT : 145,
|
||||
OP_0NOTEQUAL : 146,
|
||||
|
||||
OP_ADD : 147,
|
||||
OP_SUB : 148,
|
||||
OP_MUL : 149,
|
||||
OP_DIV : 150,
|
||||
OP_MOD : 151,
|
||||
OP_LSHIFT : 152,
|
||||
OP_RSHIFT : 153,
|
||||
|
||||
OP_BOOLAND : 154,
|
||||
OP_BOOLOR : 155,
|
||||
OP_NUMEQUAL : 156,
|
||||
OP_NUMEQUALVERIFY : 157,
|
||||
OP_NUMNOTEQUAL : 158,
|
||||
OP_LESSTHAN : 159,
|
||||
OP_GREATERTHAN : 160,
|
||||
OP_LESSTHANOREQUAL : 161,
|
||||
OP_GREATERTHANOREQUAL : 162,
|
||||
OP_MIN : 163,
|
||||
OP_MAX : 164,
|
||||
|
||||
OP_WITHIN : 165,
|
||||
|
||||
// crypto
|
||||
OP_RIPEMD160 : 166,
|
||||
OP_SHA1 : 167,
|
||||
OP_SHA256 : 168,
|
||||
OP_HASH160 : 169,
|
||||
OP_HASH256 : 170,
|
||||
OP_CODESEPARATOR : 171,
|
||||
OP_CHECKSIG : 172,
|
||||
OP_CHECKSIGVERIFY : 173,
|
||||
OP_CHECKMULTISIG : 174,
|
||||
OP_CHECKMULTISIGVERIFY : 175,
|
||||
|
||||
// expansion
|
||||
OP_NOP1 : 176,
|
||||
OP_NOP2 : 177,
|
||||
OP_NOP3 : 178,
|
||||
OP_NOP4 : 179,
|
||||
OP_NOP5 : 180,
|
||||
OP_NOP6 : 181,
|
||||
OP_NOP7 : 182,
|
||||
OP_NOP8 : 183,
|
||||
OP_NOP9 : 184,
|
||||
OP_NOP10 : 185,
|
||||
|
||||
// template matching params
|
||||
OP_PUBKEYHASH : 253,
|
||||
OP_PUBKEY : 254,
|
||||
OP_INVALIDOPCODE : 255
|
||||
};
|
||||
|
||||
Opcode.reverseMap = [];
|
||||
|
||||
for (var k in Opcode.map) {
|
||||
if(Opcode.map.hasOwnProperty(k)) {
|
||||
Opcode.reverseMap[Opcode.map[k]] = k.substr(3);
|
||||
}
|
||||
}
|
||||
|
||||
return Opcode;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,60 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var Net = b.Net || require('net');
|
||||
var Binary = b.Binary || require('binary');
|
||||
|
||||
function Peer(host, port, services) {
|
||||
if ("string" === typeof host) {
|
||||
if (host.indexOf(':') && !port) {
|
||||
var parts = host.split(':');
|
||||
host = parts[0];
|
||||
port = parts[1];
|
||||
}
|
||||
this.host = host;
|
||||
this.port = +port || 8333;
|
||||
} else if (host instanceof Peer) {
|
||||
this.host = host.host;
|
||||
this.port = host.port;
|
||||
} else if (Buffer.isBuffer(host)) {
|
||||
if (host.slice(0, 12).compare(Peer.IPV6_IPV4_PADDING) != 0) {
|
||||
throw new Error('IPV6 not supported yet! Cannot instantiate host.');
|
||||
}
|
||||
this.host = Array.prototype.slice.apply(host.slice(12)).join('.');
|
||||
this.port = +port || 8333;
|
||||
} else {
|
||||
throw new Error('Could not instantiate peer, invalid parameter type: ' +
|
||||
typeof host);
|
||||
}
|
||||
|
||||
this.services = (services) ? services : null;
|
||||
this.lastSeen = 0;
|
||||
};
|
||||
|
||||
Peer.IPV6_IPV4_PADDING = new Buffer([0,0,0,0,0,0,0,0,0,0,255,255]);
|
||||
|
||||
Peer.prototype.createConnection = function () {
|
||||
var c = Net.createConnection(this.port, this.host);
|
||||
return c;
|
||||
};
|
||||
|
||||
Peer.prototype.getHostAsBuffer = function () {
|
||||
return new Buffer(this.host.split('.'));
|
||||
};
|
||||
|
||||
Peer.prototype.toString = function () {
|
||||
return this.host + ":" + this.port;
|
||||
};
|
||||
|
||||
Peer.prototype.toBuffer = function () {
|
||||
var put = Binary.put();
|
||||
put.word32le(this.lastSeen);
|
||||
put.word64le(this.services);
|
||||
put.put(this.getHostAsBuffer());
|
||||
put.word16be(this.port);
|
||||
return put.buffer();
|
||||
};
|
||||
|
||||
return Peer;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,217 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var config = b.config || require('./config');
|
||||
var network = b.network || require('./networks')[config.network];
|
||||
var Connection = b.Connection || require('./Connection').createClass({config: config});
|
||||
var Peer = b.Peer || require('./Peer').class();
|
||||
var noop = function() {};
|
||||
var log = b.log || {info: noop, warn: noop, err: noop};
|
||||
|
||||
GetAdjustedTime = b.GetAdjustedTime || function () {
|
||||
// TODO: Implement actual adjustment
|
||||
return Math.floor(new Date().getTime() / 1000);
|
||||
};
|
||||
|
||||
function PeerManager() {
|
||||
this.active = false;
|
||||
this.timer = null;
|
||||
|
||||
this.peers = [];
|
||||
this.connections = [];
|
||||
this.isConnected = false;
|
||||
this.peerDiscovery = false;
|
||||
|
||||
// Move these to the Node's settings object
|
||||
this.interval = 5000;
|
||||
this.minConnections = 8;
|
||||
this.minKnownPeers = 10;
|
||||
};
|
||||
PeerManager.superclass = b.superclass || require('events').EventEmitter;
|
||||
|
||||
PeerManager.Connection = Connection;
|
||||
|
||||
PeerManager.prototype.start = function()
|
||||
{
|
||||
this.active = true;
|
||||
if(!this.timer) {
|
||||
this.timer = setInterval(this.checkStatus.bind(this), this.interval);
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.stop = function ()
|
||||
{
|
||||
this.active = false;
|
||||
if(this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
for(var i=0; i<this.connections.length; i++) {
|
||||
this.connections[i].socket.end();
|
||||
};
|
||||
};
|
||||
|
||||
PeerManager.prototype.addPeer = function(peer, port) {
|
||||
if(peer instanceof Peer) {
|
||||
this.peers.push(peer);
|
||||
} else if ("string" == typeof peer) {
|
||||
this.addPeer(new Peer(peer, port));
|
||||
} else {
|
||||
log.err('Node.addPeer(): Invalid value provided for peer',
|
||||
{val: peer});
|
||||
throw 'Node.addPeer(): Invalid value provided for peer.';
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.checkStatus = function checkStatus()
|
||||
{
|
||||
// Make sure we are connected to all forcePeers
|
||||
if(this.peers.length) {
|
||||
var peerIndex = {};
|
||||
this.peers.forEach(function(peer) {
|
||||
peerIndex[peer.toString()] = peer;
|
||||
});
|
||||
|
||||
// Ignore the ones we're already connected to
|
||||
this.connections.forEach(function(conn) {
|
||||
var peerName = conn.peer.toString();
|
||||
if("undefined" !== peerIndex[peerName]) {
|
||||
delete peerIndex[peerName];
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(peerIndex).forEach(function(i) {
|
||||
this.connectTo(peerIndex[i]);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.connectTo = function(peer) {
|
||||
log.info('connecting to '+peer);
|
||||
try {
|
||||
return this.addConnection(peer.createConnection(), peer);
|
||||
} catch (e) {
|
||||
log.err('creating connection',e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.addConnection = function(socketConn, peer) {
|
||||
var conn = new Connection(socketConn, peer);
|
||||
this.connections.push(conn);
|
||||
this.emit('connection', conn);
|
||||
|
||||
conn.addListener('version', this.handleVersion.bind(this));
|
||||
conn.addListener('verack', this.handleReady.bind(this));
|
||||
conn.addListener('addr', this.handleAddr.bind(this));
|
||||
conn.addListener('getaddr', this.handleGetAddr.bind(this));
|
||||
conn.addListener('error', this.handleError.bind(this));
|
||||
conn.addListener('disconnect', this.handleDisconnect.bind(this));
|
||||
|
||||
return conn;
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleVersion = function(e) {
|
||||
if (!e.conn.inbound) {
|
||||
// TODO: Advertise our address (if listening)
|
||||
}
|
||||
// Get recent addresses
|
||||
if(this.peerDiscovery &&
|
||||
(e.message.version >= 31402 || this.peers.length < 1000)) {
|
||||
e.conn.sendGetAddr();
|
||||
e.conn.getaddr = true;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleReady = function (e) {
|
||||
log.info('connected to '+e.conn.peer.host+':'+e.conn.peer.port);
|
||||
this.emit('connect', {
|
||||
pm: this,
|
||||
conn: e.conn,
|
||||
socket: e.socket,
|
||||
peer: e.peer
|
||||
});
|
||||
|
||||
if(this.isConnected == false) {
|
||||
this.emit('netConnected');
|
||||
this.isConnected = true;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleAddr = function (e) {
|
||||
if(!this.peerDiscovery) return;
|
||||
|
||||
var now = GetAdjustedTime();
|
||||
e.message.addrs.forEach(function (addr) {
|
||||
try {
|
||||
// In case of an invalid time, assume "5 days ago"
|
||||
if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) {
|
||||
addr.time = now - 5 * 24 * 60 * 60;
|
||||
}
|
||||
var peer = new Peer(addr.ip, addr.port, addr.services);
|
||||
peer.lastSeen = addr.time;
|
||||
|
||||
// TODO: Handle duplicate peers
|
||||
this.peers.push(peer);
|
||||
|
||||
// TODO: Handle addr relay
|
||||
} catch(e) {
|
||||
log.warn("Invalid addr received: "+e.message);
|
||||
}
|
||||
}.bind(this));
|
||||
if (e.message.addrs.length < 1000 ) {
|
||||
e.conn.getaddr = false;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleGetAddr = function(e) {
|
||||
// TODO: Reply with addr message.
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleError = function(e) {
|
||||
log.err(e.peer, e.err);
|
||||
this.handleDisconnect.apply(this, [].slice.call(arguments));
|
||||
};
|
||||
|
||||
PeerManager.prototype.handleDisconnect = function(e) {
|
||||
log.info('disconnected from peer '+e.peer);
|
||||
var i = this.connections.indexOf(e.conn);
|
||||
if(i != -1) this.connections.splice(i, 1);
|
||||
|
||||
if(!this.connections.length) {
|
||||
this.emit('netDisconnected');
|
||||
this.isConnected = false;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.getActiveConnection = function () {
|
||||
var activeConnections = this.connections.filter(function (conn) {
|
||||
return conn.active;
|
||||
});
|
||||
|
||||
if (activeConnections.length) {
|
||||
var randomIndex = Math.floor(Math.random()*activeConnections.length);
|
||||
var candidate = activeConnections[randomIndex];
|
||||
if (candidate.socket.writable) {
|
||||
return candidate;
|
||||
} else {
|
||||
// Socket is not writable, remove it from active connections
|
||||
activeConnections.splice(randomIndex, 1);
|
||||
|
||||
// Then try again
|
||||
// TODO: This causes an infinite recursion when all connections are dead,
|
||||
// although it shouldn't.
|
||||
return this.getActiveConnection();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
PeerManager.prototype.getActiveConnections = function () {
|
||||
return this.connections.slice(0);
|
||||
};
|
||||
|
||||
return PeerManager;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,309 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var Opcode = require('./opcode').class();
|
||||
|
||||
// Make opcodes available as pseudo-constants
|
||||
for (var i in Opcode.map) {
|
||||
eval(i + " = " + Opcode.map[i] + ";");
|
||||
}
|
||||
|
||||
var logger = b.logger || require('../ext/logger');
|
||||
var Util = b.Util || require('../ext/util');
|
||||
var Parser = b.Parser || require('../ext/binaryParser').class();
|
||||
var Put = b.Put || require('bufferput');
|
||||
|
||||
function Script(buffer) {
|
||||
if(buffer) {
|
||||
this.buffer = buffer;
|
||||
} else {
|
||||
this.buffer = Util.EMPTY_BUFFER;
|
||||
}
|
||||
this.chunks = [];
|
||||
this.parse();
|
||||
};
|
||||
this.class = Script;
|
||||
|
||||
Script.prototype.parse = function () {
|
||||
this.chunks = [];
|
||||
|
||||
var parser = new Parser(this.buffer);
|
||||
while (!parser.eof()) {
|
||||
var opcode = parser.word8();
|
||||
|
||||
var len;
|
||||
if (opcode > 0 && opcode < OP_PUSHDATA1) {
|
||||
// Read some bytes of data, opcode value is the length of data
|
||||
this.chunks.push(parser.buffer(opcode));
|
||||
} else if (opcode == OP_PUSHDATA1) {
|
||||
len = parser.word8();
|
||||
this.chunks.push(parser.buffer(len));
|
||||
} else if (opcode == OP_PUSHDATA2) {
|
||||
len = parser.word16le();
|
||||
this.chunks.push(parser.buffer(len));
|
||||
} else if (opcode == OP_PUSHDATA4) {
|
||||
len = parser.word32le();
|
||||
this.chunks.push(parser.buffer(len));
|
||||
} else {
|
||||
this.chunks.push(opcode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Script.prototype.isSentToIP = function ()
|
||||
{
|
||||
if (this.chunks.length != 2) {
|
||||
return false;
|
||||
}
|
||||
return this.chunks[1] == OP_CHECKSIG && Buffer.isBuffer(this.chunks[0]);
|
||||
};
|
||||
|
||||
Script.prototype.getOutType = function ()
|
||||
{
|
||||
if (this.chunks.length == 5 &&
|
||||
this.chunks[0] == OP_DUP &&
|
||||
this.chunks[1] == OP_HASH160 &&
|
||||
this.chunks[3] == OP_EQUALVERIFY &&
|
||||
this.chunks[4] == OP_CHECKSIG) {
|
||||
|
||||
// Transfer to Bitcoin address
|
||||
return 'Address';
|
||||
} else if (this.chunks.length == 2 &&
|
||||
this.chunks[1] == OP_CHECKSIG) {
|
||||
|
||||
// Transfer to IP address
|
||||
return 'Pubkey';
|
||||
} else {
|
||||
return 'Strange';
|
||||
}
|
||||
};
|
||||
|
||||
Script.prototype.simpleOutHash = function ()
|
||||
{
|
||||
switch (this.getOutType()) {
|
||||
case 'Address':
|
||||
return this.chunks[2];
|
||||
case 'Pubkey':
|
||||
return Util.sha256ripe160(this.chunks[0]);
|
||||
default:
|
||||
logger.scrdbg("Encountered non-standard scriptPubKey");
|
||||
logger.scrdbg("Strange script was: " + this.toString());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Script.prototype.getInType = function ()
|
||||
{
|
||||
if (this.chunks.length == 1) {
|
||||
// Direct IP to IP transactions only have the public key in their scriptSig.
|
||||
return 'Pubkey';
|
||||
} else if (this.chunks.length == 2 &&
|
||||
Buffer.isBuffer(this.chunks[0]) &&
|
||||
Buffer.isBuffer(this.chunks[1])) {
|
||||
return 'Address';
|
||||
} else {
|
||||
return 'Strange';
|
||||
}
|
||||
};
|
||||
|
||||
Script.prototype.simpleInPubKey = function ()
|
||||
{
|
||||
switch (this.getInType()) {
|
||||
case 'Address':
|
||||
return this.chunks[1];
|
||||
case 'Pubkey':
|
||||
return null;
|
||||
default:
|
||||
logger.scrdbg("Encountered non-standard scriptSig");
|
||||
logger.scrdbg("Strange script was: " + this.toString());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Script.prototype.getBuffer = function ()
|
||||
{
|
||||
return this.buffer;
|
||||
};
|
||||
|
||||
Script.prototype.getStringContent = function (truncate, maxEl)
|
||||
{
|
||||
if (truncate === null) {
|
||||
truncate = true;
|
||||
}
|
||||
|
||||
if ("undefined" === typeof maxEl) {
|
||||
maxEl = 15;
|
||||
}
|
||||
|
||||
var script = '';
|
||||
for (var i = 0, l = this.chunks.length; i < l; i++) {
|
||||
var chunk = this.chunks[i];
|
||||
|
||||
if (i > 0) {
|
||||
script += " ";
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(chunk)) {
|
||||
script += "0x"+Util.formatBuffer(chunk, truncate ? null : 0);
|
||||
} else {
|
||||
script += Opcode.reverseMap[chunk];
|
||||
}
|
||||
|
||||
if (maxEl && i > maxEl) {
|
||||
script += " ...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return script;
|
||||
};
|
||||
|
||||
Script.prototype.toString = function (truncate, maxEl)
|
||||
{
|
||||
var script = "<Script ";
|
||||
script += this.getStringContent(truncate, maxEl);
|
||||
script += ">";
|
||||
return script;
|
||||
};
|
||||
|
||||
|
||||
Script.prototype.writeOp = function (opcode)
|
||||
{
|
||||
var buf = Put();
|
||||
buf.put(this.buffer);
|
||||
buf.word8(opcode);
|
||||
this.buffer = buf.buffer();
|
||||
|
||||
this.chunks.push(opcode);
|
||||
};
|
||||
|
||||
Script.prototype.writeBytes = function (data)
|
||||
{
|
||||
var buf = Put();
|
||||
buf.put(this.buffer);
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
buf.word8(data.length);
|
||||
} else if (data.length <= 0xff) {
|
||||
buf.word8(OP_PUSHDATA1);
|
||||
buf.word8(data.length);
|
||||
} else if (data.length <= 0xffff) {
|
||||
buf.word8(OP_PUSHDATA2);
|
||||
buf.word16le(data.length);
|
||||
} else {
|
||||
buf.word8(OP_PUSHDATA4);
|
||||
buf.word32le(data.length);
|
||||
}
|
||||
buf.put(data);
|
||||
this.buffer = buf.buffer();
|
||||
this.chunks.push(data);
|
||||
};
|
||||
|
||||
Script.prototype.updateBuffer = function ()
|
||||
{
|
||||
this.buffer = Script.chunksToBuffer(this.chunks);
|
||||
};
|
||||
|
||||
Script.prototype.findAndDelete = function (chunk)
|
||||
{
|
||||
var dirty = false;
|
||||
if (Buffer.isBuffer(chunk)) {
|
||||
for (var i = 0, l = this.chunks.length; i < l; i++) {
|
||||
if (Buffer.isBuffer(this.chunks[i]) &&
|
||||
this.chunks[i].compare(chunk) == 0) {
|
||||
this.chunks.splice(i, 1);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else if ("number" === typeof chunk) {
|
||||
for (var i = 0, l = this.chunks.length; i < l; i++) {
|
||||
if (this.chunks[i] === chunk) {
|
||||
this.chunks.splice(i, 1);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid chunk datatype.");
|
||||
}
|
||||
if (dirty) {
|
||||
this.updateBuffer();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a simple OP_CHECKSIG with pubkey output script.
|
||||
*
|
||||
* These are used for coinbase transactions and at some point were used for
|
||||
* IP-based transactions as well.
|
||||
*/
|
||||
Script.createPubKeyOut = function (pubkey) {
|
||||
var script = new Script();
|
||||
script.writeBytes(pubkey);
|
||||
script.writeOp(OP_CHECKSIG);
|
||||
return script;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a standard txout script.
|
||||
*/
|
||||
Script.createPubKeyHashOut = function (pubKeyHash) {
|
||||
var script = new Script();
|
||||
script.writeOp(OP_DUP);
|
||||
script.writeOp(OP_HASH160);
|
||||
script.writeBytes(pubKeyHash);
|
||||
script.writeOp(OP_EQUALVERIFY);
|
||||
script.writeOp(OP_CHECKSIG);
|
||||
return script;
|
||||
};
|
||||
|
||||
Script.fromTestData = function (testData) {
|
||||
testData = testData.map(function (chunk) {
|
||||
if ("string" === typeof chunk) {
|
||||
return new Buffer(chunk, 'hex');
|
||||
} else {
|
||||
return chunk;
|
||||
}
|
||||
});
|
||||
|
||||
var script = new Script();
|
||||
script.chunks = testData;
|
||||
script.updateBuffer();
|
||||
return script;
|
||||
};
|
||||
|
||||
Script.fromChunks = function (chunks) {
|
||||
var script = new Script();
|
||||
script.chunks = chunks;
|
||||
script.updateBuffer();
|
||||
return script;
|
||||
};
|
||||
|
||||
Script.chunksToBuffer = function (chunks) {
|
||||
var buf = Put();
|
||||
for (var i = 0, l = chunks.length; i < l; i++) {
|
||||
var data = chunks[i];
|
||||
if (Buffer.isBuffer(data)) {
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
buf.word8(data.length);
|
||||
} else if (data.length <= 0xff) {
|
||||
buf.word8(OP_PUSHDATA1);
|
||||
buf.word8(data.length);
|
||||
} else if (data.length <= 0xffff) {
|
||||
buf.word8(OP_PUSHDATA2);
|
||||
buf.word16le(data.length);
|
||||
} else {
|
||||
buf.word8(OP_PUSHDATA4);
|
||||
buf.word32le(data.length);
|
||||
}
|
||||
buf.put(data);
|
||||
} else if ("number" === typeof data) {
|
||||
buf.word8(data);
|
||||
} else {
|
||||
throw new Error("Script.chunksToBuffer(): Invalid chunk datatype");
|
||||
}
|
||||
}
|
||||
return buf.buffer();
|
||||
};
|
||||
|
||||
return Script;
|
||||
};
|
||||
module.defineClass(spec);
|
||||
|
|
@ -0,0 +1,942 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var Opcode = require('./opcode').class();
|
||||
|
||||
// Make opcodes available as pseudo-constants
|
||||
for (var i in Opcode.map) {
|
||||
eval(i + " = " + Opcode.map[i] + ";");
|
||||
}
|
||||
|
||||
var bignum = b.bignum || require('bignum');
|
||||
var logger = b.logger || require('../ext/logger');
|
||||
var Util = b.Util || require('../ext/util');
|
||||
var Script = require('./script').class();
|
||||
|
||||
function ScriptInterpreter() {
|
||||
this.stack = [];
|
||||
this.disableUnsafeOpcodes = true;
|
||||
};
|
||||
|
||||
ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, callback)
|
||||
{
|
||||
if ("function" !== typeof callback) {
|
||||
throw new Error("ScriptInterpreter.eval() requires a callback");
|
||||
}
|
||||
|
||||
var pc = 0;
|
||||
|
||||
var execStack = [];
|
||||
var altStack = [];
|
||||
var hashStart = 0;
|
||||
var opCount = 0;
|
||||
|
||||
if (script.buffer.length > 10000) {
|
||||
callback(new Error("Oversized script (> 10k bytes)"));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Start execution by running the first step
|
||||
executeStep.call(this, callback);
|
||||
|
||||
function executeStep(cb) {
|
||||
// Once all chunks have been processed, execution ends
|
||||
if (pc >= script.chunks.length) {
|
||||
// Execution stack must be empty at the end of the script
|
||||
if (execStack.length) {
|
||||
cb(new Error("Execution stack ended non-empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Execution successful (Note that we still have to check whether the
|
||||
// final stack contains a truthy value.)
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// The execution bit is true if there are no "false" values in the
|
||||
// execution stack. (A "false" value indicates that we're in the
|
||||
// inactive branch of an if statement.)
|
||||
var exec = !~execStack.indexOf(false);
|
||||
|
||||
var opcode = script.chunks[pc++];
|
||||
|
||||
if (opcode.length > 520) {
|
||||
throw new Error("Max push value size exceeded (>520)");
|
||||
}
|
||||
|
||||
if (opcode > OP_16 && ++opCount > 201) {
|
||||
throw new Error("Opcode limit exceeded (>200)");
|
||||
}
|
||||
|
||||
if (this.disableUnsafeOpcodes &&
|
||||
"number" === typeof opcode &&
|
||||
(opcode === OP_CAT ||
|
||||
opcode === OP_SUBSTR ||
|
||||
opcode === OP_LEFT ||
|
||||
opcode === OP_RIGHT ||
|
||||
opcode === OP_INVERT ||
|
||||
opcode === OP_AND ||
|
||||
opcode === OP_OR ||
|
||||
opcode === OP_XOR ||
|
||||
opcode === OP_2MUL ||
|
||||
opcode === OP_2DIV ||
|
||||
opcode === OP_MUL ||
|
||||
opcode === OP_DIV ||
|
||||
opcode === OP_MOD ||
|
||||
opcode === OP_LSHIFT ||
|
||||
opcode === OP_RSHIFT)) {
|
||||
throw new Error("Encountered a disabled opcode");
|
||||
}
|
||||
|
||||
if (exec && Buffer.isBuffer(opcode))
|
||||
this.stack.push(opcode);
|
||||
else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
|
||||
switch (opcode) {
|
||||
case OP_0:
|
||||
this.stack.push(new Buffer([]));
|
||||
break;
|
||||
|
||||
case OP_1NEGATE:
|
||||
case OP_1:
|
||||
case OP_2:
|
||||
case OP_3:
|
||||
case OP_4:
|
||||
case OP_5:
|
||||
case OP_6:
|
||||
case OP_7:
|
||||
case OP_8:
|
||||
case OP_9:
|
||||
case OP_10:
|
||||
case OP_11:
|
||||
case OP_12:
|
||||
case OP_13:
|
||||
case OP_14:
|
||||
case OP_15:
|
||||
case OP_16:
|
||||
this.stack.push(bigintToBuffer(opcode - OP_1 + 1));
|
||||
break;
|
||||
|
||||
case OP_NOP:
|
||||
case OP_NOP1: case OP_NOP2: case OP_NOP3: case OP_NOP4: case OP_NOP5:
|
||||
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
|
||||
break;
|
||||
|
||||
case OP_IF:
|
||||
case OP_NOTIF:
|
||||
// <expression> if [statements] [else [statements]] endif
|
||||
var value = false;
|
||||
if (exec) {
|
||||
value = castBool(this.stackPop());
|
||||
if (opcode === OP_NOTIF) {
|
||||
value = !value;
|
||||
}
|
||||
}
|
||||
execStack.push(value);
|
||||
break;
|
||||
|
||||
case OP_ELSE:
|
||||
if (execStack.length < 1) {
|
||||
throw new Error("Unmatched OP_ELSE");
|
||||
}
|
||||
execStack[execStack.length-1] = !execStack[execStack.length-1];
|
||||
break;
|
||||
|
||||
case OP_ENDIF:
|
||||
if (execStack.length < 1) {
|
||||
throw new Error("Unmatched OP_ENDIF");
|
||||
}
|
||||
execStack.pop();
|
||||
break;
|
||||
|
||||
case OP_VERIFY:
|
||||
var value = castBool(this.stackTop());
|
||||
if (value) {
|
||||
this.stackPop();
|
||||
} else {
|
||||
throw new Error("OP_VERIFY negative");
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_RETURN:
|
||||
throw new Error("OP_RETURN");
|
||||
|
||||
case OP_TOALTSTACK:
|
||||
altStack.push(this.stackPop());
|
||||
break;
|
||||
|
||||
case OP_FROMALTSTACK:
|
||||
if (altStack.length < 1) {
|
||||
throw new Error("OP_FROMALTSTACK with alt stack empty");
|
||||
}
|
||||
this.stack.push(altStack.pop());
|
||||
break;
|
||||
|
||||
case OP_2DROP:
|
||||
// (x1 x2 -- )
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
break;
|
||||
|
||||
case OP_2DUP:
|
||||
// (x1 x2 -- x1 x2 x1 x2)
|
||||
var v1 = this.stackTop(2);
|
||||
var v2 = this.stackTop(1);
|
||||
this.stack.push(v1);
|
||||
this.stack.push(v2);
|
||||
break;
|
||||
|
||||
case OP_3DUP:
|
||||
// (x1 x2 -- x1 x2 x1 x2)
|
||||
var v1 = this.stackTop(3);
|
||||
var v2 = this.stackTop(2);
|
||||
var v3 = this.stackTop(1);
|
||||
this.stack.push(v1);
|
||||
this.stack.push(v2);
|
||||
this.stack.push(v3);
|
||||
break;
|
||||
|
||||
case OP_2OVER:
|
||||
// (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2)
|
||||
var v1 = this.stackTop(4);
|
||||
var v2 = this.stackTop(3);
|
||||
this.stack.push(v1);
|
||||
this.stack.push(v2);
|
||||
break;
|
||||
|
||||
case OP_2ROT:
|
||||
// (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2)
|
||||
var v1 = this.stackTop(6);
|
||||
var v2 = this.stackTop(5);
|
||||
this.stack.splice(this.stack.length - 6, 2);
|
||||
this.stack.push(v1);
|
||||
this.stack.push(v2);
|
||||
break;
|
||||
|
||||
case OP_2SWAP:
|
||||
// (x1 x2 x3 x4 -- x3 x4 x1 x2)
|
||||
this.stackSwap(4, 2);
|
||||
this.stackSwap(3, 1);
|
||||
break;
|
||||
|
||||
case OP_IFDUP:
|
||||
// (x - 0 | x x)
|
||||
var value = this.stackTop();
|
||||
if (castBool(value)) {
|
||||
this.stack.push(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_DEPTH:
|
||||
// -- stacksize
|
||||
var value = bignum(this.stack.length);
|
||||
this.stack.push(bigintToBuffer(value));
|
||||
break;
|
||||
|
||||
case OP_DROP:
|
||||
// (x -- )
|
||||
this.stackPop();
|
||||
break;
|
||||
|
||||
case OP_DUP:
|
||||
// (x -- x x)
|
||||
this.stack.push(this.stackTop());
|
||||
break;
|
||||
|
||||
case OP_NIP:
|
||||
// (x1 x2 -- x2)
|
||||
if (this.stack.length < 2) {
|
||||
throw new Error("OP_NIP insufficient stack size");
|
||||
}
|
||||
this.stack.splice(this.stack.length - 2, 1);
|
||||
break;
|
||||
|
||||
case OP_OVER:
|
||||
// (x1 x2 -- x1 x2 x1)
|
||||
this.stack.push(this.stackTop(2));
|
||||
break;
|
||||
|
||||
case OP_PICK:
|
||||
case OP_ROLL:
|
||||
// (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn)
|
||||
// (xn ... x2 x1 x0 n - ... x2 x1 x0 xn)
|
||||
var n = castInt(this.stackPop());
|
||||
if (n < 0 || n >= this.stack.length) {
|
||||
throw new Error("OP_PICK/OP_ROLL insufficient stack size");
|
||||
}
|
||||
var value = this.stackTop(n+1);
|
||||
if (opcode === OP_ROLL) {
|
||||
this.stack.splice(this.stack.length - n - 1, 1);
|
||||
}
|
||||
this.stack.push(value);
|
||||
break;
|
||||
|
||||
case OP_ROT:
|
||||
// (x1 x2 x3 -- x2 x3 x1)
|
||||
// x2 x1 x3 after first swap
|
||||
// x2 x3 x1 after second swap
|
||||
this.stackSwap(3, 2);
|
||||
this.stackSwap(2, 1);
|
||||
break;
|
||||
|
||||
case OP_SWAP:
|
||||
// (x1 x2 -- x2 x1)
|
||||
this.stackSwap(2, 1);
|
||||
break;
|
||||
|
||||
case OP_TUCK:
|
||||
// (x1 x2 -- x2 x1 x2)
|
||||
if (this.stack.length < 2) {
|
||||
throw new Error("OP_TUCK insufficient stack size");
|
||||
}
|
||||
this.stack.splice(this.stack.length - 2, 0, this.stackTop());
|
||||
break;
|
||||
|
||||
case OP_CAT:
|
||||
// (x1 x2 -- out)
|
||||
var v1 = this.stackTop(2);
|
||||
var v2 = this.stackTop(1);
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stack.push(v1.concat(v2));
|
||||
break;
|
||||
|
||||
case OP_SUBSTR:
|
||||
// (in begin size -- out)
|
||||
var buf = this.stackTop(3);
|
||||
var start = castInt(this.stackTop(2));
|
||||
var len = castInt(this.stackTop(1));
|
||||
if (start < 0 || len < 0) {
|
||||
throw new Error("OP_SUBSTR start < 0 or len < 0");
|
||||
}
|
||||
if ((start + len) >= buf.length) {
|
||||
throw new Error("OP_SUBSTR range out of bounds");
|
||||
}
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stack[this.stack.length-1] = buf.slice(start, start + len);
|
||||
break;
|
||||
|
||||
case OP_LEFT:
|
||||
case OP_RIGHT:
|
||||
// (in size -- out)
|
||||
var buf = this.stackTop(2);
|
||||
var size = castInt(this.stackTop(1));
|
||||
if (size < 0) {
|
||||
throw new Error("OP_LEFT/OP_RIGHT size < 0");
|
||||
}
|
||||
if (size > buf.length) {
|
||||
size = buf.length;
|
||||
}
|
||||
this.stackPop();
|
||||
if (opcode === OP_LEFT) {
|
||||
this.stack[this.stack.length-1] = buf.slice(0, size);
|
||||
} else {
|
||||
this.stack[this.stack.length-1] = buf.slice(buf.length - size);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_SIZE:
|
||||
// (in -- in size)
|
||||
var value = bignum(this.stackTop().length);
|
||||
this.stack.push(bigintToBuffer(value));
|
||||
break;
|
||||
|
||||
case OP_INVERT:
|
||||
// (in - out)
|
||||
var buf = this.stackTop();
|
||||
for (var i = 0, l = buf.length; i < l; i++) {
|
||||
buf[i] = ~buf[i];
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_XOR:
|
||||
// (x1 x2 - out)
|
||||
var v1 = this.stackTop(2);
|
||||
var v2 = this.stackTop(1);
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
var out = new Buffer(Math.max(v1.length, v2.length));
|
||||
if (opcode === OP_AND) {
|
||||
for (var i = 0, l = out.length; i < l; i++) {
|
||||
out[i] = v1[i] & v2[i];
|
||||
}
|
||||
} else if (opcode === OP_OR) {
|
||||
for (var i = 0, l = out.length; i < l; i++) {
|
||||
out[i] = v1[i] | v2[i];
|
||||
}
|
||||
} else if (opcode === OP_XOR) {
|
||||
for (var i = 0, l = out.length; i < l; i++) {
|
||||
out[i] = v1[i] ^ v2[i];
|
||||
}
|
||||
}
|
||||
this.stack.push(out);
|
||||
break;
|
||||
|
||||
case OP_EQUAL:
|
||||
case OP_EQUALVERIFY:
|
||||
//case OP_NOTEQUAL: // use OP_NUMNOTEQUAL
|
||||
// (x1 x2 - bool)
|
||||
var v1 = this.stackTop(2);
|
||||
var v2 = this.stackTop(1);
|
||||
var value = v1.compare(v2) == 0;
|
||||
|
||||
// OP_NOTEQUAL is disabled because it would be too easy to say
|
||||
// something like n != 1 and have some wiseguy pass in 1 with extra
|
||||
// zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001)
|
||||
//if (opcode == OP_NOTEQUAL)
|
||||
// fEqual = !fEqual;
|
||||
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stack.push(new Buffer([value ? 1 : 0]));
|
||||
if (opcode === OP_EQUALVERIFY) {
|
||||
if (value) {
|
||||
this.stackPop();
|
||||
} else {
|
||||
throw new Error("OP_EQUALVERIFY negative");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_1ADD:
|
||||
case OP_1SUB:
|
||||
case OP_2MUL:
|
||||
case OP_2DIV:
|
||||
case OP_NEGATE:
|
||||
case OP_ABS:
|
||||
case OP_NOT:
|
||||
case OP_0NOTEQUAL:
|
||||
// (in -- out)
|
||||
var num = castBigint(this.stackTop());
|
||||
switch (opcode) {
|
||||
case OP_1ADD: num = num.add(bignum(1)); break;
|
||||
case OP_1SUB: num = num.sub(bignum(1)); break;
|
||||
case OP_2MUL: num = num.mul(bignum(2)); break;
|
||||
case OP_2DIV: num = num.div(bignum(2)); break;
|
||||
case OP_NEGATE: num = num.neg(); break;
|
||||
case OP_ABS: num = num.abs(); break;
|
||||
case OP_NOT: num = bignum(num.cmp(0) == 0 ? 1 : 0); break;
|
||||
case OP_0NOTEQUAL: num = bignum(num.cmp(0) == 0 ? 0 : 1); break;
|
||||
}
|
||||
this.stack[this.stack.length-1] = bigintToBuffer(num);
|
||||
break;
|
||||
|
||||
case OP_ADD:
|
||||
case OP_SUB:
|
||||
case OP_MUL:
|
||||
case OP_DIV:
|
||||
case OP_MOD:
|
||||
case OP_LSHIFT:
|
||||
case OP_RSHIFT:
|
||||
case OP_BOOLAND:
|
||||
case OP_BOOLOR:
|
||||
case OP_NUMEQUAL:
|
||||
case OP_NUMEQUALVERIFY:
|
||||
case OP_NUMNOTEQUAL:
|
||||
case OP_LESSTHAN:
|
||||
case OP_GREATERTHAN:
|
||||
case OP_LESSTHANOREQUAL:
|
||||
case OP_GREATERTHANOREQUAL:
|
||||
case OP_MIN:
|
||||
case OP_MAX:
|
||||
// (x1 x2 -- out)
|
||||
var v1 = castBigint(this.stackTop(2));
|
||||
var v2 = castBigint(this.stackTop(1));
|
||||
var num;
|
||||
switch (opcode) {
|
||||
case OP_ADD: num = v1.add(v2); break;
|
||||
case OP_SUB: num = v1.sub(v2); break;
|
||||
case OP_MUL: num = v1.mul(v2); break;
|
||||
case OP_DIV: num = v1.div(v2); break;
|
||||
case OP_MOD: num = v1.mod(v2); break;
|
||||
|
||||
case OP_LSHIFT:
|
||||
if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) {
|
||||
throw new Error("OP_LSHIFT parameter out of bounds");
|
||||
}
|
||||
num = v1.shiftLeft(v2);
|
||||
break;
|
||||
|
||||
case OP_RSHIFT:
|
||||
if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) {
|
||||
throw new Error("OP_RSHIFT parameter out of bounds");
|
||||
}
|
||||
num = v1.shiftRight(v2);
|
||||
break;
|
||||
|
||||
case OP_BOOLAND:
|
||||
num = bignum((v1.cmp(0) != 0 && v2.cmp(0) != 0) ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_BOOLOR:
|
||||
num = bignum((v1.cmp(0) != 0 || v2.cmp(0) != 0) ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_NUMEQUAL:
|
||||
case OP_NUMEQUALVERIFY:
|
||||
num = bignum(v1.cmp(v2) == 0 ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_NUMNOTEQUAL:;
|
||||
num = bignum(v1.cmp(v2) != 0 ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_LESSTHAN:
|
||||
num = bignum(v1.lt(v2) ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_GREATERTHAN:
|
||||
num = bignum(v1.gt(v2) ? 1 : 0);
|
||||
break;
|
||||
|
||||
case OP_LESSTHANOREQUAL:
|
||||
num = bignum(v1.gt(v2) ? 0 : 1);
|
||||
break;
|
||||
|
||||
case OP_GREATERTHANOREQUAL:
|
||||
num = bignum(v1.lt(v2) ? 0 : 1);
|
||||
break;
|
||||
|
||||
case OP_MIN: num = (v1.lt(v2) ? v1 : v2); break;
|
||||
case OP_MAX: num = (v1.gt(v2) ? v1 : v2); break;
|
||||
}
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stack.push(bigintToBuffer(num));
|
||||
|
||||
if (opcode === OP_NUMEQUALVERIFY) {
|
||||
if (castBool(this.stackTop())) {
|
||||
this.stackPop();
|
||||
} else {
|
||||
throw new Error("OP_NUMEQUALVERIFY negative");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_WITHIN:
|
||||
// (x min max -- out)
|
||||
var v1 = castBigint(this.stackTop(3));
|
||||
var v2 = castBigint(this.stackTop(2));
|
||||
var v3 = castBigint(this.stackTop(1));
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
var value = v1.cmp(v2) >= 0 && v1.cmp(v3) < 0;
|
||||
this.stack.push(bigintToBuffer(value ? 1 : 0));
|
||||
break;
|
||||
|
||||
case OP_RIPEMD160:
|
||||
case OP_SHA1:
|
||||
case OP_SHA256:
|
||||
case OP_HASH160:
|
||||
case OP_HASH256:
|
||||
// (in -- hash)
|
||||
var value = this.stackPop();
|
||||
var hash;
|
||||
if (opcode === OP_RIPEMD160) {
|
||||
hash = Util.ripe160(value);
|
||||
} else if (opcode === OP_SHA1) {
|
||||
hash = Util.sha1(value);
|
||||
} else if (opcode === OP_SHA256) {
|
||||
hash = Util.sha256(value);
|
||||
} else if (opcode === OP_HASH160) {
|
||||
hash = Util.sha256ripe160(value);
|
||||
} else if (opcode === OP_HASH256) {
|
||||
hash = Util.twoSha256(value);
|
||||
}
|
||||
this.stack.push(hash);
|
||||
break;
|
||||
|
||||
case OP_CODESEPARATOR:
|
||||
// Hash starts after the code separator
|
||||
hashStart = pc;
|
||||
break;
|
||||
|
||||
case OP_CHECKSIG:
|
||||
case OP_CHECKSIGVERIFY:
|
||||
// (sig pubkey -- bool)
|
||||
var sig = this.stackTop(2);
|
||||
var pubkey = this.stackTop(1);
|
||||
|
||||
// Get the part of this script since the last OP_CODESEPARATOR
|
||||
var scriptChunks = script.chunks.slice(hashStart);
|
||||
|
||||
// Convert to binary
|
||||
var scriptCode = Script.fromChunks(scriptChunks);
|
||||
|
||||
// Remove signature if present (a signature can't sign itself)
|
||||
scriptCode.findAndDelete(sig);
|
||||
|
||||
// Verify signature
|
||||
checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function (e, result) {
|
||||
try {
|
||||
var success;
|
||||
|
||||
if (e) {
|
||||
// We intentionally ignore errors during signature verification and
|
||||
// treat these cases as an invalid signature.
|
||||
success = false;
|
||||
} else {
|
||||
success = result;
|
||||
}
|
||||
|
||||
// Update stack
|
||||
this.stackPop();
|
||||
this.stackPop();
|
||||
this.stack.push(new Buffer([success ? 1 : 0]));
|
||||
if (opcode === OP_CHECKSIGVERIFY) {
|
||||
if (success) {
|
||||
this.stackPop();
|
||||
} else {
|
||||
throw new Error("OP_CHECKSIGVERIFY negative");
|
||||
}
|
||||
}
|
||||
|
||||
// Run next step
|
||||
executeStep.call(this, cb);
|
||||
} catch(e) {
|
||||
cb(e);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Note that for asynchronous opcodes we have to return here to prevent
|
||||
// the next opcode from being executed.
|
||||
return;
|
||||
|
||||
case OP_CHECKMULTISIG:
|
||||
case OP_CHECKMULTISIGVERIFY:
|
||||
// ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool)
|
||||
var keysCount = castInt(this.stackPop());
|
||||
if (keysCount < 0 || keysCount > 20) {
|
||||
throw new Error("OP_CHECKMULTISIG keysCount out of bounds");
|
||||
}
|
||||
opCount += keysCount;
|
||||
if (opCount > 201) {
|
||||
throw new Error("Opcode limit exceeded (>200)");
|
||||
}
|
||||
var keys = [];
|
||||
for (var i = 0, l = keysCount; i < l; i++) {
|
||||
keys.push(this.stackPop());
|
||||
}
|
||||
var sigsCount = castInt(this.stackPop());
|
||||
if (sigsCount < 0 || sigsCount > keysCount) {
|
||||
throw new Error("OP_CHECKMULTISIG sigsCount out of bounds");
|
||||
}
|
||||
var sigs = [];
|
||||
for (var i = 0, l = sigsCount; i < l; i++) {
|
||||
sigs.push(this.stackPop());
|
||||
}
|
||||
|
||||
// The original client has a bug where it pops an extra element off the
|
||||
// stack. It can't be fixed without causing a chain split and we need to
|
||||
// imitate this behavior as well.
|
||||
this.stackPop();
|
||||
|
||||
// Get the part of this script since the last OP_CODESEPARATOR
|
||||
var scriptChunks = script.chunks.slice(hashStart);
|
||||
|
||||
// Convert to binary
|
||||
var scriptCode = Script.fromChunks(scriptChunks);
|
||||
|
||||
// Drop the signatures, since a signature can't sign itself
|
||||
sigs.forEach(function (sig) {
|
||||
scriptCode.findAndDelete(sig);
|
||||
});
|
||||
|
||||
var success = true, isig = 0, ikey = 0;
|
||||
checkMultiSigStep.call(this);
|
||||
|
||||
function checkMultiSigStep() {
|
||||
try {
|
||||
if (success && sigsCount > 0) {
|
||||
var sig = sigs[isig];
|
||||
var key = keys[ikey];
|
||||
|
||||
checkSig(sig, key, scriptCode, tx, inIndex, hashType, function (e, result) {
|
||||
try {
|
||||
if (!e && result) {
|
||||
isig++;
|
||||
sigsCount--;
|
||||
} else {
|
||||
ikey++;
|
||||
keysCount--;
|
||||
|
||||
// If there are more signatures than keys left, then too many
|
||||
// signatures have failed
|
||||
if (sigsCount > keysCount) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
checkMultiSigStep.call(this);
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.stack.push(new Buffer([success ? 1 : 0]));
|
||||
if (opcode === OP_CHECKMULTISIGVERIFY) {
|
||||
if (success) {
|
||||
this.stackPop();
|
||||
} else {
|
||||
throw new Error("OP_CHECKMULTISIGVERIFY negative");
|
||||
}
|
||||
}
|
||||
|
||||
// Run next step
|
||||
executeStep.call(this, cb);
|
||||
}
|
||||
} catch(e) {
|
||||
cb(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Note that for asynchronous opcodes we have to return here to prevent
|
||||
// the next opcode from being executed.
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new Error("Unknown opcode encountered");
|
||||
}
|
||||
|
||||
// Size limits
|
||||
if ((this.stack.length + altStack.length) > 1000) {
|
||||
throw new Error("Maximum stack size exceeded");
|
||||
}
|
||||
|
||||
// Run next step
|
||||
if (pc % 100) {
|
||||
// V8 allows for much deeper stacks than Bitcoin's scripting language,
|
||||
// but just to be safe, we'll reset the stack every 100 steps
|
||||
process.nextTick(executeStep.bind(this, cb));
|
||||
} else {
|
||||
executeStep.call(this, cb);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.scrdbg("Script aborted: "+
|
||||
(e.message ? e : e));
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ScriptInterpreter.prototype.evalTwo =
|
||||
function evalTwo(scriptSig, scriptPubkey, tx, n, hashType, callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self.eval(scriptSig, tx, n, hashType, function (e) {
|
||||
if (e) {
|
||||
callback(e)
|
||||
return;
|
||||
}
|
||||
|
||||
self.eval(scriptPubkey, tx, n, hashType, callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top element of the stack.
|
||||
*
|
||||
* Using the offset parameter this function can also access lower elements
|
||||
* from the stack.
|
||||
*/
|
||||
ScriptInterpreter.prototype.stackTop = function stackTop(offset) {
|
||||
offset = +offset || 1;
|
||||
if (offset < 1) offset = 1;
|
||||
|
||||
if (offset > this.stack.length) {
|
||||
throw new Error('ScriptInterpreter.stackTop(): Stack underrun');
|
||||
}
|
||||
|
||||
return this.stack[this.stack.length-offset];
|
||||
};
|
||||
|
||||
/**
|
||||
* Pop the top element off the stack and return it.
|
||||
*/
|
||||
ScriptInterpreter.prototype.stackPop = function stackPop() {
|
||||
if (this.stack.length < 1) {
|
||||
throw new Error('ScriptInterpreter.stackTop(): Stack underrun');
|
||||
}
|
||||
|
||||
return this.stack.pop();
|
||||
};
|
||||
|
||||
ScriptInterpreter.prototype.stackSwap = function stackSwap(a, b) {
|
||||
if (this.stack.length < a || this.stack.length < b) {
|
||||
throw new Error('ScriptInterpreter.stackTop(): Stack underrun');
|
||||
}
|
||||
|
||||
var s = this.stack,
|
||||
l = s.length;
|
||||
|
||||
var tmp = s[l - a];
|
||||
s[l - a] = s[l - b];
|
||||
s[l - b] = tmp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a version of the stack with only primitive types.
|
||||
*
|
||||
* The return value is an array. Any single byte buffer is converted to an
|
||||
* integer. Any longer Buffer is converted to a hex string.
|
||||
*/
|
||||
ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() {
|
||||
return this.stack.map(function (entry) {
|
||||
if (entry.length > 2) {
|
||||
return entry.slice(0).toHex();
|
||||
}
|
||||
var num = castBigint(entry);
|
||||
if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) {
|
||||
return num.toNumber();
|
||||
} else {
|
||||
return entry.slice(0).toHex();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var castBool = ScriptInterpreter.castBool = function castBool(v) {
|
||||
for (var i = 0, l = v.length; i < l; i++) {
|
||||
if (v[i] != 0) {
|
||||
// Negative zero is still zero
|
||||
if (i == (l-1) && v[i] == 0x80) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var castInt = ScriptInterpreter.castInt = function castInt(v) {
|
||||
return castBigint(v).toNumber();
|
||||
};
|
||||
var castBigint = ScriptInterpreter.castBigint = function castBigint(v) {
|
||||
if (!v.length) {
|
||||
return bignum(0);
|
||||
}
|
||||
|
||||
// Arithmetic operands must be in range [-2^31...2^31]
|
||||
if (v.length > 4) {
|
||||
throw new Error("Bigint cast overflow (> 4 bytes)");
|
||||
}
|
||||
|
||||
var w = new Buffer(v.length);
|
||||
v.copy(w);
|
||||
w.reverse();
|
||||
if (w[0] & 0x80) {
|
||||
w[0] &= 0x7f;
|
||||
return bignum.fromBuffer(w).neg();
|
||||
} else {
|
||||
// Positive number
|
||||
return bignum.fromBuffer(w);
|
||||
}
|
||||
};
|
||||
var bigintToBuffer = ScriptInterpreter.bigintToBuffer = function bigintToBuffer(v) {
|
||||
if ("number" === typeof v) {
|
||||
v = bignum(v);
|
||||
}
|
||||
|
||||
var b,c;
|
||||
|
||||
var cmp = v.cmp(0);
|
||||
if (cmp > 0) {
|
||||
b = v.toBuffer();
|
||||
if (b[0] & 0x80) {
|
||||
c = new Buffer(b.length + 1);
|
||||
b.copy(c, 1);
|
||||
c[0] = 0;
|
||||
return c.reverse();
|
||||
} else {
|
||||
return b.reverse();
|
||||
}
|
||||
} else if (cmp == 0) {
|
||||
return new Buffer([]);
|
||||
} else {
|
||||
b = v.neg().toBuffer();
|
||||
if (b[0] & 0x80) {
|
||||
c = new Buffer(b.length + 1);
|
||||
b.copy(c, 1);
|
||||
c[0] = 0x80;
|
||||
return c.reverse();
|
||||
} else {
|
||||
b[0] |= 0x80;
|
||||
return b.reverse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ScriptInterpreter.prototype.getResult = function getResult() {
|
||||
if (this.stack.length === 0) {
|
||||
throw new Error("Empty stack after script evaluation");
|
||||
}
|
||||
|
||||
return castBool(this.stack[this.stack.length-1]);
|
||||
};
|
||||
|
||||
ScriptInterpreter.verify =
|
||||
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback)
|
||||
{
|
||||
if ("function" !== typeof callback) {
|
||||
throw new Error("ScriptInterpreter.verify() requires a callback");
|
||||
}
|
||||
|
||||
// Create execution environment
|
||||
var si = new ScriptInterpreter();
|
||||
|
||||
// Evaluate scripts
|
||||
si.evalTwo(scriptSig, scriptPubKey, txTo, n, hashType, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cast result to bool
|
||||
try {
|
||||
var result = si.getResult();
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
|
||||
return si;
|
||||
};
|
||||
|
||||
var checkSig = ScriptInterpreter.checkSig =
|
||||
function (sig, pubkey, scriptCode, tx, n, hashType, callback) {
|
||||
if (!sig.length) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hashType == 0) {
|
||||
hashType = sig[sig.length -1];
|
||||
} else if (hashType != sig[sig.length -1]) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
sig = sig.slice(0, sig.length-1);
|
||||
|
||||
try {
|
||||
// Signature verification requires a special hash procedure
|
||||
var hash = tx.hashForSignature(scriptCode, n, hashType);
|
||||
|
||||
// Verify signature
|
||||
var key = new Util.BitcoinKey();
|
||||
key.public = pubkey;
|
||||
key.verifySignature(hash, sig, callback);
|
||||
} catch (err) {
|
||||
callback(null, false);
|
||||
}
|
||||
};
|
||||
|
||||
return ScriptInterpreter;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,717 @@
|
|||
require('classtool');
|
||||
|
||||
function spec(b) {
|
||||
var Script = b.Script || require('./script').class();
|
||||
var ScriptInterpreter = b.ScriptInterpreter || require('./scriptInterpreter').class();
|
||||
var util = b.util || require('../ext/util');
|
||||
var bignum = b.bignum || require('bignum');
|
||||
var Put = b.Put || require('bufferput');
|
||||
var error = b.error || require('../ext/error');
|
||||
var logger = b.logger || require('../ext/logger');
|
||||
var Step = b.Step || require('step');
|
||||
var Parser = b.Parser || require('../ext/binaryParser').class();
|
||||
|
||||
var VerificationError = error.VerificationError;
|
||||
var MissingSourceError = error.MissingSourceError;
|
||||
|
||||
var COINBASE_OP = util.NULL_HASH.concat(new Buffer("FFFFFFFF", 'hex'));
|
||||
|
||||
function TransactionIn(data) {
|
||||
if ("object" !== typeof data) {
|
||||
data = {};
|
||||
}
|
||||
if (data.o) {
|
||||
this.o = data.o;
|
||||
}
|
||||
this.s = Buffer.isBuffer(data.s) ? data.s :
|
||||
Buffer.isBuffer(data.script) ? data.script : util.EMPTY_BUFFER;
|
||||
this.q = data.q ? data.q : data.sequence;
|
||||
};
|
||||
|
||||
TransactionIn.prototype.getScript = function getScript() {
|
||||
return new Script(this.s);
|
||||
};
|
||||
|
||||
TransactionIn.prototype.isCoinBase = function isCoinBase() {
|
||||
return this.o.compare(COINBASE_OP) === 0;
|
||||
};
|
||||
|
||||
TransactionIn.prototype.serialize = function serialize() {
|
||||
var bytes = Put();
|
||||
|
||||
bytes.put(this.o);
|
||||
bytes.varint(this.s.length);
|
||||
bytes.put(this.s);
|
||||
bytes.word32le(this.q);
|
||||
|
||||
return bytes.buffer();
|
||||
};
|
||||
|
||||
TransactionIn.prototype.getOutpointHash = function getOutpointIndex() {
|
||||
if ("undefined" !== typeof this.o.outHashCache) {
|
||||
return this.o.outHashCache;
|
||||
}
|
||||
|
||||
return this.o.outHashCache = this.o.slice(0, 32);
|
||||
};
|
||||
|
||||
TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() {
|
||||
return (this.o[32] ) +
|
||||
(this.o[33] << 8) +
|
||||
(this.o[34] << 16) +
|
||||
(this.o[35] << 24);
|
||||
};
|
||||
|
||||
TransactionIn.prototype.setOutpointIndex = function setOutpointIndex(n) {
|
||||
this.o[32] = n & 0xff;
|
||||
this.o[33] = n >> 8 & 0xff;
|
||||
this.o[34] = n >> 16 & 0xff;
|
||||
this.o[35] = n >> 24 & 0xff;
|
||||
};
|
||||
|
||||
|
||||
function TransactionOut(data) {
|
||||
if ("object" !== typeof data) {
|
||||
data = {};
|
||||
}
|
||||
this.v = data.v ? data.v : data.value;
|
||||
this.s = data.s ? data.s : data.script;
|
||||
};
|
||||
|
||||
TransactionOut.prototype.getValue = function getValue() {
|
||||
return new Parser(this.v).word64lu();
|
||||
};
|
||||
|
||||
TransactionOut.prototype.getScript = function getScript() {
|
||||
return new Script(this.s);
|
||||
};
|
||||
|
||||
TransactionOut.prototype.serialize = function serialize() {
|
||||
var bytes = Put();
|
||||
|
||||
bytes.put(this.v);
|
||||
bytes.varint(this.s.length);
|
||||
bytes.put(this.s);
|
||||
|
||||
return bytes.buffer();
|
||||
};
|
||||
|
||||
function Transaction(data) {
|
||||
if ("object" !== typeof data) {
|
||||
data = {};
|
||||
}
|
||||
this.hash = data.hash || null;
|
||||
this.version = data.version;
|
||||
this.lock_time = data.lock_time;
|
||||
this.ins = Array.isArray(data.ins) ? data.ins.map(function (data) {
|
||||
var txin = new TransactionIn();
|
||||
txin.s = data.s;
|
||||
txin.q = data.q;
|
||||
txin.o = data.o;
|
||||
return txin;
|
||||
}) : [];
|
||||
this.outs = Array.isArray(data.outs) ? data.outs.map(function (data) {
|
||||
var txout = new TransactionOut();
|
||||
txout.v = data.v;
|
||||
txout.s = data.s;
|
||||
return txout;
|
||||
}) : [];
|
||||
if (data.buffer) this._buffer = data.buffer;
|
||||
};
|
||||
this.class = Transaction;
|
||||
Transaction.In = TransactionIn;
|
||||
Transaction.Out = TransactionOut;
|
||||
|
||||
Transaction.prototype.isCoinBase = function () {
|
||||
return this.ins.length == 1 && this.ins[0].isCoinBase();
|
||||
};
|
||||
|
||||
Transaction.prototype.isStandard = function isStandard() {
|
||||
var i;
|
||||
for (i = 0; i < this.ins.length; i++) {
|
||||
if (this.ins[i].getScript().getInType() == "Strange") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < this.outs.length; i++) {
|
||||
if (this.outs[i].getScript().getOutType() == "Strange") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Transaction.prototype.serialize = function serialize() {
|
||||
var bytes = Put();
|
||||
|
||||
bytes.word32le(this.version);
|
||||
bytes.varint(this.ins.length);
|
||||
this.ins.forEach(function (txin) {
|
||||
bytes.put(txin.serialize());
|
||||
});
|
||||
|
||||
bytes.varint(this.outs.length);
|
||||
this.outs.forEach(function (txout) {
|
||||
bytes.put(txout.serialize());
|
||||
});
|
||||
|
||||
bytes.word32le(this.lock_time);
|
||||
|
||||
return this._buffer = bytes.buffer();
|
||||
};
|
||||
|
||||
Transaction.prototype.getBuffer = function getBuffer() {
|
||||
if (this._buffer) return this._buffer;
|
||||
|
||||
return this.serialize();
|
||||
};
|
||||
|
||||
Transaction.prototype.calcHash = function calcHash() {
|
||||
return util.twoSha256(this.getBuffer());
|
||||
};
|
||||
|
||||
Transaction.prototype.checkHash = function checkHash() {
|
||||
if (!this.hash || !this.hash.length) return false;
|
||||
|
||||
return this.calcHash().compare(this.hash) == 0;
|
||||
};
|
||||
|
||||
Transaction.prototype.getHash = function getHash() {
|
||||
if (!this.hash || !this.hash.length) {
|
||||
this.hash = this.calcHash();
|
||||
}
|
||||
return this.hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load and cache transaction inputs.
|
||||
*
|
||||
* This function will try to load the inputs for a transaction.
|
||||
*
|
||||
* @param {BlockChain} blockChain A reference to the BlockChain object.
|
||||
* @param {TransactionMap|null} txStore Additional transactions to consider.
|
||||
* @param {Boolean} wait Whether to keep trying until the dependencies are
|
||||
* met (or a timeout occurs.)
|
||||
* @param {Function} callback Function to call on completion.
|
||||
*/
|
||||
Transaction.prototype.cacheInputs =
|
||||
function cacheInputs(blockChain, txStore, wait, callback) {
|
||||
var self = this;
|
||||
|
||||
var txCache = new TransactionInputsCache(this);
|
||||
txCache.buffer(blockChain, txStore, wait, callback);
|
||||
};
|
||||
|
||||
Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
|
||||
var self = this;
|
||||
|
||||
var txIndex = txCache.txIndex;
|
||||
|
||||
var outpoints = [];
|
||||
|
||||
var valueIn = bignum(0);
|
||||
var valueOut = bignum(0);
|
||||
|
||||
function getTxOut(txin, n) {
|
||||
var outHash = txin.getOutpointHash();
|
||||
var outIndex = txin.getOutpointIndex();
|
||||
var outHashBase64 = outHash.toString('base64');
|
||||
var fromTxOuts = txIndex[outHashBase64];
|
||||
|
||||
if (!fromTxOuts) {
|
||||
throw new MissingSourceError(
|
||||
"Source tx " + util.formatHash(outHash) +
|
||||
" for inputs " + n + " not found",
|
||||
// We store the hash of the missing tx in the error
|
||||
// so that the txStore can watch out for it.
|
||||
outHash.toString('base64')
|
||||
);
|
||||
}
|
||||
|
||||
var txout = fromTxOuts[outIndex];
|
||||
|
||||
if (!txout) {
|
||||
throw new Error("Source output index "+outIndex+
|
||||
" for input "+n+" out of bounds");
|
||||
}
|
||||
|
||||
return txout;
|
||||
};
|
||||
|
||||
Step(
|
||||
function verifyInputs() {
|
||||
var group = this.group();
|
||||
|
||||
if (self.isCoinBase()) {
|
||||
throw new Error("Coinbase tx are invalid unless part of a block");
|
||||
}
|
||||
|
||||
self.ins.forEach(function (txin, n) {
|
||||
var txout = getTxOut(txin, n);
|
||||
|
||||
// TODO: Verify coinbase maturity
|
||||
|
||||
valueIn = valueIn.add(util.valueToBigInt(txout.v));
|
||||
|
||||
outpoints.push(txin.o);
|
||||
|
||||
self.verifyInput(n, txout.getScript(), group());
|
||||
});
|
||||
},
|
||||
|
||||
function verifyInputsResults(err, results) {
|
||||
if (err) throw err;
|
||||
|
||||
for (var i = 0, l = results.length; i < l; i++) {
|
||||
if (!results[i]) {
|
||||
var txout = getTxOut(self.ins[i]);
|
||||
logger.scrdbg('Script evaluated to false');
|
||||
logger.scrdbg('|- scriptSig', ""+self.ins[i].getScript());
|
||||
logger.scrdbg('`- scriptPubKey', ""+txout.getScript());
|
||||
throw new VerificationError('Script for input '+i+' evaluated to false');
|
||||
}
|
||||
}
|
||||
|
||||
this();
|
||||
},
|
||||
|
||||
function queryConflicts(err) {
|
||||
if (err) throw err;
|
||||
|
||||
// Make sure there are no other transactions spending the same outs
|
||||
blockChain.countConflictingTransactions(outpoints, this);
|
||||
},
|
||||
function checkConflicts(err, count) {
|
||||
if (err) throw err;
|
||||
|
||||
self.outs.forEach(function (txout) {
|
||||
valueOut = valueOut.add(util.valueToBigInt(txout.v));
|
||||
});
|
||||
|
||||
if (valueIn.cmp(valueOut) < 0) {
|
||||
var outValue = util.formatValue(valueOut);
|
||||
var inValue = util.formatValue(valueIn);
|
||||
throw new Error("Tx output value (BTC "+outValue+") "+
|
||||
"exceeds input value (BTC "+inValue+")");
|
||||
}
|
||||
|
||||
var fees = valueIn.sub(valueOut);
|
||||
|
||||
if (count) {
|
||||
// Spent output detected, retrieve transaction that spends it
|
||||
blockChain.getConflictingTransactions(outpoints, function (err, results) {
|
||||
if (results.length) {
|
||||
if (results[0].getHash().compare(self.getHash()) == 0) {
|
||||
logger.warn("Detected tx re-add (recoverable db corruption): "
|
||||
+ util.formatHashAlt(results[0].getHash()));
|
||||
// TODO: Needs to return an error for the memory pool case?
|
||||
callback(null, fees);
|
||||
} else {
|
||||
callback(new Error("At least one referenced output has"
|
||||
+ " already been spent in tx "
|
||||
+ util.formatHashAlt(results[0].getHash())));
|
||||
}
|
||||
} else {
|
||||
callback(new Error("Outputs of this transaction are spent, but "+
|
||||
"the transaction(s) that spend them are not "+
|
||||
"available. This probably means you need to "+
|
||||
"reset your database."));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
this(null, fees);
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, callback) {
|
||||
return ScriptInterpreter.verify(this.ins[n].getScript(),
|
||||
scriptPubKey,
|
||||
this, n, 0,
|
||||
callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object containing all pubkey hashes affected by this transaction.
|
||||
*
|
||||
* The return object contains the base64-encoded pubKeyHash values as keys
|
||||
* and the original pubKeyHash buffers as values.
|
||||
*/
|
||||
Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) {
|
||||
// TODO: Function won't consider results cached if there are no affected
|
||||
// accounts.
|
||||
if (!(this.affects && this.affects.length)) {
|
||||
this.affects = [];
|
||||
|
||||
// Index any pubkeys affected by the outputs of this transaction
|
||||
for (var i = 0, l = this.outs.length; i < l; i++) {
|
||||
try {
|
||||
var txout = this.outs[i];
|
||||
var script = txout.getScript();
|
||||
|
||||
var outPubKey = script.simpleOutPubKeyHash();
|
||||
if (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.
|
||||
logger.debug("Unable to determine affected pubkeys: " +
|
||||
(err.stack ? err.stack : ""+err));
|
||||
}
|
||||
};
|
||||
|
||||
// Index any pubkeys affected by the inputs of this transaction
|
||||
var txIndex = txCache.txIndex;
|
||||
for (var i = 0, l = this.ins.length; i < l; i++) {
|
||||
try {
|
||||
var txin = this.ins[i];
|
||||
|
||||
if (txin.isCoinBase()) continue;
|
||||
|
||||
// In the case of coinbase or IP transactions, the txin doesn't
|
||||
// actually contain the pubkey, so we look at the referenced txout
|
||||
// instead.
|
||||
var outHash = txin.getOutpointHash();
|
||||
var outIndex = txin.getOutpointIndex();
|
||||
var outHashBase64 = outHash.toString('base64');
|
||||
var fromTxOuts = txIndex[outHashBase64];
|
||||
|
||||
if (!fromTxOuts) {
|
||||
throw new Error("Input not found!");
|
||||
}
|
||||
|
||||
var txout = fromTxOuts[outIndex];
|
||||
var script = txout.getScript();
|
||||
|
||||
var outPubKey = script.simpleOutPubKeyHash();
|
||||
if (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.
|
||||
logger.debug("Unable to determine affected pubkeys: " +
|
||||
(err.stack ? err.stack : ""+err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var affectedKeys = {};
|
||||
|
||||
this.affects.forEach(function (pubKeyHash) {
|
||||
affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash;
|
||||
});
|
||||
|
||||
return affectedKeys;
|
||||
};
|
||||
|
||||
var OP_CODESEPARATOR = 171;
|
||||
|
||||
var SIGHASH_ALL = 1;
|
||||
var SIGHASH_NONE = 2;
|
||||
var SIGHASH_SINGLE = 3;
|
||||
var SIGHASH_ANYONECANPAY = 80;
|
||||
|
||||
Transaction.prototype.hashForSignature =
|
||||
function hashForSignature(script, inIndex, hashType) {
|
||||
if (+inIndex !== inIndex ||
|
||||
inIndex < 0 || inIndex >= this.ins.length) {
|
||||
throw new Error("Input index '"+inIndex+"' invalid or out of bounds "+
|
||||
"("+this.ins.length+" inputs)");
|
||||
}
|
||||
|
||||
// Clone transaction
|
||||
var txTmp = new Transaction();
|
||||
this.ins.forEach(function (txin, i) {
|
||||
txTmp.ins.push(new TransactionIn(txin));
|
||||
});
|
||||
this.outs.forEach(function (txout) {
|
||||
txTmp.outs.push(new TransactionOut(txout));
|
||||
});
|
||||
txTmp.version = this.version;
|
||||
txTmp.lock_time = this.lock_time;
|
||||
|
||||
// In case concatenating two scripts ends up with two codeseparators,
|
||||
// or an extra one at the end, this prevents all those possible
|
||||
// incompatibilities.
|
||||
script.findAndDelete(OP_CODESEPARATOR);
|
||||
|
||||
// Get mode portion of hashtype
|
||||
var hashTypeMode = hashType & 0x1f;
|
||||
|
||||
// Generate modified transaction data for hash
|
||||
var bytes = Put();
|
||||
bytes.word32le(this.version);
|
||||
|
||||
// Serialize inputs
|
||||
if (hashType & SIGHASH_ANYONECANPAY) {
|
||||
// Blank out all inputs except current one, not recommended for open
|
||||
// transactions.
|
||||
bytes.varint(1);
|
||||
bytes.put(this.ins[inIndex].o);
|
||||
bytes.varint(script.buffer.length);
|
||||
bytes.put(script.buffer);
|
||||
bytes.word32le(this.ins[inIndex].q);
|
||||
} else {
|
||||
bytes.varint(this.ins.length);
|
||||
for (var i = 0, l = this.ins.length; i < l; i++) {
|
||||
var txin = this.ins[i];
|
||||
bytes.put(this.ins[i].o);
|
||||
|
||||
// Current input's script gets set to the script to be signed, all others
|
||||
// get blanked.
|
||||
if (inIndex === i) {
|
||||
bytes.varint(script.buffer.length);
|
||||
bytes.put(script.buffer);
|
||||
} else {
|
||||
bytes.varint(0);
|
||||
}
|
||||
|
||||
if (hashTypeMode === SIGHASH_NONE && inIndex !== i) {
|
||||
bytes.word32le(0);
|
||||
} else {
|
||||
bytes.word32le(this.ins[i].q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize outputs
|
||||
if (hashTypeMode === SIGHASH_NONE) {
|
||||
bytes.varint(0);
|
||||
} else {
|
||||
var outsLen;
|
||||
if (hashTypeMode === SIGHASH_SINGLE) {
|
||||
// TODO: Untested
|
||||
if (inIndex >= txTmp.outs.length) {
|
||||
throw new Error("Transaction.hashForSignature(): SIGHASH_SINGLE " +
|
||||
"no corresponding txout found - out of bounds");
|
||||
}
|
||||
outsLen = inIndex + 1;
|
||||
} else {
|
||||
outsLen = this.outs.length;
|
||||
}
|
||||
|
||||
// TODO: If hashTypeMode !== SIGHASH_SINGLE, we could memcpy this whole
|
||||
// section from the original transaction as is.
|
||||
bytes.varint(outsLen);
|
||||
for (var i = 0; i < outsLen; i++) {
|
||||
if (hashTypeMode === SIGHASH_SINGLE && i !== inIndex) {
|
||||
// Zero all outs except the one we want to keep
|
||||
bytes.put(util.INT64_MAX);
|
||||
bytes.varint(0);
|
||||
} else {
|
||||
bytes.put(this.outs[i].v);
|
||||
bytes.varint(this.outs[i].s.length);
|
||||
bytes.put(this.outs[i].s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bytes.word32le(this.lock_time);
|
||||
|
||||
var buffer = bytes.buffer();
|
||||
|
||||
// Append hashType
|
||||
buffer = buffer.concat(new Buffer([parseInt(hashType), 0, 0, 0]));
|
||||
|
||||
return util.twoSha256(buffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object with the same field names as jgarzik's getblock patch.
|
||||
*/
|
||||
Transaction.prototype.getStandardizedObject = function getStandardizedObject() {
|
||||
var tx = {
|
||||
hash: util.formatHashFull(this.getHash()),
|
||||
version: this.version,
|
||||
lock_time: this.lock_time
|
||||
};
|
||||
|
||||
var totalSize = 8; // version + lock_time
|
||||
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
|
||||
var ins = this.ins.map(function (txin) {
|
||||
var txinObj = {
|
||||
prev_out: {
|
||||
hash: new Buffer(txin.getOutpointHash()).reverse().toString(hex),
|
||||
n: txin.getOutpointIndex()
|
||||
}
|
||||
};
|
||||
if (txin.isCoinBase()) {
|
||||
txinObj.coinbase = txin.s.toString('hex');
|
||||
} else {
|
||||
txinObj.scriptSig = new Script(txin.s).getStringContent(false, 0);
|
||||
}
|
||||
totalSize += 36 + util.getVarIntSize(txin.s.length) +
|
||||
txin.s.length + 4; // outpoint + script_len + script + sequence
|
||||
return txinObj;
|
||||
});
|
||||
|
||||
totalSize += util.getVarIntSize(this.outs.length);
|
||||
var outs = this.outs.map(function (txout) {
|
||||
totalSize += util.getVarIntSize(txout.s.length) +
|
||||
txout.s.length + 8; // script_len + script + value
|
||||
return {
|
||||
value: util.formatValue(txout.v),
|
||||
scriptPubKey: new Script(txout.s).getStringContent(false, 0)
|
||||
};
|
||||
});
|
||||
|
||||
tx.size = totalSize;
|
||||
|
||||
tx["in"] = ins;
|
||||
tx["out"] = outs;
|
||||
|
||||
return tx;
|
||||
};
|
||||
|
||||
// Add some Mongoose compatibility functions to the plain object
|
||||
Transaction.prototype.toObject = function toObject() {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
var TransactionInputsCache = exports.TransactionInputsCache =
|
||||
function TransactionInputsCache(tx)
|
||||
{
|
||||
var txList = [];
|
||||
var txList64 = [];
|
||||
var reqOuts = {};
|
||||
|
||||
// Get list of transactions required for verification
|
||||
tx.ins.forEach(function (txin) {
|
||||
if (txin.isCoinBase()) return;
|
||||
|
||||
var hash = txin.o.slice(0, 32);
|
||||
var hash64 = hash.toString('base64');
|
||||
if (txList64.indexOf(hash64) == -1) {
|
||||
txList.push(hash);
|
||||
txList64.push(hash64);
|
||||
}
|
||||
if (!reqOuts[hash64]) {
|
||||
reqOuts[hash64] = [];
|
||||
}
|
||||
reqOuts[hash64][txin.getOutpointIndex()] = true;
|
||||
});
|
||||
|
||||
this.tx = tx;
|
||||
this.txList = txList;
|
||||
this.txList64 = txList64;
|
||||
this.txIndex = {};
|
||||
this.requiredOuts = reqOuts;
|
||||
this.callbacks = [];
|
||||
};
|
||||
|
||||
TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
var complete = false;
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
self.callbacks.push(callback);
|
||||
}
|
||||
|
||||
var missingTx = {};
|
||||
self.txList64.forEach(function (hash64) {
|
||||
missingTx[hash64] = true;
|
||||
});
|
||||
|
||||
// A utility function to create the index object from the txs result lists
|
||||
function indexTxs(err, txs) {
|
||||
if (err) throw err;
|
||||
|
||||
// Index memory transactions
|
||||
txs.forEach(function (tx) {
|
||||
var hash64 = tx.getHash().toString('base64');
|
||||
var obj = {};
|
||||
Object.keys(self.requiredOuts[hash64]).forEach(function (o) {
|
||||
obj[+o] = tx.outs[+o];
|
||||
});
|
||||
self.txIndex[hash64] = obj;
|
||||
delete missingTx[hash64];
|
||||
});
|
||||
|
||||
this(null);
|
||||
};
|
||||
|
||||
Step(
|
||||
// First find and index memory transactions (if a txStore was provided)
|
||||
function findMemTx() {
|
||||
if (txStore) {
|
||||
txStore.find(self.txList64, this);
|
||||
} else {
|
||||
this(null, []);
|
||||
}
|
||||
},
|
||||
indexTxs,
|
||||
// Second find and index persistent transactions
|
||||
function findBlockChainTx(err) {
|
||||
if (err) throw err;
|
||||
|
||||
// TODO: Major speedup should be possible if we load only the outs and not
|
||||
// whole transactions.
|
||||
var callback = this;
|
||||
blockChain.getOutputsByHashes(self.txList, function (err, result) {
|
||||
callback(err, result);
|
||||
});
|
||||
},
|
||||
indexTxs,
|
||||
function saveTxCache(err) {
|
||||
if (err) throw err;
|
||||
|
||||
var missingTxDbg = '';
|
||||
if (Object.keys(missingTx).length) {
|
||||
missingTxDbg = Object.keys(missingTx).map(function (hash64) {
|
||||
return util.formatHash(new Buffer(hash64, 'base64'));
|
||||
}).join(',');
|
||||
}
|
||||
|
||||
if (wait && Object.keys(missingTx).length) {
|
||||
// TODO: This might no longer be needed now that saveTransactions uses
|
||||
// the safe=true option.
|
||||
setTimeout(function () {
|
||||
var missingHashes = Object.keys(missingTx);
|
||||
if (missingHashes.length) {
|
||||
self.callback(new Error('Missing inputs (timeout while searching): '
|
||||
+ missingTxDbg));
|
||||
} else if (!complete) {
|
||||
self.callback(new Error('Callback failed to trigger'));
|
||||
}
|
||||
}, 10000);
|
||||
} else {
|
||||
complete = true;
|
||||
this(null, self);
|
||||
}
|
||||
},
|
||||
self.callback.bind(self)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
TransactionInputsCache.prototype.callback = function callback(err)
|
||||
{
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
|
||||
// Empty the callback array first (because downstream functions could add new
|
||||
// callbacks or otherwise interfere if were not in a consistent state.)
|
||||
var cbs = this.callbacks;
|
||||
this.callbacks = [];
|
||||
|
||||
try {
|
||||
cbs.forEach(function (cb) {
|
||||
cb.apply(null, args);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Callback error after connecting tx inputs: "+
|
||||
(err.stack ? err.stack : err.toString()));
|
||||
}
|
||||
};
|
||||
|
||||
return Transaction;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
'variables': {
|
||||
'node_shared_openssl%': 'true'
|
||||
},
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'eckey',
|
||||
'sources': [
|
||||
'src/eckey.cc'
|
||||
],
|
||||
'conditions': [
|
||||
['node_shared_openssl=="false"', {
|
||||
# so when "node_shared_openssl" is "false", then OpenSSL has been
|
||||
# bundled into the node executable. So we need to include the same
|
||||
# header files that were used when building node.
|
||||
'include_dirs': [
|
||||
'<(node_root_dir)/deps/openssl/openssl/include'
|
||||
],
|
||||
"conditions" : [
|
||||
["target_arch=='ia32'", {
|
||||
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/piii" ]
|
||||
}],
|
||||
["target_arch=='x64'", {
|
||||
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/k8" ]
|
||||
}],
|
||||
["target_arch=='arm'", {
|
||||
"include_dirs": [ "<(node_root_dir)/deps/openssl/config/arm" ]
|
||||
}]
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
var Put = require('bufferput');
|
||||
var hex = function(hex) {return new Buffer(hex, 'hex');};
|
||||
|
||||
exports.livenet = {
|
||||
name: 'livenet',
|
||||
addressVersion: 0x00,
|
||||
magic: hex('f9beb4d9'),
|
||||
genesisBlock: {
|
||||
height: 0,
|
||||
nonce: 2083236893,
|
||||
version: 1,
|
||||
hash: hex('6FE28C0AB6F1B372C1A6A246AE63F74F931E8365E15A089C68D6190000000000'),
|
||||
prev_hash: new Buffer(32).fill(0),
|
||||
timestamp: 1231006505,
|
||||
merkle_root: hex('3BA3EDFD7A7B12B27AC72C3E67768F617FC81BC3888A51323A9FB8AA4B1E5E4A'),
|
||||
bits: 486604799
|
||||
},
|
||||
genesisBlockTx: {
|
||||
'outs': [{
|
||||
'v': hex('00F2052A01000000'), // 50 BTC
|
||||
's': new Put()
|
||||
.word8(65) // 65 bytes of data follow
|
||||
.put(hex('04678AFDB0FE5548271967F1A67130B7105CD6A828E03909A67962E0EA1F61DEB649F6BC3F4CEF38C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5F'))
|
||||
.word8(0xAC) // OP_CHECKSIG
|
||||
.buffer()
|
||||
}],
|
||||
'lock_time': 0,
|
||||
'version': 1,
|
||||
'hash': hex('3BA3EDFD7A7B12B27AC72C3E67768F617FC81BC3888A51323A9FB8AA4B1E5E4A'),
|
||||
'ins': [{
|
||||
'q': 0xFFFFFFFF,
|
||||
'o': hex("0000000000000000000000000000000000000000000000000000000000000000FFFFFFFF"),
|
||||
's': new Put()
|
||||
.put(hex('04FFFF001D010445'))
|
||||
.put(new Buffer('The Times 03/Jan/2009 Chancellor on brink of ' +
|
||||
'second bailout for banks', 'ascii'))
|
||||
.buffer()
|
||||
}]
|
||||
},
|
||||
proofOfWorkLimit: hex("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
|
||||
checkpoints: [], // need to put checkpoint blocks here
|
||||
};
|
||||
|
||||
exports.testnet = {
|
||||
name: 'testnet',
|
||||
addressVersion: 0x6f,
|
||||
magic: hex('0b110907'),
|
||||
genesisBlock: {
|
||||
height: 0,
|
||||
nonce: 414098458,
|
||||
version: 1,
|
||||
hash: hex('43497FD7F826957108F4A30FD9CEC3AEBA79972084E90EAD01EA330900000000'),
|
||||
prev_hash: new Buffer(32).fill(0),
|
||||
timestamp: 1296688602,
|
||||
merkle_root: hex('3BA3EDFD7A7B12B27AC72C3E67768F617FC81BC3888A51323A9FB8AA4B1E5E4A'),
|
||||
bits: 486604799,
|
||||
},
|
||||
genesisBlockTx: module.exports.livenet.genesisBlockTx,
|
||||
proofOfWorkLimit: module.exports.livenet.proofOfWorkLimit,
|
||||
checkpoints: [], // need to put checkput blocks here
|
||||
};
|
|
@ -6,6 +6,10 @@
|
|||
"name": "Stephen Pair",
|
||||
"email": "stephen@bitpay.com"
|
||||
},
|
||||
"contributors": [
|
||||
{"name": "Stefan Thomas", "email": "moon@justmoon.net"},
|
||||
{"name": "Jeff Garzik", "email": "jgarzik@bitpay.com"}
|
||||
],
|
||||
"main": "./index",
|
||||
"keywords": [
|
||||
"bitcoin",
|
||||
|
@ -22,9 +26,10 @@
|
|||
"scripts": {},
|
||||
"dependencies": {
|
||||
"classtool": ">=1.0.0",
|
||||
"base58-native": ">=0.1.1"
|
||||
"base58-native": ">=0.1.1",
|
||||
"bindings": "1.1.0",
|
||||
"bufferput": ">=0.1.1"
|
||||
//"bignum"
|
||||
//"put"
|
||||
//"binary"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef LIBCOIN_SERVER_INCLUDE_COMMON_H_
|
||||
#define LIBCOIN_SERVER_INCLUDE_COMMON_H_
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
#define REQ_FUN_ARG(I, VAR) \
|
||||
if (args.Length() <= (I) || !args[I]->IsFunction()) \
|
||||
return v8::ThrowException(v8::Exception::TypeError( \
|
||||
v8::String::New("Argument " #I " must be a function"))); \
|
||||
v8::Local<v8::Function> VAR = v8::Local<v8::Function>::Cast(args[I]);
|
||||
|
||||
static v8::Handle<v8::Value> VException(const char *msg) {
|
||||
v8::HandleScope scope;
|
||||
return v8::ThrowException(v8::Exception::Error(v8::String::New(msg)));
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,575 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
#include <node_internals.h>
|
||||
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "eckey.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace v8;
|
||||
using namespace node;
|
||||
|
||||
int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key)
|
||||
{
|
||||
int ok = 0;
|
||||
BN_CTX *ctx = NULL;
|
||||
EC_POINT *pub_key = NULL;
|
||||
|
||||
if (!eckey) return 0;
|
||||
|
||||
const EC_GROUP *group = EC_KEY_get0_group(eckey);
|
||||
|
||||
if ((ctx = BN_CTX_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
pub_key = EC_POINT_new(group);
|
||||
|
||||
if (pub_key == NULL)
|
||||
goto err;
|
||||
|
||||
if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx))
|
||||
goto err;
|
||||
|
||||
EC_KEY_set_private_key(eckey,priv_key);
|
||||
EC_KEY_set_public_key(eckey,pub_key);
|
||||
|
||||
ok = 1;
|
||||
|
||||
err:
|
||||
|
||||
if (pub_key)
|
||||
EC_POINT_free(pub_key);
|
||||
if (ctx != NULL)
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
return(ok);
|
||||
}
|
||||
|
||||
namespace bitcoin {
|
||||
|
||||
void Key::Generate()
|
||||
{
|
||||
if (!EC_KEY_generate_key(ec)) {
|
||||
lastError = "Error from EC_KEY_generate_key";
|
||||
return;
|
||||
}
|
||||
|
||||
hasPublic = true;
|
||||
hasPrivate = true;
|
||||
}
|
||||
|
||||
int Key::VerifySignature(const unsigned char *digest, int digest_len,
|
||||
const unsigned char *sig, int sig_len)
|
||||
{
|
||||
return ECDSA_verify(0, digest, digest_len, sig, sig_len, ec);
|
||||
}
|
||||
|
||||
void Key::EIO_VerifySignature(uv_work_t *req)
|
||||
{
|
||||
verify_sig_baton_t *b = static_cast<verify_sig_baton_t *>(req->data);
|
||||
|
||||
b->result = b->key->VerifySignature(
|
||||
b->digest, b->digestLen,
|
||||
b->sig, b->sigLen
|
||||
);
|
||||
}
|
||||
|
||||
ECDSA_SIG *Key::Sign(const unsigned char *digest, int digest_len)
|
||||
{
|
||||
ECDSA_SIG *sig;
|
||||
|
||||
sig = ECDSA_do_sign(digest, digest_len, ec);
|
||||
if (sig == NULL) {
|
||||
// TODO: ERROR
|
||||
}
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
void Key::Init(Handle<Object> target)
|
||||
{
|
||||
HandleScope scope;
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
||||
|
||||
s_ct = Persistent<FunctionTemplate>::New(t);
|
||||
s_ct->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
s_ct->SetClassName(String::NewSymbol("Key"));
|
||||
|
||||
// Accessors
|
||||
s_ct->InstanceTemplate()->SetAccessor(String::New("private"),
|
||||
GetPrivate, SetPrivate);
|
||||
s_ct->InstanceTemplate()->SetAccessor(String::New("public"),
|
||||
GetPublic, SetPublic);
|
||||
|
||||
// Methods
|
||||
NODE_SET_PROTOTYPE_METHOD(s_ct, "verifySignature", VerifySignature);
|
||||
NODE_SET_PROTOTYPE_METHOD(s_ct, "verifySignatureSync", VerifySignatureSync);
|
||||
NODE_SET_PROTOTYPE_METHOD(s_ct, "regenerateSync", RegenerateSync);
|
||||
NODE_SET_PROTOTYPE_METHOD(s_ct, "toDER", ToDER);
|
||||
NODE_SET_PROTOTYPE_METHOD(s_ct, "signSync", SignSync);
|
||||
|
||||
// Static methods
|
||||
NODE_SET_METHOD(s_ct->GetFunction(), "generateSync", GenerateSync);
|
||||
NODE_SET_METHOD(s_ct->GetFunction(), "fromDER", FromDER);
|
||||
|
||||
target->Set(String::NewSymbol("Key"),
|
||||
s_ct->GetFunction());
|
||||
}
|
||||
|
||||
Key::Key() :
|
||||
lastError(NULL),
|
||||
hasPrivate(false),
|
||||
hasPublic(false)
|
||||
{
|
||||
ec = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (ec == NULL) {
|
||||
lastError = "Error from EC_KEY_new_by_curve_name";
|
||||
}
|
||||
}
|
||||
|
||||
Key::~Key()
|
||||
{
|
||||
EC_KEY_free(ec);
|
||||
}
|
||||
|
||||
Key*
|
||||
Key::New()
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
Local<Object> k = s_ct->GetFunction()->NewInstance(0, NULL);
|
||||
if (k.IsEmpty()) return NULL;
|
||||
|
||||
return ObjectWrap::Unwrap<Key>(k);
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::New(const Arguments& args)
|
||||
{
|
||||
if (!args.IsConstructCall()) {
|
||||
return FromConstructorTemplate(s_ct, args);
|
||||
}
|
||||
|
||||
HandleScope scope;
|
||||
|
||||
Key* key = new Key();
|
||||
if (key->lastError != NULL) {
|
||||
return VException(key->lastError);
|
||||
}
|
||||
|
||||
key->Wrap(args.Holder());
|
||||
|
||||
return scope.Close(args.This());
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::GenerateSync(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
Key* key = Key::New();
|
||||
|
||||
key->Generate();
|
||||
|
||||
if (key->lastError != NULL) {
|
||||
return VException(key->lastError);
|
||||
}
|
||||
|
||||
return scope.Close(key->handle_);
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::GetPrivate(Local<String> property, const AccessorInfo& info)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder());
|
||||
|
||||
if (!key->hasPrivate) {
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
const BIGNUM *bn = EC_KEY_get0_private_key(key->ec);
|
||||
int priv_size = BN_num_bytes(bn);
|
||||
|
||||
if (bn == NULL) {
|
||||
// TODO: ERROR: "Error from EC_KEY_get0_private_key(pkey)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
if (priv_size > 32) {
|
||||
// TODO: ERROR: "Secret too large (Incorrect curve parameters?)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
unsigned char *priv = (unsigned char *)malloc(32);
|
||||
|
||||
int n = BN_bn2bin(bn, &priv[32 - priv_size]);
|
||||
|
||||
if (n != priv_size) {
|
||||
// TODO: ERROR: "Error from BN_bn2bin(bn, &priv[32 - priv_size])"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
Buffer *priv_buf = Buffer::New(32);
|
||||
memcpy(Buffer::Data(priv_buf), priv, 32);
|
||||
|
||||
free(priv);
|
||||
|
||||
return scope.Close(priv_buf->handle_);
|
||||
}
|
||||
|
||||
void
|
||||
Key::SetPrivate(Local<String> property, Local<Value> value, const AccessorInfo& info)
|
||||
{
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder());
|
||||
Handle<Object> buffer = value->ToObject();
|
||||
const unsigned char *data = (const unsigned char*) Buffer::Data(buffer);
|
||||
|
||||
BIGNUM *bn = BN_bin2bn(data,Buffer::Length(buffer),BN_new());
|
||||
EC_KEY_set_private_key(key->ec, bn);
|
||||
BN_clear_free(bn);
|
||||
|
||||
key->hasPrivate = true;
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::GetPublic(Local<String> property, const AccessorInfo& info)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder());
|
||||
|
||||
if (!key->hasPublic) {
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
// Export public
|
||||
int pub_size = i2o_ECPublicKey(key->ec, NULL);
|
||||
if (!pub_size) {
|
||||
// TODO: ERROR: "Error from i2o_ECPublicKey(key->ec, NULL)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
unsigned char *pub_begin, *pub_end;
|
||||
pub_begin = pub_end = (unsigned char *)malloc(pub_size);
|
||||
|
||||
if (i2o_ECPublicKey(key->ec, &pub_end) != pub_size) {
|
||||
// TODO: ERROR: "Error from i2o_ECPublicKey(key->ec, &pub)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
Buffer *pub_buf = Buffer::New(pub_size);
|
||||
memcpy(Buffer::Data(pub_buf), pub_begin, pub_size);
|
||||
|
||||
free(pub_begin);
|
||||
|
||||
return scope.Close(pub_buf->handle_);
|
||||
}
|
||||
|
||||
void
|
||||
Key::SetPublic(Local<String> property, Local<Value> value, const AccessorInfo& info)
|
||||
{
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder());
|
||||
Handle<Object> buffer = value->ToObject();
|
||||
const unsigned char *data = (const unsigned char*) Buffer::Data(buffer);
|
||||
|
||||
if (!o2i_ECPublicKey(&(key->ec), &data, Buffer::Length(buffer))) {
|
||||
// TODO: Error
|
||||
return;
|
||||
}
|
||||
|
||||
key->hasPublic = true;
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::RegenerateSync(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(args.This());
|
||||
|
||||
if (!key->hasPrivate) {
|
||||
return VException("Regeneration requires a private key.");
|
||||
}
|
||||
|
||||
EC_KEY *old = key->ec;
|
||||
|
||||
key->ec = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (EC_KEY_regenerate_key(key->ec, EC_KEY_get0_private_key(old)) == 1) {
|
||||
key->hasPublic = true;
|
||||
}
|
||||
|
||||
EC_KEY_free(old);
|
||||
|
||||
return scope.Close(Undefined());
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::ToDER(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(args.This());
|
||||
|
||||
if (!key->hasPrivate || !key->hasPublic) {
|
||||
return scope.Close(Null());
|
||||
}
|
||||
|
||||
// Export DER
|
||||
int der_size = i2d_ECPrivateKey(key->ec, NULL);
|
||||
if (!der_size) {
|
||||
// TODO: ERROR: "Error from i2d_ECPrivateKey(key->ec, NULL)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
unsigned char *der_begin, *der_end;
|
||||
der_begin = der_end = (unsigned char *)malloc(der_size);
|
||||
|
||||
if (i2d_ECPrivateKey(key->ec, &der_end) != der_size) {
|
||||
// TODO: ERROR: "Error from i2d_ECPrivateKey(key->ec, &der_end)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
Buffer *der_buf = Buffer::New(der_size);
|
||||
memcpy(Buffer::Data(der_buf), der_begin, der_size);
|
||||
|
||||
free(der_begin);
|
||||
|
||||
return scope.Close(der_buf->handle_);
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::FromDER(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return VException("One argument expected: der");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[0])) {
|
||||
return VException("Argument 'der' must be of type Buffer");
|
||||
}
|
||||
|
||||
Key* key = new Key();
|
||||
if (key->lastError != NULL) {
|
||||
return VException(key->lastError);
|
||||
}
|
||||
|
||||
Handle<Object> der_buf = args[0]->ToObject();
|
||||
const unsigned char *data = (const unsigned char*) Buffer::Data(der_buf);
|
||||
|
||||
if (!d2i_ECPrivateKey(&(key->ec), &data, Buffer::Length(der_buf))) {
|
||||
return VException("Error from d2i_ECPrivateKey(&key, &data, len)");
|
||||
}
|
||||
|
||||
key->hasPrivate = true;
|
||||
key->hasPublic = true;
|
||||
|
||||
Handle<Function> cons = s_ct->GetFunction();
|
||||
Handle<Value> external = External::New(key);
|
||||
Handle<Value> result = cons->NewInstance(1, &external);
|
||||
|
||||
return scope.Close(result);
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::VerifySignature(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(args.This());
|
||||
|
||||
if (args.Length() != 3) {
|
||||
return VException("Three arguments expected: hash, sig, callback");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[0])) {
|
||||
return VException("Argument 'hash' must be of type Buffer");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[1])) {
|
||||
return VException("Argument 'sig' must be of type Buffer");
|
||||
}
|
||||
REQ_FUN_ARG(2, cb);
|
||||
if (!key->hasPublic) {
|
||||
return VException("Key does not have a public key set");
|
||||
}
|
||||
|
||||
Handle<Object> hash_buf = args[0]->ToObject();
|
||||
Handle<Object> sig_buf = args[1]->ToObject();
|
||||
|
||||
if (Buffer::Length(hash_buf) != 32) {
|
||||
return VException("Argument 'hash' must be Buffer of length 32 bytes");
|
||||
}
|
||||
|
||||
verify_sig_baton_t *baton = new verify_sig_baton_t();
|
||||
baton->key = key;
|
||||
baton->digest = (unsigned char *)Buffer::Data(hash_buf);
|
||||
baton->digestLen = Buffer::Length(hash_buf);
|
||||
baton->digestBuf = Persistent<Object>::New(hash_buf);
|
||||
baton->sig = (unsigned char *)Buffer::Data(sig_buf);
|
||||
baton->sigLen = Buffer::Length(sig_buf);
|
||||
baton->sigBuf = Persistent<Object>::New(sig_buf);
|
||||
baton->result = -1;
|
||||
baton->cb = Persistent<Function>::New(cb);
|
||||
|
||||
key->Ref();
|
||||
|
||||
uv_work_t *req = new uv_work_t;
|
||||
req->data = baton;
|
||||
|
||||
uv_queue_work(uv_default_loop(), req, EIO_VerifySignature, VerifySignatureCallback);
|
||||
|
||||
return scope.Close(Undefined());
|
||||
}
|
||||
|
||||
void
|
||||
Key::VerifySignatureCallback(uv_work_t *req, int status)
|
||||
{
|
||||
HandleScope scope;
|
||||
verify_sig_baton_t *baton = static_cast<verify_sig_baton_t *>(req->data);
|
||||
|
||||
baton->key->Unref();
|
||||
baton->digestBuf.Dispose();
|
||||
baton->sigBuf.Dispose();
|
||||
|
||||
Local<Value> argv[2];
|
||||
|
||||
argv[0] = Local<Value>::New(Null());
|
||||
argv[1] = Local<Value>::New(Null());
|
||||
if (baton->result == -1) {
|
||||
argv[0] = Exception::TypeError(String::New("Error during ECDSA_verify"));
|
||||
} else if (baton->result == 0) {
|
||||
// Signature invalid
|
||||
argv[1] = Local<Value>::New(Boolean::New(false));
|
||||
} else if (baton->result == 1) {
|
||||
// Signature valid
|
||||
argv[1] = Local<Value>::New(Boolean::New(true));
|
||||
} else {
|
||||
argv[0] = Exception::TypeError(
|
||||
String::New("ECDSA_verify gave undefined return value"));
|
||||
}
|
||||
|
||||
TryCatch try_catch;
|
||||
|
||||
baton->cb->Call(Context::GetCurrent()->Global(), 2, argv);
|
||||
|
||||
|
||||
baton->cb.Dispose();
|
||||
|
||||
delete baton;
|
||||
delete req;
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
FatalException(try_catch);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::VerifySignatureSync(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(args.This());
|
||||
|
||||
if (args.Length() != 2) {
|
||||
return VException("Two arguments expected: hash, sig");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[0])) {
|
||||
return VException("Argument 'hash' must be of type Buffer");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[1])) {
|
||||
return VException("Argument 'sig' must be of type Buffer");
|
||||
}
|
||||
if (!key->hasPublic) {
|
||||
return VException("Key does not have a public key set");
|
||||
}
|
||||
|
||||
Handle<Object> hash_buf = args[0]->ToObject();
|
||||
Handle<Object> sig_buf = args[1]->ToObject();
|
||||
|
||||
const unsigned char *hash_data = (unsigned char *) Buffer::Data(hash_buf);
|
||||
const unsigned char *sig_data = (unsigned char *) Buffer::Data(sig_buf);
|
||||
|
||||
unsigned int hash_len = Buffer::Length(hash_buf);
|
||||
unsigned int sig_len = Buffer::Length(sig_buf);
|
||||
|
||||
if (hash_len != 32) {
|
||||
return VException("Argument 'hash' must be Buffer of length 32 bytes");
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
int result = key->VerifySignature(hash_data, hash_len, sig_data, sig_len);
|
||||
|
||||
if (result == -1) {
|
||||
return VException("Error during ECDSA_verify");
|
||||
} else if (result == 0) {
|
||||
// Signature invalid
|
||||
return scope.Close(Boolean::New(false));
|
||||
} else if (result == 1) {
|
||||
// Signature valid
|
||||
return scope.Close(Boolean::New(true));
|
||||
} else {
|
||||
return VException("ECDSA_verify gave undefined return value");
|
||||
}
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
Key::SignSync(const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
Key* key = node::ObjectWrap::Unwrap<Key>(args.This());
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return VException("One argument expected: hash");
|
||||
}
|
||||
if (!Buffer::HasInstance(args[0])) {
|
||||
return VException("Argument 'hash' must be of type Buffer");
|
||||
}
|
||||
if (!key->hasPrivate) {
|
||||
return VException("Key does not have a private key set");
|
||||
}
|
||||
|
||||
Handle<Object> hash_buf = args[0]->ToObject();
|
||||
|
||||
const unsigned char *hash_data = (unsigned char *) Buffer::Data(hash_buf);
|
||||
|
||||
unsigned int hash_len = Buffer::Length(hash_buf);
|
||||
|
||||
if (hash_len != 32) {
|
||||
return VException("Argument 'hash' must be Buffer of length 32 bytes");
|
||||
}
|
||||
|
||||
// Create signature
|
||||
ECDSA_SIG *sig = key->Sign(hash_data, hash_len);
|
||||
|
||||
// Export DER
|
||||
int der_size = i2d_ECDSA_SIG(sig, NULL);
|
||||
if (!der_size) {
|
||||
// TODO: ERROR: "Error from i2d_ECPrivateKey(key->ec, NULL)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
unsigned char *der_begin, *der_end;
|
||||
der_begin = der_end = (unsigned char *)malloc(der_size);
|
||||
|
||||
if (i2d_ECDSA_SIG(sig, &der_end) != der_size) {
|
||||
// TODO: ERROR: "Error from i2d_ECPrivateKey(key->ec, &der_end)"
|
||||
return scope.Close(Null());
|
||||
}
|
||||
Buffer *der_buf = Buffer::New(der_size);
|
||||
memcpy(Buffer::Data(der_buf), der_begin, der_size);
|
||||
|
||||
free(der_begin);
|
||||
ECDSA_SIG_free(sig);
|
||||
|
||||
return scope.Close(der_buf->handle_);
|
||||
}
|
||||
|
||||
Persistent<FunctionTemplate> Key::s_ct;
|
||||
|
||||
}; // namespace bitcoin
|
||||
|
||||
extern "C" void
|
||||
init (Handle<Object> target)
|
||||
{
|
||||
bitcoin::Key::Init(target);
|
||||
}
|
||||
|
||||
NODE_MODULE(native, init)
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef LIBCOIN_SERVER_INCLUDE_ECKEY_H_
|
||||
#define LIBCOIN_SERVER_INCLUDE_ECKEY_H_
|
||||
|
||||
#include <v8.h>
|
||||
#include <node.h>
|
||||
|
||||
using namespace v8;
|
||||
using namespace node;
|
||||
|
||||
namespace bitcoin {
|
||||
|
||||
class Key : ObjectWrap
|
||||
{
|
||||
private:
|
||||
|
||||
const char *lastError;
|
||||
EC_KEY *ec;
|
||||
|
||||
bool hasPrivate;
|
||||
bool hasPublic;
|
||||
|
||||
void Generate();
|
||||
|
||||
struct verify_sig_baton_t {
|
||||
// Parameters
|
||||
Key *key;
|
||||
const unsigned char *digest;
|
||||
const unsigned char *sig;
|
||||
int digestLen;
|
||||
int sigLen;
|
||||
Persistent<Object> digestBuf;
|
||||
Persistent<Object> sigBuf;
|
||||
|
||||
// Result
|
||||
// -1 = error, 0 = bad sig, 1 = good
|
||||
int result;
|
||||
Persistent<Function> cb;
|
||||
};
|
||||
|
||||
int VerifySignature(const unsigned char *digest, int digest_len,
|
||||
const unsigned char *sig, int sig_len);
|
||||
|
||||
static void EIO_VerifySignature(uv_work_t *req);
|
||||
|
||||
ECDSA_SIG *Sign(const unsigned char *digest, int digest_len);
|
||||
|
||||
public:
|
||||
|
||||
static Persistent<FunctionTemplate> s_ct;
|
||||
|
||||
static void Init(Handle<Object> target);
|
||||
|
||||
Key();
|
||||
~Key();
|
||||
|
||||
static Key* New();
|
||||
|
||||
static Handle<Value> New(const Arguments& args);
|
||||
static Handle<Value> GenerateSync(const Arguments& args);
|
||||
|
||||
static Handle<Value>
|
||||
GetPrivate(Local<String> property, const AccessorInfo& info);
|
||||
|
||||
static void
|
||||
SetPrivate(Local<String> property, Local<Value> value, const AccessorInfo& info);
|
||||
|
||||
static Handle<Value>
|
||||
GetPublic(Local<String> property, const AccessorInfo& info);
|
||||
|
||||
static void
|
||||
SetPublic(Local<String> property, Local<Value> value, const AccessorInfo& info);
|
||||
|
||||
static Handle<Value>
|
||||
RegenerateSync(const Arguments& args);
|
||||
|
||||
static Handle<Value>
|
||||
ToDER(const Arguments& args);
|
||||
|
||||
static Handle<Value>
|
||||
FromDER(const Arguments& args);
|
||||
|
||||
static Handle<Value>
|
||||
VerifySignature(const Arguments& args);
|
||||
|
||||
static void
|
||||
VerifySignatureCallback(uv_work_t *req, int status);
|
||||
|
||||
static Handle<Value>
|
||||
VerifySignatureSync(const Arguments& args);
|
||||
|
||||
static Handle<Value>
|
||||
SignSync(const Arguments& args);
|
||||
};
|
||||
|
||||
}; // namespace bitcoin
|
||||
|
||||
#endif
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Simple synchronous parser based on node-binary.
|
||||
*/
|
||||
|
||||
function spec(b) {
|
||||
function Parser(buffer)
|
||||
{
|
||||
this.subject = buffer;
|
||||
this.pos = 0;
|
||||
};
|
||||
|
||||
Parser.prototype.buffer = function buffer(len) {
|
||||
var buf = this.subject.slice(this.pos, this.pos+len);
|
||||
this.pos += len;
|
||||
return buf;
|
||||
};
|
||||
|
||||
Parser.prototype.search = function search(needle) {
|
||||
var len;
|
||||
|
||||
if ("string" === typeof needle || Buffer.isBuffer(needle)) {
|
||||
// TODO: Slicing is probably too slow
|
||||
len = this.subject.slice(this.pos).indexOf(needle);
|
||||
if (len !== -1) {
|
||||
this.pos += len + needle.length;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
if ("number" === typeof needle) {
|
||||
needle = needle & 0xff;
|
||||
// Search for single byte
|
||||
for (var i = this.pos, l = this.subject.length; i < l; i++) {
|
||||
if (this.subject[i] == needle) {
|
||||
len = i - this.pos;
|
||||
this.pos = i+1;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Like search(), but returns the skipped bytes
|
||||
*/
|
||||
Parser.prototype.scan = function scan(needle) {
|
||||
var startPos = this.pos;
|
||||
var len = this.search(needle);
|
||||
if (len !== -1) {
|
||||
return this.subject.slice(startPos, startPos+len);
|
||||
} else {
|
||||
throw new Error('No match');
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.eof = function eof() {
|
||||
return this.pos >= this.subject.length;
|
||||
};
|
||||
|
||||
// convert byte strings to unsigned little endian numbers
|
||||
function decodeLEu (bytes) {
|
||||
var acc = 0;
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
acc += Math.pow(256,i) * bytes[i];
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// convert byte strings to unsigned big endian numbers
|
||||
function decodeBEu (bytes) {
|
||||
var acc = 0;
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
acc += Math.pow(256, bytes.length - i - 1) * bytes[i];
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// convert byte strings to signed big endian numbers
|
||||
function decodeBEs (bytes) {
|
||||
var val = decodeBEu(bytes);
|
||||
if ((bytes[0] & 0x80) == 0x80) {
|
||||
val -= Math.pow(256, bytes.length);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// convert byte strings to signed little endian numbers
|
||||
function decodeLEs (bytes) {
|
||||
var val = decodeLEu(bytes);
|
||||
if ((bytes[bytes.length - 1] & 0x80) == 0x80) {
|
||||
val -= Math.pow(256, bytes.length);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function getDecoder(len, fn) {
|
||||
return function () {
|
||||
var buf = this.buffer(len);
|
||||
return fn(buf);
|
||||
};
|
||||
};
|
||||
[ 1, 2, 4, 8 ].forEach(function (bytes) {
|
||||
var bits = bytes * 8;
|
||||
|
||||
Parser.prototype['word' + bits + 'le']
|
||||
= Parser.prototype['word' + bits + 'lu']
|
||||
= getDecoder(bytes, decodeLEu);
|
||||
|
||||
Parser.prototype['word' + bits + 'ls']
|
||||
= getDecoder(bytes, decodeLEs);
|
||||
|
||||
Parser.prototype['word' + bits + 'be']
|
||||
= Parser.prototype['word' + bits + 'bu']
|
||||
= getDecoder(bytes, decodeBEu);
|
||||
|
||||
Parser.prototype['word' + bits + 'bs']
|
||||
= getDecoder(bytes, decodeBEs);
|
||||
|
||||
Parser.prototype.word8 = Parser.prototype.word8u = Parser.prototype.word8be;
|
||||
Parser.prototype.word8s = Parser.prototype.word8bs;
|
||||
});
|
||||
|
||||
return Parser;
|
||||
};
|
||||
module.defineClass(spec);
|
|
@ -0,0 +1,329 @@
|
|||
require('buffertools');
|
||||
var crypto = require('crypto');
|
||||
var bignum = require('bignum');
|
||||
var Binary = require('binary');
|
||||
var Put = require('put');
|
||||
//var logger = require('./logger');
|
||||
|
||||
var ccmodule = require('bindings')('nativetools');
|
||||
|
||||
exports.ccmodule = ccmodule;
|
||||
|
||||
exports.BitcoinKey = ccmodule.BitcoinKey;
|
||||
|
||||
var sha256 = exports.sha256 = function (data) {
|
||||
return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary');
|
||||
};
|
||||
|
||||
var ripe160 = exports.ripe160 = function (data) {
|
||||
return new Buffer(crypto.createHash('rmd160').update(data).digest('binary'), 'binary');
|
||||
};
|
||||
|
||||
var sha1 = exports.sha1 = function (data) {
|
||||
return new Buffer(crypto.createHash('sha1').update(data).digest('binary'), 'binary');
|
||||
};
|
||||
|
||||
var twoSha256 = exports.twoSha256 = function (data) {
|
||||
return sha256(sha256(data));
|
||||
};
|
||||
|
||||
var sha256ripe160 = exports.sha256ripe160 = function (data) {
|
||||
return ripe160(sha256(data));
|
||||
};
|
||||
|
||||
var sha256midstate = exports.sha256midstate = ccmodule.sha256_midstate;
|
||||
|
||||
/**
|
||||
* Format a block hash like the official client does.
|
||||
*/
|
||||
var formatHash = exports.formatHash = function (hash) {
|
||||
// Make a copy, because reverse() and toHex() are destructive.
|
||||
var hashEnd = new Buffer(10);
|
||||
hash.copy(hashEnd, 0, 22, 32);
|
||||
return hashEnd.reverse().toString('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the whole hash, as hex, in correct endian order.
|
||||
*/
|
||||
var formatHashFull = exports.formatHashFull = function (hash) {
|
||||
// Make a copy, because reverse() and toHex() are destructive.
|
||||
var copy = new Buffer(hash.length);
|
||||
hash.copy(copy);
|
||||
var hex = copy.reverse().toHex();
|
||||
return hex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a block hash like Block Explorer does.
|
||||
*
|
||||
* Formats a block hash by removing leading zeros and truncating to 10 characters.
|
||||
*/
|
||||
var formatHashAlt = exports.formatHashAlt = function (hash) {
|
||||
var hex = formatHashFull(hash);
|
||||
hex = hex.replace(/^0*/, '');
|
||||
return hex.substr(0, 10);
|
||||
};
|
||||
|
||||
var formatBuffer = exports.formatBuffer = function (buffer, maxLen) {
|
||||
// Calculate amount of bytes to display
|
||||
if (maxLen === null) {
|
||||
maxLen = 10;
|
||||
}
|
||||
if (maxLen > buffer.length || maxLen === 0) {
|
||||
maxLen = buffer.length;
|
||||
}
|
||||
|
||||
// Copy those bytes into a temporary buffer
|
||||
var temp = new Buffer(maxLen);
|
||||
buffer.copy(temp, 0, 0, maxLen);
|
||||
|
||||
// Format as string
|
||||
var output = temp.toHex();
|
||||
if (temp.length < buffer.length) {
|
||||
output += "...";
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
var valueToBigInt = exports.valueToBigInt = function (valueBuffer) {
|
||||
if (Buffer.isBuffer(valueBuffer)) {
|
||||
return bignum.fromBuffer(valueBuffer, {endian: 'little', size: 8});
|
||||
} else {
|
||||
return valueBuffer;
|
||||
}
|
||||
};
|
||||
|
||||
var bigIntToValue = exports.bigIntToValue = function (valueBigInt) {
|
||||
if (Buffer.isBuffer(valueBigInt)) {
|
||||
return valueBigInt;
|
||||
} else {
|
||||
return valueBigInt.toBuffer({endian: 'little', size: 8});
|
||||
}
|
||||
};
|
||||
|
||||
var formatValue = exports.formatValue = function (valueBuffer) {
|
||||
var value = valueToBigInt(valueBuffer).toString();
|
||||
var integerPart = value.length > 8 ? value.substr(0, value.length-8) : '0';
|
||||
var decimalPart = value.length > 8 ? value.substr(value.length-8) : value;
|
||||
while (decimalPart.length < 8) {
|
||||
decimalPart = "0"+decimalPart;
|
||||
}
|
||||
decimalPart = decimalPart.replace(/0*$/, '');
|
||||
while (decimalPart.length < 2) {
|
||||
decimalPart += "0";
|
||||
}
|
||||
return integerPart+"."+decimalPart;
|
||||
};
|
||||
|
||||
var pubKeyHashToAddress = exports.pubKeyHashToAddress = function (pubKeyHash, addressVersion) {
|
||||
if (!pubKeyHash) return "";
|
||||
|
||||
var put = Put();
|
||||
// Version
|
||||
if(addressVersion) {
|
||||
put.word8le(addressVersion);
|
||||
} else {
|
||||
put.word8le(0);
|
||||
}
|
||||
// Hash
|
||||
put.put(pubKeyHash);
|
||||
// Checksum (four bytes)
|
||||
put.put(twoSha256(put.buffer()).slice(0,4));
|
||||
return encodeBase58(put.buffer());
|
||||
};
|
||||
|
||||
var addressToPubKeyHash = exports.addressToPubKeyHash = function (address) {
|
||||
// Trim
|
||||
address = String(address).replace(/\s/g, '');
|
||||
|
||||
// Check sanity
|
||||
if (!address.match(/^[1-9A-HJ-NP-Za-km-z]{27,35}$/)) {
|
||||
//logger.warn("Not a valid Bitcoin address");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decode
|
||||
var buffer = decodeBase58(address);
|
||||
|
||||
// Parse
|
||||
var parser = Binary.parse(buffer);
|
||||
parser.word8('version');
|
||||
parser.buffer('hash', 20);
|
||||
parser.buffer('checksum', 4);
|
||||
|
||||
// Check checksum
|
||||
var checksum = twoSha256(buffer.slice(0, 21)).slice(0, 4);
|
||||
if (checksum.compare(parser.vars.checksum) !== 0) {
|
||||
//logger.warn("Checksum comparison failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
return parser.vars.hash;
|
||||
};
|
||||
|
||||
// Utility that synchronizes function calls based on a key
|
||||
var createSynchrotron = exports.createSynchrotron = function (fn) {
|
||||
var table = {};
|
||||
return function (key) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var run = function () {
|
||||
// Function fn() will call when it finishes
|
||||
args[0] = function next() {
|
||||
if (table[key]) {
|
||||
if (table[key].length) {
|
||||
table[key].shift()();
|
||||
} else {
|
||||
delete table[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn.apply(null, args);
|
||||
};
|
||||
|
||||
if (!table[key]) {
|
||||
table[key] = [];
|
||||
run();
|
||||
} else {
|
||||
table[key].push(run);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a random 64-bit number.
|
||||
*
|
||||
* With ideas from node-uuid:
|
||||
* Copyright (c) 2010 Robert Kieffer
|
||||
* https://github.com/broofa/node-uuid/
|
||||
*
|
||||
* @returns Buffer random nonce
|
||||
*/
|
||||
var generateNonce = exports.generateNonce = function () {
|
||||
var b32 = 0x100000000, ff = 0xff;
|
||||
var b = new Buffer(8), i = 0;
|
||||
|
||||
// Generate eight random bytes
|
||||
r = Math.random()*b32;
|
||||
b[i++] = r & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
r = Math.random()*b32;
|
||||
b[i++] = r & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
b[i++] = (r=r>>>8) & ff;
|
||||
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode difficulty bits.
|
||||
*
|
||||
* This function calculates the difficulty target given the difficulty bits.
|
||||
*/
|
||||
var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) {
|
||||
diffBits = +diffBits;
|
||||
var target = bignum(diffBits & 0xffffff);
|
||||
target = target.shiftLeft(8*((diffBits >>> 24) - 3));
|
||||
|
||||
if (asBigInt) {
|
||||
return target;
|
||||
}
|
||||
|
||||
// Convert to buffer
|
||||
var diffBuf = target.toBuffer();
|
||||
var targetBuf = new Buffer(32).fill(0);
|
||||
diffBuf.copy(targetBuf, 32-diffBuf.length);
|
||||
return targetBuf;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode difficulty bits.
|
||||
*
|
||||
* This function calculates the compact difficulty, given a difficulty target.
|
||||
*/
|
||||
var encodeDiffBits = exports.encodeDiffBits = function encodeDiffBits(target) {
|
||||
if (Buffer.isBuffer(target)) {
|
||||
target = bignum.fromBuffer(target);
|
||||
} else if ("function" === typeof target.toBuffer) { // duck-typing bignum
|
||||
// Nothing to do
|
||||
} else {
|
||||
throw new Error("Incorrect variable type for difficulty");
|
||||
}
|
||||
|
||||
var mpiBuf = target.toBuffer("mpint");
|
||||
var size = mpiBuf.length - 4;
|
||||
|
||||
var compact = size << 24;
|
||||
if (size >= 1) compact |= mpiBuf[4] << 16;
|
||||
if (size >= 2) compact |= mpiBuf[5] << 8;
|
||||
if (size >= 3) compact |= mpiBuf[6] ;
|
||||
|
||||
return compact;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate "difficulty".
|
||||
*
|
||||
* This function calculates the maximum difficulty target divided by the given
|
||||
* difficulty target.
|
||||
*/
|
||||
var calcDifficulty = exports.calcDifficulty = function (target) {
|
||||
if (!Buffer.isBuffer(target)) {
|
||||
target = decodeDiffBits(target);
|
||||
}
|
||||
var targetBigint = bignum.fromBuffer(target, {order: 'forward'});
|
||||
var maxBigint = bignum.fromBuffer(MAX_TARGET, {order: 'forward'});
|
||||
return maxBigint.div(targetBigint).toNumber();
|
||||
};
|
||||
|
||||
var reverseBytes32 = exports.reverseBytes32 = function (data) {
|
||||
if (data.length % 4) {
|
||||
throw new Error("Util.reverseBytes32(): Data length must be multiple of 4");
|
||||
}
|
||||
var put = Put();
|
||||
var parser = Binary.parse(data);
|
||||
while (!parser.eof()) {
|
||||
var word = parser.word32le('word').vars.word;
|
||||
put.word32be(word);
|
||||
}
|
||||
return put.buffer();
|
||||
};
|
||||
|
||||
var getVarIntSize = exports.getVarIntSize = function getVarIntSize(i) {
|
||||
|
||||
if (i < 0xFD) {
|
||||
// unsigned char
|
||||
return 1;
|
||||
} else if (i <= 1<<16) {
|
||||
// unsigned short (LE)
|
||||
return 3;
|
||||
} else if (i <= 1<<32) {
|
||||
// unsigned int (LE)
|
||||
return 5;
|
||||
} else {
|
||||
// unsigned long long (LE)
|
||||
return 9;
|
||||
}
|
||||
};
|
||||
|
||||
// Initializations
|
||||
try {
|
||||
var NULL_HASH = exports.NULL_HASH = new Buffer(32).fill(0);
|
||||
var EMPTY_BUFFER = exports.EMPTY_BUFFER = new Buffer(0);
|
||||
var ZERO_VALUE = exports.ZERO_VALUE = new Buffer(8).fill(0);
|
||||
var INT64_MAX = exports.INT64_MAX = decodeHex("ffffffffffffffff");
|
||||
|
||||
// How much of Bitcoin's internal integer coin representation
|
||||
// makes 1 BTC
|
||||
var COIN = exports.COIN = 100000000;
|
||||
|
||||
var MAX_TARGET = exports.MAX_TARGET = decodeHex('00000000FFFF0000000000000000000000000000000000000000000000000000');
|
||||
} catch (e) {
|
||||
//logger.error("Error while generating utility constants:\n" +
|
||||
// (e.stack ? e.stack : e.toString()));
|
||||
process.exit(1);
|
||||
}
|
Loading…
Reference in New Issue