Some tweaks on base{32, 58, 64} (#1254)

* Some tweaks on base{32, 58, 64}

* align

* rationale
This commit is contained in:
Jaco
2021-11-27 12:50:40 +02:00
committed by GitHub
parent c29b12d3b5
commit 6bc648bfe0
7 changed files with 55 additions and 49 deletions
+8 -8
View File
@@ -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);
+20 -16
View File
@@ -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;
+6 -6
View File
@@ -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 -6
View File
@@ -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/);
});
});
+9 -6
View File
@@ -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);
}
}
+6 -6
View File
@@ -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
);