mirror of https://github.com/BTCPrivate/copay.git
Merge pull request #1001 from matiu/feature/txproposals/checks10
Refactor Transaction Proposals
This commit is contained in:
commit
569417e4a1
|
@ -8,6 +8,7 @@ lib-cov
|
|||
*.pid
|
||||
*.gz
|
||||
*.swp
|
||||
*.sig
|
||||
tags
|
||||
pids
|
||||
logs
|
||||
|
|
1
copay.js
1
copay.js
|
@ -1,5 +1,6 @@
|
|||
// core
|
||||
module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing');
|
||||
module.exports.TxProposal = require('./js/models/core/TxProposal');
|
||||
module.exports.TxProposals = require('./js/models/core/TxProposals');
|
||||
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
|
||||
module.exports.Passphrase = require('./js/models/core/Passphrase');
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
var imports = require('soop').imports();
|
||||
var bitcore = require('bitcore');
|
||||
var coinUtil = bitcore.util;
|
||||
var preconditions = require('preconditions').singleton();
|
||||
|
||||
var http;
|
||||
|
@ -37,33 +38,6 @@ function _asyncForEach(array, fn, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
function removeRepeatedElements(ar) {
|
||||
var ya = false,
|
||||
v = "",
|
||||
aux = [].concat(ar),
|
||||
r = Array();
|
||||
for (var i in aux) { //
|
||||
v = aux[i];
|
||||
ya = false;
|
||||
for (var a in aux) {
|
||||
if (v == aux[a]) {
|
||||
if (ya == false) {
|
||||
ya = true;
|
||||
} else {
|
||||
aux[a] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var a in aux) {
|
||||
if (aux[a] != "") {
|
||||
r.push(aux[a]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
Insight.prototype._getOptions = function(method, path, data) {
|
||||
return {
|
||||
host: this.host,
|
||||
|
@ -78,6 +52,25 @@ Insight.prototype._getOptions = function(method, path, data) {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
// This is vulneable to txid maneability
|
||||
// TODO: if ret = false,
|
||||
// check output address from similar transactions.
|
||||
//
|
||||
Insight.prototype.checkSentTx = function(tx, cb) {
|
||||
var hash = coinUtil.formatHashFull(tx.getHash());
|
||||
var options = this._getOptions('GET', '/api/tx/' + hash);
|
||||
|
||||
this._request(options, function(err, res) {
|
||||
if (err) return cb(err);
|
||||
var ret = false;
|
||||
if (res && res.txid === hash) {
|
||||
ret = hash;
|
||||
}
|
||||
return cb(err, ret);
|
||||
});
|
||||
};
|
||||
|
||||
Insight.prototype.getTransactions = function(addresses, cb) {
|
||||
preconditions.shouldBeArray(addresses);
|
||||
preconditions.shouldBeFunction(cb);
|
||||
|
@ -101,8 +94,11 @@ Insight.prototype.getTransactions = function(addresses, cb) {
|
|||
callback();
|
||||
});
|
||||
}, function() {
|
||||
var clean_txids = removeRepeatedElements(txids);
|
||||
_asyncForEach(clean_txids, function(txid, callback2) {
|
||||
var uniqueTxids = {};
|
||||
for (var k in txids) {
|
||||
uniqueTxids[txids[k]] = 1;
|
||||
}
|
||||
_asyncForEach(Object.keys(uniqueTxids), function(txid, callback2) {
|
||||
var options = self._getOptions('GET', '/api/tx/' + txid);
|
||||
self._request(options, function(err, res) {
|
||||
txs.push(res);
|
||||
|
@ -164,8 +160,8 @@ Insight.prototype.checkActivity = function(addresses, cb) {
|
|||
var getOutputs = function(t) {
|
||||
return flatArray(
|
||||
t.vout.map(function(vout) {
|
||||
return vout.scriptPubKey.addresses;
|
||||
})
|
||||
return vout.scriptPubKey.addresses;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -33,12 +33,12 @@ HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) {
|
|||
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||
};
|
||||
|
||||
HDPath.indicesForPath = function(path) {
|
||||
HDPath.indexesForPath = function(path) {
|
||||
preconditions.shouldBeString(path);
|
||||
var s = path.split('/');
|
||||
return {
|
||||
isChange: s[3] === '1',
|
||||
index: parseInt(s[4]),
|
||||
addressIndex: parseInt(s[4]),
|
||||
copayerIndex: parseInt(s[2])
|
||||
};
|
||||
};
|
||||
|
|
|
@ -23,14 +23,13 @@ function PublicKeyRing(opts) {
|
|||
|
||||
this.copayersHK = opts.copayersHK || [];
|
||||
|
||||
this.indexes = opts.indexes ? HDParams.fromList(opts.indexes)
|
||||
: HDParams.init(this.totalCopayers);
|
||||
this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) : HDParams.init(this.totalCopayers);
|
||||
|
||||
this.publicKeysCache = opts.publicKeysCache || {};
|
||||
this.nicknameFor = opts.nicknameFor || {};
|
||||
this.copayerIds = [];
|
||||
this.copayersBackup = opts.copayersBackup || [];
|
||||
this.addressToPath = {};
|
||||
this.publicKeysCache = opts.publicKeysCache || {};
|
||||
this.nicknameFor = opts.nicknameFor || {};
|
||||
this.copayerIds = [];
|
||||
this.copayersBackup = opts.copayersBackup || [];
|
||||
this.addressToPath = {};
|
||||
}
|
||||
|
||||
PublicKeyRing.fromObj = function(data) {
|
||||
|
@ -100,14 +99,6 @@ PublicKeyRing.prototype._checkKeys = function() {
|
|||
throw new Error('dont have required keys yet');
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype._newExtendedPublicKey = function() {
|
||||
return new PrivateKey({
|
||||
networkName: this.network.name
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype._updateBip = function(index) {
|
||||
var hk = this.copayersHK[index].derive(HDPath.IdBranch);
|
||||
this.copayerIds[index] = hk.eckey.public.toString('hex');
|
||||
|
@ -126,6 +117,8 @@ PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) {
|
|||
};
|
||||
|
||||
PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
||||
preconditions.checkArgument(newEpk);
|
||||
|
||||
if (this.isComplete())
|
||||
throw new Error('PKR already has all required key:' + this.totalCopayers);
|
||||
|
||||
|
@ -134,10 +127,6 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
|||
throw new Error('PKR already has that key');
|
||||
});
|
||||
|
||||
if (!newEpk) {
|
||||
newEpk = this._newExtendedPublicKey();
|
||||
}
|
||||
|
||||
var i = this.copayersHK.length;
|
||||
var bip = new HK(newEpk);
|
||||
this.copayersHK.push(bip);
|
||||
|
@ -192,7 +181,9 @@ PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
|
|||
// Overloaded to receive a PubkeyString or a consigner index
|
||||
PublicKeyRing.prototype.getHDParams = function(id) {
|
||||
var copayerIndex = this.getCosigner(id);
|
||||
var index = this.indexes.filter(function(i) { return i.copayerIndex == copayerIndex });
|
||||
var index = this.indexes.filter(function(i) {
|
||||
return i.copayerIndex == copayerIndex
|
||||
});
|
||||
if (index.length != 1) throw new Error('no index for copayerIndex');
|
||||
|
||||
return index[0];
|
||||
|
@ -231,9 +222,11 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) {
|
|||
if (typeof pubKey == 'undefined') return HDPath.SHARED_INDEX;
|
||||
if (typeof pubKey == 'number') return pubKey;
|
||||
|
||||
var sorted = this.copayersHK.map(function(h, i){
|
||||
var sorted = this.copayersHK.map(function(h, i) {
|
||||
return h.eckey.public.toString('hex');
|
||||
}).sort(function(h1, h2){ return h1.localeCompare(h2); });
|
||||
}).sort(function(h1, h2) {
|
||||
return h1.localeCompare(h2);
|
||||
});
|
||||
|
||||
var index = sorted.indexOf(pubKey);
|
||||
if (index == -1) throw new Error('no public key in ring');
|
||||
|
@ -255,41 +248,87 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
|
|||
PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) {
|
||||
opts = opts || {};
|
||||
|
||||
var isOwned = index.copayerIndex == HDPath.SHARED_INDEX
|
||||
|| index.copayerIndex == copayerIndex;
|
||||
var isOwned = index.copayerIndex == HDPath.SHARED_INDEX || index.copayerIndex == copayerIndex;
|
||||
|
||||
var ret = [];
|
||||
if (!opts.excludeChange) {
|
||||
for (var i = 0; i < index.changeIndex; i++) {
|
||||
var a = this.getAddress(i, true, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: true,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
var ret = [];
|
||||
if (!opts.excludeChange) {
|
||||
for (var i = 0; i < index.changeIndex; i++) {
|
||||
var a = this.getAddress(i, true, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: true,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.excludeMain) {
|
||||
for (var i = 0; i < index.receiveIndex; i++) {
|
||||
var a = this.getAddress(i, false, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: false,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
if (!opts.excludeMain) {
|
||||
for (var i = 0; i < index.receiveIndex; i++) {
|
||||
var a = this.getAddress(i, false, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: false,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.getForPath = function(path) {
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var pubKeys = this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
return pubKeys;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.getForPaths = function(paths) {
|
||||
preconditions.checkArgument(paths);
|
||||
return paths.map(this.getForPath.bind(this));
|
||||
};
|
||||
|
||||
|
||||
PublicKeyRing.prototype.forPaths = function(paths) {
|
||||
return {
|
||||
pubKeys: paths.map(this.getForPath.bind(this)),
|
||||
copayerIds: this.copayerIds,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// returns pubkey -> copayerId.
|
||||
PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
||||
preconditions.checkArgument(pubkeys);
|
||||
preconditions.checkArgument(paths);
|
||||
|
||||
var inKeyMap = {}, ret = {};
|
||||
for(var i in pubkeys ){
|
||||
inKeyMap[pubkeys[i]] = 1;
|
||||
};
|
||||
|
||||
var keys = this.getForPaths(paths);
|
||||
for(var i in keys ){
|
||||
for(var copayerIndex in keys[i] ){
|
||||
var kHex = keys[i][copayerIndex].toString('hex');
|
||||
if (inKeyMap[kHex]) {
|
||||
ret[kHex] =this.copayerIds[copayerIndex];
|
||||
delete inKeyMap[kHex];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var i in inKeyMap)
|
||||
throw new Error('Pubkey not identified')
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// TODO this could be cached
|
||||
PublicKeyRing.prototype._addScriptMap = function(map, path) {
|
||||
var p = HDPath.indicesForPath(path);
|
||||
var script = this.getRedeemScript(p.index, p.isChange, p.copayerIndex);
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex');
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var preconditions = require('preconditions').instance();
|
||||
|
||||
var VERSION = 1;
|
||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment'];
|
||||
|
||||
|
||||
function TxProposal(opts) {
|
||||
preconditions.checkArgument(opts);
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
preconditions.checkArgument(opts.builder, 'no builder');
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
|
||||
this.inputChainPaths = opts.inputChainPaths;
|
||||
this.version = opts.version;
|
||||
this.builder = opts.builder;
|
||||
this.createdTs = opts.createdTs;
|
||||
this.createdTs = opts.createdTs;
|
||||
this._inputSignatures = [];
|
||||
|
||||
// CopayerIds
|
||||
this.creator = opts.creator;
|
||||
this.signedBy = opts.signedBy || {};
|
||||
this.seenBy = opts.seenBy || {};
|
||||
this.rejectedBy = opts.rejectedBy || {};
|
||||
this.sentTs = opts.sentTs || null;
|
||||
this.sentTxid = opts.sentTxid || null;
|
||||
this.comment = opts.comment || null;
|
||||
this.readonly = opts.readonly || null;
|
||||
this._sync();
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype._check = function() {
|
||||
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
throw new Error('Invalid tx proposal');
|
||||
}
|
||||
|
||||
var tx = this.builder.build();
|
||||
if (!tx.ins.length)
|
||||
throw new Error('Invalid tx proposal: no ins');
|
||||
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = tx.ins[i].s;
|
||||
if (!scriptSig || !scriptSig.length) {
|
||||
throw new Error('Invalid tx proposal: no signatures');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
||||
throw new Error('Invalid tx proposal: bad signatures');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype._updateSignedBy = function() {
|
||||
this._inputSignatures = [];
|
||||
|
||||
var tx = this.builder.build();
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = new Script(tx.ins[i].s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
||||
var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
if (signatureIndexes.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
this._inputSignatures[i] = signatureIndexes.map(function(i) {
|
||||
var r = info.keys[i].toString('hex');
|
||||
return r;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype._sync = function() {
|
||||
this._check();
|
||||
this._updateSignedBy();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype.getId = function() {
|
||||
preconditions.checkState(this.builder);
|
||||
return this.builder.build().getNormalizedHash().toString('hex');
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObj = function() {
|
||||
var o = JSON.parse(JSON.stringify(this));
|
||||
delete o['builder'];
|
||||
o.builderObj = this.builder.toObj();
|
||||
return o;
|
||||
};
|
||||
|
||||
|
||||
TxProposal._trim = function(o) {
|
||||
var ret = {};
|
||||
CORE_FIELDS.forEach(function(k) {
|
||||
ret[k] = o[k];
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
preconditions.checkArgument(o.builderObj);
|
||||
delete o['builder'];
|
||||
|
||||
try {
|
||||
// force opts is requested.
|
||||
for (var k in forceOpts) {
|
||||
o.builderObj.opts[k] = forceOpts[k];
|
||||
}
|
||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
} catch (e) {
|
||||
|
||||
// backwards (V0) compatatibility fix.
|
||||
if (!o.version) {
|
||||
o.builder = new BuilderMockV0(o.builderObj);
|
||||
o.readonly = 1;
|
||||
};
|
||||
}
|
||||
return new TxProposal(o);
|
||||
};
|
||||
|
||||
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
||||
return TxProposal.fromObj(TxProposal._trim(o), forceOpts);
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObjTrim = function() {
|
||||
return TxProposal._trim(this.toObj());
|
||||
};
|
||||
|
||||
TxProposal._formatKeys = function(keys) {
|
||||
var ret = [];
|
||||
for (var i in keys) {
|
||||
if (!Buffer.isBuffer(keys[i]))
|
||||
throw new Error('keys must be buffers');
|
||||
|
||||
var k = new Key();
|
||||
k.public = keys[i];
|
||||
ret.push(k);
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) {
|
||||
preconditions.checkArgument(Buffer.isBuffer(txSigHash));
|
||||
preconditions.checkArgument(inKeys);
|
||||
preconditions.checkState(Buffer.isBuffer(inKeys[0]));
|
||||
|
||||
if (scriptSig.chunks[0] !== 0)
|
||||
throw new Error('Invalid scriptSig');
|
||||
|
||||
var keys = TxProposal._formatKeys(inKeys);
|
||||
var ret = [];
|
||||
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
|
||||
var chunk = scriptSig.chunks[i];
|
||||
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
|
||||
for (var j in keys) {
|
||||
var k = keys[j];
|
||||
if (k.verifySignatureSync(txSigHash, sigRaw)) {
|
||||
ret.push(parseInt(j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal._infoFromRedeemScript = function(s) {
|
||||
var redeemScript = new Script(s.chunks[s.chunks.length - 1]);
|
||||
if (!redeemScript)
|
||||
throw new Error('Bad scriptSig (no redeemscript)');
|
||||
|
||||
var pubkeys = redeemScript.capture();
|
||||
if (!pubkeys || !pubkeys.length)
|
||||
throw new Error('Bad scriptSig (no pubkeys)');
|
||||
|
||||
return {
|
||||
keys: pubkeys,
|
||||
script: redeemScript,
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeBuilder = function(incoming) {
|
||||
var b0 = this.builder;
|
||||
var b1 = incoming.builder;
|
||||
|
||||
var before = JSON.stringify(b0.toObj());
|
||||
b0.merge(b1);
|
||||
var after = JSON.stringify(b0.toObj());
|
||||
return after !== before;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setSeen = function(copayerId) {
|
||||
if (!this.seenBy[copayerId])
|
||||
this.seenBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.prototype.setRejected = function(copayerId) {
|
||||
|
||||
if (this.signedBy[copayerId])
|
||||
throw new Error('Can not reject a signed TX');
|
||||
|
||||
if (!this.rejectedBy[copayerId])
|
||||
this.rejectedBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.prototype.setSent = function(sentTxid) {
|
||||
this.sentTxid = sentTxid;
|
||||
this.sentTs = Date.now();
|
||||
};
|
||||
|
||||
|
||||
|
||||
TxProposal.prototype._allSignatures = function() {
|
||||
var ret = {};
|
||||
for (var i in this._inputSignatures)
|
||||
for (var j in this._inputSignatures[i])
|
||||
ret[this._inputSignatures[i][j]] = true;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||
var newCopayer = {},
|
||||
oldCopayers = {},
|
||||
newSignedBy = {},
|
||||
readOnlyPeers = {},
|
||||
isNew = 1;
|
||||
|
||||
for (var k in this.signedBy) {
|
||||
oldCopayers[k] = 1;
|
||||
isNew = 0;
|
||||
};
|
||||
|
||||
if (isNew == 0) {
|
||||
if (!this.creator || !this.createdTs)
|
||||
throw new Error('Existing TX has no creator');
|
||||
|
||||
if (!this.signedBy[this.creator])
|
||||
throw new Error('Existing TX is not signed by creator');
|
||||
|
||||
|
||||
if (Object.keys(this.signedBy).length === 0)
|
||||
throw new Error('Existing TX has no signatures');
|
||||
}
|
||||
|
||||
|
||||
var iSig = this._inputSignatures[0];
|
||||
for (var i in iSig) {
|
||||
var copayerId = keyMap[iSig[i]];
|
||||
if (!copayerId)
|
||||
throw new Error('Found unknown signature')
|
||||
|
||||
if (oldCopayers[copayerId]) {
|
||||
//Already have it. Do nothing
|
||||
} else {
|
||||
newCopayer[copayerId] = Date.now();
|
||||
delete oldCopayers[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Seems unncessary to check this:
|
||||
// if (!newCopayer[senderId] && !readOnlyPeers[senderId])
|
||||
// throw new Error('TX must have a (new) senders signature')
|
||||
|
||||
if (Object.keys(newCopayer).length > 1)
|
||||
throw new Error('New TX must have only 1 new signature');
|
||||
|
||||
// Handler creator / createdTs.
|
||||
// from senderId, and must be signed by senderId
|
||||
if (isNew) {
|
||||
this.creator = Object.keys(newCopayer)[0];
|
||||
this.seenBy[this.creator] = this.createdTs = Date.now();
|
||||
}
|
||||
|
||||
//Ended. Update this.
|
||||
for (var i in newCopayer) {
|
||||
this.signedBy[i] = newCopayer[i];
|
||||
}
|
||||
|
||||
// signedBy has preference over rejectedBy
|
||||
for (var i in this.signedBy) {
|
||||
delete this.rejectedBy[i];
|
||||
}
|
||||
|
||||
return Object.keys(newCopayer);
|
||||
};
|
||||
|
||||
// merge will not merge any metadata.
|
||||
TxProposal.prototype.merge = function(incoming) {
|
||||
var hasChanged = this.mergeBuilder(incoming);
|
||||
this._sync();
|
||||
return hasChanged;
|
||||
};
|
||||
|
||||
//This should be on bitcore / Transaction
|
||||
TxProposal.prototype.countSignatures = function() {
|
||||
var tx = this.builder.build();
|
||||
var ret = 0;
|
||||
for (var i in tx.ins) {
|
||||
ret += tx.countInputSignatures(i);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = TxProposal;
|
|
@ -1,174 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var TxProposal = require('./TxProposal');;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var preconditions = require('preconditions').instance();
|
||||
|
||||
function TxProposal(opts) {
|
||||
this.creator = opts.creator;
|
||||
this.createdTs = opts.createdTs;
|
||||
this.seenBy = opts.seenBy || {};
|
||||
this.signedBy = opts.signedBy || {};
|
||||
this.rejectedBy = opts.rejectedBy || {};
|
||||
this.builder = opts.builder;
|
||||
this.sentTs = opts.sentTs || null;
|
||||
this.sentTxid = opts.sentTxid || null;
|
||||
this.inputChainPaths = opts.inputChainPaths || [];
|
||||
this.comment = opts.comment || null;
|
||||
}
|
||||
|
||||
TxProposal.prototype.getID = function() {
|
||||
return this.builder.build().getNormalizedHash().toString('hex');
|
||||
};
|
||||
|
||||
TxProposal.prototype.toObj = function() {
|
||||
var o = JSON.parse(JSON.stringify(this));
|
||||
delete o['builder'];
|
||||
o.builderObj = this.builder.toObj();
|
||||
return o;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setSent = function(sentTxid) {
|
||||
this.sentTxid = sentTxid;
|
||||
this.sentTs = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
var t = new TxProposal(o);
|
||||
|
||||
try {
|
||||
// force opts is requested.
|
||||
for (var k in forceOpts) {
|
||||
o.builderObj.opts[k] = forceOpts[k];
|
||||
}
|
||||
t.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
|
||||
} catch (e) {
|
||||
if (!o.version) {
|
||||
t.builder = new BuilderMockV0(o.builderObj);
|
||||
t.readonly = 1;
|
||||
};
|
||||
}
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.isValid = function() {
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
return false;
|
||||
}
|
||||
var tx = this.builder.build();
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
TxProposal.getSentTs = function() {
|
||||
return this.sentTs;
|
||||
};
|
||||
|
||||
TxProposal.prototype.merge = function(other, author) {
|
||||
var ret = {};
|
||||
ret.events = this.mergeMetadata(other, author);
|
||||
ret.hasChanged = this.mergeBuilder(other);
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeBuilder = function(other) {
|
||||
var b0 = this.builder;
|
||||
var b1 = other.builder;
|
||||
|
||||
// TODO: improve this comparison
|
||||
var before = JSON.stringify(b0.toObj());
|
||||
b0.merge(b1);
|
||||
var after = JSON.stringify(b0.toObj());
|
||||
return after !== before;
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeMetadata = function(v1, author) {
|
||||
var events = [];
|
||||
var v0 = this;
|
||||
|
||||
var ntxid = this.getID();
|
||||
|
||||
Object.keys(v1.seenBy).forEach(function(k) {
|
||||
if (!v0.seenBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative seenBy change by ' + author);
|
||||
v0.seenBy[k] = v1.seenBy[k];
|
||||
events.push({
|
||||
type: 'seen',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(v1.signedBy).forEach(function(k) {
|
||||
if (!v0.signedBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative signedBy change by ' + author);
|
||||
v0.signedBy[k] = v1.signedBy[k];
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(v1.rejectedBy).forEach(function(k) {
|
||||
if (!v0.rejectedBy[k]) {
|
||||
// TODO: uncomment below and change protocol to make this work
|
||||
//if (k != author) throw new Error('Non authoritative rejectedBy change by ' + author);
|
||||
v0.rejectedBy[k] = v1.rejectedBy[k];
|
||||
events.push({
|
||||
type: 'rejected',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!v0.sentTxid && v1.sentTxid) {
|
||||
v0.sentTs = v1.sentTs;
|
||||
v0.sentTxid = v1.sentTxid;
|
||||
events.push({
|
||||
type: 'broadcast',
|
||||
txId: ntxid
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
|
||||
};
|
||||
|
||||
//This should be on bitcore / Transaction
|
||||
TxProposal.prototype.countSignatures = function() {
|
||||
var tx = this.builder.build();
|
||||
|
||||
var ret = 0;
|
||||
for (var i in tx.ins) {
|
||||
ret += tx.countInputSignatures(i);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(TxProposal);
|
||||
|
||||
|
||||
function TxProposals(opts) {
|
||||
opts = opts || {};
|
||||
|
@ -178,6 +20,7 @@ function TxProposals(opts) {
|
|||
this.txps = {};
|
||||
}
|
||||
|
||||
// fromObj => from a trusted source
|
||||
TxProposals.fromObj = function(o, forceOpts) {
|
||||
var ret = new TxProposals({
|
||||
networkName: o.networkName,
|
||||
|
@ -187,7 +30,7 @@ TxProposals.fromObj = function(o, forceOpts) {
|
|||
o.txps.forEach(function(o2) {
|
||||
var t = TxProposal.fromObj(o2, forceOpts);
|
||||
if (t.builder) {
|
||||
var id = t.getID();
|
||||
var id = t.getId();
|
||||
ret.txps[id] = t;
|
||||
}
|
||||
});
|
||||
|
@ -198,14 +41,9 @@ TxProposals.prototype.getNtxids = function() {
|
|||
return Object.keys(this.txps);
|
||||
};
|
||||
|
||||
TxProposals.prototype.toObj = function(onlyThisNtxid) {
|
||||
if (onlyThisNtxid) throw new Error();
|
||||
TxProposals.prototype.toObj = function() {
|
||||
var ret = [];
|
||||
for (var id in this.txps) {
|
||||
|
||||
if (onlyThisNtxid && id != onlyThisNtxid)
|
||||
continue;
|
||||
|
||||
var t = this.txps[id];
|
||||
if (!t.sent)
|
||||
ret.push(t.toObj());
|
||||
|
@ -217,50 +55,53 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) {
|
|||
};
|
||||
};
|
||||
|
||||
TxProposals.prototype.merge = function(inTxp, author) {
|
||||
var myTxps = this.txps;
|
||||
|
||||
var ntxid = inTxp.getID();
|
||||
var ret = {};
|
||||
ret.events = [];
|
||||
ret.events.hasChanged = false;
|
||||
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
||||
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
||||
incomingTx._sync();
|
||||
|
||||
var myTxps = this.txps;
|
||||
var ntxid = incomingTx.getId();
|
||||
var ret = {
|
||||
ntxid: ntxid
|
||||
};
|
||||
|
||||
if (myTxps[ntxid]) {
|
||||
var v0 = myTxps[ntxid];
|
||||
var v1 = inTxp;
|
||||
ret = v0.merge(v1, author);
|
||||
|
||||
// Merge an existing txProposal
|
||||
ret.hasChanged = myTxps[ntxid].merge(incomingTx);
|
||||
|
||||
|
||||
} else {
|
||||
this.txps[ntxid] = inTxp;
|
||||
ret.hasChanged = true;
|
||||
ret.events.push({
|
||||
type: 'new',
|
||||
cid: inTxp.creator,
|
||||
tx: ntxid
|
||||
});
|
||||
// Create a new one
|
||||
ret.new = ret.hasChanged = 1;
|
||||
this.txps[ntxid] = incomingTx;
|
||||
}
|
||||
|
||||
ret.txp = this.txps[ntxid];
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.prototype.add = function(data) {
|
||||
preconditions.checkArgument(data.inputChainPaths);
|
||||
preconditions.checkArgument(data.signedBy);
|
||||
preconditions.checkArgument(data.creator);
|
||||
preconditions.checkArgument(data.createdTs);
|
||||
preconditions.checkArgument(data.builder);
|
||||
var txp = new TxProposal(data);
|
||||
var ntxid = txp.getID();
|
||||
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||
TxProposals.prototype.add = function(txp) {
|
||||
txp._sync();
|
||||
var ntxid = txp.getId();
|
||||
this.txps[ntxid] = txp;
|
||||
return ntxid;
|
||||
};
|
||||
|
||||
TxProposals.prototype.setSent = function(ntxid, txid) {
|
||||
//sent TxProposals are local an not broadcasted.
|
||||
this.txps[ntxid].setSent(txid);
|
||||
|
||||
TxProposals.prototype.get = function(ntxid) {
|
||||
var ret = this.txps[ntxid];
|
||||
if (!ret)
|
||||
throw new Error('Unknown TXP: '+ntxid);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
||||
var txp = this.txps[ntxid];
|
||||
var txp = this.get(ntxid);
|
||||
|
||||
var i = JSON.parse(JSON.stringify(txp));
|
||||
i.builder = txp.builder;
|
||||
i.ntxid = ntxid;
|
||||
|
@ -296,6 +137,17 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
|||
return i;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.reject = function(ntxid, copayerId) {
|
||||
var txp = this.get(ntxid);
|
||||
txp.setRejected(copayerId);
|
||||
};
|
||||
|
||||
TxProposals.prototype.seen = function(ntxid, copayerId) {
|
||||
var txp = this.get(ntxid);
|
||||
txp.setSeen(copayerId);
|
||||
};
|
||||
|
||||
//returns the unspent txid-vout used in PENDING Txs
|
||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||
var ret = {};
|
||||
|
@ -312,5 +164,4 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
|||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.TxProposal = TxProposal;
|
||||
module.exports = require('soop')(TxProposals);
|
||||
module.exports = TxProposals;
|
||||
|
|
|
@ -17,6 +17,7 @@ var Address = bitcore.Address;
|
|||
|
||||
var HDParams = require('./HDParams');
|
||||
var PublicKeyRing = require('./PublicKeyRing');
|
||||
var TxProposal = require('./TxProposal');
|
||||
var TxProposals = require('./TxProposals');
|
||||
var PrivateKey = require('./PrivateKey');
|
||||
var copayConfig = require('../../../config');
|
||||
|
@ -36,7 +37,7 @@ function Wallet(opts) {
|
|||
});
|
||||
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
||||
throw new Error('Network forced to ' + copayConfig.networkName +
|
||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||
|
||||
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
||||
|
||||
|
@ -58,11 +59,11 @@ function Wallet(opts) {
|
|||
}
|
||||
|
||||
|
||||
Wallet.builderOpts = {
|
||||
lockTime: null,
|
||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||
fee: null,
|
||||
feeSat: null,
|
||||
Wallet.builderOpts = {
|
||||
lockTime: null,
|
||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||
fee: null,
|
||||
feeSat: null,
|
||||
};
|
||||
|
||||
Wallet.parent = EventEmitter;
|
||||
|
@ -129,39 +130,158 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
|||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||
this.log('RECV TXPROPOSAL: ', data);
|
||||
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts);
|
||||
|
||||
|
||||
|
||||
var valid = inTxp.isValid();
|
||||
if (!valid) {
|
||||
var corruptEvent = {
|
||||
Wallet.prototype._processProposalEvents = function(senderId, m) {
|
||||
var ev;
|
||||
if (m) {
|
||||
if (m.new) {
|
||||
ev = {
|
||||
type: 'new',
|
||||
cid: senderId
|
||||
}
|
||||
} else if (m.newCopayer) {
|
||||
ev = {
|
||||
type: 'signed',
|
||||
cid: m.newCopayer
|
||||
};
|
||||
}
|
||||
} else {
|
||||
ev = {
|
||||
type: 'corrupt',
|
||||
cId: inTxp.creator
|
||||
cId: senderId,
|
||||
};
|
||||
this.emit('txProposalEvent', corruptEvent);
|
||||
return;
|
||||
}
|
||||
var mergeInfo = this.txProposals.merge(inTxp, senderId);
|
||||
var added = this.addSeenToTxProposals();
|
||||
|
||||
if (added) {
|
||||
this.log('### BROADCASTING txProposals with my seenBy updated.');
|
||||
this.sendTxProposal(inTxp.getID());
|
||||
}
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
|
||||
for (var i = 0; i < mergeInfo.events.length; i++) {
|
||||
this.emit('txProposalEvent', mergeInfo.events[i]);
|
||||
}
|
||||
if (ev)
|
||||
this.emit('txProposalEvent', ev);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* OTDO
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
*/
|
||||
Wallet.prototype._getKeyMap = function(txp) {
|
||||
preconditions.checkArgument(txp);
|
||||
|
||||
var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.inputChainPaths);
|
||||
|
||||
var inSig = JSON.stringify(txp._inputSignatures[0].sort());
|
||||
|
||||
if (JSON.stringify(Object.keys(keyMap).sort()) !== inSig) {
|
||||
throw new Error('inputSignatures dont match know copayers pubkeys');
|
||||
}
|
||||
|
||||
var keyMapStr = JSON.stringify(keyMap);
|
||||
// All inputs must be signed with the same copayers
|
||||
for (var i in txp._inputSignatures) {
|
||||
if (!i) continue;
|
||||
var inSigX = JSON.stringify(txp._inputSignatures[i].sort());
|
||||
if (inSigX !== inSig)
|
||||
throw new Error('found inputs with different signatures:');
|
||||
}
|
||||
return keyMap;
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._checkSentTx = function(ntxid, cb) {
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
var tx = txp.builder.build();
|
||||
|
||||
this.blockchain.checkSentTx(tx, function(err, txid) {
|
||||
var ret = false;
|
||||
if (txid) {
|
||||
txp.setSent(txid);
|
||||
ret = txid;
|
||||
}
|
||||
return cb(ret);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||
var self = this;
|
||||
this.log('RECV TXPROPOSAL: ', data);
|
||||
var m;
|
||||
|
||||
try {
|
||||
m = this.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
||||
var keyMap = this._getKeyMap(m.txp);
|
||||
ret.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
||||
|
||||
} catch (e) {
|
||||
this.log('Corrupt TX proposal received from:', senderId, e);
|
||||
}
|
||||
|
||||
if (m) {
|
||||
|
||||
if (m.hasChanged) {
|
||||
this.sendSeen(m.ntxid);
|
||||
var tx = m.txp.builder.build();
|
||||
if (tx.isComplete()) {
|
||||
this._checkSentTx(m.ntxid, function(ret) {
|
||||
if (ret) {
|
||||
self.emit('txProposalsUpdated');
|
||||
self.store();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.sendTxProposal(m.ntxid);
|
||||
}
|
||||
}
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
}
|
||||
this._processProposalEvents(senderId, m);
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._handleReject = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.ntxid);
|
||||
this.log('RECV REJECT:', data);
|
||||
|
||||
var txp = this.txProposals.get(data.ntxid);
|
||||
|
||||
if (!txp)
|
||||
throw new Error('Received Reject for an unknown TX from:' + senderId);
|
||||
|
||||
if (txp.signedBy[senderId])
|
||||
throw new Error('Received Reject for an already signed TX from:' + senderId);
|
||||
|
||||
txp.setRejected(senderId);
|
||||
this.store();
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'rejected',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype._handleSeen = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.ntxid);
|
||||
this.log('RECV SEEN:', data);
|
||||
|
||||
var txp = this.txProposals.get(data.ntxid);
|
||||
txp.setSeen(senderId);
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'seen',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) {
|
||||
preconditions.checkState(data.addressBook);
|
||||
this.log('RECV ADDRESSBOOK:', data);
|
||||
var rcv = data.addressBook;
|
||||
var hasChange;
|
||||
|
@ -193,24 +313,30 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
|||
// This handler is repeaded on WalletFactory (#join). TODO
|
||||
case 'walletId':
|
||||
this.sendWalletReady(senderId);
|
||||
break;
|
||||
break;
|
||||
case 'walletReady':
|
||||
this.sendPublicKeyRing(senderId);
|
||||
this.sendAddressBook(senderId);
|
||||
this.sendAllTxProposals(senderId); // send old txps
|
||||
break;
|
||||
this.sendAddressBook(senderId);
|
||||
this.sendAllTxProposals(senderId); // send old txps
|
||||
break;
|
||||
case 'publicKeyRing':
|
||||
this._handlePublicKeyRing(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'reject':
|
||||
this._handleReject(senderId, data, isInbound);
|
||||
break;
|
||||
case 'seen':
|
||||
this._handleSeen(senderId, data, isInbound);
|
||||
break;
|
||||
case 'txProposal':
|
||||
this._handleTxProposal(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'indexes':
|
||||
this._handleIndexes(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
case 'addressbook':
|
||||
this._handleAddressBook(senderId, data, isInbound);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -384,6 +510,7 @@ Wallet.prototype.toObj = function() {
|
|||
return walletObj;
|
||||
};
|
||||
|
||||
// fromObj => from a trusted source
|
||||
Wallet.fromObj = function(o, storage, network, blockchain) {
|
||||
var opts = JSON.parse(JSON.stringify(o.opts));
|
||||
opts.addressBook = o.addressBook;
|
||||
|
@ -418,11 +545,31 @@ Wallet.prototype.sendAllTxProposals = function(recipients) {
|
|||
|
||||
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
preconditions.checkState(this.txProposals.txps[ntxid]);
|
||||
|
||||
this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
|
||||
this.send(recipients, {
|
||||
type: 'txProposal',
|
||||
txProposal: this.txProposals.txps[ntxid].toObj(),
|
||||
txProposal: this.txProposals.get(ntxid).toObjTrim(),
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.sendSeen = function(ntxid) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
this.log('### SENDING seen: ' + ntxid + ' TO: All');
|
||||
this.send(null, {
|
||||
type: 'seen',
|
||||
ntxid: ntxid,
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.sendReject = function(ntxid) {
|
||||
preconditions.checkArgument(ntxid);
|
||||
this.log('### SENDING reject: ' + ntxid + ' TO: All');
|
||||
this.send(null, {
|
||||
type: 'reject',
|
||||
ntxid: ntxid,
|
||||
walletId: this.id,
|
||||
});
|
||||
};
|
||||
|
@ -517,30 +664,22 @@ Wallet.prototype.getTxProposals = function() {
|
|||
|
||||
|
||||
Wallet.prototype.reject = function(ntxid) {
|
||||
var myId = this.getMyCopayerId();
|
||||
var txp = this.txProposals.txps[ntxid];
|
||||
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
throw new Error('Invalid transaction to reject: ' + ntxid);
|
||||
}
|
||||
|
||||
txp.rejectedBy[myId] = Date.now();
|
||||
this.sendTxProposal(ntxid);
|
||||
var txp = this.txProposals.reject(ntxid, this.getMyCopayerId());
|
||||
this.sendReject(ntxid);
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype.sign = function(ntxid, cb) {
|
||||
preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined');
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
var myId = self.getMyCopayerId();
|
||||
var txp = self.txProposals.txps[ntxid];
|
||||
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
if (cb) cb(false);
|
||||
}
|
||||
|
||||
var txp = self.txProposals.get(ntxid);
|
||||
// if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
// if (cb) cb(false);
|
||||
// }
|
||||
//
|
||||
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
|
||||
|
||||
var b = txp.builder;
|
||||
|
@ -559,14 +698,13 @@ Wallet.prototype.sign = function(ntxid, cb) {
|
|||
}, 10);
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype.sendTx = function(ntxid, cb) {
|
||||
var txp = this.txProposals.txps[ntxid];
|
||||
if (!txp) return;
|
||||
|
||||
var txp = this.txProposals.get(ntxid);
|
||||
var tx = txp.builder.build();
|
||||
if (!tx.isComplete()) return;
|
||||
if (!tx.isComplete())
|
||||
throw new Error('Tx is not complete. Can not broadcast');
|
||||
this.log('Broadcasting Transaction');
|
||||
|
||||
var scriptSig = tx.ins[0].getScript();
|
||||
var size = scriptSig.serialize().length;
|
||||
|
||||
|
@ -577,28 +715,23 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
|
|||
this.blockchain.sendRawTransaction(txHex, function(txid) {
|
||||
self.log('BITCOIND txid:', txid);
|
||||
if (txid) {
|
||||
self.txProposals.setSent(ntxid, txid);
|
||||
self.txProposals.get(ntxid).setSent(txid);
|
||||
self.sendTxProposal(ntxid);
|
||||
self.store();
|
||||
return cb(txid);
|
||||
} else {
|
||||
self.log('Sent failed. Checking is the TX was sent already');
|
||||
self._checkSentTx(ntxid, function(txid) {
|
||||
console.log('[Wallet.js.730:txid:]', txid); //TODO
|
||||
if (txid)
|
||||
self.store();
|
||||
|
||||
return cb(txid);
|
||||
});
|
||||
}
|
||||
return cb(txid);
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.addSeenToTxProposals = function() {
|
||||
var ret = false;
|
||||
var myId = this.getMyCopayerId();
|
||||
|
||||
for (var k in this.txProposals.txps) {
|
||||
var txp = this.txProposals.txps[k];
|
||||
if (!txp.seenBy[myId]) {
|
||||
|
||||
txp.seenBy[myId] = Date.now();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// TODO: remove this method and use getAddressesInfo everywhere
|
||||
Wallet.prototype.getAddresses = function(opts) {
|
||||
|
@ -719,8 +852,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
var priv = this.privateKey;
|
||||
opts = opts || {};
|
||||
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
|
||||
preconditions.checkState(pkr.isComplete());
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch');
|
||||
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
|
||||
preconditions.checkState(priv, 'no private key');
|
||||
if (comment) preconditions.checkArgument(comment.length <= 100);
|
||||
|
||||
if (!opts.remainderOut) {
|
||||
|
@ -729,16 +863,16 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
};
|
||||
}
|
||||
|
||||
for (var k in Wallet.builderOpts){
|
||||
for (var k in Wallet.builderOpts) {
|
||||
opts[k] = Wallet.builderOpts[k];
|
||||
}
|
||||
|
||||
var b = new Builder(opts)
|
||||
.setUnspent(utxos)
|
||||
.setOutputs([{
|
||||
address: toAddress,
|
||||
amountSatStr: amountSatStr,
|
||||
}]);
|
||||
.setUnspent(utxos)
|
||||
.setOutputs([{
|
||||
address: toAddress,
|
||||
amountSatStr: amountSatStr,
|
||||
}]);
|
||||
|
||||
var selectedUtxos = b.getSelectedUnspent();
|
||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||
|
@ -747,22 +881,23 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
|
||||
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
||||
|
||||
if (priv) {
|
||||
var keys = priv.getForPaths(inputChainPaths);
|
||||
var signed = b.sign(keys);
|
||||
}
|
||||
var keys = priv.getForPaths(inputChainPaths);
|
||||
var signed = b.sign(keys);
|
||||
var myId = this.getMyCopayerId();
|
||||
var now = Date.now();
|
||||
|
||||
var me = {};
|
||||
|
||||
var tx = b.build();
|
||||
if (priv && tx.countInputSignatures(0)) me[myId] = now;
|
||||
if (!tx.countInputSignatures(0))
|
||||
throw new Error('Could not sign generated tx');
|
||||
|
||||
var me = {};
|
||||
me[myId] = now;
|
||||
|
||||
var meSeen = {};
|
||||
if (priv) meSeen[myId] = now;
|
||||
|
||||
var data = {
|
||||
var ntxid = this.txProposals.add(new TxProposal({
|
||||
inputChainPaths: inputChainPaths,
|
||||
signedBy: me,
|
||||
seenBy: meSeen,
|
||||
|
@ -770,9 +905,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
createdTs: now,
|
||||
builder: b,
|
||||
comment: comment
|
||||
};
|
||||
|
||||
var ntxid = this.txProposals.add(data);
|
||||
}));
|
||||
return ntxid;
|
||||
};
|
||||
|
||||
|
@ -831,29 +964,29 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
|
|||
var self = this;
|
||||
async.doWhilst(
|
||||
function _do(next) {
|
||||
// Optimize window to minimize the derivations.
|
||||
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
||||
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
||||
self.blockchain.checkActivity(addresses, function(err, actives) {
|
||||
if (err) throw err;
|
||||
// Optimize window to minimize the derivations.
|
||||
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
||||
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
||||
self.blockchain.checkActivity(addresses, function(err, actives) {
|
||||
if (err) throw err;
|
||||
|
||||
// Check for new activities in the newlly scanned addresses
|
||||
var recentActive = actives.reduce(function(r, e, i) {
|
||||
return e ? scanIndex + i : r;
|
||||
}, lastActive);
|
||||
hasActivity = lastActive != recentActive;
|
||||
lastActive = recentActive;
|
||||
scanIndex += scanWindow;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function _while() {
|
||||
return hasActivity;
|
||||
},
|
||||
function _finnaly(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, lastActive);
|
||||
}
|
||||
// Check for new activities in the newlly scanned addresses
|
||||
var recentActive = actives.reduce(function(r, e, i) {
|
||||
return e ? scanIndex + i : r;
|
||||
}, lastActive);
|
||||
hasActivity = lastActive != recentActive;
|
||||
lastActive = recentActive;
|
||||
scanIndex += scanWindow;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function _while() {
|
||||
return hasActivity;
|
||||
},
|
||||
function _finnaly(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, lastActive);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -102,10 +102,7 @@ WalletFactory.prototype.read = function(walletId) {
|
|||
|
||||
WalletFactory.prototype.create = function(opts) {
|
||||
opts = opts || {};
|
||||
this.log('### CREATING NEW WALLET.' +
|
||||
(opts.id ? ' USING ID: ' + opts.id : ' NEW ID') +
|
||||
(opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')
|
||||
);
|
||||
this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
|
||||
|
||||
opts.privateKey = opts.privateKey || new PrivateKey({
|
||||
networkName: this.networkName
|
||||
|
@ -121,7 +118,8 @@ WalletFactory.prototype.create = function(opts) {
|
|||
});
|
||||
opts.publicKeyRing.addCopayer(
|
||||
opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
|
||||
opts.nickname);
|
||||
opts.nickname
|
||||
);
|
||||
this.log('\t### PublicKeyRing Initialized');
|
||||
|
||||
opts.txProposals = opts.txProposals || new TxProposals({
|
||||
|
@ -157,9 +155,9 @@ WalletFactory.prototype._checkVersion = function(inVersion) {
|
|||
//We only check for major version differences
|
||||
if (thisV0 < inV0) {
|
||||
throw new Error('Major difference in software versions' +
|
||||
'. Received:' + inVersion +
|
||||
'. Current version:' + this.version +
|
||||
'. Aborting.');
|
||||
'. Received:' + inVersion +
|
||||
'. Current version:' + this.version +
|
||||
'. Aborting.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
function FakeBlockchain(opts) {
|
||||
|
@ -47,4 +46,4 @@ FakeBlockchain.prototype.sendRawTransaction = function(rawtx, cb) {
|
|||
return cb(txid);
|
||||
};
|
||||
|
||||
module.exports = require('soop')(FakeBlockchain);
|
||||
module.exports = FakeBlockchain;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
'use scrict';
|
||||
var bitcore = bitcore || require('bitcore');
|
||||
var Script = bitcore.Script;
|
||||
|
||||
var VALID_SCRIPTSIG_BUF = new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae','hex');
|
||||
|
||||
function Tx() {
|
||||
this.ins = [{s: VALID_SCRIPTSIG_BUF }];
|
||||
};
|
||||
|
||||
|
||||
Tx.prototype.getHashType = function() {
|
||||
return 1;
|
||||
};
|
||||
|
||||
Tx.prototype.getNormalizedHash = function() {
|
||||
return '123456';
|
||||
};
|
||||
Tx.prototype.hashForSignature = function() {
|
||||
return new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex');
|
||||
};
|
||||
|
||||
|
||||
function FakeBuilder() {
|
||||
this.test = 1;
|
||||
this.tx = new Tx();
|
||||
this.signhash = 1;
|
||||
this.inputMap = [{ address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||
scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')),
|
||||
scriptType: 4,
|
||||
i: 0 }];
|
||||
|
||||
this.vanilla = {
|
||||
scriptSig: [VALID_SCRIPTSIG_BUF],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FakeBuilder.prototype.merge = function() {
|
||||
};
|
||||
|
||||
FakeBuilder.prototype.build = function() {
|
||||
return this.tx;
|
||||
};
|
||||
|
||||
|
||||
FakeBuilder.prototype.toObj = function() {
|
||||
return this;
|
||||
};
|
||||
FakeBuilder.VALID_SCRIPTSIG_BUF = VALID_SCRIPTSIG_BUF;
|
||||
module.exports = FakeBuilder;
|
|
@ -70,9 +70,9 @@ describe('HDPath model', function() {
|
|||
].forEach(function(datum) {
|
||||
var path = datum[0];
|
||||
var result = datum[1];
|
||||
it('should get the correct indices for path ' + path, function() {
|
||||
var i = HDPath.indicesForPath(path);
|
||||
i.index.should.equal(result.index);
|
||||
it('should get the correct indexes for path ' + path, function() {
|
||||
var i = HDPath.indexesForPath(path);
|
||||
i.addressIndex.should.equal(result.index);
|
||||
i.isChange.should.equal(result.isChange);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,11 +13,20 @@ try {
|
|||
} catch (e) {
|
||||
var copay = require('../copay'); //node
|
||||
}
|
||||
var PrivateKey = copay.PrivateKey;
|
||||
var PublicKeyRing = copay.PublicKeyRing;
|
||||
|
||||
var aMasterPubKey = 'tprv8ZgxMBicQKsPdSVTiWXEqCCzqRaRr9EAQdn5UVMpT9UHX67Dh1FmzEMbavPumpAicsUm2XvC6NTdcWB89yN5DUWx5HQ7z3KByUg7Ht74VRZ';
|
||||
|
||||
|
||||
var getNewEpk = function() {
|
||||
return new PrivateKey({
|
||||
networkName: 'livenet',
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
}
|
||||
|
||||
var createW = function(networkName) {
|
||||
var config = {
|
||||
networkName: networkName || 'livenet',
|
||||
|
@ -29,8 +38,8 @@ var createW = function(networkName) {
|
|||
var copayers = [];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
w.isComplete().should.equal(false);
|
||||
w.remainingCopayers().should.equal(5-i);
|
||||
var newEpk = w.addCopayer();
|
||||
w.remainingCopayers().should.equal(5 - i);
|
||||
var newEpk = w.addCopayer(getNewEpk());
|
||||
copayers.push(newEpk);
|
||||
}
|
||||
w.isComplete().should.equal(true);
|
||||
|
@ -43,6 +52,14 @@ var createW = function(networkName) {
|
|||
};
|
||||
};
|
||||
|
||||
var cachedW;
|
||||
var getCachedW = function() {
|
||||
if (!cachedW) {
|
||||
cachedW = createW();
|
||||
}
|
||||
return cachedW;
|
||||
};
|
||||
|
||||
describe('PublicKeyRing model', function() {
|
||||
|
||||
it('should create an instance (livenet)', function() {
|
||||
|
@ -78,7 +95,7 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
|
||||
it('should add and check when adding shared pub keys', function() {
|
||||
var k = createW();
|
||||
var k = getCachedW();
|
||||
var w = k.w;
|
||||
var copayers = k.copayers;
|
||||
|
||||
|
@ -92,7 +109,7 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
|
||||
it('should be able to to store and read', function() {
|
||||
var k = createW();
|
||||
var k = getCachedW();
|
||||
var w = k.w;
|
||||
var copayers = k.copayers;
|
||||
var changeN = 2;
|
||||
|
@ -124,10 +141,10 @@ describe('PublicKeyRing model', function() {
|
|||
|
||||
|
||||
it('should generate some p2sh addresses', function() {
|
||||
var k = createW();
|
||||
var k = getCachedW();
|
||||
var w = k.w;
|
||||
|
||||
[true, false].forEach(function(isChange){
|
||||
[true, false].forEach(function(isChange) {
|
||||
for (var i = 0; i < 2; i++) {
|
||||
var a = w.generateAddress(isChange, k.pub);
|
||||
a.isValid().should.equal(true);
|
||||
|
@ -148,7 +165,7 @@ describe('PublicKeyRing model', function() {
|
|||
var a = w.getAddresses();
|
||||
a.length.should.equal(1);
|
||||
|
||||
[true, false].forEach(function(isChange){
|
||||
[true, false].forEach(function(isChange) {
|
||||
for (var i = 0; i < 2; i++) {
|
||||
w.generateAddress(isChange, k.pub);
|
||||
}
|
||||
|
@ -185,18 +202,12 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
|
||||
it('should set backup ready', function() {
|
||||
var w = createW().w;
|
||||
var w = getCachedW().w;
|
||||
w.isBackupReady().should.equal(false);
|
||||
w.setBackupReady();
|
||||
w.isBackupReady().should.equal(true);
|
||||
});
|
||||
|
||||
it('should set backup ready', function() {
|
||||
var w = createW().w;
|
||||
w.isBackupReady().should.equal(false);
|
||||
w.setBackupReady();
|
||||
w.isBackupReady().should.equal(true);
|
||||
});
|
||||
|
||||
it('should check for other backups', function() {
|
||||
var w = createW().w;
|
||||
|
@ -213,7 +224,7 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
|
||||
it('should merge backup', function() {
|
||||
var w = createW().w;
|
||||
var w = getCachedW().w;
|
||||
|
||||
w.copayersBackup = ["a", "b"];
|
||||
var hasChanged = w.mergeBackups(["b", "c"]);
|
||||
|
@ -313,11 +324,10 @@ describe('PublicKeyRing model', function() {
|
|||
var w0 = new PublicKeyRing({
|
||||
networkName: 'livenet',
|
||||
});
|
||||
w0.addCopayer();
|
||||
w0.addCopayer();
|
||||
w0.addCopayer();
|
||||
w0.addCopayer();
|
||||
w0.addCopayer();
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
w0.addCopayer(getNewEpk());
|
||||
|
||||
(function() {
|
||||
w0.merge(w);
|
||||
}).should.throw();
|
||||
|
@ -327,7 +337,7 @@ describe('PublicKeyRing model', function() {
|
|||
var wx = new PublicKeyRing({
|
||||
networkName: 'livenet',
|
||||
});
|
||||
wx.addCopayer();
|
||||
wx.addCopayer(getNewEpk());
|
||||
(function() {
|
||||
w.merge(wx);
|
||||
}).should.throw();
|
||||
|
@ -343,7 +353,7 @@ describe('PublicKeyRing model', function() {
|
|||
var copayers = [];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
w.isComplete().should.equal(false);
|
||||
w.addCopayer();
|
||||
w.addCopayer(getNewEpk());
|
||||
}
|
||||
|
||||
var w2 = new PublicKeyRing({
|
||||
|
@ -354,7 +364,7 @@ describe('PublicKeyRing model', function() {
|
|||
var copayers = [];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
w2.isComplete().should.equal(false);
|
||||
w2.addCopayer();
|
||||
w2.addCopayer(getNewEpk());
|
||||
}
|
||||
w2.merge(w).should.equal(true);
|
||||
w2.isComplete().should.equal(true);
|
||||
|
@ -379,7 +389,7 @@ describe('PublicKeyRing model', function() {
|
|||
networkName: 'livenet',
|
||||
id: w.id,
|
||||
});
|
||||
w2.addCopayer();
|
||||
w2.addCopayer(getNewEpk());
|
||||
w.merge(w2).should.equal(true);
|
||||
}
|
||||
w.isComplete().should.equal(true);
|
||||
|
@ -393,7 +403,7 @@ describe('PublicKeyRing model', function() {
|
|||
var w = new PublicKeyRing(config);
|
||||
should.exist(w);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
w.addCopayer();
|
||||
w.addCopayer(getNewEpk());
|
||||
};
|
||||
w._setNicknameForIndex(0, 'pepe0');
|
||||
w._setNicknameForIndex(1, 'pepe1');
|
||||
|
@ -409,7 +419,7 @@ describe('PublicKeyRing model', function() {
|
|||
networkName: 'livenet',
|
||||
id: w.id,
|
||||
});
|
||||
w2.addCopayer();
|
||||
w2.addCopayer(getNewEpk());
|
||||
w2._setNicknameForIndex(0, 'juan' + i);
|
||||
w.merge(w2).should.equal(true);
|
||||
}
|
||||
|
@ -430,7 +440,7 @@ describe('PublicKeyRing model', function() {
|
|||
var w = new PublicKeyRing(config);
|
||||
should.exist(w);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
w.addCopayer(null, 'tito' + i);
|
||||
w.addCopayer(getNewEpk(), 'tito' + i);
|
||||
};
|
||||
w.nicknameForIndex(0).should.equal('tito0');
|
||||
w.nicknameForIndex(1).should.equal('tito1');
|
||||
|
@ -468,7 +478,7 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
|
||||
it('#getRedeemScriptMap check tests', function() {
|
||||
var k = createW();
|
||||
var k = getCachedW();
|
||||
var w = k.w;
|
||||
var amount = 2;
|
||||
|
||||
|
@ -489,4 +499,27 @@ describe('PublicKeyRing model', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('#getForPath should return 5 pubkeys', function() {
|
||||
var w = getCachedW().w;
|
||||
var pubkeys = w.getForPath('m/45\'/2147483647/1/0');
|
||||
pubkeys.length.should.equal(5);
|
||||
});
|
||||
|
||||
it('#getForPaths should return 2 arrays of 5 pubkey ', function() {
|
||||
var w = getCachedW().w;
|
||||
var pubkeys = w.getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']);
|
||||
pubkeys.length.should.equal(2);
|
||||
pubkeys[0].length.should.equal(5);
|
||||
pubkeys[1].length.should.equal(5);
|
||||
});
|
||||
|
||||
it('#forPaths should return copayers and pubkeys ', function() {
|
||||
var w = getCachedW().w;
|
||||
var ret = w.forPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']);
|
||||
ret.copayerIds.length.should.equal(5);
|
||||
ret.pubKeys.length.should.equal(2);
|
||||
ret.pubKeys[0].length.should.equal(5);
|
||||
ret.pubKeys[1].length.should.equal(5);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,431 @@
|
|||
'use strict';
|
||||
|
||||
var chai = chai || require('chai');
|
||||
var should = chai.should();
|
||||
var bitcore = bitcore || require('bitcore');
|
||||
var Transaction = bitcore.Transaction;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var WalletKey = bitcore.WalletKey;
|
||||
var Key = bitcore.Key;
|
||||
var bignum = bitcore.Bignum;
|
||||
var Script = bitcore.Script;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var util = bitcore.util;
|
||||
var networks = bitcore.networks;
|
||||
var sinon = require('sinon');
|
||||
try {
|
||||
var copay = require('copay'); //browser
|
||||
} catch (e) {
|
||||
var copay = require('../copay'); //node
|
||||
}
|
||||
|
||||
var FakeBuilder = require('./mocks/FakeBuilder');
|
||||
var TxProposal = copay.TxProposal;
|
||||
|
||||
var dummyProposal = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: new FakeBuilder(),
|
||||
inputChainPaths: ['m/1'],
|
||||
});
|
||||
|
||||
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
||||
|
||||
describe('TxProposal', function() {
|
||||
describe('new', function() {
|
||||
it('should fail to create an instance with wrong arguments', function() {
|
||||
|
||||
(function() {
|
||||
var txp = new TxProposal();
|
||||
}).should.throw('Illegal Argument');
|
||||
|
||||
(function() {
|
||||
var txp = new TxProposal({
|
||||
creator: 1
|
||||
});
|
||||
}).should.throw('no inputChainPaths');
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should create an instance', function() {
|
||||
var txp = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: new FakeBuilder(),
|
||||
inputChainPaths: 'm/1',
|
||||
});
|
||||
should.exist(txp);
|
||||
|
||||
txp.creator.should.equal(1);
|
||||
should.exist(txp.builder);
|
||||
txp.inputChainPaths.should.equal('m/1');
|
||||
});
|
||||
});
|
||||
describe('#getId', function() {
|
||||
it('should return id', function() {
|
||||
var b = new FakeBuilder();
|
||||
var spy = sinon.spy(b.tx, 'getNormalizedHash');
|
||||
var txp = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: b,
|
||||
inputChainPaths: 'm/1',
|
||||
});
|
||||
txp.getId().should.equal('123456');;
|
||||
sinon.assert.callCount(spy, 1);
|
||||
});
|
||||
});
|
||||
describe('#toObj', function() {
|
||||
it('should return an object and remove builder', function() {
|
||||
var b = new FakeBuilder();
|
||||
var txp = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: b,
|
||||
inputChainPaths: 'm/1',
|
||||
});
|
||||
var o = txp.toObj();
|
||||
should.exist(o);
|
||||
o.creator.should.equal(1);
|
||||
should.not.exist(o.builder);
|
||||
should.exist(o.builderObj);
|
||||
});
|
||||
it('toObjTrim', function() {
|
||||
var b = new FakeBuilder();
|
||||
var txp = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: b,
|
||||
inputChainPaths: 'm/1',
|
||||
comment: 'hola',
|
||||
});
|
||||
var o = txp.toObjTrim();
|
||||
should.exist(o);
|
||||
should.not.exist(o.creator);
|
||||
should.not.exist(o.builder);
|
||||
should.exist(o.comment);
|
||||
should.exist(o.builderObj);
|
||||
});
|
||||
|
||||
});
|
||||
describe('#fromObj', function() {
|
||||
it.skip('should create from Object', function() {
|
||||
var b = new FakeBuilder();
|
||||
var txp = TxProposal.fromObj({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builderObj: b.toObj(),
|
||||
inputChainPaths: ['m/1'],
|
||||
});
|
||||
should.exist(txp);
|
||||
});
|
||||
|
||||
|
||||
it('should fail to create from wrong object', function() {
|
||||
var b = new FakeBuilder();
|
||||
(function() {
|
||||
var txp = TxProposal.fromObj({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builderObj: b.toObj(),
|
||||
inputChainPaths: ['m/1'],
|
||||
});
|
||||
}).should.throw('Invalid');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('#setSent', function() {
|
||||
it('should set txid and timestamp', function() {
|
||||
var now = Date.now();
|
||||
var txp = dummyProposal;
|
||||
txp.setSent('3a42');
|
||||
txp.sentTs.should.gte(now);
|
||||
txp.sentTxid.should.equal('3a42');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Signature verification', function() {
|
||||
var validScriptSig = new bitcore.Script(FakeBuilder.VALID_SCRIPTSIG_BUF);
|
||||
|
||||
var pubkeys = [
|
||||
'03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d',
|
||||
'0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127',
|
||||
'0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03',
|
||||
'03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3',
|
||||
'03e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e4'
|
||||
].map(function(hex) {
|
||||
return new Buffer(hex, 'hex');
|
||||
});
|
||||
var keyBuf = someKeys.map(function(hex) {
|
||||
return new Buffer(hex, 'hex');
|
||||
});
|
||||
it('#_formatKeys', function() {
|
||||
(function() {
|
||||
TxProposal._formatKeys(someKeys);
|
||||
}).should.throw('buffers');
|
||||
var res = TxProposal._formatKeys(keyBuf);
|
||||
});
|
||||
it('#_verifyScriptSig arg checks', function() {
|
||||
(function() {
|
||||
TxProposal._verifySignatures(
|
||||
keyBuf,
|
||||
new bitcore.Script(new Buffer('112233', 'hex')),
|
||||
new Buffer('1a', 'hex'));
|
||||
}).should.throw('script');
|
||||
});
|
||||
it('#_verifyScriptSig, no signatures', function() {
|
||||
var ret = TxProposal._verifySignatures(keyBuf, validScriptSig, new Buffer(32));
|
||||
ret.length.should.equal(0);
|
||||
});
|
||||
it('#_verifyScriptSig, two signatures', function() {
|
||||
// Data taken from bitcore's TransactionBuilder test
|
||||
var txp = dummyProposal;
|
||||
var tx = dummyProposal.builder.build();
|
||||
var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
||||
ret.should.deep.equal([0, 3]);
|
||||
});
|
||||
it('#_infoFromRedeemScript', function() {
|
||||
var info = TxProposal._infoFromRedeemScript(validScriptSig);
|
||||
var keys = info.keys;
|
||||
keys.length.should.equal(5);
|
||||
for (var i in keys) {
|
||||
keys[i].toString('hex').should.equal(pubkeys[i].toString('hex'));
|
||||
}
|
||||
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
||||
});
|
||||
it('#_updateSignedBy', function() {
|
||||
var txp = dummyProposal;
|
||||
txp._inputSignatures.should.deep.equal([
|
||||
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
|
||||
]);
|
||||
});
|
||||
describe('#_check', function() {
|
||||
var txp = dummyProposal;
|
||||
var backup = txp.builder.tx.ins;
|
||||
|
||||
it('OK', function() {
|
||||
txp._check();
|
||||
});
|
||||
it('FAIL ins', function() {
|
||||
txp.builder.tx.ins = [];
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('no ins');
|
||||
txp.builder.tx.ins = backup;
|
||||
});
|
||||
it('FAIL signhash SINGLE', function() {
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_SINGLE);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL signhash NONE', function() {
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_NONE);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL signhash ANYONECANPAY', function() {
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL no signatures', function() {
|
||||
var backup = txp.builder.tx.ins[0].s;
|
||||
txp.builder.tx.ins[0].s = undefined;
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('no signatures');
|
||||
txp.builder.tx.ins[0].s = backup;
|
||||
});
|
||||
});
|
||||
describe('#merge', function() {
|
||||
var txp = dummyProposal;
|
||||
var backup = txp.builder.tx.ins;
|
||||
it('with self', function() {
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(false);
|
||||
});
|
||||
|
||||
it('with less signatures', function() {
|
||||
var backup = txp.builder.vanilla.scriptSig[0];
|
||||
txp.builder.merge = function() {
|
||||
// 2 signatures.
|
||||
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
||||
};
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(true);
|
||||
|
||||
txp.builder.vanilla.scriptSig = [backup];
|
||||
txp.builder.tx.ins[0].s = new Buffer(backup, 'hex');
|
||||
});
|
||||
|
||||
|
||||
it('with more signatures', function() {
|
||||
txp.builder.merge = function() {
|
||||
// 3 signatures.
|
||||
this.vanilla.scriptSig = ['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
||||
};
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('#setCopayers', function() {
|
||||
it("should fails if Tx has no creator", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.signedBy = {
|
||||
'hugo': 1
|
||||
};
|
||||
delete txp['creator'];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('no creator');
|
||||
});
|
||||
it("should fails if Tx is not signed by creator", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
'hugo': 1
|
||||
};
|
||||
txp._inputSignatures = [
|
||||
['pkX']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('creator');
|
||||
});
|
||||
|
||||
|
||||
it("should fails if Tx has unmapped signatures", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
creator: 1
|
||||
};
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pkX']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('unknown sig');
|
||||
});
|
||||
|
||||
// This was disabled. Unnecessary to check this.
|
||||
it.skip("should be signed by sender", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk1', 'pk0']
|
||||
];
|
||||
txp.signedBy = {
|
||||
'creator': Date.now()
|
||||
};
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
}).should.throw('senders sig');
|
||||
});
|
||||
|
||||
|
||||
it("should set signedBy (trivial case)", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk1', 'pk0']
|
||||
];
|
||||
txp.signedBy = {
|
||||
'creator': Date.now()
|
||||
};
|
||||
txp.setCopayers('pepe', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(2);
|
||||
txp.signedBy['pepe'].should.gte(ts);
|
||||
txp.signedBy['creator'].should.gte(ts);
|
||||
});
|
||||
it("should assign creator", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk0']
|
||||
];
|
||||
txp.signedBy = {};
|
||||
delete txp['creator'];
|
||||
delete txp['creatorTs'];
|
||||
txp.setCopayers('creator', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(1);
|
||||
txp.creator.should.equal('creator');
|
||||
txp.createdTs.should.gte(ts);
|
||||
txp.seenBy['creator'].should.equal(txp.createdTs);
|
||||
})
|
||||
it("New tx should have only 1 signature", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp.signedBy = {};
|
||||
delete txp['creator'];
|
||||
delete txp['creatorTs'];
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pk1']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers(
|
||||
'creator', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
}, {
|
||||
'creator2': 1
|
||||
}
|
||||
);
|
||||
}).should.throw('only 1');
|
||||
})
|
||||
|
||||
it("if signed, should not change ts", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pk1']
|
||||
];
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
'creator': 1
|
||||
};
|
||||
txp.setCopayers('pepe', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(2);
|
||||
txp.creator.should.equal('creator');
|
||||
txp.signedBy['creator'].should.equal(1);
|
||||
txp.signedBy['pepe'].should.gte(ts);
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -12,717 +12,105 @@ var Script = bitcore.Script;
|
|||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var util = bitcore.util;
|
||||
var networks = bitcore.networks;
|
||||
var sinon = require('sinon');
|
||||
try {
|
||||
var copay = require('copay'); //browser
|
||||
} catch (e) {
|
||||
var copay = require('../copay'); //node
|
||||
}
|
||||
var fakeStorage = copay.FakeStorage;
|
||||
var PrivateKey = copay.PrivateKey || require('../js/models/PrivateKey');
|
||||
var TxProposals = copay.TxProposals || require('../js/models/TxProposal');
|
||||
var is_browser = (typeof process == 'undefined' || typeof process.versions === 'undefined')
|
||||
var PublicKeyRing = is_browser ? copay.PublicKeyRing :
|
||||
require('soop').load('../js/models/core/PublicKeyRing', {
|
||||
Storage: fakeStorage
|
||||
|
||||
var FakeBuilder = require('./mocks/FakeBuilder');
|
||||
var TxProposal = copay.TxProposal;
|
||||
var TxProposals = copay.TxProposals;
|
||||
|
||||
var dummyProposal = new TxProposal({
|
||||
creator: 1,
|
||||
createdTs: 1,
|
||||
builder: new FakeBuilder(),
|
||||
inputChainPaths: ['m/1'],
|
||||
});
|
||||
|
||||
var config = {
|
||||
networkName: 'testnet',
|
||||
};
|
||||
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
||||
|
||||
var unspentTest = [{
|
||||
"address": "dummy",
|
||||
"scriptPubKey": "dummy",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
"vout": 1,
|
||||
"amount": 10,
|
||||
"confirmations": 7
|
||||
}];
|
||||
|
||||
var createPKR = function(bip32s) {
|
||||
var w = new PublicKeyRing(config);
|
||||
should.exist(w);
|
||||
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if (bip32s && i < bip32s.length) {
|
||||
var b = bip32s[i];
|
||||
w.addCopayer(b.deriveBIP45Branch().extendedPublicKeyString());
|
||||
} else {
|
||||
w.addCopayer();
|
||||
}
|
||||
}
|
||||
|
||||
var pubkey = bip32s[0].publicHex;
|
||||
|
||||
w.generateAddress(false, pubkey);
|
||||
w.generateAddress(false, pubkey);
|
||||
w.generateAddress(false, pubkey);
|
||||
w.generateAddress(true, pubkey);
|
||||
w.generateAddress(true, pubkey);
|
||||
w.generateAddress(true, pubkey);
|
||||
|
||||
return w;
|
||||
};
|
||||
|
||||
var vopts = {
|
||||
verifyP2SH: true,
|
||||
dontVerifyStrictEnc: true
|
||||
};
|
||||
|
||||
|
||||
describe('TxProposals model', function() {
|
||||
|
||||
var isChange = false;
|
||||
var addressIndex = 0;
|
||||
|
||||
it('verify TXs', function(done) {
|
||||
|
||||
var priv = new PrivateKey(config);
|
||||
var priv2 = new PrivateKey(config);
|
||||
var priv3 = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var ts = Date.now();
|
||||
var pkr = createPKR([priv, priv2, priv3]);
|
||||
var opts = {
|
||||
remainderOut: {
|
||||
address: pkr.generateAddress(true, pub).toString()
|
||||
}
|
||||
};
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var b = w.txps[ntxid].builder;
|
||||
var tx = b.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
|
||||
var ringIndex = pkr.getHDParams(pub);
|
||||
b.sign(priv2.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.copayerIndex));
|
||||
b.sign(priv3.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.copayerIndex));
|
||||
tx = b.build();
|
||||
tx.isComplete().should.equal(true);
|
||||
|
||||
var s = new Script(new bitcore.Buffer(unspentTest[0].scriptPubKey, 'hex'));
|
||||
|
||||
tx.verifyInput(0, s, {
|
||||
verifyP2SH: true,
|
||||
dontVerifyStrictEnc: true
|
||||
}, function(err, results) {
|
||||
should.not.exist(err);
|
||||
results.should.equal(true);
|
||||
done();
|
||||
describe('TxProposals', function() {
|
||||
describe('constructor', function() {
|
||||
it('should create an instance', function() {
|
||||
var txps = new TxProposals();
|
||||
should.exist(txps);
|
||||
txps.network.name.should.equal('testnet');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should create an instance', function() {
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName
|
||||
});
|
||||
should.exist(w);
|
||||
w.network.name.should.equal(config.networkName);
|
||||
});
|
||||
|
||||
var createTx = function(toAddress, amountSatStr, utxos, opts, priv, pkr) {
|
||||
opts = opts || {};
|
||||
|
||||
var pub = priv.publicHex;
|
||||
|
||||
if (!pkr.isComplete()) {
|
||||
throw new Error('publicKeyRing is not complete');
|
||||
}
|
||||
|
||||
if (!opts.remainderOut) {
|
||||
opts.remainderOut = {
|
||||
address: pkr.generateAddress(true, pub).toString()
|
||||
};
|
||||
};
|
||||
|
||||
var b = new TransactionBuilder(opts)
|
||||
.setUnspent(utxos)
|
||||
.setOutputs([{
|
||||
address: toAddress,
|
||||
amountSatStr: amountSatStr,
|
||||
}]);
|
||||
var selectedUtxos = b.getSelectedUnspent();
|
||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||
return pkr.pathForAddress(utxo.address);
|
||||
});
|
||||
|
||||
var selectedUtxos = b.getSelectedUnspent();
|
||||
|
||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||
return pkr.pathForAddress(utxo.address);
|
||||
});
|
||||
|
||||
b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
||||
var signRet;
|
||||
if (priv) {
|
||||
var pkeys = priv.getForPaths(inputChainPaths);
|
||||
b.sign(pkeys);
|
||||
}
|
||||
var me = {};
|
||||
if (priv) me[priv.getId()] = Date.now();
|
||||
|
||||
var tx = b.build();
|
||||
|
||||
return {
|
||||
inputChainPaths: inputChainPaths,
|
||||
creator: priv.getId(),
|
||||
createdTs: new Date(),
|
||||
signedBy: priv && tx.countInputSignatures(0) ? me : {},
|
||||
seenBy: priv ? me : {},
|
||||
builder: b,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
it('#getUsedUnspend', function() {
|
||||
var priv = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
var start = new Date().getTime();
|
||||
var pkr = createPKR([priv]);
|
||||
var ts = Date.now();
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest, {},
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var uu = w.getUsedUnspent();
|
||||
var uuk = Object.keys(uu);
|
||||
uuk.length.should.equal(1);
|
||||
uuk[0].split(',')[0].should.equal(unspentTest[0].txid);
|
||||
});
|
||||
|
||||
it('#merge with self', function() {
|
||||
var priv = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
var start = new Date().getTime();
|
||||
var pkr = createPKR([priv]);
|
||||
var ts = Date.now();
|
||||
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest, {},
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
|
||||
var x = priv.getId();
|
||||
(w.txps[ntxid].signedBy[priv.getId()] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
var info = w.merge(w.txps[ntxid], pkr.getCopayerId(0));
|
||||
info.events.length.should.equal(0);
|
||||
|
||||
Object.keys(w.txps).length.should.equal(1);
|
||||
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('#merge, merge signatures case 1', function() {
|
||||
var priv2 = new PrivateKey(config);
|
||||
var priv = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var ts = Date.now();
|
||||
var pkr = createPKR([priv]);
|
||||
var opts = {
|
||||
remainderOut: {
|
||||
address: pkr.generateAddress(true, pub).toString()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv2,
|
||||
pkr
|
||||
));
|
||||
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputSignatures(0).should.equal(0);
|
||||
tx.countInputMissingSignatures(0).should.equal(1);
|
||||
|
||||
Object.keys(w.txps[ntxid].signedBy).length.should.equal(0);
|
||||
Object.keys(w.txps[ntxid].seenBy).length.should.equal(1);
|
||||
|
||||
|
||||
var w2 = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
publicKeyRing: w.publicKeyRing,
|
||||
});
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w2.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var tx = w2.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
|
||||
|
||||
(w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true, 'asdsd');
|
||||
(w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0));
|
||||
info.events.length.should.equal(2);
|
||||
info.events[0].type.should.equal('seen');
|
||||
info.events[1].type.should.equal('signed');
|
||||
|
||||
Object.keys(w.txps).length.should.equal(1);
|
||||
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
});
|
||||
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
it('#merge, merge signatures case 2', function() {
|
||||
|
||||
var o1 = {
|
||||
extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdSF1avR6mXyDj5Uv1XY2UyUHSDpAXQ5TvPN7prGeDppjy4562rBB9gMMAhRfFdJrNDpQ4t69kkqHNEEen3PX1zBJqSehJDH',
|
||||
networkName: 'testnet',
|
||||
privateKeyCache: {}
|
||||
};
|
||||
var o2 = {
|
||||
extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdVeB5RzuxS9JQcACueZYgUaM5eWzaEBkHjW5Pg6Mqez1APSqoUP1jUdbT8WVG7ZJYTXvUL7XtPzFYBXjmdKuwSor1dcNQ8j',
|
||||
networkName: 'testnet',
|
||||
privateKeyCache: {}
|
||||
};
|
||||
var o3 = {
|
||||
extendedPrivateKeyString: 'tprv8ZgxMBicQKsPeHWNrPVZtQVgcCtXBr5TACNbDQ56rwqNJce9MEc64US6DJKxpWsrebEomxxWZFDtkvkZGkzA43uLvdF4XHiWqoNaL6Dq2Gd',
|
||||
networkName: 'testnet',
|
||||
privateKeyCache: {}
|
||||
};
|
||||
|
||||
|
||||
var priv = PrivateKey.fromObj(o1);
|
||||
var priv2 = PrivateKey.fromObj(o2);
|
||||
var priv3 = PrivateKey.fromObj(o3);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var ts = Date.now();
|
||||
var pkr = createPKR([priv, priv2]);
|
||||
var opts = {
|
||||
remainderOut: {
|
||||
address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr'
|
||||
}
|
||||
};
|
||||
var addressToSign = pkr.generateAddress(false, pub);
|
||||
unspentTest[0].address = addressToSign.toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
var tx, txb;
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv3,
|
||||
pkr
|
||||
));
|
||||
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
txb = w.txps[ntxid].builder;
|
||||
tx = txb.build();
|
||||
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(1);
|
||||
|
||||
Object.keys(w.txps[ntxid].signedBy).length.should.equal(0);
|
||||
Object.keys(w.txps[ntxid].seenBy).length.should.equal(1);
|
||||
|
||||
var w2 = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
|
||||
|
||||
|
||||
w2.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var ntxid = Object.keys(w2.txps)[0];
|
||||
txb = w2.txps[ntxid].builder;
|
||||
tx = txb.build();
|
||||
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
|
||||
(w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0));
|
||||
info.events.length.should.equal(2);
|
||||
info.events[0].type.should.equal('seen');
|
||||
info.events[1].type.should.equal('signed');
|
||||
|
||||
tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
|
||||
var w3 = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
publicKeyRing: pkr,
|
||||
});
|
||||
w3.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv2,
|
||||
pkr
|
||||
));
|
||||
tx = w3.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
|
||||
(w3.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w3.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true);
|
||||
|
||||
var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(1));
|
||||
|
||||
Object.keys(w.txps).length.should.equal(1);
|
||||
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true);
|
||||
|
||||
tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(1);
|
||||
});
|
||||
|
||||
|
||||
it('#merge, merge signatures case 3', function() {
|
||||
|
||||
var priv = new PrivateKey(config);
|
||||
var priv2 = new PrivateKey(config);
|
||||
var priv3 = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
|
||||
var ts = Date.now();
|
||||
var pkr = createPKR([priv, priv2, priv3]);
|
||||
var opts = {
|
||||
remainderOut: {
|
||||
address: pkr.generateAddress(true, pub).toString()
|
||||
}
|
||||
};
|
||||
|
||||
var w = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
|
||||
var w2 = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w2.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv2,
|
||||
pkr
|
||||
));
|
||||
var tx = w2.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w2.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w2.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true);
|
||||
|
||||
var w3 = new TxProposals({
|
||||
networkName: config.networkName,
|
||||
});
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w3.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest,
|
||||
opts,
|
||||
priv3,
|
||||
pkr
|
||||
));
|
||||
var tx = w3.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true);
|
||||
(w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true);
|
||||
|
||||
var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(1));
|
||||
|
||||
Object.keys(w.txps).length.should.equal(1);
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(1);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true);
|
||||
|
||||
|
||||
var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(2));
|
||||
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.countInputMissingSignatures(0).should.equal(0);
|
||||
Object.keys(w.txps).length.should.equal(1);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
it('#fromObj stored (hardcoded) data', function() {
|
||||
var txp = TxProposals.TxProposal.fromObj(txpv1);
|
||||
txp.getID().should.equal('5cae6e225335acd2725856c71ef1ca61c42f118967102c5d0ed6710343e4a19f');
|
||||
var tx = txp.builder.build();
|
||||
tx.countInputSignatures(0).should.equal(2);
|
||||
tx.countInputMissingSignatures(0).should.equal(0);
|
||||
});
|
||||
|
||||
it('#toObj #fromObj roundtrip', function() {
|
||||
|
||||
var priv = new PrivateKey(config);
|
||||
var pub = priv.publicHex;
|
||||
|
||||
var pkr = createPKR([priv]);
|
||||
var w = new TxProposals({
|
||||
walletId: 'qwerty',
|
||||
networkName: config.networkName,
|
||||
});
|
||||
var ts = Date.now();
|
||||
|
||||
unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString();
|
||||
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub);
|
||||
w.add(createTx(
|
||||
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||
'123456789',
|
||||
unspentTest, {},
|
||||
priv,
|
||||
pkr
|
||||
));
|
||||
var ntxid = Object.keys(w.txps)[0];
|
||||
var tx = w.txps[ntxid].builder.build();
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.countInputMissingSignatures(0).should.equal(2);
|
||||
(w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
|
||||
var o = w.toObj();
|
||||
should.exist(o);
|
||||
o.txps.length.should.equal(1);
|
||||
|
||||
should.exist(o.txps[0]);
|
||||
should.exist(o.txps[0].signedBy);
|
||||
should.exist(o.txps[0].seenBy);
|
||||
should.exist(o.txps[0].builderObj);
|
||||
should.exist(o.txps[0].signedBy[priv.id]);
|
||||
|
||||
var o2 = JSON.parse(JSON.stringify(o));
|
||||
var w2 = TxProposals.fromObj(o2);
|
||||
w2.walletId.should.equal(w.walletId);
|
||||
|
||||
var tx2 = w2.txps[ntxid].builder.build();
|
||||
tx2.isComplete().should.equal(false);
|
||||
tx2.countInputMissingSignatures(0).should.equal(2);
|
||||
(w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true);
|
||||
(w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true);
|
||||
should.exist(w2.txps[ntxid].builder);
|
||||
should.exist(w2.txps[ntxid].builder.valueInSat);
|
||||
|
||||
w2.merge(w.txps[ntxid], pkr.getCopayerId(0));
|
||||
Object.keys(w2.txps).length.should.equal(1);
|
||||
});
|
||||
|
||||
describe('TxProposal model', function() {
|
||||
var createMockTxp = function(raw) {
|
||||
var tx = new Transaction();
|
||||
tx.parse(new Buffer(raw, 'hex'));
|
||||
var txb = new TransactionBuilder();
|
||||
var txp = new TxProposals.TxProposal({
|
||||
builder: txb
|
||||
describe('#fromObj', function() {
|
||||
it('should create an instance from an Object', function() {
|
||||
var txps = TxProposals.fromObj({
|
||||
networkName:'livenet',
|
||||
walletId: '123a12',
|
||||
txps: [],
|
||||
});
|
||||
txb.build = function() {
|
||||
return tx;
|
||||
};
|
||||
return txp;
|
||||
};
|
||||
|
||||
it('should validate for no signatures yet in tx', function() {
|
||||
// taken from https://gist.github.com/gavinandresen/3966071
|
||||
var raw = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d60000000000ffffffff0140420f000000000017a914f815b036d9bbbce5e9f2a00abd1bf3dc91e955108700000000';
|
||||
var txp = createMockTxp(raw);
|
||||
txp.isValid().should.equal(true);
|
||||
should.exist(txps);
|
||||
txps.network.name.should.equal('livenet');
|
||||
});
|
||||
it('should validate for no signatures yet in copay generated tx', function() {
|
||||
// taken from copay incomplete tx proposal
|
||||
var raw = '0100000001e205297fd05e4504d72761dc7a16e5cc9f4ab89877f28aee97c1cc66b3f07d690100000000ffffffff01706f9800000000001976a91473707e88f79c9c616b44bc766a25efcb9f49346688ac00000000';
|
||||
var txp = createMockTxp(raw);
|
||||
txp.isValid().should.equal(true);
|
||||
});
|
||||
it('should validate for a SIGHASH_NONE tx in builder', function() {
|
||||
var raw = '010000000145c3bf51ced6cefaea8c6578a645316270dbf8600f46969d31136e1e06829598000000007000483045022100877c715e0f3bd6377086c96d4757b2c983682a1934d9e3f894941f4f1e18d4710220272ed81758d7a391ee4c15a29246f3fe75efbddeaf1118e4c0d3bb14f57cdba601255121022f58491a833933a9bea80d8e820e66bee91bd8c71bfa972fe70482360b48129951aeffffffff01706f9800000000001976a91408328947f0caf8728729d740cbecdfe3c2327db588ac00000000';
|
||||
var txp = createMockTxp(raw);
|
||||
txp.isValid().should.equal(true);
|
||||
});
|
||||
it('should not validate for a non SIGHASH_NONE tx in builder with 1 input', function() {
|
||||
var raw = '0100000001eaf08f93f895127fbf000128ac74f6e8c7f003854e5ee1f02a5fd820cb689beb00000000fdfe00004730440220778f3174393e9ee6b0bfa876b4150db6f12a4da9715044ead5e345c2781ceee002203aab31f1e1d3dcf77ca780d9af798139719891917c9a09123dba54483ef462bc02493046022100dd93b64b30580029605dbba09d7fa34194d9ff38fda0c4fa187c52bf7f79ae98022100dd7b056762087b9aa8ccfde328d7067fa1753b78c0ee25577122569ff9de1d57024c695221039f847c24f09d7299c10bba4e41b24dc78e47bbb05fd7c1d209c994899d6881062103d363476e634fc5cdc11e9330c05a141c1e0c7f8b616817bdb83e7579bbf870942103fb2072953ceab87c6da450ac661685a881ddb661002d2ec1d60bfd33e3ec807d53aeffffffff01d06bf5050000000017a914db682f579cf6ca483880460fcf4ab63e223dc07e8700000000';
|
||||
var txp = createMockTxp(raw);
|
||||
txp.isValid().should.equal(false);
|
||||
});
|
||||
it('should not validate for a non SIGHASH_NONE tx in builder with 1 input', function() {
|
||||
var raw = '0100000002d903852d223b3100fcc01e0b02d73a76a0787cdff7d000e9cba0e931917f407501000000fdfe0000493046022100b232e994fdca7fd61fcf8ffe4a7f746ff8f8baf2667ac80841de0250f521c402022100862c0783ca7eafcbd2786b9444ed6e83ae941dcc2248bea4db12b7815d15de050247304402200189fe0cde9d1dd192553f4dddb6764df3eb643f9f71be8aa015f41f2d4fd11f02205513b8ca985c3b5b936f814c7eba92e2e2985c83927ca06c41081d264c0be7a7024c695221026fa1a3ed0c820c1053c8ba101f3c96f85c55624a902a82cf6b2896ed5f9b3d1521035a3383c13dd346a5784adfe3ec3026ab31d519fdfae2740497b10bdfb994e6442103c7477a6668d5bc250fe727e358d951b9e05f1d7c02059bf59ecbb335f1eeec7953aeffffffffd903852d223b3100fcc01e0b02d73a76a0787cdff7d000e9cba0e931917f407500000000fdfd0000483045022100bdb9d14569af66d84af63416d77296ace24a96f1720d30e74bc6e316a4b3727502206ed54d532467393488889d72edbb667d075de491a89e8e496fee8791b943fa37024730440220379c30c884a21a949d8ec32d6934ffa9faf86add4d839de0f5fbd2b90f8ef1e802204048df2ec0035ce5e4bf01e9d70fd93a45a41ce2630100d692cd908cdaa61fc0024c69522102203938ef947327edce2cf2997c55b433be3d3ffcf3284c10d6fcdf4b01c6221f21033b60c3363a226ce9b850af655c6e1470d9a0936d7f56ea4a07ab84005f91cd1b210385755bc813fe7f92577b93bf689bf0d9b2118e6bbb7fee5d3d16976f4f7271af53aeffffffff01c02d9a3b0000000017a914db682f579cf6ca483880460fcf4ab63e223dc07e8700000000';
|
||||
var txp = createMockTxp(raw);
|
||||
txp.isValid().should.equal(false);
|
||||
it('should fail create an instance from an Object with errors', function() {
|
||||
(function() {var txps = TxProposals.fromObj({
|
||||
networkName:'livenet',
|
||||
walletId: '123a12',
|
||||
txps: [ { a: 1 }],
|
||||
}) }).should.throw('Illegal');
|
||||
});
|
||||
});
|
||||
describe('#getNtxids', function() {
|
||||
it('should return keys', function() {
|
||||
var txps = new TxProposals();
|
||||
txps.txps = {a:1, b:2};
|
||||
txps.getNtxids().should.deep.equal(['a','b']);
|
||||
});
|
||||
});
|
||||
describe('#toObj', function() {
|
||||
it('should an object', function() {
|
||||
var txps = TxProposals.fromObj({
|
||||
networkName:'livenet',
|
||||
walletId: '123a12',
|
||||
txps: [],
|
||||
});
|
||||
var o = txps.toObj();
|
||||
o.walletId.should.equal('123a12');
|
||||
o.networkName.should.equal('livenet');
|
||||
});
|
||||
it('should export txps', function() {
|
||||
var txps = TxProposals.fromObj({
|
||||
networkName:'livenet',
|
||||
walletId: '123a12',
|
||||
txps: [],
|
||||
});
|
||||
txps.txps = {
|
||||
'hola' : dummyProposal,
|
||||
'chau' : dummyProposal,
|
||||
};
|
||||
var o = txps.toObj();
|
||||
o.txps.length.should.equal(2);
|
||||
});
|
||||
it('should filter sent txp', function() {
|
||||
var txps = TxProposals.fromObj({
|
||||
networkName:'livenet',
|
||||
walletId: '123a12',
|
||||
txps: [],
|
||||
});
|
||||
var d = JSON.parse(JSON.stringify(dummyProposal));
|
||||
d.sent=1;
|
||||
txps.txps = {
|
||||
'hola' : dummyProposal,
|
||||
'chau' : d,
|
||||
};
|
||||
var o = txps.toObj();
|
||||
o.txps.length.should.equal(1);
|
||||
});
|
||||
});
|
||||
describe.skip('#merge', function() {
|
||||
it('should merge', function() {
|
||||
var txps = new TxProposals();
|
||||
var d = dummyProposal;
|
||||
txps.merge(d.toObj(),{});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
var txpv1 = {
|
||||
"creator": "0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0",
|
||||
"createdTs": 1406310417996,
|
||||
"seenBy": {
|
||||
"0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0": 1406310417996,
|
||||
"02ba1599c64da4d80e25985be46c50e944b65f02e2b48c930528ce763d6710158f": 1406310418162
|
||||
},
|
||||
"signedBy": {
|
||||
"0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0": 1406310417996,
|
||||
"02ba1599c64da4d80e25985be46c50e944b65f02e2b48c930528ce763d6710158f": 1406310645549
|
||||
},
|
||||
"rejectedBy": {},
|
||||
"sentTs": 1406310645873,
|
||||
"sentTxid": "87296c50e8601437d63d556afb27c3b8e3819214be0a9d756d401a8286c0ec43",
|
||||
"inputChainPaths": ["m/45'/0/1/1"],
|
||||
"comment": "test 6",
|
||||
"builderObj": {
|
||||
"version": 1,
|
||||
"outs": [{
|
||||
"address": "mph66bnLvcn9KUSMrpikUBUZZkN2C1Z5tg",
|
||||
"amountSatStr": 100
|
||||
}],
|
||||
"utxos": [{
|
||||
"address": "2NEodmgBa4SH3VwE2asgW34vMYe8VThBZNo",
|
||||
"txid": "8f8deda12dad6248e655054632a27f6891ebb37e8d2b3dd1bff87e71fd451ac7",
|
||||
"vout": 1,
|
||||
"ts": 1406312717,
|
||||
"scriptPubKey": "a914ec7bce12d0e82a7d2b5431f6d89ca70af317f5a187",
|
||||
"amount": 0.009798,
|
||||
"confirmations": 0,
|
||||
"confirmationsFromCache": false
|
||||
}],
|
||||
"opts": {
|
||||
"spendUnconfirmed": true,
|
||||
"remainderOut": {
|
||||
"address": "2N74XAozMH3JB3XgeBkRvRw1J8TtfLTtvny"
|
||||
}
|
||||
},
|
||||
"scriptSig": ["00483045022100f167ad33b8bef4c65af8d19c1a849d1770cc8d1e35bffebe6b5459dcbe655c7802207b37370b308ba668fe19f8e8bc462c9fbdc6c67f79900670758d228d83ea96da014730440220038ad3f4cc7b0738b593454ec189913ae4b442bc83da153d68d9a0077bd1b09102202b5728a08f302e97de61ea37280b48ccdd575f0d235c22f5e0ecac6a4ab0f46401475221024739614847d5233a46913482c17c6860194ad78abb3bf47de46223047d8a0b5821024c6dc65a52c5eaaa080b96888091544f8ab8712caa7e0b69ea4b45f6f059557452ae"],
|
||||
"hashToScriptMap": {
|
||||
"2NEodmgBa4SH3VwE2asgW34vMYe8VThBZNo": "5221024739614847d5233a46913482c17c6860194ad78abb3bf47de46223047d8a0b5821024c6dc65a52c5eaaa080b96888091544f8ab8712caa7e0b69ea4b45f6f059557452ae"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ try {
|
|||
}
|
||||
var copayConfig = require('../config');
|
||||
var Wallet = require('../js/models/core/Wallet');
|
||||
var Structure = copay.Structure;
|
||||
var PrivateKey = copay.PrivateKey;
|
||||
var Storage = require('./mocks/FakeStorage');
|
||||
var Network = require('./mocks/FakeNetwork');
|
||||
var Blockchain = require('./mocks/FakeBlockchain');
|
||||
|
@ -19,22 +19,30 @@ var TransactionBuilder = bitcore.TransactionBuilder;
|
|||
var Transaction = bitcore.Transaction;
|
||||
var Address = bitcore.Address;
|
||||
|
||||
var config = {
|
||||
requiredCopayers: 3,
|
||||
totalCopayers: 5,
|
||||
spendUnconfirmed: true,
|
||||
reconnectDelay: 100,
|
||||
networkName: 'testnet',
|
||||
};
|
||||
|
||||
var getNewEpk = function() {
|
||||
return new PrivateKey({
|
||||
networkName: config.networkName,
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
}
|
||||
|
||||
var addCopayers = function(w) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
w.publicKeyRing.addCopayer();
|
||||
w.publicKeyRing.addCopayer(getNewEpk());
|
||||
}
|
||||
};
|
||||
|
||||
describe('Wallet model', function() {
|
||||
|
||||
var config = {
|
||||
requiredCopayers: 3,
|
||||
totalCopayers: 5,
|
||||
spendUnconfirmed: true,
|
||||
reconnectDelay: 100,
|
||||
networkName: 'testnet',
|
||||
};
|
||||
|
||||
it('should fail to create an instance', function() {
|
||||
(function() {
|
||||
new Wallet(config)
|
||||
|
@ -47,12 +55,11 @@ describe('Wallet model', function() {
|
|||
});
|
||||
|
||||
|
||||
var createW = function(netKey, N, conf) {
|
||||
var createW = function(N, conf) {
|
||||
|
||||
var c = JSON.parse(JSON.stringify(conf || config));
|
||||
if (!N) N = c.totalCopayers;
|
||||
|
||||
if (netKey) c.netKey = netKey;
|
||||
var mainPrivateKey = new copay.PrivateKey({
|
||||
networkName: config.networkName
|
||||
});
|
||||
|
@ -148,8 +155,7 @@ describe('Wallet model', function() {
|
|||
|
||||
var createW2 = function(privateKeys, N, conf) {
|
||||
if (!N) N = 3;
|
||||
var netKey = 'T0FbU2JLby0=';
|
||||
var w = createW(netKey, N, conf);
|
||||
var w = createW(N, conf);
|
||||
should.exist(w);
|
||||
|
||||
var pkr = w.publicKeyRing;
|
||||
|
@ -157,9 +163,9 @@ describe('Wallet model', function() {
|
|||
for (var i = 0; i < N - 1; i++) {
|
||||
if (privateKeys) {
|
||||
var k = privateKeys[i];
|
||||
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : null);
|
||||
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk());
|
||||
} else {
|
||||
pkr.addCopayer();
|
||||
pkr.addCopayer(getNewEpk());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,12 +218,12 @@ describe('Wallet model', function() {
|
|||
|
||||
var t = w.txProposals;
|
||||
var txp = t.txps[ntxid];
|
||||
Object.keys(txp._inputSignatures).length.should.equal(1);
|
||||
var tx = txp.builder.build();
|
||||
should.exist(tx);
|
||||
chai.expect(txp.comment).to.be.null;
|
||||
tx.isComplete().should.equal(false);
|
||||
Object.keys(txp.seenBy).length.should.equal(1);
|
||||
Object.keys(txp.signedBy).length.should.equal(1);
|
||||
});
|
||||
|
||||
it('#create with comment', function() {
|
||||
|
@ -434,19 +440,7 @@ describe('Wallet model', function() {
|
|||
var w = createW();
|
||||
var txp = {
|
||||
'txProposal': {
|
||||
creator: '02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96',
|
||||
createdTs: '2014-07-24T23:54:26.682Z',
|
||||
seenBy: {
|
||||
'02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96': 1406246066682
|
||||
},
|
||||
signedBy: {
|
||||
'02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96': 1406246066682
|
||||
},
|
||||
rejectedBy: {},
|
||||
sentTs: null,
|
||||
sentTxid: null,
|
||||
inputChainPaths: ['m/45\'/2/0/0'],
|
||||
comment: null,
|
||||
inputChainPaths: ['m/1'],
|
||||
builderObj: {
|
||||
version: 1,
|
||||
outs: [{
|
||||
|
@ -474,9 +468,13 @@ describe('Wallet model', function() {
|
|||
}
|
||||
};
|
||||
|
||||
var stub = sinon.stub(w.publicKeyRing,'copayersForPubkeys').returns(
|
||||
{'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509':'pepe'}
|
||||
);
|
||||
w._handleTxProposal('senderID', txp, true);
|
||||
Object.keys(w.txProposals.txps).length.should.equal(1);
|
||||
w.getTxProposals().length.should.equal(1);
|
||||
//stub.restore();
|
||||
});
|
||||
|
||||
var newId = '00bacacafe';
|
||||
|
@ -502,7 +500,8 @@ describe('Wallet model', function() {
|
|||
var w = createW();
|
||||
var r = w.getRegisteredCopayerIds();
|
||||
r.length.should.equal(1);
|
||||
w.publicKeyRing.addCopayer();
|
||||
w.publicKeyRing.addCopayer(getNewEpk());
|
||||
|
||||
r = w.getRegisteredCopayerIds();
|
||||
r.length.should.equal(2);
|
||||
r[0].should.not.equal(r[1]);
|
||||
|
@ -512,7 +511,7 @@ describe('Wallet model', function() {
|
|||
var w = createW();
|
||||
var r = w.getRegisteredPeerIds();
|
||||
r.length.should.equal(1);
|
||||
w.publicKeyRing.addCopayer();
|
||||
w.publicKeyRing.addCopayer(getNewEpk());
|
||||
r = w.getRegisteredPeerIds();
|
||||
r.length.should.equal(2);
|
||||
r[0].should.not.equal(r[1]);
|
||||
|
@ -642,10 +641,11 @@ describe('Wallet model', function() {
|
|||
});
|
||||
});
|
||||
it('should create & sign transaction from received funds', function(done) {
|
||||
this.timeout(10000);
|
||||
var w = cachedCreateW2();
|
||||
var pk = w.privateKey;
|
||||
w.privateKey = null;
|
||||
var k2 = new PrivateKey({
|
||||
networkName: config.networkName
|
||||
});
|
||||
|
||||
var w = createW2([k2]);
|
||||
var utxo = createUTXO(w);
|
||||
w.blockchain.fixUnspent(utxo);
|
||||
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||
|
@ -654,24 +654,36 @@ describe('Wallet model', function() {
|
|||
w.getTxProposals()[0].rejectedByUs.should.equal(false);
|
||||
done();
|
||||
});
|
||||
w.privateKey = pk;
|
||||
w.privateKey = k2;
|
||||
w.sign(ntxid, function(success) {
|
||||
success.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should create & reject transaction', function(done) {
|
||||
it('should fail to reject a signed transaction', function() {
|
||||
var w = cachedCreateW2();
|
||||
w.privateKey = null;
|
||||
var utxo = createUTXO(w);
|
||||
w.blockchain.fixUnspent(utxo);
|
||||
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||
w.on('txProposalsUpdated', function() {
|
||||
w.getTxProposals()[0].signedByUs.should.equal(false);
|
||||
w.getTxProposals()[0].rejectedByUs.should.equal(true);
|
||||
done();
|
||||
});
|
||||
(function() {
|
||||
w.reject(ntxid);
|
||||
}).should.throw('reject a signed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create & reject transaction', function(done) {
|
||||
var w = cachedCreateW2();
|
||||
var oldK = w.privateKey;
|
||||
var utxo = createUTXO(w);
|
||||
w.blockchain.fixUnspent(utxo);
|
||||
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||
var s = sinon.stub(w, 'getMyCopayerId').returns('213');
|
||||
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0);
|
||||
w.reject(ntxid);
|
||||
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1);
|
||||
w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1);
|
||||
s.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should create & sign & send a transaction', function(done) {
|
||||
|
@ -1021,32 +1033,91 @@ describe('Wallet model', function() {
|
|||
copayConfig.forceNetwork = backup;
|
||||
});
|
||||
});
|
||||
describe('_getKeymap', function() {
|
||||
var w = cachedCreateW();
|
||||
|
||||
describe('validate txProposals', function() {
|
||||
var a1 = 'n1pKARYYUnZwxBuGj3y7WqVDu6VLN7n971';
|
||||
var a2 = 'mtxYYJXZJmQc2iJRHQ4RZkfxU5K7TE2qMJ';
|
||||
var utxos = [{
|
||||
address: a1,
|
||||
txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1',
|
||||
vout: 1,
|
||||
scriptPubKey: Address.getScriptPubKeyFor(a1).serialize().toString('hex'),
|
||||
amount: 0.5,
|
||||
confirmations: 200
|
||||
}, {
|
||||
address: a2,
|
||||
txid: '88c4520ffd97ea565578afe0b40919120be704b36561c71ba4e450e83cb3c9fd',
|
||||
vout: 1,
|
||||
scriptPubKey: Address.getScriptPubKeyFor(a2).serialize().toString('hex'),
|
||||
amount: 0.5001,
|
||||
confirmations: 200
|
||||
}];
|
||||
var destAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1';
|
||||
var outs = [{
|
||||
address: destAddress,
|
||||
amount: 1.0
|
||||
}];
|
||||
it('should set keymap', function() {
|
||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() {
|
||||
return {
|
||||
'123': 'juan'
|
||||
};
|
||||
});
|
||||
var txp = {
|
||||
_inputSignatures: [
|
||||
['123']
|
||||
],
|
||||
inputChainPaths: ['/m/1'],
|
||||
};
|
||||
var map = w._getKeyMap(txp);
|
||||
Object.keys(map).length.should.equal(1);
|
||||
map['123'].should.equal('juan');
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('should throw if unmatched sigs', function() {
|
||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() {
|
||||
return {
|
||||
'123': 'juan'
|
||||
};
|
||||
});
|
||||
var txp = {
|
||||
_inputSignatures: [
|
||||
['234']
|
||||
],
|
||||
inputChainPaths: ['/m/1'],
|
||||
};
|
||||
(function() {
|
||||
w._getKeyMap(txp);
|
||||
}).should.throw('dont match know copayers');
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('should set keymap with multiple signatures', function() {
|
||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() {
|
||||
return {
|
||||
'123': 'juan',
|
||||
'234': 'pepe',
|
||||
};
|
||||
});
|
||||
var txp = {
|
||||
_inputSignatures: [
|
||||
['234', '123']
|
||||
],
|
||||
inputChainPaths: ['/m/1'],
|
||||
};
|
||||
var map = w._getKeyMap(txp);
|
||||
Object.keys(map).length.should.equal(2);
|
||||
map['123'].should.equal('juan');
|
||||
map['234'].should.equal('pepe');
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('should throw is one inputs has missing sigs', function() {
|
||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() {
|
||||
return {
|
||||
'123': 'juan',
|
||||
'234': 'pepe',
|
||||
};
|
||||
});
|
||||
var txp = {
|
||||
_inputSignatures: [
|
||||
['234', '123'],
|
||||
['234']
|
||||
],
|
||||
inputChainPaths: ['/m/1'],
|
||||
};
|
||||
(function() {
|
||||
w._getKeyMap(txp);
|
||||
}).should.throw('different sig');
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('_handleTxProposal', function() {
|
||||
var testValidate = function(response, result, done) {
|
||||
|
||||
var testValidate = function(signhash, result, done) {
|
||||
var w = cachedCreateW();
|
||||
var spy = sinon.spy();
|
||||
w.on('txProposalEvent', spy);
|
||||
|
@ -1054,47 +1125,149 @@ describe('Wallet model', function() {
|
|||
e.type.should.equal(result);
|
||||
done();
|
||||
});
|
||||
var opts = {};
|
||||
opts.signhash = signhash;
|
||||
var txb = new TransactionBuilder(opts)
|
||||
.setUnspent(utxos)
|
||||
.setOutputs(outs)
|
||||
.sign(['cVBtNonMyTydnS3NnZyipbduXo9KZfF1aUZ3uQHcvJB6UARZbiWG',
|
||||
'cRVF68hhZp1PUQCdjr2k6aVYb2cn6uabbySDPBizAJ3PXF7vDXTL'
|
||||
]);
|
||||
// txp.prototype.getId = function() {return 'aa'};
|
||||
var txp = {
|
||||
'txProposal': {
|
||||
'builderObj': txb.toObj()
|
||||
}
|
||||
dummy: 1
|
||||
};
|
||||
w._handleTxProposal('senderID', txp, true);
|
||||
var txp = {
|
||||
'txProposal': txp
|
||||
};
|
||||
var merge = sinon.stub(w.txProposals, 'merge', function() {
|
||||
if (response == 0) throw new Error();
|
||||
return {
|
||||
newCopayer: ['juan'],
|
||||
ntxid: 1,
|
||||
new: response == 1
|
||||
};
|
||||
});
|
||||
|
||||
w._handleTxProposal('senderID', txp);
|
||||
spy.callCount.should.equal(1);
|
||||
merge.restore();
|
||||
};
|
||||
|
||||
it('should validate for undefined', function(done) {
|
||||
it('should handle corrupt', function(done) {
|
||||
var result = 'corrupt';
|
||||
testValidate(0, result, done);
|
||||
});
|
||||
it('should handle new', function(done) {
|
||||
var result = 'new';
|
||||
var signhash;
|
||||
testValidate(signhash, result, done);
|
||||
testValidate(1, result, done);
|
||||
});
|
||||
it('should validate for SIGHASH_ALL', function(done) {
|
||||
var result = 'new';
|
||||
var signhash = Transaction.SIGHASH_ALL;
|
||||
testValidate(signhash, result, done);
|
||||
it('should handle signed', function(done) {
|
||||
var result = 'signed';
|
||||
testValidate(2, result, done);
|
||||
});
|
||||
it('should not validate for different SIGHASH_NONE', function(done) {
|
||||
var result = 'corrupt';
|
||||
var signhash = Transaction.SIGHASH_NONE;
|
||||
testValidate(signhash, result, done);
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('_handleReject', function() {
|
||||
it('should fails if unknown tx', function() {
|
||||
var w = cachedCreateW();
|
||||
(function() {
|
||||
w._handleReject(1, {
|
||||
ntxid: 1
|
||||
}, 1);
|
||||
}).should.throw('Unknown TXP');
|
||||
});
|
||||
it('should not validate for different SIGHASH_SINGLE', function(done) {
|
||||
var result = 'corrupt';
|
||||
var signhash = Transaction.SIGHASH_SINGLE;
|
||||
testValidate(signhash, result, done);
|
||||
it('should fail to reject a signed tx', function() {
|
||||
var w = cachedCreateW();
|
||||
w.txProposals.txps['qwerty'] = {
|
||||
signedBy: {
|
||||
john: 1
|
||||
}
|
||||
};
|
||||
(function() {
|
||||
w._handleReject('john', {
|
||||
ntxid: 'qwerty'
|
||||
}, 1);
|
||||
}).should.throw('already signed');
|
||||
});
|
||||
it('should not validate for different SIGHASH_ANYONECANPAY', function(done) {
|
||||
var result = 'corrupt';
|
||||
var signhash = Transaction.SIGHASH_ANYONECANPAY;
|
||||
testValidate(signhash, result, done);
|
||||
it('should reject a tx', function() {
|
||||
var w = cachedCreateW();
|
||||
|
||||
function txp() {
|
||||
this.ok = 0;
|
||||
this.signedBy = {};
|
||||
};
|
||||
txp.prototype.setRejected = function() {
|
||||
this.ok = 1;
|
||||
};
|
||||
txp.prototype.toObj = function() {};
|
||||
|
||||
var spy1 = sinon.spy(w, 'store');
|
||||
var spy2 = sinon.spy(w, 'emit');
|
||||
w.txProposals.txps['qwerty'] = new txp();
|
||||
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
||||
w._handleReject('john', {
|
||||
ntxid: 'qwerty'
|
||||
}, 1);
|
||||
w.txProposals.txps['qwerty'].ok.should.equal(1);
|
||||
spy1.calledOnce.should.equal(true);
|
||||
spy2.callCount.should.equal(2);
|
||||
spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']);
|
||||
spy2.secondCall.args.should.deep.equal(['txProposalEvent', {
|
||||
type: 'rejected',
|
||||
cId: 'john',
|
||||
txId: 'qwerty',
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('_handleSeen', function() {
|
||||
it('should fails if unknown tx', function() {
|
||||
var w = cachedCreateW();
|
||||
(function() {
|
||||
w._handleReject(1, {
|
||||
ntxid: 1
|
||||
}, 1);
|
||||
}).should.throw('Unknown TXP');
|
||||
});
|
||||
it('should set seen a tx', function() {
|
||||
var w = cachedCreateW();
|
||||
|
||||
function txp() {
|
||||
this.ok = 0;
|
||||
this.signedBy = {};
|
||||
};
|
||||
txp.prototype.setSeen = function() {
|
||||
this.ok = 1;
|
||||
};
|
||||
txp.prototype.toObj = function() {};
|
||||
|
||||
var spy1 = sinon.spy(w, 'store');
|
||||
var spy2 = sinon.spy(w, 'emit');
|
||||
w.txProposals.txps['qwerty'] = new txp();
|
||||
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
||||
w._handleSeen('john', {
|
||||
ntxid: 'qwerty'
|
||||
}, 1);
|
||||
w.txProposals.txps['qwerty'].ok.should.equal(1);
|
||||
spy1.calledOnce.should.equal(true);
|
||||
spy2.callCount.should.equal(2);
|
||||
spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']);
|
||||
spy2.secondCall.args.should.deep.equal(['txProposalEvent', {
|
||||
type: 'seen',
|
||||
cId: 'john',
|
||||
txId: 'qwerty',
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
it('getNetwork', function() {
|
||||
var w = cachedCreateW();
|
||||
var n = w.getNetwork();
|
||||
n.maxPeers.should.equal(5);
|
||||
should.exist(n.networkNonce);
|
||||
});
|
||||
|
||||
it('#disconnect', function() {
|
||||
var w = cachedCreateW();
|
||||
var spy1 = sinon.spy(w.network, 'disconnect');
|
||||
w.disconnect();
|
||||
spy1.callCount.should.equal(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -82,9 +82,9 @@ describe('Insight model', function() {
|
|||
|
||||
|
||||
sinon
|
||||
.stub(http, 'request')
|
||||
.returns(req)
|
||||
.yields(request);
|
||||
.stub(http, 'request')
|
||||
.returns(req)
|
||||
.yields(request);
|
||||
|
||||
i.getUnspent(['2MuD5LnZSViZZYwZbpVsagwrH8WWvCztdmV', '2NBSLoMvsHsf2Uv3LA17zV4beH6Gze6RovA'], function(e, ret) {
|
||||
should.not.exist(e);
|
||||
|
@ -113,9 +113,9 @@ describe('Insight model', function() {
|
|||
req.end = function() {};
|
||||
|
||||
sinon
|
||||
.stub(http, 'request')
|
||||
.returns(req)
|
||||
.yields(request);
|
||||
.stub(http, 'request')
|
||||
.returns(req)
|
||||
.yields(request);
|
||||
|
||||
i.sendRawTransaction(rawtx, function(a) {
|
||||
should.exist(a);
|
||||
|
@ -200,5 +200,33 @@ describe('Insight model', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#checkSentTx", function() {
|
||||
it('should return true if Tx is found', function(done) {
|
||||
var w = new Insight();
|
||||
w._request = sinon.stub().yields(null, {
|
||||
txid: "414142",
|
||||
});
|
||||
var tx = function() {};
|
||||
tx.prototype.getHash = function(){return new Buffer('BAA')};
|
||||
w.checkSentTx(new tx(), function(err, ret) {
|
||||
should.not.exist(err);
|
||||
ret.should.equal('414142');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should return false if Tx is not found', function(done) {
|
||||
var w = new Insight();
|
||||
w._request = sinon.stub().yields(null, {
|
||||
txid: "414142",
|
||||
});
|
||||
var tx = function() {};
|
||||
tx.prototype.getHash = function(){return new Buffer('ABC')};
|
||||
w.checkSentTx(new tx(), function(err, ret) {
|
||||
should.not.exist(err);
|
||||
ret.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -5,6 +5,15 @@ var should = chai.should();
|
|||
var PrivateKey = require('../js/models/core/PrivateKey');
|
||||
var PublicKeyRing = require('../js/models/core/PublicKeyRing');
|
||||
|
||||
var getNewEpk = function() {
|
||||
return new PrivateKey({
|
||||
networkName: 'livenet',
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
}
|
||||
|
||||
|
||||
describe('Performance tests', function() {
|
||||
describe('PrivateKey', function() {
|
||||
it('should optimize BIP32 private key gen time with cache', function() {
|
||||
|
@ -43,7 +52,7 @@ describe('Performance tests', function() {
|
|||
requiredCopayers: M
|
||||
});
|
||||
for (var i = 0; i < N; i++) {
|
||||
pkr1.addCopayer(); // add new random ext public key
|
||||
pkr1.addCopayer(getNewEpk()); // add new random ext public key
|
||||
}
|
||||
var generateN = 5;
|
||||
var generated = [];
|
||||
|
|
Loading…
Reference in New Issue