1import { getRandomBytes } from 'expo-random'; 2 3const MAX_RANDOM_BYTES = 65536; 4 5type IntegerArray = 6 | Int8Array 7 | Uint8Array 8 | Int16Array 9 | Uint16Array 10 | Int32Array 11 | Uint32Array 12 | Uint8ClampedArray; 13 14type IntegerArrayConstructor = 15 | Int8ArrayConstructor 16 | Uint8ArrayConstructor 17 | Int16ArrayConstructor 18 | Uint16ArrayConstructor 19 | Int32ArrayConstructor 20 | Uint32ArrayConstructor 21 | Uint8ClampedArrayConstructor; 22 23/** 24 * An implementation of Crypto.getRandomValues that uses expo-random's secure random generator if 25 * available and falls back to Math.random (cryptographically insecure) when synchronous bridged 26 * methods are unavailable. 27 * 28 * See https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues 29 */ 30export default function getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray { 31 if (arguments.length < 1) { 32 throw new TypeError( 33 `An ArrayBuffer view must be specified as the destination for the random values` 34 ); 35 } 36 37 if ( 38 !(values instanceof Int8Array) && 39 !(values instanceof Uint8Array) && 40 !(values instanceof Int16Array) && 41 !(values instanceof Uint16Array) && 42 !(values instanceof Int32Array) && 43 !(values instanceof Uint32Array) && 44 !(values instanceof Uint8ClampedArray) 45 ) { 46 throw new TypeError(`The provided ArrayBuffer view is not an integer-typed array`); 47 } 48 49 if (values.byteLength > MAX_RANDOM_BYTES) { 50 throw new QuotaExceededError( 51 `The ArrayBuffer view's byte length (${values.byteLength}) exceeds the number of bytes of entropy available via this API (${MAX_RANDOM_BYTES})` 52 ); 53 } 54 55 let randomBytes: Uint8Array; 56 try { 57 // NOTE: Consider implementing `fillRandomBytes` to populate the given TypedArray directly 58 randomBytes = getRandomBytes(values.byteLength); 59 } catch { 60 // TODO: rethrow the error if it's not due to a lack of synchronous methods 61 console.warn(`Random.getRandomBytes is not supported; falling back to insecure Math.random`); 62 return getRandomValuesInsecure(values); 63 } 64 65 // Create a new TypedArray that is of the same type as the given TypedArray but is backed with the 66 // array buffer containing random bytes. This is cheap and copies no data. 67 const TypedArrayConstructor = values.constructor as IntegerArrayConstructor; 68 const randomValues = new TypedArrayConstructor( 69 randomBytes.buffer, 70 randomBytes.byteOffset, 71 values.length 72 ); 73 74 // Copy the data into the given TypedArray, letting the VM optimize the copy if possible 75 values.set(randomValues); 76 return values; 77} 78 79export function getRandomValuesInsecure<TArray extends IntegerArray>(values: TArray): TArray { 80 // Write random bytes to the given TypedArray's underlying ArrayBuffer 81 const byteView = new Uint8Array(values.buffer, values.byteOffset, values.byteLength); 82 for (let i = 0; i < byteView.length; i++) { 83 // The range of Math.random() is [0, 1) and the ToUint8 abstract operation rounds down 84 byteView[i] = Math.random() * 256; 85 } 86 return values; 87} 88 89class QuotaExceededError extends Error { 90 name = 'QuotaExceededError'; 91 code = 22; // QUOTA_EXCEEDED_ERR 92} 93