Modify Transaction to use Multisig
* Allow `Script#add` to add a Script causing concatenation of opcodes * Add `Script#equals` to compare scripts * Add `Script#fromAddress` * Drop `_payTo` methods * Add `Script.buildP2SHMultisigIn` Greatly simplifying the internal transaction object
This commit is contained in:
parent
1535805f1c
commit
de4d2884c7
|
@ -59,7 +59,7 @@ function Address(data, network, type) {
|
|||
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'PublicKey')) {
|
||||
info = Address._transformPublicKey(data);
|
||||
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'Script')) {
|
||||
info = Address._transformScript(data);
|
||||
info = Address._transformScript(data, network);
|
||||
} else if (data instanceof Address) {
|
||||
return data;
|
||||
} else if (typeof(data) === 'string') {
|
||||
|
@ -204,11 +204,12 @@ Address._transformPublicKey = function(pubkey){
|
|||
* @returns {Object} An object with keys: hashBuffer, type
|
||||
* @private
|
||||
*/
|
||||
Address._transformScript = function(script){
|
||||
Address._transformScript = function(script, network){
|
||||
var info = {};
|
||||
if (!script.constructor || (script.constructor.name && script.constructor.name !== 'Script')) {
|
||||
throw new TypeError('Address must be an instance of Script.');
|
||||
}
|
||||
info.network = network || Networks.defaultNetwork;
|
||||
info.hashBuffer = Hash.sha256ripemd160(script.toBuffer());
|
||||
info.type = Address.PayToScriptHash;
|
||||
return info;
|
||||
|
@ -277,7 +278,7 @@ Address.fromScriptHash = function(hash, network) {
|
|||
* @returns {Address} A new valid and frozen instance of an Address
|
||||
*/
|
||||
Address.fromScript = function(script, network) {
|
||||
var info = Address._transformScript(script);
|
||||
var info = Address._transformScript(script, network);
|
||||
return new Address(info.hashBuffer, network, info.type);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var BN = require('./bn');
|
||||
var BufferReader = require('../encoding/bufferreader');
|
||||
var Point = require('./point');
|
||||
var Signature = require('./signature');
|
||||
var PublicKey = require('../publickey');
|
||||
|
|
|
@ -38,6 +38,12 @@ BufferReader.prototype.read = function(len) {
|
|||
return buf;
|
||||
};
|
||||
|
||||
BufferReader.prototype.readAll = function() {
|
||||
var buf = this.buf.slice(this.pos, this.buf.length);
|
||||
this.pos = this.buf.length;
|
||||
return buf;
|
||||
};
|
||||
|
||||
BufferReader.prototype.readUInt8 = function() {
|
||||
var val = this.buf.readUInt8(this.pos);
|
||||
this.pos = this.pos + 1;
|
||||
|
|
|
@ -34,6 +34,13 @@ module.exports = [{
|
|||
}, {
|
||||
name: 'InvalidArgumentType',
|
||||
message: format('Invalid Argument for {2}, expected {1} but got ') + '+ typeof arguments[0]',
|
||||
}, {
|
||||
name: 'Script',
|
||||
message: format('Internal Error on Script {0}'),
|
||||
errors: [{
|
||||
name: 'UnrecognizedAddress',
|
||||
message: format('Expected argument {0} to be an address')
|
||||
}]
|
||||
}, {
|
||||
name: 'HDPrivateKey',
|
||||
message: format('Internal Error on HDPrivateKey {0}'),
|
||||
|
|
|
@ -38,6 +38,9 @@ var PrivateKey = function PrivateKey(data, network, compressed) {
|
|||
if (!(this instanceof PrivateKey)) {
|
||||
return new PrivateKey(data, network, compressed);
|
||||
}
|
||||
if (data instanceof PrivateKey) {
|
||||
return data;
|
||||
}
|
||||
|
||||
var info = {
|
||||
compressed: typeof(compressed) !== 'undefined' ? compressed : true,
|
||||
|
|
|
@ -11,8 +11,9 @@ var Signature = require('./crypto/signature');
|
|||
|
||||
var $ = require('./util/preconditions');
|
||||
var _ = require('lodash');
|
||||
var errors = require('./errors');
|
||||
var buffer = require('buffer');
|
||||
var bufferUtil = require('./util/buffer');
|
||||
var BufferUtil = require('./util/buffer');
|
||||
var jsUtil = require('./util/js');
|
||||
|
||||
/**
|
||||
|
@ -31,8 +32,10 @@ var Script = function Script(from) {
|
|||
|
||||
this.chunks = [];
|
||||
|
||||
if (bufferUtil.isBuffer(from)) {
|
||||
if (BufferUtil.isBuffer(from)) {
|
||||
return Script.fromBuffer(from);
|
||||
} else if (from instanceof Address) {
|
||||
return Script.fromAddress(from);
|
||||
} else if (from instanceof Script) {
|
||||
return Script.fromBuffer(from.toBuffer());
|
||||
} else if (typeof from === 'string') {
|
||||
|
@ -236,7 +239,7 @@ Script.prototype.getPublicKeyHash = function() {
|
|||
*/
|
||||
Script.prototype.isPublicKeyOut = function() {
|
||||
return this.chunks.length === 2 &&
|
||||
bufferUtil.isBuffer(this.chunks[0].buf) &&
|
||||
BufferUtil.isBuffer(this.chunks[0].buf) &&
|
||||
PublicKey.isValid(this.chunks[0].buf) &&
|
||||
this.chunks[1].opcodenum === Opcode.OP_CHECKSIG;
|
||||
};
|
||||
|
@ -246,7 +249,7 @@ Script.prototype.isPublicKeyOut = function() {
|
|||
*/
|
||||
Script.prototype.isPublicKeyIn = function() {
|
||||
return this.chunks.length === 1 &&
|
||||
bufferUtil.isBuffer(this.chunks[0].buf) &&
|
||||
BufferUtil.isBuffer(this.chunks[0].buf) &&
|
||||
this.chunks[0].buf.length === 0x47;
|
||||
};
|
||||
|
||||
|
@ -290,7 +293,7 @@ Script.prototype.isMultisigOut = function() {
|
|||
return (this.chunks.length > 3 &&
|
||||
Opcode.isSmallIntOp(this.chunks[0].opcodenum) &&
|
||||
this.chunks.slice(1, this.chunks.length - 2).every(function(obj) {
|
||||
return obj.buf && bufferUtil.isBuffer(obj.buf);
|
||||
return obj.buf && BufferUtil.isBuffer(obj.buf);
|
||||
}) &&
|
||||
Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2].opcodenum) &&
|
||||
this.chunks[this.chunks.length - 1].opcodenum === Opcode.OP_CHECKMULTISIG);
|
||||
|
@ -305,7 +308,7 @@ Script.prototype.isMultisigIn = function() {
|
|||
this.chunks[0].opcodenum === 0 &&
|
||||
this.chunks.slice(1, this.chunks.length).every(function(obj) {
|
||||
return obj.buf &&
|
||||
bufferUtil.isBuffer(obj.buf) &&
|
||||
BufferUtil.isBuffer(obj.buf) &&
|
||||
obj.buf.length === 0x47;
|
||||
});
|
||||
};
|
||||
|
@ -392,6 +395,30 @@ Script.prototype.prepend = function(obj) {
|
|||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares a script with another script
|
||||
*/
|
||||
Script.prototype.equals = function(script) {
|
||||
$.checkState(script instanceof Script, 'Must provide another script');
|
||||
if (this.chunks.length !== script.chunks.length) {
|
||||
return false;
|
||||
}
|
||||
var i;
|
||||
for (i = 0; i < this.chunks.length; i++) {
|
||||
if (BufferUtil.isBuffer(this.chunks[i]) && !BufferUtil.isBuffer(script.chunks[i])) {
|
||||
return false;
|
||||
} else if (this.chunks[i] instanceof Opcode && !(script.chunks[i] instanceof Opcode)) {
|
||||
return false;
|
||||
}
|
||||
if (BufferUtil.isBuffer(this.chunks[i]) && !BufferUtil.equals(this.chunks[i], script.chunks[i])) {
|
||||
return false;
|
||||
} else if (this.chunks[i].num !== script.chunks[i].num) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a script element to the end of the script.
|
||||
*
|
||||
|
@ -411,10 +438,12 @@ Script.prototype._addByType = function(obj, prepend) {
|
|||
this._addOpcode(obj, prepend);
|
||||
} else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') {
|
||||
this._addOpcode(obj, prepend);
|
||||
} else if (bufferUtil.isBuffer(obj)) {
|
||||
} else if (BufferUtil.isBuffer(obj)) {
|
||||
this._addBuffer(obj, prepend);
|
||||
} else if (typeof obj === 'object') {
|
||||
this._insertAtPosition(obj, prepend);
|
||||
} else if (obj instanceof Script) {
|
||||
this.chunks = this.chunks.concat(obj.chunks);
|
||||
} else {
|
||||
throw new Error('Invalid script chunk');
|
||||
}
|
||||
|
@ -493,6 +522,7 @@ Script.buildMultisigOut = function(pubkeys, m, opts) {
|
|||
opts = opts || {};
|
||||
var s = new Script();
|
||||
s.add(Opcode.smallInt(m));
|
||||
pubkeys = _.map(pubkeys, function(pubkey) { return PublicKey(pubkey); });
|
||||
var sorted = pubkeys;
|
||||
if (!opts.noSorting) {
|
||||
sorted = _.sortBy(pubkeys, function(pubkey) {
|
||||
|
@ -508,6 +538,29 @@ Script.buildMultisigOut = function(pubkeys, m, opts) {
|
|||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* A new P2SH Multisig input script for the given public keys, requiring m of those public keys to spend
|
||||
*
|
||||
* @param {PublicKey[]} pubkeys list of all public keys controlling the output
|
||||
* @param {number} threshold amount of required signatures to spend the output
|
||||
* @param {Array} signatures signatures to append to the script
|
||||
* @param {Object=} opts
|
||||
* @param {boolean=false} opts.noSorting don't sort the given public keys before creating the script
|
||||
* @param {Script=} opts.cachedMultisig don't recalculate the redeemScript
|
||||
*
|
||||
* @returns Script
|
||||
*/
|
||||
Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) {
|
||||
opts = opts || {};
|
||||
var s = new Script();
|
||||
s.add(Opcode.OP_0);
|
||||
_.each(signatures, function(signature) {
|
||||
s.add(signature);
|
||||
});
|
||||
s.add((opts.cachedMultisig || Script.buildMultisigOut(pubkeys, threshold, opts)).toBuffer());
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns a new pay to public key hash output for the given
|
||||
* address or public key
|
||||
|
@ -575,9 +628,9 @@ Script.buildScriptHashOut = function(script) {
|
|||
*/
|
||||
Script.buildPublicKeyHashIn = function(publicKey, signature, sigtype) {
|
||||
var script = new Script()
|
||||
.add(bufferUtil.concat([
|
||||
.add(BufferUtil.concat([
|
||||
signature,
|
||||
bufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL)
|
||||
BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL)
|
||||
]))
|
||||
.add(new PublicKey(publicKey).toBuffer());
|
||||
return script;
|
||||
|
@ -597,4 +650,17 @@ Script.prototype.toScriptHashOut = function() {
|
|||
return Script.buildScriptHashOut(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return Script a script built from the address
|
||||
*/
|
||||
Script.fromAddress = function(address) {
|
||||
address = Address(address);
|
||||
if (address.isPayToScriptHash()) {
|
||||
return Script.buildScriptHashOut(address);
|
||||
} else if (address.isPayToPublicKeyHash()) {
|
||||
return Script.buildPublicKeyHashOut(address);
|
||||
}
|
||||
throw new errors.Script.UnrecognizedAddress(address);
|
||||
};
|
||||
|
||||
module.exports = Script;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
'use strict';
|
||||
module.exports = require('./input');
|
||||
|
||||
var Input = require('./input');
|
||||
|
||||
Input.PublicKeyHash = require('./publicKeyHash');
|
||||
Input.ScriptHash = require('./scriptHash');
|
||||
|
||||
module.exports = Input;
|
||||
module.exports.PublicKeyHash = require('./publickeyhash');
|
||||
module.exports.MultiSigScriptHash = require('./multisigscripthash.js');
|
||||
|
|
|
@ -7,6 +7,7 @@ var buffer = require('buffer');
|
|||
var bufferUtil = require('../../util/buffer');
|
||||
var JSUtil = require('../../util/js');
|
||||
var Script = require('../../script');
|
||||
var Sighash = require('../sighash');
|
||||
|
||||
function Input(params) {
|
||||
if (!(this instanceof Input)) {
|
||||
|
@ -123,4 +124,24 @@ Input.prototype.getSignatures = function() {
|
|||
throw new errors.AbstractMethodInvoked('Input#getSignatures');
|
||||
};
|
||||
|
||||
Input.prototype.isFullySigned = function() {
|
||||
throw new errors.AbstractMethodInvoked('Input#isFullySigned');
|
||||
};
|
||||
|
||||
Input.prototype.addSignature = function() {
|
||||
throw new errors.AbstractMethodInvoked('Input#addSignature');
|
||||
};
|
||||
|
||||
Input.prototype.isValidSignature = function(transaction, signature) {
|
||||
// FIXME: Refactor signature so this is not necessary
|
||||
signature.signature.nhashtype = signature.sigtype;
|
||||
return Sighash.verify(
|
||||
transaction,
|
||||
signature.signature,
|
||||
signature.publicKey,
|
||||
signature.inputIndex,
|
||||
this.output.script
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = Input;
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var JSUtil = require('../../util/js');
|
||||
var inherits = require('inherits');
|
||||
var Input = require('./input');
|
||||
var Output = require('../output');
|
||||
var $ = require('../../util/preconditions');
|
||||
|
||||
var Script = require('../../script');
|
||||
var Signature = require('../../crypto/signature');
|
||||
var Sighash = require('../sighash');
|
||||
var BufferUtil = require('../../util/buffer');
|
||||
|
||||
function MultiSigScriptHashInput(input, pubkeys, threshold) {
|
||||
Input.apply(this, arguments);
|
||||
var self = this;
|
||||
this.publicKeys = _.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); });
|
||||
this.redeemScript = Script.buildMultisigOut(this.publicKeys, threshold);
|
||||
$.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script),
|
||||
'Provided public keys don\'t hash to the provided output');
|
||||
this.publicKeyIndex = {};
|
||||
_.each(this.publicKeys, function(publicKey, index) {
|
||||
self.publicKeyIndex[publicKey.toString()] = index;
|
||||
});
|
||||
this.threshold = threshold;
|
||||
// Empty array of signatures
|
||||
this.signatures = new Array(this.publicKeys.length);
|
||||
}
|
||||
inherits(MultiSigScriptHashInput, Input);
|
||||
|
||||
MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) {
|
||||
$.checkState(this.output instanceof Output);
|
||||
sigtype = sigtype || Signature.SIGHASH_ALL;
|
||||
|
||||
var self = this;
|
||||
var results = [];
|
||||
_.each(this.publicKeys, function(publicKey) {
|
||||
if (publicKey.toString() === privateKey.publicKey.toString()) {
|
||||
results.push({
|
||||
publicKey: privateKey.publicKey,
|
||||
prevTxId: self.txId,
|
||||
outputIndex: self.outputIndex,
|
||||
inputIndex: index,
|
||||
signature: Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript),
|
||||
sigtype: sigtype
|
||||
});
|
||||
}
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype.addSignature = function(transaction, signature) {
|
||||
$.checkState(!this.isFullySigned(), 'All needed signatures have already been added');
|
||||
$.checkArgument(!_.isUndefined(this.publicKeyIndex[signature.publicKey.toString()]),
|
||||
'Signature has no matching public key');
|
||||
$.checkState(this.isValidSignature(transaction, signature));
|
||||
this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature;
|
||||
this._updateScript();
|
||||
return this;
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype._updateScript = function() {
|
||||
this.setScript(Script.buildP2SHMultisigIn(
|
||||
this.publicKeys,
|
||||
this.threshold,
|
||||
this._createSignatures(),
|
||||
{ cachedMultisig: this.redeemScript }
|
||||
));
|
||||
return this;
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype._createSignatures = function() {
|
||||
var reverseOrder = JSUtil.cloneArray(this.signatures).reverse();
|
||||
return _.map(
|
||||
_.filter(reverseOrder, function(signature) { return !_.isUndefined(signature); }),
|
||||
function(signature) {
|
||||
return BufferUtil.concat([
|
||||
signature.signature.toDER(),
|
||||
BufferUtil.integerAsSingleByteBuffer(signature.sigtype)
|
||||
]);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype.clearSignatures = function() {
|
||||
this.signatures = new Array(this.publicKeys.length);
|
||||
this._updateScript();
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype.isFullySigned = function() {
|
||||
var count = _.reduce(this.signatures, function(sum, signature) { return sum + (!!signature); }, 0);
|
||||
return count === this.threshold;
|
||||
};
|
||||
|
||||
MultiSigScriptHashInput.prototype.isValidSignature = function(transaction, signature) {
|
||||
// FIXME: Refactor signature so this is not necessary
|
||||
signature.signature.nhashtype = signature.sigtype;
|
||||
return Sighash.verify(
|
||||
transaction,
|
||||
signature.signature,
|
||||
signature.publicKey,
|
||||
signature.inputIndex,
|
||||
this.redeemScript
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = MultiSigScriptHashInput;
|
|
@ -57,7 +57,8 @@ PublicKeyHashInput.prototype.getSignatures = function(transaction, privateKey, i
|
|||
* @param {number=Signature.SIGHASH_ALL} signature.sigtype
|
||||
* @return {PublicKeyHashInput} this, for chaining
|
||||
*/
|
||||
PublicKeyHashInput.prototype.addSignature = function(signature) {
|
||||
PublicKeyHashInput.prototype.addSignature = function(transaction, signature) {
|
||||
$.checkState(this.isValidSignature(transaction, signature), 'Signature is invalid');
|
||||
this.setScript(Script.buildPublicKeyHashIn(
|
||||
signature.publicKey,
|
||||
signature.signature.toDER(),
|
|
@ -1,19 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var inherits = require('inherits');
|
||||
var Input = require('./input');
|
||||
var Hash = require('../../crypto/hash');
|
||||
var Signature = require('../../crypto/signature');
|
||||
var Sighash = require('../sighash');
|
||||
var BufferUtil = require('../../util/buffer');
|
||||
|
||||
function ScriptHashInput() {
|
||||
Input.apply(this, arguments);
|
||||
}
|
||||
inherits(ScriptHashInput, Input);
|
||||
|
||||
ScriptHashInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype, hashData) {
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = ScriptHashInput;
|
|
@ -4,7 +4,6 @@ var buffer = require('buffer');
|
|||
|
||||
var Signature = require('../crypto/signature');
|
||||
var Script = require('../script');
|
||||
var Input = require('./input');
|
||||
var Output = require('./output');
|
||||
var BufferReader = require('../encoding/bufferreader');
|
||||
var BufferWriter = require('../encoding/bufferwriter');
|
||||
|
@ -25,9 +24,11 @@ var BITS_64_ON = 'ffffffffffffffff';
|
|||
* @param {Script} subscript the script that will be signed
|
||||
*/
|
||||
function sighash(transaction, sighashType, inputNumber, subscript) {
|
||||
var Transaction = require('./transaction');
|
||||
var Input = require('./input');
|
||||
|
||||
var i;
|
||||
// Copy transaction
|
||||
var Transaction = require('./transaction');
|
||||
var txcopy = Transaction.shallowCopy(transaction);
|
||||
|
||||
// Copy script
|
||||
|
@ -88,12 +89,14 @@ function sighash(transaction, sighashType, inputNumber, subscript) {
|
|||
|
||||
function sign(transaction, keypair, nhashtype, nin, subscript) {
|
||||
var hashbuf = sighash(transaction, nhashtype, nin, subscript);
|
||||
hashbuf = new BufferReader(hashbuf).readReverse();
|
||||
var sig = ECDSA.sign(hashbuf, keypair, 'little').set({nhashtype: nhashtype});
|
||||
return sig;
|
||||
}
|
||||
|
||||
function verify(transaction, sig, pubkey, nin, subscript) {
|
||||
var hashbuf = sighash(transaction, sig.nhashtype, nin, subscript);
|
||||
hashbuf = new BufferReader(hashbuf).readReverse();
|
||||
return ECDSA.verify(hashbuf, sig, pubkey, 'little');
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,13 @@ var JSUtil = require('../util/js');
|
|||
var BufferReader = require('../encoding/bufferreader');
|
||||
var BufferWriter = require('../encoding/bufferwriter');
|
||||
var Hash = require('../crypto/hash');
|
||||
var Sighash = require('./sighash');
|
||||
var Signature = require('../crypto/signature');
|
||||
|
||||
var errors = require('../errors');
|
||||
|
||||
var Address = require('../address');
|
||||
var Unit = require('../unit');
|
||||
var Input = require('./input');
|
||||
var PublicKeyHashInput = Input.PublicKeyHash;
|
||||
var MultiSigScriptHashInput = Input.MultiSigScriptHash;
|
||||
var Output = require('./output');
|
||||
var Script = require('../script');
|
||||
var PrivateKey = require('../privatekey');
|
||||
|
@ -26,16 +25,20 @@ var CURRENT_VERSION = 1;
|
|||
var DEFAULT_NLOCKTIME = 0;
|
||||
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
||||
|
||||
/**
|
||||
* Represents a transaction, a set of inputs and outputs to change
|
||||
* ownership of tokens
|
||||
*
|
||||
* @param {*} serialized
|
||||
*/
|
||||
function Transaction(serialized) {
|
||||
if (!(this instanceof Transaction)) {
|
||||
return new Transaction(serialized);
|
||||
}
|
||||
this.inputs = [];
|
||||
this.outputs = [];
|
||||
this._outpoints = [];
|
||||
this._inputAmount = 0;
|
||||
this._outputAmount = 0;
|
||||
this._signatures = {};
|
||||
|
||||
if (serialized) {
|
||||
if (serialized instanceof Transaction) {
|
||||
|
@ -54,6 +57,13 @@ function Transaction(serialized) {
|
|||
|
||||
/* Constructors and Serialization */
|
||||
|
||||
/**
|
||||
* Create a "shallow" copy of the transaction, by serializing and deserializing
|
||||
* it dropping any additional information that inputs and outputs may have hold
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @return {Transaction}
|
||||
*/
|
||||
Transaction.shallowCopy = function(transaction) {
|
||||
var copy = new Transaction(transaction.toBuffer());
|
||||
return copy;
|
||||
|
@ -69,10 +79,20 @@ var hashProperty = {
|
|||
Object.defineProperty(Transaction.prototype, 'hash', hashProperty);
|
||||
Object.defineProperty(Transaction.prototype, 'id', hashProperty);
|
||||
|
||||
/**
|
||||
* Retrieve the little endian hash of the transaction (used for serialization)
|
||||
* @return {Buffer}
|
||||
*/
|
||||
Transaction.prototype._getHash = function() {
|
||||
return Hash.sha256sha256(this.toBuffer());
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
||||
* (decoderawtransaction, sendrawtransaction)
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
Transaction.prototype.serialize = Transaction.prototype.toString = function() {
|
||||
return this.toBuffer().toString('hex');
|
||||
};
|
||||
|
@ -113,7 +133,6 @@ Transaction.prototype.fromBufferReader = function(reader) {
|
|||
for (i = 0; i < sizeTxIns; i++) {
|
||||
var input = Input.fromBufferReader(reader);
|
||||
this.inputs.push(input);
|
||||
this._outpoints.push(Transaction._makeOutpoint(input));
|
||||
}
|
||||
sizeTxOuts = reader.readVarintNum();
|
||||
for (i = 0; i < sizeTxOuts; i++) {
|
||||
|
@ -185,7 +204,13 @@ Transaction.prototype.from = function(utxo, pubkeys, threshold) {
|
|||
};
|
||||
|
||||
Transaction.prototype._fromMultiSigP2SH = function(utxo, pubkeys, threshold) {
|
||||
throw new errors.NotImplemented('Transaction#_fromMultiSigP2SH');
|
||||
if (Transaction._isNewUtxo(utxo)) {
|
||||
this._fromMultisigNewUtxo(utxo, pubkeys, threshold);
|
||||
} else if (Transaction._isOldUtxo(utxo)) {
|
||||
this._fromMultisigOldUtxo(utxo, pubkeys, threshold);
|
||||
} else {
|
||||
throw new Transaction.Errors.UnrecognizedUtxoFormat(utxo);
|
||||
}
|
||||
};
|
||||
|
||||
Transaction.prototype._fromNonP2SH = function(utxo) {
|
||||
|
@ -205,6 +230,11 @@ Transaction.prototype._fromNonP2SH = function(utxo) {
|
|||
}
|
||||
};
|
||||
|
||||
Transaction._isNewUtxo = function(utxo) {
|
||||
var isDefined = function(param) { return !_.isUndefined(param); };
|
||||
return _.all(_.map([utxo.txId, utxo.outputIndex, utxo.satoshis, utxo.script], isDefined));
|
||||
};
|
||||
|
||||
Transaction._isOldUtxo = function(utxo) {
|
||||
var isDefined = function(param) { return !_.isUndefined(param); };
|
||||
return _.all(_.map([utxo.txid, utxo.vout, utxo.scriptPubKey, utxo.amount], isDefined));
|
||||
|
@ -215,20 +245,15 @@ Transaction.prototype._fromOldUtxo = function(utxo) {
|
|||
address: utxo.address && new Address(utxo.address),
|
||||
txId: utxo.txid,
|
||||
outputIndex: utxo.vout,
|
||||
script: new buffer.Buffer(utxo.scriptPubKey, 'hex'),
|
||||
script: util.isHexa(utxo.script) ? new buffer.Buffer(utxo.scriptPubKey, 'hex') : utxo.scriptPubKey,
|
||||
satoshis: Unit.fromBTC(utxo.amount).satoshis
|
||||
});
|
||||
};
|
||||
|
||||
Transaction._isNewUtxo = function(utxo) {
|
||||
var isDefined = function(param) { return !_.isUndefined(param); };
|
||||
return _.all(_.map([utxo.txId, utxo.outputIndex, utxo.satoshis, utxo.script], isDefined));
|
||||
};
|
||||
|
||||
Transaction.prototype._fromNewUtxo = function(utxo) {
|
||||
utxo.address = utxo.address && new Address(utxo.address);
|
||||
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
|
||||
this.inputs.push(new Input({
|
||||
this.inputs.push(new PublicKeyHashInput({
|
||||
output: new Output({
|
||||
script: utxo.script,
|
||||
satoshis: utxo.satoshis
|
||||
|
@ -241,15 +266,30 @@ Transaction.prototype._fromNewUtxo = function(utxo) {
|
|||
this._inputAmount += utxo.satoshis;
|
||||
};
|
||||
|
||||
Transaction._makeOutpoint = function(data) {
|
||||
if (!_.isUndefined(data.txId) && !_.isUndefined(data.outputIndex)) {
|
||||
return data.txId + ':' + data.outputIndex;
|
||||
}
|
||||
if (!_.isUndefined(data.prevTxId) && !_.isUndefined(data.outputIndex)) {
|
||||
var prevTxId = _.isString(data.prevTxId) ? data.prevTxId : data.prevTxId.toString('hex');
|
||||
return prevTxId + ':' + data.outputIndex;
|
||||
}
|
||||
throw new Transaction.Errors.InvalidOutpointInfo(data);
|
||||
Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold) {
|
||||
return this._fromMultisigNewUtxo({
|
||||
address: utxo.address && new Address(utxo.address),
|
||||
txId: utxo.txid,
|
||||
outputIndex: utxo.vout,
|
||||
script: new buffer.Buffer(utxo.scriptPubKey, 'hex'),
|
||||
satoshis: Unit.fromBTC(utxo.amount).satoshis
|
||||
}, pubkeys, threshold);
|
||||
};
|
||||
|
||||
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
|
||||
utxo.address = utxo.address && new Address(utxo.address);
|
||||
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
|
||||
this.inputs.push(new MultiSigScriptHashInput({
|
||||
output: new Output({
|
||||
script: utxo.script,
|
||||
satoshis: utxo.satoshis
|
||||
}),
|
||||
prevTxId: utxo.txId,
|
||||
outputIndex: utxo.outputIndex,
|
||||
sequenceNumber: DEFAULT_SEQNUMBER,
|
||||
script: Script.empty()
|
||||
}, pubkeys, threshold));
|
||||
this._inputAmount += utxo.satoshis;
|
||||
};
|
||||
|
||||
Transaction.prototype.hasAllUtxoInfo = function() {
|
||||
|
@ -270,29 +310,12 @@ Transaction.prototype.change = function(address) {
|
|||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.to = function() {
|
||||
// TODO: Type validation
|
||||
var argSize = _.size(arguments);
|
||||
if (argSize === 3) {
|
||||
Transaction.prototype._payToMultisig.apply(this, arguments);
|
||||
} else if (argSize === 2) {
|
||||
Transaction.prototype._payToAddress.apply(this, arguments);
|
||||
} else {
|
||||
// TODO: Error
|
||||
throw new Error('');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype._payToMultisig = function(pubkeys, threshold, amount) {
|
||||
throw new errors.NotImplemented('Transaction#_payToMultisig');
|
||||
};
|
||||
|
||||
Transaction.prototype._payToAddress = function(address, amount) {
|
||||
Transaction.prototype.to = function(address, amount) {
|
||||
this._addOutput(new Output({
|
||||
script: Script.buildPublicKeyHashOut(address),
|
||||
script: Script(new Address(address)),
|
||||
satoshis: amount
|
||||
}));
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype._addOutput = function(output) {
|
||||
|
@ -310,7 +333,7 @@ Transaction.prototype.addData = function(value) {
|
|||
|
||||
/* Signature handling */
|
||||
|
||||
Transaction.prototype.sign = function(privKey) {
|
||||
Transaction.prototype.sign = function(privKey, sigtype) {
|
||||
// TODO: Change for preconditions
|
||||
assert(this.hasAllUtxoInfo());
|
||||
var self = this;
|
||||
|
@ -320,7 +343,7 @@ Transaction.prototype.sign = function(privKey) {
|
|||
});
|
||||
return this;
|
||||
}
|
||||
_.each(this.getSignatures(privKey), function(signature) {
|
||||
_.each(this.getSignatures(privKey, sigtype), function(signature) {
|
||||
self.applySignature(signature);
|
||||
});
|
||||
return this;
|
||||
|
@ -341,12 +364,23 @@ Transaction.prototype._getPrivateKeySignatures = function(privKey, sigtype) {
|
|||
};
|
||||
|
||||
Transaction.prototype.applySignature = function(signature) {
|
||||
this.inputs[signature.inputIndex].addSignature(signature);
|
||||
this.inputs[signature.inputIndex].addSignature(this, signature);
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.getSignatures = function(privKey) {
|
||||
return this._getPrivateKeySignatures(privKey);
|
||||
Transaction.prototype.getSignatures = function(privKey, sigtype) {
|
||||
return this._getPrivateKeySignatures(privKey, sigtype);
|
||||
};
|
||||
|
||||
Transaction.prototype.isFullySigned = function() {
|
||||
return _.all(_.map(this.inputs, function(input) {
|
||||
return input.isFullySigned();
|
||||
}));
|
||||
};
|
||||
|
||||
Transaction.prototype.isValidSignature = function(signature) {
|
||||
var self = this;
|
||||
return this.inputs[signature.inputIndex].isValidSignature(self, signature);
|
||||
};
|
||||
|
||||
module.exports = Transaction;
|
||||
|
|
|
@ -33,6 +33,13 @@ module.exports = {
|
|||
isHexa: isHexa,
|
||||
isHexaString: isHexa,
|
||||
|
||||
/**
|
||||
* Clone an array
|
||||
*/
|
||||
cloneArray: function(array) {
|
||||
return [].concat(array);
|
||||
},
|
||||
|
||||
/**
|
||||
* Define immutable properties on a target object
|
||||
*
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint unused: false */
|
||||
/* jshint latedef: false */
|
||||
var should = require('chai').should();
|
||||
var _ = require('lodash');
|
||||
|
||||
var bitcore = require('..');
|
||||
var Transaction = bitcore.Transaction;
|
||||
var Script = bitcore.Script;
|
||||
|
||||
var valid = require('./data/bitcoind/tx_valid.json');
|
||||
var invalid = require('./data/bitcoind/tx_invalid.json');
|
||||
|
||||
describe('Transaction', function() {
|
||||
|
||||
describe('bitcoind compliance', function() {
|
||||
|
||||
valid.map(function(datum){
|
||||
if ( typeof(datum[0]) === 'string' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should deserialize/serialize '+datum[1].slice(0, 15)+'... transaction', function() {
|
||||
var serialized = datum[1];
|
||||
var t = new Transaction(serialized);
|
||||
t.serialize().should.equal(serialized);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should serialize and deserialize correctly a given transaction', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.serialize().should.equal(tx_1_hex);
|
||||
});
|
||||
|
||||
it('should display correctly in console', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>');
|
||||
});
|
||||
|
||||
it('standard hash of transaction should be decoded correctly', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.id.should.equal(tx_1_id);
|
||||
});
|
||||
|
||||
it('serializes an empty transaction', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.serialize().should.equal(tx_empty_hex);
|
||||
});
|
||||
|
||||
it('serializes and deserializes correctly', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.serialize().should.equal(tx_1_hex);
|
||||
});
|
||||
|
||||
it('should input/output json', function() {
|
||||
var transaction = JSON.parse(Transaction().fromJSON(tx_1_json).toJSON());
|
||||
transaction.should.deep.equal(JSON.parse(tx_1_json));
|
||||
});
|
||||
|
||||
it('should create a sample transaction from an utxo', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(utxo_1a)
|
||||
.to(address_1, amount_1)
|
||||
.sign(privkey_1a)
|
||||
.serialize()
|
||||
.should.equal(tx_1_hex);
|
||||
});
|
||||
|
||||
it.skip('should create a transaction with two utxos', function() {
|
||||
var transaction = new Transaction()
|
||||
.from([utxo_2a, utxo_2b])
|
||||
.to(address_2, amount_2)
|
||||
.sign([privkey_2a, privkey_2b])
|
||||
.serialize()
|
||||
.should.equal(tx_2_hex);
|
||||
});
|
||||
});
|
||||
|
||||
var tx_empty_hex = '01000000000000000000';
|
||||
|
||||
/* jshint maxlen: 1000 */
|
||||
var tx_1_hex = '01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006a473044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000';
|
||||
var tx_1_id = '779a3e5b3c2c452c85333d8521f804c1a52800e60f4b7c3bbe36f4bab350b72c';
|
||||
var tx_2_hex = '';
|
||||
|
||||
var tx_1_json = JSON.stringify({
|
||||
version:1,
|
||||
inputs:[{
|
||||
prevTxId:"a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458",
|
||||
outputIndex:0,
|
||||
sequenceNumber:4294967295,
|
||||
script:'71 0x3044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501 33 0x0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e'}],
|
||||
outputs:[{
|
||||
satoshis:1010000,
|
||||
script:'OP_DUP OP_HASH160 20 0x7821c0a3768aa9d1a37e16cf76002aef5373f1a8 OP_EQUALVERIFY OP_CHECKSIG'
|
||||
}],
|
||||
nLockTime:0
|
||||
});
|
||||
|
||||
var utxo_1a_address = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||
|
||||
var utxo_2a_address = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||
var utxo_2b_address = 'mrCHmWgn54hJNty2srFF4XLmkey5GnCv5m';
|
||||
|
||||
/* A new-format utxo */
|
||||
var utxo_1a = {
|
||||
address: utxo_1a_address,
|
||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||
outputIndex: 0,
|
||||
script: Script.buildPublicKeyHashOut(utxo_1a_address).toString(),
|
||||
satoshis: 1020000
|
||||
};
|
||||
/* An old-format utxo */
|
||||
var utxo_2a = {
|
||||
address: utxo_2a_address,
|
||||
txid: '779a3e5b3c2c452c85333d8521f804c1a52800e60f4b7c3bbe36f4bab350b72c',
|
||||
vout: 0,
|
||||
scriptPubKey: Script.buildPublicKeyHashOut(utxo_2a_address).toString(),
|
||||
amount: 0.01010000
|
||||
};
|
||||
var utxo_2b = {
|
||||
address: utxo_2b_address,
|
||||
txid: 'e0f44096fcac31c1baede0714997c831123ecb5e258b52617fb093ba487c1d04',
|
||||
vout: 0,
|
||||
scriptPubKey: Script.buildPublicKeyHashOut(utxo_2b_address).toString(),
|
||||
amount: 0.00090000
|
||||
};
|
||||
|
||||
var address_1 = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||
var address_2 = 'mrCHmWgn54hJNty2srFF4XLmkey5GnCv5m';
|
||||
var amount_1 = 1010000;
|
||||
var amount_2 = 1090000;
|
||||
var privkey_1a = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||
var privkey_2a = 'cVLKm6LT1VTpZJVaSYtkYPLP1UP2Ph6NFxGVNLPAKKuSfv8hHreU';
|
||||
var privkey_2b = 'cVWHj19aJXVAxcKC5xAWQmiyhWyarmcPcuv4dT7nZy1JR37dbWgT';
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('../..');
|
||||
var Script = bitcore.Script;
|
||||
|
||||
module.exports = [
|
||||
[
|
||||
'from', [{
|
||||
address: 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1',
|
||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||
outputIndex: 0,
|
||||
script: Script.buildPublicKeyHashOut('mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1').toString(),
|
||||
satoshis: 1020000
|
||||
}],
|
||||
'to', ['mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', 1010000],
|
||||
'sign', ['cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'],
|
||||
'serialize', '01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006b4830450221009972100061da4a17a471ac1906c18bb5445c03da2a0be52c59aca6c58f1e342302205eac5ba43830a397f613f40addea4a2eeaa485a1f9a6efa61344c3560762fe3d01210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000'
|
||||
],
|
||||
[
|
||||
'from', [{
|
||||
"txid" : "e42447187db5a29d6db161661e4bc66d61c3e499690fe5ea47f87b79ca573986",
|
||||
"vout" : 1,
|
||||
"address" : "mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up",
|
||||
"scriptPubKey" : "76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac",
|
||||
"amount" : 0.01080000,
|
||||
}],
|
||||
'to', ['mn9new5vPYWuVN5m3gUBujfKh1uPQvR9mf', 500000],
|
||||
'to', ['mw5ctwgEaNRbxkM4JhXH3rp5AyGvTWDZCD', 570000],
|
||||
'sign', ['cSQUuwwJBAg6tYQhzqqLWW115D1s5KFZDyhCF2ffrnukZxMK6rNZ'],
|
||||
'serialize', '0100000001863957ca797bf847eae50f6999e4c3616dc64b1e6661b16d9da2b57d184724e4010000006b483045022100855691c90510edf83ab632f0a0b17f5202d2cf7071050dcf0c2778325ed403cd02207270a2f0b30c13dc3c1dee74b5ccabcc2632b402c4f38adabcd07357df1442270121039dd446bbc85db6917f39c0b4c295b0f8cce76d1926fa76d7b84e3f7ff1c5eec5ffffffff0220a10700000000001976a91448c819246ae5645ceecd41fbe1aa6202a0a9b5ca88ac90b20800000000001976a914aab76ba4877d696590d94ea3e02948b55294815188ac00000000'
|
||||
],
|
||||
[
|
||||
'from', [[{
|
||||
"txid" : "a9db84566e0fc9351e86337d2828ab281b25ddc06fab798f6d4b5baef48c02b3",
|
||||
"vout" : 0,
|
||||
"address" : "mn9new5vPYWuVN5m3gUBujfKh1uPQvR9mf",
|
||||
"account" : "",
|
||||
"scriptPubKey" : "76a91448c819246ae5645ceecd41fbe1aa6202a0a9b5ca88ac",
|
||||
"amount" : 0.00500000,
|
||||
"confirmations" : 0
|
||||
}, {
|
||||
"txid" : "a9db84566e0fc9351e86337d2828ab281b25ddc06fab798f6d4b5baef48c02b3",
|
||||
"vout" : 1,
|
||||
"address" : "mw5ctwgEaNRbxkM4JhXH3rp5AyGvTWDZCD",
|
||||
"account" : "",
|
||||
"scriptPubKey" : "76a914aab76ba4877d696590d94ea3e02948b55294815188ac",
|
||||
"amount" : 0.00570000,
|
||||
"confirmations" : 0
|
||||
}]],
|
||||
'to', ['mtymcCX5KixPjT1zxtg59qewBGWptj9etH', 1060000],
|
||||
'sign', [['cPGbA2C54ZZ1sw4dc2ckBE1WqkdrNSbEV8Tkjhi2p1J15oErdgP2', 'cSpyve5bXAuyHrNeV9MjTdFz3HLw739yUjjUAUSMe3ppf2qzj2hw']],
|
||||
'serialize', '0100000002b3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9000000006a4730440220635e95e1981bbb360feaf4c232f626a0af8eb5c043a99749a21b0e37fd0048fd02207889f6974f0cad39ce8c2a6dff05c8ca402da9ff6fc41e06c12d86853c91a9d80121030253c73236acf5ea9085d408220141197f6094de07426bd0d32c7a543614fdd7ffffffffb3028cf4ae5b4b6d8f79ab6fc0dd251b28ab28287d33861e35c90f6e5684dba9010000006a4730440220319a0b5ee9c67ccb7de4222234f31059354be4f239c99ca24bff30adfec8e8ec022056e6e99e50f7ceaa062958b8424cde1d504019f95c1dc0a0f0778848d0fb9f4b012102977a001a0a7bbfd1f8a647c7d46e13e8f6920635b328390b43b3303977101149ffffffff01a02c1000000000001976a91493abf1e9e4a20c125b93f93ee39efc16b6e4bc4688ac00000000'
|
||||
],
|
||||
[
|
||||
'from', [{
|
||||
"txid": "c8beceb964dec7ae5ec6ef5d019429b50c2e5fd07bd369e9a282d5153f23589c",
|
||||
"vout": 0,
|
||||
"address": "mtymcCX5KixPjT1zxtg59qewBGWptj9etH",
|
||||
"account": "",
|
||||
"scriptPubKey": "76a91493abf1e9e4a20c125b93f93ee39efc16b6e4bc4688ac",
|
||||
"amount": 0.01060000,
|
||||
}],
|
||||
'to', ['2NEQb8rtiUgxqQ9eif4XVeMUEW2LSZ64s58', 1050000],
|
||||
'sign', ['cMh7xdJ5EZVg6kvFsBybwK1EYGJw3G1DHhe5sNPAwbDts94ohKyK'],
|
||||
'serialize', '01000000019c58233f15d582a2e969d37bd05f2e0cb52994015defc65eaec7de64b9cebec8000000006a473044022050442862e892b1d12bcaa03857746f0ed168122e093d799861f4e081756bb8aa0220081d4eaf9281ae8f954efaeb47500d9a02e5a74b3ada51b6a258ac83c1f4f6420121039dbeac2610d53eb7107b14c0fa9be4006a731fa5bcef392d4e1a25ec0e58f0d3ffffffff01900510000000000017a91490edc43da6b052c4a23fc178979ce358a8caad5e8700000000'
|
||||
],
|
||||
[
|
||||
'from', [{
|
||||
"address": "2N6TY8Dc5JmJ87Fg9DhmN66fvFSwnTrjgip",
|
||||
"txid": "66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140",
|
||||
"vout": 0,
|
||||
"scriptPubKey": "a91490edc43da6b052c4a23fc178979ce358a8caad5e87",
|
||||
"amount": 0.01050000
|
||||
}, ['03fd45c8cd28c4c6a9a89b4515173edebc66a2418353976eccf01c73a7da9bbb12', '0349e0138b2c2f496121258e0426e1dbd698b2c6038e70fd17e3563aa87b4384f9'], 2],
|
||||
'to', ['mssMdcEm6PiJEr4XZtjk6kkai84EjBbi91', 1040000],
|
||||
'sign', [['L3wRFe9XHLnkLquf41F56ac77uRXwJ97HZPQ9tppqyMANBKXpoc5', 'KzkfNSL1gvdyU3CGLaP1Cs3pW167M8r9uE8yMtWQrAzz5vCv59CM']],
|
||||
'serialize', '010000000140c1ae9d6933e4a08594f814ba73a4e94d19c8a83f45784b1684b3a3f84ee66600000000da004730440220366678972728684a94f35635b855583603b28065d430949c08be89412a4ee45d02201aa62e3129c8819ecf2048230e8c77e244d6a496f296954a5bb4a0d0185f8c0201483045022100d06f348b4ef793f2bf749b288f1df165c0946779391c50ddc050e5b1608b2dda02200fcc8c6874b9a313374020253c5de346fe3517c97b18bfa769cea1089ad97144014752210349e0138b2c2f496121258e0426e1dbd698b2c6038e70fd17e3563aa87b4384f92103fd45c8cd28c4c6a9a89b4515173edebc66a2418353976eccf01c73a7da9bbb1252aeffffffff0180de0f00000000001976a914877d4f3be444448f868b345153bc4fc7a11a7c6388ac00000000'
|
||||
]
|
||||
];
|
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint unused: false */
|
||||
/* jshint latedef: false */
|
||||
var should = require('chai').should();
|
||||
var _ = require('lodash');
|
||||
|
||||
var bitcore = require('../..');
|
||||
var Transaction = bitcore.Transaction;
|
||||
var PrivateKey = bitcore.PrivateKey;
|
||||
var Script = bitcore.Script;
|
||||
var Address = bitcore.Address;
|
||||
var Networks = bitcore.Networks;
|
||||
|
||||
var transactionVector = require('./creation');
|
||||
|
||||
describe('Transaction', function() {
|
||||
|
||||
it('should serialize and deserialize correctly a given transaction', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.serialize().should.equal(tx_1_hex);
|
||||
});
|
||||
|
||||
it('should display correctly in console', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>');
|
||||
});
|
||||
|
||||
it('standard hash of transaction should be decoded correctly', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.id.should.equal(tx_1_id);
|
||||
});
|
||||
|
||||
it('serializes an empty transaction', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.serialize().should.equal(tx_empty_hex);
|
||||
});
|
||||
|
||||
it('serializes and deserializes correctly', function() {
|
||||
var transaction = new Transaction(tx_1_hex);
|
||||
transaction.serialize().should.equal(tx_1_hex);
|
||||
});
|
||||
|
||||
describe('transaction creation test vector', function() {
|
||||
var index = 0;
|
||||
transactionVector.forEach(function(vector) {
|
||||
index++;
|
||||
it('case ' + index, function() {
|
||||
var i = 0;
|
||||
var transaction = new Transaction();
|
||||
while (i < vector.length) {
|
||||
var command = vector[i];
|
||||
var args = vector[i+1];
|
||||
if (command === 'serialize') {
|
||||
transaction.serialize().should.equal(args);
|
||||
} else {
|
||||
transaction[command].apply(transaction, args);
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var tx_empty_hex = '01000000000000000000';
|
||||
|
||||
/* jshint maxlen: 1000 */
|
||||
var tx_1_hex = '01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006a473044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000';
|
||||
var tx_1_id = '779a3e5b3c2c452c85333d8521f804c1a52800e60f4b7c3bbe36f4bab350b72c';
|
Loading…
Reference in New Issue