bunch of interim (broken) stuff

This commit is contained in:
Stephen Pair 2013-07-09 14:45:52 -04:00
commit 56a20ef25d
20 changed files with 4859 additions and 21 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build
node_modules

View File

@ -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);

569
Block.js Normal file
View File

@ -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);

589
Connection.js Normal file
View File

@ -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);

25
LICENSE Normal file
View File

@ -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.

161
Opcode.js Normal file
View File

@ -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);

60
Peer.js Normal file
View File

@ -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);

217
PeerManager.js Normal file
View File

@ -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);

309
Script.js Normal file
View File

@ -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);

942
ScriptInterpreter.js Normal file
View File

@ -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);

717
Transaction.js Normal file
View File

@ -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);

35
binding.gyp Normal file
View File

@ -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" ]
}]
]
}]
]
}
]
}

3
config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
network: 'livenet'
};

61
networks.js Normal file
View File

@ -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
};

View File

@ -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": {},

17
src/common.h Normal file
View File

@ -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

575
src/eckey.cc Normal file
View File

@ -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)

97
src/eckey.h Normal file
View File

@ -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

125
util/BinaryParser.js Normal file
View File

@ -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);

329
util/util.js Normal file
View File

@ -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);
}