From b474b212142bea58b41e32b83ee6487409668110 Mon Sep 17 00:00:00 2001 From: turiyadev <130711224+turiyadev@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:01:16 -0600 Subject: [PATCH] Add safe mode (base24) (#20) * Use recommended LCG parameter * Add base24 mode * Update dependencies --- README.md | 237 ++++++------ package.json | 14 +- pnpm-lock.yaml | 275 +++++++------- src/Keymask.ts | 65 +++- src/KeymaskEncoder.ts | 119 ++++-- src/KeymaskGenerator.ts | 93 +++-- src/bufferUtils.ts | 2 +- test/Keymask.test.ts | 541 ++++++++++++++++++-------- test/KeymaskEncoder.test.ts | 694 +++++++++++++++++++++++++++++----- test/KeymaskGenerator.test.ts | 454 ++++++++++++++++++++++ util/harmonics.js | 198 +++++++--- util/{lcg41.js => lcg.js} | 5 +- 12 files changed, 2035 insertions(+), 662 deletions(-) rename util/{lcg41.js => lcg.js} (96%) diff --git a/README.md b/README.md index d9112d7..128c2b5 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,28 @@ random-looking character strings. Values are first transformed by a Linear Congruential Generator (a fast, reversible pseudo-random number generator), then the result is encoded using a -Base41 encoding scheme. Minimum output length(s) can be specified, otherwise -the keymask length will scale as needed. +URL-safe character encoding. Minimum output length(s) can be specified, +otherwise the keymask length will scale as needed. Each instance can be +personalized using a unique seed value, resulting in unique keymask mappings. -The `Keymask` instance can be personalized using a 256-bit seed value. As long -as this value is kept secret, it will be difficult for anyone who doesn't know -the seed to reverse map the encoded values. +There are two modes of operation. The standard mode encodes each 32 bits to 6 +characters, using a base-41 encoding scheme. Since this uses a mix of uppercase +and lowercase letters, it is only suitable when case-sensitivity can be +guaranteed. The more restrictive "safe" mode encodes each 32 bits to 7 +characters using a base-24 encoding scheme. This can be used in +case-insensitive settings, such as a hostname or subdomain. ## Motivation -Serial numbers and sequential database IDs are extremely useful, however, when -displayed publicly they can leak information about system internals, such as -how old a given record is, or the frequency of record creation (see -["German tank problem"](https://search.brave.com/search?q=german+tank+problem)). +When serial numbers or sequential database IDs are displayed publicly, they can +leak information about system internals, such as how old a given record is, or +the frequency of record creation. `Keymask` encodes serial numbers in such a way that they can be displayed to -end-users without revealing these kinds of details. +end users without revealing these kinds of details. Since they are meant to be +displayed publicly, measures are taken to avoid potentially offensive character +combinations (by omitting vowels from its encoding alphabet, `Keymask` +generally does not output recognizable words of any kind). ## Installation @@ -39,70 +45,46 @@ using your preferred package manager (`npm i keymask`, `yarn add keymask`, ## Usage The module exports three classes, `Keymask`, `KeymaskGenerator` (the LCG) and -`KeymaskEncoder` (the base41 encoder). These can be used independently of each -other, but for simple use cases, the main `Keymask` class is typically all you -need. +`KeymaskEncoder` (the character encoder). These can be used independently of +each other, but for simple use cases, the main `Keymask` class is typically all +you need. -The `Keymask` class constructor can optionally be passed an object containing -various settings. When no settings are provided, the resulting class instance -will encode variable-length outputs (the output length will depend on the -magnitude of the input value), and decoded values will be returned as either a -`number`, a `bigint` or an `ArrayBuffer`, depending again on the magnitude of -the value. +**Example (Default settings)** -**Options** +```JavaScript +import { Keymask } from "keymask"; + +const keymask = new Keymask(); + +const masked = keymask.mask(123456789); // "wMjMGR" +const unmask = keymask.unmask("wMjMGR"); // 123456789 +``` + +## Options ```Typescript type KeymaskOptions = { seed?: ArrayBuffer; size?: number | number[]; + safe?: boolean; type?: "number" | "bigint" | "string" | "integer" | "buffer"; encoder?: KeymaskEncoder; }; ``` -**Example (Default settings, no seed, variable outputs)** - -```JavaScript -import { Keymask } from "keymask"; - -const keymask = new Keymask(); - -const masked1 = keymask.mask(12); // "X" -const unmask1 = keymask.unmask("X"); // 12 - -const masked2 = keymask.mask(123456789); // "wMjMGR" -const unmask2 = keymask.unmask("wMjMGR"); // 123456789 - -const masked3 = keymask.mask(1234567890123456789n); // "csMvrvQsMdVG" -const unmask3 = keymask.unmask("csMvrvQsMdVG"); // 1234567890123456789n - -const masked4 = keymask.mask(new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 -]).buffer); // "NpRcJcFtscDkyxmQkD" - -const unmask4 = kaymask.unmask("NpRcJcFtscDkyxmQkD"); -// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as ArrayBuffer -``` - -Since `v0.9.2`, the input value to the `mask` function can also be provided as -a `string`. This will be converted internally to a `BigInt` and treated as a -numeric value. For example, in the above example `keymask.mask("123456789")` -would also mask to `"wMjMGR"`. - ### `seed` If a `seed` value is provided, it will be used to initialize LCG offsets -and/or shuffle the Base41 encoding alphabet. This allows different `Keymask` -instances to produce completely different outputs. +and shuffle the encoding alphabet. This allows different `Keymask` instances to +produce completely different outputs. -The `seed` should be provided as an `ArrayBuffer`, and should be either 32 or 8 -bytes long depending on whether a preconfigured `KeymaskEncoder` is used (see -`encoder` option below). When no `encoder` is provided, the full `32` bytes -are required. +The `seed` should be provided as an `ArrayBuffer`, and should be 32 bytes long +for standard mode, or 20 bytes for safe mode. Note, however, that if a +preconfigured `KeymaskEncoder` is used (see `encoder` option below), then the +seed supplied to the `Keymask` constructor only needs to be 8 bytes long. -Providing a randomized `seed` is generally recommended, as this makes the -mappings between inputs and outputs highly unpredictable. However, the `seed` +Providing a randomized `seed` is highly recommended, as this makes the mappings +between inputs and outputs relatively unpredictable. Note that the `seed` should typically not change for the lifetime of your application, as this would render it impossible to unmask previously masked values. @@ -118,21 +100,8 @@ const keymask = new Keymask({ ]).buffer }); -const masked1 = keymask.mask(12); // "P" -const unmask1 = keymask.unmask("P"); // 12 - -const masked2 = keymask.mask(123456789); // "GVSYBp" -const unmask2 = keymask.unmask("GVSYBp"); // 123456789 - -const masked3 = keymask.mask(1234567890123456789n); // "BVTFGGfNNqCX" -const unmask3 = keymask.unmask("BVTFGGfNNqCX"); // 1234567890123456789n - -const masked4 = keymask.mask(new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 -]).buffer); // "FYNGFBkhgnvBChrHQg" - -const unmask4 = kaymask.unmask("FYNGFBkhgnvBChrHQg"); -// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as ArrayBuffer +const masked = keymask.mask(123456789); // "ycQXDm" +const unmask = keymask.unmask("ycQXDm"); // 123456789 ``` ### `size` @@ -143,19 +112,18 @@ numbers to the `size` option. If a single number is provided, it defines the automatically, with additional characters added as needed. If an array of numbers is provided, they represent successive allowable output -lengths. If the highest provided `size` is less than `12`, then longer outputs -will scale automatically, as above. If you do not want this auto-scaling -behaviour, be sure to include `12` as the last value in the `size` array. +lengths. If the highest provided `size` is less than `12` (`14` in safe mode), +then longer outputs will scale automatically, as above. If you do not want +this auto-scaling behavior, be sure to include `12` ( or `14` in safe mode) as +the last value in the `size` array. -Note that long inputs (greater than 64 bits) are processed in 64-bit blocks; -the `size` setting applies only to the final block. If your inputs are +Note that long inputs (greater than 64 bits) are processed in 64-bit blocks, +and the `size` setting applies only to the final block. If your inputs are word-aligned (to some multiple of 64 bits), it is generally recommended to -provide the setting `size: 12`, as this will ensure that unmasked values are -always a multiple of 64 bits long, even when the final block happens to contain -a value that can be encoded in fewer characters. - -The `size`(s) should be between `1` and `12`, inclusive. Other values will be -silently ignored. +provide the setting `size: 12` (or `size: 14` in safe mode), as this will +ensure that unmasked values are always a multiple of 64 bits long, even when +the final block happens to contain a value that can be encoded in fewer +characters. This setting should generally not be changed for the lifetime of your application, as this may interfere with the ability to unmask previously masked @@ -170,55 +138,57 @@ const keymask = new Keymask({ size: [5, 10] }); -const masked1 = keymask.mask(12); // "MxHqP" -const unmask1 = keymask.unmask("MxHqP"); // 12 +const masked = keymask.mask(123456789); // "xMMJdmtCcf" +const unmask = keymask.unmask("xMMJdmtCcf"); // 123456789 +``` + +### `safe` + +Safe mode is triggered using a boolean flag on the options object. -const masked2 = keymask.mask(123456789); // "xMMJdmtCcf" -const unmask2 = keymask.unmask("xMMJdmtCcf"); // 123456789 +**Example (Safe mode)** -const masked3 = keymask.mask(1234567890123456789n); // "csMvrvQsMdVG" -const unmask3 = keymask.unmask("csMvrvQsMdVG"); // 1234567890123456789n +```JavaScript +import { Keymask } from "keymask"; -const masked4 = keymask.mask(new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 -]).buffer); // "NpRcJcFtscDkbZZXpWbVyd" +const keymask = new Keymask({ + safe: true +}); -const unmask4 = kaymask.unmask("NpRcJcFtscDkbZZXpWbVyd"); -// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as ArrayBuffer +const masked = keymask.mask(123456789); // "mfwbdg" +const unmask = keymask.unmask("mfwbdg"); // 123456789 ``` ### `type` -By default, keymasks between 1 and 10 characters long will unmask to a -`number`, while 11- or 12-character keymasks will be unmask to a `BigInt` and -anything longer than 12 characters (=64 bits) will be returned as an -`ArrayBuffer`. Since there is no way of knowing in advance how long the -supplied keymask will be, the return type is a union type: +By default, `Keymask` unmasks values as a `number` when possible, while larger +values, up to 64 bits, will unmask to a `BigInt` and above 64 bits they will be +returned as an `ArrayBuffer`. Since there is no way of knowing in advance how +long the supplied keymask will be, the return type is a union type: ```TypeScript type KeymaskData = number | bigint | string | ArrayBuffer; ``` -There may very well be times when you know the expected return type in advance, -or you want to consistently cast the result to a specified type. In such cases, -you can supply the expected or desired type using the `type` option. If -provided, it must conform to one of the following strings: +There may well be times when you know the expected return type in advance, or +you want to cast the result to a specified type. In such cases, you can supply +the expected or desired type using the `type` option. If provided, it must +conform to one of the following strings (otherwise it will fall back to the +default behavior). - `"number"` The result will be returned optimistically as a `number` type (no -type conversion is done, so be sure to only use this with short keymasks). +type conversion is done, so type safety is not guaranteed; be sure to only use +this with short keymasks). - `"bigint"` The result will be converted to a `BigInt` regardless of its magnitude. - `"string"` The result will be converted to a `BigInt` then cast to a `string` regardless of its magnitude. - `"integer"` Similar to the default behaviour, but values larger than 64 bits -will be returned as a `BigInt` rather than an `ArrayBuffer`. +will be returned as a `BigInt` rather than an `ArrayBuffer`. The return type is +`number | bigint`. - `"buffer"` The result will be converted to an `ArrayBuffer` regardless of its magnitude. -These conversions are type-safe, so when calling from TypeScript, there is -generally no need to further cast the result before using it (except for -`"integer"` which returns a `number | bigint` union type). - **Example (Specify the return type)** ```JavaScript @@ -230,11 +200,11 @@ const bigintKeymask = new Keymask({ type: "bigint" }); const stringKeymask = new Keymask({ type: "string" }); const bufferKeymask = new Keymask({ type: "buffer" }); -const unmask1 = defaultKeymask.unmask("GVSYBp"); // 123456789 as KeymaskData -const unmask2 = numberKeymask.unmask("GVSYBp"); // 123456789 as number -const unmask3 = bigintKeymask.unmask("GVSYBp"); // 123456789n as bigint -const unmask4 = stringKeymask.unmask("GVSYBp"); // "123456789" -const unmask5 = bufferKeymask.unmask("GVSYBp"); +const unmask1 = defaultKeymask.unmask("wMjMGR"); // 123456789 as KeymaskData +const unmask2 = numberKeymask.unmask("wMjMGR"); // 123456789 as number +const unmask3 = bigintKeymask.unmask("wMjMGR"); // 123456789n as bigint +const unmask4 = stringKeymask.unmask("wMjMGR"); // "123456789" +const unmask5 = bufferKeymask.unmask("wMjMGR"); // [21, 205, 91, 7, 0, 0, 0, 0] as ArrayBuffer ``` @@ -261,30 +231,30 @@ const sharedEncoder = new KeymaskEncoder(new Uint8Array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 -])); +]).buffer); const keymask1 = new Keymask({ encoder: sharedEncoder, - seed: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), + seed: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]).buffer, size: 5 }); const keymask2 = new Keymask({ encoder: sharedEncoder, - seed: new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88]), + seed: new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88]).buffer, size: 5 }); -const mask1a = keymask1.mask(1); // "bgLtm" -const mask1b = keymask1.mask(2); // "FVyHP" -const mask1c = keymask1.mask(3); // "vrMZL" +const mask1a = keymask1.mask(1); // "yBtrf" +const mask1b = keymask1.mask(2); // "qTvWY" +const mask1c = keymask1.mask(3); // "ZkgVt" -const mask2a = keymask2.mask(1); // "LjjWh" -const mask2b = keymask2.mask(2); // "RHRkJ" -const mask2c = keymask2.mask(3); // "xmXrp" +const mask2a = keymask2.mask(1); // "tHHCd" +const mask2b = keymask2.mask(2); // "LWLzR" +const mask2c = keymask2.mask(3); // "xfQkX" ``` -## Why Base41? +## Why Base 41? Base41 is a highly efficient encoding for 16-, 32- and 64-bit values, comparable to Base57 or Base85 in this respect. Whereas Base85 encodes 32 bits @@ -317,6 +287,25 @@ otherwise offensive character combinations. The encoding is therefore both URL-safe and "safe for all audiences". In addition, it is free of commonly confused character sets, including `O` / `0` and `l` / `I` / `1`. +## Why Base 24? + +The motivating use case for the "safe" mode is URL subdomains, as this portion +of the URL is case-insensitive. Thus, we only have 36 characters at our +disposal (the lowercase Latin ASCII alphabet and the 10 numerals). Base41 is +therefore not an option. + +If we are to maintain the "safe for all audiences" standard, we must remove the +five vowels and any numbers that are commonly substituded for vowels in 1337 +speak, pagercode and similar conventions. These include `0` for `o`, `1` for +`i`, `3` for `e`, and, depending on dialect, `4`, `6` or `8` for `a`, leaving +a maximum of 25-27 encoding characters. + +Efficient encodings for 64-bit values include Base31 (13 characters per 64 +bits) and Base24 (14 characters per 64 bits), and, given the above, the latter +is the most efficient encoding that can be used. Keymask's "safe" encoding +alphabet therefore consists of the lowercase Latin alphabet, minus the five +vowels and the letter `l`, plus the numbers `2`, `5`, `7` and `9`. + ## Performance On commodity hardware (2020 M1 Macbook Air), a single invocation of diff --git a/package.json b/package.json index 7d063c1..cbbe34b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keymask", - "version": "0.9.2", + "version": "0.10.0", "description": "Map sequential IDs or serial numbers to random-looking strings", "type": "module", "exports": { @@ -52,17 +52,17 @@ }, "bugs": "https://github.com/keymask/keymask-js/issues", "devDependencies": { - "@rollup/plugin-typescript": "^11.1.5", + "@rollup/plugin-typescript": "^11.1.6", "@types/mocha": "^10.0.6", - "@types/node": "^20.10.4", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", + "@types/node": "^20.11.13", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", "c8": "^8.0.1", "eslint": "^8.56.0", - "eslint-plugin-jsdoc": "^46.9.1", + "eslint-plugin-jsdoc": "^46.10.1", "mocha": "^10.2.0", "rimraf": "^5.0.5", - "rollup": "^4.9.1", + "rollup": "^4.9.6", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-dts": "^6.1.0", "ts-node": "^10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eda38e7..9549dc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,20 +6,20 @@ settings: devDependencies: '@rollup/plugin-typescript': - specifier: ^11.1.5 - version: 11.1.5(rollup@4.9.1)(tslib@2.6.2)(typescript@5.3.3) + specifier: ^11.1.6 + version: 11.1.6(rollup@4.9.6)(tslib@2.6.2)(typescript@5.3.3) '@types/mocha': specifier: ^10.0.6 version: 10.0.6 '@types/node': - specifier: ^20.10.4 - version: 20.10.4 + specifier: ^20.11.13 + version: 20.11.13 '@typescript-eslint/eslint-plugin': - specifier: ^6.14.0 - version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3) + specifier: ^6.20.0 + version: 6.20.0(@typescript-eslint/parser@6.20.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': - specifier: ^6.14.0 - version: 6.14.0(eslint@8.56.0)(typescript@5.3.3) + specifier: ^6.20.0 + version: 6.20.0(eslint@8.56.0)(typescript@5.3.3) c8: specifier: ^8.0.1 version: 8.0.1 @@ -27,8 +27,8 @@ devDependencies: specifier: ^8.56.0 version: 8.56.0 eslint-plugin-jsdoc: - specifier: ^46.9.1 - version: 46.9.1(eslint@8.56.0) + specifier: ^46.10.1 + version: 46.10.1(eslint@8.56.0) mocha: specifier: ^10.2.0 version: 10.2.0 @@ -36,17 +36,17 @@ devDependencies: specifier: ^5.0.5 version: 5.0.5 rollup: - specifier: ^4.9.1 - version: 4.9.1 + specifier: ^4.9.6 + version: 4.9.6 rollup-plugin-cleanup: specifier: ^3.2.1 - version: 3.2.1(rollup@4.9.1) + version: 3.2.1(rollup@4.9.6) rollup-plugin-dts: specifier: ^6.1.0 - version: 6.1.0(rollup@4.9.1)(typescript@5.3.3) + version: 6.1.0(rollup@4.9.6)(typescript@5.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.10.4)(typescript@5.3.3) + version: 10.9.2(@types/node@20.11.13)(typescript@5.3.3) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -146,11 +146,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@humanwhocodes/config-array@0.11.13: - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 2.0.1 + '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: @@ -162,8 +162,8 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@2.0.1: - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} dev: true /@isaacs/cliui@8.0.2: @@ -192,8 +192,8 @@ packages: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + /@jridgewell/trace-mapping@0.3.22: + resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 @@ -224,7 +224,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 + fastq: 1.17.0 dev: true /@pkgjs/parseargs@0.11.0: @@ -234,8 +234,8 @@ packages: dev: true optional: true - /@rollup/plugin-typescript@11.1.5(rollup@4.9.1)(tslib@2.6.2)(typescript@5.3.3): - resolution: {integrity: sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==} + /@rollup/plugin-typescript@11.1.6(rollup@4.9.6)(tslib@2.6.2)(typescript@5.3.3): + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 @@ -247,14 +247,14 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.9.1) + '@rollup/pluginutils': 5.1.0(rollup@4.9.6) resolve: 1.22.8 - rollup: 4.9.1 + rollup: 4.9.6 tslib: 2.6.2 typescript: 5.3.3 dev: true - /@rollup/pluginutils@5.1.0(rollup@4.9.1): + /@rollup/pluginutils@5.1.0(rollup@4.9.6): resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} peerDependencies: @@ -266,107 +266,107 @@ packages: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.9.1 + rollup: 4.9.6 dev: true - /@rollup/rollup-android-arm-eabi@4.9.1: - resolution: {integrity: sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==} + /@rollup/rollup-android-arm-eabi@4.9.6: + resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.9.1: - resolution: {integrity: sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==} + /@rollup/rollup-android-arm64@4.9.6: + resolution: {integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.9.1: - resolution: {integrity: sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==} + /@rollup/rollup-darwin-arm64@4.9.6: + resolution: {integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.9.1: - resolution: {integrity: sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==} + /@rollup/rollup-darwin-x64@4.9.6: + resolution: {integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.9.1: - resolution: {integrity: sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==} + /@rollup/rollup-linux-arm-gnueabihf@4.9.6: + resolution: {integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.9.1: - resolution: {integrity: sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==} + /@rollup/rollup-linux-arm64-gnu@4.9.6: + resolution: {integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.9.1: - resolution: {integrity: sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==} + /@rollup/rollup-linux-arm64-musl@4.9.6: + resolution: {integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.9.1: - resolution: {integrity: sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==} + /@rollup/rollup-linux-riscv64-gnu@4.9.6: + resolution: {integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.9.1: - resolution: {integrity: sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==} + /@rollup/rollup-linux-x64-gnu@4.9.6: + resolution: {integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.9.1: - resolution: {integrity: sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==} + /@rollup/rollup-linux-x64-musl@4.9.6: + resolution: {integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.9.1: - resolution: {integrity: sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==} + /@rollup/rollup-win32-arm64-msvc@4.9.6: + resolution: {integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.9.1: - resolution: {integrity: sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==} + /@rollup/rollup-win32-ia32-msvc@4.9.6: + resolution: {integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.9.1: - resolution: {integrity: sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==} + /@rollup/rollup-win32-x64-msvc@4.9.6: + resolution: {integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==} cpu: [x64] os: [win32] requiresBuild: true @@ -405,8 +405,8 @@ packages: resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} dev: true - /@types/node@20.10.4: - resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} + /@types/node@20.11.13: + resolution: {integrity: sha512-5G4zQwdiQBSWYTDAH1ctw2eidqdhMJaNsiIDKHFr55ihz5Trl2qqR8fdrT732yPBho5gkNxXm67OxWFBqX9aPg==} dependencies: undici-types: 5.26.5 dev: true @@ -415,8 +415,8 @@ packages: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true - /@typescript-eslint/eslint-plugin@6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==} + /@typescript-eslint/eslint-plugin@6.20.0(@typescript-eslint/parser@6.20.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -427,11 +427,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 6.14.0 - '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.14.0 + '@typescript-eslint/parser': 6.20.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.20.0 + '@typescript-eslint/type-utils': 6.20.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.20.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.20.0 debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 graphemer: 1.4.0 @@ -444,8 +444,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.14.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==} + /@typescript-eslint/parser@6.20.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -454,10 +454,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.14.0 - '@typescript-eslint/types': 6.14.0 - '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.14.0 + '@typescript-eslint/scope-manager': 6.20.0 + '@typescript-eslint/types': 6.20.0 + '@typescript-eslint/typescript-estree': 6.20.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.20.0 debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 typescript: 5.3.3 @@ -465,16 +465,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager@6.14.0: - resolution: {integrity: sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==} + /@typescript-eslint/scope-manager@6.20.0: + resolution: {integrity: sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.14.0 - '@typescript-eslint/visitor-keys': 6.14.0 + '@typescript-eslint/types': 6.20.0 + '@typescript-eslint/visitor-keys': 6.20.0 dev: true - /@typescript-eslint/type-utils@6.14.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==} + /@typescript-eslint/type-utils@6.20.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -483,8 +483,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 6.20.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.20.0(eslint@8.56.0)(typescript@5.3.3) debug: 4.3.4(supports-color@8.1.1) eslint: 8.56.0 ts-api-utils: 1.0.3(typescript@5.3.3) @@ -493,13 +493,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types@6.14.0: - resolution: {integrity: sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==} + /@typescript-eslint/types@6.20.0: + resolution: {integrity: sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.14.0(typescript@5.3.3): - resolution: {integrity: sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==} + /@typescript-eslint/typescript-estree@6.20.0(typescript@5.3.3): + resolution: {integrity: sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -507,11 +507,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.14.0 - '@typescript-eslint/visitor-keys': 6.14.0 + '@typescript-eslint/types': 6.20.0 + '@typescript-eslint/visitor-keys': 6.20.0 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 + minimatch: 9.0.3 semver: 7.5.4 ts-api-utils: 1.0.3(typescript@5.3.3) typescript: 5.3.3 @@ -519,8 +520,8 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.14.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==} + /@typescript-eslint/utils@6.20.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -528,9 +529,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.14.0 - '@typescript-eslint/types': 6.14.0 - '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.20.0 + '@typescript-eslint/types': 6.20.0 + '@typescript-eslint/typescript-estree': 6.20.0(typescript@5.3.3) eslint: 8.56.0 semver: 7.5.4 transitivePeerDependencies: @@ -538,11 +539,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys@6.14.0: - resolution: {integrity: sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==} + /@typescript-eslint/visitor-keys@6.20.0: + resolution: {integrity: sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.14.0 + '@typescript-eslint/types': 6.20.0 eslint-visitor-keys: 3.4.3 dev: true @@ -550,21 +551,21 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /acorn-jsx@5.3.2(acorn@8.11.2): + /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.11.2 + acorn: 8.11.3 dev: true - /acorn-walk@8.3.1: - resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} dev: true - /acorn@8.11.2: - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -884,11 +885,11 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-jsdoc@46.9.1(eslint@8.56.0): - resolution: {integrity: sha512-11Ox5LCl2wY7gGkp9UOyew70o9qvii1daAH+h/MFobRVRNcy7sVlH+jm0HQdgcvcru6285GvpjpUyoa051j03Q==} + /eslint-plugin-jsdoc@46.10.1(eslint@8.56.0): + resolution: {integrity: sha512-x8wxIpv00Y50NyweDUpa+58ffgSAI5sqe+zcZh33xphD0AVh+1kqr1ombaTRb7Fhpove1zfUuujlX9DWWBP5ag==} engines: {node: '>=16'} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 dependencies: '@es-joy/jsdoccomment': 0.41.0 are-docs-informative: 0.0.2 @@ -926,7 +927,7 @@ packages: '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.56.0 - '@humanwhocodes/config-array': 0.11.13 + '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 @@ -968,8 +969,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.11.2 - acorn-jsx: 5.3.2(acorn@8.11.2) + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 dev: true @@ -1028,8 +1029,8 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + /fastq@1.17.0: + resolution: {integrity: sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==} dependencies: reusify: 1.0.4 dev: true @@ -1403,8 +1404,8 @@ packages: is-unicode-supported: 0.1.0 dev: true - /lru-cache@10.1.0: - resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} dev: true @@ -1590,7 +1591,7 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} dependencies: - lru-cache: 10.1.0 + lru-cache: 10.2.0 minipass: 7.0.4 dev: true @@ -1675,18 +1676,18 @@ packages: glob: 10.3.10 dev: true - /rollup-plugin-cleanup@3.2.1(rollup@4.9.1): + /rollup-plugin-cleanup@3.2.1(rollup@4.9.6): resolution: {integrity: sha512-zuv8EhoO3TpnrU8MX8W7YxSbO4gmOR0ny06Lm3nkFfq0IVKdBUtHwhVzY1OAJyNCIAdLiyPnOrU0KnO0Fri1GQ==} engines: {node: ^10.14.2 || >=12.0.0} peerDependencies: rollup: '>=2.0' dependencies: js-cleanup: 1.2.0 - rollup: 4.9.1 + rollup: 4.9.6 rollup-pluginutils: 2.8.2 dev: true - /rollup-plugin-dts@6.1.0(rollup@4.9.1)(typescript@5.3.3): + /rollup-plugin-dts@6.1.0(rollup@4.9.6)(typescript@5.3.3): resolution: {integrity: sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw==} engines: {node: '>=16'} peerDependencies: @@ -1694,7 +1695,7 @@ packages: typescript: ^4.5 || ^5.0 dependencies: magic-string: 0.30.5 - rollup: 4.9.1 + rollup: 4.9.6 typescript: 5.3.3 optionalDependencies: '@babel/code-frame': 7.23.5 @@ -1706,24 +1707,26 @@ packages: estree-walker: 0.6.1 dev: true - /rollup@4.9.1: - resolution: {integrity: sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==} + /rollup@4.9.6: + resolution: {integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + dependencies: + '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.9.1 - '@rollup/rollup-android-arm64': 4.9.1 - '@rollup/rollup-darwin-arm64': 4.9.1 - '@rollup/rollup-darwin-x64': 4.9.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.9.1 - '@rollup/rollup-linux-arm64-gnu': 4.9.1 - '@rollup/rollup-linux-arm64-musl': 4.9.1 - '@rollup/rollup-linux-riscv64-gnu': 4.9.1 - '@rollup/rollup-linux-x64-gnu': 4.9.1 - '@rollup/rollup-linux-x64-musl': 4.9.1 - '@rollup/rollup-win32-arm64-msvc': 4.9.1 - '@rollup/rollup-win32-ia32-msvc': 4.9.1 - '@rollup/rollup-win32-x64-msvc': 4.9.1 + '@rollup/rollup-android-arm-eabi': 4.9.6 + '@rollup/rollup-android-arm64': 4.9.6 + '@rollup/rollup-darwin-arm64': 4.9.6 + '@rollup/rollup-darwin-x64': 4.9.6 + '@rollup/rollup-linux-arm-gnueabihf': 4.9.6 + '@rollup/rollup-linux-arm64-gnu': 4.9.6 + '@rollup/rollup-linux-arm64-musl': 4.9.6 + '@rollup/rollup-linux-riscv64-gnu': 4.9.6 + '@rollup/rollup-linux-x64-gnu': 4.9.6 + '@rollup/rollup-linux-x64-musl': 4.9.6 + '@rollup/rollup-win32-arm64-msvc': 4.9.6 + '@rollup/rollup-win32-ia32-msvc': 4.9.6 + '@rollup/rollup-win32-x64-msvc': 4.9.6 fsevents: 2.3.3 dev: true @@ -1787,14 +1790,14 @@ packages: deprecated: Please use @jridgewell/sourcemap-codec instead dev: true - /spdx-exceptions@2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + /spdx-exceptions@2.4.0: + resolution: {integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==} dev: true /spdx-expression-parse@4.0.0: resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} dependencies: - spdx-exceptions: 2.3.0 + spdx-exceptions: 2.4.0 spdx-license-ids: 3.0.16 dev: true @@ -1896,7 +1899,7 @@ packages: typescript: 5.3.3 dev: true - /ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3): + /ts-node@10.9.2(@types/node@20.11.13)(typescript@5.3.3): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -1915,9 +1918,9 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.10.4 - acorn: 8.11.2 - acorn-walk: 8.3.1 + '@types/node': 20.11.13 + acorn: 8.11.3 + acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -1967,7 +1970,7 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.22 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 dev: true diff --git a/src/Keymask.ts b/src/Keymask.ts index d074bf2..98f2fcb 100644 --- a/src/Keymask.ts +++ b/src/Keymask.ts @@ -2,7 +2,7 @@ import { KeymaskEncoder } from "./KeymaskEncoder"; import { KeymaskGenerator } from "./KeymaskGenerator"; import { padBuffer, toBigInt, toBuffer } from "./bufferUtils"; -const limits: bigint[] = [ +const limits12: bigint[] = [ 0n, 41n, 1021n, @@ -18,14 +18,34 @@ const limits: bigint[] = [ 18446744073709551557n ]; +const limits14: bigint[] = [ + 0n, + 23n, + 509n, + 8191n, + 262139n, + 4194301n, + 134217689n, + 4294967291n, + 68719476731n, + 2199023255531n, + 35184372088777n, + 1125899906842597n, + 36028797018963913n, + 576460752303423433n, + 18446744073709551557n +]; + /** * Determine the encoding length of the provided value. * @param {number | bigint} value The value to be encoded. * @param {number[]} sizes The available encoding lengths. + * @param {number} block The encoding block size. * @returns {number} The smallest available encoding length. */ -function encodingLength(value: number | bigint, sizes: number[]): number { - let length = 12; +function encodingLength(value: number | bigint, sizes: number[], block: number): number { + const limits = block === 12 ? limits12 : limits14; + let length = block; let size = 0; value = typeof value === "bigint" ? value : BigInt(value); for (let i = 0; i < sizes.length; i++) { @@ -35,8 +55,8 @@ function encodingLength(value: number | bigint, sizes: number[]): number { break; } } - if (length === 12 && value >= limits[size]) { - for (let i = sizes[sizes.length - 1] + 1; i < 12; i++) { + if (length === block && value >= limits[size]) { + for (let i = sizes[sizes.length - 1] + 1; i < block; i++) { if (value < limits[i]) { length = i; break; @@ -61,6 +81,7 @@ export type KeymaskValue = export type KeymaskOptions = { seed?: ArrayBuffer; size?: number | number[]; + safe?: boolean; type?: T; encoder?: KeymaskEncoder; }; @@ -81,6 +102,7 @@ export class Keymask { * @typedef {object} KeymaskOptions * @property {?ArrayBuffer} seed The seed value. * @property {?(number | number[])} size The minimum encoding size or allowable sizes. + * @property {?boolean} safe Use safe mode (Base24 encoding, no uppercase letters). * @property {?KeymaskType} type Optionally specify the `unmask()` return type. * @property {?KeymaskEncoder} encoder Custom/shared `KeymaskEncoder` instance. */ @@ -93,25 +115,29 @@ export class Keymask { if (options.encoder) { this.encoder = options.encoder; - this.generator = new KeymaskGenerator(options.seed); + this.generator = new KeymaskGenerator(options.seed, options.safe); } else if (options.seed) { - this.encoder = new KeymaskEncoder(options.seed.slice(0, 24)); + this.encoder = new KeymaskEncoder(options.seed, options.safe); this.generator = new KeymaskGenerator( - options.seed.byteLength < 32 ? void 0 : options.seed.slice(24) + options.seed.byteLength < (options.safe ? 20 : 32) + ? void 0 + : options.seed.slice(options.safe ? 12 : 24), + options.safe ); } else { - this.encoder = new KeymaskEncoder(); - this.generator = new KeymaskGenerator(); + this.encoder = new KeymaskEncoder(void 0, options.safe); + this.generator = new KeymaskGenerator(void 0, options.safe); } const sizes = options.size; + const max = this.encoder.block; if (Array.isArray(sizes)) { this.sizes = sizes - .filter((size) => size > 0 && size < 13) + .filter((size) => size > 0 && size <= max) .sort((a, b) => a - b); } else { - this.sizes = [sizes && sizes > 0 && sizes < 13 ? sizes : 1]; + this.sizes = [sizes && sizes > 0 && sizes <= max ? sizes : 1]; } this.type = options.type as T; @@ -128,13 +154,13 @@ export class Keymask { value = padBuffer(value.slice(0), 8); const data = new DataView(value); const final = value.byteLength - 8; - length = encodingLength(data.getBigUint64(final, true), this.sizes); + length = encodingLength(data.getBigUint64(final, true), this.sizes, this.encoder.block); for (let i = 0; i < value.byteLength; i += 8) { data.setBigUint64( i, this.generator.next( data.getBigUint64(i, true), - i < final ? 12 : length, + i < final ? this.encoder.block : length, true) as bigint, true ); @@ -144,7 +170,7 @@ export class Keymask { if (typeof value === "string") { value = BigInt(value); } - length = encodingLength(value, this.sizes); + length = encodingLength(value, this.sizes, this.encoder.block); return this.encoder.encode( this.generator.next(value, length), length @@ -159,16 +185,17 @@ export class Keymask { */ unmask(value: string): KeymaskValue { const result = this.encoder.decode(value); + const block = this.encoder.block; if (result instanceof ArrayBuffer) { const data = new DataView(result); - const length = value.length % 12 || 12; + const length = value.length % block || block; const final = result.byteLength - 8; for (let i = 0; i <= final; i += 8) { data.setBigUint64( i, this.generator.previous( data.getBigUint64(i, true), - i < final ? 12 : length, + i < final ? block : length, true) as bigint, true ); @@ -176,12 +203,12 @@ export class Keymask { if ( this.type === "bigint" || this.type === "string" || - this.type === "integer" && value.length > 10 + this.type === "integer" && this.generator.bigIntOutput(value.length) ) { const n = toBigInt(data); return (this.type === "string" ? n.toString() : n) as KeymaskValue; } - if (length < 12) { + if (length < block) { const bytes = new Uint8Array(result); let end = result.byteLength - 1; while (end >= 0 && bytes[end] === 0) { diff --git a/src/KeymaskEncoder.ts b/src/KeymaskEncoder.ts index fc2cd49..251f6ca 100644 --- a/src/KeymaskEncoder.ts +++ b/src/KeymaskEncoder.ts @@ -1,36 +1,43 @@ import { blockLimit, clampBuffer, padBuffer, toBigInt } from "./bufferUtils"; -const alphabet: string[] = [ +const alpha41: string[] = [ "B", "C", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z", "b", "c", "d", "f", "g", "h", "j", "k", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z" ]; +const alpha24: string[] = [ + "b", "c", "d", "f", "g", "h", "j", "k", "m", "n", + "p", "q", "r", "s", "t", "v", "w", "x", "y", "z", + "2", "5", "7", "9" +]; + const factors: number[] = [ 0x055c3050, // 41*40*39*38*37 - 0x02b24b00, // 36*35*34*33*32 - 0x1f990650, // 31*30*29*28*27*26 - 0x0799adc0, // 25*24*23*22*21*20 - 0x0f230dc0, // 19*18*17*16*15*14*13 - 0x1c8cfc00 // 12*11*10*9*8*7*6*5*4*3*2*1 + 0x53971500, // 36*35*34*33*32*31 + 0x197b6830, // 30*29*28*27*26*25 + 0x05c6b740, // 24*23*22*21*20*19 + 0x098f6700, // 18*17*16*15*14*13*12 + 0x02611500 // 11*10*9*8*7*6*5*4*3*2*1 ]; /** * Shuffle the encoding alphabat using the Fisher-Yates algorithm and the * provided seed. * @param {?ArrayBuffer} seed The seed value. + * @param {?boolean} safe Use safe mode (base24). * @returns {string[]} The shuffled encoding alphabet. */ -function shuffleAlphabet(seed?: ArrayBuffer): string[] { - const chars = alphabet.slice(0); +function shuffleAlphabet(seed?: ArrayBuffer, safe?: boolean): string[] { + const chars = (safe ? alpha24 : alpha41).slice(0); if (seed) { const source = new DataView(seed); - let mod = 41; + let mod = safe ? 24 : 41; let value: number; let n: number; let swap: string; - factors.forEach((range, i) => { + (safe ? factors.slice(3) : factors).forEach((range, i) => { value = source.getUint32(i * 4, true) % range; while (range > 1) { range /= mod--; @@ -47,18 +54,24 @@ function shuffleAlphabet(seed?: ArrayBuffer): string[] { /** * Encode a numeric value (maximum 53 bits). + * @param {number} base The encoding base (24 or 41). * @param {number} value The value to encode. * @param {string[]} chars The encoding characters. * @param {?number} length The target output length. * @returns {string} The encoded value. */ -function encodeNumber(value: number, chars: string[], length: number = 0): string { +function encodeNumber( + base: number, + value: number, + chars: string[], + length: number = 0 +): string { let n: number; let result = ""; for (let i = 0; length === 0 && value > 0 || i < length; i++) { if (value) { - n = value % 41; - value = Math.floor(value / 41); + n = value % base; + value = Math.floor(value / base); } else { n = 0; } @@ -69,18 +82,25 @@ function encodeNumber(value: number, chars: string[], length: number = 0): strin /** * Encode a bigint value (maximum 64 bits). + * @param {number} base The encoding base (24 or 41). * @param {bigint} value The value to encode. * @param {string[]} chars The encoding characters. * @param {?number} length The target output length. * @returns {string} The encoded value. */ -function encodeBigInt(value: bigint, chars: string[], length: number = 0): string { +function encodeBigInt( + base: number, + value: bigint, + chars: string[], + length: number = 0 +): string { + const b = BigInt(base); let n: number; let result = ""; for (let i = 0; length === 0 && value > 0n || i < length; i++) { if (value) { - n = Number(value % 41n); - value /= 41n; + n = Number(value % b); + value /= b; } else { n = 0; } @@ -91,14 +111,15 @@ function encodeBigInt(value: bigint, chars: string[], length: number = 0): strin /** * Restore a `number` value from raw decoded data. + * @param {number} base The encoding base (24 or 41). * @param {Uint8Array} values Raw values to be restored. * @returns {number} Restored value. */ -function restoreNumber(values: Uint8Array): number { +function restoreNumber(base: number, values: Uint8Array): number { let i = values.length - 1; let n = values[i--]; while (i >= 0) { - n *= 41; + n *= base; n += values[i--]; } return n; @@ -106,37 +127,52 @@ function restoreNumber(values: Uint8Array): number { /** * Restore a `bigint` value from raw decoded data. + * @param {number} base The encoding base (24 or 41). * @param {Uint8Array} values Raw values to be restored. * @returns {bigint} Restored value. */ -function restoreBigInt(values: Uint8Array): bigint { +function restoreBigInt(base: number, values: Uint8Array): bigint { + const b = BigInt(base); let i = values.length - 1; let n = BigInt(values[i--]); while (i >= 0) { - n *= 41n; + n *= b; n += BigInt(values[i--]); } return n; } /** - * Provides base41 `encode` and `decode` functions, optionally using a shuffled - * encoding alphabet. + * Provides base24 or base41 `encode` and `decode` functions, using the requested + * encoding base and optionally using a shuffled encoding alphabet. */ export class KeymaskEncoder { private chars: string[]; + private base: 24 | 41; + private limit: 10 | 11; + block: 12 | 14; /** - * Create a new `KeymaskEncoder` using the provided `seed`. If provided, the - * `seed` will be used to shuffle the encoding alphabet. + * Create a new `KeymaskEncoder` using the provided encoding `base` and `seed`. + * If provided, the `seed` will be used to shuffle the encoding alphabet. * @param {?ArrayBuffer} seed The seed value. + * @param {?boolean} safe Use safe mode (base24). */ - constructor(seed?: ArrayBuffer) { - this.chars = shuffleAlphabet(seed ? clampBuffer(seed, 24) : seed); + constructor(seed?: ArrayBuffer, safe?: boolean) { + if (safe) { + this.base = 24; + this.block = 14; + this.limit = 11; + } else { + this.base = 41; + this.block = 12; + this.limit = 10; + } + this.chars = shuffleAlphabet(seed ? clampBuffer(seed, safe ? 12 : 24) : seed, safe); } /** - * Encode the given value as base41. + * Encode the given value as base24 or base41. * @param {number | bigint | ArrayBuffer} value The value to encode. * @param {?number} length The target output length in characters. For inputs * greater than 64 bits, this applies to the final encoding block. @@ -144,7 +180,7 @@ export class KeymaskEncoder { */ encode(value: number | bigint | ArrayBuffer, length?: number): string { if (typeof value === "number") { - return encodeNumber(value, this.chars, length); + return encodeNumber(this.base, value, this.chars, length); } else if (typeof value === "bigint") { if (value >= blockLimit) { @@ -153,13 +189,16 @@ export class KeymaskEncoder { while (value > 0n) { next = value / blockLimit; result += encodeBigInt( - value % blockLimit, this.chars, next > 0n ? 12 : length + this.base, + value % blockLimit, + this.chars, next > 0n ? this.block : length ); value = next; } return result; } - return encodeBigInt(value, this.chars, length); + return encodeBigInt(this.base, value, this.chars, length); + } value = padBuffer(value, 8); const data = new DataView(value); @@ -167,7 +206,9 @@ export class KeymaskEncoder { let result = ""; for (let i = 0; i <= last; i++) { result += encodeBigInt( - data.getBigUint64(i * 8, true), this.chars, i < last ? 12 : length + this.base, + data.getBigUint64(i * 8, true), + this.chars, i < last ? this.block : length ); } return result; @@ -186,18 +227,18 @@ export class KeymaskEncoder { values[i] = this.chars.indexOf(value.charAt(i)); } - if (length <= 10 && !bigint) { - return restoreNumber(values); + if (length <= this.limit && !bigint) { + return restoreNumber(this.base, values); - } else if (length <= 12) { - return restoreBigInt(values); + } else if (length <= this.block) { + return restoreBigInt(this.base, values); } else { - values = new Uint8Array(padBuffer(values.buffer, 12)); - const output = new ArrayBuffer(Math.round(values.byteLength * 2 / 3)); + values = new Uint8Array(padBuffer(values.buffer, this.block)); + const output = new ArrayBuffer(Math.round(values.byteLength * 8 / this.block)); const data = new DataView(output); - for (let i = 0, j = 0; i < output.byteLength; i += 8, j += 12) { - data.setBigUint64(i, restoreBigInt(values.slice(j, j + 12)), true); + for (let i = 0, j = 0; i < output.byteLength; i += 8, j += this.block) { + data.setBigUint64(i, restoreBigInt(this.base, values.slice(j, j + this.block)), true); } if (bigint) { return toBigInt(data); diff --git a/src/KeymaskGenerator.ts b/src/KeymaskGenerator.ts index 9a606e2..e3ab420 100644 --- a/src/KeymaskGenerator.ts +++ b/src/KeymaskGenerator.ts @@ -6,7 +6,7 @@ import { clampBuffer } from "./bufferUtils"; // low harmonic factors of the modulus (1...8). See `util/harmonics.js`. This // means that they are least likely to present repeating patterns across short // sequences of values. -const lcgMap: (number[] | bigint[])[] = [ +const lcgMap41: (number[] | bigint[])[] = [ [], [41, 22, 28], [1021, 65, 377], // 2^10 - 3 @@ -22,36 +22,69 @@ const lcgMap: (number[] | bigint[])[] = [ [18446744073709551557n, 9044836419713972268n, 13891176665706064842n] // 2^64 - 59 ]; +const lcgMap24: (number[] | bigint[])[] = [ + [], + [23, 7, 10], + [509, 110, 236], // 2^9 - 3 + [8191, 1716, 5580], // 2^13 - 1 + [262139, 92717, 166972], // 2^18 - 5 + [4194301, 1731287, 2040406], // 2^22 - 3 + [134217689, 45576512, 70391260], // 2^27 - 39 + [4294967291n, 1588635695n, 3870709308n], // 2^32 - 5 + [68719476731n, 40162435147n, 45453986995n], // 2^36 - 5 + [2199023255531n, 717943173063n, 1319743354064n], // 2^41 - 21 + [35184372088777n, 11850386302026n, 18586042069168n], // 2^45 - 55 + [1125899906842597n, 605985299432352n, 791038363307311n], // 2^50 - 27 + [36028797018963913n, 19708881949174686n, 32182684885571630n], // 2^55 - 55 + [576460752303423433n, 287514719519235431n, 346764851511064641n], // 2^59 - 55 + [18446744073709551557n, 9044836419713972268n, 13891176665706064842n] // 2^64 - 59 +]; + /** * Provides an array of Multiplicative Linear Congruential Generators whose - * moduli are compatible with Base41 encoding outputs 1 to 12 characters in - * length. + * moduli are compatible with KeymaskEncoder. */ export class KeymaskGenerator { private offsets: (number | bigint)[]; + private safe: boolean; /** * Create a Generator with custom offsets. * @param {?ArrayBuffer} seed 8-byte seed value. + * @param {?boolean} safe Use safe mode (base24). */ - constructor(seed?: ArrayBuffer) { - this.offsets = new Array(13) as (number | bigint)[]; + constructor(seed?: ArrayBuffer, safe: boolean = false) { + this.safe = safe; + this.offsets = new Array(safe ? 15 : 13) as (number | bigint)[]; if (seed) { const data = new DataView(clampBuffer(seed, 8)); const n32 = (data.getUint32(0, true) ^ data.getUint32(4, true)) >>> 0; const n64 = data.getBigUint64(0, true); - this.offsets[1] = n32 % 40; - this.offsets[2] = n32 % 1020; - this.offsets[3] = n32 % 65520; - this.offsets[4] = n32 % 2097142; - this.offsets[5] = n32 % 67108858; - this.offsets[6] = BigInt(n32 % 4294967290); - this.offsets[7] = n64 % 137438953446n; - this.offsets[8] = n64 % 4398046511092n; - this.offsets[9] = n64 % 281474976710596n; - this.offsets[10] = n64 % 9007199254740880n; - this.offsets[11] = n64 % 288230376151711716n; - this.offsets[12] = n64 % 18446744073709551556n; + if (safe) { + for (let i = 1; i < 15; i++) { + if (i < 7) { + this.offsets[i] = n32 % (lcgMap24[i][0] - 1); + } else if (i > 7) { + this.offsets[i] = n64 % (lcgMap24[i][0] - 1n); + } else { + this.offsets[7] = BigInt(n32 % 4294967290); + } + } + } else { + for (let i = 1; i < 13; i++) { + if (i < 6) { + this.offsets[i] = n32 % (lcgMap41[i][0] - 1); + } else if (i > 6) { + this.offsets[i] = n64 % (lcgMap41[i][0] - 1n); + } else { + this.offsets[6] = BigInt(n32 % 4294967290); + } + } + } + } else if (safe) { + for (let i = 1; i < 15; i++) { + this.offsets[i] = i < 7 ? 0 : 0n; + } } else { for (let i = 1; i < 13; i++) { this.offsets[i] = i < 6 ? 0 : 0n; @@ -59,6 +92,10 @@ export class KeymaskGenerator { } } + bigIntOutput(length: number): boolean { + return !this.safe && length > 10 || this.safe && length > 11; + } + /** * Calculate the next value in the given MLCG sequence. * @param {number | bigint} value The starting value. @@ -68,19 +105,20 @@ export class KeymaskGenerator { */ next(value: number | bigint, range: number, bigint: boolean = false): number | bigint { if (!value) { - return range > 10 || bigint ? 0n : 0; + return this.bigIntOutput(range) || bigint ? 0n : 0; } - const mod = lcgMap[range][0]; - const mult = lcgMap[range][1]; + const map = this.safe ? lcgMap24 : lcgMap41; + const mod = map[range][0]; + const mult = map[range][1]; const offset = this.offsets[range]; - if (range > 5) { + if (!this.safe && range > 5 || this.safe && range > 6) { if (typeof value === "number") { value = BigInt(value); } value = value * mult % mod + offset; value = value < mod ? value : value - mod + 1n; - return range > 10 || bigint ? value : Number(value); + return this.bigIntOutput(range) || bigint ? value : Number(value); } if (typeof value === "bigint") { value = Number(value); @@ -99,19 +137,20 @@ export class KeymaskGenerator { */ previous(value: number | bigint, range: number, bigint: boolean = false): number | bigint { if (!value) { - return range > 10 || bigint ? 0n : 0; + return this.bigIntOutput(range) || bigint ? 0n : 0; } - const mod = lcgMap[range][0]; - const mult = lcgMap[range][2]; + const map = this.safe ? lcgMap24 : lcgMap41 + const mod = map[range][0]; + const mult = map[range][2]; const offset = this.offsets[range]; - if (range > 5) { + if (!this.safe && range > 5 || this.safe && range > 6) { if (typeof value === "number") { value = BigInt(value); } value -= offset; value = (value > 0 ? value : value + mod - 1n) * mult % mod; - return range > 10 || bigint ? value : Number(value); + return this.bigIntOutput(range) || bigint ? value : Number(value); } if (typeof value === "bigint") { value = Number(value); diff --git a/src/bufferUtils.ts b/src/bufferUtils.ts index e09c2df..4cc202f 100644 --- a/src/bufferUtils.ts +++ b/src/bufferUtils.ts @@ -27,7 +27,7 @@ export function clampBuffer(buffer: ArrayBuffer, size: number): ArrayBuffer { return buffer; } const clamped = new ArrayBuffer(size); - new Uint8Array(clamped).set(new Uint8Array(buffer)); + new Uint8Array(clamped).set(new Uint8Array(buffer).slice(0, size)); return clamped; } diff --git a/test/Keymask.test.ts b/test/Keymask.test.ts index ea95e32..57d0ef5 100644 --- a/test/Keymask.test.ts +++ b/test/Keymask.test.ts @@ -99,6 +99,117 @@ describe("Keymask", () => { }); }); + describe("Safe mode", () => { + const keymask = new Keymask({ safe: true }); + + it("should mask and unmask in range 1", () => { + equal(keymask.mask(1), "k"); + equal(keymask.mask(22), "w"); + equal(keymask.unmask("k"), 1); + equal(keymask.unmask("w"), 22); + }); + + it("should mask and unmask in range 2", () => { + equal(keymask.mask(23), "t2"); + equal(keymask.mask(508), "vw"); + equal(keymask.unmask("t2"), 23); + equal(keymask.unmask("vw"), 508); + }); + + it("should mask and unmask in range 3", () => { + equal(keymask.mask(509), "tbn"); + equal(keymask.mask(8190), "zhq"); + equal(keymask.unmask("tbn"), 509); + equal(keymask.unmask("zhq"), 8190); + }); + + it("should mask and unmask in range 4", () => { + equal(keymask.mask(8191), "wccd"); + equal(keymask.mask(262138), "jfjr"); + equal(keymask.unmask("wccd"), 8191); + equal(keymask.unmask("jfjr"), 262138); + }); + + it("should mask and unmask in range 5", () => { + equal(keymask.mask(262139), "tm2wh"); + equal(keymask.mask(4194300), "tcgpk"); + equal(keymask.unmask("tm2wh"), 262139); + equal(keymask.unmask("tcgpk"), 4194300); + }); + + it("should mask and unmask in range 6", () => { + equal(keymask.mask(4194301), "9zn2vj"); + equal(keymask.mask(134217688), "n7dgfq"); + equal(keymask.unmask("9zn2vj"), 4194301); + equal(keymask.unmask("n7dgfq"), 134217688); + }); + + it("should mask and unmask in range 7", () => { + equal(keymask.mask(134217689), "jjc2rjd"); + equal(keymask.mask(4294967290), "rmrd5ft"); + equal(keymask.unmask("jjc2rjd"), 134217689); + equal(keymask.unmask("rmrd5ft"), 4294967290); + }); + + it("should mask and unmask in range 8", () => { + equal(keymask.mask(4294967291), "7hwj72gt"); + equal(keymask.mask(68719476730), "wghjnphj"); + equal(keymask.unmask("7hwj72gt"), 4294967291); + equal(keymask.unmask("wghjnphj"), 68719476730); + }); + + it("should mask and unmask in range 9", () => { + equal(keymask.mask(68719476731), "xhkztn29v"); + equal(keymask.mask(2199023255530), "2tfrbg7ps"); + equal(keymask.unmask("xhkztn29v"), 68719476731); + equal(keymask.unmask("2tfrbg7ps"), 2199023255530); + }); + + it("should mask and unmask in range 10", () => { + equal(keymask.mask(2199023255531), "wyjnjyf5pn"); + equal(keymask.mask(35184372088776), "k5tvhvs9zm"); + equal(keymask.unmask("wyjnjyf5pn"), 2199023255531); + equal(keymask.unmask("k5tvhvs9zm"), 35184372088776); + }); + + it("should mask and unmask in range 11", () => { + equal(keymask.mask(35184372088777), "5yqxcwjgcfv"); + equal(keymask.mask(1125899906842596), "s5xtjhkjzgm"); + equal(keymask.unmask("5yqxcwjgcfv"), 35184372088777); + equal(keymask.unmask("s5xtjhkjzgm"), 1125899906842596); + }); + + it("should mask and unmask in range 12", () => { + equal(keymask.mask(1125899906842597n), "kzd2kmfzbksd"); + equal(keymask.mask(36028797018963912n), "zxdhgvqmsnxp"); + equal(keymask.unmask("kzd2kmfzbksd"), 1125899906842597n); + equal(keymask.unmask("zxdhgvqmsnxp"), 36028797018963912n); + }); + + it("should mask and unmask in range 13", () => { + equal(keymask.mask(36028797018963913n), "fm7mspv5nt2mq"); + equal(keymask.mask(576460752303423432n), "dgwh9ks2mj55k"); + equal(keymask.unmask("fm7mspv5nt2mq"), 36028797018963913n); + equal(keymask.unmask("dgwh9ks2mj55k"), 576460752303423432n); + }); + + it("should mask and unmask in range 14", () => { + equal(keymask.mask(576460752303423433n), "cfkgghxwykkjsj"); + equal(keymask.mask(18446744073709551556n), "xfhpdkrn9fvpxp"); + equal(keymask.unmask("cfkgghxwykkjsj"), 576460752303423433n); + equal(keymask.unmask("xfhpdkrn9fvpxp"), 18446744073709551556n); + }); + + it("should process binary data", () => { + const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer; + const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer; + equal(keymask.mask(buffer1), "gjph95tz2792txgrfmn9dvvhn9vm"); + equal(keymask.mask(buffer2), "9xcxhygbpfrvngnm"); + deepEqual(keymask.unmask("gjph95tz2792txgrfmn9dvvhn9vm"), buffer1); + deepEqual(keymask.unmask("9xcxhygbpfrvngnm"), buffer2); + }); + }); + describe("Integer output", () => { const keymask = new Keymask({ type: "integer" }); @@ -859,96 +970,96 @@ describe("Keymask", () => { }); it("should mask and unmask in range 1", () => { - equal(keymask.mask(1), "m"); - equal(keymask.mask(40), "r"); - equal(keymask.unmask("m"), 1); - equal(keymask.unmask("r"), 40); + equal(keymask.mask(1), "w"); + equal(keymask.mask(40), "F"); + equal(keymask.unmask("w"), 1); + equal(keymask.unmask("F"), 40); }); it("should mask and unmask in range 2", () => { - equal(keymask.mask(41), "yv"); - equal(keymask.mask(1020), "Jh"); - equal(keymask.unmask("yv"), 41); - equal(keymask.unmask("Jh"), 1020); + equal(keymask.mask(41), "fC"); + equal(keymask.mask(1020), "Sn"); + equal(keymask.unmask("fC"), 41); + equal(keymask.unmask("Sn"), 1020); }); it("should mask and unmask in range 3", () => { - equal(keymask.mask(1021), "ZQJ"); - equal(keymask.mask(65520), "PGR"); - equal(keymask.unmask("ZQJ"), 1021); - equal(keymask.unmask("PGR"), 65520); + equal(keymask.mask(1021), "BgS"); + equal(keymask.mask(65520), "pqH"); + equal(keymask.unmask("BgS"), 1021); + equal(keymask.unmask("pqH"), 65520); }); it("should mask and unmask in range 4", () => { - equal(keymask.mask(65521), "XQPR"); - equal(keymask.mask(2097142), "XNPv"); - equal(keymask.unmask("XQPR"), 65521); - equal(keymask.unmask("XNPv"), 2097142); + equal(keymask.mask(65521), "NgpH"); + equal(keymask.mask(2097142), "NXpC"); + equal(keymask.unmask("NgpH"), 65521); + equal(keymask.unmask("NXpC"), 2097142); }); it("should mask and unmask in range 5", () => { - equal(keymask.mask(2097143), "LphvK"); - equal(keymask.mask(67108858), "MmDJr"); - equal(keymask.unmask("LphvK"), 2097143); - equal(keymask.unmask("MmDJr"), 67108858); + equal(keymask.mask(2097143), "PGnCW"); + equal(keymask.mask(67108858), "vwmSF"); + equal(keymask.unmask("PGnCW"), 2097143); + equal(keymask.unmask("vwmSF"), 67108858); }); it("should mask and unmask in range 6", () => { - equal(keymask.mask(67108859), "QXFspG"); - equal(keymask.mask(4294967290), "vThLNK"); - equal(keymask.unmask("QXFspG"), 67108859); - equal(keymask.unmask("vThLNK"), 4294967290); + equal(keymask.mask(67108859), "gNQsGq"); + equal(keymask.mask(4294967290), "CKnPXW"); + equal(keymask.unmask("gNQsGq"), 67108859); + equal(keymask.unmask("CKnPXW"), 4294967290); }); it("should mask and unmask in range 7", () => { - equal(keymask.mask(4294967291), "nHJmmZh"); - equal(keymask.mask(137438953446), "WKkwKXc"); - equal(keymask.unmask("nHJmmZh"), 4294967291); - equal(keymask.unmask("WKkwKXc"), 137438953446); + equal(keymask.mask(4294967291), "rJSwwBn"); + equal(keymask.mask(137438953446), "MWkxWNj"); + equal(keymask.unmask("rJSwwBn"), 4294967291); + equal(keymask.unmask("MWkxWNj"), 137438953446); }); it("should mask and unmask in range 8", () => { - equal(keymask.mask(137438953447), "cmDdGmNq"); - equal(keymask.mask(4398046511092), "HHVtfsDK"); - equal(keymask.unmask("cmDdGmNq"), 137438953447); - equal(keymask.unmask("HHVtfsDK"), 4398046511092); + equal(keymask.mask(137438953447), "jwmhqwXc"); + equal(keymask.mask(4398046511092), "JJVRdsmW"); + equal(keymask.unmask("jwmhqwXc"), 137438953447); + equal(keymask.unmask("JJVRdsmW"), 4398046511092); }); it("should mask and unmask in range 9", () => { - equal(keymask.mask(4398046511093), "wcJsTPvwc"); - equal(keymask.mask(281474976710596), "hchhyhrQF"); - equal(keymask.unmask("wcJsTPvwc"), 4398046511093); - equal(keymask.unmask("hchhyhrQF"), 281474976710596); + equal(keymask.mask(4398046511093), "xjSsKpCxj"); + equal(keymask.mask(281474976710596), "njnnfnFgQ"); + equal(keymask.unmask("xjSsKpCxj"), 4398046511093); + equal(keymask.unmask("njnnfnFgQ"), 281474976710596); }); it("should mask and unmask in range 10", () => { - equal(keymask.mask(281474976710597), "ZhLhMstdWJ"); - equal(keymask.mask(9007199254740880), "PYcmhfLbWF"); - equal(keymask.unmask("ZhLhMstdWJ"), 281474976710597); - equal(keymask.unmask("PYcmhfLbWF"), 9007199254740880); + equal(keymask.mask(281474976710597), "BnPnvsRhMS"); + equal(keymask.mask(9007199254740880), "pYjwndPTMQ"); + equal(keymask.unmask("BnPnvsRhMS"), 281474976710597); + equal(keymask.unmask("pYjwndPTMQ"), 9007199254740880); }); it("should mask and unmask in range 11", () => { - equal(keymask.mask(9007199254740881n), "HDbsJnNYFvP"); - equal(keymask.mask(288230376151711716n), "wLxfWFcTLTB"); - equal(keymask.unmask("HDbsJnNYFvP"), 9007199254740881n); - equal(keymask.unmask("wLxfWFcTLTB"), 288230376151711716n); + equal(keymask.mask(9007199254740881n), "JmTsSrXYQCp"); + equal(keymask.mask(288230376151711716n), "xPDdMQjKPKL"); + equal(keymask.unmask("JmTsSrXYQCp"), 9007199254740881n); + equal(keymask.unmask("xPDdMQjKPKL"), 288230376151711716n); }); it("should mask and unmask in range 12", () => { - equal(keymask.mask(288230376151711717n), "zcXxvFjvCZvP"); - equal(keymask.mask(18446744073709551556n), "QwLqQXvtsmYd"); - equal(keymask.unmask("zcXxvFjvCZvP"), 288230376151711717n); - equal(keymask.unmask("QwLqQXvtsmYd"), 18446744073709551556n); + equal(keymask.mask(288230376151711717n), "zjNDCQyCbBCp"); + equal(keymask.mask(18446744073709551556n), "gxPcgNCRswYh"); + equal(keymask.unmask("zjNDCQyCbBCp"), 288230376151711717n); + equal(keymask.unmask("gxPcgNCRswYh"), 18446744073709551556n); }); it("should process binary data", () => { const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer; const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer; - equal(keymask.mask(buffer1), "tTxGLKMvKqStrdjpjGNGWsLW"); - equal(keymask.mask(buffer2), "zHYscNMqWmVTGq"); - deepEqual(keymask.unmask("tTxGLKMvKqStrdjpjGNGWsLW"), buffer1); - deepEqual(keymask.unmask("zHYscNMqWmVTGq"), buffer2); + equal(keymask.mask(buffer1), "RKDqPWvCWctRFhyGyqXqMsPM"); + equal(keymask.mask(buffer2), "zJYsjXvcMwVKqc"); + deepEqual(keymask.unmask("RKDqPWvCWctRFhyGyqXqMsPM"), buffer1); + deepEqual(keymask.unmask("zJYsjXvcMwVKqc"), buffer2); }); }); @@ -962,96 +1073,96 @@ describe("Keymask", () => { }); it("should mask and unmask in range 1", () => { - equal(keymask.mask(1), "m"); - equal(keymask.mask(40), "r"); - equal(keymask.unmask("m"), 1); - equal(keymask.unmask("r"), 40); + equal(keymask.mask(1), "w"); + equal(keymask.mask(40), "F"); + equal(keymask.unmask("w"), 1); + equal(keymask.unmask("F"), 40); }); it("should mask and unmask in range 2", () => { - equal(keymask.mask(41), "fq"); - equal(keymask.mask(1020), "Bv"); - equal(keymask.unmask("fq"), 41); - equal(keymask.unmask("Bv"), 1020); + equal(keymask.mask(41), "dc"); + equal(keymask.mask(1020), "LC"); + equal(keymask.unmask("dc"), 41); + equal(keymask.unmask("LC"), 1020); }); it("should mask and unmask in range 3", () => { - equal(keymask.mask(1021), "fWm"); - equal(keymask.mask(65520), "vwL"); - equal(keymask.unmask("fWm"), 1021); - equal(keymask.unmask("vwL"), 65520); + equal(keymask.mask(1021), "dMw"); + equal(keymask.mask(65520), "CxP"); + equal(keymask.unmask("dMw"), 1021); + equal(keymask.unmask("CxP"), 65520); }); it("should mask and unmask in range 4", () => { - equal(keymask.mask(65521), "JmQf"); - equal(keymask.mask(2097142), "JYQq"); - equal(keymask.unmask("JmQf"), 65521); - equal(keymask.unmask("JYQq"), 2097142); + equal(keymask.mask(65521), "Swgd"); + equal(keymask.mask(2097142), "SYgc"); + equal(keymask.unmask("Swgd"), 65521); + equal(keymask.unmask("SYgc"), 2097142); }); it("should mask and unmask in range 5", () => { - equal(keymask.mask(2097143), "HTbhF"); - equal(keymask.mask(67108858), "vMygR"); - equal(keymask.unmask("HTbhF"), 2097143); - equal(keymask.unmask("vMygR"), 67108858); + equal(keymask.mask(2097143), "JKTnQ"); + equal(keymask.mask(67108858), "CvfZH"); + equal(keymask.unmask("JKTnQ"), 2097143); + equal(keymask.unmask("CvfZH"), 67108858); }); it("should mask and unmask in range 6", () => { - equal(keymask.mask(67108859), "TLFykZ"); - equal(keymask.mask(4294967290), "NGhMGv"); - equal(keymask.unmask("TLFykZ"), 67108859); - equal(keymask.unmask("NGhMGv"), 4294967290); + equal(keymask.mask(67108859), "KPQfkB"); + equal(keymask.mask(4294967290), "XqnvqC"); + equal(keymask.unmask("KPQfkB"), 67108859); + equal(keymask.unmask("XqnvqC"), 4294967290); }); it("should mask and unmask in range 7", () => { - equal(keymask.mask(4294967291), "MmHsQqR"); - equal(keymask.mask(137438953446), "NbhKgcm"); - equal(keymask.unmask("MmHsQqR"), 4294967291); - equal(keymask.unmask("NbhKgcm"), 137438953446); + equal(keymask.mask(4294967291), "vwJsgcH"); + equal(keymask.mask(137438953446), "XTnWZjw"); + equal(keymask.unmask("vwJsgcH"), 4294967291); + equal(keymask.unmask("XTnWZjw"), 137438953446); }); it("should mask and unmask in range 8", () => { - equal(keymask.mask(137438953447), "kVNPxzPx"); - equal(keymask.mask(4398046511092), "bVvdBGmP"); - equal(keymask.unmask("kVNPxzPx"), 137438953447); - equal(keymask.unmask("bVvdBGmP"), 4398046511092); + equal(keymask.mask(137438953447), "kVXpDzpD"); + equal(keymask.mask(4398046511092), "TVChLqwp"); + equal(keymask.unmask("kVXpDzpD"), 137438953447); + equal(keymask.unmask("TVChLqwp"), 4398046511092); }); it("should mask and unmask in range 9", () => { - equal(keymask.mask(4398046511093), "nCKtdTNzK"); - equal(keymask.mask(281474976710596), "VxRtRYkZd"); - equal(keymask.unmask("nCKtdTNzK"), 4398046511093); - equal(keymask.unmask("VxRtRYkZd"), 281474976710596); + equal(keymask.mask(4398046511093), "rbWRhKXzW"); + equal(keymask.mask(281474976710596), "VDHRHYkBh"); + equal(keymask.unmask("rbWRhKXzW"), 4398046511093); + equal(keymask.unmask("VDHRHYkBh"), 281474976710596); }); it("should mask and unmask in range 10", () => { - equal(keymask.mask(281474976710597), "MTBTrVMLwD"); - equal(keymask.mask(9007199254740880), "PvFkfyGPhH"); - equal(keymask.unmask("MTBTrVMLwD"), 281474976710597); - equal(keymask.unmask("PvFkfyGPhH"), 9007199254740880); + equal(keymask.mask(281474976710597), "vKLKFVvPxm"); + equal(keymask.mask(9007199254740880), "pCQkdfqpnJ"); + equal(keymask.unmask("vKLKFVvPxm"), 281474976710597); + equal(keymask.unmask("pCQkdfqpnJ"), 9007199254740880); }); it("should mask and unmask in range 11", () => { - equal(keymask.mask(9007199254740881n), "JdcLVNWPPRQ"); - equal(keymask.mask(288230376151711716n), "qcNzphPnZJT"); - equal(keymask.unmask("JdcLVNWPPRQ"), 9007199254740881n); - equal(keymask.unmask("qcNzphPnZJT"), 288230376151711716n); + equal(keymask.mask(9007199254740881n), "ShjPVXMppHg"); + equal(keymask.mask(288230376151711716n), "cjXzGnprBSK"); + equal(keymask.unmask("ShjPVXMppHg"), 9007199254740881n); + equal(keymask.unmask("cjXzGnprBSK"), 288230376151711716n); }); it("should mask and unmask in range 12", () => { - equal(keymask.mask(288230376151711717n), "DdWLFxfjmyfM"); - equal(keymask.mask(18446744073709551556n), "rmTWnYzPrSKT"); - equal(keymask.unmask("DdWLFxfjmyfM"), 288230376151711717n); - equal(keymask.unmask("rmTWnYzPrSKT"), 18446744073709551556n); + equal(keymask.mask(288230376151711717n), "mhMPQDdywfdv"); + equal(keymask.mask(18446744073709551556n), "FwKMrYzpFtWK"); + equal(keymask.unmask("mhMPQDdywfdv"), 288230376151711717n); + equal(keymask.unmask("FwKMrYzpFtWK"), 18446744073709551556n); }); it("should process binary data", () => { const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer; const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer; - equal(keymask.mask(buffer1), "JXBmhmKjgmDLdvWSfWKTbjCB"); - equal(keymask.mask(buffer2), "tSNPdzbSbSyPmP"); - deepEqual(keymask.unmask("JXBmhmKjgmDLdvWSfWKTbjCB"), buffer1); - deepEqual(keymask.unmask("tSNPdzbSbSyPmP"), buffer2); + equal(keymask.mask(buffer1), "SNLwnwWyZwmPhCMtdMWKTybL"); + equal(keymask.mask(buffer2), "RtXphzTtTtfpwp"); + deepEqual(keymask.unmask("SNLwnwWyZwmPhCMtdMWKTybL"), buffer1); + deepEqual(keymask.unmask("RtXphzTtTtfpwp"), buffer2); }); }); @@ -1066,96 +1177,214 @@ describe("Keymask", () => { }); it("should mask and unmask in range 1", () => { - equal(keymask.mask(1), "m"); - equal(keymask.mask(40), "r"); - equal(keymask.unmask("m"), 1); - equal(keymask.unmask("r"), 40); + equal(keymask.mask(1), "w"); + equal(keymask.mask(40), "F"); + equal(keymask.unmask("w"), 1); + equal(keymask.unmask("F"), 40); + }); + + it("should mask and unmask in range 2", () => { + equal(keymask.mask(41), "fC"); + equal(keymask.mask(1020), "Sn"); + equal(keymask.unmask("fC"), 41); + equal(keymask.unmask("Sn"), 1020); + }); + + it("should mask and unmask in range 3", () => { + equal(keymask.mask(1021), "BgS"); + equal(keymask.mask(65520), "pqH"); + equal(keymask.unmask("BgS"), 1021); + equal(keymask.unmask("pqH"), 65520); + }); + + it("should mask and unmask in range 4", () => { + equal(keymask.mask(65521), "NgpH"); + equal(keymask.mask(2097142), "NXpC"); + equal(keymask.unmask("NgpH"), 65521); + equal(keymask.unmask("NXpC"), 2097142); + }); + + it("should mask and unmask in range 5", () => { + equal(keymask.mask(2097143), "PGnCW"); + equal(keymask.mask(67108858), "vwmSF"); + equal(keymask.unmask("PGnCW"), 2097143); + equal(keymask.unmask("vwmSF"), 67108858); + }); + + it("should mask and unmask in range 6", () => { + equal(keymask.mask(67108859), "gNQsGq"); + equal(keymask.mask(4294967290), "CKnPXW"); + equal(keymask.unmask("gNQsGq"), 67108859); + equal(keymask.unmask("CKnPXW"), 4294967290); + }); + + it("should mask and unmask in range 7", () => { + equal(keymask.mask(4294967291), "rJSwwBn"); + equal(keymask.mask(137438953446), "MWkxWNj"); + equal(keymask.unmask("rJSwwBn"), 4294967291); + equal(keymask.unmask("MWkxWNj"), 137438953446); + }); + + it("should mask and unmask in range 8", () => { + equal(keymask.mask(137438953447), "jwmhqwXc"); + equal(keymask.mask(4398046511092), "JJVRdsmW"); + equal(keymask.unmask("jwmhqwXc"), 137438953447); + equal(keymask.unmask("JJVRdsmW"), 4398046511092); + }); + + it("should mask and unmask in range 9", () => { + equal(keymask.mask(4398046511093), "xjSsKpCxj"); + equal(keymask.mask(281474976710596), "njnnfnFgQ"); + equal(keymask.unmask("xjSsKpCxj"), 4398046511093); + equal(keymask.unmask("njnnfnFgQ"), 281474976710596); + }); + + it("should mask and unmask in range 10", () => { + equal(keymask.mask(281474976710597), "BnPnvsRhMS"); + equal(keymask.mask(9007199254740880), "pYjwndPTMQ"); + equal(keymask.unmask("BnPnvsRhMS"), 281474976710597); + equal(keymask.unmask("pYjwndPTMQ"), 9007199254740880); + }); + + it("should mask and unmask in range 11", () => { + equal(keymask.mask(9007199254740881n), "JmTsSrXYQCp"); + equal(keymask.mask(288230376151711716n), "xPDdMQjKPKL"); + equal(keymask.unmask("JmTsSrXYQCp"), 9007199254740881n); + equal(keymask.unmask("xPDdMQjKPKL"), 288230376151711716n); + }); + + it("should mask and unmask in range 12", () => { + equal(keymask.mask(288230376151711717n), "zjNDCQyCbBCp"); + equal(keymask.mask(18446744073709551556n), "gxPcgNCRswYh"); + equal(keymask.unmask("zjNDCQyCbBCp"), 288230376151711717n); + equal(keymask.unmask("gxPcgNCRswYh"), 18446744073709551556n); + }); + + it("should process binary data", () => { + const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer; + const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer; + equal(keymask.mask(buffer1), "RKDqPWvCWctRFhyGyqXqMsPM"); + equal(keymask.mask(buffer2), "zJYsjXvcMwVKqc"); + deepEqual(keymask.unmask("RKDqPWvCWctRFhyGyqXqMsPM"), buffer1); + deepEqual(keymask.unmask("zJYsjXvcMwVKqc"), buffer2); + }); + }); + + describe("Seed generator and encoder, safe mode", () => { + const keymask = new Keymask({ + seed: new Uint8Array([ + 1, 2, 3, 4, 5, 6, 7, 8, + 10, 20, 30, 40, 50, 60, 70, 80, + 11, 21, 31, 41 + ]).buffer, + safe: true + }); + + it("should mask and unmask in range 1", () => { + equal(keymask.mask(1), "k"); + equal(keymask.mask(22), "2"); + equal(keymask.unmask("k"), 1); + equal(keymask.unmask("2"), 22); }); it("should mask and unmask in range 2", () => { - equal(keymask.mask(41), "yv"); - equal(keymask.mask(1020), "Jh"); - equal(keymask.unmask("yv"), 41); - equal(keymask.unmask("Jh"), 1020); + equal(keymask.mask(23), "y2"); + equal(keymask.mask(508), "59"); + equal(keymask.unmask("y2"), 23); + equal(keymask.unmask("59"), 508); }); it("should mask and unmask in range 3", () => { - equal(keymask.mask(1021), "ZQJ"); - equal(keymask.mask(65520), "PGR"); - equal(keymask.unmask("ZQJ"), 1021); - equal(keymask.unmask("PGR"), 65520); + equal(keymask.mask(509), "bcj"); + equal(keymask.mask(8190), "fvn"); + equal(keymask.unmask("bcj"), 509); + equal(keymask.unmask("fvn"), 8190); }); it("should mask and unmask in range 4", () => { - equal(keymask.mask(65521), "XQPR"); - equal(keymask.mask(2097142), "XNPv"); - equal(keymask.unmask("XQPR"), 65521); - equal(keymask.unmask("XNPv"), 2097142); + equal(keymask.mask(8191), "prvn"); + equal(keymask.mask(262138), "7ftq"); + equal(keymask.unmask("prvn"), 8191); + equal(keymask.unmask("7ftq"), 262138); }); it("should mask and unmask in range 5", () => { - equal(keymask.mask(2097143), "LphvK"); - equal(keymask.mask(67108858), "MmDJr"); - equal(keymask.unmask("LphvK"), 2097143); - equal(keymask.unmask("MmDJr"), 67108858); + equal(keymask.mask(262139), "byscn"); + equal(keymask.mask(4194300), "btzg5"); + equal(keymask.unmask("byscn"), 262139); + equal(keymask.unmask("btzg5"), 4194300); }); it("should mask and unmask in range 6", () => { - equal(keymask.mask(67108859), "QXFspG"); - equal(keymask.mask(4294967290), "vThLNK"); - equal(keymask.unmask("QXFspG"), 67108859); - equal(keymask.unmask("vThLNK"), 4294967290); + equal(keymask.mask(4194301), "c5hf5g"); + equal(keymask.mask(134217688), "qxyjwp"); + equal(keymask.unmask("c5hf5g"), 4194301); + equal(keymask.unmask("qxyjwp"), 134217688); }); it("should mask and unmask in range 7", () => { - equal(keymask.mask(4294967291), "nHJmmZh"); - equal(keymask.mask(137438953446), "WKkwKXc"); - equal(keymask.unmask("nHJmmZh"), 4294967291); - equal(keymask.unmask("WKkwKXc"), 137438953446); + equal(keymask.mask(134217689), "wdv9dv5"); + equal(keymask.mask(4294967290), "bmtrtjq"); + equal(keymask.unmask("wdv9dv5"), 134217689); + equal(keymask.unmask("bmtrtjq"), 4294967290); }); it("should mask and unmask in range 8", () => { - equal(keymask.mask(137438953447), "cmDdGmNq"); - equal(keymask.mask(4398046511092), "HHVtfsDK"); - equal(keymask.unmask("cmDdGmNq"), 137438953447); - equal(keymask.unmask("HHVtfsDK"), 4398046511092); + equal(keymask.mask(4294967291), "vzmnzmkg"); + equal(keymask.mask(68719476730), "cpnnrymr"); + equal(keymask.unmask("vzmnzmkg"), 4294967291); + equal(keymask.unmask("cpnnrymr"), 68719476730); }); it("should mask and unmask in range 9", () => { - equal(keymask.mask(4398046511093), "wcJsTPvwc"); - equal(keymask.mask(281474976710596), "hchhyhrQF"); - equal(keymask.unmask("wcJsTPvwc"), 4398046511093); - equal(keymask.unmask("hchhyhrQF"), 281474976710596); + equal(keymask.mask(68719476731), "ptccc27ns"); + equal(keymask.mask(2199023255530), "cp5gqqbm9"); + equal(keymask.unmask("ptccc27ns"), 68719476731); + equal(keymask.unmask("cp5gqqbm9"), 2199023255530); }); it("should mask and unmask in range 10", () => { - equal(keymask.mask(281474976710597), "ZhLhMstdWJ"); - equal(keymask.mask(9007199254740880), "PYcmhfLbWF"); - equal(keymask.unmask("ZhLhMstdWJ"), 281474976710597); - equal(keymask.unmask("PYcmhfLbWF"), 9007199254740880); + equal(keymask.mask(2199023255531), "qw2gg57nmt"); + equal(keymask.mask(35184372088776), "7rcxjg9pst"); + equal(keymask.unmask("qw2gg57nmt"), 2199023255531); + equal(keymask.unmask("7rcxjg9pst"), 35184372088776); }); it("should mask and unmask in range 11", () => { - equal(keymask.mask(9007199254740881n), "HDbsJnNYFvP"); - equal(keymask.mask(288230376151711716n), "wLxfWFcTLTB"); - equal(keymask.unmask("HDbsJnNYFvP"), 9007199254740881n); - equal(keymask.unmask("wLxfWFcTLTB"), 288230376151711716n); + equal(keymask.mask(35184372088777), "2vnstxyssry"); + equal(keymask.mask(1125899906842596), "wf79gt5jf9t"); + equal(keymask.unmask("2vnstxyssry"), 35184372088777); + equal(keymask.unmask("wf79gt5jf9t"), 1125899906842596); }); it("should mask and unmask in range 12", () => { - equal(keymask.mask(288230376151711717n), "zcXxvFjvCZvP"); - equal(keymask.mask(18446744073709551556n), "QwLqQXvtsmYd"); - equal(keymask.unmask("zcXxvFjvCZvP"), 288230376151711717n); - equal(keymask.unmask("QwLqQXvtsmYd"), 18446744073709551556n); + equal(keymask.mask(1125899906842597n), "75pmxgky7s2j"); + equal(keymask.mask(36028797018963912n), "dyps5ctrsgyc"); + equal(keymask.unmask("75pmxgky7s2j"), 1125899906842597n); + equal(keymask.unmask("dyps5ctrsgyc"), 36028797018963912n); + }); + + it("should mask and unmask in range 13", () => { + equal(keymask.mask(36028797018963913n), "pfvdbdgnwrwpp"); + equal(keymask.mask(576460752303423432n), "5kxqd92gv7wqn"); + equal(keymask.unmask("pfvdbdgnwrwpp"), 36028797018963913n); + equal(keymask.unmask("5kxqd92gv7wqn"), 576460752303423432n); + }); + + it("should mask and unmask in range 14", () => { + equal(keymask.mask(576460752303423433n), "yqj9bkczrzpgvg"); + equal(keymask.mask(18446744073709551556n), "99sg7vy2snmpqz"); + equal(keymask.unmask("yqj9bkczrzpgvg"), 576460752303423433n); + equal(keymask.unmask("99sg7vy2snmpqz"), 18446744073709551556n); }); it("should process binary data", () => { const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer; const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer; - equal(keymask.mask(buffer1), "tTxGLKMvKqStrdjpjGNGWsLW"); - equal(keymask.mask(buffer2), "zHYscNMqWmVTGq"); - deepEqual(keymask.unmask("tTxGLKMvKqStrdjpjGNGWsLW"), buffer1); - deepEqual(keymask.unmask("zHYscNMqWmVTGq"), buffer2); + equal(keymask.mask(buffer1), "zdytzpz79dsffmzyt2fxqpvyxqr5"); + equal(keymask.mask(buffer2), "g7qckgtvcghhh2nc"); + deepEqual(keymask.unmask("zdytzpz79dsffmzyt2fxqpvyxqr5"), buffer1); + deepEqual(keymask.unmask("g7qckgtvcghhh2nc"), buffer2); }); }); }); \ No newline at end of file diff --git a/test/KeymaskEncoder.test.ts b/test/KeymaskEncoder.test.ts index c3c0b0d..dccd988 100644 --- a/test/KeymaskEncoder.test.ts +++ b/test/KeymaskEncoder.test.ts @@ -243,6 +243,248 @@ describe("KeymaskEncoder", () => { }); }); + describe("Unseeded, safe mode", () => { + const encoder = new KeymaskEncoder(void 0, true); + + it("should handle null character encodings", () => { + equal(encoder.encode(0), ""); + equal(encoder.encode(0, 1), "b"); + equal(encoder.decode("b"), 0); + }); + + it("should handle 1-character encodings", () => { + equal(encoder.encode(1), "c"); + equal(encoder.encode(1, 1), "c"); + equal(encoder.encode(1, 2), "cb"); + equal(encoder.encode(1, 3), "cbb"); + equal(encoder.encode(22, 1), "7"); + equal(encoder.encode(22, 2), "7b"); + equal(encoder.encode(22, 3), "7bb"); + equal(encoder.decode("c"), 1); + equal(encoder.decode("cb"), 1); + equal(encoder.decode("cbb"), 1); + equal(encoder.decode("7"), 22); + equal(encoder.decode("7b"), 22); + equal(encoder.decode("7bb"), 22); + }); + + it("should handle 2-character encodings", () => { + equal(encoder.encode(23), "9"); + equal(encoder.encode(23, 2), "9b"); + equal(encoder.encode(508, 2), "g5"); + equal(encoder.decode("9"), 23); + equal(encoder.decode("9b"), 23); + equal(encoder.decode("g5"), 508); + }); + + it("should handle 3-character encodings", () => { + equal(encoder.encode(509), "h5"); + equal(encoder.encode(509, 3), "h5b"); + equal(encoder.encode(8190, 3), "jht"); + equal(encoder.decode("h5"), 509); + equal(encoder.decode("h5b"), 509); + equal(encoder.decode("jht"), 8190); + }); + + it("should handle 4-character encodings", () => { + equal(encoder.encode(8191), "kht"); + equal(encoder.encode(8191, 4), "khtb"); + equal(encoder.encode(262138, 4), "pd9y"); + equal(encoder.decode("kht"), 8191); + equal(encoder.decode("khtb"), 8191); + equal(encoder.decode("pd9y"), 262138); + }); + + it("should handle 5-character encodings", () => { + equal(encoder.encode(262139), "qd9y"); + equal(encoder.encode(262139, 5), "qd9yb"); + equal(encoder.encode(4194300, 5), "rynvr"); + equal(encoder.decode("qd9y"), 262139); + equal(encoder.decode("qd9yb"), 262139); + equal(encoder.decode("rynvr"), 4194300); + }); + + it("should handle 6-character encodings", () => { + equal(encoder.encode(4194301), "synvr"); + equal(encoder.encode(4194301, 6), "synvrb"); + equal(encoder.encode(134217688, 6), "wzbs2w"); + equal(encoder.decode("synvr"), 4194301); + equal(encoder.decode("synvrb"), 4194301); + equal(encoder.decode("wzbs2w"), 134217688); + }); + + it("should handle 7-character encodings", () => { + equal(encoder.encode(134217689), "xzbs2w"); + equal(encoder.encode(134217689, 7), "xzbs2wb"); + equal(encoder.encode(4294967290, 7), "ppgnnq7"); + equal(encoder.decode("xzbs2w"), 134217689); + equal(encoder.decode("xzbs2wb"), 134217689); + equal(encoder.decode("ppgnnq7"), 4294967290); + }); + + it("should handle 8-character encodings", () => { + equal(encoder.encode(4294967291), "qpgnnq7"); + equal(encoder.encode(4294967291, 8), "qpgnnq7b"); + equal(encoder.encode(68719476730, 8), "pd9djt9t"); + equal(encoder.decode("qpgnnq7"), 4294967291); + equal(encoder.decode("qpgnnq7b"), 4294967291); + equal(encoder.decode("pd9djt9t"), 68719476730); + }); + + it("should handle 9-character encodings", () => { + equal(encoder.encode(68719476731), "qd9djt9t"); + equal(encoder.encode(68719476731, 9), "qd9djt9tb"); + equal(encoder.encode(2199023255530, 9), "prz7fbq9z"); + equal(encoder.decode("qd9djt9t"), 68719476731); + equal(encoder.decode("qd9djt9tb"), 68719476731); + equal(encoder.decode("prz7fbq9z"), 2199023255530); + }); + + it("should handle 10-character encodings", () => { + equal(encoder.encode(2199023255531), "qrz7fbq9z"); + equal(encoder.encode(2199023255531, 10), "qrz7fbq9zb"); + equal(encoder.encode(35184372088776, 10), "bzbhvdmvks"); + equal(encoder.decode("qrz7fbq9z"), 2199023255531); + equal(encoder.decode("qrz7fbq9zb"), 2199023255531); + equal(encoder.decode("bzbhvdmvks"), 35184372088776); + }); + + it("should handle 11-character encodings", () => { + equal(encoder.encode(35184372088777), "czbhvdmvks"); + equal(encoder.encode(35184372088777, 11), "czbhvdmvksb"); + equal(encoder.encode(1125899906842596, 11), "rngxjrzpgyx"); + equal(encoder.decode("czbhvdmvks"), 35184372088777); + equal(encoder.decode("czbhvdmvksb"), 35184372088777); + equal(encoder.decode("rngxjrzpgyx"), 1125899906842596); + }); + + it("should handle 12-character encodings", () => { + equal(encoder.encode(1125899906842597n), "sngxjrzpgyx"); + equal(encoder.encode(1125899906842597n, 12), "sngxjrzpgyxb"); + equal(encoder.encode(36028797018963912n, 12), "bf757mbp7hw9"); + equal(encoder.decode("sngxjrzpgyx"), 1125899906842597); + equal(encoder.decode("sngxjrzpgyx", true), 1125899906842597n); + equal(encoder.decode("sngxjrzpgyxb"), 1125899906842597n); + equal(encoder.decode("bf757mbp7hw9"), 36028797018963912n); + }); + + it("should handle 13-character encodings", () => { + equal(encoder.encode(36028797018963913n), "cf757mbp7hw9"); + equal(encoder.encode(36028797018963913n, 13), "cf757mbp7hw9b"); + equal(encoder.encode(576460752303423432n, 13), "bqztj9hw77zyv"); + equal(encoder.decode("cf757mbp7hw9"), 36028797018963913n); + equal(encoder.decode("cf757mbp7hw9b"), 36028797018963913n); + equal(encoder.decode("bqztj9hw77zyv"), 576460752303423432n); + }); + + it("should handle 14-character encodings", () => { + equal(encoder.encode(576460752303423433n), "cqztj9hw77zyv"); + equal(encoder.encode(576460752303423433n, 14), "cqztj9hw77zyvb"); + equal(encoder.encode(18446744073709551556n, 14), "gwcyzb9vhttdc5"); + equal(encoder.decode("cqztj9hw77zyv"), 576460752303423433n); + equal(encoder.decode("cqztj9hw77zyvb"), 576460752303423433n); + equal(encoder.decode("gwcyzb9vhttdc5"), 18446744073709551556n); + }); + + it("should handle larger than 64-bit bigints", () => { + equal(encoder.encode(18446744073709551616n), "bbbbbbbbbbbbbbc"); + equal(encoder.encode(18446744073709551616n, 7), "bbbbbbbbbbbbbbcbbbbbb"); + equal(encoder.encode(987654321098765432109876543210n), "ycz7qgnf5w7rb55f5qbgwq"); + equal(encoder.encode(987654321098765432109876543210n, 14), "ycz7qgnf5w7rb55f5qbgwqbbbbbb"); + + equal(encoder.decode("bbbbbbbbbbbbbbc", true), 18446744073709551616n); + equal(encoder.decode("bbbbbbbbbbbbbbcbbbbbb", true), 18446744073709551616n); + + deepEqual( + encoder.decode("bbbbbbbbbbbbbbc"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + deepEqual( + encoder.decode("bbbbbbbbbbbbbbcbbbbbb"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + + equal(encoder.decode("ycz7qgnf5w7rb55f5qbgwq", true), 987654321098765432109876543210n); + equal(encoder.decode("ycz7qgnf5w7rb55f5qbgwqbbbb", true), 987654321098765432109876543210n); + + deepEqual( + encoder.decode("ycz7qgnf5w7rb55f5qbgwq"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + deepEqual( + encoder.decode("ycz7qgnf5w7rb55f5qbgwqbbbbbb"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + }); + + it("should handle binary values", () => { + equal( + encoder.encode( + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer + ), + "bbbbbbbbbbbbbbc" + ); + equal( + encoder.encode( + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1]).buffer, + 6 + ), + "bbbbbbbbbbbbbbcbbbbb" + ); + equal( + encoder.encode( + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ), + "ycz7qgnf5w7rb55f5qbgwq" + ); + equal( + encoder.encode( + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0 + ]).buffer, + 12 + ), + "ycz7qgnf5w7rb55f5qbgwqbbbb" + ); + + equal(encoder.decode("bbbbbbbbbbbbbbc", true), 18446744073709551616n); + equal(encoder.decode("bbbbbbbbbbbbbbcbbbbb", true), 18446744073709551616n); + + deepEqual( + encoder.decode("bbbbbbbbbbbbbbc"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + deepEqual( + encoder.decode("bbbbbbbbbbbbbbcbbbbb"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + + equal(encoder.decode("ycz7qgnf5w7rb55f5qbgwq", true), 987654321098765432109876543210n); + equal(encoder.decode("ycz7qgnf5w7rb55f5qbgwqbbbb", true), 987654321098765432109876543210n); + + deepEqual( + encoder.decode("ycz7qgnf5w7rb55f5qbgwq"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + deepEqual( + encoder.decode("ycz7qgnf5w7rb55f5qbgwqbbbb"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + }); + }); + describe("Seeded", () => { const encoder = new KeymaskEncoder(new Uint8Array([ 1, 2, 3, 4, 5, 6, 7, 8, @@ -252,153 +494,399 @@ describe("KeymaskEncoder", () => { it("should handle null character encodings", () => { equal(encoder.encode(0), ""); - equal(encoder.encode(0, 1), "q"); - equal(encoder.decode("q"), 0); + equal(encoder.encode(0, 1), "r"); + equal(encoder.decode("r"), 0); }); it("should handle 1-character encodings", () => { - equal(encoder.encode(1), "d"); - equal(encoder.encode(1, 1), "d"); - equal(encoder.encode(1, 2), "dq"); - equal(encoder.encode(1, 3), "dqq"); + equal(encoder.encode(1), "q"); + equal(encoder.encode(1, 1), "q"); + equal(encoder.encode(1, 2), "qr"); + equal(encoder.encode(1, 3), "qrr"); equal(encoder.encode(40, 1), "n"); - equal(encoder.encode(40, 2), "nq"); - equal(encoder.encode(40, 3), "nqq"); - equal(encoder.decode("d"), 1); - equal(encoder.decode("dq"), 1); - equal(encoder.decode("dqq"), 1); + equal(encoder.encode(40, 2), "nr"); + equal(encoder.encode(40, 3), "nrr"); + equal(encoder.decode("q"), 1); + equal(encoder.decode("qr"), 1); + equal(encoder.decode("qrr"), 1); equal(encoder.decode("n"), 40); - equal(encoder.decode("nq"), 40); - equal(encoder.decode("nqq"), 40); + equal(encoder.decode("nr"), 40); + equal(encoder.decode("nrr"), 40); + }); + + it("should handle 2-character encodings", () => { + equal(encoder.encode(41), "rq"); + equal(encoder.encode(41, 2), "rq"); + equal(encoder.encode(1020, 2), "gK"); + equal(encoder.decode("rq"), 41); + equal(encoder.decode("gK"), 1020); + }); + + it("should handle 3-character encodings", () => { + equal(encoder.encode(1021), "ZK"); + equal(encoder.encode(1021, 3), "ZKr"); + equal(encoder.encode(65520, 3), "TnW"); + equal(encoder.decode("ZK"), 1021); + equal(encoder.decode("ZKr"), 1021); + equal(encoder.decode("TnW"), 65520); + }); + + it("should handle 4-character encodings", () => { + equal(encoder.encode(65521), "QnW"); + equal(encoder.encode(65521, 4), "QnWr"); + equal(encoder.encode(2097142, 4), "BsCM"); + equal(encoder.decode("QnW"), 65521); + equal(encoder.decode("QnWr"), 65521); + equal(encoder.decode("BsCM"), 2097142); + }); + + it("should handle 5-character encodings", () => { + equal(encoder.encode(2097143), "VsCM"); + equal(encoder.encode(2097143, 5), "VsCMr"); + equal(encoder.encode(67108858, 5), "CnHMX"); + equal(encoder.decode("VsCM"), 2097143); + equal(encoder.decode("VsCMr"), 2097143); + equal(encoder.decode("CnHMX"), 67108858); + }); + + it("should handle 6-character encodings", () => { + equal(encoder.encode(67108859), "wnHMX"); + equal(encoder.encode(67108859, 6), "wnHMXr"); + equal(encoder.encode(4294967290, 6), "GbxWTZ"); + equal(encoder.decode("wnHMX"), 67108859); + equal(encoder.decode("wnHMXr"), 67108859); + equal(encoder.decode("GbxWTZ"), 4294967290); + }); + + it("should handle 7-character encodings", () => { + equal(encoder.encode(4294967291), "DbxWTZ"); + equal(encoder.encode(4294967291, 7), "DbxWTZr"); + equal(encoder.encode(137438953446, 7), "xTTFkWH"); + equal(encoder.decode("DbxWTZ"), 4294967291); + equal(encoder.decode("DbxWTZr"), 4294967291); + equal(encoder.decode("xTTFkWH"), 137438953446); + }); + + it("should handle 8-character encodings", () => { + equal(encoder.encode(137438953447), "kTTFkWH"); + equal(encoder.encode(137438953447, 8), "kTTFkWHr"); + equal(encoder.encode(4398046511092, 8), "BdJzxgXs"); + equal(encoder.decode("kTTFkWH"), 137438953447); + equal(encoder.decode("kTTFkWHr"), 137438953447); + equal(encoder.decode("BdJzxgXs"), 4398046511092); + }); + + it("should handle 9-character encodings", () => { + equal(encoder.encode(4398046511093), "VdJzxgXs"); + equal(encoder.encode(4398046511093, 9), "VdJzxgXsr"); + equal(encoder.encode(281474976710596, 9), "DSRByKkxF"); + equal(encoder.decode("VdJzxgXs"), 4398046511093); + equal(encoder.decode("VdJzxgXsr"), 4398046511093); + equal(encoder.decode("DSRByKkxF"), 281474976710596); + }); + + it("should handle 10-character encodings", () => { + equal(encoder.encode(281474976710597), "BSRByKkxF"); + equal(encoder.encode(281474976710597, 10), "BSRByKkxFr"); + equal(encoder.encode(9007199254740880, 10), "QYQQYFqqmp"); + equal(encoder.decode("BSRByKkxF"), 281474976710597); + equal(encoder.decode("BSRByKkxFr"), 281474976710597); + equal(encoder.decode("QYQQYFqqmp"), 9007199254740880); + }); + + it("should handle 11-character encodings", () => { + equal(encoder.encode(9007199254740881n), "hYQQYFqqmp"); + equal(encoder.encode(9007199254740881n, 11), "hYQQYFqqmpr"); + equal(encoder.encode(288230376151711716n, 11), "QYcRvXwBRNm"); + equal(encoder.decode("hYQQYFqqmp"), 9007199254740881); + equal(encoder.decode("hYQQYFqqmp", true), 9007199254740881n); + equal(encoder.decode("hYQQYFqqmpr"), 9007199254740881n); + equal(encoder.decode("QYcRvXwBRNm"), 288230376151711716n); + }); + + it("should handle 12-character encodings", () => { + equal(encoder.encode(288230376151711717n), "hYcRvXwBRNm"); + equal(encoder.encode(288230376151711717n, 12), "hYcRvXwBRNmr"); + equal(encoder.encode(18446744073709551556n, 12), "WChnhdnvxbmB"); + equal(encoder.decode("hYcRvXwBRNm"), 288230376151711717n); + equal(encoder.decode("hYcRvXwBRNmr"), 288230376151711717n); + equal(encoder.decode("WChnhdnvxbmB"), 18446744073709551556n); + }); + + it("should handle larger than 64-bit bigints", () => { + equal(encoder.encode(18446744073709551616n), "rrrrrrrrrrrrq"); + equal(encoder.encode(18446744073709551616n, 6), "rrrrrrrrrrrrqrrrrr"); + equal(encoder.encode(987654321098765432109876543210n), "HNCJNWscMMNBfSMRPkk"); + equal(encoder.encode(987654321098765432109876543210n, 12), "HNCJNWscMMNBfSMRPkkrrrrr"); + + equal(encoder.decode("rrrrrrrrrrrrq", true), 18446744073709551616n); + equal(encoder.decode("rrrrrrrrrrrrqrrrrr", true), 18446744073709551616n); + + deepEqual( + encoder.decode("rrrrrrrrrrrrq"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + deepEqual( + encoder.decode("rrrrrrrrrrrrqrrrrr"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + + equal(encoder.decode("HNCJNWscMMNBfSMRPkk", true), 987654321098765432109876543210n); + equal(encoder.decode("HNCJNWscMMNBfSMRPkkrrrrr", true), 987654321098765432109876543210n); + + deepEqual( + encoder.decode("HNCJNWscMMNBfSMRPkk"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + deepEqual( + encoder.decode("HNCJNWscMMNBfSMRPkkrrrrr"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + }); + + it("should handle binary values", () => { + equal( + encoder.encode( + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer + ), + "rrrrrrrrrrrrq" + ); + equal( + encoder.encode( + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1]).buffer, + 6 + ), + "rrrrrrrrrrrrqrrrrr" + ); + equal( + encoder.encode( + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ), + "HNCJNWscMMNBfSMRPkk" + ); + equal( + encoder.encode( + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0 + ]).buffer, + 12 + ), + "HNCJNWscMMNBfSMRPkkrrrrr" + ); + + equal(encoder.decode("rrrrrrrrrrrrq", true), 18446744073709551616n); + equal(encoder.decode("rrrrrrrrrrrrqrrrrr", true), 18446744073709551616n); + + deepEqual( + encoder.decode("rrrrrrrrrrrrq"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + deepEqual( + encoder.decode("rrrrrrrrrrrrqrrrrr"), + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); + + equal(encoder.decode("HNCJNWscMMNBfSMRPkk", true), 987654321098765432109876543210n); + equal(encoder.decode("HNCJNWscMMNBfSMRPkkrrrrr", true), 987654321098765432109876543210n); + + deepEqual( + encoder.decode("HNCJNWscMMNBfSMRPkk"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + deepEqual( + encoder.decode("HNCJNWscMMNBfSMRPkkrrrrr"), + new Uint8Array([ + 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, + 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 + ]).buffer + ); + }); + }); + + describe("Seeded, safe mode", () => { + const encoder = new KeymaskEncoder(new Uint8Array([ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24 + ]).buffer, true); + + it("should handle null character encodings", () => { + equal(encoder.encode(0), ""); + equal(encoder.encode(0, 1), "q"); + equal(encoder.decode("q"), 0); + }); + + it("should handle 1-character encodings", () => { + equal(encoder.encode(1), "g"); + equal(encoder.encode(1, 1), "g"); + equal(encoder.encode(1, 2), "gq"); + equal(encoder.encode(1, 3), "gqq"); + equal(encoder.encode(22, 1), "v"); + equal(encoder.encode(22, 2), "vq"); + equal(encoder.encode(22, 3), "vqq"); + equal(encoder.decode("g"), 1); + equal(encoder.decode("gq"), 1); + equal(encoder.decode("gqq"), 1); + equal(encoder.decode("v"), 22); + equal(encoder.decode("vq"), 22); + equal(encoder.decode("vqq"), 22); }); it("should handle 2-character encodings", () => { - equal(encoder.encode(41), "qd"); - equal(encoder.encode(41, 2), "qd"); - equal(encoder.encode(1020, 2), "gr"); - equal(encoder.decode("qd"), 41); - equal(encoder.decode("gr"), 1020); + equal(encoder.encode(23), "w"); + equal(encoder.encode(23, 2), "wq"); + equal(encoder.encode(508, 2), "nm"); + equal(encoder.decode("w"), 23); + equal(encoder.decode("wq"), 23); + equal(encoder.decode("nm"), 508); }); it("should handle 3-character encodings", () => { - equal(encoder.encode(1021), "Zr"); - equal(encoder.encode(1021, 3), "Zrq"); - equal(encoder.encode(65520, 3), "ynW"); - equal(encoder.decode("Zr"), 1021); - equal(encoder.decode("Zrq"), 1021); - equal(encoder.decode("ynW"), 65520); + equal(encoder.encode(509), "sm"); + equal(encoder.encode(509, 3), "smq"); + equal(encoder.encode(8190, 3), "dsz"); + equal(encoder.decode("sm"), 509); + equal(encoder.decode("smq"), 509); + equal(encoder.decode("dsz"), 8190); }); it("should handle 4-character encodings", () => { - equal(encoder.encode(65521), "SnW"); - equal(encoder.encode(65521, 4), "SnWq"); - equal(encoder.encode(2097142, 4), "DxwP"); - equal(encoder.decode("SnW"), 65521); - equal(encoder.decode("SnWq"), 65521); - equal(encoder.decode("DxwP"), 2097142); + equal(encoder.encode(8191), "9sz"); + equal(encoder.encode(8191, 4), "9szq"); + equal(encoder.encode(262138, 4), "tfwh"); + equal(encoder.decode("9sz"), 8191); + equal(encoder.decode("9szq"), 8191); + equal(encoder.decode("tfwh"), 262138); }); it("should handle 5-character encodings", () => { - equal(encoder.encode(2097143), "JxwP"); - equal(encoder.encode(2097143, 5), "JxwPq"); - equal(encoder.encode(67108858, 5), "wnRPY"); - equal(encoder.decode("JxwP"), 2097143); - equal(encoder.decode("JxwPq"), 2097143); - equal(encoder.decode("wnRPY"), 67108858); + equal(encoder.encode(262139), "yfwh"); + equal(encoder.encode(262139, 5), "yfwhq"); + equal(encoder.encode(4194300, 5), "5hjx5"); + equal(encoder.decode("yfwh"), 262139); + equal(encoder.decode("yfwhq"), 262139); + equal(encoder.decode("5hjx5"), 4194300); }); it("should handle 6-character encodings", () => { - equal(encoder.encode(67108859), "snRPY"); - equal(encoder.encode(67108859, 6), "snRPYq"); - equal(encoder.encode(4294967290, 6), "HhvWyZ"); - equal(encoder.decode("snRPY"), 67108859); - equal(encoder.decode("snRPYq"), 67108859); - equal(encoder.decode("HhvWyZ"), 4294967290); + equal(encoder.encode(4194301), "phjx5"); + equal(encoder.encode(4194301, 6), "phjx5q"); + equal(encoder.encode(134217688, 6), "cbqpkc"); + equal(encoder.decode("phjx5"), 4194301); + equal(encoder.decode("phjx5q"), 4194301); + equal(encoder.decode("cbqpkc"), 134217688); }); it("should handle 7-character encodings", () => { - equal(encoder.encode(4294967291), "BhvWyZ"); - equal(encoder.encode(4294967291, 7), "BhvWyZq"); - equal(encoder.encode(137438953446, 7), "vyytNWR"); - equal(encoder.decode("BhvWyZ"), 4294967291); - equal(encoder.decode("BhvWyZq"), 4294967291); - equal(encoder.decode("vyytNWR"), 137438953446); + equal(encoder.encode(134217689), "7bqpkc"); + equal(encoder.encode(134217689, 7), "7bqpkcq"); + equal(encoder.encode(4294967290, 7), "ttnjjyv"); + equal(encoder.decode("7bqpkc"), 134217689); + equal(encoder.decode("7bqpkcq"), 134217689); + equal(encoder.decode("ttnjjyv"), 4294967290); }); it("should handle 8-character encodings", () => { - equal(encoder.encode(137438953447), "NyytNWR"); - equal(encoder.encode(137438953447, 8), "NyytNWRq"); - equal(encoder.encode(4398046511092, 8), "DcCQvgYx"); - equal(encoder.decode("NyytNWR"), 137438953447); - equal(encoder.decode("NyytNWRq"), 137438953447); - equal(encoder.decode("DcCQvgYx"), 4398046511092); + equal(encoder.encode(4294967291), "ytnjjyv"); + equal(encoder.encode(4294967291, 8), "ytnjjyvq"); + equal(encoder.encode(68719476730, 8), "tfwfdzwz"); + equal(encoder.decode("ytnjjyv"), 4294967291); + equal(encoder.decode("ytnjjyvq"), 4294967291); + equal(encoder.decode("tfwfdzwz"), 68719476730); }); it("should handle 9-character encodings", () => { - equal(encoder.encode(4398046511093), "JcCQvgYx"); - equal(encoder.encode(4398046511093, 9), "JcCQvgYxq"); - equal(encoder.encode(281474976710596, 9), "BfMDGrNvt"); - equal(encoder.decode("JcCQvgYx"), 4398046511093); - equal(encoder.decode("JcCQvgYxq"), 4398046511093); - equal(encoder.decode("BfMDGrNvt"), 281474976710596); + equal(encoder.encode(68719476731), "yfwfdzwz"); + equal(encoder.encode(68719476731, 9), "yfwfdzwzq"); + equal(encoder.encode(2199023255530, 9), "t5bvrqywb"); + equal(encoder.decode("yfwfdzwz"), 68719476731); + equal(encoder.decode("yfwfdzwzq"), 68719476731); + equal(encoder.decode("t5bvrqywb"), 2199023255530); }); it("should handle 10-character encodings", () => { - equal(encoder.encode(281474976710597), "DfMDGrNvt"); - equal(encoder.encode(281474976710597, 10), "DfMDGrNvtq"); - equal(encoder.encode(9007199254740880, 10), "SmSSmtddpL"); - equal(encoder.decode("DfMDGrNvt"), 281474976710597); - equal(encoder.decode("DfMDGrNvtq"), 281474976710597); - equal(encoder.decode("SmSSmtddpL"), 9007199254740880); + equal(encoder.encode(2199023255531), "y5bvrqywb"); + equal(encoder.encode(2199023255531, 10), "y5bvrqywbq"); + equal(encoder.encode(35184372088776, 10), "qbqsxf2x9p"); + equal(encoder.decode("y5bvrqywb"), 2199023255531); + equal(encoder.decode("y5bvrqywbq"), 2199023255531); + equal(encoder.decode("qbqsxf2x9p"), 35184372088776); }); it("should handle 11-character encodings", () => { - equal(encoder.encode(9007199254740881n), "zmSSmtddpL"); - equal(encoder.encode(9007199254740881n, 11), "zmSSmtddpLq"); - equal(encoder.encode(288230376151711716n, 11), "SmVMXYsDMkp"); - equal(encoder.decode("zmSSmtddpL"), 9007199254740881); - equal(encoder.decode("zmSSmtddpL", true), 9007199254740881n); - equal(encoder.decode("zmSSmtddpLq"), 9007199254740881n); - equal(encoder.decode("SmVMXYsDMkp"), 288230376151711716n); + equal(encoder.encode(35184372088777), "gbqsxf2x9p"); + equal(encoder.encode(35184372088777, 11), "gbqsxf2x9pq"); + equal(encoder.encode(1125899906842596, 11), "5jn7d5btnh7"); + equal(encoder.decode("gbqsxf2x9p"), 35184372088777); + equal(encoder.decode("gbqsxf2x9pq"), 35184372088777); + equal(encoder.decode("5jn7d5btnh7"), 1125899906842596); }); it("should handle 12-character encodings", () => { - equal(encoder.encode(288230376151711717n), "zmVMXYsDMkp"); - equal(encoder.encode(288230376151711717n, 12), "zmVMXYsDMkpq"); - equal(encoder.encode(18446744073709551556n, 12), "WwznzcnXvhpD"); - equal(encoder.decode("zmVMXYsDMkp"), 288230376151711717n); - equal(encoder.decode("zmVMXYsDMkpq"), 288230376151711717n); - equal(encoder.decode("WwznzcnXvhpD"), 18446744073709551556n); + equal(encoder.encode(1125899906842597), "pjn7d5btnh7"); + equal(encoder.encode(1125899906842597n, 12), "pjn7d5btnh7q"); + equal(encoder.encode(36028797018963912n, 12), "qrvmv2qtvscw"); + equal(encoder.decode("pjn7d5btnh7"), 1125899906842597); + equal(encoder.decode("pjn7d5btnh7", true), 1125899906842597n); + equal(encoder.decode("pjn7d5btnh7q"), 1125899906842597n); + equal(encoder.decode("qrvmv2qtvscw"), 36028797018963912n); + }); + + it("should handle 13-character encodings", () => { + equal(encoder.encode(36028797018963913n), "grvmv2qtvscw"); + equal(encoder.encode(36028797018963913n, 13), "grvmv2qtvscwq"); + equal(encoder.encode(576460752303423432n, 13), "qybzdwscvvbhx"); + equal(encoder.decode("grvmv2qtvscw"), 36028797018963913n); + equal(encoder.decode("grvmv2qtvscwq"), 36028797018963913n); + equal(encoder.decode("qybzdwscvvbhx"), 576460752303423432n); + }); + + it("should handle 14-character encodings", () => { + equal(encoder.encode(576460752303423433n), "gybzdwscvvbhx"); + equal(encoder.encode(576460752303423433n, 14), "gybzdwscvvbhxq"); + equal(encoder.encode(18446744073709551556n, 14), "ncghbqwxszzfgm"); + equal(encoder.decode("gybzdwscvvbhx"), 576460752303423433n); + equal(encoder.decode("gybzdwscvvbhxq"), 576460752303423433n); + equal(encoder.decode("ncghbqwxszzfgm"), 18446744073709551556n); }); it("should handle larger than 64-bit bigints", () => { - equal(encoder.encode(18446744073709551616n), "qqqqqqqqqqqqd"); - equal(encoder.encode(18446744073709551616n, 6), "qqqqqqqqqqqqdqqqqq"); - equal(encoder.encode(987654321098765432109876543210n), "RkwCkWxVPPkDTfPMbNN"); - equal(encoder.encode(987654321098765432109876543210n, 12), "RkwCkWxVPPkDTfPMbNNqqqqq"); + equal(encoder.encode(18446744073709551616n), "qqqqqqqqqqqqqqg"); + equal(encoder.encode(18446744073709551616n, 6), "qqqqqqqqqqqqqqgqqqqq"); + equal(encoder.encode(987654321098765432109876543210n), "hgbvynjrmcv5qmmrmyqncy"); + equal(encoder.encode(987654321098765432109876543210n, 12), "hgbvynjrmcv5qmmrmyqncyqqqq"); - equal(encoder.decode("qqqqqqqqqqqqd", true), 18446744073709551616n); - equal(encoder.decode("qqqqqqqqqqqqdqqqqq", true), 18446744073709551616n); + equal(encoder.decode("qqqqqqqqqqqqqqg", true), 18446744073709551616n); + equal(encoder.decode("qqqqqqqqqqqqqqgqqqqq", true), 18446744073709551616n); deepEqual( - encoder.decode("qqqqqqqqqqqqd"), + encoder.decode("qqqqqqqqqqqqqqg"), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); deepEqual( - encoder.decode("qqqqqqqqqqqqdqqqqq"), + encoder.decode("qqqqqqqqqqqqqqgqqqqq"), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); - equal(encoder.decode("RkwCkWxVPPkDTfPMbNN", true), 987654321098765432109876543210n); - equal(encoder.decode("RkwCkWxVPPkDTfPMbNNqqqqq", true), 987654321098765432109876543210n); + equal(encoder.decode("hgbvynjrmcv5qmmrmyqncy", true), 987654321098765432109876543210n); + equal(encoder.decode("hgbvynjrmcv5qmmrmyqncyqqqq", true), 987654321098765432109876543210n); deepEqual( - encoder.decode("RkwCkWxVPPkDTfPMbNN"), + encoder.decode("hgbvynjrmcv5qmmrmyqncy"), new Uint8Array([ 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 ]).buffer ); deepEqual( - encoder.decode("RkwCkWxVPPkDTfPMbNNqqqqq"), + encoder.decode("hgbvynjrmcv5qmmrmyqncyqqqq"), new Uint8Array([ 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 @@ -411,14 +899,14 @@ describe("KeymaskEncoder", () => { encoder.encode( new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer ), - "qqqqqqqqqqqqd" + "qqqqqqqqqqqqqqg" ); equal( encoder.encode( new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1]).buffer, 6 ), - "qqqqqqqqqqqqdqqqqq" + "qqqqqqqqqqqqqqgqqqqq" ); equal( encoder.encode( @@ -427,7 +915,7 @@ describe("KeymaskEncoder", () => { 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 ]).buffer ), - "RkwCkWxVPPkDTfPMbNN" + "hgbvynjrmcv5qmmrmyqncy" ); equal( encoder.encode( @@ -437,31 +925,31 @@ describe("KeymaskEncoder", () => { ]).buffer, 12 ), - "RkwCkWxVPPkDTfPMbNNqqqqq" + "hgbvynjrmcv5qmmrmyqncyqqqq" ); - equal(encoder.decode("qqqqqqqqqqqqd", true), 18446744073709551616n); - equal(encoder.decode("qqqqqqqqqqqqdqqqqq", true), 18446744073709551616n); + equal(encoder.decode("qqqqqqqqqqqqqqg", true), 18446744073709551616n); + equal(encoder.decode("qqqqqqqqqqqqqqgqqqqq", true), 18446744073709551616n); deepEqual( - encoder.decode("qqqqqqqqqqqqd"), + encoder.decode("qqqqqqqqqqqqqqg"), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); deepEqual( - encoder.decode("qqqqqqqqqqqqdqqqqq"), + encoder.decode("qqqqqqqqqqqqqqgqqqqq"), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]).buffer); - equal(encoder.decode("RkwCkWxVPPkDTfPMbNN", true), 987654321098765432109876543210n); - equal(encoder.decode("RkwCkWxVPPkDTfPMbNNqqqqq", true), 987654321098765432109876543210n); + equal(encoder.decode("hgbvynjrmcv5qmmrmyqncy", true), 987654321098765432109876543210n); + equal(encoder.decode("hgbvynjrmcv5qmmrmyqncyqqqq", true), 987654321098765432109876543210n); deepEqual( - encoder.decode("RkwCkWxVPPkDTfPMbNN"), + encoder.decode("hgbvynjrmcv5qmmrmyqncy"), new Uint8Array([ 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 ]).buffer ); deepEqual( - encoder.decode("RkwCkWxVPPkDTfPMbNNqqqqq"), + encoder.decode("hgbvynjrmcv5qmmrmyqncyqqqq"), new Uint8Array([ 0xea, 0x7e, 0xc6, 0xd1, 0x38, 0x24, 0xb6, 0xff, 0x9d, 0x81, 0x48, 0x77, 0x0c, 0, 0, 0 diff --git a/test/KeymaskGenerator.test.ts b/test/KeymaskGenerator.test.ts index 7117e44..369dae3 100644 --- a/test/KeymaskGenerator.test.ts +++ b/test/KeymaskGenerator.test.ts @@ -208,6 +208,232 @@ describe("KeymaskGenerator", () => { }); }); + describe("No offset, safe mode", () => { + const generator = new KeymaskGenerator(void 0, true); + + it("should handle empty inputs", () => { + equal(generator.next(0, 6), 0); + equal(generator.next(0n, 13), 0n); + equal(generator.previous(0, 3), 0); + equal(generator.previous(0n, 12), 0n); + }); + + it("should handle unexpected bigint inputs", () => { + equal(generator.next(1n, 1), 7); + equal(generator.previous(7n, 1), 1); + }); + + it("should compute values in mode 1", () => { + const next1 = generator.next(1, 1); + const next2 = generator.next(next1, 1); + const next3 = generator.next(next2, 1); + const prev3 = generator.previous(next3, 1); + const prev2 = generator.previous(prev3, 1); + const prev1 = generator.previous(prev2, 1); + equal(next1, 7); + equal(next2, 3); + equal(next3, 21); + equal(prev3, 3); + equal(prev2, 7); + equal(prev1, 1); + }); + + it("should compute values in mode 2", () => { + const next1 = generator.next(1, 2); + const next2 = generator.next(next1, 2); + const next3 = generator.next(next2, 2); + const prev3 = generator.previous(next3, 2); + const prev2 = generator.previous(prev3, 2); + const prev1 = generator.previous(prev2, 2); + equal(next1, 110); + equal(next2, 393); + equal(next3, 474); + equal(prev3, 393); + equal(prev2, 110); + equal(prev1, 1); + }); + + it("should compute values in mode 3", () => { + const next1 = generator.next(1, 3); + const next2 = generator.next(next1, 3); + const next3 = generator.next(next2, 3); + const prev3 = generator.previous(next3, 3); + const prev2 = generator.previous(prev3, 3); + const prev1 = generator.previous(prev2, 3); + equal(next1, 1716); + equal(next2, 4087); + equal(next3, 1796); + equal(prev3, 4087); + equal(prev2, 1716); + equal(prev1, 1); + }); + + it("should compute values in mode 4", () => { + const next1 = generator.next(1, 4); + const next2 = generator.next(next1, 4); + const next3 = generator.next(next2, 4); + const prev3 = generator.previous(next3, 4); + const prev2 = generator.previous(prev3, 4); + const prev1 = generator.previous(prev2, 4); + equal(next1, 92717); + equal(next2, 117862); + equal(next3, 22561); + equal(prev3, 117862); + equal(prev2, 92717); + equal(prev1, 1); + }); + + it("should compute values in mode 5", () => { + const next1 = generator.next(1, 5); + const next2 = generator.next(next1, 5); + const next3 = generator.next(next2, 5); + const prev3 = generator.previous(next3, 5); + const prev2 = generator.previous(prev3, 5); + const prev1 = generator.previous(prev2, 5); + equal(next1, 1731287); + equal(next2, 2324244); + equal(next3, 734347); + equal(prev3, 2324244); + equal(prev2, 1731287); + equal(prev1, 1); + }); + + it("should compute values in mode 6", () => { + const next1 = generator.next(1, 6); + const next2 = generator.next(next1, 6); + const next3 = generator.next(next2, 6); + const prev3 = generator.previous(next3, 6); + const prev2 = generator.previous(prev3, 6); + const prev1 = generator.previous(prev2, 6); + equal(next1, 45576512); + equal(next2, 127107601); + equal(next3, 29785520); + equal(prev3, 127107601); + equal(prev2, 45576512); + equal(prev1, 1); + }); + + it("should compute values in mode 7", () => { + const next1 = generator.next(1, 7); + const next2 = generator.next(next1, 7); + const next3 = generator.next(next2, 7); + const prev3 = generator.previous(next3, 7); + const prev2 = generator.previous(prev3, 7); + const prev1 = generator.previous(prev2, 7); + equal(next1, 1588635695); + equal(next2, 3795633075); + equal(next3, 1359842614); + equal(prev3, 3795633075); + equal(prev2, 1588635695); + equal(prev1, 1); + }); + + it("should compute values in mode 8", () => { + const next1 = generator.next(1, 8); + const next2 = generator.next(next1, 8); + const next3 = generator.next(next2, 8); + const prev3 = generator.previous(next3, 8); + const prev2 = generator.previous(prev3, 8); + const prev1 = generator.previous(prev2, 8); + equal(next1, 40162435147); + equal(next2, 6434331558); + equal(next3, 34088349488); + equal(prev3, 6434331558); + equal(prev2, 40162435147); + equal(prev1, 1); + }); + + it("should compute values in mode 9", () => { + const next1 = generator.next(1, 9); + const next2 = generator.next(next1, 9); + const next3 = generator.next(next2, 9); + const prev3 = generator.previous(next3, 9); + const prev2 = generator.previous(prev3, 9); + const prev1 = generator.previous(prev2, 9); + equal(next1, 717943173063); + equal(next2, 215767240487); + equal(next3, 1006265014734); + equal(prev3, 215767240487); + equal(prev2, 717943173063); + equal(prev1, 1); + }); + + it("should compute values in mode 10", () => { + const next1 = generator.next(1, 10); + const next2 = generator.next(next1, 10); + const next3 = generator.next(next2, 10); + const prev3 = generator.previous(next3, 10); + const prev2 = generator.previous(prev3, 10); + const prev1 = generator.previous(prev2, 10); + equal(next1, 11850386302026); + equal(next2, 26888292506525); + equal(next3, 4129700370630); + equal(prev3, 26888292506525); + equal(prev2, 11850386302026); + equal(prev1, 1); + }); + + it("should compute values in mode 11", () => { + const next1 = generator.next(1, 11); + const next2 = generator.next(next1, 11); + const next3 = generator.next(next2, 11); + const prev3 = generator.previous(next3, 11); + const prev2 = generator.previous(prev3, 11); + const prev1 = generator.previous(prev2, 11); + equal(next1, 605985299432352); + equal(next2, 277998636065574); + equal(next3, 307477457863406); + equal(prev3, 277998636065574); + equal(prev2, 605985299432352); + equal(prev1, 1); + }); + + it("should compute values in mode 12", () => { + const next1 = generator.next(1n, 12); + const next2 = generator.next(next1, 12); + const next3 = generator.next(next2, 12); + const prev3 = generator.previous(next3, 12); + const prev2 = generator.previous(prev3, 12); + const prev1 = generator.previous(prev2, 12); + equal(next1, 19708881949174686n); + equal(next2, 19671333105867716n); + equal(next3, 9303007882087066n); + equal(prev3, 19671333105867716n); + equal(prev2, 19708881949174686n); + equal(prev1, 1n); + }); + + it("should compute values in mode 13", () => { + const next1 = generator.next(1n, 13); + const next2 = generator.next(next1, 13); + const next3 = generator.next(next2, 13); + const prev3 = generator.previous(next3, 13); + const prev2 = generator.previous(prev3, 13); + const prev1 = generator.previous(prev2, 13); + equal(next1, 287514719519235431n); + equal(next2, 386640941313532123n); + equal(next3, 308504311051725166n); + equal(prev3, 386640941313532123n); + equal(prev2, 287514719519235431n); + equal(prev1, 1n); + }); + + it("should compute values in mode 14", () => { + const next1 = generator.next(1n, 14); + const next2 = generator.next(next1, 14); + const next3 = generator.next(next2, 14); + const prev3 = generator.previous(next3, 14); + const prev2 = generator.previous(prev3, 14); + const prev1 = generator.previous(prev2, 14); + equal(next1, 9044836419713972268n); + equal(next2, 17873101000765772758n); + equal(next3, 2109684930356029100n); + equal(prev3, 17873101000765772758n); + equal(prev2, 9044836419713972268n); + equal(prev1, 1n); + }); + }); + describe("With offsets", () => { const generator = new KeymaskGenerator(new Uint8Array([ 55, 98, 111, 81, 13, 119, 31, 74 @@ -406,6 +632,234 @@ describe("KeymaskGenerator", () => { }); }); + describe("With offsets, safe mode", () => { + const generator = new KeymaskGenerator(new Uint8Array([ + 55, 98, 111, 81, 13, 119, 31, 74 + ]).buffer, true); + + it("should handle empty inputs", () => { + equal(generator.next(0, 6), 0); + equal(generator.next(0n, 13), 0n); + equal(generator.previous(0, 3), 0); + equal(generator.previous(0n, 12), 0n); + }); + + it("should handle unexpected bigint inputs", () => { + equal(generator.next(1n, 1), 17); + equal(generator.previous(17n, 1), 1); + }); + + it("should compute values in mode 1", () => { + const next1 = generator.next(1, 1); + const next2 = generator.next(next1, 1); + const next3 = generator.next(next2, 1); + const prev3 = generator.previous(next3, 1); + const prev2 = generator.previous(prev3, 1); + const prev1 = generator.previous(prev2, 1); + equal(next1, 17); + equal(next2, 14); + equal(next3, 16); + equal(prev3, 14); + equal(prev2, 17); + equal(prev1, 1); + }); + + it("should compute values in mode 2", () => { + const next1 = generator.next(1, 2); + const next2 = generator.next(next1, 2); + const next3 = generator.next(next2, 2); + const prev3 = generator.previous(next3, 2); + const prev2 = generator.previous(prev3, 2); + const prev1 = generator.previous(prev2, 2); + equal(next1, 112); + equal(next2, 106); + equal(next3, 464); + equal(prev3, 106); + equal(prev2, 112); + equal(prev1, 1); + }); + + it("should compute values in mode 3", () => { + const next1 = generator.next(1, 3); + const next2 = generator.next(next1, 3); + const next3 = generator.next(next2, 3); + const prev3 = generator.previous(next3, 3); + const prev2 = generator.previous(prev3, 3); + const prev1 = generator.previous(prev2, 3); + equal(next1, 4874); + equal(next2, 3931); + equal(next3, 7561); + equal(prev3, 3931); + equal(prev2, 4874); + equal(prev1, 1); + }); + + it("should compute values in mode 4", () => { + const next1 = generator.next(1, 4); + const next2 = generator.next(next1, 4); + const next3 = generator.next(next2, 4); + const prev3 = generator.previous(next3, 4); + const prev2 = generator.previous(prev3, 4); + const prev1 = generator.previous(prev2, 4); + equal(next1, 108687); + equal(next2, 1112); + equal(next3, 96647); + equal(prev3, 1112); + equal(prev2, 108687); + equal(prev1, 1); + }); + + it("should compute values in mode 5", () => { + const next1 = generator.next(1, 5); + const next2 = generator.next(next1, 5); + const next3 = generator.next(next2, 5); + const prev3 = generator.previous(next3, 5); + const prev2 = generator.previous(prev3, 5); + const prev1 = generator.previous(prev2, 5); + equal(next1, 688585); + equal(next2, 3625865); + equal(next3, 2915301); + equal(prev3, 3625865); + equal(prev2, 688585); + equal(prev1, 1); + }); + + it("should compute values in mode 6", () => { + const next1 = generator.next(1, 6); + const next2 = generator.next(next1, 6); + const next3 = generator.next(next2, 6); + const prev3 = generator.previous(next3, 6); + const prev2 = generator.previous(prev3, 6); + const prev1 = generator.previous(prev2, 6); + equal(next1, 103253746); + equal(next2, 73518693); + equal(next3, 104442888); + equal(prev3, 73518693); + equal(prev2, 103253746); + equal(prev1, 1); + }); + + it("should compute values in mode 7", () => { + const next1 = generator.next(1, 7); + const next2 = generator.next(next1, 7); + const next3 = generator.next(next2, 7); + const prev3 = generator.previous(next3, 7); + const prev2 = generator.previous(prev3, 7); + const prev1 = generator.previous(prev2, 7); + equal(next1, 2048965993); + equal(next2, 2882355448); + equal(next3, 1358781941); + equal(prev3, 2882355448); + equal(prev2, 2048965993); + equal(prev1, 1); + }); + + it("should compute values in mode 8", () => { + const next1 = generator.next(1, 8); + const next2 = generator.next(next1, 8); + const next3 = generator.next(next2, 8); + const prev3 = generator.previous(next3, 8); + const prev2 = generator.previous(prev3, 8); + const prev1 = generator.previous(prev2, 8); + equal(next1, 29110128424); + equal(next2, 1183414022); + equal(next3, 1639275494); + equal(prev3, 1183414022); + equal(prev2, 29110128424); + equal(prev1, 1); + }); + + it("should compute values in mode 9", () => { + const next1 = generator.next(1, 9); + const next2 = generator.next(next1, 9); + const next3 = generator.next(next2, 9); + const prev3 = generator.previous(next3, 9); + const prev2 = generator.previous(prev3, 9); + const prev1 = generator.previous(prev2, 9); + equal(next1, 1874709064720); + equal(next2, 1085017648074); + equal(next3, 768581879344); + equal(prev3, 1085017648074); + equal(prev2, 1874709064720); + equal(prev1, 1); + }); + + it("should compute values in mode 10", () => { + const next1 = generator.next(1, 10); + const next2 = generator.next(next1, 10); + const next3 = generator.next(next2, 10); + const prev3 = generator.previous(next3, 10); + const prev2 = generator.previous(prev3, 10); + const prev1 = generator.previous(prev2, 10); + equal(next1, 2011990982049); + equal(next2, 15001030797763); + equal(next3, 10816797220892); + equal(prev3, 15001030797763); + equal(prev2, 2011990982049); + equal(prev1, 1); + }); + + it("should compute values in mode 11", () => { + const next1 = generator.next(1, 11); + const next2 = generator.next(next1, 11); + const next3 = generator.next(next2, 11); + const prev3 = generator.previous(next3, 11); + const prev2 = generator.previous(prev3, 11); + const prev1 = generator.previous(prev2, 11); + equal(next1, 455409407388855); + equal(next2, 1125415030257687); + equal(next3, 501822946954661); + equal(prev3, 1125415030257687); + equal(prev2, 455409407388855); + equal(prev1, 1); + }); + + it("should compute values in mode 12", () => { + const next1 = generator.next(1n, 12); + const next2 = generator.next(next1, 12); + const next3 = generator.next(next2, 12); + const prev3 = generator.previous(next3, 12); + const prev2 = generator.previous(prev3, 12); + const prev1 = generator.previous(prev2, 12); + equal(next1, 28565505311747637n); + equal(next2, 29473870147412094n); + equal(next3, 5738407593423799n); + equal(prev3, 29473870147412094n); + equal(prev2, 28565505311747637n); + equal(prev1, 1n); + }); + + it("should compute values in mode 13", () => { + const next1 = generator.next(1n, 13); + const next2 = generator.next(next1, 13); + const next3 = generator.next(next2, 13); + const prev3 = generator.previous(next3, 13); + const prev2 = generator.previous(prev3, 13); + const prev1 = generator.previous(prev2, 13); + equal(next1, 440486530957656470n); + equal(next2, 146641254034747117n); + equal(next3, 235389632144868738n); + equal(prev3, 146641254034747117n); + equal(prev2, 440486530957656470n); + equal(prev1, 1n); + }); + + it("should compute values in mode 14", () => { + const next1 = generator.next(1n, 14); + const next2 = generator.next(next1, 14); + const next3 = generator.next(next2, 14); + const prev3 = generator.previous(next3, 14); + const prev2 = generator.previous(prev3, 14); + const prev1 = generator.previous(prev2, 14); + equal(next1, 14385955001883204195n); + equal(next2, 10091079920073714907n); + equal(next3, 14289084463478828271n); + equal(prev3, 10091079920073714907n); + equal(prev2, 14385955001883204195n); + equal(prev1, 1n); + }); + }); + describe("Positive offsets", () => { const generator = new KeymaskGenerator(new Uint8Array([ 77, 158, 218, 19, 241, 33, 41, 218 diff --git a/util/harmonics.js b/util/harmonics.js index 8b93d02..6dbb9c6 100644 --- a/util/harmonics.js +++ b/util/harmonics.js @@ -2,101 +2,203 @@ const maxHarmonic = 8; const tests = [ { - modulus: 41, + modulus: 23, params: [ - [6, 7], - [22, 28], - [26, 30] + [5, 14], + [7, 10], + [11, 21], + [15, 20], + [17, 19] ] }, { - modulus: 1021, + modulus: 509, params: [ - [65, 377], - [644, 956] + [35, 160], + [110, 236], + [273, 399], + [349, 474] ] }, { - modulus: 65521, + modulus: 8191, params: [ - [17364, 32236], - [33285, 48157], - [2469, 47104] + [884, 7459], + [1716, 5580], + [2685, 6083] ] }, { - modulus: 2097143, + modulus: 262139, params: [ - [360889, 1372180], - [1043187, 1352851], - [1939807, 1969917] + [92717, 166972], + [21876, 118068] ] }, { - modulus: 67108859, + modulus: 4194301, params: [ - [26590841, 11526618], - [19552116, 24409594], - [66117721, 6763103] + [914334, 1406151], + [2788150, 3279967], + [1731287, 2040406], + [2463014, 2153895] ] }, { - modulus: 4294967291, + modulus: 134217689, params: [ - [1588635695, 3870709308], - [1223106847, 4223879656], - [279470273, 1815976680] + [45576512, 70391260], + [63826429, 88641177], + [3162696, 71543207] ] }, + // 2^32-5 { - modulus: 137438953447, + modulus: 68719476731, params: [ - [76886758244, 31450092817], - [2996735870, 105638438130], - [85876534675, 116895888786] + [49865143810, 44525253482], + [45453986995, 40162435147] ] }, { - modulus: 4398046511093, + modulus: 2199023255531, params: [ - [2214813540776, 4365946432566], - [2928603677866, 3015630915308], - [92644101553, 626031856758] + [140245111714, 1888116500887], + [416480024109, 1420814698317], + [1319743354064, 717943173063] ] }, { - modulus: 281474976710597, + modulus: 35184372088777, params: [ - [49235258628958, 253087341916107], - [51699608632694, 8419150949545], - [59279420901007, 163724808306782] + [25933916233908, 3608903742640], + [18586042069168, 11850386302026], + [20827157855185, 5870357204989] ] }, { - modulus: 9007199254740881, + modulus: 1125899906842597, params: [ - [2082839274626558, 3141627116318043], - [4179081713689027, 1169831480608704], - [5667072534355537, 7982986707690649] + [1087141320185010, 1051122009542795], + [157252724901243, 422705992136651], + [791038363307311, 605985299432352] ] }, { - modulus: 288230376151711717n, + modulus: 36028797018963913n, params: [ - [101565695086122187n, 56502943171806276n], - [163847936876980536n, 256462492811829427n], - [206638310974457555n, 28146528635210647n] + [33266544676670489n, 11719476530693442n], + [19708881949174686n, 32182684885571630n], + [32075972421209701n, 15995561023396933n] ] }, { - modulus: 18446744073709551557n, + modulus: 576460752303423433n, params: [ - [13891176665706064842n, 9044836419713972268n], - [2227057010910366687n, 17412224886468018797n], - [18263440312458789471n, 811465980874026894n] + [346764851511064641n, 287514719519235431n], + [124795884580648576n, 526457461907464601n], + [573223409952553925n, 81222304453481810n] ] } -] +]; + +// const tests = [ +// { +// modulus: 41, +// params: [ +// [6, 7], +// [22, 28], +// [26, 30] +// ] +// }, +// { +// modulus: 1021, +// params: [ +// [65, 377], +// [644, 956] +// ] +// }, +// { +// modulus: 65521, +// params: [ +// [17364, 32236], +// [33285, 48157], +// [2469, 47104] +// ] +// }, +// { +// modulus: 2097143, +// params: [ +// [360889, 1372180], +// [1043187, 1352851], +// [1939807, 1969917] +// ] +// }, +// { +// modulus: 67108859, +// params: [ +// [26590841, 11526618], +// [19552116, 24409594], +// [66117721, 6763103] +// ] +// }, +// { +// modulus: 4294967291, +// params: [ +// [1588635695, 3870709308], +// [1223106847, 4223879656], +// [279470273, 1815976680] +// ] +// }, +// { +// modulus: 137438953447, +// params: [ +// [76886758244, 31450092817], +// [2996735870, 105638438130], +// [85876534675, 116895888786] +// ] +// }, +// { +// modulus: 4398046511093, +// params: [ +// [2214813540776, 4365946432566], +// [2928603677866, 3015630915308], +// [92644101553, 626031856758] +// ] +// }, +// { +// modulus: 281474976710597, +// params: [ +// [49235258628958, 253087341916107], +// [51699608632694, 8419150949545], +// [59279420901007, 163724808306782] +// ] +// }, +// { +// modulus: 9007199254740881, +// params: [ +// [2082839274626558, 3141627116318043], +// [4179081713689027, 1169831480608704], +// [5667072534355537, 7982986707690649] +// ] +// }, +// { +// modulus: 288230376151711717n, +// params: [ +// [101565695086122187n, 56502943171806276n], +// [163847936876980536n, 256462492811829427n], +// [206638310974457555n, 28146528635210647n] +// ] +// }, +// { +// modulus: 18446744073709551557n, +// params: [ +// [13891176665706064842n, 9044836419713972268n], +// [2227057010910366687n, 17412224886468018797n], +// [18263440312458789471n, 811465980874026894n] +// ] +// } +// ]; let a, b; tests.forEach((test) => { diff --git a/util/lcg41.js b/util/lcg.js similarity index 96% rename from util/lcg41.js rename to util/lcg.js index 63253c0..ed376e8 100644 --- a/util/lcg41.js +++ b/util/lcg.js @@ -1,9 +1,10 @@ -// Makeshift script to roughly quantify the quality of base41 LCGs. +// Makeshift script to roughly quantify the quality of small LCGs. // Basically this measures the number of flipped bits at each step // and picks the full-cycle LCG that comes closest to flipping half // the bits at each step, with the smallest standard deviation. -const mod = 41; +const mod = 23; +// const mod = 41; const steps = mod - 1; // const half = steps / 2; const halfbits = Math.log2(mod) / 2;