|
|
@ -88,6 +88,8 @@ var buffertools = imports.buffertools || require('buffertools'); |
|
|
|
var networks = imports.networks || require('../networks'); |
|
|
|
var WalletKey = imports.WalletKey || require('./WalletKey'); |
|
|
|
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 FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); |
|
|
@ -481,7 +483,7 @@ TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHa |
|
|
|
var scriptSig = new Script(); |
|
|
|
scriptSig.chunks.push(sig); |
|
|
|
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) { |
|
|
@ -500,42 +502,55 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS |
|
|
|
scriptSig.chunks.push(sig); |
|
|
|
scriptSig.chunks.push(wk.privKey.public); |
|
|
|
scriptSig.updateBuffer(); |
|
|
|
return {isFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; |
|
|
|
return {inputFullySigned: true, signaturesAdded: 1, 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]); |
|
|
|
// var _dumpChunks = function (scriptSig, label) {
|
|
|
|
// console.log('## DUMP: ' + label + ' ##');
|
|
|
|
// for(var i=0; i<scriptSig.chunks.length; i++) {
|
|
|
|
// console.log('\tCHUNK ', i, Buffer.isBuffer(scriptSig.chunks[i])
|
|
|
|
// ?scriptSig.chunks[i].toString('hex'):scriptSig.chunks[i] );
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._chunkSignedWithKey = function(scriptSig, txSigHash, publicKey) { |
|
|
|
var ret; |
|
|
|
var k = new Key(); |
|
|
|
k.public =publicKey; |
|
|
|
|
|
|
|
for(var i=1; i<= scriptSig.countSignatures(); i++) { |
|
|
|
var chunk = scriptSig.chunks[i]; |
|
|
|
var sigRaw = new Buffer(chunk.slice(0,chunk.length-1)); |
|
|
|
if (k.verifySignatureSync(txSigHash, sigRaw) ) { |
|
|
|
ret=chunk; |
|
|
|
} |
|
|
|
} |
|
|
|
return ret; |
|
|
|
}; |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
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 wasUpdated; |
|
|
|
return j; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._isSignedWithKey = function(wk, scriptSig, txSigHash, nreq) { |
|
|
|
var ret=false; |
|
|
|
// _dumpChunks(scriptSig);
|
|
|
|
for(var i=1; i<=nreq; i++) { |
|
|
|
TransactionBuilder.prototype._getNewSignatureOrder = function(sigPrio, scriptSig, txSigHash, pubkeys) { |
|
|
|
var iPrio; |
|
|
|
for(var i=1; i<= scriptSig.countSignatures(); 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; |
|
|
|
} |
|
|
|
iPrio = this._getSignatureOrder(sigPrio, sigRaw, txSigHash, pubkeys); |
|
|
|
if (sigPrio <= iPrio) break; |
|
|
|
} |
|
|
|
return ret; |
|
|
|
return (sigPrio === iPrio? -1: i-1); |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._chunkIsEmpty = function(chunk) { |
|
|
@ -543,31 +558,33 @@ TransactionBuilder.prototype._chunkIsEmpty = function(chunk) { |
|
|
|
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) { |
|
|
|
var wasUpdated = this._initMultiSig(scriptSig, nreq); |
|
|
|
TransactionBuilder.prototype._updateMultiSig = function(sigPrio, wk, scriptSig, txSigHash, pubkeys) { |
|
|
|
var wasUpdated = this._initMultiSig(scriptSig); |
|
|
|
|
|
|
|
if (this._isSignedWithKey(wk,scriptSig, txSigHash, nreq)) |
|
|
|
if (this._chunkSignedWithKey(scriptSig, txSigHash, wk.privKey.public)) |
|
|
|
return null; |
|
|
|
|
|
|
|
console.log('[TransactionBuilder.js.552] ', wk.privKey.public.toString('hex')); //TODO
|
|
|
|
// 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; |
|
|
|
} |
|
|
|
// _dumpChunks(scriptSig); // TODO
|
|
|
|
|
|
|
|
// Create signature
|
|
|
|
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); |
|
|
|
var sigType = new Buffer(1); |
|
|
|
sigType[0] = this.signhash; |
|
|
|
var sig = Buffer.concat([sigRaw, sigType]); |
|
|
|
|
|
|
|
// Add signature
|
|
|
|
var order = this._getNewSignatureOrder(sigPrio,scriptSig,txSigHash,pubkeys); |
|
|
|
scriptSig.chunks.splice(order+1,0,sig); |
|
|
|
scriptSig.updateBuffer(); |
|
|
|
wasUpdated=true; |
|
|
|
|
|
|
|
return wasUpdated ? scriptSig : null; |
|
|
|
}; |
|
|
|
|
|
|
@ -581,28 +598,26 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig |
|
|
|
var scriptSig = new Script (originalScriptBuf); |
|
|
|
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
|
|
|
|
var wk = this._findWalletKey(walletKeyMap, {pubKeyBuf: pubkeys[j]}); |
|
|
|
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) { |
|
|
|
scriptSig = newScriptSig; |
|
|
|
signaturesAdded++; |
|
|
|
} |
|
|
|
} |
|
|
|
if (!scriptSig.countMissingSignatures()) { |
|
|
|
|
|
|
|
console.log('########## DONE!!!'); //TODO
|
|
|
|
if (scriptSig.countSignatures() === nreq) { |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
isFullySigned: scriptSig.countMissingSignatures() === 0, |
|
|
|
var ret = { |
|
|
|
inputFullySigned: scriptSig.countSignatures() === nreq, |
|
|
|
signaturesAdded: signaturesAdded, |
|
|
|
script: scriptSig.getBuffer(), |
|
|
|
}; |
|
|
|
return ret; |
|
|
|
}; |
|
|
|
|
|
|
|
var fnToSign = {}; |
|
|
@ -648,13 +663,11 @@ TransactionBuilder.prototype._getInputForP2sh = function(script, index) { |
|
|
|
pubKeyHash: pubKeyHash, |
|
|
|
scriptPubKey: script, |
|
|
|
scriptType: scriptType, |
|
|
|
isP2sh: true, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) { |
|
|
|
var originalScriptBuf = this.tx.ins[input.i].s; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._p2shInput = function(input) { |
|
|
|
if (!this.hashToScriptMap) |
|
|
|
throw new Error('hashToScriptMap not set'); |
|
|
|
|
|
|
@ -668,12 +681,21 @@ TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txS |
|
|
|
if (!fnToSign[scriptType] || scriptType === Script.TX_SCRIPTHASH) |
|
|
|
throw new Error('dont know how to sign p2sh script type:'+ script.getRawOutType()); |
|
|
|
|
|
|
|
var newInput = this._getInputForP2sh(script, input.i); |
|
|
|
var newTxSigHash = this.tx.hashForSignature( script, newInput.i, this.signhash); |
|
|
|
var ret = fnToSign[scriptType].call(this, walletKeyMap, newInput, newTxSigHash); |
|
|
|
return { |
|
|
|
input: this._getInputForP2sh(script, input.i), |
|
|
|
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) { |
|
|
|
ret.script = this._addScript(ret.script, scriptBuf); |
|
|
|
ret.script = this._addScript(ret.script, p2sh.scriptBuf); |
|
|
|
} |
|
|
|
return ret; |
|
|
|
}; |
|
|
@ -699,7 +721,7 @@ TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); |
|
|
|
if (ret && 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; |
|
|
|
} |
|
|
|
} |
|
|
@ -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!)
|
|
|
|
TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf, ignoreConflictingSignatures) { |
|
|
|
if (buffertools.compare(s0buf,s1buf) === 0) { |
|
|
|
//console.log('BUFFERS .s MATCH'); //TODO
|
|
|
|
return s0buf; |
|
|
|
// Look for differences
|
|
|
|
var s0keys = {}; |
|
|
|
var l = pubkeys.length; |
|
|
|
for (var j=0; j<l; j++) { |
|
|
|
if ( this._chunkSignedWithKey(s0, p2sh.txSigHash, pubkeys[j])) |
|
|
|
s0keys[pubkeys[j].toString('hex')] = 1; |
|
|
|
} |
|
|
|
|
|
|
|
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++; |
|
|
|
} |
|
|
|
// Is multisig?
|
|
|
|
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 s1 = new Script(s1buf); |
|
|
|
var l0 = s0.chunks.length; |
|
|
|
var l1 = s1.chunks.length; |
|
|
|
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'); |
|
|
|
|
|
|
|
if (!l0 && !l1) return s0buf; |
|
|
|
if ( l0 && !l1) return s0buf; |
|
|
|
if (!l0 && l1) return s1buf; |
|
|
|
|
|
|
|
// Look for differences.
|
|
|
|
for (var i=0; i<l0; i++) { |
|
|
|
if (!this._chunkIsEmpty(s0.chunks[i])) |
|
|
|
s0map[s0.chunks[i]] = 1; |
|
|
|
}; |
|
|
|
|
|
|
|
var diff = []; |
|
|
|
for (var i=0; i<l1; i++) { |
|
|
|
if ( !this._chunkIsEmpty(s1.chunks[i]) && !s0map[s1.chunks[i]]) { |
|
|
|
diff.push(s1.chunks[i]); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
if (!diff) { |
|
|
|
console.log('[TransactionBuilder.js.857: NO DIFF FOUND, just ORDER DIFF]'); //TODO
|
|
|
|
return s0.getBuffer(); |
|
|
|
} |
|
|
|
if ((!l0 && !l1) || ( l0 && !l1) || (!l0 && l1)) |
|
|
|
return s1buf; |
|
|
|
|
|
|
|
var emptySlots = []; |
|
|
|
for (var i=1; i<l0; i++) { |
|
|
|
if (this._chunkIsEmpty(s0.chunks[i])) { |
|
|
|
emptySlots.push(i); |
|
|
|
} |
|
|
|
} |
|
|
|
// Get the pubkeys
|
|
|
|
var input = this.inputMap[index]; |
|
|
|
var type = input.scriptPubKey.classify(); |
|
|
|
|
|
|
|
if (emptySlots.length<diff.length) { |
|
|
|
if (!ignoreConflictingSignatures) { |
|
|
|
throw new Error( |
|
|
|
'no enough empty slots to merge Txs: Check ignoreConflictingSignatures option'); |
|
|
|
} |
|
|
|
//p2pubkey or p2pubkeyhash
|
|
|
|
if (type === Script.TX_PUBKEYHASH || type === Script.TX_PUBKEY) { |
|
|
|
log.debug('Merging two signed inputs type:' + |
|
|
|
input.scriptPubKey.getRawOutType() + '. Signatures differs. Using the first version.'); |
|
|
|
return s0buf; |
|
|
|
} |
|
|
|
else { |
|
|
|
for (var i=0; i<diff.length; i++) { |
|
|
|
s0.chunks[emptySlots[i]] = diff[i]; |
|
|
|
this.signaturesAdded++; |
|
|
|
} |
|
|
|
s0.updateBuffer(); |
|
|
|
else if (type!== Script.TX_SCRIPTHASH) { |
|
|
|
// No support for normal multisig or strange txs.
|
|
|
|
throw new Error('Script type:'+input.scriptPubKey.getRawOutType()+'not supported at #merge'); |
|
|
|
} |
|
|
|
return s0.getBuffer(); |
|
|
|
return this._mergeInputSigP2sh(input,s0, s1); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures) { |
|
|
|
var v0 = this.tx; |
|
|
|
var v1 = tx; |
|
|
@ -912,27 +941,26 @@ TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures |
|
|
|
if (i0.q !== i1.q) |
|
|
|
throw new Error('TX sequence ins mismatch in merge. Input:',i); |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
i0.s=this._mergeInputSig(i0.s,i1.s, ignoreConflictingSignatures); |
|
|
|
i0.s=this._mergeInputSig(i, i0.s,i1.s); |
|
|
|
|
|
|
|
if (v0.isInputComplete(i)) this.inputsSigned++; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.merge = function(b, ignoreConflictingSignatures) { |
|
|
|
TransactionBuilder.prototype.merge = function(b) { |
|
|
|
this._checkMergeability(b); |
|
|
|
|
|
|
|
|
|
|
|
// Does this tX have any signature already?
|
|
|
|
if (this.tx || b.tx) { |
|
|
|
if (this.tx.getNormalizedHash().toString('hex') |
|
|
|
!== b.tx.getNormalizedHash().toString('hex')) |
|
|
|
throw new Error('mismatch at TransactionBuilder NTXID'); |
|
|
|
|
|
|
|
this._mergeTx(b.tx, ignoreConflictingSignatures); |
|
|
|
this._mergeTx(b.tx); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|