allbases
Version:
Transform values of abritrary size to an arbritrary base
180 lines (148 loc) • 4.75 kB
JavaScript
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
}