add TX signing. Support to p2pubkeyhash

This commit is contained in:
Matias Alejo Garcia 2014-03-15 21:12:23 -03:00
parent a6463a3835
commit a2041d5790
3 changed files with 145 additions and 11 deletions

View File

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

View File

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

View File

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