Merge pull request #202 from matiu/feature/TransactionBuilder-Multisig-support

Feature/transaction builder multisig support
This commit is contained in:
Ryan X. Charles 2014-04-01 12:20:24 -04:00
commit abcaa3d16f
11 changed files with 736 additions and 60 deletions

View File

@ -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;
};

View File

@ -58,7 +58,7 @@
*
* @opts
* {
* remainderAddress: null,
* remainderOut: null,
* fee: 0.001,
* lockTime: null,
* spendUnconfirmed: false,
@ -67,8 +67,12 @@
* Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
* repectively, to provide amounts in satoshis.
*
* If no remainderAddress is given, and there are remainder coins, the
* first IN address will be used to return the coins. (TODO: is this is reasonable?)
* If no remainderOut is given, and there are remainder coins, the
* first IN out will be used to return the coins. remainderOut has the form:
* remainderOut = { address: 1xxxxx}
* or
* remainderOut = { pubkeys: ['hex1','hex2',...} for multisig
*
*
*/
@ -89,7 +93,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;
@ -101,7 +105,7 @@ function TransactionBuilder(opts) {
if (opts.fee || opts.feeSat) {
this.givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
}
this.remainderAddress = opts.remainderAddress;
this.remainderOut = opts.remainderOut;
this.signhash = opts.signhash || Transaction.SIGHASH_ALL;
this.tx = {};
@ -134,6 +138,49 @@ TransactionBuilder._scriptForAddress = function(addressString) {
return script;
};
TransactionBuilder._scriptForPubkeys = function(out) {
var l = out.pubkeys.length;
var pubKeyBuf=[];
for (var i=0; i<l; i++) {
pubKeyBuf.push(new Buffer(out.pubkeys[i],'hex'));
}
return Script.createMultisig(out.nreq, pubKeyBuf);
};
TransactionBuilder._scriptForOut = function(out) {
var ret;
if (out.address)
ret = this._scriptForAddress(out.address);
else if (out.pubkeys || out.nreq || out.nreq > 1)
ret = this._scriptForPubkeys(out);
else
throw new Error('unknown out type');
return ret;
};
TransactionBuilder.infoForP2sh = function(opts, networkName) {
var script = this._scriptForOut(opts);
var hash = util.sha256ripe160(script.getBuffer());
var version = networkName === 'testnet' ?
networks.testnet.addressScript : networks.livenet.addressScript;
var addr = new Address(version, hash);
var addrStr = addr.as('base58');
return {
script: script,
scriptBufHex: script.getBuffer().toString('hex'),
hash: hash,
address: addrStr,
};
};
TransactionBuilder.prototype.setUnspent = function(utxos) {
this.utxos = utxos;
return this;
@ -144,18 +191,28 @@ 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;
};
@ -200,7 +257,8 @@ TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) {
} while (!fulfill && minConfirmationSteps.length);
if (!fulfill)
throw new Error('no enough unspent to fulfill totalNeededAmount');
throw new Error('no enough unspent to fulfill totalNeededAmount [SAT]:' +
neededAmountSat);
this.selectedUtxos = sel;
this._setInputMap();
@ -268,9 +326,9 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) {
}
if (remainderSat.cmp(0) > 0) {
var remainderAddress = this.remainderAddress || this.selectedUtxos[0].address;
var remainderOut = this.remainderOut || this.selectedUtxos[0];
var value = util.bigIntToValue(remainderSat);
var script = TransactionBuilder._scriptForAddress(remainderAddress);
var script = TransactionBuilder._scriptForOut(remainderOut);
var txout = {
v: value,
s: script.getBuffer(),
@ -316,7 +374,7 @@ TransactionBuilder.prototype.setOutputs = function(outs) {
for (var i = 0; i < l; i++) {
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
var value = util.bigIntToValue(amountSat);
var script = TransactionBuilder._scriptForAddress(outs[i].address);
var script = TransactionBuilder._scriptForOut(outs[i]);
var txout = {
v: value,
s: script.getBuffer(),
@ -360,14 +418,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 +437,260 @@ 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 = {};
TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) {
var originalScriptBuf = this.tx.ins[input.i].s;
if (!this.hashToScriptMap)
throw new Error('hashToScriptMap not set');
var scriptHex = this.hashToScriptMap[input.address];
if (!scriptHex) return;
var script = new Script(new Buffer(scriptHex,'hex'));
var scriptType = script.classify();
var scriptPubKeyHex = script.getBuffer().toString('hex');
if (!fnToSign[scriptType])
throw new Error('dont know how to sign p2sh script type'+ script.getRawOutType());
var newInput = {
address: 'TODO', // if p2pkubkeyhash -> get the address
i: input.i,
scriptPubKey: script,
scriptPubKeyHex: scriptPubKeyHex ,
scriptType: scriptType,
};
var txSigHash2 = this.tx.hashForSignature( script, input.i, this.signhash);
var ret = fnToSign[scriptType].call(this, walletKeyMap, newInput, txSigHash2);
var rc =1; //TODO : si alguno firmó...
if (ret.script) {
console.log('[TransactionBuilder.js.634] IN'); //TODO
var scriptSig = new Script(originalScriptBuf);
var len = scriptSig.chunks.length;
var scriptBufNotAlreadyAppended = scriptSig.chunks[len-1] !== undefined && (typeof scriptSig.chunks[len-1] == "number" || scriptSig.chunks[len-1].toString('hex') != scriptBuf.toString('hex'));
if (rc > 0 && scriptBufNotAlreadyAppended) {
scriptSig.chunks.push(scriptBuf);
scriptSig.updateBuffer();
ret.script = scriptSig.getBuffer();
}
if (scriptType == Script.TX_MULTISIG && scriptSig.finishedMultiSig())
{
scriptSig.removePlaceHolders();
scriptSig.prependOp0();
ret.script = scriptSig.getBuffer();
}
}
return ret;
};
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;
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;
return this;
};
TransactionBuilder.prototype.isFullySigned = function() {
return this.inputsSigned === this.tx.ins.length;
};

View File

@ -0,0 +1,104 @@
var run = function() {
bitcore = typeof (bitcore) === 'undefined' ? require('../bitcore') : bitcore;
var networks = require('../networks');
var WalletKey = bitcore.WalletKey;
var Script = bitcore.Script;
var Builder = bitcore.TransactionBuilder;
var opts = {network: networks.testnet};
console.log('## Network: ' + opts.network.name);
var input = {};
input.addr = "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp";
input.priv = "cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V";
// Complete with the corresponding UTXO you want to use
var utxos = [
{
address: input.addr,
txid: "a2a1b0bfbbe769253787d83c097adf61e6d77088e295249e9c3f1ca8a035c639",
vout: 0,
ts: 1396288753,
scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac",
amount: 0.63,
confirmations: 2
}
];
var privs = [
"cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN",
"cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ",
"cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg",
"cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm",
"cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu",
];
var pubkeys = []
privs.forEach(function(p) {
var wk = new WalletKey(opts);
wk.fromObj({priv: p});
pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public));
});
var outs = [{nreq:3, pubkeys:pubkeys, amount:0.05}];
var tx = new Builder(opts)
.setUnspent(utxos)
.setOutputs(outs)
.sign([input.priv])
.build();
var txHex = tx.serialize().toString('hex');
console.log('1) SEND TO MULSISIG TX: ', txHex);
console.log('[this example originally generated TXID: ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d on testnet]\n\n\thttp://test.bitcore.io/tx/ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d\n\n');
//save scriptPubKey
var scriptPubKey = tx.outs[0].s.toString('hex');
/*
*
* REDDEEM TX
*/
var utxos2 = [
{
address: input.addr,
txid: "ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d",
vout: 0,
ts: 1396288753,
scriptPubKey: scriptPubKey,
amount: 0.05,
confirmations: 2
}
];
outs = [{address:input.addr, amount:0.04}];
var b = new Builder(opts)
.setUnspent(utxos2)
.setOutputs(outs)
.sign(privs);
tx= b.build();
var txHex = tx.serialize().toString('hex');
console.log('2) REDEEM SCRIPT: ', txHex);
console.log('=> Is signed status:', b.isFullySigned(), b.countInputMultiSig(0) );
console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e');
};
// This is just for browser & mocha compatibility
if (typeof module !== 'undefined') {
module.exports.run = run;
if (require.main === module) {
run();
}
} else {
run();
}
////

View File

@ -0,0 +1,120 @@
var run = function() {
bitcore = typeof (bitcore) === 'undefined' ? require('../bitcore') : bitcore;
var networks = require('../networks');
var WalletKey = bitcore.WalletKey;
var Script = bitcore.Script;
var Builder = bitcore.TransactionBuilder;
var opts = {network: networks.testnet};
console.log('## Network: ' + opts.network.name);
var input = {};
input.addr = "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp";
input.priv = "cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V";
// Complete with the corresponding UTXO you want to use
var utxos = [
{
address: "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp",
txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71",
vout: 1,
ts: 1396290442,
scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac",
amount: 0.5298,
confirmations: 7
}
];
var privs = [
"cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA",
"cVf32m9MR4vxcPwKNJuPepUe8XrHD2z63eCk76d6njRGyCkXpkSM",
"cQ2sVRFX4jQYMLhWyzz6jTQ2xju51P36968ecXnPhRLKLH677eKR",
"cSw7x9ERcmeWCU3yVBT6Nz7b9JiZ5yjUB7JMhBUv9UM7rSaDpwX9",
"cRQBM8qM4ZXJGP1De4D5RtJm7Q6FNWQSMx7YExxzgn2ehjM3haxW",
];
var pubkeys = []
privs.forEach(function(p) {
var wk = new WalletKey(opts);
wk.fromObj({priv: p});
pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public));
});
// multisig p2sh
var opts = {nreq:3, pubkeys:pubkeys, amount:0.05};
// p2scriphash p2sh
//var opts = [{address: an_address, amount:0.05}];
var info = Builder.infoForP2sh(opts, 'testnet');
var p2shScript = info.scriptBufHex;
var p2shAddress = info.address;
var outs = [{address:p2shAddress, amount:0.05}];
var tx = new Builder(opts)
.setUnspent(utxos)
.setOutputs(outs)
.sign([input.priv])
.build();
var txHex = tx.serialize().toString('hex');
console.log('p2sh address: ' + p2shAddress); //TODO
console.log('1) SEND TO P2SH TX: ', txHex);
console.log('[this example originally generated TXID: 8675a1f7ab0c2eeec2ff2def539446d1942efffd468319107429b894e60ecac3 on testnet]\n\n\thttp://test.bitcore.io/tx/8675a1f7ab0c2eeec2ff2def539446d1942efffd468319107429b894e60ecac3\n\n');
//save scriptPubKey
var scriptPubKey = tx.outs[0].s.toString('hex');
/*
*
* REDDEEM TX
*/
var utxos2 = [
{
address: p2shAddress,
txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71",
vout: 0,
ts: 1396288753,
scriptPubKey: scriptPubKey,
amount: 0.05,
confirmations: 2
}
];
outs = [{address:input.addr, amount:0.04}];
var hashMap = {};
hashMap[p2shAddress]=p2shScript;
var b = new Builder(opts)
.setUnspent(utxos2)
.setHashToScriptMap(hashMap)
.setOutputs(outs)
.sign(privs);
tx= b.build();
var txHex = tx.serialize().toString('hex');
console.log('2) REDEEM SCRIPT: ', txHex);
console.log('=> Is signed status:', b.isFullySigned(), b.countInputMultiSig(0) );
console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e');
};
// This is just for browser & mocha compatibility
if (typeof module !== 'undefined') {
module.exports.run = run;
if (require.main === module) {
run();
}
} else {
run();
}
////

View File

@ -3,12 +3,12 @@
var run = function() {
// Replace '../bitcore' with 'bitcore' if you use this code elsewhere.
// replace '../bitcore' with 'bitcore' if you use this code elsewhere.
var bitcore = require('../bitcore');
var networks = require('../networks');
var WalletKey = bitcore.WalletKey;
var opts = {network: networks.livenet};
var opts = {network: networks.testnet};
function print(wk) {

View File

@ -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;

View File

@ -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

View File

@ -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"
]
}

View File

@ -104,7 +104,7 @@ describe('TransactionBuilder', function() {
var getBuilder2 = function (fee) {
var opts = {
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'},
spendUnconfirmed: true,
};
@ -134,7 +134,7 @@ describe('TransactionBuilder', function() {
var utxos = testdata.dataUnspent;
var opts = {
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'},
spendUnconfirmed: true,
};
var outs = [{
@ -211,7 +211,7 @@ describe('TransactionBuilder', function() {
var getBuilder3 = function (outs) {
var opts = {
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'},
spendUnconfirmed: true,
};
@ -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,137 @@ 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 = {
remainderOut: {address: '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 = {
remainderOut: {address: '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 = {
remainderOut: {address: '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 = {
remainderOut: {address: '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);
});
it('should avoid siging with the same key twice multisig signs (3-5)', function() {
var opts = {
remainderOut: {address: '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 k23 = testdata.dataUnspentSign.keyStringsMulti.slice(1,3);
b.countInputMultiSig(0).should.equal(0);
b.sign(k1);
b.isFullySigned().should.equal(false);
b.countInputMultiSig(0).should.equal(1);
b.sign(k1);
b.isFullySigned().should.equal(false);
b.countInputMultiSig(0).should.equal(1);
b.sign(k1);
b.isFullySigned().should.equal(false);
b.countInputMultiSig(0).should.equal(1);
b.sign(k23);
b.isFullySigned().should.equal(true);
b.countInputMultiSig(0).should.equal(3);
});
});

View File

@ -10,7 +10,11 @@ var examples = [
'PeerManager',
'Rpc',
'SendTx',
'CreateAndSignTx',
'CreateScript',
'CreateKey',
'CreateAndSignTx-Multisig',
'CreateAndSignTx-PayToPubkeyHash',
'CreateAndSignTx-PayToScriptHash',
'Script',
];