TX_MULTISIG support
This commit is contained in:
parent
5dd945f2b7
commit
2af6ab7650
|
@ -553,15 +553,28 @@ Transaction.prototype.getSize = function getHash() {
|
|||
};
|
||||
|
||||
Transaction.prototype.isComplete = function() {
|
||||
var l = this.ins.length;
|
||||
|
||||
var ret = true;
|
||||
var l = this.ins.length;
|
||||
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (buffertools.compare(this.ins[i].s, util.EMPTY_BUFFER) === 0) {
|
||||
ret = false;
|
||||
break;
|
||||
var script = new Script(this.ins[i].s);
|
||||
// Multisig?
|
||||
if (!Buffer.isBuffer(script.chunks[0]) && script.chunks[0] ===0) {
|
||||
for (var i = 1; i < script.chunks.length; i++) {
|
||||
if (buffertools.compare(script.chunks[i], util.EMPTY_BUFFER) === 0){
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (buffertools.compare(this.ins[i].s, util.EMPTY_BUFFER) === 0) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ var Transaction = imports.Transaction || require('./Transaction');
|
|||
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
||||
|
||||
function TransactionBuilder(opts) {
|
||||
var opts = opts || {};
|
||||
opts = opts || {};
|
||||
this.txobj = {};
|
||||
this.txobj.version = 1;
|
||||
this.txobj.lock_time = opts.lockTime || 0;
|
||||
|
@ -144,18 +144,29 @@ TransactionBuilder.prototype._setInputMap = function() {
|
|||
|
||||
var l = this.selectedUtxos.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var s = this.selectedUtxos[i];
|
||||
var utxo = this.selectedUtxos[i];
|
||||
|
||||
var scriptBuf = new Buffer(utxo.scriptPubKey, 'hex');
|
||||
var scriptPubKey = new Script(scriptBuf);
|
||||
var scriptType = scriptPubKey.classify();
|
||||
|
||||
if (scriptType === Script.TX_UNKNOWN)
|
||||
throw new Error('unkown output type at:' + i +
|
||||
' Type:' + scriptPubKey.getRawOutType());
|
||||
|
||||
inputMap.push({
|
||||
address: s.address,
|
||||
scriptPubKey: s.scriptPubKey
|
||||
address: utxo.address, //TODO que pasa en multisig normal?
|
||||
scriptPubKeyHex: utxo.scriptPubKey,
|
||||
scriptPubKey: scriptPubKey,
|
||||
scriptType: scriptType,
|
||||
i: i,
|
||||
});
|
||||
}
|
||||
this.inputMap = inputMap;
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.getSelectedUnspent = function(neededAmountSat) {
|
||||
TransactionBuilder.prototype.getSelectedUnspent = function() {
|
||||
return this.selectedUtxos;
|
||||
};
|
||||
|
||||
|
@ -360,14 +371,6 @@ TransactionBuilder._mapKeys = function(keys) {
|
|||
return walletKeyMap;
|
||||
};
|
||||
|
||||
TransactionBuilder._checkSupportedScriptType = function (s) {
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('scriptSig type:' + s.getRawOutType() +
|
||||
' not supported yet');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder._signHashAndVerify = function(wk, txSigHash) {
|
||||
var triesLeft = 10, sigRaw;
|
||||
|
||||
|
@ -387,44 +390,208 @@ TransactionBuilder.prototype._checkTx = function() {
|
|||
throw new Error('tx is not defined');
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHash) {
|
||||
if (this.tx.ins[input.i].s.length > 0) return {};
|
||||
|
||||
var wk = walletKeyMap[input.address];
|
||||
if (!wk) return;
|
||||
|
||||
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = this.signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.updateBuffer();
|
||||
return {isFullySigned: true, script: scriptSig.getBuffer()};
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) {
|
||||
|
||||
if (this.tx.ins[input.i].s.length > 0) return {};
|
||||
|
||||
var wk = walletKeyMap[input.address];
|
||||
if (!wk) return;
|
||||
|
||||
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = this.signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wk.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
return {isFullySigned: true, script: scriptSig.getBuffer()};
|
||||
};
|
||||
|
||||
// FOR TESTING
|
||||
/*
|
||||
var _dumpChunks = function (scriptSig, label) {
|
||||
console.log('## DUMP: ' + label + ' ##');
|
||||
for(var i=0; i<scriptSig.chunks.length; i++) {
|
||||
console.log('\tCHUNK ', i, scriptSig.chunks[i]);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
TransactionBuilder.prototype._initMultiSig = function(scriptSig, nreq) {
|
||||
var wasUpdated = false;
|
||||
if (scriptSig.chunks.length < nreq + 1) {
|
||||
wasUpdated = true;
|
||||
scriptSig.writeN(0);
|
||||
while (scriptSig.chunks.length <= nreq)
|
||||
scriptSig.chunks.push(util.EMPTY_BUFFER);
|
||||
}
|
||||
return wasUpdated;
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder.prototype._isSignedWithKey = function(wk, scriptSig, txSigHash, nreq) {
|
||||
var ret=0;
|
||||
for(var i=1; i<=nreq; i++) {
|
||||
var chunk = scriptSig.chunks[i];
|
||||
if (chunk ===0 || chunk.length === 0) continue;
|
||||
|
||||
var sigRaw = new Buffer(chunk.slice(0,chunk.length-1));
|
||||
if(wk.privKey.verifySignatureSync(txSigHash, sigRaw) === true) {
|
||||
ret=true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._chunkIsEmpty = function(chunk) {
|
||||
return chunk === 0 || // when serializing and back, EMPTY_BUFFER becomes 0
|
||||
buffertools.compare(chunk, util.EMPTY_BUFFER) === 0;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._updateMultiSig = function(wk, scriptSig, txSigHash, nreq) {
|
||||
var wasUpdated = this._initMultiSig(scriptSig, nreq);
|
||||
|
||||
if (this._isSignedWithKey(wk,scriptSig, txSigHash, nreq))
|
||||
return null;
|
||||
|
||||
// Find an empty slot and sign
|
||||
for(var i=1; i<=nreq; i++) {
|
||||
var chunk = scriptSig.chunks[i];
|
||||
if (!this._chunkIsEmpty(chunk))
|
||||
continue;
|
||||
|
||||
// Add signature
|
||||
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = this.signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
scriptSig.chunks[i] = sig;
|
||||
scriptSig.updateBuffer();
|
||||
wasUpdated=true;
|
||||
break;
|
||||
}
|
||||
return wasUpdated ? scriptSig : null;
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder.prototype._multiFindKey = function(walletKeyMap,pubKeyHash) {
|
||||
var wk;
|
||||
[ networks.livenet, networks.testnet].forEach(function(n) {
|
||||
[ n.addressPubkey, n.addressScript].forEach(function(v) {
|
||||
var a = new Address(v,pubKeyHash);
|
||||
if (!wk && walletKeyMap[a]) {
|
||||
wk = walletKeyMap[a];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return wk;
|
||||
};
|
||||
|
||||
|
||||
|
||||
TransactionBuilder.prototype._countMultiSig = function(script) {
|
||||
var nsigs = 0;
|
||||
for (var i = 1; i < script.chunks.length; i++)
|
||||
if (!this._chunkIsEmpty(script.chunks[i]))
|
||||
nsigs++;
|
||||
|
||||
return nsigs;
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder.prototype.countInputMultiSig = function(i) {
|
||||
var s = new Script(this.tx.ins[i].s);
|
||||
if (!s.chunks.length || s.chunks[0] !== 0)
|
||||
return 0; // does not seems multisig
|
||||
|
||||
return this._countMultiSig(s);
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSigHash) {
|
||||
var pubkeys = input.scriptPubKey.capture(),
|
||||
nreq = input.scriptPubKey.chunks[0] - 80, //see OP_2-OP_16
|
||||
l = pubkeys.length,
|
||||
originalScriptBuf = this.tx.ins[input.i].s;
|
||||
|
||||
var scriptSig = new Script (originalScriptBuf);
|
||||
|
||||
for(var j=0; j<l && this._countMultiSig(scriptSig)<nreq; j++) {
|
||||
|
||||
var pubKeyHash = util.sha256ripe160(pubkeys[j]);
|
||||
var wk = this._multiFindKey(walletKeyMap, pubKeyHash);
|
||||
if (!wk) continue;
|
||||
|
||||
var newScriptSig = this._updateMultiSig(wk, scriptSig, txSigHash, nreq);
|
||||
if (newScriptSig)
|
||||
scriptSig = newScriptSig;
|
||||
}
|
||||
|
||||
return {
|
||||
isFullySigned: this._countMultiSig(scriptSig) === nreq,
|
||||
script: scriptSig.getBuffer(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
var fnToSign = {};
|
||||
fnToSign[Script.TX_PUBKEYHASH] = TransactionBuilder.prototype._signPubKeyHash;
|
||||
fnToSign[Script.TX_PUBKEY] = TransactionBuilder.prototype._signPubKey;
|
||||
fnToSign[Script.TX_MULTISIG] = TransactionBuilder.prototype._signMultiSig;
|
||||
fnToSign[Script.TX_SCRIPTHASH] = TransactionBuilder.prototype._signScriptHash;
|
||||
//if (!this.hashToScriptMap) throw new Error('hashToScriptMap not set');
|
||||
|
||||
TransactionBuilder.prototype.sign = function(keys) {
|
||||
this._checkTx();
|
||||
|
||||
var tx = this.tx,
|
||||
ins = tx.ins,
|
||||
l = ins.length;
|
||||
l = ins.length,
|
||||
walletKeyMap = TransactionBuilder._mapKeys(keys);
|
||||
|
||||
var walletKeyMap = TransactionBuilder._mapKeys(keys);
|
||||
|
||||
for (var i = 0; i < l; i++) {
|
||||
var im = this.inputMap[i];
|
||||
if (typeof im === 'undefined') continue;
|
||||
var wk = walletKeyMap[im.address];
|
||||
if (!wk) continue;
|
||||
var input = this.inputMap[i];
|
||||
|
||||
var scriptBuf = new Buffer(im.scriptPubKey, 'hex');
|
||||
var txSigHash = this.tx.hashForSignature(
|
||||
input.scriptPubKey, i, this.signhash);
|
||||
|
||||
//TODO: support p2sh
|
||||
var s = new Script(scriptBuf);
|
||||
TransactionBuilder._checkSupportedScriptType(s);
|
||||
|
||||
var txSigHash = this.tx.hashForSignature(s, i, this.signhash);
|
||||
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = this.signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wk.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
tx.ins[i].s = scriptSig.getBuffer();
|
||||
this.inputsSigned++;
|
||||
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash);
|
||||
if (ret && ret.script) {
|
||||
tx.ins[i].s = ret.script; //esto no aqui TODO
|
||||
if (ret.isFullySigned) this.inputsSigned++;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// [addr -> script]
|
||||
TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) {
|
||||
this.hashToScriptMap= hashToScriptMap;
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder.prototype.isFullySigned = function() {
|
||||
return this.inputsSigned === this.tx.ins.length;
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ var run = function() {
|
|||
var buffertools = bitcore.buffertools;
|
||||
var Address = bitcore.Address;
|
||||
var util = bitcore.util;
|
||||
var opts = {network: networks.livenet};
|
||||
var opts = {network: networks.testnet};
|
||||
|
||||
var p = console.log;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{
|
||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
"vout": 0,
|
||||
"confirmations": 1,
|
||||
"amount": 0.1
|
||||
|
@ -18,7 +18,7 @@
|
|||
{
|
||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
"vout": 3,
|
||||
"confirmations": 0,
|
||||
"amount": 1
|
||||
|
|
|
@ -29,6 +29,43 @@
|
|||
"cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV",
|
||||
"cPa87VgwZfowGZYaEenoQeJgRfKW6PhZ1R65EHTkN1K19cSvc92G",
|
||||
"cPQ9DSbBRLva9av5nqeF5AGrh3dsdW8p2E5jS4P8bDWZAoQTeeKB"
|
||||
],
|
||||
"unspentPubKey": [
|
||||
{
|
||||
"address": "mqqnn93xN81eZTLqj7Wk2cacBBTR8agFZ5",
|
||||
"scriptPubKey": "2102aa869ff719f23d9959dca340cbf3b72770294c64005e53e0429948aa6e9701d1ac",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"vout": 1,
|
||||
"amount": 1,
|
||||
"confirmations":7
|
||||
}
|
||||
],
|
||||
"keyStringsPubKey": [
|
||||
"cTSvhK2b3XxJezmDjVN5x1KTCtui4NaLhvb78nvprpVAiqHgQvMm"
|
||||
],
|
||||
"unspentMulti": [
|
||||
{
|
||||
"address": [
|
||||
"n4JAZc4cJimQbky5wxZUEDeAFZtGaZrjWK",
|
||||
"msge5muNmBSRDn5nsaRcHCU6dg2zimA8wQ",
|
||||
"mvz9MjocpyXdgXqRcZYazsdE8iThdvjdhk",
|
||||
"miQGZ2gybQe7UvUQDBYsgcctUteij5pTpm",
|
||||
"mu9kmhGrzREKsWaXUEUrsRLLMG4UMPy1LF"
|
||||
],
|
||||
"scriptPubKey": "532103bf025eb410407aec5a67c975ce222e363bb88c69bb1acce45d20d85602df2ec52103d76dd6d99127f4b733e772f0c0a09c573ac7e4d69b8bf50272292da2e093de2c2103dd9acd8dd1816c825d6b0739339c171ae2cb10efb53699680537865b07086e9b2102371cabbaf466c3a536034b4bda64ad515807bffd87488f44f93c2373d4d189c9210264cd444358f8d57f8637a7309f9736806f4883aebc4fe7da4bad1e4b37f2d12c55ae",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"vout": 1,
|
||||
"amount": 1,
|
||||
"confirmations":7
|
||||
}
|
||||
],
|
||||
"keyStringsMulti": [
|
||||
"cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN",
|
||||
"cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ",
|
||||
"cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg",
|
||||
"cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm",
|
||||
"cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu"
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -220,7 +220,6 @@ describe('TransactionBuilder', function() {
|
|||
amount: 0.08
|
||||
}];
|
||||
|
||||
//console.log('[test.TransactionBuilder.js.216:outs:]',outs, outs.length); //TODO
|
||||
return new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspent)
|
||||
.setOutputs(outs);
|
||||
|
@ -395,4 +394,107 @@ describe('TransactionBuilder', function() {
|
|||
util.valueToBigInt(tx.outs[N].v).cmp(970000).should.equal(0);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
|
||||
it('should sign a p2pubkey tx', function() {
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
};
|
||||
var outs = outs || [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
|
||||
var b = new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspentPubKey)
|
||||
.setOutputs(outs)
|
||||
.sign(testdata.dataUnspentSign.keyStringsPubKey);
|
||||
|
||||
b.isFullySigned().should.equal(true);
|
||||
var tx = b.build();
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
});
|
||||
|
||||
|
||||
it('should sign a multisig tx', function() {
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
};
|
||||
var outs = outs || [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var b = new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspentMulti)
|
||||
.setOutputs(outs);
|
||||
|
||||
b.sign(testdata.dataUnspentSign.keyStringsMulti);
|
||||
b.isFullySigned().should.equal(true);
|
||||
var tx = b.build();
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
it('should sign a multisig tx in steps (3-5)', function() {
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
};
|
||||
var outs = outs || [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var b = new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspentMulti)
|
||||
.setOutputs(outs);
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1);
|
||||
var k2 = testdata.dataUnspentSign.keyStringsMulti.slice(1,2);
|
||||
var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3);
|
||||
|
||||
b.sign(k1);
|
||||
b.isFullySigned().should.equal(false);
|
||||
b.sign(k2);
|
||||
b.isFullySigned().should.equal(false);
|
||||
b.sign(k3);
|
||||
b.isFullySigned().should.equal(true);
|
||||
|
||||
var tx = b.build();
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
it('should count multisig signs (3-5)', function() {
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
};
|
||||
var outs = outs || [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var b = new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspentMulti)
|
||||
.setOutputs(outs);
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1);
|
||||
var k2 = testdata.dataUnspentSign.keyStringsMulti.slice(1,2);
|
||||
var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3);
|
||||
|
||||
b.countInputMultiSig(0).should.equal(0);
|
||||
b.sign(k1);
|
||||
b.isFullySigned().should.equal(false);
|
||||
b.countInputMultiSig(0).should.equal(1);
|
||||
b.sign(k2);
|
||||
b.isFullySigned().should.equal(false);
|
||||
b.countInputMultiSig(0).should.equal(2);
|
||||
b.sign(k3);
|
||||
b.isFullySigned().should.equal(true);
|
||||
b.countInputMultiSig(0).should.equal(3);
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue