|
@ -89,7 +89,7 @@ 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); |
|
|
|
|
|
|
|
|
function TransactionBuilder(opts) { |
|
|
function TransactionBuilder(opts) { |
|
|
var opts = opts || {}; |
|
|
opts = opts || {}; |
|
|
this.txobj = {}; |
|
|
this.txobj = {}; |
|
|
this.txobj.version = 1; |
|
|
this.txobj.version = 1; |
|
|
this.txobj.lock_time = opts.lockTime || 0; |
|
|
this.txobj.lock_time = opts.lockTime || 0; |
|
@ -144,18 +144,29 @@ TransactionBuilder.prototype._setInputMap = function() { |
|
|
|
|
|
|
|
|
var l = this.selectedUtxos.length; |
|
|
var l = this.selectedUtxos.length; |
|
|
for (var i = 0; i < l; i++) { |
|
|
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({ |
|
|
inputMap.push({ |
|
|
address: s.address, |
|
|
address: utxo.address, //TODO que pasa en multisig normal?
|
|
|
scriptPubKey: s.scriptPubKey |
|
|
scriptPubKeyHex: utxo.scriptPubKey, |
|
|
|
|
|
scriptPubKey: scriptPubKey, |
|
|
|
|
|
scriptType: scriptType, |
|
|
|
|
|
i: i, |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
this.inputMap = inputMap; |
|
|
this.inputMap = inputMap; |
|
|
return this; |
|
|
return this; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.getSelectedUnspent = function(neededAmountSat) { |
|
|
TransactionBuilder.prototype.getSelectedUnspent = function() { |
|
|
return this.selectedUtxos; |
|
|
return this.selectedUtxos; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -360,14 +371,6 @@ TransactionBuilder._mapKeys = function(keys) { |
|
|
return walletKeyMap; |
|
|
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) { |
|
|
TransactionBuilder._signHashAndVerify = function(wk, txSigHash) { |
|
|
var triesLeft = 10, sigRaw; |
|
|
var triesLeft = 10, sigRaw; |
|
|
|
|
|
|
|
@ -387,44 +390,208 @@ TransactionBuilder.prototype._checkTx = function() { |
|
|
throw new Error('tx is not defined'); |
|
|
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) { |
|
|
TransactionBuilder.prototype.sign = function(keys) { |
|
|
this._checkTx(); |
|
|
this._checkTx(); |
|
|
|
|
|
|
|
|
var tx = this.tx, |
|
|
var tx = this.tx, |
|
|
ins = tx.ins, |
|
|
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++) { |
|
|
for (var i = 0; i < l; i++) { |
|
|
var im = this.inputMap[i]; |
|
|
var input = this.inputMap[i]; |
|
|
if (typeof im === 'undefined') continue; |
|
|
|
|
|
var wk = walletKeyMap[im.address]; |
|
|
|
|
|
if (!wk) continue; |
|
|
|
|
|
|
|
|
|
|
|
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 ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); |
|
|
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); |
|
|
if (ret && ret.script) { |
|
|
var sigType = new Buffer(1); |
|
|
tx.ins[i].s = ret.script; //esto no aqui TODO
|
|
|
sigType[0] = this.signhash; |
|
|
if (ret.isFullySigned) this.inputsSigned++; |
|
|
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++; |
|
|
|
|
|
} |
|
|
} |
|
|
return this; |
|
|
return this; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// [addr -> script]
|
|
|
|
|
|
TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { |
|
|
|
|
|
this.hashToScriptMap= hashToScriptMap; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.isFullySigned = function() { |
|
|
TransactionBuilder.prototype.isFullySigned = function() { |
|
|
return this.inputsSigned === this.tx.ins.length; |
|
|
return this.inputsSigned === this.tx.ins.length; |
|
|
}; |
|
|
}; |
|
|