Merge pull request #1061 from eordano/refactor/signature
Add Signature subclass for Transaction Signatures
This commit is contained in:
commit
994ea957d4
|
@ -50,7 +50,7 @@ Signature.fromCompact = function(buf) {
|
||||||
return sig;
|
return sig;
|
||||||
};
|
};
|
||||||
|
|
||||||
Signature.fromDER = function(buf, strict) {
|
Signature.fromDER = Signature.fromBuffer = function(buf, strict) {
|
||||||
var obj = Signature.parseDER(buf, strict);
|
var obj = Signature.parseDER(buf, strict);
|
||||||
var sig = new Signature();
|
var sig = new Signature();
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,4 @@ module.exports = require('./transaction');
|
||||||
module.exports.Input = require('./input');
|
module.exports.Input = require('./input');
|
||||||
module.exports.Output = require('./output');
|
module.exports.Output = require('./output');
|
||||||
module.exports.UnspentOutput = require('./unspentoutput');
|
module.exports.UnspentOutput = require('./unspentoutput');
|
||||||
|
module.exports.Signature = require('./signature');
|
||||||
|
|
|
@ -11,6 +11,7 @@ var Signature = require('../../crypto/signature');
|
||||||
var Sighash = require('../sighash');
|
var Sighash = require('../sighash');
|
||||||
var PublicKey = require('../../publickey');
|
var PublicKey = require('../../publickey');
|
||||||
var BufferUtil = require('../../util/buffer');
|
var BufferUtil = require('../../util/buffer');
|
||||||
|
var TransactionSignature = require('../signature');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -45,14 +46,14 @@ MultiSigScriptHashInput.prototype._deserializeSignatures = function(signatures)
|
||||||
if (!signature) {
|
if (!signature) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {
|
return new TransactionSignature({
|
||||||
publicKey: new PublicKey(signature.publicKey),
|
publicKey: new PublicKey(signature.publicKey),
|
||||||
prevTxId: signature.txId,
|
prevTxId: signature.txId,
|
||||||
outputIndex: signature.outputIndex,
|
outputIndex: signature.outputIndex,
|
||||||
inputIndex: signature.inputIndex,
|
inputIndex: signature.inputIndex,
|
||||||
signature: Signature.fromString(signature.signature),
|
signature: Signature.fromString(signature.signature),
|
||||||
sigtype: signature.sigtype
|
sigtype: signature.sigtype
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,14 +62,14 @@ MultiSigScriptHashInput.prototype._serializeSignatures = function() {
|
||||||
if (!signature) {
|
if (!signature) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {
|
return new TransactionSignature({
|
||||||
publicKey: signature.publicKey.toString(),
|
publicKey: signature.publicKey.toString(),
|
||||||
prevTxId: signature.txId,
|
prevTxId: signature.txId,
|
||||||
outputIndex: signature.outputIndex,
|
outputIndex: signature.outputIndex,
|
||||||
inputIndex: signature.inputIndex,
|
inputIndex: signature.inputIndex,
|
||||||
signature: signature.signature.toString(),
|
signature: signature.signature.toString(),
|
||||||
sigtype: signature.sigtype
|
sigtype: signature.sigtype
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,14 +81,14 @@ MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateK
|
||||||
var results = [];
|
var results = [];
|
||||||
_.each(this.publicKeys, function(publicKey) {
|
_.each(this.publicKeys, function(publicKey) {
|
||||||
if (publicKey.toString() === privateKey.publicKey.toString()) {
|
if (publicKey.toString() === privateKey.publicKey.toString()) {
|
||||||
results.push({
|
results.push(new TransactionSignature({
|
||||||
publicKey: privateKey.publicKey,
|
publicKey: privateKey.publicKey,
|
||||||
prevTxId: self.txId,
|
prevTxId: self.prevTxId,
|
||||||
outputIndex: self.outputIndex,
|
outputIndex: self.outputIndex,
|
||||||
inputIndex: index,
|
inputIndex: index,
|
||||||
signature: Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript),
|
signature: Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript),
|
||||||
sigtype: sigtype
|
sigtype: sigtype
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
|
|
|
@ -11,6 +11,7 @@ var Output = require('../output');
|
||||||
var Sighash = require('../sighash');
|
var Sighash = require('../sighash');
|
||||||
var Script = require('../../script');
|
var Script = require('../../script');
|
||||||
var Signature = require('../../crypto/signature');
|
var Signature = require('../../crypto/signature');
|
||||||
|
var TransactionSignature = require('../signature');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a special kind of input of PayToPublicKeyHash kind.
|
* Represents a special kind of input of PayToPublicKeyHash kind.
|
||||||
|
@ -36,14 +37,14 @@ PublicKeyHashInput.prototype.getSignatures = function(transaction, privateKey, i
|
||||||
sigtype = sigtype || Signature.SIGHASH_ALL;
|
sigtype = sigtype || Signature.SIGHASH_ALL;
|
||||||
|
|
||||||
if (BufferUtil.equals(hashData, this.output.script.getPublicKeyHash())) {
|
if (BufferUtil.equals(hashData, this.output.script.getPublicKeyHash())) {
|
||||||
return [{
|
return [new TransactionSignature({
|
||||||
publicKey: privateKey.publicKey,
|
publicKey: privateKey.publicKey,
|
||||||
prevTxId: this.prevTxId,
|
prevTxId: this.prevTxId,
|
||||||
outputIndex: this.outputIndex,
|
outputIndex: this.outputIndex,
|
||||||
inputIndex: index,
|
inputIndex: index,
|
||||||
signature: Sighash.sign(transaction, privateKey, sigtype, index, this.output.script),
|
signature: Sighash.sign(transaction, privateKey, sigtype, index, this.output.script),
|
||||||
sigtype: sigtype
|
sigtype: sigtype
|
||||||
}];
|
})];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
var $ = require('../util/preconditions');
|
||||||
|
var inherits = require('inherits');
|
||||||
|
var BufferUtil = require('../util/buffer');
|
||||||
|
var JSUtil = require('../util/js');
|
||||||
|
|
||||||
|
var PublicKey = require('../publickey');
|
||||||
|
var errors = require('../errors');
|
||||||
|
var Signature = require('../crypto/signature');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Wrapper around Signature with fields related to signing a transaction specifically
|
||||||
|
*
|
||||||
|
* @param {Object|string|TransactionSignature} arg
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function TransactionSignature(arg) {
|
||||||
|
if (!(this instanceof TransactionSignature)) {
|
||||||
|
return new TransactionSignature(arg);
|
||||||
|
}
|
||||||
|
if (arg instanceof TransactionSignature) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
if (_.isString(arg)) {
|
||||||
|
if (JSUtil.isValidJSON(arg)) {
|
||||||
|
return TransactionSignature.fromJSON(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_.isObject(arg)) {
|
||||||
|
return this._fromObject(arg);
|
||||||
|
}
|
||||||
|
throw new errors.InvalidArgument('TransactionSignatures must be instantiated from an object');
|
||||||
|
}
|
||||||
|
inherits(TransactionSignature, Signature);
|
||||||
|
|
||||||
|
TransactionSignature.prototype._fromObject = function(arg) {
|
||||||
|
this._checkObjectArgs(arg);
|
||||||
|
this.publicKey = new PublicKey(arg.publicKey);
|
||||||
|
this.prevTxId = BufferUtil.isBuffer(arg.prevTxId) ? arg.prevTxId : new Buffer(arg.prevTxId, 'hex');
|
||||||
|
this.outputIndex = arg.outputIndex;
|
||||||
|
this.inputIndex = arg.inputIndex;
|
||||||
|
this.signature = (arg.signature instanceof Signature) ? arg.signature :
|
||||||
|
BufferUtil.isBuffer(arg.signature) ? Signature.fromBuffer(arg.signature) :
|
||||||
|
Signature.fromString(arg.signature);
|
||||||
|
this.sigtype = arg.sigtype;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSignature.prototype._checkObjectArgs = function(arg) {
|
||||||
|
$.checkArgument(PublicKey(arg.publicKey), 'publicKey');
|
||||||
|
$.checkArgument(!_.isUndefined(arg.inputIndex), 'inputIndex');
|
||||||
|
$.checkArgument(!_.isUndefined(arg.outputIndex), 'outputIndex');
|
||||||
|
$.checkState(_.isNumber(arg.inputIndex), 'inputIndex must be a number');
|
||||||
|
$.checkState(_.isNumber(arg.outputIndex), 'outputIndex must be a number');
|
||||||
|
$.checkArgument(arg.signature, 'signature');
|
||||||
|
$.checkArgument(arg.prevTxId, 'prevTxId');
|
||||||
|
$.checkState(arg.signature instanceof Signature ||
|
||||||
|
BufferUtil.isBuffer(arg.signature) ||
|
||||||
|
JSUtil.isHexa(arg.signature), 'signature must be a buffer or hexa value');
|
||||||
|
$.checkState(BufferUtil.isBuffer(arg.prevTxId) ||
|
||||||
|
JSUtil.isHexa(arg.prevTxId), 'prevTxId must be a buffer or hexa value');
|
||||||
|
$.checkArgument(arg.sigtype, 'sigtype');
|
||||||
|
$.checkState(_.isNumber(arg.sigtype), 'sigtype must be a number');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a transaction to a plain JS object
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
TransactionSignature.prototype.toObject = function() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey.toString(),
|
||||||
|
prevTxId: this.prevTxId.toString('hex'),
|
||||||
|
outputIndex: this.outputIndex,
|
||||||
|
inputIndex: this.inputIndex,
|
||||||
|
signature: this.signature.toString(),
|
||||||
|
sigtype: this.sigtype
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a transaction to a JSON string
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
TransactionSignature.prototype.toJSON = function() {
|
||||||
|
return JSON.stringify(this.toObject());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a TransactionSignature from a JSON string
|
||||||
|
* @param {string} json
|
||||||
|
* @return {TransactionSignature}
|
||||||
|
*/
|
||||||
|
TransactionSignature.fromJSON = function(json) {
|
||||||
|
return new TransactionSignature(JSON.parse(json));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a TransactionSignature from an object
|
||||||
|
* @param {Object} object
|
||||||
|
* @return {TransactionSignature}
|
||||||
|
*/
|
||||||
|
TransactionSignature.fromObject = function(object) {
|
||||||
|
$.checkArgument(object);
|
||||||
|
return new TransactionSignature(object);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = TransactionSignature;
|
|
@ -0,0 +1,132 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* jshint unused: false */
|
||||||
|
/* jshint latedef: false */
|
||||||
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var bitcore = require('../..');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var TransactionSignature = bitcore.Transaction.Signature;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var PrivateKey = bitcore.PrivateKey;
|
||||||
|
var errors = bitcore.errors;
|
||||||
|
|
||||||
|
describe('TransactionSignature', function() {
|
||||||
|
|
||||||
|
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||||
|
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||||
|
var simpleUtxoWith100000Satoshis = {
|
||||||
|
address: fromAddress,
|
||||||
|
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||||
|
satoshis: 100000
|
||||||
|
};
|
||||||
|
|
||||||
|
var getSignatureFromTransaction = function() {
|
||||||
|
var transaction = new Transaction();
|
||||||
|
transaction.from(simpleUtxoWith100000Satoshis);
|
||||||
|
return transaction.getSignatures(privateKey)[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
it('can be created without the `new` keyword', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
var serialized = signature.toObject();
|
||||||
|
var nonew = TransactionSignature(serialized);
|
||||||
|
expect(nonew.toObject()).to.deep.equal(serialized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be retrieved from Transaction#getSignatures', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
expect(signature instanceof TransactionSignature).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when trying to create from invalid arguments', function() {
|
||||||
|
expect(function() {
|
||||||
|
return new TransactionSignature();
|
||||||
|
}).to.throw(errors.InvalidArgument);
|
||||||
|
expect(function() {
|
||||||
|
return new TransactionSignature(1);
|
||||||
|
}).to.throw(errors.InvalidArgument);
|
||||||
|
expect(function() {
|
||||||
|
return new TransactionSignature('hello world');
|
||||||
|
}).to.throw(errors.InvalidArgument);
|
||||||
|
});
|
||||||
|
it('returns the same object if called with a TransactionSignature', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
expect(new TransactionSignature(signature)).to.equal(signature);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets returned by a P2SH multisig output', function() {
|
||||||
|
var private1 = new PrivateKey('6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976');
|
||||||
|
var private2 = new PrivateKey('c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890');
|
||||||
|
var public1 = private1.publicKey;
|
||||||
|
var public2 = private2.publicKey;
|
||||||
|
var utxo = {
|
||||||
|
txId: '0000000000000000000000000000000000000000000000000000000000000000', // Not relevant
|
||||||
|
outputIndex: 0,
|
||||||
|
script: Script.buildMultisigOut([public1, public2], 2).toScriptHashOut(),
|
||||||
|
satoshis: 100000
|
||||||
|
};
|
||||||
|
var transaction = new Transaction().from(utxo, [public1, public2], 2);
|
||||||
|
var signatures = transaction.getSignatures(private1);
|
||||||
|
expect(signatures[0] instanceof TransactionSignature).to.equal(true);
|
||||||
|
signatures = transaction.getSignatures(private2);
|
||||||
|
expect(signatures[0] instanceof TransactionSignature).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be aplied to a Transaction with Transaction#addSignature', function() {
|
||||||
|
var transaction = new Transaction();
|
||||||
|
transaction.from(simpleUtxoWith100000Satoshis);
|
||||||
|
var signature = transaction.getSignatures(privateKey)[0];
|
||||||
|
var addSignature = function() {
|
||||||
|
return transaction.applySignature(signature);
|
||||||
|
};
|
||||||
|
expect(signature instanceof TransactionSignature).to.equal(true);
|
||||||
|
expect(addSignature).to.not.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('serialization', function() {
|
||||||
|
it('serializes to an object and roundtrips correctly', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
var serialized = signature.toObject();
|
||||||
|
expect(new TransactionSignature(serialized).toObject()).to.deep.equal(serialized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be deserialized with fromObject', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
var serialized = signature.toObject();
|
||||||
|
expect(TransactionSignature.fromObject(serialized).toObject()).to.deep.equal(serialized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can deserialize when signature is a buffer', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
var serialized = signature.toObject();
|
||||||
|
serialized.signature = new Buffer(serialized.signature, 'hex');
|
||||||
|
expect(TransactionSignature.fromObject(serialized).toObject()).to.deep.equal(signature.toObject());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can roundtrip to/from json', function() {
|
||||||
|
var signature = getSignatureFromTransaction();
|
||||||
|
var serialized = signature.toObject();
|
||||||
|
var json = signature.toJSON();
|
||||||
|
expect(TransactionSignature(json).toObject()).to.deep.equal(serialized);
|
||||||
|
expect(TransactionSignature.fromJSON(json).toObject()).to.deep.equal(serialized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can parse a previously known json string', function() {
|
||||||
|
expect(JSON.parse(TransactionSignature(testJSON).toJSON())).to.deep.equal(JSON.parse(testJSON));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can deserialize a previously known object', function() {
|
||||||
|
expect(TransactionSignature(testObject).toObject()).to.deep.equal(testObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* jshint maxlen: 500 */
|
||||||
|
var testJSON = '{"publicKey":"0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e","prevTxId":"a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458","outputIndex":0,"inputIndex":0,"signature":"3045022100c728eac064154edba15d4f3e6cbd9be6da3498f80a783ab3391f992b4d9d71ca0220729eff4564dc06aa1d80ab73100540fe5ebb6f280b4a87bc32399f861a7b2563","sigtype":1}';
|
||||||
|
var testObject = JSON.parse(testJSON);
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue