signature sorting in .sign and .merge in transaction builder
This commit is contained in:
parent
56d81bc0df
commit
9951b55a4f
|
@ -119,19 +119,64 @@ Script.prototype.isMultiSig = function() {
|
||||||
this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG);
|
this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Script.prototype.isP2shScriptSig = function() {
|
||||||
|
if( !isSmallIntOp(this.chunks[0]) || this.chunks[0] !==0 )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var redeemScript = new Script(this.chunks[this.chunks.length-1]);
|
||||||
|
var type=redeemScript.classify();
|
||||||
|
return type !== TX_UNKNOWN;
|
||||||
|
};
|
||||||
|
|
||||||
|
Script.prototype.isMultiSigScriptSig = function() {
|
||||||
|
if( !isSmallIntOp(this.chunks[0]) || this.chunks[0] !==0 )
|
||||||
|
return false;
|
||||||
|
return !this.isP2shScriptSig();
|
||||||
|
};
|
||||||
|
|
||||||
|
Script.prototype.countSignatures = function() {
|
||||||
|
var ret = 0;
|
||||||
|
var l =this.chunks.length;
|
||||||
|
|
||||||
|
// Multisig?
|
||||||
|
if (this.isMultiSigScriptSig()){
|
||||||
|
ret = l - 1;
|
||||||
|
}
|
||||||
|
else if (this.isP2shScriptSig()) {
|
||||||
|
ret = l - 2;
|
||||||
|
}
|
||||||
|
// p2pubkey or p2pubkeyhash
|
||||||
|
else {
|
||||||
|
ret = buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER)===0?0:1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
Script.prototype.countMissingSignatures = function() {
|
Script.prototype.countMissingSignatures = function() {
|
||||||
if (this.isMultiSig()) {
|
if (this.isMultiSig()) {
|
||||||
log.debug("Can not count missing signatures on normal Multisig script");
|
log.debug("Can not count missing signatures on normal Multisig script");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// P2SH?
|
var ret = 0;
|
||||||
var l =this.chunks.length;
|
var l =this.chunks.length;
|
||||||
|
// P2SH?
|
||||||
if (isSmallIntOp(this.chunks[0]) && this.chunks[0] ===0) {
|
if (isSmallIntOp(this.chunks[0]) && this.chunks[0] ===0) {
|
||||||
var redeemScript = new Script(this.chunks[l-1]);
|
var redeemScript = new Script(this.chunks[l-1]);
|
||||||
if (!isSmallIntOp(redeemScript.chunks[0])) {
|
if (!isSmallIntOp(redeemScript.chunks[0])) {
|
||||||
log.debug("Unrecognized script type");
|
log.debug("Unrecognized script type");
|
||||||
ret = null;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var nreq = redeemScript.chunks[0] - 80; //see OP_2-OP_16
|
var nreq = redeemScript.chunks[0] - 80; //see OP_2-OP_16
|
||||||
|
@ -147,9 +192,11 @@ Script.prototype.countMissingSignatures = function() {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Script.prototype.finishedMultiSig = function() {
|
Script.prototype.finishedMultiSig = function() {
|
||||||
return this.countMissingSignatures() === 0;
|
var missing = this.countMissingSignatures();
|
||||||
|
if (missing === null) return null;
|
||||||
|
|
||||||
|
return missing === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Script.prototype.prependOp0 = function() {
|
Script.prototype.prependOp0 = function() {
|
||||||
|
|
|
@ -593,18 +593,27 @@ Transaction.prototype.getSize = function () {
|
||||||
return this.size;
|
return this.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.countInputSignatures = function(index) {
|
||||||
|
var ret = 0;
|
||||||
|
var script = new Script(this.ins[index].s);
|
||||||
|
return script.countSignatures();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
||||||
Transaction.prototype.countInputMissingSignatures = function(index) {
|
Transaction.prototype.countInputMissingSignatures = function(index) {
|
||||||
var ret = 0;
|
var ret = 0;
|
||||||
var script = new Script(this.ins[index].s);
|
var script = new Script(this.ins[index].s);
|
||||||
return script.countMissingSignatures();
|
return script.countMissingSignatures();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
||||||
Transaction.prototype.isInputComplete = function(index) {
|
Transaction.prototype.isInputComplete = function(index) {
|
||||||
return this.countInputMissingSignatures(index)===0;
|
var m = this.countInputMissingSignatures(index);
|
||||||
|
if (m===null) return null;
|
||||||
|
return m === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig)
|
||||||
Transaction.prototype.isComplete = function() {
|
Transaction.prototype.isComplete = function() {
|
||||||
var ret = true;
|
var ret = true;
|
||||||
var l = this.ins.length;
|
var l = this.ins.length;
|
||||||
|
@ -615,7 +624,6 @@ Transaction.prototype.isComplete = function() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,8 @@ var buffertools = imports.buffertools || require('buffertools');
|
||||||
var networks = imports.networks || require('../networks');
|
var networks = imports.networks || require('../networks');
|
||||||
var WalletKey = imports.WalletKey || require('./WalletKey');
|
var WalletKey = imports.WalletKey || require('./WalletKey');
|
||||||
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
|
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
|
||||||
|
var Key = imports.Key || require('./Key');
|
||||||
|
var log = imports.log || require('../util/log');
|
||||||
|
|
||||||
var Transaction = imports.Transaction || require('./Transaction');
|
var Transaction = imports.Transaction || require('./Transaction');
|
||||||
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
||||||
|
@ -481,7 +483,7 @@ TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHa
|
||||||
var scriptSig = new Script();
|
var scriptSig = new Script();
|
||||||
scriptSig.chunks.push(sig);
|
scriptSig.chunks.push(sig);
|
||||||
scriptSig.updateBuffer();
|
scriptSig.updateBuffer();
|
||||||
return {isFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()};
|
return {inputFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()};
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) {
|
TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) {
|
||||||
|
@ -500,74 +502,89 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS
|
||||||
scriptSig.chunks.push(sig);
|
scriptSig.chunks.push(sig);
|
||||||
scriptSig.chunks.push(wk.privKey.public);
|
scriptSig.chunks.push(wk.privKey.public);
|
||||||
scriptSig.updateBuffer();
|
scriptSig.updateBuffer();
|
||||||
return {isFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()};
|
return {inputFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()};
|
||||||
};
|
};
|
||||||
|
|
||||||
// FOR TESTING
|
// FOR TESTING
|
||||||
var _dumpChunks = function (scriptSig, label) {
|
// var _dumpChunks = function (scriptSig, label) {
|
||||||
console.log('## DUMP: ' + label + ' ##');
|
// console.log('## DUMP: ' + label + ' ##');
|
||||||
for(var i=0; i<scriptSig.chunks.length; i++) {
|
// for(var i=0; i<scriptSig.chunks.length; i++) {
|
||||||
console.log('\tCHUNK ', i, scriptSig.chunks[i]);
|
// console.log('\tCHUNK ', i, Buffer.isBuffer(scriptSig.chunks[i])
|
||||||
}
|
// ?scriptSig.chunks[i].toString('hex'):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) {
|
TransactionBuilder.prototype._chunkSignedWithKey = function(scriptSig, txSigHash, publicKey) {
|
||||||
var ret=false;
|
var ret;
|
||||||
// _dumpChunks(scriptSig);
|
var k = new Key();
|
||||||
for(var i=1; i<=nreq; i++) {
|
k.public =publicKey;
|
||||||
|
|
||||||
|
for(var i=1; i<= scriptSig.countSignatures(); i++) {
|
||||||
var chunk = scriptSig.chunks[i];
|
var chunk = scriptSig.chunks[i];
|
||||||
if (chunk ===0 || chunk.length === 0) continue;
|
|
||||||
|
|
||||||
var sigRaw = new Buffer(chunk.slice(0,chunk.length-1));
|
var sigRaw = new Buffer(chunk.slice(0,chunk.length-1));
|
||||||
if(wk.privKey.verifySignatureSync(txSigHash, sigRaw) === true) {
|
if (k.verifySignatureSync(txSigHash, sigRaw) ) {
|
||||||
ret=true;
|
ret=chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._getSignatureOrder = function(sigPrio, sigRaw, txSigHash, pubkeys) {
|
||||||
|
var l=pubkeys.length;
|
||||||
|
for(var j=0; j<l; j++) {
|
||||||
|
var k = new Key();
|
||||||
|
k.public = new Buffer(pubkeys[j],'hex');
|
||||||
|
if (k.verifySignatureSync(txSigHash, sigRaw))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._getNewSignatureOrder = function(sigPrio, scriptSig, txSigHash, pubkeys) {
|
||||||
|
var iPrio;
|
||||||
|
for(var i=1; i<= scriptSig.countSignatures(); i++) {
|
||||||
|
var chunk = scriptSig.chunks[i];
|
||||||
|
var sigRaw = new Buffer(chunk.slice(0,chunk.length-1));
|
||||||
|
iPrio = this._getSignatureOrder(sigPrio, sigRaw, txSigHash, pubkeys);
|
||||||
|
if (sigPrio <= iPrio) break;
|
||||||
|
}
|
||||||
|
return (sigPrio === iPrio? -1: i-1);
|
||||||
|
};
|
||||||
|
|
||||||
TransactionBuilder.prototype._chunkIsEmpty = function(chunk) {
|
TransactionBuilder.prototype._chunkIsEmpty = function(chunk) {
|
||||||
return chunk === 0 || // when serializing and back, EMPTY_BUFFER becomes 0
|
return chunk === 0 || // when serializing and back, EMPTY_BUFFER becomes 0
|
||||||
buffertools.compare(chunk, util.EMPTY_BUFFER) === 0;
|
buffertools.compare(chunk, util.EMPTY_BUFFER) === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._initMultiSig = function(script) {
|
||||||
|
var wasUpdated = false;
|
||||||
|
if (script.chunks[0] !== 0) {
|
||||||
|
script.prependOp0();
|
||||||
|
wasUpdated = true;
|
||||||
|
}
|
||||||
|
return wasUpdated;
|
||||||
|
};
|
||||||
|
|
||||||
TransactionBuilder.prototype._updateMultiSig = function(wk, scriptSig, txSigHash, nreq) {
|
TransactionBuilder.prototype._updateMultiSig = function(sigPrio, wk, scriptSig, txSigHash, pubkeys) {
|
||||||
var wasUpdated = this._initMultiSig(scriptSig, nreq);
|
var wasUpdated = this._initMultiSig(scriptSig);
|
||||||
|
|
||||||
if (this._isSignedWithKey(wk,scriptSig, txSigHash, nreq))
|
if (this._chunkSignedWithKey(scriptSig, txSigHash, wk.privKey.public))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
console.log('[TransactionBuilder.js.552] ', wk.privKey.public.toString('hex')); //TODO
|
// Create signature
|
||||||
// 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 sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||||
var sigType = new Buffer(1);
|
var sigType = new Buffer(1);
|
||||||
sigType[0] = this.signhash;
|
sigType[0] = this.signhash;
|
||||||
var sig = Buffer.concat([sigRaw, sigType]);
|
var sig = Buffer.concat([sigRaw, sigType]);
|
||||||
scriptSig.chunks[i] = sig;
|
|
||||||
|
// Add signature
|
||||||
|
var order = this._getNewSignatureOrder(sigPrio,scriptSig,txSigHash,pubkeys);
|
||||||
|
scriptSig.chunks.splice(order+1,0,sig);
|
||||||
scriptSig.updateBuffer();
|
scriptSig.updateBuffer();
|
||||||
wasUpdated=true;
|
wasUpdated=true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
// _dumpChunks(scriptSig); // TODO
|
|
||||||
return wasUpdated ? scriptSig : null;
|
return wasUpdated ? scriptSig : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -581,28 +598,26 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig
|
||||||
var scriptSig = new Script (originalScriptBuf);
|
var scriptSig = new Script (originalScriptBuf);
|
||||||
var signaturesAdded = 0;
|
var signaturesAdded = 0;
|
||||||
|
|
||||||
for(var j=0; j<l && scriptSig.countMissingSignatures(); j++) {
|
for(var j=0; j<l && scriptSig.countSignatures() < nreq ; j++) {
|
||||||
//console.log('[TransactionBuilder.js] pubkey [j]',j, pubkeys[j].toString('hex')); //TODO
|
//console.log('[TransactionBuilder.js] pubkey [j]',j, pubkeys[j].toString('hex')); //TODO
|
||||||
var wk = this._findWalletKey(walletKeyMap, {pubKeyBuf: pubkeys[j]});
|
var wk = this._findWalletKey(walletKeyMap, {pubKeyBuf: pubkeys[j]});
|
||||||
if (!wk) continue;
|
if (!wk) continue;
|
||||||
//console.log('[TransactionBuilder.js.585] wk pubkey: PRIO:',j, wk.privKey.public.toString('hex')); //TODO
|
|
||||||
|
|
||||||
var newScriptSig = this._updateMultiSig(wk, scriptSig, txSigHash, nreq);
|
var newScriptSig = this._updateMultiSig(j, wk, scriptSig, txSigHash, pubkeys);
|
||||||
if (newScriptSig) {
|
if (newScriptSig) {
|
||||||
scriptSig = newScriptSig;
|
scriptSig = newScriptSig;
|
||||||
signaturesAdded++;
|
signaturesAdded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!scriptSig.countMissingSignatures()) {
|
if (scriptSig.countSignatures() === nreq) {
|
||||||
|
|
||||||
console.log('########## DONE!!!'); //TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
var ret = {
|
||||||
isFullySigned: scriptSig.countMissingSignatures() === 0,
|
inputFullySigned: scriptSig.countSignatures() === nreq,
|
||||||
signaturesAdded: signaturesAdded,
|
signaturesAdded: signaturesAdded,
|
||||||
script: scriptSig.getBuffer(),
|
script: scriptSig.getBuffer(),
|
||||||
};
|
};
|
||||||
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
var fnToSign = {};
|
var fnToSign = {};
|
||||||
|
@ -648,13 +663,11 @@ TransactionBuilder.prototype._getInputForP2sh = function(script, index) {
|
||||||
pubKeyHash: pubKeyHash,
|
pubKeyHash: pubKeyHash,
|
||||||
scriptPubKey: script,
|
scriptPubKey: script,
|
||||||
scriptType: scriptType,
|
scriptType: scriptType,
|
||||||
|
isP2sh: true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._p2shInput = function(input) {
|
||||||
TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) {
|
|
||||||
var originalScriptBuf = this.tx.ins[input.i].s;
|
|
||||||
|
|
||||||
if (!this.hashToScriptMap)
|
if (!this.hashToScriptMap)
|
||||||
throw new Error('hashToScriptMap not set');
|
throw new Error('hashToScriptMap not set');
|
||||||
|
|
||||||
|
@ -668,12 +681,21 @@ TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txS
|
||||||
if (!fnToSign[scriptType] || scriptType === Script.TX_SCRIPTHASH)
|
if (!fnToSign[scriptType] || scriptType === Script.TX_SCRIPTHASH)
|
||||||
throw new Error('dont know how to sign p2sh script type:'+ script.getRawOutType());
|
throw new Error('dont know how to sign p2sh script type:'+ script.getRawOutType());
|
||||||
|
|
||||||
var newInput = this._getInputForP2sh(script, input.i);
|
return {
|
||||||
var newTxSigHash = this.tx.hashForSignature( script, newInput.i, this.signhash);
|
input: this._getInputForP2sh(script, input.i),
|
||||||
var ret = fnToSign[scriptType].call(this, walletKeyMap, newInput, newTxSigHash);
|
txSigHash: this.tx.hashForSignature( script, input.i, this.signhash),
|
||||||
|
scriptType: script.classify(),
|
||||||
|
scriptBuf: scriptBuf,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) {
|
||||||
|
|
||||||
|
var p2sh = this._p2shInput(input);
|
||||||
|
|
||||||
|
var ret = fnToSign[p2sh.scriptType].call(this, walletKeyMap, p2sh.input, p2sh.txSigHash);
|
||||||
if (ret && ret.script && ret.signaturesAdded) {
|
if (ret && ret.script && ret.signaturesAdded) {
|
||||||
ret.script = this._addScript(ret.script, scriptBuf);
|
ret.script = this._addScript(ret.script, p2sh.scriptBuf);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
@ -699,7 +721,7 @@ TransactionBuilder.prototype.sign = function(keys) {
|
||||||
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash);
|
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash);
|
||||||
if (ret && ret.script) {
|
if (ret && ret.script) {
|
||||||
tx.ins[i].s = ret.script;
|
tx.ins[i].s = ret.script;
|
||||||
if (ret.isFullySigned) this.inputsSigned++;
|
if (ret.inputFullySigned) this.inputsSigned++;
|
||||||
if (ret.signaturesAdded) this.signaturesAdded +=ret.signaturesAdded;
|
if (ret.signaturesAdded) this.signaturesAdded +=ret.signaturesAdded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -833,69 +855,76 @@ TransactionBuilder.prototype._checkMergeability = function(b) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._mergeInputSigP2sh = function(input,s0,s1) {
|
||||||
|
var p2sh = this._p2shInput(input);
|
||||||
|
var redeemScript = new Script(p2sh.scriptBuf);
|
||||||
|
var pubkeys = redeemScript.capture();
|
||||||
|
|
||||||
// this assumes that the same signature can not be v0 / v1 (which shouldnt be!)
|
// Look for differences
|
||||||
TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf, ignoreConflictingSignatures) {
|
var s0keys = {};
|
||||||
if (buffertools.compare(s0buf,s1buf) === 0) {
|
var l = pubkeys.length;
|
||||||
//console.log('BUFFERS .s MATCH'); //TODO
|
for (var j=0; j<l; j++) {
|
||||||
return s0buf;
|
if ( this._chunkSignedWithKey(s0, p2sh.txSigHash, pubkeys[j]))
|
||||||
|
s0keys[pubkeys[j].toString('hex')] = 1;
|
||||||
}
|
}
|
||||||
// Is multisig?
|
|
||||||
|
var diff = [];
|
||||||
|
for (var j=0; j<l; j++) {
|
||||||
|
var chunk = this._chunkSignedWithKey(s1, p2sh.txSigHash, pubkeys[j]);
|
||||||
|
var pubHex = pubkeys[j].toString('hex');
|
||||||
|
if (chunk && !s0keys[pubHex]) {
|
||||||
|
diff.push({
|
||||||
|
prio: j,
|
||||||
|
chunk: chunk,
|
||||||
|
pubHex: pubHex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add signatures
|
||||||
|
for(var j in diff) {
|
||||||
|
var newSig = diff[j];
|
||||||
|
var order = this._getNewSignatureOrder(newSig.prio,s0,p2sh.txSigHash,pubkeys);
|
||||||
|
s0.chunks.splice(order+1,0,newSig.chunk);
|
||||||
|
this.signaturesAdded++;
|
||||||
|
}
|
||||||
|
s0.updateBuffer();
|
||||||
|
return s0.getBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) {
|
||||||
|
if (buffertools.compare(s0buf,s1buf) === 0)
|
||||||
|
return s0buf;
|
||||||
|
|
||||||
var s0 = new Script(s0buf);
|
var s0 = new Script(s0buf);
|
||||||
var s1 = new Script(s1buf);
|
var s1 = new Script(s1buf);
|
||||||
var l0 = s0.chunks.length;
|
var l0 = s0.chunks.length;
|
||||||
var l1 = s1.chunks.length;
|
var l1 = s1.chunks.length;
|
||||||
var s0map = {};
|
var s0map = {};
|
||||||
|
|
||||||
if (l0 && l1 && l0 !== l1)
|
if (l0 && l1 && ((l0<2 && l1>2) || (l1<2 && l0>2 )))
|
||||||
throw new Error('TX sig types mismatch in merge');
|
throw new Error('TX sig types mismatch in merge');
|
||||||
|
|
||||||
if (!l0 && !l1) return s0buf;
|
if ((!l0 && !l1) || ( l0 && !l1) || (!l0 && l1))
|
||||||
if ( l0 && !l1) return s0buf;
|
return s1buf;
|
||||||
if (!l0 && l1) return s1buf;
|
|
||||||
|
|
||||||
// Look for differences.
|
// Get the pubkeys
|
||||||
for (var i=0; i<l0; i++) {
|
var input = this.inputMap[index];
|
||||||
if (!this._chunkIsEmpty(s0.chunks[i]))
|
var type = input.scriptPubKey.classify();
|
||||||
s0map[s0.chunks[i]] = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
var diff = [];
|
//p2pubkey or p2pubkeyhash
|
||||||
for (var i=0; i<l1; i++) {
|
if (type === Script.TX_PUBKEYHASH || type === Script.TX_PUBKEY) {
|
||||||
if ( !this._chunkIsEmpty(s1.chunks[i]) && !s0map[s1.chunks[i]]) {
|
log.debug('Merging two signed inputs type:' +
|
||||||
diff.push(s1.chunks[i]);
|
input.scriptPubKey.getRawOutType() + '. Signatures differs. Using the first version.');
|
||||||
|
return s0buf;
|
||||||
}
|
}
|
||||||
};
|
else if (type!== Script.TX_SCRIPTHASH) {
|
||||||
|
// No support for normal multisig or strange txs.
|
||||||
if (!diff) {
|
throw new Error('Script type:'+input.scriptPubKey.getRawOutType()+'not supported at #merge');
|
||||||
console.log('[TransactionBuilder.js.857: NO DIFF FOUND, just ORDER DIFF]'); //TODO
|
|
||||||
return s0.getBuffer();
|
|
||||||
}
|
}
|
||||||
|
return this._mergeInputSigP2sh(input,s0, s1);
|
||||||
var emptySlots = [];
|
|
||||||
for (var i=1; i<l0; i++) {
|
|
||||||
if (this._chunkIsEmpty(s0.chunks[i])) {
|
|
||||||
emptySlots.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emptySlots.length<diff.length) {
|
|
||||||
if (!ignoreConflictingSignatures) {
|
|
||||||
throw new Error(
|
|
||||||
'no enough empty slots to merge Txs: Check ignoreConflictingSignatures option');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (var i=0; i<diff.length; i++) {
|
|
||||||
s0.chunks[emptySlots[i]] = diff[i];
|
|
||||||
this.signaturesAdded++;
|
|
||||||
}
|
|
||||||
s0.updateBuffer();
|
|
||||||
}
|
|
||||||
return s0.getBuffer();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures) {
|
TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures) {
|
||||||
var v0 = this.tx;
|
var v0 = this.tx;
|
||||||
var v1 = tx;
|
var v1 = tx;
|
||||||
|
@ -915,24 +944,23 @@ TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures
|
||||||
if (buffertools.compare(i0.o,i1.o) !== 0)
|
if (buffertools.compare(i0.o,i1.o) !== 0)
|
||||||
throw new Error('TX .o in mismatch in merge. Input:',i);
|
throw new Error('TX .o in mismatch in merge. Input:',i);
|
||||||
|
|
||||||
i0.s=this._mergeInputSig(i0.s,i1.s, ignoreConflictingSignatures);
|
i0.s=this._mergeInputSig(i, i0.s,i1.s);
|
||||||
|
|
||||||
if (v0.isInputComplete(i)) this.inputsSigned++;
|
if (v0.isInputComplete(i)) this.inputsSigned++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TransactionBuilder.prototype.merge = function(b, ignoreConflictingSignatures) {
|
TransactionBuilder.prototype.merge = function(b) {
|
||||||
this._checkMergeability(b);
|
this._checkMergeability(b);
|
||||||
|
|
||||||
|
|
||||||
// Does this tX have any signature already?
|
// Does this tX have any signature already?
|
||||||
if (this.tx || b.tx) {
|
if (this.tx || b.tx) {
|
||||||
if (this.tx.getNormalizedHash().toString('hex')
|
if (this.tx.getNormalizedHash().toString('hex')
|
||||||
!== b.tx.getNormalizedHash().toString('hex'))
|
!== b.tx.getNormalizedHash().toString('hex'))
|
||||||
throw new Error('mismatch at TransactionBuilder NTXID');
|
throw new Error('mismatch at TransactionBuilder NTXID');
|
||||||
|
|
||||||
this._mergeTx(b.tx, ignoreConflictingSignatures);
|
this._mergeTx(b.tx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,7 @@ describe('Script', function() {
|
||||||
var b = new Buffer('00483045022100aac57f3ba004e6265097b759d92132c43fb5dcb033c2a3f6e61caa5e05e6b97e02200dae579e54c8e733d222eae5bbbaf557bbcf03271cf76775c91744c24a99916b014c69522103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e453ae', 'hex');
|
var b = new Buffer('00483045022100aac57f3ba004e6265097b759d92132c43fb5dcb033c2a3f6e61caa5e05e6b97e02200dae579e54c8e733d222eae5bbbaf557bbcf03271cf76775c91744c24a99916b014c69522103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e453ae', 'hex');
|
||||||
var s = new Script(b);
|
var s = new Script(b);
|
||||||
s.countMissingSignatures().should.equal(1);
|
s.countMissingSignatures().should.equal(1);
|
||||||
|
s.countSignatures().should.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should count missing signatures p2sh 2-3 0 missing', function() {
|
it('should count missing signatures p2sh 2-3 0 missing', function() {
|
||||||
|
@ -148,9 +149,8 @@ describe('Script', function() {
|
||||||
var b = new Buffer('00483045022100aac57f3ba004e6265097b759d92132c43fb5dcb033c2a3f6e61caa5e05e6b97e02200dae579e54c8e733d222eae5bbbaf557bbcf03271cf76775c91744c24a99916b01483045022100a505aff6a1d9cc14d0658a99ebcf1901b5c9f9e6408055fa9b9da443c80bfdb602207f0391c98abecc93bc3b353c55ada4d3fb6d4bab48fd63ae184df1af367cee46014c69522103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e453ae', 'hex');
|
var b = new Buffer('00483045022100aac57f3ba004e6265097b759d92132c43fb5dcb033c2a3f6e61caa5e05e6b97e02200dae579e54c8e733d222eae5bbbaf557bbcf03271cf76775c91744c24a99916b01483045022100a505aff6a1d9cc14d0658a99ebcf1901b5c9f9e6408055fa9b9da443c80bfdb602207f0391c98abecc93bc3b353c55ada4d3fb6d4bab48fd63ae184df1af367cee46014c69522103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e453ae', 'hex');
|
||||||
var s = new Script(b);
|
var s = new Script(b);
|
||||||
s.countMissingSignatures().should.equal(0);
|
s.countMissingSignatures().should.equal(0);
|
||||||
|
s.countSignatures().should.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -601,24 +601,23 @@ describe('TransactionBuilder', function() {
|
||||||
var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3);
|
var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3);
|
||||||
|
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
|
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
|
|
||||||
// This is cumbersome. Before sign, missing is 1. Need to be changed in the future
|
tx.countInputSignatures(0).should.equal(0);
|
||||||
tx.countInputMissingSignatures(0).should.equal(1);
|
|
||||||
b.sign(['cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV']);
|
|
||||||
tx.countInputMissingSignatures(0).should.equal(1);
|
|
||||||
|
|
||||||
|
b.sign(['cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV']);
|
||||||
|
|
||||||
|
tx.countInputSignatures(0).should.equal(0);
|
||||||
b.sign(k1);
|
b.sign(k1);
|
||||||
tx.countInputMissingSignatures(0).should.equal(2);
|
tx.countInputSignatures(0).should.equal(1);
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
|
|
||||||
b.sign(k2);
|
b.sign(k2);
|
||||||
tx.countInputMissingSignatures(0).should.equal(1);
|
tx.countInputSignatures(0).should.equal(2);
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
|
|
||||||
b.sign(k3);
|
b.sign(k3);
|
||||||
tx.countInputMissingSignatures(0).should.equal(0);
|
tx.countInputSignatures(0).should.equal(3);
|
||||||
b.isFullySigned().should.equal(true);
|
b.isFullySigned().should.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -639,19 +638,19 @@ describe('TransactionBuilder', function() {
|
||||||
var k23 = testdata.dataUnspentSign.keyStringsMulti.slice(1,3);
|
var k23 = testdata.dataUnspentSign.keyStringsMulti.slice(1,3);
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
|
|
||||||
tx.countInputMissingSignatures(0).should.equal(1);
|
tx.countInputSignatures(0).should.equal(0);
|
||||||
b.sign(k1);
|
b.sign(k1);
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
tx.countInputMissingSignatures(0).should.equal(2);
|
tx.countInputSignatures(0).should.equal(1);
|
||||||
b.sign(k1);
|
b.sign(k1);
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
tx.countInputMissingSignatures(0).should.equal(2);
|
tx.countInputSignatures(0).should.equal(1);
|
||||||
b.sign(k1);
|
b.sign(k1);
|
||||||
b.isFullySigned().should.equal(false);
|
b.isFullySigned().should.equal(false);
|
||||||
tx.countInputMissingSignatures(0).should.equal(2);
|
tx.countInputSignatures(0).should.equal(1);
|
||||||
b.sign(k23);
|
b.sign(k23);
|
||||||
b.isFullySigned().should.equal(true);
|
b.isFullySigned().should.equal(true);
|
||||||
tx.countInputMissingSignatures(0).should.equal(0);
|
tx.countInputSignatures(0).should.equal(3);
|
||||||
|
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
var shex = testdata.dataUnspentSign.unspentMulti[0].scriptPubKey;
|
var shex = testdata.dataUnspentSign.unspentMulti[0].scriptPubKey;
|
||||||
|
@ -698,12 +697,9 @@ describe('TransactionBuilder', function() {
|
||||||
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'},
|
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'},
|
||||||
};
|
};
|
||||||
var data = getInfoForP2sh();
|
var data = getInfoForP2sh();
|
||||||
console.log('[test.TransactionBuilder.js.700:data:]',data); //TODO
|
|
||||||
process.exit(1);
|
|
||||||
// multisig p2sh
|
// multisig p2sh
|
||||||
var p2shOpts = {nreq:3, pubkeys:data.pubkeys};
|
var p2shOpts = {nreq:3, pubkeys:data.pubkeys};
|
||||||
var info = TransactionBuilder.infoForP2sh(p2shOpts, network);
|
var info = TransactionBuilder.infoForP2sh(p2shOpts, network);
|
||||||
console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
|
||||||
|
|
||||||
var outs = outs || [{
|
var outs = outs || [{
|
||||||
address: 'mon1Hqs3jqKTtRSnRwJ3pRYMFos9WYfKb5',
|
address: 'mon1Hqs3jqKTtRSnRwJ3pRYMFos9WYfKb5',
|
||||||
|
@ -726,11 +722,8 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
(function() {b.sign(testdata.dataUnspentSign.keyStringsP2sh);}).should.throw();
|
(function() {b.sign(testdata.dataUnspentSign.keyStringsP2sh);}).should.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sign a p2sh/multisig tx right order', function(done) {
|
|
||||||
var b = getP2shBuilder(1);
|
var _checkOK = function(b, done) {
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]);
|
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]);
|
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]);
|
|
||||||
b.isFullySigned().should.equal(true);
|
b.isFullySigned().should.equal(true);
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
tx.ins.length.should.equal(1);
|
tx.ins.length.should.equal(1);
|
||||||
|
@ -745,29 +738,19 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should failed to verify a p2sh/multisig tx wrong order', function(done) {
|
};
|
||||||
|
|
||||||
|
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]].forEach(function(order) {
|
||||||
|
it('should sign a p2sh/multisig tx in order ' + order.join(','), function(done) {
|
||||||
var b = getP2shBuilder(1);
|
var b = getP2shBuilder(1);
|
||||||
|
b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]);
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]);
|
b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]);
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]);
|
b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]);
|
||||||
b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]);
|
_checkOK(b, done);
|
||||||
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);
|
|
||||||
|
|
||||||
var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey;
|
|
||||||
var s = new Script(new Buffer(shex,'hex'));
|
|
||||||
tx.verifyInput(0,s, vopts, function(err, results){
|
|
||||||
should.not.exist(results);
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should sign in steps a p2sh/multisign tx', function() {
|
it('should sign in steps a p2sh/multisign tx', function() {
|
||||||
var b = getP2shBuilder(1);
|
var b = getP2shBuilder(1);
|
||||||
|
|
||||||
|
@ -991,8 +974,9 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
amount: 16
|
amount: 16
|
||||||
}])
|
}])
|
||||||
.sign(testdata.dataUnspentSign.keyStrings);
|
.sign(testdata.dataUnspentSign.keyStrings);
|
||||||
(function() {b2.merge(b);}).should.throw();
|
b2.isFullySigned().should.equal(true);
|
||||||
b2.merge(b, true);
|
b2.merge(b);
|
||||||
|
b2.isFullySigned().should.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#merge p2sh/steps', function(done) {
|
it('#merge p2sh/steps', function(done) {
|
||||||
|
@ -1008,6 +992,9 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
tx.isComplete().should.equal(false);
|
tx.isComplete().should.equal(false);
|
||||||
|
|
||||||
|
b = TransactionBuilder.fromObj(b.toObj());
|
||||||
|
|
||||||
|
// TODO TO OBJ!
|
||||||
var b2 = getP2shBuilder(1);
|
var b2 = getP2shBuilder(1);
|
||||||
b2.sign(k2);
|
b2.sign(k2);
|
||||||
b2.signaturesAdded.should.equal(1);
|
b2.signaturesAdded.should.equal(1);
|
||||||
|
@ -1016,6 +1003,7 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
tx = b2.build();
|
tx = b2.build();
|
||||||
tx.isComplete().should.equal(false);
|
tx.isComplete().should.equal(false);
|
||||||
|
|
||||||
|
b2 = TransactionBuilder.fromObj(b2.toObj());
|
||||||
var b3 = getP2shBuilder(1);
|
var b3 = getP2shBuilder(1);
|
||||||
b3.sign(k3);
|
b3.sign(k3);
|
||||||
b3.signaturesAdded.should.equal(1);
|
b3.signaturesAdded.should.equal(1);
|
||||||
|
@ -1024,6 +1012,7 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
tx = b3.build();
|
tx = b3.build();
|
||||||
tx.isComplete().should.equal(true);
|
tx.isComplete().should.equal(true);
|
||||||
|
|
||||||
|
b3 = TransactionBuilder.fromObj(b3.toObj());
|
||||||
b2.merge(b3);
|
b2.merge(b3);
|
||||||
b2.signaturesAdded.should.equal(3);
|
b2.signaturesAdded.should.equal(3);
|
||||||
tx = b2.build();
|
tx = b2.build();
|
||||||
|
@ -1032,13 +1021,10 @@ console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO
|
||||||
var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey;
|
var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey;
|
||||||
var s = new Script(new Buffer(shex,'hex'));
|
var s = new Script(new Buffer(shex,'hex'));
|
||||||
tx.verifyInput(0,s, vopts, function(err, results){
|
tx.verifyInput(0,s, vopts, function(err, results){
|
||||||
console.log('[test.TransactionBuilder.js.870:err:]',err,results); //TODO
|
should.exist(results);
|
||||||
//TODO
|
results.should.equal(true);
|
||||||
// should.not.exist(err);
|
should.not.exist(err);
|
||||||
// should.exist(results);
|
done();
|
||||||
// results.should.equal(true);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue