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 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 DEFAULT_FEE = 0.0001;
|
||||
|
@ -780,12 +782,12 @@ Transaction._scriptForAddress = function (addressString) {
|
|||
*
|
||||
* (TODO: should we use uBTC already?)
|
||||
*
|
||||
* If not remainderAddress is given, and there is a remainderAddress
|
||||
* first in address will be used. (TODO: is this is reasoable?)
|
||||
* If no remainderAddress is given, and there is a remainderAddress
|
||||
* 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) {
|
||||
|
@ -798,6 +800,8 @@ Transaction.create = function (ins, outs, opts) {
|
|||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
var inputMap = [];
|
||||
|
||||
var l = ins.length;
|
||||
var valueInSat = bignum(0);
|
||||
for(var i=0; i<l; i++) {
|
||||
|
@ -816,6 +820,10 @@ Transaction.create = function (ins, outs, opts) {
|
|||
|
||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||
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 =
|
||||
function TransactionInputsCache(tx)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
"scriptPubKey": "76a914fe021bac469a5c49915b2a8ffa7390a9ce5580f988ac",
|
||||
"vout": 1,
|
||||
"amount": 1.01,
|
||||
"confirmations":7
|
||||
|
@ -11,7 +11,7 @@
|
|||
{
|
||||
"address": "mhNCT9TwZAGF1tLPpZdqfkTmtBkY282YDW",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
||||
"scriptPubKey": "76a9141448534cb1a1ec44665b0eb2326e570814afe3f188ac",
|
||||
"vout": 0,
|
||||
"confirmations": 1,
|
||||
"amount": 10
|
||||
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"address": "n44hn28zAooZpn8mpWKzATbabqaHDK9oNJ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
||||
"scriptPubKey": "76a914f753f58b1fb1daaa5534b10af85ca9210f3445d288ac",
|
||||
"vout": 3,
|
||||
"confirmations": 0,
|
||||
"amount": 5
|
||||
|
|
|
@ -116,13 +116,42 @@ describe('Transaction', function() {
|
|||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
||||
});
|
||||
|
||||
it.skip('#sign should sign a tx', function() {
|
||||
var utxos = Transaction.selectUnspent(testdata.dataUnspentign.unspent,0.1);
|
||||
it('#sign should 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(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
|
||||
// Format is an array of arrays
|
||||
|
|
Loading…
Reference in New Issue