add bare multisig support

This commit is contained in:
Ruben de Vries 2015-10-30 14:05:41 +01:00
parent 4e007e22be
commit 4f99bf0882
3 changed files with 193 additions and 0 deletions

View File

@ -687,6 +687,33 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) {
return script;
};
/**
* A new 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 and array of signature buffers to append to the script
* @param {Object=} opts
* @param {boolean=} opts.noSorting don't sort the given public keys before creating the script (false by default)
* @param {Script=} opts.cachedMultisig don't recalculate the redeemScript
*
* @returns {Script}
*/
Script.buildMultisigIn = function(pubkeys, threshold, signatures, opts) {
$.checkArgument(_.isArray(pubkeys));
$.checkArgument(_.isNumber(threshold));
$.checkArgument(_.isArray(signatures));
opts = opts || {};
var s = new Script();
s.add(Opcode.OP_0);
_.each(signatures, function(signature) {
$.checkArgument(BufferUtil.isBuffer(signature), 'Signatures must be an array of Buffers');
// TODO: allow signatures to be an array of Signature objects
s.add(signature);
});
return s;
};
/**
* A new P2SH Multisig input script for the given public keys, requiring m of those public keys to spend
*

View File

@ -2,4 +2,5 @@ module.exports = require('./input');
module.exports.PublicKey = require('./publickey');
module.exports.PublicKeyHash = require('./publickeyhash');
module.exports.MultiSig = require('./multisig.js');
module.exports.MultiSigScriptHash = require('./multisigscripthash.js');

View File

@ -0,0 +1,165 @@
'use strict';
var _ = require('lodash');
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 PublicKey = require('../../publickey');
var BufferUtil = require('../../util/buffer');
var TransactionSignature = require('../signature');
/**
* @constructor
*/
function MultiSigInput(input, pubkeys, threshold, signatures) {
Input.apply(this, arguments);
var self = this;
pubkeys = pubkeys || input.publicKeys;
threshold = threshold || input.threshold;
signatures = signatures || input.signatures;
this.publicKeys = _.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); });
$.checkState(Script.buildMultisigOut(this.publicKeys, threshold).equals(this.output.script),
'Provided public keys don\'t match to the provided output script');
this.publicKeyIndex = {};
_.each(this.publicKeys, function(publicKey, index) {
self.publicKeyIndex[publicKey.toString()] = index;
});
this.threshold = threshold;
// Empty array of signatures
this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length);
}
inherits(MultiSigInput, Input);
MultiSigInput.prototype.toObject = function() {
var obj = Input.prototype.toObject.apply(this, arguments);
obj.threshold = this.threshold;
obj.publicKeys = _.map(this.publicKeys, function(publicKey) { return publicKey.toString(); });
obj.signatures = this._serializeSignatures();
return obj;
};
MultiSigInput.prototype._deserializeSignatures = function(signatures) {
return _.map(signatures, function(signature) {
if (!signature) {
return undefined;
}
return new TransactionSignature(signature);
});
};
MultiSigInput.prototype._serializeSignatures = function() {
return _.map(this.signatures, function(signature) {
if (!signature) {
return undefined;
}
return signature.toObject();
});
};
MultiSigInput.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(new TransactionSignature({
publicKey: privateKey.publicKey,
prevTxId: self.prevTxId,
outputIndex: self.outputIndex,
inputIndex: index,
signature: Sighash.sign(transaction, privateKey, sigtype, index, self.output.script),
sigtype: sigtype
}));
}
});
return results;
};
MultiSigInput.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;
};
MultiSigInput.prototype._updateScript = function() {
this.setScript(Script.buildMultisigIn(
this.publicKeys,
this.threshold,
this._createSignatures()
));
return this;
};
MultiSigInput.prototype._createSignatures = function() {
return _.map(
_.filter(this.signatures, function(signature) { return !_.isUndefined(signature); }),
function(signature) {
return BufferUtil.concat([
signature.signature.toDER(),
BufferUtil.integerAsSingleByteBuffer(signature.sigtype)
]);
}
);
};
MultiSigInput.prototype.clearSignatures = function() {
this.signatures = new Array(this.publicKeys.length);
this._updateScript();
};
MultiSigInput.prototype.isFullySigned = function() {
return this.countSignatures() === this.threshold;
};
MultiSigInput.prototype.countMissingSignatures = function() {
return this.threshold - this.countSignatures();
};
MultiSigInput.prototype.countSignatures = function() {
return _.reduce(this.signatures, function(sum, signature) {
return sum + (!!signature);
}, 0);
};
MultiSigInput.prototype.publicKeysWithoutSignature = function() {
var self = this;
return _.filter(this.publicKeys, function(publicKey) {
return !(self.signatures[self.publicKeyIndex[publicKey.toString()]]);
});
};
MultiSigInput.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
);
};
MultiSigInput.OPCODES_SIZE = 7; // serialized size (<=3) + 0 .. N .. M OP_CHECKMULTISIG
MultiSigInput.SIGNATURE_SIZE = 74; // size (1) + DER (<=72) + sighash (1)
MultiSigInput.PUBKEY_SIZE = 34; // size (1) + DER (<=33)
MultiSigInput.prototype._estimateSize = function() {
return MultiSigInput.OPCODES_SIZE +
this.threshold * MultiSigInput.SIGNATURE_SIZE +
this.publicKeys.length * MultiSigInput.PUBKEY_SIZE;
};
module.exports = MultiSigInput;