UNPKG

allbases

Version:

Transform values of abritrary size to an arbritrary base

180 lines (148 loc) 4.75 kB
var crypto = require("crypto"); var check = require("check-types"); var _ = require("lodash"); var BigNumber = require("bignumber.js"); var HexChars = "0123456789abcdef"; var Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //TODO: We are ignoring padding for now. var Base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; function log(b, n) { return Math.log(n) / Math.log(b); } /* * Converts str from one encoding into another. * * @param {string} str - thing to convert from * @param {string} from - character class you're converting from * @param {string} to - character class you're converting to * @param {object} opts - opts to use for the encode and decode functions * */ function convert(str, from, to, opts) { check.assert.unemptyString(str); check.assert.unemptyString(from); check.assert.unemptyString(to); opts = opts || {}; var decodedNum = decode(str, _.assign(opts, {chars: from})); return encode(decodedNum, _.assign(opts, {chars: to, convert: true})); } function encode(num, opts) { check.assert.instance(num, BigNumber, "not BigNumber or not using allbases.BigNumber"); opts = _.defaults(opts || {}, { chars: HexChars }); check.assert.unemptyString(opts.chars); var len = opts.chars.length; var indices = []; do { indices.push(num.mod(len).toNumber()); num = num.divToInt(len); } while (num.gt(0)); var str = []; while (indices.length > 0) { str.push(opts.chars[indices.pop()]); } return str.join(""); } //Using factored out polynomial series to decode only with mul & add // i.e. a + bX + cX^2 + dX^4 ... === a + X(b + X(c + X(d + ... function decode(str, opts) { check.assert.unemptyString(str); opts = _.defaults(opts || {}, { chars: HexChars, strict: true }); //ignore case on hex encoding if (opts.chars === HexChars) { str = str.toLowerCase(); } if (opts.strict && !strictCheck(str, opts.chars)) { throw "str falls outside of specified character class." } var num = new BigNumber(opts.chars.indexOf(str[0])); for (var i = 1; i < str.length; i++) { var ch = str[i]; num = num.mul(opts.chars.length).add(opts.chars.indexOf(ch)); } return num; } function strictCheck(str, chars) { for (var i = 0; i < str.length; i++) { if (chars.indexOf(str[i]) === -1) { return false; } } return true; } function trimPadding(str) { while (str.slice(-1) === "=") { str = str.slice(0, -1); } return str; } function genRandom(numberOfBytes, chars, rand) { //TODO: allow user to specify random byte method used var buf = rand(numberOfBytes); var bHex = buf.toString("hex"); return encode(new BigNumber(bHex, 16), {chars: chars}); } function random(size, opts) { check.assert.positive(size); var defaultRandom = crypto.pseudoRandomBytes ? crypto.pseudoRandomBytes : crypto.randomBytes; //TODO: verify opts opts = _.defaults(opts || {}, { chars: HexChars, rand: defaultRandom, strictSize: false }); var len = opts.chars.length; //TODO: circumvent current combo size limitation //TODO: for base 2 char sizes we can calculate byte size directly. var combos = Math.pow(len, size); if (combos === Infinity) { throw "Too large of a size, can't estimate needed bytes" } //TODO: in order to circumvent current combo size limitation need to //implement the log function for big decimals. //https://stackoverflow.com/questions/739532/logarithm-of-a-bigdecimal var bits = Math.ceil(log(2, combos)); //Add 1 to give padding and reduce chance we will need to recalculate with //strict size enabled. var numberOfBytes = Math.ceil(bits / 8) + 1; var res; if (opts.strictSize) { // With strict size need to gurantee random value of certain size is generated do { res = genRandom(numberOfBytes, opts.chars, opts.rand); } while (res.length < size); } else { res = genRandom(numberOfBytes, opts.chars, opts.rand); } return res.substr(0, size); } function genBaseAPI(func, base) { return function(v, opts) { return func(v, _.assign(opts || {}, { chars: base })); } } module.exports = { Chars: { Hex: HexChars, Base16: HexChars, Base62: Base62Chars, Base64: Base64Chars }, encode: encode, encodeHex: genBaseAPI(encode, HexChars), encodeBase62: genBaseAPI(encode, Base62Chars), encodeBase64: genBaseAPI(encode, Base64Chars), decode: decode, decodeHex: genBaseAPI(decode, HexChars), decodeBase62: genBaseAPI(decode, Base62Chars), decodeBase64: genBaseAPI(decode, Base64Chars), random: random, randomHex: genBaseAPI(random, HexChars), randomBase62: genBaseAPI(random, Base62Chars), randomBase64: genBaseAPI(random, Base64Chars), convert: convert, BigNumber: BigNumber }