From a5a672b47353ee12a221b2c922fbb4a62d9fecba Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 18 Feb 2015 15:23:34 -0300 Subject: [PATCH 1/3] add parseAmount method --- lib/walletutils.js | 28 ++++++++++++++++++++++- test/walletutils.js | 56 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/lib/walletutils.js b/lib/walletutils.js index 184cb5b..c46210a 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -1,11 +1,13 @@ var _ = require('lodash'); +var sjcl = require('sjcl'); + var Bitcore = require('bitcore'); var Address = Bitcore.Address; var PrivateKey = Bitcore.PrivateKey; var PublicKey = Bitcore.PublicKey; var crypto = Bitcore.crypto; var HDPath = require('./hdpath'); -var sjcl = require('sjcl'); +var Utils = require('./utils'); function WalletUtils() {}; @@ -98,4 +100,28 @@ WalletUtils.decryptMessage = function(cyphertextJson, privKey) { return sjcl.decrypt(key, cyphertextJson); }; + +WalletUtils.UNITS = { + 'btc': 100000000, + 'bit': 100, + 'sat': 1, +}; + +WalletUtils.parseAmount = function(text) { + var regex = '^(\\d*(\\.\\d{0,8})?)\\s*(' + _.keys(WalletUtils.UNITS).join('|') + ')?$'; + + var match = new RegExp(regex, 'i').exec(text.trim()); + + if (!match || match.length === 0) throw new Error('Invalid amount'); + + var amount = parseFloat(match[1]); + if (!_.isNumber(amount) || _.isNaN(amount)) throw new Error('Invalid amount'); + + var unit = (match[3] || 'sat').toLowerCase(); + var rate = WalletUtils.UNITS[unit]; + if (!rate) throw new Error('Invalid unit') + + return Utils.strip(amount * rate); +}; + module.exports = WalletUtils; diff --git a/test/walletutils.js b/test/walletutils.js index 1383885..d4ef888 100644 --- a/test/walletutils.js +++ b/test/walletutils.js @@ -6,7 +6,6 @@ var sinon = require('sinon'); var should = chai.should(); var WalletUtils = require('../lib/walletutils'); - var aText = 'hola'; var aPubKey = '03bec86ad4a8a91fe7c11ec06af27246ec55094db3d86098b7d8b2f12afe47627f'; var aPrivKey = '09458c090a69a38368975fb68115df2f4b0ab7d1bc463fc60c67aa1730641d6c'; @@ -58,6 +57,7 @@ describe('WalletUtils', function() { res.should.equal(true); }); }); + 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."; @@ -74,4 +74,58 @@ describe('WalletUtils', function() { msg.should.equal('hello world'); }); }); + + describe('#parseAmount', function() { + it('should successfully parse amounts', function() { + var texts = { + '1': 1, + '0': 0, + '000000.0000': 0, + '123': 123, + '123sat': 123, + '123 sat': 123, + '00123 sat': 123, + '1.23bit': 123, + '1.23 bit': 123, + '0 bit': 0, + '.45bit': 45, + '1btc': 100000000, + ' 1btc': 100000000, + '9999btc': 999900000000, + '0.00000001btc': 1, + '00000.00000001BTC': 1, + '0.00000001 BTC': 1, + '0.123btc': 12300000, + '0.123 bTc': 12300000, + }; + _.each(texts, function(satoshi, text) { + var amount = WalletUtils.parseAmount(text); + amount.should.equal(satoshi); + }); + }); + it('should fail to parse incorrect amounts', function() { + var texts = [ + '', + ' ', + 'btc', + '1satoshi', + 'no-number', + '-3', + '1 b t c', + 'btc1', + '1,234', + '0.000000001btc', + ]; + _.each(texts, function(text) { + var valid = true; + try { + var amount = WalletUtils.parseAmount(text); + } catch (e) { + valid = false; + } + valid.should.be.false; + }); + }); + }); + }); From c23f962030722356d6357b967be2d4c1c5ee7268 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 18 Feb 2015 15:39:18 -0300 Subject: [PATCH 2/3] check precision is consistent with unit --- lib/walletutils.js | 5 ++++- test/walletutils.js | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/walletutils.js b/lib/walletutils.js index c46210a..dabd89c 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -121,7 +121,10 @@ WalletUtils.parseAmount = function(text) { var rate = WalletUtils.UNITS[unit]; if (!rate) throw new Error('Invalid unit') - return Utils.strip(amount * rate); + var amountSat = Utils.strip(amount * rate); + if (amountSat != Math.round(amountSat)) throw new Error('Invalid amount'); + + return amountSat; }; module.exports = WalletUtils; diff --git a/test/walletutils.js b/test/walletutils.js index d4ef888..d8695eb 100644 --- a/test/walletutils.js +++ b/test/walletutils.js @@ -80,6 +80,7 @@ describe('WalletUtils', function() { var texts = { '1': 1, '0': 0, + '1.': 1, '000000.0000': 0, '123': 123, '123sat': 123, @@ -113,8 +114,12 @@ describe('WalletUtils', function() { '-3', '1 b t c', 'btc1', + 'btc 1', '1,234', '0.000000001btc', + '0.1sat', + '0.123bit', + '2.000000009btc', ]; _.each(texts, function(text) { var valid = true; From dac04f3db79184891e6dbc4e92ec703e3e3bc9a1 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 18 Feb 2015 15:55:16 -0300 Subject: [PATCH 3/3] parse amount on client API sendTxProposal --- lib/client/api.js | 36 ++++++++++++++++++----------------- test/integration/clientApi.js | 4 ++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/client/api.js b/lib/client/api.js index 63a5a88..1e65eb0 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -17,17 +17,6 @@ var BASE_URL = 'http://localhost:3001/copay/api'; var WALLET_CRITICAL_DATA = ['xPrivKey', 'm', 'publicKeyRing']; -function _createProposalOpts(opts, signingKey) { - var args = { - toAddress: opts.toAddress, - amount: opts.amount, - message: _encryptProposalMessage(opts.message, signingKey), - }; - var hash = WalletUtils.getProposalHash(args.toAddress, args.amount, args.message); - args.proposalSignature = WalletUtils.signMessage(hash, signingKey); - return args; -}; - function _encryptProposalMessage(message, encryptingKey) { if (!message) return null; return WalletUtils.encryptMessage(message, encryptingKey); @@ -298,18 +287,31 @@ API.prototype.getStatus = function(cb) { /** * send * - * @param inArgs - * @param inArgs.toAddress - * @param inArgs.amount - * @param inArgs.message + * @param opts + * @param opts.toAddress + * @param opts.amount + * @param opts.message */ -API.prototype.sendTxProposal = function(inArgs, cb) { +API.prototype.sendTxProposal = function(opts, cb) { var self = this; this._loadAndCheck(function(err, data) { if (err) return cb(err); - var args = _createProposalOpts(inArgs, data.signingPrivKey); + var amount; + try { + amount = WalletUtils.parseAmount(opts.amount); + } catch (ex) { + return cb(ex); + } + + var args = { + toAddress: opts.toAddress, + amount: amount, + message: _encryptProposalMessage(opts.message, data.signingPrivKey), + }; + var hash = WalletUtils.getProposalHash(args.toAddress, args.amount, args.message); + args.proposalSignature = WalletUtils.signMessage(hash, data.signingPrivKey); var url = '/v1/txproposals/'; self._doPostRequest(url, args, data, cb); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 947c9ea..526e557 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -183,13 +183,13 @@ describe('client API ', function() { var args = { toAddress: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', - amount: 100000, + amount: '200bit', message: 'some message', }; client.sendTxProposal(args, function(err) { var callArgs = request.getCall(0).args[0].body; callArgs.toAddress.should.equal(args.toAddress); - callArgs.amount.should.equal(args.amount); + callArgs.amount.should.equal(20000); callArgs.message.should.not.equal(args.message); var decryptedMsg = WalletUtils.decryptMessage(callArgs.message, '42798f82c4ed9ace4d66335165071edf180e70bc0fc08dacb3e35185a2141d5b'); decryptedMsg.should.equal(args.message);