@signumjs/crypto
Version:
Cryptographic functions for building Signum Network apps.
106 lines • 3.98 kB
JavaScript
;
/**
* Original work Copyright (c) 2024 Signum Network
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultAlphabet = void 0;
exports.getRandomBytes = getRandomBytes;
exports.getRandomWords = getRandomWords;
exports.getRandomString = getRandomString;
const base_1 = require("./base");
/**
* Gets am Array of random bytes
*
* This random function used is based on what kind of entropy the User Agent (Browser, OS) returns.
* Depending on its runtime environment, it may use https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
* or https://nodejs.org/api/webcrypto.html#cryptogetrandomvaluestypedarray
*
* This means for that for browsers a secure context, i.e. https is required to make it work!
*
* @param length Length of array
*
* @return An array of bytes of given length
*
*
* @category random
*/
function getRandomBytes(length) {
return base_1.Crypto.adapter.getRandomValues(new Uint8Array(length));
}
/**
* Gets an array of random words from a dictionary
*
* > Usually 2048 words are sufficient. This method does not support dictionaries with more than 65536 words
*
* @param count The number of words generated
* @param dictionary An array of strings from where the random words are picked
*
* @return An array with _count_ words
* @throws An error if dictionary's length is smaller than _count_
*
* @category random
*/
function getRandomWords(count, dictionary) {
if (count > dictionary.length) {
throw new Error('Too few words in dictionary');
}
if (dictionary.length > 2 ** 32) {
throw new Error('Dictionary must have less than 65536 words');
}
const indices = new Set();
const mask = 2 ** 32 - (2 ** 32 % dictionary.length);
const randomBytes = new Uint8Array(4);
const cp = base_1.Crypto.adapter;
while (indices.size < count) {
cp.getRandomValues(randomBytes);
const randomUint32 = ((randomBytes[0] << 24) | (randomBytes[1] << 16) | (randomBytes[2] << 8) | randomBytes[3]) >>> 0;
const randomIndex = randomUint32 % dictionary.length;
// avoid random bias
if (randomUint32 < mask) {
indices.add(randomUint32 % dictionary.length);
}
}
return Array.from(indices).map(index => dictionary[index]);
}
/**
* Default Alphabet
* @see {@link getRandomString}
*
* @category random
*/
exports.DefaultAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
* Gets a string of random characters from a given alphabet
*
* @param length The length of the resulting string
* @param alphabet An string with characters used for random picks. Default Alphabet is alphanumeric without special chars
*
* @return A string with random characters of given length
* @throws An error if alphabet is empty or too large (2^16 chars is limit)
*
* @category random
*/
function getRandomString(length, alphabet = exports.DefaultAlphabet) {
if (alphabet.length === 0) {
throw new Error('Alphabet must not be empty.');
}
const MaxLength = 65536; // 2**16
if (alphabet.length >= MaxLength) { // just supporting UInt16
throw new Error(`Alphabet is too large. Alphabet must be less than ${MaxLength} characters long.`);
}
const maxNonBiasedValue = Math.floor(MaxLength / alphabet.length) * alphabet.length;
const indices = base_1.Crypto.adapter.getRandomValues(new Uint8Array(length * 2));
const uint16View = new Uint16Array(indices.buffer);
let result = '';
for (let i = 0; i < length; i++) {
let value = uint16View[i];
// Reject values that would create bias
while (value >= maxNonBiasedValue) {
const newBytes = base_1.Crypto.adapter.getRandomValues(new Uint8Array(2));
value = new Uint16Array(newBytes.buffer)[0];
}
result += alphabet[value % alphabet.length];
}
return result;
}
//# sourceMappingURL=random.js.map