add TX signing. Support to p2pubkeyhash
This commit is contained in:
parent
a6463a3835
commit
a2041d5790
117
Transaction.js
117
Transaction.js
|
@ -12,6 +12,8 @@ var Step = imports.Step || require('step');
|
||||||
var buffertools = imports.buffertools || require('buffertools');
|
var buffertools = imports.buffertools || require('buffertools');
|
||||||
var error = imports.error || require('./util/error');
|
var error = imports.error || require('./util/error');
|
||||||
var networks = imports.networks || require('./networks');
|
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 COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]);
|
||||||
var DEFAULT_FEE = 0.0001;
|
var DEFAULT_FEE = 0.0001;
|
||||||
|
@ -780,12 +782,12 @@ Transaction._scriptForAddress = function (addressString) {
|
||||||
*
|
*
|
||||||
* (TODO: should we use uBTC already?)
|
* (TODO: should we use uBTC already?)
|
||||||
*
|
*
|
||||||
* If not remainderAddress is given, and there is a remainderAddress
|
* If no remainderAddress is given, and there is a remainderAddress
|
||||||
* first in address will be used. (TODO: is this is reasoable?)
|
* first in address will be used. (TODO: is this is reasonable?)
|
||||||
|
*
|
||||||
|
* The address from the inputs will be added to the Transaction object
|
||||||
|
* for latter signing
|
||||||
*
|
*
|
||||||
* TODO add exceptions for validations:
|
|
||||||
* out address - ins amount > out amount - fees < maxFees
|
|
||||||
* + more?
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Transaction.create = function (ins, outs, opts) {
|
Transaction.create = function (ins, outs, opts) {
|
||||||
|
@ -798,6 +800,8 @@ Transaction.create = function (ins, outs, opts) {
|
||||||
txobj.ins = [];
|
txobj.ins = [];
|
||||||
txobj.outs = [];
|
txobj.outs = [];
|
||||||
|
|
||||||
|
var inputMap = [];
|
||||||
|
|
||||||
var l = ins.length;
|
var l = ins.length;
|
||||||
var valueInSat = bignum(0);
|
var valueInSat = bignum(0);
|
||||||
for(var i=0; i<l; i++) {
|
for(var i=0; i<l; i++) {
|
||||||
|
@ -816,6 +820,10 @@ Transaction.create = function (ins, outs, opts) {
|
||||||
|
|
||||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||||
txobj.ins.push(txin);
|
txobj.ins.push(txin);
|
||||||
|
inputMap[i]= {
|
||||||
|
address: ins[i].address,
|
||||||
|
scriptPubKey: ins[i].scriptPubKey
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -860,9 +868,106 @@ Transaction.create = function (ins, outs, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Transaction(txobj);
|
var tx = new Transaction(txobj);
|
||||||
|
tx.inputMap = inputMap;
|
||||||
|
return tx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sign
|
||||||
|
*
|
||||||
|
* signs the transaction
|
||||||
|
*
|
||||||
|
* @keypairs
|
||||||
|
* an array of strings representing private keys to sign the
|
||||||
|
* transaction in WIF private key format OR WalletKey objects
|
||||||
|
*
|
||||||
|
* @opts
|
||||||
|
* signhash: Transaction.SIGHASH_ALL
|
||||||
|
*
|
||||||
|
* Return the 'completeness' status of the tx (i.e, if all inputs are signed).
|
||||||
|
*
|
||||||
|
* To sign a TX, the TX must contain .inputMap to match private keys
|
||||||
|
* with inputs and scriptPubKey
|
||||||
|
*/
|
||||||
|
|
||||||
|
Transaction.prototype.sign = function (keys, opts) {
|
||||||
|
var self = this;
|
||||||
|
var complete = false;
|
||||||
|
var m = keys.length;
|
||||||
|
opts = opts || {};
|
||||||
|
var signhash = opts.signhash || SIGHASH_ALL;
|
||||||
|
|
||||||
|
if (!self.inputMap) {
|
||||||
|
throw new Error('this TX does not have information about input address, cannot be signed');
|
||||||
|
}
|
||||||
|
|
||||||
|
//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[self.inputMap[i].address];
|
||||||
|
|
||||||
|
if (typeof wk === 'undefined') {
|
||||||
|
if ( buffertools.compare(aIn.s,util.EMPTY_BUFFER)!==0 )
|
||||||
|
inputSigned++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var scriptBuf = new Buffer(self.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;
|
||||||
|
do {
|
||||||
|
sigRaw = wk.privKey.signSync(txSigHash);
|
||||||
|
} while ( wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false );
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var TransactionInputsCache = exports.TransactionInputsCache =
|
var TransactionInputsCache = exports.TransactionInputsCache =
|
||||||
function TransactionInputsCache(tx)
|
function TransactionInputsCache(tx)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{
|
{
|
||||||
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
"amount": 1.01,
|
"amount": 1.01,
|
||||||
"confirmations":7
|
"confirmations":7
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
{
|
{
|
||||||
"address": "mhNCT9TwZAGF1tLPpZdqfkTmtBkY282YDW",
|
"address": "mhNCT9TwZAGF1tLPpZdqfkTmtBkY282YDW",
|
||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
"scriptPubKey": "76a9141448534cb1a1ec44665b0eb2326e570814afe3f188ac",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"confirmations": 1,
|
"confirmations": 1,
|
||||||
"amount": 10
|
"amount": 10
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
{
|
{
|
||||||
"address": "n44hn28zAooZpn8mpWKzATbabqaHDK9oNJ",
|
"address": "n44hn28zAooZpn8mpWKzATbabqaHDK9oNJ",
|
||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
"scriptPubKey": "76a914f753f58b1fb1daaa5534b10af85ca9210f3445d288ac",
|
||||||
"vout": 3,
|
"vout": 3,
|
||||||
"confirmations": 0,
|
"confirmations": 0,
|
||||||
"amount": 5
|
"amount": 5
|
||||||
|
|
|
@ -116,13 +116,42 @@ describe('Transaction', function() {
|
||||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('#sign should sign a tx', function() {
|
it('#sign should sign a tx', function() {
|
||||||
var utxos = Transaction.selectUnspent(testdata.dataUnspentign.unspent,0.1);
|
var utxos = Transaction.selectUnspent(testdata.dataUnspentSign.unspent,0.1);
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||||
var tx = Transaction.create(utxos, outs, opts);
|
var tx = Transaction.create(utxos, outs, opts);
|
||||||
|
tx.sign(testdata.dataUnspentSign.keyStrings).should.equal(true);
|
||||||
|
|
||||||
|
var utxos2 = Transaction.selectUnspent(testdata.dataUnspentSign.unspent,16, true);
|
||||||
|
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||||
|
var tx2 = Transaction.create(utxos2, outs2, opts);
|
||||||
|
tx2.sign(testdata.dataUnspentSign.keyStrings).should.equal(true);
|
||||||
});
|
});
|
||||||
|
it('#sign should fail to sign a tx', function() {
|
||||||
|
var utxos = Transaction.selectUnspent(testdata.dataUnspentSign.unspent,0.1);
|
||||||
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||||
|
var tx = Transaction.create(utxos, outs, opts);
|
||||||
|
tx.sign(['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ']).should.equal(false);
|
||||||
|
});
|
||||||
|
it('#sign should sign a tx in multiple steps', function() {
|
||||||
|
var utxos = Transaction.selectUnspent(testdata.dataUnspentSign.unspent,13, true);
|
||||||
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||||
|
|
||||||
|
var tx = Transaction.create(utxos, outs, opts);
|
||||||
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
||||||
|
var k23 = testdata.dataUnspentSign.keyStrings.slice(1,3);
|
||||||
|
tx.sign(k1).should.equal(false);
|
||||||
|
tx.sign(k23).should.equal(true);
|
||||||
|
|
||||||
|
var tx2 = Transaction.create(utxos, outs, opts);
|
||||||
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
||||||
|
var k2 = testdata.dataUnspentSign.keyStrings.slice(1,2);
|
||||||
|
var k3 = testdata.dataUnspentSign.keyStrings.slice(2,3);
|
||||||
|
tx2.sign(k1).should.equal(false);
|
||||||
|
tx2.sign(k2).should.equal(false);
|
||||||
|
tx2.sign(k3).should.equal(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
// Read tests from test/data/tx_valid.json
|
// Read tests from test/data/tx_valid.json
|
||||||
// Format is an array of arrays
|
// Format is an array of arrays
|
||||||
|
|
Loading…
Reference in New Issue