From 139465c794d592be63eb7b01449af3e96cac4ab3 Mon Sep 17 00:00:00 2001 From: Gabriel Masclef Date: Mon, 28 May 2018 15:38:21 -0300 Subject: [PATCH 1/2] Feat: validation for bchtest prefix and tests for it --- lib/uri.js | 4 +- test/uri.js | 176 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 171 insertions(+), 9 deletions(-) diff --git a/lib/uri.js b/lib/uri.js index 830cc2c44..867769890 100644 --- a/lib/uri.js +++ b/lib/uri.js @@ -110,7 +110,7 @@ URI.isValid = function(arg, knownParams) { URI.parse = function(uri) { var info = URL.parse(uri, true); - if (info.protocol !== 'bitcoincash:') { + if (info.protocol !== 'bitcoincash:' && info.protocol !== 'bchtest:') { throw new TypeError('Invalid bitcoin URI'); } @@ -205,7 +205,7 @@ URI.prototype.toString = function() { _.extend(query, this.extras); return URL.format({ - protocol: 'bitcoincash:', + protocol: this.network == 'testnet' ? 'bchtest:' : 'bitcoincash:', host: this.address.toString(true), query: query }); diff --git a/test/uri.js b/test/uri.js index 73d9910f9..3fdf6816a 100644 --- a/test/uri.js +++ b/test/uri.js @@ -39,6 +39,35 @@ describe('URI', function() { uri['req-extra'].should.equal('param'); }); + // TODO: Split this and explain tests + it('parses uri strings correctly for testnet network (test vector)', function() { + var uri; + + URI.parse.bind(URI, 'badURI').should.throw(TypeError); + + uri = URI.parse('bchtest:'); + expect(uri.address).to.be.undefined(); + expect(uri.amount).to.be.undefined(); + expect(uri.otherParam).to.be.undefined(); + + uri = URI.parse('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + expect(uri.amount).to.be.undefined(); + expect(uri.otherParam).to.be.undefined(); + + uri = URI.parse('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22'); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + expect(uri.otherParam).to.be.undefined(); + + uri = URI.parse('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22' + + '&other-param=something&req-extra=param'); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + uri['other-param'].should.equal('something'); + uri['req-extra'].should.equal('param'); + }); + describe('cashaddr', function() { URI.parse.bind(URI, 'badURI').should.throw(TypeError); @@ -105,7 +134,73 @@ describe('URI', function() { // becase other-; req-* was not supplied to validator URI.isValid(str).should.equal(true); }); + }); + + describe('cashaddr for testnet', function() { + URI.parse.bind(URI, 'badURI').should.throw(TypeError); + // cashaddr + it('address only', function() { + var uri; + var str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'; + uri = URI.parse(str); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + expect(uri.amount).to.be.undefined(); + expect(uri.otherParam).to.be.undefined(); + URI.isValid(str).should.equal(true); + }); + + it('address +amount', function() { + var uri; + var str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22'; + uri = URI.parse(str); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + expect(uri.otherParam).to.be.undefined(); + URI.isValid(str).should.equal(true); + }); + + + it('address +amount + opts', function() { + var uri; + var str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22' + + '&other-param=something&req-extra=param'; + uri = URI.parse(str); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + uri['other-param'].should.equal('something'); + uri['req-extra'].should.equal('param'); + + URI.isValid(str).should.equal(false); + }); + + it('address +amount + opts', function() { + var uri; + var str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22' + + '&other-param=something&req-extra=param'; + uri = URI.parse(str); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + uri['other-param'].should.equal('something'); + uri['req-extra'].should.equal('param'); + + // becase other-; req-* was not supplied to validator + URI.isValid(str).should.equal(false); + }); + + it('address +amount + opts', function() { + var uri; + var str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22' + + '&message=Donation%20for%20project%20xyz&label=myLabel'; + uri = URI.parse(str); + uri.address.should.equal('qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal('123.22'); + uri.label.should.equal('myLabel'); + uri.message.should.equal('Donation for project xyz'); + + // becase other-; req-* was not supplied to validator + URI.isValid(str).should.equal(true); + }); }); @@ -114,7 +209,6 @@ describe('URI', function() { // TODO: Split this and explain tests it('URIs can be validated statically (test vector)', function() { URI.isValid('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m').should.equal(true); - URI.isValid('bitcoincash:mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw').should.equal(true); URI.isValid('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2') .should.equal(true); @@ -134,6 +228,28 @@ describe('URI', function() { .should.equal(false); }); + // TODO: Split this and explain tests + it('URIs can be validated statically (test vector)', function() { + URI.isValid('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x').should.equal(true); + + URI.isValid('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2') + .should.equal(true); + URI.isValid('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2&other=param') + .should.equal(true); + URI.isValid('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2&req-other=param', + ['req-other']).should.equal(true); + URI.isValid('bchtest:mmrqEBJxUCf42vdb3oozZtyz5mKr3Vb2Em?amount=0.1&' + + 'r=https%3A%2F%2Ftest.bitpay.com%2Fi%2F6DKgf8cnJC388irbXk5hHu').should.equal(true); + + URI.isValid('bchtest:').should.equal(false); + URI.isValid('bchtest:badUri').should.equal(false); + URI.isValid('bchtest:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfk?amount=bad').should.equal(false); + URI.isValid('bchtest:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfk?amount=1.2&req-other=param') + .should.equal(false); + URI.isValid('bchtest:?r=https%3A%2F%2Ftest.bitpay.com%2Fi%2F6DKgf8cnJC388irbXk5hHu') + .should.equal(false); + }); + it('fails on creation with no params', function() { (function(){ return new URI(); @@ -145,6 +261,11 @@ describe('URI', function() { uri.should.be.instanceof(URI); }); + it('do not need new keyword for testnet', function() { + var uri = URI('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.should.be.instanceof(URI); + }); + describe('instantiation from bitcoin uri', function() { /* jshint maxstatements: 25 */ var uri; @@ -162,14 +283,48 @@ describe('URI', function() { expect(uri.otherParam).to.be.undefined(); }); + it('stores unknown parameters as "extras"', function() { + uri = new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param'); + uri.address.should.be.instanceof(bitcore.Address); + expect(uri.other).to.be.undefined(); + uri.extras.other.should.equal('param'); + }); + + it('throws error when a required feature is not supported', function() { + (function() { + return new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param&req-required=param'); + }).should.throw(Error); + }); + + it('has no false negative when checking supported features', function() { + uri = new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param&' + + 'req-required=param', ['req-required']); + uri.address.should.be.instanceof(bitcore.Address); + uri.amount.should.equal(120000000); + uri.extras.other.should.equal('param'); + uri.extras['req-required'].should.equal('param'); + }); + }); + + describe('instantiation from bitcoin uri for testnet', function() { + /* jshint maxstatements: 25 */ + var uri; + it('parses a testnet address', function() { - uri = new URI('bitcoincash:mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw'); + uri = new URI('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); uri.address.should.be.instanceof(bitcore.Address); uri.network.should.equal(Networks.testnet); }); + it('parses amount', function() { + uri = URI.fromString('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=123.22'); + uri.address.toString().should.equal('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.amount.should.equal(12322000000); + expect(uri.otherParam).to.be.undefined(); + }); + it('stores unknown parameters as "extras"', function() { - uri = new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param'); + uri = new URI('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2&other=param'); uri.address.should.be.instanceof(bitcore.Address); expect(uri.other).to.be.undefined(); uri.extras.other.should.equal('param'); @@ -177,12 +332,12 @@ describe('URI', function() { it('throws error when a required feature is not supported', function() { (function() { - return new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param&req-required=param'); + return new URI('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2&other=param&req-required=param'); }).should.throw(Error); }); it('has no false negative when checking supported features', function() { - uri = new URI('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?amount=1.2&other=param&' + + uri = new URI('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?amount=1.2&other=param&' + 'req-required=param', ['req-required']); uri.address.should.be.instanceof(bitcore.Address); uri.amount.should.equal(120000000); @@ -203,7 +358,7 @@ describe('URI', function() { uri.network.should.equal(Networks.livenet); uri = new URI({ - address: 'mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw' + address: 'qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x' }); uri.address.should.be.instanceof(bitcore.Address); uri.network.should.equal(Networks.testnet); @@ -240,12 +395,19 @@ describe('URI', function() { it('should support double slash scheme', function() { var uri = new URI('bitcoincash://qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m'); uri.address.toString().should.equal('bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m'); + + uri = new URI('bchtest://qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + uri.address.toString().should.equal('bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); }); it('should input/output String', function() { var str = 'bitcoincash:qzruaav37d2hwqfaqvsktwdqjly502s06qfra0qe9m?' + 'message=Donation%20for%20project%20xyz&label=myLabel&other=xD'; URI.fromString(str).toString().should.equal(str); + + str = 'bchtest:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x?' + + 'message=Donation%20for%20project%20xyz&label=myLabel&other=xD'; + URI.fromString(str).toString().should.equal(str); }); it('should input/output JSON', function() { @@ -303,7 +465,7 @@ describe('URI', function() { }); it('writes correctly the "r" parameter on string serialization', function() { - var originalString = 'bitcoincash:qpzextxrtp4ettwsfru86fggmwf565h3jshdfuz5vj?amount=0.1&' + + var originalString = 'bchtest:qpzextxrtp4ettwsfru86fggmwf565h3jshdfuz5vj?amount=0.1&' + 'r=https%3A%2F%2Ftest.bitpay.com%2Fi%2F6DKgf8cnJC388irbXk5hHu'; var uri = new URI(originalString); uri.toString().should.equal(originalString); From 6d9026d280246ef79b9e6195522163413900d217 Mon Sep 17 00:00:00 2001 From: Gabriel Masclef Date: Tue, 29 May 2018 15:39:02 -0300 Subject: [PATCH 2/2] Ref: uri parse function and URL protocol parameter now use networks get --- lib/uri.js | 20 ++++++++++---------- test/uri.js | 10 ++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/uri.js b/lib/uri.js index 867769890..57ee36bcc 100644 --- a/lib/uri.js +++ b/lib/uri.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var URL = require('url'); var Address = require('./address'); +var Networks = require('./networks'); var Unit = require('./unit'); /** @@ -109,16 +110,15 @@ URI.isValid = function(arg, knownParams) { */ URI.parse = function(uri) { var info = URL.parse(uri, true); - - if (info.protocol !== 'bitcoincash:' && info.protocol !== 'bchtest:') { - throw new TypeError('Invalid bitcoin URI'); + if (Networks.get( info.protocol.replace(':', '') ,'prefix')) { + // workaround to host insensitiveness + var group = /[^:]*:\/?\/?([^?]*)/.exec(uri); + info.query.address = group && group[1] || undefined; + + return info.query; +} else { + throw new TypeError('Invalid bitcoin URI'); } - - // workaround to host insensitiveness - var group = /[^:]*:\/?\/?([^?]*)/.exec(uri); - info.query.address = group && group[1] || undefined; - - return info.query; }; URI.Members = ['address', 'amount', 'message', 'label', 'r']; @@ -205,7 +205,7 @@ URI.prototype.toString = function() { _.extend(query, this.extras); return URL.format({ - protocol: this.network == 'testnet' ? 'bchtest:' : 'bitcoincash:', + protocol: Networks.get(this.network ,'name').prefix + ':', host: this.address.toString(true), query: query }); diff --git a/test/uri.js b/test/uri.js index 3fdf6816a..cbb07d1c8 100644 --- a/test/uri.js +++ b/test/uri.js @@ -68,6 +68,16 @@ describe('URI', function() { uri['req-extra'].should.equal('param'); }); + it('Should return error if try to use an invalid bitcoin URI', function() { + var uri; + + try { + uri = URI.parse('badprefix:qqkj609un9sl896yezxj0j5hxagk7h7pnyyzaz887x'); + } catch (e) { + expect(e.message).to.equal('Invalid bitcoin URI'); + } + }); + describe('cashaddr', function() { URI.parse.bind(URI, 'badURI').should.throw(TypeError);