Created
June 30, 2023 15:56
-
-
Save hrishioa/1d0452415c47eb0d6a8b78cc6ddc3f4b to your computer and use it in GitHub Desktop.
Implementation for generating short, human-readable and pronounceable random strings from integers, and back again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Proquint implementation based on https://arxiv.org/html/0901.4016 | |
const uint2consonant: string[] = [ | |
'b', | |
'd', | |
'f', | |
'g', | |
'h', | |
'j', | |
'k', | |
'l', | |
'm', | |
'n', | |
'p', | |
'r', | |
's', | |
't', | |
'v', | |
'z', | |
]; | |
const uint2vowel: string[] = ['a', 'i', 'o', 'u']; | |
const MASK_FIRST4 = 0xf0000000; | |
const MASK_FIRST2 = 0xc0000000; | |
function quint2hex(inp: string): string { | |
let ret = 'x'; | |
let remaining = inp; | |
while (remaining.length > 0) { | |
let current = remaining.substring(0, 11); | |
if ( | |
!current.match(/^[bdfghjklmnprstvzaiou]{5}-[bdfghjklmnprstvzaiou]{5}$/) | |
) { | |
console.error( | |
'bad quint format detected. expected format cvcvc-cvcvc - found: ' + | |
current | |
); | |
throw 'bad quint format for: ' + inp; | |
} | |
if (remaining.length > 12) remaining = remaining.substring(12); | |
else remaining = remaining.substring(11); | |
let currentHex = quint2uint(current).toString(16); | |
while (currentHex.length < 8) currentHex = '0' + currentHex; | |
ret += currentHex; | |
} | |
return ret; | |
} | |
function hex2quint(hex: string): string { | |
let sep = '-'; | |
let ret = ''; | |
if (!hex.match(/^x(([0-9a-f]{2}){2})+$/)) { | |
console.error('Bad hex length: ' + hex.length); | |
throw 'Bad hex length: ' + hex.length; | |
} | |
let remaining = hex.substring(1); //skip x | |
while (remaining.length > 0) { | |
if (remaining.length < 8 && remaining.length % 8 != 0) { | |
console.error('Bad hex length: ' + remaining.length); | |
throw 'Bad hex length: ' + hex; | |
} | |
let current = remaining.substring(0, 8); | |
remaining = remaining.substring(8); | |
let currentQuint = uint2quint(parseInt(current, 16) >> 0); | |
ret += currentQuint; | |
if (sep) ret += sep; | |
} | |
if (ret.endsWith(sep)) ret = ret.substring(0, ret.length - sep.length); | |
return ret; | |
} | |
var crypto = require('crypto'); | |
export function getRandom32UInt() { | |
return crypto.randomBytes(4).readUInt32BE(0, true); | |
} | |
// 32 bits of entropy, add more quints to get to more | |
export function uint2quint(i: number): string { | |
let j: number; | |
let quint = ''; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
j = i & MASK_FIRST2; | |
i <<= 2; | |
j >>>= 30; | |
quint += uint2vowel[j]; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
j = i & MASK_FIRST2; | |
i <<= 2; | |
j >>>= 30; | |
quint += uint2vowel[j]; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
quint += '-'; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
j = i & MASK_FIRST2; | |
i <<= 2; | |
j >>>= 30; | |
quint += uint2vowel[j]; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
j = i & MASK_FIRST2; | |
i <<= 2; | |
j >>>= 30; | |
quint += uint2vowel[j]; | |
j = i & MASK_FIRST4; | |
i <<= 4; | |
j >>>= 28; | |
quint += uint2consonant[j]; | |
return quint; | |
} | |
export function quint2uint(quint: string): number { | |
let res = 0; | |
let remaining = quint.length; | |
let i = 0; | |
while (remaining > 0) { | |
let c = quint[i++]; | |
remaining--; | |
switch (c) { | |
/* consonants */ | |
case 'b': | |
res <<= 4; | |
res += 0; | |
break; | |
case 'd': | |
res <<= 4; | |
res += 1; | |
break; | |
case 'f': | |
res <<= 4; | |
res += 2; | |
break; | |
case 'g': | |
res <<= 4; | |
res += 3; | |
break; | |
case 'h': | |
res <<= 4; | |
res += 4; | |
break; | |
case 'j': | |
res <<= 4; | |
res += 5; | |
break; | |
case 'k': | |
res <<= 4; | |
res += 6; | |
break; | |
case 'l': | |
res <<= 4; | |
res += 7; | |
break; | |
case 'm': | |
res <<= 4; | |
res += 8; | |
break; | |
case 'n': | |
res <<= 4; | |
res += 9; | |
break; | |
case 'p': | |
res <<= 4; | |
res += 10; | |
break; | |
case 'r': | |
res <<= 4; | |
res += 11; | |
break; | |
case 's': | |
res <<= 4; | |
res += 12; | |
break; | |
case 't': | |
res <<= 4; | |
res += 13; | |
break; | |
case 'v': | |
res <<= 4; | |
res += 14; | |
break; | |
case 'z': | |
res <<= 4; | |
res += 15; | |
break; | |
/* vowels */ | |
case 'a': | |
res <<= 2; | |
res += 0; | |
break; | |
case 'i': | |
res <<= 2; | |
res += 1; | |
break; | |
case 'o': | |
res <<= 2; | |
res += 2; | |
break; | |
case 'u': | |
res <<= 2; | |
res += 3; | |
break; | |
/* separators */ | |
default: | |
break; | |
} | |
} | |
return res >>> 0; //the unsigned right shift fixes signed int issue - http://stackoverflow.com/a/17106974/1777150 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment