Some tweaks on base{32, 58, 64} (#1254)
* Some tweaks on base{32, 58, 64}
* align
* rationale
This commit is contained in:
@@ -5,20 +5,20 @@ import { utils } from 'micro-base';
|
||||
|
||||
import { createDecode, createEncode, createIs, createValidate } from './helpers';
|
||||
|
||||
const BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz234567';
|
||||
|
||||
const BASE32_CONFIG = {
|
||||
alphabet: BASE32_ALPHABET,
|
||||
const config = {
|
||||
chars,
|
||||
coder: utils.chain(
|
||||
// We define our own chain, the default base32 has padding
|
||||
utils.radix2(5),
|
||||
utils.alphabet(BASE32_ALPHABET),
|
||||
utils.alphabet(chars),
|
||||
{
|
||||
decode: (input: string) => input.split(''),
|
||||
encode: (input: string[]) => input.join('')
|
||||
}
|
||||
),
|
||||
ipfsChar: 'b',
|
||||
ipfs: 'b',
|
||||
type: 'base32'
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ const BASE32_CONFIG = {
|
||||
* @description
|
||||
* Validates that the supplied value is valid base32, throwing exceptions if not
|
||||
*/
|
||||
export const base32Validate = createValidate(BASE32_CONFIG);
|
||||
export const base32Validate = createValidate(config);
|
||||
|
||||
/**
|
||||
* @name isBase32
|
||||
@@ -42,7 +42,7 @@ export const isBase32 = createIs(base32Validate);
|
||||
* @description
|
||||
* From the provided input, decode the base32 and return the result as an `Uint8Array`.
|
||||
*/
|
||||
export const base32Decode = createDecode(BASE32_CONFIG, base32Validate);
|
||||
export const base32Decode = createDecode(config, base32Validate);
|
||||
|
||||
/**
|
||||
* @name base32Encode
|
||||
@@ -50,4 +50,4 @@ export const base32Decode = createDecode(BASE32_CONFIG, base32Validate);
|
||||
* @description
|
||||
* From the provided input, create the base32 and return the result as a string.
|
||||
*/
|
||||
export const base32Encode = createEncode(BASE32_CONFIG);
|
||||
export const base32Encode = createEncode(config);
|
||||
|
||||
@@ -21,9 +21,9 @@ interface Coder {
|
||||
}
|
||||
|
||||
interface Config {
|
||||
alphabet: string;
|
||||
chars: string;
|
||||
coder: Coder;
|
||||
ipfsChar?: string;
|
||||
ipfs?: string;
|
||||
regex?: RegExp;
|
||||
type: string;
|
||||
}
|
||||
@@ -34,24 +34,24 @@ type EncodeFn = (value: U8aLike, ipfsCompat?: boolean) => string;
|
||||
|
||||
type ValidateFn = (value?: unknown, ipfsCompat?: boolean) => value is string;
|
||||
|
||||
export function createDecode ({ coder, ipfsChar }: Config, validate: ValidateFn): DecodeFn {
|
||||
export function createDecode ({ coder, ipfs }: Config, validate: ValidateFn): DecodeFn {
|
||||
return (value: string, ipfsCompat?: boolean): Uint8Array => {
|
||||
validate(value, ipfsCompat);
|
||||
|
||||
return coder.decode(
|
||||
ipfsChar && ipfsCompat
|
||||
ipfs && ipfsCompat
|
||||
? value.substr(1)
|
||||
: value
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function createEncode ({ coder, ipfsChar }: Config): EncodeFn {
|
||||
export function createEncode ({ coder, ipfs }: Config): EncodeFn {
|
||||
return (value: U8aLike, ipfsCompat?: boolean): string => {
|
||||
const out = coder.encode(u8aToU8a(value));
|
||||
|
||||
return ipfsChar && ipfsCompat
|
||||
? `${ipfsChar}${out}`
|
||||
return ipfs && ipfsCompat
|
||||
? `${ipfs}${out}`
|
||||
: out;
|
||||
};
|
||||
}
|
||||
@@ -66,20 +66,24 @@ export function createIs (validate: ValidateFn): ValidateFn {
|
||||
};
|
||||
}
|
||||
|
||||
export function createValidate ({ alphabet, ipfsChar, regex, type }: Config): ValidateFn {
|
||||
export function createValidate ({ chars, ipfs, type }: Config): ValidateFn {
|
||||
return (value?: unknown, ipfsCompat?: boolean): value is string => {
|
||||
assert(value && typeof value === 'string', () => `Expected non-null, non-empty ${type} string input`);
|
||||
|
||||
if (ipfsChar && ipfsCompat) {
|
||||
assert(value[0] === ipfsChar, () => `Expected ipfs-compatible ${type} to start with '${ipfsChar}'`);
|
||||
if (ipfs && ipfsCompat) {
|
||||
assert(value[0] === ipfs, () => `Expected ipfs-compatible ${type} to start with '${ipfs}'`);
|
||||
}
|
||||
|
||||
if (regex) {
|
||||
assert(regex.test(value), `Invalid ${type} encoding`);
|
||||
} else {
|
||||
for (let i = (ipfsCompat ? 1 : 0); i < value.length; i++) {
|
||||
assert(alphabet.includes(value[i]), () => `Invalid ${type} character "${value[i]}" (0x${value.charCodeAt(i).toString(16)}) at index ${i}`);
|
||||
}
|
||||
for (let i = (ipfsCompat ? 1 : 0); i < value.length; i++) {
|
||||
assert(
|
||||
chars.includes(value[i]) || (
|
||||
value[i] === '=' && (
|
||||
(i === value.length - 1) ||
|
||||
!chars.includes(value[i + 1])
|
||||
)
|
||||
),
|
||||
() => `Invalid ${type} character "${value[i]}" (0x${value.charCodeAt(i).toString(16)}) at index ${i}`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { base58 } from 'micro-base';
|
||||
|
||||
import { createDecode, createEncode, createIs, createValidate } from '../base32/helpers';
|
||||
|
||||
const BASE58_CONFIG = {
|
||||
alphabet: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
|
||||
const config = {
|
||||
chars: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
|
||||
coder: base58,
|
||||
ipfsChar: 'z',
|
||||
ipfs: 'z',
|
||||
type: 'base58'
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ const BASE58_CONFIG = {
|
||||
* @description
|
||||
* Validates that the supplied value is valid base58, throwing exceptions if not
|
||||
*/
|
||||
export const base58Validate = createValidate(BASE58_CONFIG);
|
||||
export const base58Validate = createValidate(config);
|
||||
|
||||
/**
|
||||
* @name base58Decode
|
||||
@@ -26,7 +26,7 @@ export const base58Validate = createValidate(BASE58_CONFIG);
|
||||
* @description
|
||||
* From the provided input, decode the base58 and return the result as an `Uint8Array`.
|
||||
*/
|
||||
export const base58Decode = createDecode(BASE58_CONFIG, base58Validate);
|
||||
export const base58Decode = createDecode(config, base58Validate);
|
||||
|
||||
/**
|
||||
* @name base58Encode
|
||||
@@ -34,7 +34,7 @@ export const base58Decode = createDecode(BASE58_CONFIG, base58Validate);
|
||||
* @description
|
||||
* From the provided input, create the base58 and return the result as a string.
|
||||
*/
|
||||
export const base58Encode = createEncode(BASE58_CONFIG);
|
||||
export const base58Encode = createEncode(config);
|
||||
|
||||
/**
|
||||
* @name isBase58
|
||||
|
||||
@@ -5,10 +5,9 @@ import { base64 } from 'micro-base';
|
||||
|
||||
import { createDecode, createEncode, createIs, createValidate } from '../base32/helpers';
|
||||
|
||||
const BASE64_CONFIG = {
|
||||
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
||||
const config = {
|
||||
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
||||
coder: base64,
|
||||
regex: /^(?:[A-Za-z0-9+/]{2}[A-Za-z0-9+/]{2})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,
|
||||
type: 'base64'
|
||||
};
|
||||
|
||||
@@ -18,7 +17,7 @@ const BASE64_CONFIG = {
|
||||
* @description
|
||||
* Validates that the supplied value is valid base64
|
||||
*/
|
||||
export const base64Validate = createValidate(BASE64_CONFIG);
|
||||
export const base64Validate = createValidate(config);
|
||||
|
||||
/**
|
||||
* @name isBase64
|
||||
@@ -32,7 +31,7 @@ export const isBase64 = createIs(base64Validate);
|
||||
* @description
|
||||
* From the provided input, decode the base64 and return the result as an `Uint8Array`.
|
||||
*/
|
||||
export const base64Decode = createDecode(BASE64_CONFIG, base64Validate);
|
||||
export const base64Decode = createDecode(config, base64Validate);
|
||||
|
||||
/**
|
||||
* @name base64Encode
|
||||
@@ -40,4 +39,4 @@ export const base64Decode = createDecode(BASE64_CONFIG, base64Validate);
|
||||
* @description
|
||||
* From the provided input, create the base64 and return the result as a string.
|
||||
*/
|
||||
export const base64Encode = createEncode(BASE64_CONFIG);
|
||||
export const base64Encode = createEncode(config);
|
||||
|
||||
@@ -7,6 +7,6 @@ describe('base64Validate', (): void => {
|
||||
it('validates a mixed base64 utf8 string', (): void => {
|
||||
expect(
|
||||
() => base64Validate('aGVsbG8gd29ybGQg0J/RgNC40LLQtd^GC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ==')
|
||||
).toThrow(/Invalid base64 encoding/);
|
||||
).toThrow(/Invalid base64 character "\^" \(0x5e\) at index 30/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright 2017-2021 @polkadot/x-randomvalues authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// a tiny base64 decoder for RN usage
|
||||
// A tiny base64 decoder for RN usage when atob is not available.
|
||||
// The alternative would be to rely on Buffer with 'base64'
|
||||
//
|
||||
// Uint8Array.from(Buffer.from(data, 'base64'))
|
||||
//
|
||||
// We provide an own (tiny) decoder to not have Buffer deps
|
||||
|
||||
const ALPHABET =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
export function base64Decode (data: string): Uint8Array {
|
||||
const bytes = [];
|
||||
@@ -13,11 +17,10 @@ export function base64Decode (data: string): Uint8Array {
|
||||
|
||||
for (let i = 0; i < data.length && data[i] !== '='; i++) {
|
||||
// each character represents 6 bits
|
||||
bits += 6;
|
||||
byte = (byte << 6) + ALPHABET.indexOf(data[i]);
|
||||
byte = (byte << 6) | chars.indexOf(data[i]);
|
||||
|
||||
// each byte needs to contain 8 bits
|
||||
if (bits >= 8) {
|
||||
if ((bits += 6) >= 8) {
|
||||
bytes.push((byte >>> (bits -= 8)) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { NativeModules } from 'react-native';
|
||||
import { xglobal } from '@polkadot/x-global';
|
||||
|
||||
import { base64Decode } from './base64';
|
||||
import { getRandomValues as getRandomValuesGlobal } from './browser';
|
||||
import { insecureRandomValues } from './fallback';
|
||||
|
||||
export { packageInfo } from './packageInfo';
|
||||
@@ -20,6 +21,10 @@ interface RNExt {
|
||||
}
|
||||
}
|
||||
|
||||
interface GlobalExt extends Window {
|
||||
nativeCallSyncHook: unknown;
|
||||
}
|
||||
|
||||
function getRandomValuesNative <T extends Uint8Array> (output: T): T {
|
||||
const bytes = base64Decode(
|
||||
(NativeModules as RNExt).RNGetRandomValues.getRandomBase64(output.length)
|
||||
@@ -32,15 +37,10 @@ function getRandomValuesNative <T extends Uint8Array> (output: T): T {
|
||||
return output;
|
||||
}
|
||||
|
||||
function getRandomValuesGlobal <T extends Uint8Array> (output: T): T {
|
||||
return crypto.getRandomValues(output);
|
||||
}
|
||||
|
||||
export const getRandomValues = (
|
||||
typeof xglobal.crypto === 'object' && typeof xglobal.crypto.getRandomValues === 'function'
|
||||
? getRandomValuesGlobal
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any
|
||||
: (typeof (xglobal as any).nativeCallSyncHook === 'undefined' || !NativeModules.ExpoRandom)
|
||||
: (typeof (xglobal as unknown as GlobalExt).nativeCallSyncHook === 'undefined' || !NativeModules.ExpoRandom)
|
||||
? insecureRandomValues
|
||||
: getRandomValuesNative
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user