diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 1dce48e6..1a9f22e0 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -141,6 +141,10 @@ utils.fromBase58 = function fromBase58(str) { return z.concat(res.toArray()); }; +utils.isBase58 = function isBase58(msg) { + return typeof msg === 'string' && /^[1-9a-zA-Z]+$/.test(msg); +}; + utils.ripemd160 = function ripemd160(data, enc) { return hash.ripemd160().update(data, enc).digest(); }; @@ -372,6 +376,10 @@ function toHex(msg) { utils.toHex = toHex; +utils.isHex = function isHex(msg) { + return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg); +}; + function binaryInsert(list, item, compare, search) { var start = 0; var end = list.length; @@ -544,16 +552,16 @@ utils.toBTC = function toBTC(satoshi, strict) { var m = new bn(10000000).mul(new bn(10)); var lo; - if (typeof satoshi === 'string' && /^\d+(?:\.\d+)?$/.test(satoshi)) { - satoshi = new bn(satoshi, 10); - } else if (typeof satoshi === 'number') { - // XXX Dangerous precision - do not use for any real txes - if (!strict) - satoshi = new bn(Math.floor(+satoshi || 0).toString(16), 16); - } + if (utils.isBTC(satoshi)) + return utils.isBTC(satoshi); - if (!(satoshi instanceof bn)) - throw new Error('could not calculate btc'); + if (!strict && utils.isFinite(satoshi)) + satoshi = new bn(Math.floor(satoshi) + '', 10); + + satoshi = utils.isSatoshi(satoshi); + + if (!satoshi) + throw new Error('Could not calculate BTC'); lo = satoshi.mod(m); @@ -563,36 +571,85 @@ utils.toBTC = function toBTC(satoshi, strict) { lo = '0' + lo; lo = '.' + lo; } else { - lo = ''; + lo = '.0'; } - return satoshi.div(m).toString(10) + lo.replace(/0+$/, ''); + lo = lo.replace(/0+$/, ''); + if (lo === '.') + lo += '0'; + + return satoshi.div(m).toString(10) + lo; }; utils.fromBTC = function fromBTC(btc, strict) { - var m = new bn(10000000).mul(new bn(10)); - var satoshi, parts; + var satoshi, parts, hi, lo; - if (typeof btc === 'string' && /^\d+(?:\.\d+)?$/.test(btc)) { - parts = btc.split('.'); - parts[0] = parts[0] || '0'; - parts[1] = parts[1] || '0'; - while (parts[1].length < 8) - parts[1] += '0'; - parts[0] = parts[0].replace(/^0+/, ''); - satoshi = new bn(parts[0] + parts[1], 10); - } else if (typeof btc === 'number') { - // XXX Dangerous precision - do not use for any real txes - if (!strict) - satoshi = new bn(+btc || 0); + if (utils.isSatoshi(btc)) + return utils.isSatoshi(btc); + + if (!strict && utils.isFinite(btc)) { + btc = btc + ''; + if (utils.isInt(btc)) + btc += '.0'; } - if (!(satoshi instanceof bn)) - throw new Error('could not calculate satoshis'); + btc = utils.isBTC(btc); - satoshi.imuln(m); + if (!btc) + throw new Error('Could not calculate satoshis'); - return satoshi; + parts = btc.split('.'); + hi = parts[0] || '0'; + lo = parts[1] || '0'; + + while (lo.length < 8) + lo += '0'; + + satoshi = (hi + lo).replace(/^0+/, ''); + + return new bn(satoshi, 10); +}; + +utils.isInt = function isInt(val) { + return typeof val === 'string' && /^\d+$/.test(val); +}; + +utils.isFloat = function isFloat(val) { + return typeof val === 'string' && /^\d+\.\d+$/.test(val); +}; + +utils.isFinite = function _isFinite(val) { + return typeof val === 'number' && isFinite(val); +}; + +utils.isSatoshi = function isSatoshi(val) { + if (val instanceof bn) + return val; + if (utils.isInt(val)) + return new bn(val, 10); + if (utils.isHex(val)) + return new bn(val, 'hex'); + if (Array.isArray(val)) + return new bn(val); + return false; +}; + +utils.isBTC = function isBTC(val) { + if (utils.isFloat(val)) + return val; + // For user input strings. Might cause overlap + // with isSatoshi if not used carefully. + // if (utils.isInt(val)) + // return val; + return false; +}; + +utils.toFloat = function toFloat(val) { + if (utils.isFloat(val)) + return val; + if (utils.isInt(val)) + return val + '.0'; + throw new Error('Could not convert ' + val + ' to float'); }; utils.isIP = function isIP(ip) { @@ -608,18 +665,27 @@ utils.isIP = function isIP(ip) { return 0; }; -utils.isHex = function isHex(msg) { - return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg); +utils.isArrayLike = function isArrayLike(msg) { + return msg + && !Array.isArray(msg) + && typeof msg === 'object' + && typeof msg.length === 'number'; }; utils.toKeyArray = function toKeyArray(msg) { - if (typeof msg !== 'string') + if (Array.isArray(msg)) return msg; + if (utils.isArrayLike(msg)) + return Array.prototype.slice.call(msg); + if (utils.isHex(msg)) return utils.toArray(msg, 'hex'); - return utils.fromBase58(msg); + if (utils.isBase58(msg)) + return utils.fromBase58(msg); + + throw new Error('Cannot ensure array'); }; utils.inspect = function inspect(obj) { diff --git a/test/utils-test.js b/test/utils-test.js index d5fa8320..1a29e3f6 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -23,9 +23,22 @@ describe('Utils', function() { it('should convert satoshi to btc', function() { var btc = bcoin.utils.toBTC(new bn(5460)); assert.equal(btc, '0.0000546'); - var btc = bcoin.utils.toBTC(new bn(54678).mul(new bn(1000000))); + btc = bcoin.utils.toBTC(new bn(54678).mul(new bn(1000000))); assert.equal(btc, '546.78'); - var btc = bcoin.utils.toBTC(new bn(5460).mul(new bn(10000000))); - assert.equal(btc, '546'); + btc = bcoin.utils.toBTC(new bn(5460).mul(new bn(10000000))); + assert.equal(btc, '546.0'); + btc = bcoin.utils.toBTC(new bn(5460).mul(new bn(10000000)).toArray()); + assert.equal(btc, '546.0'); + btc = bcoin.utils.toBTC(new bn(5460).mul(new bn(10000000)).toString('hex')); + assert.equal(btc, '546.0'); + }); + + it('should convert btc to satoshi', function() { + var btc = bcoin.utils.fromBTC('0.0000546'); + assert(btc.cmp(new bn(5460)) === 0); + btc = bcoin.utils.fromBTC('546.78'); + assert(btc.cmp(new bn(54678).mul(new bn(1000000))) === 0); + btc = bcoin.utils.fromBTC('546.0'); + assert(btc.cmp(new bn(5460).mul(new bn(10000000))) === 0); }); });