From 9665f23bc50bb99cd8caf48c0508a40fa6f818ef Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 17 Feb 2015 11:52:29 -0300 Subject: [PATCH 1/4] add proposal check on sign --- lib/client/Verifier.js | 10 ++++++--- lib/client/api.js | 5 ++++- lib/server.js | 2 +- test/integration/clientApi.js | 40 +++++++++++++++++++++++++++++++++++ test/integration/server.js | 4 ++-- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index 42ae592..797233e 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -17,9 +17,6 @@ Verifier.checkAddress = function(data, address) { return (local.address == address.address && JSON.stringify(local.publicKeys) == JSON.stringify(address.publicKeys)); }; - -// - Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { var walletPubKey = Bitcore.PrivateKey.fromString(walletPrivKey).toPublicKey().toString(); @@ -56,4 +53,11 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { }; +Verifier.checkTxProposal = function(data, txp) { + var header = txp.toAddress + '|' + txp.amount + '|' + (txp.message || ''); + if (!SignUtils.verify(header, txp.proposalSignature, data.signingPubKey)) return false; + + return Verifier.checkAddress(data, txp.changeAddress); +}; + module.exports = Verifier; diff --git a/lib/client/api.js b/lib/client/api.js index 066308d..3f0cd56 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -62,7 +62,6 @@ function API(opts) { }; - API.prototype._tryToComplete = function(data, cb) { var self = this; @@ -329,6 +328,10 @@ API.prototype.signTxProposal = function(txp, cb) { function(err, data) { if (err) return cb(err); + if (!Verifier.checkTxProposal(data, txp)) { + return cb(new ServerCompromisedError('Server sent fake transaction proposal')); + } + //Derive proper key to sign, for each input var privs = [], diff --git a/lib/server.js b/lib/server.js index ac1373d..b15ccf8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -464,7 +464,7 @@ CopayServer.prototype.createTx = function(opts, cb) { if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); var copayer = wallet.getCopayer(self.copayerId); - var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; + var msg = opts.toAddress + '|' + opts.amount + '|' + (opts.message || ''); if (!self._verifySignature(msg, opts.proposalSignature, copayer.signingPubKey)) return cb(new ClientError('Invalid proposal signature')); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 07f29fd..699a777 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -126,6 +126,46 @@ describe(' client API ', function() { client.request = request; + client.createAddress(function(err, x) { + should.not.exist(err); + x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); + done(); + }); + }) + it(' should detect fake addresses ', function(done) { + var response = { + createdOn: 1424105995, + address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + path: 'm/2147483647/0/8', + publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] + }; + var request = sinon.mock().yields(null, { + statusCode: 200 + }, response); + client.request = request; + client.createAddress(function(err, x) { + err.code.should.equal('SERVERCOMPROMISED'); + err.message.should.contain('fake address'); + done(); + }); + }) + }) + + describe(' createAddress ', function() { + it(' should check address ', function(done) { + + var response = { + createdOn: 1424105995, + address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + path: 'm/2147483647/0/7', + publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] + }; + var request = sinon.mock().yields(null, { + statusCode: 200 + }, response); + client.request = request; + + client.createAddress(function(err, x) { should.not.exist(err); x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); diff --git a/test/integration/server.js b/test/integration/server.js index 85b6438..ae8ee1c 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -176,7 +176,7 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { message: message, proposalSignature: null, }; - var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; + var msg = opts.toAddress + '|' + opts.amount + '|' + (opts.message || ''); try { opts.proposalSignature = SignUtils.sign(msg, signingKey); } catch (ex) {} @@ -706,7 +706,7 @@ describe('Copay server', function() { }); }); - it('should fail to create tx for address invalid address', function(done) { + it('should fail to create tx for invalid address', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { helpers.stubBlockExplorer(server, utxos); var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey); From b1a29f5005d18e88016448535a261047692fbf9a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 17 Feb 2015 12:39:11 -0300 Subject: [PATCH 2/4] check header & change address --- lib/client/Verifier.js | 3 +- test/integration/clientApi.js | 102 +++++++++++++++++----------------- 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index 797233e..8ad79e3 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -55,7 +55,8 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { Verifier.checkTxProposal = function(data, txp) { var header = txp.toAddress + '|' + txp.amount + '|' + (txp.message || ''); - if (!SignUtils.verify(header, txp.proposalSignature, data.signingPubKey)) return false; + var signingPubKey = Bitcore.PrivateKey.fromString(data.signingPrivKey).toPublicKey().toString(); + if (!SignUtils.verify(header, txp.proposalSignature, signingPubKey)) return false; return Verifier.checkAddress(data, txp.changeAddress); }; diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 699a777..441665f 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -9,12 +9,10 @@ var API = Client.API; var Bitcore = require('bitcore'); var TestData = require('./clienttestdata'); -describe(' client API ', function() { - +describe('client API ', function() { var client; beforeEach(function() { - var fsmock = {};; fsmock.readFile = sinon.mock().yields(null, JSON.stringify(TestData.storage.wallet11)); fsmock.writeFile = sinon.mock().yields(); @@ -27,7 +25,7 @@ describe(' client API ', function() { }); }); - describe(' _tryToComplete ', function() { + describe('#_tryToComplete ', function() { it('should complete a wallet ', function(done) { var request = sinon.stub(); @@ -45,7 +43,7 @@ describe(' client API ', function() { should.not.exist(err); done(); }); - }) + }); it('should handle incomple wallets', function(done) { @@ -62,7 +60,7 @@ describe(' client API ', function() { err.should.contain('Incomplete'); done(); }); - }) + }); it('should reject wallets with bad signatures', function(done) { var request = sinon.stub(); @@ -77,7 +75,8 @@ describe(' client API ', function() { err.should.contain('verified'); done(); }); - }) + }); + it('should reject wallets with missing signatures ', function(done) { var request = sinon.stub(); // Wallet request @@ -91,7 +90,7 @@ describe(' client API ', function() { err.should.contain('verified'); done(); }); - }) + }); it('should reject wallets missing caller"s pubkey', function(done) { var request = sinon.stub(); @@ -106,13 +105,11 @@ describe(' client API ', function() { err.should.contain('verified'); done(); }); - }) - - + }); }); - describe(' createAddress ', function() { - it(' should check address ', function(done) { + describe('#createAddress ', function() { + it('should check address ', function(done) { var response = { createdOn: 1424105995, @@ -131,8 +128,9 @@ describe(' client API ', function() { x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); done(); }); - }) - it(' should detect fake addresses ', function(done) { + }); + + it('should detect fake addresses ', function(done) { var response = { createdOn: 1424105995, address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', @@ -148,46 +146,48 @@ describe(' client API ', function() { err.message.should.contain('fake address'); done(); }); - }) - }) + }); + }); - describe(' createAddress ', function() { - it(' should check address ', function(done) { + describe('#signTxProposal ', function() { + it.skip('should sign tx proposal', function(done) {}); - var response = { - createdOn: 1424105995, - address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', - path: 'm/2147483647/0/7', - publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] + it('should detect fake tx proposal signature', function(done) { + var txp = { + toAddress: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + amount: 100000, + message: 'some message', + proposalSignature: 'dummy', + changeAddress: { + address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + path: 'm/2147483647/0/7', + publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] + }, }; - var request = sinon.mock().yields(null, { - statusCode: 200 - }, response); - client.request = request; - - - client.createAddress(function(err, x) { - should.not.exist(err); - x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); - done(); - }); - }) - it(' should detect fake addresses ', function(done) { - var response = { - createdOn: 1424105995, - address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', - path: 'm/2147483647/0/8', - publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] - }; - var request = sinon.mock().yields(null, { - statusCode: 200 - }, response); - client.request = request; - client.createAddress(function(err, x) { + client.signTxProposal(txp, function(err) { err.code.should.equal('SERVERCOMPROMISED'); - err.message.should.contain('fake address'); + err.message.should.contain('fake transaction proposal'); done(); }); - }) - }) + }); + + it('should detect fake tx proposal change address', function(done) { + var txp = { + toAddress: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + amount: 100000, + message: 'some message', + proposalSignature: '3045022100e2d9ef7ed592217ab2256fdcf9627075f35ecdf431dde8c9a9c9422b7b1fb00f02202bc8ce066db4401bdbafb2492c3138debbc69c4c01db50d8c22a227e744c8906', + changeAddress: { + address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', + path: 'm/2147483647/0/8', + publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] + }, + }; + client.signTxProposal(txp, function(err) { + err.code.should.equal('SERVERCOMPROMISED'); + err.message.should.contain('fake transaction proposal'); + done(); + }); + }); + }); }); From 5161d96dd6061f542cdadbd0fd9f71fcdc0e0478 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 17 Feb 2015 13:11:14 -0300 Subject: [PATCH 3/4] rename BitcoinUtils -> WalletUtils --- lib/client/Verifier.js | 8 ++++---- lib/model/wallet.js | 4 ++-- lib/server.js | 5 +++-- lib/{bitcoinutils.js => walletutils.js} | 11 +++++++---- test/integration/server.js | 5 +++-- 5 files changed, 19 insertions(+), 14 deletions(-) rename lib/{bitcoinutils.js => walletutils.js} (64%) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index 8ad79e3..dd01585 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -3,7 +3,7 @@ var _ = require('lodash'); var log = require('npmlog'); var Bitcore = require('bitcore'); -var BitcoinUtils = require('../bitcoinutils') +var WalletUtils = require('../walletutils') var SignUtils = require('../signutils'); /* @@ -13,7 +13,7 @@ var SignUtils = require('../signutils'); function Verifier(opts) {}; Verifier.checkAddress = function(data, address) { - var local = BitcoinUtils.deriveAddress(data.publicKeyRing, address.path, data.m, data.network); + var local = WalletUtils.deriveAddress(data.publicKeyRing, address.path, data.m, data.network); return (local.address == address.address && JSON.stringify(local.publicKeys) == JSON.stringify(address.publicKeys)); }; @@ -54,9 +54,9 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { Verifier.checkTxProposal = function(data, txp) { - var header = txp.toAddress + '|' + txp.amount + '|' + (txp.message || ''); + var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.message); var signingPubKey = Bitcore.PrivateKey.fromString(data.signingPrivKey).toPublicKey().toString(); - if (!SignUtils.verify(header, txp.proposalSignature, signingPubKey)) return false; + if (!SignUtils.verify(hash, txp.proposalSignature, signingPubKey)) return false; return Verifier.checkAddress(data, txp.changeAddress); }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 6c91cdd..84c9cfa 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -8,7 +8,7 @@ var Uuid = require('uuid'); var Address = require('./address'); var Copayer = require('./copayer'); var AddressManager = require('./addressmanager'); -var BitcoinUtils = require('../bitcoinutils'); +var WalletUtils = require('../walletutils'); var VERSION = '1.0.0'; @@ -117,7 +117,7 @@ Wallet.prototype.createAddress = function(isChange) { $.checkState(this.isComplete()); var path = this.addressManager.getNewAddressPath(isChange); - return new Address(BitcoinUtils.deriveAddress(this.publicKeyRing, path, this.m, this.network)); + return new Address(WalletUtils.deriveAddress(this.publicKeyRing, path, this.m, this.network)); }; diff --git a/lib/server.js b/lib/server.js index b15ccf8..31b617a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -17,6 +17,7 @@ var Explorers = require('bitcore-explorers'); var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); +var WalletUtils = require('./walletutils'); var SignUtils = require('./signutils'); var Wallet = require('./model/wallet'); @@ -464,8 +465,8 @@ CopayServer.prototype.createTx = function(opts, cb) { if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); var copayer = wallet.getCopayer(self.copayerId); - var msg = opts.toAddress + '|' + opts.amount + '|' + (opts.message || ''); - if (!self._verifySignature(msg, opts.proposalSignature, copayer.signingPubKey)) + var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); + if (!self._verifySignature(hash, opts.proposalSignature, copayer.signingPubKey)) return cb(new ClientError('Invalid proposal signature')); var toAddress; diff --git a/lib/bitcoinutils.js b/lib/walletutils.js similarity index 64% rename from lib/bitcoinutils.js rename to lib/walletutils.js index 2a29ea1..57ecf48 100644 --- a/lib/bitcoinutils.js +++ b/lib/walletutils.js @@ -1,12 +1,11 @@ - var _ = require('lodash'); var Bitcore = require('bitcore'); var BitcoreAddress = Bitcore.Address; -function BitcoinUtils () {}; +function WalletUtils() {}; -BitcoinUtils.deriveAddress = function(publicKeyRing, path, m, network) { +WalletUtils.deriveAddress = function(publicKeyRing, path, m, network) { var publicKeys = _.map(publicKeyRing, function(xPubKey) { var xpub = new Bitcore.HDPublicKey(xPubKey); @@ -22,4 +21,8 @@ BitcoinUtils.deriveAddress = function(publicKeyRing, path, m, network) { }; }; -module.exports = BitcoinUtils; +WalletUtils.getProposalHash = function(toAddress, amount, message) { + return toAddress + '|' + amount + '|' + (message || ''); +}; + +module.exports = WalletUtils; diff --git a/test/integration/server.js b/test/integration/server.js index ae8ee1c..fa9d7b2 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -11,6 +11,7 @@ var memdown = require('memdown'); var Bitcore = require('bitcore'); var Utils = require('../../lib/utils'); +var WalletUtils = require('../../lib/walletutils'); var SignUtils = require('../../lib/signutils'); var Storage = require('../../lib/storage'); @@ -176,9 +177,9 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { message: message, proposalSignature: null, }; - var msg = opts.toAddress + '|' + opts.amount + '|' + (opts.message || ''); + var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); try { - opts.proposalSignature = SignUtils.sign(msg, signingKey); + opts.proposalSignature = SignUtils.sign(hash, signingKey); } catch (ex) {} return opts; From bea3cf7a6532e92667ec09333f01a5b372d25d14 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 17 Feb 2015 16:42:47 -0300 Subject: [PATCH 4/4] merge SignUtils into WalletUtils --- lib/client/Verifier.js | 5 ++-- lib/client/api.js | 8 ++--- lib/server.js | 3 +- lib/signutils.js | 43 --------------------------- lib/walletutils.js | 37 +++++++++++++++++++++-- test/integration/server.js | 5 ++-- test/{signutils.js => walletutils.js} | 33 ++++++++++---------- 7 files changed, 59 insertions(+), 75 deletions(-) delete mode 100644 lib/signutils.js rename test/{signutils.js => walletutils.js} (68%) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index dd01585..b671c39 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -4,7 +4,6 @@ var log = require('npmlog'); var Bitcore = require('bitcore'); var WalletUtils = require('../walletutils') -var SignUtils = require('../signutils'); /* * Checks data given by the server @@ -36,7 +35,7 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { } // Not signed pub keys - if (!SignUtils.verify(copayer.xPubKey, copayer.xPubKeySignature, walletPubKey)) { + if (!WalletUtils.verifyMessage(copayer.xPubKey, copayer.xPubKeySignature, walletPubKey)) { log.error('Invalid signatures in server response'); error = true; } @@ -56,7 +55,7 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { Verifier.checkTxProposal = function(data, txp) { var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.message); var signingPubKey = Bitcore.PrivateKey.fromString(data.signingPrivKey).toPublicKey().toString(); - if (!SignUtils.verify(hash, txp.proposalSignature, signingPubKey)) return false; + if (!WalletUtils.verifyMessage(hash, txp.proposalSignature, signingPubKey)) return false; return Verifier.checkAddress(data, txp.changeAddress); }; diff --git a/lib/client/api.js b/lib/client/api.js index 3f0cd56..c3e6428 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -9,7 +9,7 @@ var request = require('request') log.debug = log.verbose; var Bitcore = require('bitcore') -var SignUtils = require('../signutils'); +var WalletUtils = require('../walletutils'); var Verifier = require('./verifier'); var ServerCompromisedError = require('./servercompromisederror') @@ -17,7 +17,7 @@ var BASE_URL = 'http://localhost:3001/copay/api'; function _createProposalOpts(opts, signingKey) { var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; - opts.proposalSignature = SignUtils.sign(msg, signingKey); + opts.proposalSignature = WalletUtils.signMessage(msg, signingKey); return opts; }; @@ -42,7 +42,7 @@ function _parseError(body) { function _signRequest(method, url, args, privKey) { var message = method.toLowerCase() + '|' + url + '|' + JSON.stringify(args); - return SignUtils.sign(message, privKey); + return WalletUtils.signMessage(message, privKey); }; function _createXPrivKey(network) { @@ -208,7 +208,7 @@ API.prototype._joinWallet = function(data, secret, copayerName, cb) { data.xPrivKey = _createXPrivKey(network); var xPubKey = new Bitcore.HDPublicKey(data.xPrivKey); - var xPubKeySignature = SignUtils.sign(xPubKey.toString(), walletPrivKey); + var xPubKeySignature = WalletUtils.signMessage(xPubKey.toString(), walletPrivKey); var signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey; var args = { diff --git a/lib/server.js b/lib/server.js index 31b617a..ce3c3c6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -18,7 +18,6 @@ var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); var WalletUtils = require('./walletutils'); -var SignUtils = require('./signutils'); var Wallet = require('./model/wallet'); var Copayer = require('./model/copayer'); @@ -152,7 +151,7 @@ CopayServer.prototype.getWallet = function(opts, cb) { * @param pubKey */ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { - return SignUtils.verify(text, signature, pubKey); + return WalletUtils.verifyMessage(text, signature, pubKey); }; diff --git a/lib/signutils.js b/lib/signutils.js deleted file mode 100644 index c8913c1..0000000 --- a/lib/signutils.js +++ /dev/null @@ -1,43 +0,0 @@ -var _ = require('lodash'); -var Bitcore = require('bitcore'); -var PrivateKey = Bitcore.PrivateKey; -var PublicKey = Bitcore.PublicKey; -var Signature = Bitcore.crypto.Signature; -var ECDSA = Bitcore.crypto.ECDSA; -var Hash = Bitcore.crypto.Hash; -var BufferReader = Bitcore.encoding.BufferReader; - - - -var SignUtils = function() {}; - -/* TODO: It would be nice to be compatible with bitcoind signmessage. How - * the hash is calculated there? */ -SignUtils.hash = function(text) { - var buf = new Buffer(text); - var ret = Hash.sha256sha256(buf); - ret = new BufferReader(ret).readReverse(); - return ret; -}; - - -SignUtils.sign = function(text, privKey) { - var priv = new PrivateKey(privKey); - var hash = SignUtils.hash(text); - return ECDSA.sign(hash, priv, 'little').toString(); -}; - - -SignUtils.verify = function(text, signature, pubKey) { - var pub = new PublicKey(pubKey); - var hash = SignUtils.hash(text); - - try { - var sig = new Signature.fromString(signature); - return ECDSA.verify(hash, sig, pub, 'little'); - } catch (e) { - return false; - } -}; - -module.exports = SignUtils; diff --git a/lib/walletutils.js b/lib/walletutils.js index 57ecf48..68de114 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -1,10 +1,41 @@ var _ = require('lodash'); - var Bitcore = require('bitcore'); -var BitcoreAddress = Bitcore.Address; +var Address = Bitcore.Address; +var PrivateKey = Bitcore.PrivateKey; +var PublicKey = Bitcore.PublicKey; +var crypto = Bitcore.crypto; function WalletUtils() {}; +/* TODO: It would be nice to be compatible with bitcoind signmessage. How + * the hash is calculated there? */ +WalletUtils.hashMessage = function(text) { + var buf = new Buffer(text); + var ret = crypto.Hash.sha256sha256(buf); + ret = new Bitcore.encoding.BufferReader(ret).readReverse(); + return ret; +}; + + +WalletUtils.signMessage = function(text, privKey) { + var priv = new PrivateKey(privKey); + var hash = WalletUtils.hashMessage(text); + return crypto.ECDSA.sign(hash, priv, 'little').toString(); +}; + + +WalletUtils.verifyMessage = function(text, signature, pubKey) { + var pub = new PublicKey(pubKey); + var hash = WalletUtils.hashMessage(text); + + try { + var sig = new crypto.Signature.fromString(signature); + return crypto.ECDSA.verify(hash, sig, pub, 'little'); + } catch (e) { + return false; + } +}; + WalletUtils.deriveAddress = function(publicKeyRing, path, m, network) { var publicKeys = _.map(publicKeyRing, function(xPubKey) { @@ -12,7 +43,7 @@ WalletUtils.deriveAddress = function(publicKeyRing, path, m, network) { return xpub.derive(path).publicKey; }); - var bitcoreAddress = BitcoreAddress.createMultisig(publicKeys, m, network); + var bitcoreAddress = Address.createMultisig(publicKeys, m, network); return { address: bitcoreAddress.toString(), diff --git a/test/integration/server.js b/test/integration/server.js index fa9d7b2..51c0a53 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -12,7 +12,6 @@ var Bitcore = require('bitcore'); var Utils = require('../../lib/utils'); var WalletUtils = require('../../lib/walletutils'); -var SignUtils = require('../../lib/signutils'); var Storage = require('../../lib/storage'); var Wallet = require('../../lib/model/wallet'); @@ -179,7 +178,7 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { }; var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); try { - opts.proposalSignature = SignUtils.sign(hash, signingKey); + opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); } catch (ex) {} return opts; @@ -214,7 +213,7 @@ describe('Copay server', function() { .toString(); var message = 'hola'; - var sig = SignUtils.sign(message, priv); + var sig = WalletUtils.signMessage(message, priv); CopayServer.getInstanceWithAuth({ copayerId: wallet.copayers[0].id, diff --git a/test/signutils.js b/test/walletutils.js similarity index 68% rename from test/signutils.js rename to test/walletutils.js index 4acaddd..0addd17 100644 --- a/test/signutils.js +++ b/test/walletutils.js @@ -4,7 +4,7 @@ var _ = require('lodash'); var chai = require('chai'); var sinon = require('sinon'); var should = chai.should(); -var SignUtils = require('../lib/signutils'); +var WalletUtils = require('../lib/walletutils'); var aText = 'hola'; @@ -14,56 +14,55 @@ var aSignature = '3045022100d6186930e4cd9984e3168e15535e2297988555838ad10126d6c2 var otherPubKey = '02555a2d45e309c00cc8c5090b6ec533c6880ab2d3bc970b3943def989b3373f16'; -describe('SignUtils', function() { +describe('WalletUtils', function() { - describe('#hash', function() { + describe('#hashMessage', function() { it('Should create a hash', function() { - var res = SignUtils.hash(aText); + var res = WalletUtils.hashMessage(aText); res.toString('hex').should.equal('4102b8a140ec642feaa1c645345f714bc7132d4fd2f7f6202db8db305a96172f'); }); }); - describe('#sign', function() { - it('Should sign', function() { - var sig = SignUtils.sign(aText, aPrivKey); + describe('#signMessage', function() { + it('Should sign a message', function() { + var sig = WalletUtils.signMessage(aText, aPrivKey); should.exist(sig); sig.should.equal(aSignature); }); it('Should fail to sign with wrong args', function() { (function() { - SignUtils.sign(aText, aPubKey); + WalletUtils.signMessage(aText, aPubKey); }).should.throw('Number'); }); }); - describe('#verify', function() { + describe('#verifyMessage', function() { it('Should fail to verify a malformed signature', function() { - var res = SignUtils.verify(aText, 'badsignature', otherPubKey); + var res = WalletUtils.verifyMessage(aText, 'badsignature', otherPubKey); should.exist(res); res.should.equal(false); }); it('Should fail to verify a null signature', function() { - var res = SignUtils.verify(aText, null, otherPubKey); + var res = WalletUtils.verifyMessage(aText, null, otherPubKey); should.exist(res); res.should.equal(false); }); it('Should fail to verify with wrong pubkey', function() { - var res = SignUtils.verify(aText, aSignature, otherPubKey); + var res = WalletUtils.verifyMessage(aText, aSignature, otherPubKey); should.exist(res); res.should.equal(false); }); it('Should verify', function() { - var res = SignUtils.verify(aText, aSignature, aPubKey); + var res = WalletUtils.verifyMessage(aText, aSignature, aPubKey); should.exist(res); res.should.equal(true); }); }); - describe('#sign #verify round trip', function() { + describe('#signMessage #verifyMessage round trip', function() { it('Should sign and verify', function() { var aLongerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - var sig = SignUtils.sign(aLongerText, aPrivKey); - SignUtils.verify(aLongerText, sig, aPubKey).should.equal(true); + var sig = WalletUtils.signMessage(aLongerText, aPrivKey); + WalletUtils.verifyMessage(aLongerText, sig, aPubKey).should.equal(true); }); }); - });