How to encode/decode a Uint8Array in Base36 in JavaScript?

762 Views Asked by At

I want to encode and decode Bytes from an Uint8Array (or an ArrayBuffer) to/from a string in Base36. JavaScript has the toString and parseInt functions which both support base36 but I am not sure if first converting 8 Bytes to a 64 bit floating point is the right idea.

In JS one can encode a BigInt (a number of arbitrary length) in Base36. However the other direction does not work.

How can I do this?

3

There are 3 best solutions below

0
On BEST ANSWER

I found the solution with the help of these two posts: Why JavaScript base-36 conversion appears to be ambiguous and How to go between JS BigInts and TypedArrays

function bigIntToBase36(num){
    return num.toString(36);
}

function base36ToBigInt(str){
    return [...str].reduce((acc,curr) => BigInt(parseInt(curr, 36)) + BigInt(36) * acc, 0n);
}

function bigIntToBuffer(bn) {
    let hex = BigInt(bn).toString(16);
    if (hex.length % 2) { hex = '0' + hex; }

    const len = hex.length / 2;
    const u8 = new Uint8Array(len);

    let i = 0;
    let j = 0;
    while (i < len) {
        u8[i] = parseInt(hex.slice(j, j+2), 16);
        i += 1;
        j += 2;
    }

    return u8;
}

function bufferToBigInt(buf) {
    const hex = [];
    const u8 = Uint8Array.from(buf);

    u8.forEach(function (i) {
        var h = i.toString(16);
        if (h.length % 2) { h = '0' + h; }
        hex.push(h);
    });

    return BigInt('0x' + hex.join(''));
}

const t1 = new Uint8Array([123, 51, 234, 234, 24, 124, 2, 125, 34, 255]);
console.log(t1);
const t2 = bigIntToBase36(bufferToBigInt(t1));
console.log(t2);
console.log(t2.length)
const t3 = bigIntToBuffer(base36ToBigInt(t2));
console.log(t3);

0
On

An alternative implementation of the bufferToBigInt method compared to the accepted answer would be:

function bufferToBigInt_2(buf) {
  let ret = 0n
  for (const i of buf.values()) {
    const bi = BigInt(i)
    ret = (ret << 8n) + bi
  }
  return ret;
}

If you want the resulting base36 string to have a fixed length, then you can use this bigIntToBase36 alternative:

function bigIntToBase36_2(num, exactLength){
  return num.toString(36).substring(0, exactLength).padStart(exactLength, '0');
}
0
On

There is a package that does this very well: multiformats:

npm install multiformats

And:

import { base36 } from "multiformats/bases/base36"

// you have to prepend your base36 string with "k":
const decoded = base36.decode("k" + "welcomebacklongtimenotseen")
console.log(decoded)

// don't forget to remove the leading "k":
const reEncoded = base36.encode(decoded).slice(1)
console.log(reEncoded)

This will give you the following output:

Uint8Array(17) [
   76, 249,  40, 198, 158, 140,
  198,  47,  99, 101, 143,  20,
   54,  15, 186, 196, 239
]
welcomebacklongtimenotseen