From 6bf2a10fd21d2e31decabf10084ffad08c8b40ae Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 22 Feb 2015 01:35:25 -0300 Subject: [PATCH 1/3] base58 walletId in secret --- lib/client/api.js | 6 ++++- lib/model/notification.js | 3 ++- lib/model/txproposal.js | 2 +- lib/walletutils.js | 47 +++++++++++++++++++++++++---------- test/integration/clientApi.js | 16 ++++++++++-- 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/lib/client/api.js b/lib/client/api.js index 1caa8a8..c73448b 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -307,7 +307,11 @@ API.prototype.joinWallet = function(secret, copayerName, cb) { if (data) return cb('Storage already contains a wallet'); - var secretData = WalletUtils.fromSecret(secret); + try { + var secretData = WalletUtils.fromSecret(secret); + } catch (ex) { + return cb(ex); + } var data = self._initData(secretData.network, secretData.walletPrivKey); self._doJoinWallet(secretData.walletId, secretData.walletPrivKey, data.publicKeyRing[0], copayerName, function(err, wallet) { diff --git a/lib/model/notification.js b/lib/model/notification.js index 07b621f..18f94ec 100644 --- a/lib/model/notification.js +++ b/lib/model/notification.js @@ -1,3 +1,4 @@ +var _ = require('lodash'); var Uuid = require('uuid'); /* @@ -33,7 +34,7 @@ Notification.create = function(opts) { var now = Date.now(); x.createdOn = Math.floor(now / 1000); - x.id = ('00000000000000' + now).slice(-14) + ('0000' + opts.ticker || 0).slice(-4); + x.id = _.padLeft(now, 14, '0') + _.padLeft(opts.ticker || 0, 4, '0'); x.type = opts.type || 'general'; x.data = opts.data; diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 3742b6a..e1a1b23 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -18,7 +18,7 @@ TxProposal.create = function(opts) { var now = Date.now(); x.createdOn = Math.floor(now / 1000); - x.id = ('00000000000000' + now).slice(-14) + Uuid.v4(); + x.id = _.padLeft(now, 14, '0') + Uuid.v4(); x.creatorId = opts.creatorId; x.toAddress = opts.toAddress; x.amount = opts.amount; diff --git a/lib/walletutils.js b/lib/walletutils.js index a5d151c..092e3ec 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -7,6 +7,7 @@ var Address = Bitcore.Address; var PrivateKey = Bitcore.PrivateKey; var PublicKey = Bitcore.PublicKey; var crypto = Bitcore.crypto; +var encoding = Bitcore.encoding; var HDPath = require('./hdpath'); var Utils = require('./utils'); @@ -32,10 +33,10 @@ WalletUtils.signMessage = function(text, privKey) { WalletUtils.accessFromData = function(data) { - if (data.xPrivKey) + if (data.xPrivKey) return 'full'; - - if (data.rwPrivKey) + + if (data.rwPrivKey) return 'readwrite'; return 'readonly'; @@ -84,22 +85,42 @@ WalletUtils.xPubToCopayerId = function(xpub) { }; WalletUtils.toSecret = function(walletId, walletPrivKey, network) { - return walletId + ':' + walletPrivKey.toWIF() + ':' + (network == 'testnet' ? 'T' : 'L'); + var widHex = new Buffer(walletId.replace(/-/g, ''), 'hex'); + widBase58 = new encoding.Base58(widHex).toString(); + return widBase58 + walletPrivKey.toWIF() + (network == 'testnet' ? 'T' : 'L'); }; WalletUtils.fromSecret = function(secret) { $.checkArgument(secret); - var secretSplit = secret.split(':'); - var walletId = secretSplit[0]; - var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]); - var networkChar = secretSplit[2]; - - return { - walletId: walletId, - walletPrivKey: walletPrivKey, - network: networkChar == 'T' ? 'testnet' : 'livenet', + function split(str, indexes) { + var parts = []; + indexes.push(str.length); + var i = 0; + while (i < indexes.length) { + parts.push(str.substring(i == 0 ? 0 : indexes[i - 1], indexes[i])); + i++; + }; + return parts; }; + + try { + var secretSplit = split(secret, [22, 74]); + var widBase58 = secretSplit[0]; + var widHex = encoding.Base58.decode(widBase58).toString('hex'); + var walletId = split(widHex, [8, 12, 16, 20]).join('-'); + + var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]); + var networkChar = secretSplit[2]; + + return { + walletId: walletId, + walletPrivKey: walletPrivKey, + network: networkChar == 'T' ? 'testnet' : 'livenet', + }; + } catch (ex) { + throw new Error('Invalid secret'); + } }; diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 8d8c486..8782b3f 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -193,8 +193,20 @@ describe('client API ', function() { }); }); }); - it('should fail with a unknown secret', function(done) { - var oldSecret = '3f8e5acb-ceeb-4aae-134f-692d934e3b1c:L2gohj8s2fLKqVU5cQutAVGciutUxczFxLxxXHFsjzLh71ZjkFQQ:T'; + it('should fail with an invalid secret', function(done) { + // Invalid length + clients[0].joinWallet('dummy', 'copayer', function(err, result) { + err.message.should.contain('Invalid secret'); + // Right length, invalid char for base 58 + clients[0].joinWallet('lPU9zqUiQFRnt3sWBQ1pnPKx9xC3KD83jxhYVJbLABxn1qhdBfQ7dYtzQqNKpSrDWDqF261S1zT', 'copayer', function(err, result) { + err.message.should.contain('Invalid secret'); + done(); + }); + }); + }); + it('should fail with an unknown secret', function(done) { + // Unknown walletId + var oldSecret = 'VPU9zqUiQFRnt3sWBQ1pnPKx9xC3KD83jxhYVJbLABxn1qhdBfQ7dYtzQqNKpSrDWDqF261S1zT'; clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { err.code.should.contain('BADREQUEST'); done(); From 1f446fcce85ff5bb4a4f8b537e3e915bdaa5b791 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 22 Feb 2015 01:36:53 -0300 Subject: [PATCH 2/3] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1beae0b..78b1e1d 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,14 @@ A Multisig HD Wallet Service, with minimun server trust. # Create a 2-of-2 wallet (john.dat is the file where the wallet critical data will be stored, add -t for testnet) ./bit create 2-2 john * Secret to share: - 0a18bed5-5607-4fde-a809-dc6561bc0664:L3WtafRAEHty7h2J7VCHdiyzFboAdVFnNZXMmqDGw4yiu5kW9Tp4:T + XYbdNQjWgoTr8uzGMcmkdqKy4fVLGePvfVXQHb8YZM2RJKfvf2pcE8KzTZtxHQNVFM2aJYsFv3T ./bit status # Use -h or BIT_HOST to setup the base URL for your server. # Use -f or BIT_FILE to setup the wallet data file # Join the wallet from other copayer - ./bit -f pete.dat join 0a18bed5-5607-4fde-a809-dc6561bc0664:L3WtafRAEHty7h2J7VCHdiyzFboAdVFnNZXMmqDGw4yiu5kW9Tp4:T + ./bit -f pete.dat join XYbdNQjWgoTr8uzGMcmkdqKy4fVLGePvfVXQHb8YZM2RJKfvf2pcE8KzTZtxHQNVFM2aJYsFv3T export BIT_FILE=pete.dat ./bit -f pete.dat status From 32ccddc71cee9336c937e78c223bfe5f83802006 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 22 Feb 2015 11:16:29 -0300 Subject: [PATCH 3/3] add padding to walletId base58 --- README.md | 4 ++-- lib/walletutils.js | 8 +++++--- test/integration/clientApi.js | 6 +++--- test/walletutils.js | 35 +++++++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 78b1e1d..87cec43 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,14 @@ A Multisig HD Wallet Service, with minimun server trust. # Create a 2-of-2 wallet (john.dat is the file where the wallet critical data will be stored, add -t for testnet) ./bit create 2-2 john * Secret to share: - XYbdNQjWgoTr8uzGMcmkdqKy4fVLGePvfVXQHb8YZM2RJKfvf2pcE8KzTZtxHQNVFM2aJYsFv3T + JevjEwaaxW6gdAZjqgWcimL525DR8zQsAXf4cscWDa8u1qKTN5eFGSFssuSvT1WySu4YYLYMUPT ./bit status # Use -h or BIT_HOST to setup the base URL for your server. # Use -f or BIT_FILE to setup the wallet data file # Join the wallet from other copayer - ./bit -f pete.dat join XYbdNQjWgoTr8uzGMcmkdqKy4fVLGePvfVXQHb8YZM2RJKfvf2pcE8KzTZtxHQNVFM2aJYsFv3T + ./bit -f pete.dat join JevjEwaaxW6gdAZjqgWcimL525DR8zQsAXf4cscWDa8u1qKTN5eFGSFssuSvT1WySu4YYLYMUPT export BIT_FILE=pete.dat ./bit -f pete.dat status diff --git a/lib/walletutils.js b/lib/walletutils.js index 092e3ec..5829c82 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -1,3 +1,5 @@ +'use strict'; + var _ = require('lodash'); var $ = require('preconditions').singleton(); var sjcl = require('sjcl'); @@ -86,8 +88,8 @@ WalletUtils.xPubToCopayerId = function(xpub) { WalletUtils.toSecret = function(walletId, walletPrivKey, network) { var widHex = new Buffer(walletId.replace(/-/g, ''), 'hex'); - widBase58 = new encoding.Base58(widHex).toString(); - return widBase58 + walletPrivKey.toWIF() + (network == 'testnet' ? 'T' : 'L'); + var widBase58 = new encoding.Base58(widHex).toString(); + return _.padRight(widBase58, 22, '0') + walletPrivKey.toWIF() + (network == 'testnet' ? 'T' : 'L'); }; WalletUtils.fromSecret = function(secret) { @@ -106,7 +108,7 @@ WalletUtils.fromSecret = function(secret) { try { var secretSplit = split(secret, [22, 74]); - var widBase58 = secretSplit[0]; + var widBase58 = secretSplit[0].replace(/0/g, ''); var widHex = encoding.Base58.decode(widBase58).toString('hex'); var walletId = split(widHex, [8, 12, 16, 20]).join('-'); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 8782b3f..43760e5 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -194,11 +194,11 @@ describe('client API ', function() { }); }); it('should fail with an invalid secret', function(done) { - // Invalid length + // Invalid clients[0].joinWallet('dummy', 'copayer', function(err, result) { err.message.should.contain('Invalid secret'); // Right length, invalid char for base 58 - clients[0].joinWallet('lPU9zqUiQFRnt3sWBQ1pnPKx9xC3KD83jxhYVJbLABxn1qhdBfQ7dYtzQqNKpSrDWDqF261S1zT', 'copayer', function(err, result) { + clients[0].joinWallet('DsZbqNQQ9LrTKU8EknR7gFKyCQMPg2UUHNPZ1BzM5EbJwjRZaUNBfNtdWLluuFc0f7f7sTCkh7T', 'copayer', function(err, result) { err.message.should.contain('Invalid secret'); done(); }); @@ -206,7 +206,7 @@ describe('client API ', function() { }); it('should fail with an unknown secret', function(done) { // Unknown walletId - var oldSecret = 'VPU9zqUiQFRnt3sWBQ1pnPKx9xC3KD83jxhYVJbLABxn1qhdBfQ7dYtzQqNKpSrDWDqF261S1zT'; + var oldSecret = '3bJKRn1HkQTpwhVaJMaJ22KwsjN24ML9uKfkSrP7iDuq91vSsTEygfGMMpo6kWLp1pXG9wZSKcT'; clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { err.code.should.contain('BADREQUEST'); done(); diff --git a/test/walletutils.js b/test/walletutils.js index 192df87..b3e187e 100644 --- a/test/walletutils.js +++ b/test/walletutils.js @@ -1,9 +1,11 @@ 'use strict'; var _ = require('lodash'); +var Uuid = require('uuid'); var chai = require('chai'); var sinon = require('sinon'); var should = chai.should(); +var Bitcore = require('bitcore'); var WalletUtils = require('../lib/walletutils'); var aText = 'hola'; @@ -16,19 +18,19 @@ var otherPubKey = '02555a2d45e309c00cc8c5090b6ec533c6880ab2d3bc970b3943def989b33 describe('WalletUtils', function() { describe('#hashMessage', function() { - it('Should create a hash', function() { + it('should create a hash', function() { var res = WalletUtils.hashMessage(aText); res.toString('hex').should.equal('4102b8a140ec642feaa1c645345f714bc7132d4fd2f7f6202db8db305a96172f'); }); }); describe('#signMessage', function() { - it('Should sign a message', 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() { + it('should fail to sign with wrong args', function() { (function() { WalletUtils.signMessage(aText, aPubKey); }).should.throw('Number'); @@ -36,22 +38,22 @@ describe('WalletUtils', function() { }); describe('#verifyMessage', function() { - it('Should fail to verify a malformed signature', function() { + it('should fail to verify a malformed signature', function() { var res = WalletUtils.verifyMessage(aText, 'badsignature', otherPubKey); should.exist(res); res.should.equal(false); }); - it('Should fail to verify a null signature', function() { + it('should fail to verify a null signature', function() { var res = WalletUtils.verifyMessage(aText, null, otherPubKey); should.exist(res); res.should.equal(false); }); - it('Should fail to verify with wrong pubkey', function() { + it('should fail to verify with wrong pubkey', function() { var res = WalletUtils.verifyMessage(aText, aSignature, otherPubKey); should.exist(res); res.should.equal(false); }); - it('Should verify', function() { + it('should verify', function() { var res = WalletUtils.verifyMessage(aText, aSignature, aPubKey); should.exist(res); res.should.equal(true); @@ -59,7 +61,7 @@ describe('WalletUtils', function() { }); describe('#signMessage #verifyMessage round trip', function() { - it('Should sign and verify', 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 = WalletUtils.signMessage(aLongerText, aPrivKey); WalletUtils.verifyMessage(aLongerText, sig, aPubKey).should.equal(true); @@ -74,4 +76,21 @@ describe('WalletUtils', function() { msg.should.equal('hello world'); }); }); + + describe('#toSecret #fromSecret round trip', function() { + it('should create secret and parse secret', function() { + var i = 0; + while (i++ < 100) { + var walletId = Uuid.v4(); + var walletPrivKey = new Bitcore.PrivateKey(); + var network = 'testnet'; + var secret = WalletUtils.toSecret(walletId, walletPrivKey, network); + var result = WalletUtils.fromSecret(secret); + result.walletId.should.equal(walletId); + result.walletPrivKey.toString().should.equal(walletPrivKey.toString()); + result.network.should.equal(network); + }; + }); + }); + });