Manuel Aráoz
10 years ago
6 changed files with 256 additions and 10 deletions
@ -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