commit
806e424680
|
@ -8,3 +8,4 @@ node_modules/
|
|||
*~
|
||||
.project
|
||||
README.html
|
||||
tags
|
||||
|
|
16
Address.js
16
Address.js
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
var imports = require('soop').imports();
|
||||
var parent = imports.parent || require('./util/VersionedData');
|
||||
var networks= imports.networks || require('./networks');
|
||||
|
||||
function Address() {
|
||||
Address.super(this, arguments);
|
||||
|
@ -22,4 +23,19 @@ Address.prototype.isValid = function() {
|
|||
return answer;
|
||||
};
|
||||
|
||||
Address.prototype.network = function() {
|
||||
var version = this.version();
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
|
||||
var answer;
|
||||
if (version === livenet.addressPubkey || version === livenet.addressScript)
|
||||
answer = livenet;
|
||||
else if (version === testnet.addressPubkey || version === testnet.addressScript)
|
||||
answer = testnet;
|
||||
|
||||
return answer;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Address);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var imports = require('soop').imports();
|
||||
|
||||
var parent = imports.parent || require('./util/VersionedData');
|
||||
var networks= imports.networks || require('./networks');
|
||||
|
||||
//compressed is true if public key is compressed; false otherwise
|
||||
function PrivateKey(version, buf, compressed) {
|
||||
|
@ -61,4 +62,19 @@ PrivateKey.prototype.compressed = function(compressed) {
|
|||
}
|
||||
};
|
||||
|
||||
PrivateKey.prototype.network = function() {
|
||||
var version = this.version();
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
|
||||
var answer;
|
||||
if (version === livenet.keySecret)
|
||||
answer = livenet;
|
||||
else if (version === testnet.keySecret)
|
||||
answer = testnet;
|
||||
|
||||
return answer;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(PrivateKey);
|
||||
|
|
83
README.md
83
README.md
|
@ -130,54 +130,53 @@ var bitcore = require('bitcore');
|
|||
var networks = bitcore.networks;
|
||||
var Peer = bitcore.Peer;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var Address = bitcore.Address;
|
||||
var Script = bitcore.Script;
|
||||
var coinUtil = bitcore.util;
|
||||
var PeerManager = require('soop').load('../PeerManager', {
|
||||
network: networks.testnet
|
||||
});
|
||||
|
||||
var createTx = function() {
|
||||
var TXIN = 'd05f35e0bbc495f6dcab03e599c8f5e32a07cdb4bc76964de201d06a2a7d8265';
|
||||
var TXIN_N = 0;
|
||||
var ADDR = 'muHct3YZ9Nd5Pq7uLYYhXRAxeW4EnpcaLz';
|
||||
var VAL = '0.001';
|
||||
|
||||
var txobj = {
|
||||
version: 1,
|
||||
lock_time: 0,
|
||||
ins: [],
|
||||
outs: []
|
||||
};
|
||||
|
||||
var txin = {
|
||||
s: coinUtil.EMPTY_BUFFER, // Add signature
|
||||
q: 0xffffffff
|
||||
};
|
||||
|
||||
var hash = new Buffer(TXIN.split('').reverse(), 'hex');
|
||||
var vout = parseInt(TXIN_N);
|
||||
var voutBuf = new Buffer(4);
|
||||
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
txin.o = Buffer.concat([hash, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
|
||||
var addr = new Address(ADDR);
|
||||
var script = Script.createPubKeyHashOut(addr.payload());
|
||||
var valueNum = coinUtil.parseValue(VAL);
|
||||
var value = coinUtil.bigIntToValue(valueNum);
|
||||
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
|
||||
return new Transaction(txobj);
|
||||
|
||||
// this can be get from insight.bitcore.io API o blockchain.info
|
||||
var utxos = {
|
||||
"unspent": [
|
||||
{
|
||||
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
|
||||
"vout": 1,
|
||||
"amount": 1.0101,
|
||||
"confirmations":7
|
||||
},
|
||||
{
|
||||
"address": "mhNCT9TwZAGF1tLPpZdqfkTmtBkY282YDW",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||
"scriptPubKey": "76a9141448534cb1a1ec44665b0eb2326e570814afe3f188ac",
|
||||
"vout": 0,
|
||||
"confirmations": 1,
|
||||
"amount": 10
|
||||
},
|
||||
};
|
||||
|
||||
//private keys in WIF format (see Transaction.js for other options)
|
||||
var keys = [
|
||||
"cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV",
|
||||
"cPa87VgwZfowGZYaEenoQeJgRfKW6PhZ1R65EHTkN1K19cSvc92G",
|
||||
"cPQ9DSbBRLva9av5nqeF5AGrh3dsdW8p2E5jS4P8bDWZAoQTeeKB"
|
||||
];
|
||||
|
||||
function createTx() {
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys);
|
||||
|
||||
/ * create and signing can be done in 2 steps using:
|
||||
* var ret = Transaction.create(utxos,outs);
|
||||
* and later:
|
||||
* ret.tx.sign(ret.tx.selectedUtxos, outs, keys);
|
||||
*/
|
||||
|
||||
return ret.tx.serialize().toString('hex');
|
||||
};
|
||||
|
||||
|
||||
var peerman = new PeerManager();
|
||||
peerman.addPeer(new Peer('127.0.0.1', 18333));
|
||||
|
||||
|
|
443
Transaction.js
443
Transaction.js
|
@ -11,8 +11,12 @@ var Parser = imports.Parser || require('./util/BinaryParser');
|
|||
var Step = imports.Step || require('step');
|
||||
var buffertools = imports.buffertools || require('buffertools');
|
||||
var error = imports.error || require('./util/error');
|
||||
var networks = imports.networks || require('./networks');
|
||||
var WalletKey = imports.WalletKey || require('./WalletKey');
|
||||
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
|
||||
|
||||
var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]);
|
||||
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
||||
|
||||
function TransactionIn(data) {
|
||||
if ("object" !== typeof data) {
|
||||
|
@ -679,6 +683,445 @@ Transaction.prototype.parse = function (parser) {
|
|||
this.calcHash();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* selectUnspent
|
||||
*
|
||||
* Selects some unspent outputs for later usage in tx inputs
|
||||
*
|
||||
* @utxos
|
||||
* @totalNeededAmount: output transaction amount in BTC, including fee
|
||||
* @allowUnconfirmed: false (allow selecting unconfirmed utxos)
|
||||
*
|
||||
* Note that the sum of the selected unspent is >= the desired amount.
|
||||
* Returns the selected unspent outputs if the totalNeededAmount was reach.
|
||||
* 'null' if not.
|
||||
*
|
||||
* TODO: utxo selection is not optimized to minimize mempool usage.
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed) {
|
||||
|
||||
var minConfirmationSteps = [6,1];
|
||||
if (allowUnconfirmed) minConfirmationSteps.push(0);
|
||||
|
||||
var ret = [];
|
||||
var l = utxos.length;
|
||||
var totalSat = bignum(0);
|
||||
var totalNeededAmountSat = util.parseValue(totalNeededAmount);
|
||||
var fulfill = false;
|
||||
var maxConfirmations = null;
|
||||
|
||||
do {
|
||||
var minConfirmations = minConfirmationSteps.shift();
|
||||
for(var i = 0; i<l; i++) {
|
||||
var u = utxos[i];
|
||||
|
||||
var c = u.confirmations || 0;
|
||||
|
||||
if ( c < minConfirmations || (maxConfirmations && c >=maxConfirmations) )
|
||||
continue;
|
||||
|
||||
|
||||
var sat = u.amountSat || util.parseValue(u.amount);
|
||||
totalSat = totalSat.add(sat);
|
||||
ret.push(u);
|
||||
if(totalSat.cmp(totalNeededAmountSat) >= 0) {
|
||||
fulfill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
maxConfirmations = minConfirmations;
|
||||
} while( !fulfill && minConfirmationSteps.length);
|
||||
|
||||
//TODO(?): sort ret and check is some inputs can be avoided.
|
||||
//If the initial utxos are sorted, this step would be necesary only if
|
||||
//utxos were selected from different minConfirmationSteps.
|
||||
|
||||
return fulfill ? ret : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* _scriptForAddress
|
||||
*
|
||||
* Returns a scriptPubKey for the given address type
|
||||
*/
|
||||
|
||||
Transaction._scriptForAddress = function (addressString) {
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
var address = new Address(addressString);
|
||||
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version == livenet.addressPubkey || version == testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version == livenet.addressScript || version == testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
Transaction._sumOutputs = function(outs) {
|
||||
var valueOutSat = bignum(0);
|
||||
var l = outs.length;
|
||||
|
||||
for(var i=0;i<outs.length;i++) {
|
||||
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
valueOutSat = valueOutSat.add(sat);
|
||||
}
|
||||
return valueOutSat;
|
||||
}
|
||||
|
||||
/*
|
||||
* createWithFee
|
||||
* Create a TX given ins (selected already), outs, and a FIXED fee
|
||||
* details on the input on .create
|
||||
*/
|
||||
|
||||
Transaction.createWithFee = function (ins, outs, feeSat, opts) {
|
||||
opts = opts || {};
|
||||
feeSat = feeSat || 0;
|
||||
|
||||
var txobj = {};
|
||||
txobj.version = 1;
|
||||
txobj.lock_time = opts.lockTime || 0;
|
||||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
|
||||
var l = ins.length;
|
||||
var valueInSat = bignum(0);
|
||||
for(var i=0; i<l; i++) {
|
||||
valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
|
||||
|
||||
var txin = {};
|
||||
txin.s = util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new Buffer(ins[i].txid, 'hex');
|
||||
var hashReversed = buffertools.reverse(hash);
|
||||
|
||||
var vout = parseInt(ins[i].vout);
|
||||
var voutBuf = new Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
}
|
||||
|
||||
var valueOutSat = Transaction._sumOutputs(outs);
|
||||
valueOutSat = valueOutSat.add(feeSat);
|
||||
|
||||
if (valueInSat.cmp(valueOutSat)<0) {
|
||||
var inv = valueInSat.toString();
|
||||
var ouv = valueOutSat.toString();
|
||||
throw new Error('transaction input amount is less than outputs: ' +
|
||||
inv + ' < '+ouv + ' [SAT]');
|
||||
}
|
||||
|
||||
for(var i=0;i<outs.length;i++) {
|
||||
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
var value = util.bigIntToValue(amountSat);
|
||||
var script = Transaction._scriptForAddress(outs[i].address);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
}
|
||||
|
||||
// add remainder (without modifiying outs[])
|
||||
var remainderSat = valueInSat.sub(valueOutSat);
|
||||
if (remainderSat.cmp(0)>0) {
|
||||
var remainderAddress = opts.remainderAddress || ins[0].address;
|
||||
var value = util.bigIntToValue(remainderSat);
|
||||
var script = Transaction._scriptForAddress(remainderAddress);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
}
|
||||
|
||||
|
||||
return new Transaction(txobj);
|
||||
};
|
||||
|
||||
Transaction.prototype.calcSize = function () {
|
||||
var totalSize = 8; // version + lock_time
|
||||
totalSize += util.getVarIntSize(this.ins.length); // tx_in count
|
||||
this.ins.forEach(function (txin) {
|
||||
totalSize += 36 + util.getVarIntSize(txin.s.length) +
|
||||
txin.s.length + 4; // outpoint + script_len + script + sequence
|
||||
});
|
||||
|
||||
totalSize += util.getVarIntSize(this.outs.length);
|
||||
this.outs.forEach(function (txout) {
|
||||
totalSize += util.getVarIntSize(txout.s.length) +
|
||||
txout.s.length + 8; // script_len + script + value
|
||||
});
|
||||
this.size = totalSize;
|
||||
return totalSize;
|
||||
};
|
||||
|
||||
Transaction.prototype.getSize = function getHash() {
|
||||
if (!this.size) {
|
||||
this.size = this.calcSize();
|
||||
}
|
||||
return this.size;
|
||||
};
|
||||
|
||||
|
||||
Transaction.prototype.isComplete = function () {
|
||||
var l = this.ins.length;
|
||||
|
||||
var ret = true;
|
||||
for (var i=0; i<l; i++) {
|
||||
if ( buffertools.compare(this.ins[i].s,util.EMPTY_BUFFER)===0 ) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* sign
|
||||
*
|
||||
* signs the transaction
|
||||
*
|
||||
* @ utxos
|
||||
* @keypairs
|
||||
* @opts
|
||||
* signhash: Transaction.SIGHASH_ALL
|
||||
*
|
||||
* Return the 'completeness' status of the tx (i.e, if all inputs are signed).
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.prototype.sign = function (selectedUtxos, keys, opts) {
|
||||
var self = this;
|
||||
var complete = false;
|
||||
var m = keys.length;
|
||||
opts = opts || {};
|
||||
var signhash = opts.signhash || SIGHASH_ALL;
|
||||
|
||||
if (selectedUtxos.length !== self.ins.length)
|
||||
throw new Error('given selectedUtxos do not match tx inputs');
|
||||
|
||||
var inputMap = [];
|
||||
var l = selectedUtxos.length;
|
||||
for(var i=0; i<l; i++) {
|
||||
inputMap[i]= {
|
||||
address: selectedUtxos[i].address,
|
||||
scriptPubKey: selectedUtxos[i].scriptPubKey
|
||||
};
|
||||
}
|
||||
|
||||
//prepare keys
|
||||
var walletKeyMap = {};
|
||||
var l = keys.length;
|
||||
var wk;
|
||||
for(var i=0; i<l; i++) {
|
||||
var k = keys[i];
|
||||
|
||||
if (typeof k === 'string') {
|
||||
var pk = new PrivateKey(k);
|
||||
wk = new WalletKey({network: pk.network()});
|
||||
wk.fromObj({priv:k});
|
||||
}
|
||||
else if (k instanceof WalletKey) {
|
||||
wk = k;
|
||||
}
|
||||
else {
|
||||
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
|
||||
}
|
||||
walletKeyMap[wk.storeObj().addr] = wk;
|
||||
}
|
||||
|
||||
var inputSigned = 0;
|
||||
l = self.ins.length;
|
||||
for(var i=0;i<l;i++) {
|
||||
var aIn = self.ins[i];
|
||||
var wk = walletKeyMap[inputMap[i].address];
|
||||
|
||||
if (typeof wk === 'undefined') {
|
||||
if ( buffertools.compare(aIn.s,util.EMPTY_BUFFER)!==0 )
|
||||
inputSigned++;
|
||||
continue;
|
||||
}
|
||||
var scriptBuf = new Buffer(inputMap[i].scriptPubKey, 'hex');
|
||||
var s = new Script(scriptBuf);
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('input:'+i+' script type:'+ s.getRawOutType() +' not supported yet');
|
||||
}
|
||||
|
||||
var txSigHash = self.hashForSignature(s, i, signhash);
|
||||
|
||||
var sigRaw;
|
||||
var triesLeft = 10;
|
||||
do {
|
||||
sigRaw = wk.privKey.signSync(txSigHash);
|
||||
} while ( wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false && triesLeft-- );
|
||||
|
||||
if (!triesLeft) {
|
||||
log.debug('could not sign input:'+i +' verification failed');
|
||||
continue;
|
||||
}
|
||||
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wk.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
self.ins[i].s = scriptSig.getBuffer();
|
||||
inputSigned++;
|
||||
}
|
||||
var complete = inputSigned === l;
|
||||
return complete;
|
||||
};
|
||||
|
||||
/*
|
||||
* create
|
||||
*
|
||||
* creates a transaction without signing it.
|
||||
*
|
||||
* @utxos
|
||||
* @outs
|
||||
* @opts
|
||||
*
|
||||
* See createAndSign for documentation on the inputs
|
||||
*
|
||||
* Returns:
|
||||
* { tx: {}, selectedUtxos: []}
|
||||
* see createAndSign for details
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.create = function (utxos, outs, opts) {
|
||||
|
||||
//starting size estimation
|
||||
var size = 500;
|
||||
var opts = opts || {};
|
||||
|
||||
var givenFeeSat;
|
||||
if (opts.fee || opts.feeSat) {
|
||||
givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
|
||||
}
|
||||
|
||||
var selectedUtxos;
|
||||
do {
|
||||
// based on https://en.bitcoin.it/wiki/Transaction_fees
|
||||
maxSizeK = parseInt(size/1000) + 1;
|
||||
var feeSat = givenFeeSat
|
||||
? givenFeeSat : maxSizeK * FEE_PER_1000B_SAT ;
|
||||
|
||||
var valueOutSat = Transaction
|
||||
._sumOutputs(outs)
|
||||
.add(feeSat);
|
||||
|
||||
selectedUtxos = Transaction
|
||||
.selectUnspent(utxos,valueOutSat / util.COIN, opts.allowUnconfirmed);
|
||||
|
||||
if (!selectedUtxos) {
|
||||
throw new Error(
|
||||
'the given UTXOs dont sum up the given outputs: '
|
||||
+ valueOutSat.toString()
|
||||
+ ' (fee is ' + feeSat
|
||||
+ ' )SAT'
|
||||
);
|
||||
}
|
||||
var tx = Transaction.createWithFee(selectedUtxos, outs, feeSat, {
|
||||
remainderAddress: opts.remainderAddress,
|
||||
lockTime: opts.lockTime,
|
||||
});
|
||||
|
||||
size = tx.getSize();
|
||||
} while (size > (maxSizeK+1)*1000 );
|
||||
|
||||
return {tx: tx, selectedUtxos: selectedUtxos};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* createAndSign
|
||||
*
|
||||
* creates and signs a transaction
|
||||
*
|
||||
* @utxos
|
||||
* unspent outputs array (UTXO), using the following format:
|
||||
* [{
|
||||
* address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
* hash: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
* vout: 1,
|
||||
* amount: 0.01,
|
||||
* confirmations: 3
|
||||
* }, ...
|
||||
* ]
|
||||
* This is compatible con insight's utxo API.
|
||||
* That amount is in BTCs (as returned in insight and bitcoind).
|
||||
* amountSat (instead of amount) can be given to provide amount in satochis.
|
||||
*
|
||||
|
||||
* @outs
|
||||
* an array of [{
|
||||
* address: xx,
|
||||
* amount:0.001
|
||||
* },...]
|
||||
*
|
||||
* @keys
|
||||
* an array of strings representing private keys to sign the
|
||||
* transaction in WIF private key format OR WalletKey objects
|
||||
*
|
||||
* @opts
|
||||
* {
|
||||
* remainderAddress: null,
|
||||
* fee: 0.001,
|
||||
* lockTime: null,
|
||||
* allowUnconfirmed: false,
|
||||
* signhash: SIGHASH_ALL
|
||||
* }
|
||||
*
|
||||
*
|
||||
* Retuns:
|
||||
* {
|
||||
* tx: The new created transaction,
|
||||
* selectedUtxos: The UTXOs selected as inputs for this transaction
|
||||
* }
|
||||
*
|
||||
* Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
|
||||
* repectively, to provide amounts in satoshis.
|
||||
*
|
||||
* If no remainderAddress is given, and there are remainder coins, the
|
||||
* first IN address will be used to return the coins. (TODO: is this is reasonable?)
|
||||
*
|
||||
* The Transaction creation is handled in 2 steps:
|
||||
* .create
|
||||
* .selectUnspent
|
||||
* .createWithFee
|
||||
* .sign
|
||||
*
|
||||
* If you need just to create a TX and not sign it, use .create
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.createAndSign = function (utxos, outs, keys, opts) {
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
ret.tx.sign(ret.selectedUtxos, keys);
|
||||
return ret;
|
||||
};
|
||||
|
||||
var TransactionInputsCache = exports.TransactionInputsCache =
|
||||
function TransactionInputsCache(tx)
|
||||
{
|
||||
|
|
|
@ -8,195 +8,34 @@ var run = function() {
|
|||
var amt = '0.005';
|
||||
var toAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1';
|
||||
var changeAddressString = 'moDz3jEo9q7CxjBDjmb13sL4SKkgo2AACE';
|
||||
var feeString = '0.0001';
|
||||
|
||||
var safeUnspent = [
|
||||
{
|
||||
address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
hash: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
vout: 1,
|
||||
ts: 1394719301,
|
||||
scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
amount: 0.01,
|
||||
confirmations: 2
|
||||
}
|
||||
]
|
||||
;
|
||||
var utxos = [{
|
||||
address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
txid: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
vout: 1,
|
||||
ts: 1394719301,
|
||||
scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
amount: 0.01,
|
||||
confirmations: 2
|
||||
}];
|
||||
|
||||
console.log('TX Data: BTC:' + amt + ' => '+ toAddress + ', change To:' + changeAddressString ) ;
|
||||
console.log('Unspends:', safeUnspent);
|
||||
|
||||
var wk = new bitcore.WalletKey({
|
||||
network: bitcore.networks.testnet
|
||||
});
|
||||
wk.fromObj({ priv: priv, });
|
||||
|
||||
var wkObj= wk.storeObj();
|
||||
var keyPairs = [{
|
||||
key: wkObj.priv,
|
||||
address: wkObj.addr,
|
||||
}];
|
||||
console.log('KEY DB IS:', keyPairs);
|
||||
|
||||
var Address = bitcore.Address;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var Script = bitcore.Script;
|
||||
var nets = bitcore.networks;
|
||||
var z = bitcore.bignum(0);
|
||||
var amt = bitcore.util.parseValue(amt);
|
||||
|
||||
if(z.cmp(amt) === 0 )
|
||||
throw "spend amount must be greater than zero";
|
||||
|
||||
if(!changeAddressString)
|
||||
throw "change address was not provided";
|
||||
|
||||
var fee = bitcore.util.parseValue(feeString || '0');
|
||||
var total = bitcore.bignum(0).add(amt).add(fee);
|
||||
var address = new Address(toAddress);
|
||||
var sendTx = new Transaction();
|
||||
var i;
|
||||
|
||||
var unspent = [];
|
||||
var unspentAmt = bitcore.bignum(0);
|
||||
console.log('Unspends Outputs:', utxos);
|
||||
|
||||
|
||||
for(i=0;i<safeUnspent.length;i++) {
|
||||
unspent.push(safeUnspent[i]);
|
||||
var outs = [{address:toAddress, amount:amt}];
|
||||
var keys = [priv];
|
||||
|
||||
var amountSatoshiString = new bitcore.bignum(safeUnspent[i].amount * Math.pow(10,8)).toString();
|
||||
var ret = bitcore.Transaction.createAndSign(utxos, outs, keys,
|
||||
{remainderAddress: changeAddressString});
|
||||
|
||||
unspentAmt = unspentAmt.add(new bitcore.bignum(amountSatoshiString));
|
||||
/* create and signing can be done in 2 steps using:
|
||||
* var ret = Transaction.create(utxos,outs);
|
||||
* and later:
|
||||
* ret.tx.sign(ret.tx.selectedUtxos, outs, keys);
|
||||
*/
|
||||
|
||||
// If > -1, we have enough to send the requested amount
|
||||
if(unspentAmt.cmp(total) > -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(unspentAmt.cmp(total) < 0) {
|
||||
throw "you do not have enough bitcoins to send this amount";
|
||||
}
|
||||
|
||||
var txobj = {};
|
||||
txobj.version = 1;
|
||||
txobj.lock_time = 0;
|
||||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
for(i=0;i<unspent.length;i++) {
|
||||
var txin = {};
|
||||
|
||||
txin.s = bitcore.util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new bitcore.Buffer(unspent[i].hash, 'hex');
|
||||
var hashReversed = bitcore.buffertools.reverse(hash);
|
||||
var vout = parseInt(unspent[i].vout);
|
||||
var voutBuf = new bitcore.Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = bitcore.Buffer.concat([hashReversed, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
}
|
||||
|
||||
|
||||
// This is not used really, but we keep it for the future.
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version == nets.livenet.addressPubkey || version == nets.testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version == nets.livenet.addressScript || version == nets.testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
var value = bitcore.util.bigIntToValue(amt);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
var remainder = unspentAmt.sub(total);
|
||||
|
||||
if(z.cmp(amt) !== 0 ) {
|
||||
var changeAddress = new Address(changeAddressString);
|
||||
var changeValue = bitcore.util.bigIntToValue(remainder);
|
||||
|
||||
// This is not used really, but we keep it for the future.
|
||||
var cversion = changeAddress.version();
|
||||
var cscript;
|
||||
if (cversion == nets.livenet.addressPubkey || cversion == nets.testnet.addressPubkey)
|
||||
cscript = Script.createPubKeyHashOut(changeAddress.payload());
|
||||
else if (cversion == nets.livenet.addressScript || cversion == nets.testnet.addressScript)
|
||||
cscript = Script.createP2SH(changeAddress.payload());
|
||||
else
|
||||
throw new Error('invalid change output address');
|
||||
|
||||
var change = {
|
||||
v: changeValue,
|
||||
s: cscript.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(change);
|
||||
}
|
||||
|
||||
var tx = new Transaction(txobj);
|
||||
var anypay = false;
|
||||
var l = unspent.length;
|
||||
var allFound = l;
|
||||
|
||||
// Here will be the beginning of your signing for loop
|
||||
for(i=0;i < l;i++) {
|
||||
var scriptBuf = new bitcore.Buffer(unspent[i].scriptPubKey, 'hex');
|
||||
|
||||
var s = new Script(scriptBuf);
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('input script type '+ s.getRawOutType() +' not supported yet');
|
||||
}
|
||||
var txSigHash = tx.hashForSignature(s, i,
|
||||
anypay ? Transaction.SIGHASH_ANYONECANPAY : Transaction.SIGHASH_ALL);
|
||||
|
||||
|
||||
// txSigHash = bitcore.buffertools.reverse(txSigHash);
|
||||
|
||||
for(var j=0;j<keyPairs.length;j++) {
|
||||
var kp = keyPairs[j];
|
||||
if(kp.address === unspent[i].address) {
|
||||
console.log('SIGNING With...', kp.key); //TODO
|
||||
console.log('HASH TO SIGN: ',bitcore.buffertools.toHex(txSigHash)); //TODO
|
||||
var wKey = new bitcore.WalletKey({network: bitcore.networks.testnet});
|
||||
wKey.fromObj({
|
||||
priv: kp.key,
|
||||
});
|
||||
|
||||
console.log('PRIV KEY', bitcore.buffertools.toHex(wKey.privKey.private )); //TODO
|
||||
console.log('PUB KEY', bitcore.buffertools.toHex(wKey.privKey.public )); //TODO
|
||||
|
||||
var sigRaw = wKey.privKey.signSync(txSigHash);
|
||||
console.log('SIGNATURE: ',bitcore.buffertools.toHex(sigRaw)); //TODO
|
||||
|
||||
console.log('VERIFY: ',wKey.privKey.verifySignatureSync(txSigHash, sigRaw)); //TODO
|
||||
|
||||
var sigType = new bitcore.Buffer(1);
|
||||
sigType[0] = anypay ? Transaction.SIGHASH_ANYONECANPAY : Transaction.SIGHASH_ALL;
|
||||
var sig = bitcore.Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wKey.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
tx.ins[i].s = scriptSig.getBuffer();
|
||||
allFound--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allFound !== 0) {
|
||||
throw new Error('could not find priv key for some inputs');
|
||||
}
|
||||
|
||||
var txHex = tx.serialize().toString('hex');
|
||||
var txHex = ret.tx.serialize().toString('hex');
|
||||
console.log('TX HEX IS: ', txHex);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
|
||||
var run = function() {
|
||||
// Replace '../bitcore' with 'bitcore' if you use this code elsewhere.
|
||||
var bitcore = require('../bitcore');
|
||||
var networks = require('../networks');
|
||||
var WalletKey = bitcore.WalletKey;
|
||||
|
||||
var opts = {network: networks.livenet};
|
||||
|
||||
function print(wk) {
|
||||
|
||||
console.log('\n## Network: ' + wk.network.name);
|
||||
console.log ('\t * Hex Representation');
|
||||
console.log ('\tPrivate: ' + bitcore.buffertools.toHex(wk.privKey.private));
|
||||
console.log ('\tPublic : ' + bitcore.buffertools.toHex(wk.privKey.public));
|
||||
console.log ('\tPublic Compressed : ' + (wk.privKey.compressed?'Yes':'No'));
|
||||
|
||||
var wkObj = wk.storeObj();
|
||||
console.log ('\n\t * WalletKey Store Object');
|
||||
console.log ('\tPrivate: ' + wkObj.priv);
|
||||
console.log ('\tPublic : ' + wkObj.pub);
|
||||
console.log ('\tAddr : ' + wkObj.addr);
|
||||
};
|
||||
|
||||
//Generate a new one (compressed public key, compressed WIF flag)
|
||||
var wk = new WalletKey(opts);
|
||||
wk.generate();
|
||||
print(wk);
|
||||
|
||||
//Generate from private Key WIF. Compressed status taken from WIF.
|
||||
var wk2 = new WalletKey(opts);
|
||||
wk2.fromObj({priv:'cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V'});
|
||||
print(wk2);
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports.run = run;
|
||||
if (require.main === module) {
|
||||
run();
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
|
||||
|
||||
|
||||
var bitcore = require('../bitcore');
|
||||
|
||||
var priv = 'cTgGUrcro89yUtKeG6gHBAS14r3qp25KwTTxG9d4kEzcFxecuZDm';
|
||||
var amt = '0.005';
|
||||
var toAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1';
|
||||
var changeAddressString = 'moDz3jEo9q7CxjBDjmb13sL4SKkgo2AACE';
|
||||
var feeString = '0.0001';
|
||||
|
||||
var safeUnspent = [
|
||||
{
|
||||
address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
hash: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
vout: 1,
|
||||
ts: 1394719301,
|
||||
scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
amount: 0.01,
|
||||
confirmations: 2
|
||||
}
|
||||
]
|
||||
;
|
||||
|
||||
console.log('TX Data: BTC:' + amt + ' => '+ toAddress + ', change To:' + changeAddressString ) ;
|
||||
console.log('Unspends:', safeUnspent);
|
||||
|
||||
var wk = new bitcore.WalletKey({
|
||||
network: bitcore.networks.testnet
|
||||
});
|
||||
wk.fromObj({ priv: priv, });
|
||||
|
||||
var wkObj= wk.storeObj();
|
||||
var keyPairs = [{
|
||||
key: wkObj.priv,
|
||||
address: wkObj.addr,
|
||||
}];
|
||||
console.log('KEY DB IS:', keyPairs);
|
||||
|
||||
var Address = bitcore.Address;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var Script = bitcore.Script;
|
||||
var nets = bitcore.networks;
|
||||
var z = bitcore.bignum(0);
|
||||
var amt = bitcore.util.parseValue(amt);
|
||||
|
||||
if(z.cmp(amt) === 0 )
|
||||
throw "spend amount must be greater than zero";
|
||||
|
||||
if(!changeAddressString)
|
||||
throw "change address was not provided";
|
||||
|
||||
var fee = bitcore.util.parseValue(feeString || '0');
|
||||
var total = bitcore.bignum(0).add(amt).add(fee);
|
||||
var address = new Address(toAddress);
|
||||
var sendTx = new Transaction();
|
||||
var i;
|
||||
|
||||
var unspent = [];
|
||||
var unspentAmt = bitcore.bignum(0);
|
||||
|
||||
|
||||
for(i=0;i<safeUnspent.length;i++) {
|
||||
unspent.push(safeUnspent[i]);
|
||||
|
||||
var amountSatoshiString = new bitcore.bignum(safeUnspent[i].amount * Math.pow(10,8)).toString();
|
||||
|
||||
unspentAmt = unspentAmt.add(new bitcore.bignum(amountSatoshiString));
|
||||
|
||||
// If > -1, we have enough to send the requested amount
|
||||
if(unspentAmt.cmp(total) > -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(unspentAmt.cmp(total) < 0) {
|
||||
throw "you do not have enough bitcoins to send this amount";
|
||||
}
|
||||
|
||||
var txobj = {};
|
||||
txobj.version = 1;
|
||||
txobj.lock_time = 0;
|
||||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
for(i=0;i<unspent.length;i++) {
|
||||
var txin = {};
|
||||
|
||||
txin.s = bitcore.util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new bitcore.Buffer(unspent[i].hash, 'hex');
|
||||
var hashReversed = bitcore.buffertools.reverse(hash);
|
||||
var vout = parseInt(unspent[i].vout);
|
||||
var voutBuf = new bitcore.Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = bitcore.Buffer.concat([hashReversed, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
}
|
||||
|
||||
|
||||
// This is not used really, but we keep it for the future.
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version == nets.livenet.addressPubkey || version == nets.testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version == nets.livenet.addressScript || version == nets.testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
var value = bitcore.util.bigIntToValue(amt);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
var remainder = unspentAmt.sub(total);
|
||||
|
||||
if(z.cmp(amt) !== 0 ) {
|
||||
var changeAddress = new Address(changeAddressString);
|
||||
var changeValue = bitcore.util.bigIntToValue(remainder);
|
||||
|
||||
// This is not used really, but we keep it for the future.
|
||||
var cversion = changeAddress.version();
|
||||
var cscript;
|
||||
if (cversion == nets.livenet.addressPubkey || cversion == nets.testnet.addressPubkey)
|
||||
cscript = Script.createPubKeyHashOut(changeAddress.payload());
|
||||
else if (cversion == nets.livenet.addressScript || cversion == nets.testnet.addressScript)
|
||||
cscript = Script.createP2SH(changeAddress.payload());
|
||||
else
|
||||
throw new Error('invalid change output address');
|
||||
|
||||
var change = {
|
||||
v: changeValue,
|
||||
s: cscript.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(change);
|
||||
}
|
||||
|
||||
var tx = new Transaction(txobj);
|
||||
var anypay = false;
|
||||
var l = unspent.length;
|
||||
var allFound = l;
|
||||
|
||||
// Here will be the beginning of your signing for loop
|
||||
for(i=0;i < l;i++) {
|
||||
var scriptBuf = new bitcore.Buffer(unspent[i].scriptPubKey, 'hex');
|
||||
|
||||
var s = new Script(scriptBuf);
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('input script type '+ s.getRawOutType() +' not supported yet');
|
||||
}
|
||||
var txSigHash = tx.hashForSignature(s, i,
|
||||
anypay ? Transaction.SIGHASH_ANYONECANPAY : Transaction.SIGHASH_ALL);
|
||||
|
||||
|
||||
// txSigHash = bitcore.buffertools.reverse(txSigHash);
|
||||
|
||||
for(var j=0;j<keyPairs.length;j++) {
|
||||
var kp = keyPairs[j];
|
||||
if(kp.address === unspent[i].address) {
|
||||
console.log('[wallet.js.409:address:] SIGNING With...', kp.key); //TODO
|
||||
|
||||
console.log('[kk.js.168:txSigHash:] HASH TO SIGN: ',bitcore.buffertools.toHex(txSigHash)); //TODO
|
||||
var wKey = new bitcore.WalletKey({network: bitcore.networks.testnet});
|
||||
wKey.fromObj({
|
||||
priv: kp.key,
|
||||
});
|
||||
|
||||
console.log('[kk2.js.169] PRIV KEY', bitcore.buffertools.toHex(wKey.privKey.private )); //TODO
|
||||
console.log('[kk2.js.169] PUB KEY', bitcore.buffertools.toHex(wKey.privKey.public )); //TODO
|
||||
|
||||
var sigRaw = wKey.privKey.signSync(txSigHash);
|
||||
console.log('[kk.js.168:txSigHash:] SIGNATURE: ',bitcore.buffertools.toHex(sigRaw)); //TODO
|
||||
|
||||
//VERIFY
|
||||
console.log('[kk.js.168:txSigHash:] VERIFY: ',wKey.privKey.verifySignatureSync(txSigHash, sigRaw)); //TODO
|
||||
|
||||
var sigType = new bitcore.Buffer(1);
|
||||
sigType[0] = anypay ? Transaction.SIGHASH_ANYONECANPAY : Transaction.SIGHASH_ALL;
|
||||
var sig = bitcore.Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wKey.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
tx.ins[i].s = scriptSig.getBuffer();
|
||||
allFound--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allFound !== 0) {
|
||||
throw new Error('could not find priv key for some inputs');
|
||||
}
|
||||
|
||||
var txHex = tx.serialize().toString('hex');
|
||||
console.log('[kk.js.188:ret:] OUT IS: ', txHex);
|
||||
|
||||
|
||||
////
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[
|
||||
{
|
||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
"vout": 1,
|
||||
"amount": 0.01,
|
||||
"confirmations":7
|
||||
},
|
||||
{
|
||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
||||
"vout": 0,
|
||||
"confirmations": 1,
|
||||
"amount": 0.1
|
||||
},
|
||||
{
|
||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
||||
"vout": 3,
|
||||
"confirmations": 0,
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"unspent": [
|
||||
{
|
||||
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
|
||||
"vout": 1,
|
||||
"amount": 1.0101,
|
||||
"confirmations":7
|
||||
},
|
||||
{
|
||||
"address": "mhNCT9TwZAGF1tLPpZdqfkTmtBkY282YDW",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||
"scriptPubKey": "76a9141448534cb1a1ec44665b0eb2326e570814afe3f188ac",
|
||||
"vout": 0,
|
||||
"confirmations": 1,
|
||||
"amount": 10
|
||||
},
|
||||
{
|
||||
"address": "n44hn28zAooZpn8mpWKzATbabqaHDK9oNJ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||
"scriptPubKey": "76a914f753f58b1fb1daaa5534b10af85ca9210f3445d288ac",
|
||||
"vout": 3,
|
||||
"confirmations": 0,
|
||||
"amount": 5
|
||||
}
|
||||
],
|
||||
"keyStrings": [
|
||||
"cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV",
|
||||
"cPa87VgwZfowGZYaEenoQeJgRfKW6PhZ1R65EHTkN1K19cSvc92G",
|
||||
"cPQ9DSbBRLva9av5nqeF5AGrh3dsdW8p2E5jS4P8bDWZAoQTeeKB"
|
||||
]
|
||||
}
|
||||
|
|
@ -47,4 +47,24 @@ describe('Address', function() {
|
|||
s.should.equal(a.toString()); // check that validation doesn't change data
|
||||
});
|
||||
});
|
||||
it('should be able to detect network from an address', function() {
|
||||
var a = new Address('1KfyjCgBSMsLqiCbakfSdeoBUqMqLUiu3T');
|
||||
a.network().name.should.equal('livenet');
|
||||
var a = new Address('1dice8EMZmqKvrGE4Qc9bUFf9PX3xaYDp');
|
||||
a.network().name.should.equal('livenet');
|
||||
//p2sh
|
||||
var a = new Address('3QRhucKtEn5P9i7YPxzXCqBtPJTPbRFycn');
|
||||
a.network().name.should.equal('livenet');
|
||||
|
||||
//testnet
|
||||
var a = new Address('mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE');
|
||||
a.network().name.should.equal('testnet');
|
||||
var a = new Address('n2ekxibY5keRiMaoKFGfiNfXQCS4zTUpct');
|
||||
a.network().name.should.equal('testnet');
|
||||
|
||||
//p2sh
|
||||
var a = new Address('2NBSBcf2KfjPEEqVusmrWdmUeNHRiUTS3Li');
|
||||
a.network().name.should.equal('testnet');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -29,4 +29,24 @@ describe('PrivateKey', function() {
|
|||
|
||||
privkey.as('base58').should.equal('cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH');
|
||||
});
|
||||
|
||||
it('should be able to detect network from privatekey', function() {
|
||||
var a = new PrivateKey('cMu64LfQqrPC83SjJqde4mZ5jzC48zyeKbUZbTjQdh6pa1h48TdM');
|
||||
a.network().name.should.equal('testnet');
|
||||
var a = new PrivateKey('cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V');
|
||||
a.network().name.should.equal('testnet');
|
||||
|
||||
//compress flag = on
|
||||
var a = new PrivateKey('KwHXRTLNWKzxy2NUnnhFtxricC3Dod4Dd3D7RKzVkKDtWrZhuDHs');
|
||||
a.network().name.should.equal('livenet');
|
||||
var a = new PrivateKey('KwaLX8oyJNNCL9tcyYakQHJDTnrPAmZ2M1YK7NhEcT9j55LWqMZz');
|
||||
a.network().name.should.equal('livenet');
|
||||
|
||||
//compress flag = off
|
||||
var a = new PrivateKey('5KS4jw2kT3VoEFUfzgSpX3GVi7qRYkTfwTBU7qxPKyvbGuiVj33');
|
||||
a.network().name.should.equal('livenet');
|
||||
var a = new PrivateKey('5JZsbYcnYN8Dz2YeSLZr6aswrVevedMUSFWxpie6SPpYRb2E4Gi');
|
||||
a.network().name.should.equal('livenet');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ var Transaction;
|
|||
var In;
|
||||
var Out;
|
||||
var Script = bitcore.Script;
|
||||
var util = bitcore.util;
|
||||
var buffertools = require('buffertools');
|
||||
var testdata = testdata || require('./testdata');
|
||||
|
||||
|
@ -25,11 +26,240 @@ describe('Transaction', function() {
|
|||
should.exist(In);
|
||||
should.exist(Out);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to create instance', function() {
|
||||
var t = new Transaction();
|
||||
should.exist(t);
|
||||
});
|
||||
|
||||
|
||||
it('#selectUnspent should be able to select utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,1.0, true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
should.exist(u[0].amount);
|
||||
should.exist(u[0].txid);
|
||||
should.exist(u[0].scriptPubKey);
|
||||
should.exist(u[0].vout);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.5, true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.1, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.05, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.015, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.01, true);
|
||||
u.length.should.equal(1);
|
||||
});
|
||||
|
||||
it('#selectUnspent should return null if not enough utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,1.12);
|
||||
should.not.exist(u);
|
||||
});
|
||||
|
||||
|
||||
it('#selectUnspent should check confirmations', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9);
|
||||
should.not.exist(u);
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9,true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.11);
|
||||
u.length.should.equal(2);
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.111);
|
||||
should.not.exist(u);
|
||||
});
|
||||
|
||||
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
allowUnconfirmed: true,
|
||||
};
|
||||
|
||||
it('#create should be able to create instance', function() {
|
||||
var utxos =testdata.dataUnspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
should.exist(ret.tx);
|
||||
should.exist(ret.selectedUtxos);
|
||||
ret.selectedUtxos.length.should.equal(2);
|
||||
|
||||
var tx = ret.tx;
|
||||
|
||||
tx.version.should.equal(1);
|
||||
tx.ins.length.should.equal(2);
|
||||
tx.outs.length.should.equal(2);
|
||||
|
||||
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
||||
|
||||
// remainder is 0.0299 here because unspent select utxos in order
|
||||
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
|
||||
it('#create should fail if not enough inputs ', function() {
|
||||
var utxos =testdata.dataUnspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:80}];
|
||||
Transaction
|
||||
.create
|
||||
.bind(utxos, outs, opts)
|
||||
.should.throw();
|
||||
|
||||
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.5}];
|
||||
should.exist( Transaction.create(utxos, outs2, opts));
|
||||
|
||||
// do not allow unconfirmed
|
||||
Transaction.create.bind(utxos, outs2).should.throw();
|
||||
});
|
||||
|
||||
|
||||
it('#create should create same output as bitcoind createrawtransaction ', function() {
|
||||
var utxos =testdata.dataUnspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
var tx = ret.tx;
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08,"mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd":0.0299}'
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
|
||||
|
||||
});
|
||||
|
||||
it('#create should create same output as bitcoind createrawtransaction wo remainder', function() {
|
||||
var utxos =testdata.dataUnspent;
|
||||
// no remainder
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var ret = Transaction.create(utxos, outs, {fee:0.03} );
|
||||
var tx = ret.tx;
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
||||
//
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
||||
});
|
||||
|
||||
it('#createAndSign should sign a tx', function() {
|
||||
var utxos =testdata.dataUnspentSign.unspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx = ret.tx;
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
|
||||
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
||||
var ret2 = Transaction.createAndSign(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx2 = ret2.tx;
|
||||
tx2.isComplete().should.equal(true);
|
||||
tx2.ins.length.should.equal(3);
|
||||
tx2.outs.length.should.equal(2);
|
||||
});
|
||||
|
||||
it('#createAndSign should sign an incomplete tx ', function() {
|
||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||
var utxos =testdata.dataUnspentSign.unspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||
var tx = ret.tx;
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
});
|
||||
it('#isComplete should return TX signature status', function() {
|
||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||
var utxos =testdata.dataUnspentSign.unspent;
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||
var tx = ret.tx;
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.sign(ret.selectedUtxos, testdata.dataUnspentSign.keyStrings);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('#sign should sign a tx in multiple steps (case1)', function() {
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:1.08}];
|
||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||
var tx = ret.tx;
|
||||
var selectedUtxos = ret.selectedUtxos;
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
||||
|
||||
tx.isComplete().should.equal(false);
|
||||
|
||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||
|
||||
var k23 = testdata.dataUnspentSign.keyStrings.slice(1,3);
|
||||
tx.sign(selectedUtxos, k23).should.equal(true);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('#sign should sign a tx in multiple steps (case2)', function() {
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||
var tx = ret.tx;
|
||||
var selectedUtxos = ret.selectedUtxos;
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
||||
var k2 = testdata.dataUnspentSign.keyStrings.slice(1,2);
|
||||
var k3 = testdata.dataUnspentSign.keyStrings.slice(2,3);
|
||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||
tx.sign(selectedUtxos, k2).should.equal(false);
|
||||
tx.sign(selectedUtxos, k3).should.equal(true);
|
||||
|
||||
});
|
||||
|
||||
it('#createAndSign: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
|
||||
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
||||
//so, it should trigger adding a new 10BTC utxo
|
||||
var utxos =testdata.dataUnspentSign.unspent;
|
||||
var outs = [];
|
||||
var n =101;
|
||||
for (var i=0; i<n; i++) {
|
||||
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
||||
}
|
||||
|
||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx = ret.tx;
|
||||
tx.getSize().should.equal(3560);
|
||||
|
||||
// ins = 11.0101 BTC (2 inputs: 1.0101 + 10 );
|
||||
tx.ins.length.should.equal(2);
|
||||
// outs = 101 outs:
|
||||
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
|
||||
// remainder = 11.0101-1.0104 = 9.9997
|
||||
tx.outs.length.should.equal(102);
|
||||
util.valueToBigInt(tx.outs[n].v).cmp(999970000).should.equal(0);
|
||||
tx.isComplete().should.equal(true);
|
||||
|
||||
|
||||
//this is the complementary case, it does not trigger a new utxo
|
||||
var utxos =testdata.dataUnspentSign.unspent;
|
||||
var outs = [];
|
||||
var n =100;
|
||||
for (var i=0; i<n; i++) {
|
||||
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
||||
}
|
||||
|
||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx = ret.tx;
|
||||
tx.getSize().should.equal(3485);
|
||||
|
||||
// ins = 1.0101 BTC (1 inputs: 1.0101);
|
||||
tx.ins.length.should.equal(1);
|
||||
// outs = 100 outs:
|
||||
// 100 * 0.01 = 1BTC; + 0.0004 fee = 1.0004btc
|
||||
// remainder = 1.0101-1.0004 = 0.0097
|
||||
tx.outs.length.should.equal(101);
|
||||
util.valueToBigInt(tx.outs[n].v).cmp(970000).should.equal(0);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
// Read tests from test/data/tx_valid.json
|
||||
// Format is an array of arrays
|
||||
// Inner arrays are either [ "comment" ]
|
||||
|
|
|
@ -7,6 +7,8 @@ var dataTxValid = JSON.parse(fs.readFileSync('test/data/tx_valid.json'));
|
|||
var dataTxInvalid = JSON.parse(fs.readFileSync('test/data/tx_invalid.json'));
|
||||
var dataScriptValid = JSON.parse(fs.readFileSync('test/data/script_valid.json'));
|
||||
var dataScriptInvalid = JSON.parse(fs.readFileSync('test/data/script_invalid.json'));
|
||||
var dataUnspent = JSON.parse(fs.readFileSync('test/data/unspent.json'));
|
||||
var dataUnspentSign = JSON.parse(fs.readFileSync('test/data/unspentSign.json'));
|
||||
var dataSigCanonical = JSON.parse(fs.readFileSync('test/data/sig_canonical.json'));
|
||||
var dataSigNonCanonical = JSON.parse(fs.readFileSync('test/data/sig_noncanonical.json'));
|
||||
|
||||
|
@ -18,6 +20,8 @@ module.exports.dataTxInvalid = dataTxInvalid;
|
|||
module.exports.dataScriptValid = dataScriptValid;
|
||||
module.exports.dataScriptInvalid = dataScriptInvalid;
|
||||
module.exports.dataScriptAll = dataScriptValid.concat(dataScriptInvalid);
|
||||
module.exports.dataUnspent = dataUnspent;
|
||||
module.exports.dataUnspentSign = dataUnspentSign;
|
||||
|
||||
module.exports.dataSigCanonical = dataSigCanonical;
|
||||
module.exports.dataSigNonCanonical = dataSigNonCanonical;
|
||||
|
|
|
@ -188,6 +188,9 @@ function parseWholeValue(res)
|
|||
|
||||
exports.parseValue = function parseValue(valueStr)
|
||||
{
|
||||
if (typeof valueStr !== 'string')
|
||||
valueStr = valueStr.toString();
|
||||
|
||||
var res = valueStr.match(reFullVal);
|
||||
if (res)
|
||||
return parseFullValue(res);
|
||||
|
|
Loading…
Reference in New Issue