mirror of https://github.com/BTCPrivate/copay.git
txproposal basic features and test
This commit is contained in:
parent
4f2499e13c
commit
e0993c90a6
|
@ -14,17 +14,6 @@ var buffertools = bitcore.buffertools;
|
||||||
var Storage = imports.Storage || require('./Storage');
|
var Storage = imports.Storage || require('./Storage');
|
||||||
var storage = Storage.default();
|
var storage = Storage.default();
|
||||||
|
|
||||||
/*
|
|
||||||
* This follow Electrum convetion, as described in
|
|
||||||
* https://bitcointalk.org/index.php?topic=274182.0
|
|
||||||
*
|
|
||||||
* We should probably adopt the next standard once it's ready, as discussed in:
|
|
||||||
* http://sourceforge.net/p/bitcoin/mailman/message/32148600/
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PUBLIC_BRANCH = 'm/0/';
|
|
||||||
var CHANGE_BRANCH = 'm/1/';
|
|
||||||
|
|
||||||
function PublicKeyRing(opts) {
|
function PublicKeyRing(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -44,6 +33,22 @@ function PublicKeyRing(opts) {
|
||||||
this.addressIndex=0;
|
this.addressIndex=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This follow Electrum convetion, as described in
|
||||||
|
* https://bitcointalk.org/index.php?topic=274182.0
|
||||||
|
*
|
||||||
|
* We should probably adopt the next standard once it's ready, as discussed in:
|
||||||
|
* http://sourceforge.net/p/bitcoin/mailman/message/32148600/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
PublicKeyRing.PublicBranch = function (index) {
|
||||||
|
return 'm/0/'+index;
|
||||||
|
};
|
||||||
|
|
||||||
|
PublicKeyRing.ChangeBranch = function (index) {
|
||||||
|
return 'm/1/'+index;
|
||||||
|
};
|
||||||
|
|
||||||
PublicKeyRing.getRandomId = function () {
|
PublicKeyRing.getRandomId = function () {
|
||||||
return buffertools.toHex(coinUtil.generateNonce());
|
return buffertools.toHex(coinUtil.generateNonce());
|
||||||
|
@ -177,7 +182,7 @@ PublicKeyRing.prototype.getPubKeys = function (index, isChange) {
|
||||||
var pubKeys = [];
|
var pubKeys = [];
|
||||||
var l = this.copayersBIP32.length;
|
var l = this.copayersBIP32.length;
|
||||||
for(var i=0; i<l; i++) {
|
for(var i=0; i<l; i++) {
|
||||||
var path = (isChange ? CHANGE_BRANCH : PUBLIC_BRANCH) + index;
|
var path = isChange ? PublicKeyRing.ChangeBranch(index) : PublicKeyRing.PublicBranch(index);
|
||||||
var bip32 = this.copayersBIP32[i].derive(path);
|
var bip32 = this.copayersBIP32[i].derive(path);
|
||||||
pubKeys[i] = bip32.eckey.public;
|
pubKeys[i] = bip32.eckey.public;
|
||||||
}
|
}
|
||||||
|
@ -201,6 +206,7 @@ PublicKeyRing.prototype.getRedeemScript = function (index, isChange) {
|
||||||
return script;
|
return script;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
PublicKeyRing.prototype.getAddress = function (index, isChange) {
|
PublicKeyRing.prototype.getAddress = function (index, isChange) {
|
||||||
this._checkIndexRange(index, isChange);
|
this._checkIndexRange(index, isChange);
|
||||||
|
|
||||||
|
@ -238,6 +244,21 @@ PublicKeyRing.prototype.getAddresses = function() {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PublicKeyRing.prototype.getRedeemScriptMap = function () {
|
||||||
|
var ret = {};
|
||||||
|
|
||||||
|
for (var i=0; i<this.changeAddressIndex; i++) {
|
||||||
|
ret[this.getAddress(i,true)] = this.getRedeemScript(i,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0; i<this.addressIndex; i++) {
|
||||||
|
ret[this.getAddress(i)] = this.getRedeemScript(i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PublicKeyRing.prototype._checkInPRK = function(inPKR, ignoreId) {
|
PublicKeyRing.prototype._checkInPRK = function(inPKR, ignoreId) {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
var imports = require('soop').imports();
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var coinUtil = bitcore.util;
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var Builder = bitcore.TransactionBuilder;
|
||||||
|
var buffertools = bitcore.buffertools;
|
||||||
|
|
||||||
|
var Storage = imports.Storage || require('./Storage');
|
||||||
|
var storage = Storage.default();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This follow Electrum convetion, as described in
|
||||||
|
* https://bitcointalk.org/index.php?topic=274182.0
|
||||||
|
*
|
||||||
|
* We should probably adopt the next standard once it's ready, as discussed in:
|
||||||
|
* http://sourceforge.net/p/bitcoin/mailman/message/32148600/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var PUBLIC_BRANCH = 'm/0/';
|
||||||
|
var CHANGE_BRANCH = 'm/1/';
|
||||||
|
|
||||||
|
function TxProposals(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
|
this.network = opts.networkName === 'livenet' ?
|
||||||
|
bitcore.networks.livenet : bitcore.networks.testnet;
|
||||||
|
|
||||||
|
this.publicKeyRing = opts.publicKeyRing;
|
||||||
|
this.requiredCopayers = opts.requiredCopayers || 3;
|
||||||
|
this.txs = [];
|
||||||
|
this.dirty = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TxProposals.prototype.list = function() {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
this.txs.forEach(function(tx) {
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposals.prototype.create = function(toAddress, amountSat, utxos, onePrivKey) {
|
||||||
|
var pkr = this.publicKeyRing;
|
||||||
|
|
||||||
|
if (! pkr.isComplete() ) {
|
||||||
|
throw new Error('publicKeyRing is not complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
remainderOut: { address: pkr.generateAddress(true) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var b = new Builder(opts)
|
||||||
|
.setUnspent(utxos)
|
||||||
|
.setHashToScriptMap(pkr.getRedeemScriptMap())
|
||||||
|
.setOutputs([{address: toAddress, amountSat: amountSat}])
|
||||||
|
;
|
||||||
|
|
||||||
|
if (onePrivKey) {
|
||||||
|
b.sign([onePrivKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tx = b.build();
|
||||||
|
var txHex = tx.serialize();
|
||||||
|
this.txs.push(txHex);
|
||||||
|
return txHex;
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposals.prototype.sign = function(index) {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = require('soop')(TxProposals);
|
|
@ -268,6 +268,24 @@ describe('PublicKeyRing model', function() {
|
||||||
}
|
}
|
||||||
w.isComplete().should.equal(true);
|
w.isComplete().should.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('#getRedeemScriptMap check tests', function () {
|
||||||
|
var k = createW();
|
||||||
|
var w = k.w;
|
||||||
|
|
||||||
|
for(var i=0; i<2; i++)
|
||||||
|
w.generateAddress(true);
|
||||||
|
for(var i=0; i<3; i++)
|
||||||
|
w.generateAddress(false);
|
||||||
|
|
||||||
|
var m = w.getRedeemScriptMap();
|
||||||
|
Object.keys(m).length.should.equal(5);
|
||||||
|
Object.keys(m).forEach(function (k) {
|
||||||
|
should.exist(m[k]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
'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 BIP32 = bitcore.BIP32;
|
||||||
|
var bignum = bitcore.bignum;
|
||||||
|
var networks = bitcore.networks;
|
||||||
|
var copay = copay || require('../copay');
|
||||||
|
var fakeStorage = copay.FakeStorage;
|
||||||
|
var TxProposals = copay.TxProposals || require('../js/models/TxProposal');
|
||||||
|
var PublicKeyRing = (typeof process.versions === 'undefined') ? copay.PublicKeyRing :
|
||||||
|
require('soop').load('../js/models/PublicKeyRing', {Storage: fakeStorage});
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
networkName:'livenet',
|
||||||
|
};
|
||||||
|
|
||||||
|
var unspentTest = [
|
||||||
|
{
|
||||||
|
"address": "dummy",
|
||||||
|
"scriptPubKey": "dummy",
|
||||||
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||||
|
"vout": 1,
|
||||||
|
"amount": 10,
|
||||||
|
"confirmations":7
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var createW = function (bip32s) {
|
||||||
|
var w = new PublicKeyRing(config);
|
||||||
|
should.exist(w);
|
||||||
|
|
||||||
|
for(var i=0; i<5; i++) {
|
||||||
|
if (bip32s) {
|
||||||
|
var b=bip32s[i];
|
||||||
|
w.addCopayer(b?b.extendedPrivateKeyString():null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
w.addCopayer();
|
||||||
|
}
|
||||||
|
w.generateAddress(true);
|
||||||
|
w.generateAddress(true);
|
||||||
|
w.generateAddress(true);
|
||||||
|
w.generateAddress(false);
|
||||||
|
w.generateAddress(false);
|
||||||
|
|
||||||
|
return w;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
describe('TxProposals model', function() {
|
||||||
|
|
||||||
|
it('should create an instance', function () {
|
||||||
|
var w = new TxProposals({
|
||||||
|
networkName: config.networkName
|
||||||
|
});
|
||||||
|
should.exist(w);
|
||||||
|
w.network.name.should.equal('livenet');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#create', function () {
|
||||||
|
var w = new TxProposals({
|
||||||
|
networkName: config.networkName,
|
||||||
|
publicKeyRing: createW(),
|
||||||
|
});
|
||||||
|
should.exist(w);
|
||||||
|
w.network.name.should.equal('livenet');
|
||||||
|
|
||||||
|
unspentTest[0].address = w.publicKeyRing.getAddress(1, true);
|
||||||
|
unspentTest[0].scriptPubKey = w.publicKeyRing.getRedeemScript(1, true).getBuffer();
|
||||||
|
|
||||||
|
var txHex = w.create(
|
||||||
|
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||||
|
bignum('123456789'),
|
||||||
|
unspentTest
|
||||||
|
);
|
||||||
|
should.exist(txHex);
|
||||||
|
|
||||||
|
var t2=new Transaction();
|
||||||
|
t2.parse(txHex);
|
||||||
|
t2.isComplete().should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#create. Singing with derivate keys', function () {
|
||||||
|
|
||||||
|
var oneBIP32 = new BIP32(config.networkName);
|
||||||
|
|
||||||
|
var w = new TxProposals({
|
||||||
|
networkName: config.networkName,
|
||||||
|
publicKeyRing: createW([oneBIP32]),
|
||||||
|
});
|
||||||
|
should.exist(w);
|
||||||
|
w.network.name.should.equal('livenet');
|
||||||
|
|
||||||
|
|
||||||
|
for (var isChange=0; isChange<2; isChange++) {
|
||||||
|
for (var index=0; index<3; index++) {
|
||||||
|
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange);
|
||||||
|
unspentTest[0].scriptPubKey = w.publicKeyRing.getRedeemScript(index, isChange).getBuffer();
|
||||||
|
|
||||||
|
var derivedBip32 = oneBIP32.derive( isChange ? PublicKeyRing.ChangeBranch(index):PublicKeyRing.PublicBranch(index) );
|
||||||
|
var wk = new WalletKey({network: networks.livenet});
|
||||||
|
var p = derivedBip32.eckey.private.toString('hex');
|
||||||
|
wk.fromObj({priv: p});
|
||||||
|
|
||||||
|
var txHex = w.create(
|
||||||
|
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
|
||||||
|
bignum('123456789'),
|
||||||
|
unspentTest,
|
||||||
|
wk
|
||||||
|
);
|
||||||
|
should.exist(txHex);
|
||||||
|
var t2=new Transaction();
|
||||||
|
t2.parse(txHex);
|
||||||
|
t2.isComplete().should.equal(false);
|
||||||
|
t2.countInputMissingSignatures(0).should.equal(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue