diff --git a/CHANGELOG.md b/CHANGELOG.md index 712ef5e87..10de0f2c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ ## 4.0.0-x -- **Breaking change** Polyfills are not provided by these libraries anymore to avoid clobbering external globals, the user should provide them if their environment is missing base functionality. +- **Breaking change** Polyfills are not injected by these libraries anymore to avoid clobbering external globals, the user should provide them if their environment is missing base functionality. Changes: - Remove built-in utility polyfills for `Array.fill`, `String.padStart`, `String.padEnd` as well as `Object.setPrototypeOf`. These should be provided by the actual user of the libraries if so required. - Update `@polkadot/wasm-crypto` library (under-the-hood cleanups) - Remove explicit crypto polyfill injection (aligning with updated `@polkadot/wasm-crypto`) -- Add `@polkadot/x-randomvalues` polyfill (for use in `@polkadot/wasm-crypto`) +- Add cross-environment `@polkadot/x-randomvalues` (for use in `@polkadot/wasm-crypto`) ## 3.7.1 Nov 1, 2020 diff --git a/packages/util-crypto/package.json b/packages/util-crypto/package.json index ad42bdf76..330396a61 100644 --- a/packages/util-crypto/package.json +++ b/packages/util-crypto/package.json @@ -28,7 +28,7 @@ "@babel/runtime": "^7.12.1", "@polkadot/networks": "^4.0.0-9", "@polkadot/util": "^4.0.0-9", - "@polkadot/wasm-crypto": "^2.0.0-10", + "@polkadot/wasm-crypto": "^2.0.1", "@polkadot/x-randomvalues": "^4.0.0-9", "base-x": "^3.0.8", "blakejs": "^1.1.0", diff --git a/packages/util-crypto/src/base32/is.ts b/packages/util-crypto/src/base32/is.ts index 56f058428..93cdf849e 100644 --- a/packages/util-crypto/src/base32/is.ts +++ b/packages/util-crypto/src/base32/is.ts @@ -3,10 +3,14 @@ import validate from './validate'; -export default function isBase32 (value?: string | null): boolean { +export function testValidator (validate: (value?: string | null, ipfsCompat?: boolean) => boolean, value?: string | null, ipfsCompat?: boolean): boolean { try { - return validate(value); + return validate(value, ipfsCompat); } catch (error) { return false; } } + +export default function isBase32 (value?: string | null, ipfsCompat?: boolean): boolean { + return testValidator(validate, value, ipfsCompat); +} diff --git a/packages/util-crypto/src/base58/is.ts b/packages/util-crypto/src/base58/is.ts index a004491b5..02ed1beb1 100644 --- a/packages/util-crypto/src/base58/is.ts +++ b/packages/util-crypto/src/base58/is.ts @@ -1,12 +1,9 @@ // Copyright 2017-2020 @polkadot/util-crypto authors & contributors // SPDX-License-Identifier: Apache-2.0 +import { testValidator } from '../base32/is'; import validate from './validate'; export default function isBase58 (value?: string | null, ipfsCompat?: boolean): boolean { - try { - return validate(value, ipfsCompat); - } catch (error) { - return false; - } + return testValidator(validate, value, ipfsCompat); } diff --git a/packages/util/src/bn/toBn.ts b/packages/util/src/bn/toBn.ts index d3c015271..7dd37c395 100644 --- a/packages/util/src/bn/toBn.ts +++ b/packages/util/src/bn/toBn.ts @@ -10,6 +10,14 @@ import isToBn from '../is/toBn'; import isHex from '../is/hex'; import hexToBn from '../hex/toBn'; +function numberToBn (value: number | ExtToBn | BN): BN { + return BN.isBN(value) + ? value + : isToBn(value) + ? value.toBn() + : new BN(value); +} + /** * @name bnToBn * @summary Creates a BN value from a BN, BigInt, string (base 10 or hex) or number input. @@ -35,9 +43,5 @@ export default function bnToBn (value?: ExtToBn | BN | Bi return new BN(value.toString()); } - return BN.isBN(value) - ? value - : isToBn(value) - ? value.toBn() - : new BN(value); + return numberToBn(value); } diff --git a/packages/util/src/bn/toU8a.ts b/packages/util/src/bn/toU8a.ts index 597c1db9d..7e915e1cb 100644 --- a/packages/util/src/bn/toU8a.ts +++ b/packages/util/src/bn/toU8a.ts @@ -12,7 +12,13 @@ interface Options extends ToBnOptions { bitLength?: number; } -function setBn (valueBn: BN, byteLength: number, { isLe, isNegative }: Options): Uint8Array { +function createEmpty (byteLength: number, options: Options): Uint8Array { + return options.bitLength === -1 + ? new Uint8Array() + : new Uint8Array(byteLength); +} + +function createValue (valueBn: BN, byteLength: number, { isLe, isNegative }: Options): Uint8Array { const output = new Uint8Array(byteLength); const bn = isNegative ? valueBn.toTwos(byteLength * 8) : valueBn; @@ -51,10 +57,8 @@ function bnToU8a (value?: ExtToBn | BN | BigInt | number : Math.ceil((options.bitLength || 0) / 8); return value - ? setBn(valueBn, byteLength, options) - : options.bitLength === -1 - ? new Uint8Array() - : new Uint8Array(byteLength); + ? createValue(valueBn, byteLength, options) + : createEmpty(byteLength, options); } export default bnToU8a; diff --git a/packages/util/src/format/formatElapsed.ts b/packages/util/src/format/formatElapsed.ts index a579e5ca2..f6b30e7a1 100644 --- a/packages/util/src/format/formatElapsed.ts +++ b/packages/util/src/format/formatElapsed.ts @@ -35,11 +35,7 @@ export default function formatElapsed (now?: Date | null, const tsNow = (now && now.getTime()) || 0; const tsValue = getValue(value || 0); - if (tsNow && tsValue) { - const elapsed = Math.max(Math.abs(tsNow - tsValue), 0) / 1000; - - return formatValue(elapsed); - } - - return '0.0s'; + return (tsNow && tsValue) + ? formatValue(Math.max(Math.abs(tsNow - tsValue), 0) / 1000) + : '0.0s'; } diff --git a/packages/util/src/logger.ts b/packages/util/src/logger.ts index aebd910ec..7dedf39b8 100644 --- a/packages/util/src/logger.ts +++ b/packages/util/src/logger.ts @@ -22,14 +22,16 @@ const logTo = { warn: 'warn' }; -function formatObject (value: Record): Record { - const result: Record = {}; +function formatOther (value: unknown): unknown { + if (value && isObject(value) && value.constructor === Object) { + return Object.keys(value).reduce((result: Record, key): Record => { + result[key] = format(value[key]); - return Object.keys(value).reduce((result, key): Record => { - result[key] = format(value[key]); + return result; + }, {}); + } - return result; - }, result); + return value; } export function format (value: unknown): unknown { @@ -39,11 +41,9 @@ export function format (value: unknown): unknown { return value.toString(); } else if (isU8a(value) || isBuffer(value)) { return u8aToHex(u8aToU8a(value)); - } else if (value && isObject(value) && value.constructor === Object) { - return formatObject(value); } - return value; + return formatOther(value); } function apply (log: LogType, type: string, values: Logger$Data): void { diff --git a/packages/util/src/u8a/toU8a.ts b/packages/util/src/u8a/toU8a.ts index eb6c3186d..3cfeeb431 100644 --- a/packages/util/src/u8a/toU8a.ts +++ b/packages/util/src/u8a/toU8a.ts @@ -8,6 +8,18 @@ import bufferToU8a from '../buffer/toU8a'; import hexToU8a from '../hex/toU8a'; import stringToU8a from '../string/toU8a'; +function convertArray (value: number[] | Uint8Array): Uint8Array { + return Array.isArray(value) + ? Uint8Array.from(value) + : value; +} + +function convertString (value: string): Uint8Array { + return isHex(value) + ? hexToU8a(value) + : stringToU8a(value); +} + /** * @name u8aToU8a * @summary Creates a Uint8Array value from a Uint8Array, Buffer, string or hex input. @@ -29,12 +41,8 @@ export default function u8aToU8a (value?: number[] | Buffer | Uint8Array | strin } else if (isBuffer(value)) { return bufferToU8a(value); } else if (isString(value)) { - return isHex(value) - ? hexToU8a(value) - : stringToU8a(value); + return convertString(value); } - return Array.isArray(value) - ? Uint8Array.from(value) - : value; + return convertArray(value); } diff --git a/packages/x-randomvalues/src/fallback.spec.ts b/packages/x-randomvalues/src/fallback.spec.ts new file mode 100644 index 000000000..9ac31caff --- /dev/null +++ b/packages/x-randomvalues/src/fallback.spec.ts @@ -0,0 +1,20 @@ +// Copyright 2017-2020 @polkadot/x-randomvalues authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import fallback from './fallback'; + +describe('fallback (insecure)', (): void => { + it('subsequent results does not match', (): void => { + expect( + fallback(new Uint8Array(32)).toString() + ).not.toEqual( + fallback(new Uint8Array(32)).toString() + ); + }); + + it('generates with the supplied length', (): void => { + expect( + fallback(new Uint8Array(128)) + ).toHaveLength(128); + }); +}); diff --git a/packages/x-randomvalues/src/fallback.ts b/packages/x-randomvalues/src/fallback.ts index ffed5c922..c9fe54d9b 100644 --- a/packages/x-randomvalues/src/fallback.ts +++ b/packages/x-randomvalues/src/fallback.ts @@ -16,11 +16,11 @@ export default function insecureRandomValues (arr: T): T let r = 0; for (let i = 0; i < arr.length; i++) { - if ((i & 0x03) === 0) { + if ((i & 0b11) === 0) { r = Math.random() * 0x100000000; } - arr[i] = (r >>> ((i & 0x03) << 3)) & 0xff; + arr[i] = (r >>> ((i & 0b11) << 3)) & 0xff; } return arr; diff --git a/packages/x-randomvalues/src/react-native.ts b/packages/x-randomvalues/src/react-native.ts index f9321e864..796ddb642 100644 --- a/packages/x-randomvalues/src/react-native.ts +++ b/packages/x-randomvalues/src/react-native.ts @@ -16,9 +16,6 @@ interface RNExt { } function getRandomValuesNative (arr: T): T { - // We may want to use a base64 decoder here, but we certainly have an issue where we - // want/need this part as lean as possible. Since Buffer is already needed elsewhere - // (an generally prevalent in JS), we rather use it as-is. return Buffer .from((NativeModules as RNExt).RNGetRandomValues.getRandomBase64(arr.length), 'base64') .reduce((arr, byte, index): T => { diff --git a/packages/x-textdecoder/src/browser.ts b/packages/x-textdecoder/src/browser.ts index a914b06d2..ac01b6850 100644 --- a/packages/x-textdecoder/src/browser.ts +++ b/packages/x-textdecoder/src/browser.ts @@ -4,5 +4,5 @@ import Fallback from './fallback'; export default typeof TextDecoder === 'undefined' - ? Fallback + ? Fallback as unknown as typeof TextDecoder : TextDecoder; diff --git a/packages/x-textdecoder/src/fallback.ts b/packages/x-textdecoder/src/fallback.ts index dad31b2c2..7b9d4441c 100644 --- a/packages/x-textdecoder/src/fallback.ts +++ b/packages/x-textdecoder/src/fallback.ts @@ -1,6 +1,7 @@ // Copyright 2017-2020 @polkadot/x-textencoder authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is very limited, only handling Ascii values export default class TextDecoder { // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-useless-constructor constructor (_: 'utf-8') { diff --git a/packages/x-textencoder/src/browser.ts b/packages/x-textencoder/src/browser.ts index f4f7246b0..39a2ba5e2 100644 --- a/packages/x-textencoder/src/browser.ts +++ b/packages/x-textencoder/src/browser.ts @@ -1,16 +1,8 @@ // Copyright 2017-2020 @polkadot/x-textencoder authors & contributors // SPDX-License-Identifier: Apache-2.0 +import Fallback from './fallback'; + export default typeof TextEncoder === 'undefined' - ? class { - encode (value: string): Uint8Array { - const u8a = new Uint8Array(value.length); - - for (let i = 0; i < value.length; i++) { - u8a[i] = value.charCodeAt(i); - } - - return u8a; - } - } + ? Fallback as unknown as typeof TextEncoder : TextEncoder; diff --git a/packages/x-textencoder/src/fallback.ts b/packages/x-textencoder/src/fallback.ts new file mode 100644 index 000000000..d14a7e1d9 --- /dev/null +++ b/packages/x-textencoder/src/fallback.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2020 @polkadot/x-textencoder authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +// This is very limited, only handling Ascii values +export default class TextEncoder { + encode (value: string): Uint8Array { + const u8a = new Uint8Array(value.length); + + for (let i = 0; i < value.length; i++) { + u8a[i] = value.charCodeAt(i); + } + + return u8a; + } +} diff --git a/packages/x-textencoder/src/node.ts b/packages/x-textencoder/src/node.ts index 21e85555f..2d8862693 100644 --- a/packages/x-textencoder/src/node.ts +++ b/packages/x-textencoder/src/node.ts @@ -3,19 +3,21 @@ import { TextEncoder as NodeTextEncoder } from 'util'; +class NodeFallback { + #encoder: NodeTextEncoder; + + constructor () { + this.#encoder = new NodeTextEncoder(); + } + + // For a Jest 26.0.1 environment, Buffer !== Uint8Array + encode (value: string): Uint8Array { + const encoded = this.#encoder.encode(value); + + return Uint8Array.from(encoded); + } +} + export default typeof TextEncoder === 'undefined' - ? class { - #encoder: NodeTextEncoder; - - constructor () { - this.#encoder = new NodeTextEncoder(); - } - - // For a Jest 26.0.1 environment, Buffer !== Uint8Array - encode (value: string): Uint8Array { - const encoded = this.#encoder.encode(value); - - return Uint8Array.from(encoded); - } - } as unknown as typeof TextEncoder + ? NodeFallback as unknown as typeof TextEncoder : TextEncoder; diff --git a/yarn.lock b/yarn.lock index 9c07f126f..1141599c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1861,7 +1861,7 @@ __metadata: "@babel/runtime": ^7.12.1 "@polkadot/networks": ^4.0.0-9 "@polkadot/util": ^4.0.0-9 - "@polkadot/wasm-crypto": ^2.0.0-10 + "@polkadot/wasm-crypto": ^2.0.1 "@polkadot/x-randomvalues": ^4.0.0-9 "@types/bn.js": ^4.11.6 "@types/create-hash": ^1.2.2 @@ -1896,13 +1896,13 @@ __metadata: languageName: unknown linkType: soft -"@polkadot/wasm-crypto@npm:^2.0.0-10": - version: 2.0.0-10 - resolution: "@polkadot/wasm-crypto@npm:2.0.0-10" +"@polkadot/wasm-crypto@npm:^2.0.1": + version: 2.0.1 + resolution: "@polkadot/wasm-crypto@npm:2.0.1" peerDependencies: "@polkadot/util": "*" "@polkadot/x-randomvalues": "*" - checksum: 1217c72172b3781608280bd6c001b199ada00cec14326b27d081e1904967fc0246f512d1cd74bd15797c87f39d2efec2d639d89c608c0bcf140ec05ff8fe7564 + checksum: eb5ff8e6614c98dcac94967e40d4e96bdd48521f26ab1f6419dbf14a9d76080880ab069ea2a1a55b1bd86b5c9f1480810bfb2ae36f3d54312a1631c0fefb04e3 languageName: node linkType: hard