@cloudyventures/baseh 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -0
- package/dist/basen.d.ts +2 -0
- package/dist/basen.js +7 -0
- package/dist/blocklist.js +4 -1
- package/dist/checksum.d.ts +4 -2
- package/dist/checksum.js +14 -8
- package/dist/cjs/basen.d.ts +7 -0
- package/dist/cjs/basen.js +47 -0
- package/dist/cjs/blocklist.d.ts +15 -0
- package/dist/cjs/blocklist.js +37 -0
- package/dist/cjs/checksum.d.ts +10 -0
- package/dist/cjs/checksum.js +40 -0
- package/dist/cjs/codec.d.ts +94 -0
- package/dist/cjs/codec.js +408 -0
- package/dist/cjs/errors.d.ts +8 -0
- package/dist/cjs/errors.js +15 -0
- package/dist/cjs/facade.d.ts +9 -0
- package/dist/cjs/facade.js +36 -0
- package/dist/cjs/feistel.d.ts +16 -0
- package/dist/cjs/feistel.js +119 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +50 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/profile.d.ts +91 -0
- package/dist/cjs/profile.js +279 -0
- package/dist/cjs/profiles.d.ts +33 -0
- package/dist/cjs/profiles.js +192 -0
- package/dist/codec.d.ts +58 -2
- package/dist/codec.js +246 -34
- package/dist/facade.d.ts +9 -0
- package/dist/facade.js +30 -0
- package/dist/feistel.d.ts +6 -0
- package/dist/feistel.js +11 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/profile.d.ts +52 -1
- package/dist/profile.js +129 -21
- package/dist/profiles.d.ts +18 -7
- package/dist/profiles.js +91 -29
- package/package.json +33 -7
- package/dist/zero.d.ts +0 -4
- package/dist/zero.js +0 -39
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripVowels = exports.effectiveBlocklist = exports.DEFAULT_BLOCKLIST = exports.inspect = exports.validate = exports.decode = exports.encode = exports.expandableGrouping = exports.generationForId = exports.generationCapacity = exports.generationBase = exports.FROZEN_KEY_BYTES = exports.basehExpandablePV1 = exports.basehExpandableV1 = exports.basehHeavyPV1 = exports.basehMediumPV1 = exports.basehLightPV1 = exports.basehMinimumPV1 = exports.basehHeavyV1 = exports.basehMediumV1 = exports.basehLightV1 = exports.basehMinimumV1 = exports.inversePermute = exports.permute = exports.checksumValue = exports.calculateChecksum = exports.alphabetIndex = exports.decodeBaseN = exports.encodeBaseN = exports.CONFUSION_MAPS = exports.generateCandidates = exports.formatRaw = exports.normalize = exports.Baseh = exports.effectiveChecksumLength = exports.prepareProfile = exports.BasehError = void 0;
|
|
4
|
+
var errors_js_1 = require("./errors.js");
|
|
5
|
+
Object.defineProperty(exports, "BasehError", { enumerable: true, get: function () { return errors_js_1.BasehError; } });
|
|
6
|
+
var profile_js_1 = require("./profile.js");
|
|
7
|
+
Object.defineProperty(exports, "prepareProfile", { enumerable: true, get: function () { return profile_js_1.prepareProfile; } });
|
|
8
|
+
Object.defineProperty(exports, "effectiveChecksumLength", { enumerable: true, get: function () { return profile_js_1.effectiveChecksumLength; } });
|
|
9
|
+
var codec_js_1 = require("./codec.js");
|
|
10
|
+
Object.defineProperty(exports, "Baseh", { enumerable: true, get: function () { return codec_js_1.Baseh; } });
|
|
11
|
+
Object.defineProperty(exports, "normalize", { enumerable: true, get: function () { return codec_js_1.normalize; } });
|
|
12
|
+
Object.defineProperty(exports, "formatRaw", { enumerable: true, get: function () { return codec_js_1.formatRaw; } });
|
|
13
|
+
Object.defineProperty(exports, "generateCandidates", { enumerable: true, get: function () { return codec_js_1.generateCandidates; } });
|
|
14
|
+
Object.defineProperty(exports, "CONFUSION_MAPS", { enumerable: true, get: function () { return codec_js_1.CONFUSION_MAPS; } });
|
|
15
|
+
var basen_js_1 = require("./basen.js");
|
|
16
|
+
Object.defineProperty(exports, "encodeBaseN", { enumerable: true, get: function () { return basen_js_1.encodeBaseN; } });
|
|
17
|
+
Object.defineProperty(exports, "decodeBaseN", { enumerable: true, get: function () { return basen_js_1.decodeBaseN; } });
|
|
18
|
+
Object.defineProperty(exports, "alphabetIndex", { enumerable: true, get: function () { return basen_js_1.alphabetIndex; } });
|
|
19
|
+
var checksum_js_1 = require("./checksum.js");
|
|
20
|
+
Object.defineProperty(exports, "calculateChecksum", { enumerable: true, get: function () { return checksum_js_1.calculateChecksum; } });
|
|
21
|
+
Object.defineProperty(exports, "checksumValue", { enumerable: true, get: function () { return checksum_js_1.checksumValue; } });
|
|
22
|
+
var feistel_js_1 = require("./feistel.js");
|
|
23
|
+
Object.defineProperty(exports, "permute", { enumerable: true, get: function () { return feistel_js_1.permute; } });
|
|
24
|
+
Object.defineProperty(exports, "inversePermute", { enumerable: true, get: function () { return feistel_js_1.inversePermute; } });
|
|
25
|
+
var profiles_js_1 = require("./profiles.js");
|
|
26
|
+
Object.defineProperty(exports, "basehMinimumV1", { enumerable: true, get: function () { return profiles_js_1.basehMinimumV1; } });
|
|
27
|
+
Object.defineProperty(exports, "basehLightV1", { enumerable: true, get: function () { return profiles_js_1.basehLightV1; } });
|
|
28
|
+
Object.defineProperty(exports, "basehMediumV1", { enumerable: true, get: function () { return profiles_js_1.basehMediumV1; } });
|
|
29
|
+
Object.defineProperty(exports, "basehHeavyV1", { enumerable: true, get: function () { return profiles_js_1.basehHeavyV1; } });
|
|
30
|
+
Object.defineProperty(exports, "basehMinimumPV1", { enumerable: true, get: function () { return profiles_js_1.basehMinimumPV1; } });
|
|
31
|
+
Object.defineProperty(exports, "basehLightPV1", { enumerable: true, get: function () { return profiles_js_1.basehLightPV1; } });
|
|
32
|
+
Object.defineProperty(exports, "basehMediumPV1", { enumerable: true, get: function () { return profiles_js_1.basehMediumPV1; } });
|
|
33
|
+
Object.defineProperty(exports, "basehHeavyPV1", { enumerable: true, get: function () { return profiles_js_1.basehHeavyPV1; } });
|
|
34
|
+
Object.defineProperty(exports, "basehExpandableV1", { enumerable: true, get: function () { return profiles_js_1.basehExpandableV1; } });
|
|
35
|
+
Object.defineProperty(exports, "basehExpandablePV1", { enumerable: true, get: function () { return profiles_js_1.basehExpandablePV1; } });
|
|
36
|
+
Object.defineProperty(exports, "FROZEN_KEY_BYTES", { enumerable: true, get: function () { return profiles_js_1.FROZEN_KEY_BYTES; } });
|
|
37
|
+
var codec_js_2 = require("./codec.js");
|
|
38
|
+
Object.defineProperty(exports, "generationBase", { enumerable: true, get: function () { return codec_js_2.generationBase; } });
|
|
39
|
+
Object.defineProperty(exports, "generationCapacity", { enumerable: true, get: function () { return codec_js_2.generationCapacity; } });
|
|
40
|
+
Object.defineProperty(exports, "generationForId", { enumerable: true, get: function () { return codec_js_2.generationForId; } });
|
|
41
|
+
Object.defineProperty(exports, "expandableGrouping", { enumerable: true, get: function () { return codec_js_2.expandableGrouping; } });
|
|
42
|
+
var facade_js_1 = require("./facade.js");
|
|
43
|
+
Object.defineProperty(exports, "encode", { enumerable: true, get: function () { return facade_js_1.encode; } });
|
|
44
|
+
Object.defineProperty(exports, "decode", { enumerable: true, get: function () { return facade_js_1.decode; } });
|
|
45
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return facade_js_1.validate; } });
|
|
46
|
+
Object.defineProperty(exports, "inspect", { enumerable: true, get: function () { return facade_js_1.inspect; } });
|
|
47
|
+
var blocklist_js_1 = require("./blocklist.js");
|
|
48
|
+
Object.defineProperty(exports, "DEFAULT_BLOCKLIST", { enumerable: true, get: function () { return blocklist_js_1.DEFAULT_BLOCKLIST; } });
|
|
49
|
+
Object.defineProperty(exports, "effectiveBlocklist", { enumerable: true, get: function () { return blocklist_js_1.effectiveBlocklist; } });
|
|
50
|
+
Object.defineProperty(exports, "stripVowels", { enumerable: true, get: function () { return blocklist_js_1.stripVowels; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type BasehProfanity } from "./blocklist.js";
|
|
2
|
+
export type BasehPermutation = {
|
|
3
|
+
enabled: false;
|
|
4
|
+
} | {
|
|
5
|
+
enabled: true;
|
|
6
|
+
algorithm: "feistel-v1";
|
|
7
|
+
keyId: string;
|
|
8
|
+
keyBytes: Uint8Array;
|
|
9
|
+
rounds: number;
|
|
10
|
+
};
|
|
11
|
+
export interface BasehProfile {
|
|
12
|
+
profileId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Spec 2.1/19.9. "fixed" keeps the classic constant-width behaviour;
|
|
15
|
+
* "expandable" gives variable-length codes driven by id magnitude
|
|
16
|
+
* (spec 19). Profiles that predate the mode field are fixed: the shared
|
|
17
|
+
* frozen vectors pin their byte-for-byte behaviour, so a missing mode is
|
|
18
|
+
* prepared as "fixed".
|
|
19
|
+
*/
|
|
20
|
+
mode?: "fixed" | "expandable";
|
|
21
|
+
bodyAlphabet: string;
|
|
22
|
+
/** Fixed mode only; ignored in expandable mode. */
|
|
23
|
+
bodyLength?: number;
|
|
24
|
+
/** Expandable mode only; default 4. Must exceed checksumLength. */
|
|
25
|
+
minLength?: number;
|
|
26
|
+
checksumAlphabet: string;
|
|
27
|
+
checksumLength: number;
|
|
28
|
+
/**
|
|
29
|
+
* Spec 22. Expandable mode only. The checksum width used by generations at
|
|
30
|
+
* or below `shortChecksumUntil`; may be 0 (a zero-checksum window: those
|
|
31
|
+
* generations carry no checksum symbols and no typo detection). Without a
|
|
32
|
+
* window (`shortChecksumUntil` absent or 0) this must be absent or 0.
|
|
33
|
+
*/
|
|
34
|
+
shortChecksumLength?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Spec 22. The last generation (total length) that uses the short
|
|
37
|
+
* checksum; 0 or absent turns the feature off (the codebase convention,
|
|
38
|
+
* like maxRepetition). When set it must be an integer from `minLength`
|
|
39
|
+
* through 8.
|
|
40
|
+
*/
|
|
41
|
+
shortChecksumUntil?: number;
|
|
42
|
+
caseSensitive: boolean;
|
|
43
|
+
separator: string;
|
|
44
|
+
/** Expandable mode only; default 0 (separator always applies). */
|
|
45
|
+
separatorMinLength?: number;
|
|
46
|
+
grouping: number[];
|
|
47
|
+
aliases: Record<string, string>;
|
|
48
|
+
permutation: BasehPermutation;
|
|
49
|
+
/** Spec 18. Defaults to mode "none". */
|
|
50
|
+
profanity?: BasehProfanity;
|
|
51
|
+
/**
|
|
52
|
+
* Spec 21. Maximum allowed run of the same symbol in a raw code. 0 (the
|
|
53
|
+
* default) disables the filter; otherwise it must be an integer of at
|
|
54
|
+
* least 3. A value above the code length is a legal no-op.
|
|
55
|
+
*/
|
|
56
|
+
maxRepetition?: number;
|
|
57
|
+
}
|
|
58
|
+
/** Case-prepared derived data, computed once at construction. */
|
|
59
|
+
export interface PreparedProfile extends BasehProfile {
|
|
60
|
+
readonly mode: "fixed" | "expandable";
|
|
61
|
+
readonly minLength: number;
|
|
62
|
+
readonly separatorMinLength: number;
|
|
63
|
+
readonly bodyAlphabetNorm: string;
|
|
64
|
+
readonly checksumAlphabetNorm: string;
|
|
65
|
+
readonly aliasesNorm: Record<string, string>;
|
|
66
|
+
readonly checksumModulus: bigint;
|
|
67
|
+
/** Fixed-mode capacity A^bodyLength. Meaningless in expandable mode. */
|
|
68
|
+
readonly capacity: bigint;
|
|
69
|
+
/** Spec 18. Empty unless the profile uses mode "blocklist". */
|
|
70
|
+
readonly blocklist: string[];
|
|
71
|
+
/** Spec 21. 0 disables the repetition filter. */
|
|
72
|
+
readonly maxRepetition: number;
|
|
73
|
+
/** Spec 22. 0 disables the short checksum. */
|
|
74
|
+
readonly shortChecksumLength: number;
|
|
75
|
+
/** Spec 22. Last short-checksum generation; 0 when the feature is off. */
|
|
76
|
+
readonly shortChecksumUntil: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Spec 22. The checksum length that applies to a generation of the given
|
|
80
|
+
* total length: `shortChecksumLength` at or below `shortChecksumUntil`,
|
|
81
|
+
* `checksumLength` above it (and always in fixed mode). The feature is on
|
|
82
|
+
* exactly when `shortChecksumUntil` is non-zero; a `shortChecksumLength` of
|
|
83
|
+
* 0 then means the window's generations carry no checksum symbols at all.
|
|
84
|
+
*/
|
|
85
|
+
export declare function effectiveChecksumLength(profile: PreparedProfile, length: number): number;
|
|
86
|
+
/**
|
|
87
|
+
* Validates a profile per spec section 2.2 and returns it with derived,
|
|
88
|
+
* pre-computed values. Throws BasehError INVALID_PROFILE on any violation.
|
|
89
|
+
* Call once at construction, never per encode/decode.
|
|
90
|
+
*/
|
|
91
|
+
export declare function prepareProfile(profile: BasehProfile): PreparedProfile;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.effectiveChecksumLength = effectiveChecksumLength;
|
|
4
|
+
exports.prepareProfile = prepareProfile;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const basen_js_1 = require("./basen.js");
|
|
7
|
+
const blocklist_js_1 = require("./blocklist.js");
|
|
8
|
+
/**
|
|
9
|
+
* Spec 22. The checksum length that applies to a generation of the given
|
|
10
|
+
* total length: `shortChecksumLength` at or below `shortChecksumUntil`,
|
|
11
|
+
* `checksumLength` above it (and always in fixed mode). The feature is on
|
|
12
|
+
* exactly when `shortChecksumUntil` is non-zero; a `shortChecksumLength` of
|
|
13
|
+
* 0 then means the window's generations carry no checksum symbols at all.
|
|
14
|
+
*/
|
|
15
|
+
function effectiveChecksumLength(profile, length) {
|
|
16
|
+
if (profile.mode === "expandable" && profile.shortChecksumUntil > 0 && length <= profile.shortChecksumUntil) {
|
|
17
|
+
return profile.shortChecksumLength;
|
|
18
|
+
}
|
|
19
|
+
return profile.checksumLength;
|
|
20
|
+
}
|
|
21
|
+
const ASCII_ONLY = /^[\x20-\x7e]*$/;
|
|
22
|
+
function fail(reason) {
|
|
23
|
+
throw new errors_js_1.BasehError("INVALID_PROFILE", `Invalid baseH profile: ${reason}`, false);
|
|
24
|
+
}
|
|
25
|
+
function isAsciiChar(ch) {
|
|
26
|
+
return ch.length === 1 && ASCII_ONLY.test(ch);
|
|
27
|
+
}
|
|
28
|
+
function norm(profile, ch) {
|
|
29
|
+
return profile.caseSensitive ? ch : ch.toUpperCase();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validates a profile per spec section 2.2 and returns it with derived,
|
|
33
|
+
* pre-computed values. Throws BasehError INVALID_PROFILE on any violation.
|
|
34
|
+
* Call once at construction, never per encode/decode.
|
|
35
|
+
*/
|
|
36
|
+
function prepareProfile(profile) {
|
|
37
|
+
if (!profile || typeof profile !== "object")
|
|
38
|
+
fail("profile is required");
|
|
39
|
+
if (typeof profile.profileId !== "string" || profile.profileId.length === 0) {
|
|
40
|
+
fail("profileId must be non-empty");
|
|
41
|
+
}
|
|
42
|
+
if (!ASCII_ONLY.test(profile.profileId))
|
|
43
|
+
fail("profileId must be ASCII");
|
|
44
|
+
// Spec 2.2/19.9. A persisted or frozen profile declares its mode; profiles
|
|
45
|
+
// built before the mode field existed are fixed, so the frozen vectors keep
|
|
46
|
+
// matching byte for byte.
|
|
47
|
+
const mode = profile.mode ?? "fixed";
|
|
48
|
+
if (mode !== "fixed" && mode !== "expandable")
|
|
49
|
+
fail("mode must be fixed or expandable");
|
|
50
|
+
const caseSensitive = profile.caseSensitive === true;
|
|
51
|
+
const bodyAlphabet = profile.bodyAlphabet;
|
|
52
|
+
if (typeof bodyAlphabet !== "string" || bodyAlphabet.length < 2) {
|
|
53
|
+
fail("bodyAlphabet needs at least two symbols");
|
|
54
|
+
}
|
|
55
|
+
for (const ch of bodyAlphabet) {
|
|
56
|
+
if (!isAsciiChar(ch))
|
|
57
|
+
fail(`body alphabet symbol is not single ASCII: ${JSON.stringify(ch)}`);
|
|
58
|
+
}
|
|
59
|
+
const view = { caseSensitive };
|
|
60
|
+
let bodyNorm = [...bodyAlphabet].map((c) => norm(view, c)).join("");
|
|
61
|
+
// Spec 19.2: in expandable mode the zero ban strips 0 and O from the body
|
|
62
|
+
// alphabet silently, before any other validation, exactly like the
|
|
63
|
+
// no-vowels strip of section 18.1.
|
|
64
|
+
if (mode === "expandable") {
|
|
65
|
+
bodyNorm = [...bodyNorm].filter((c) => c !== "0" && c !== "O").join("");
|
|
66
|
+
}
|
|
67
|
+
if (new Set(bodyNorm).size !== bodyNorm.length) {
|
|
68
|
+
fail("body alphabet symbols must be unique after case normalization");
|
|
69
|
+
}
|
|
70
|
+
if (mode === "fixed") {
|
|
71
|
+
if (!Number.isInteger(profile.bodyLength) ||
|
|
72
|
+
profile.bodyLength < 1 ||
|
|
73
|
+
profile.bodyLength > 32) {
|
|
74
|
+
fail("bodyLength must be an integer from 1 through 32");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const minLength = profile.minLength ?? 4;
|
|
78
|
+
const separatorMinLength = profile.separatorMinLength ?? 0;
|
|
79
|
+
if (mode === "fixed" && separatorMinLength !== 0) {
|
|
80
|
+
fail("separatorMinLength must be 0 in fixed mode");
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isInteger(profile.checksumLength) ||
|
|
83
|
+
profile.checksumLength < 0 ||
|
|
84
|
+
profile.checksumLength > 8) {
|
|
85
|
+
fail("checksumLength must be an integer from 0 through 8");
|
|
86
|
+
}
|
|
87
|
+
if (mode === "expandable") {
|
|
88
|
+
if (!Number.isInteger(minLength) || minLength < 1) {
|
|
89
|
+
fail("minLength must be an integer of at least 1");
|
|
90
|
+
}
|
|
91
|
+
if (minLength <= profile.checksumLength) {
|
|
92
|
+
fail("minLength must be greater than checksumLength");
|
|
93
|
+
}
|
|
94
|
+
if (!Number.isInteger(separatorMinLength) || separatorMinLength < 0) {
|
|
95
|
+
fail("separatorMinLength must be an integer of at least 0");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Spec 22. The short checksum is expandable-only. The window field is the
|
|
99
|
+
// switch: `shortChecksumUntil` of 0 or absent turns the feature off (the
|
|
100
|
+
// codebase convention, like maxRepetition), and the length field without
|
|
101
|
+
// a window is INVALID_PROFILE.
|
|
102
|
+
const shortChecksumLength = profile.shortChecksumLength ?? 0;
|
|
103
|
+
const shortChecksumUntil = profile.shortChecksumUntil ?? 0;
|
|
104
|
+
if (mode === "fixed") {
|
|
105
|
+
if (shortChecksumLength !== 0 || shortChecksumUntil !== 0) {
|
|
106
|
+
fail("shortChecksumLength and shortChecksumUntil are expandable-mode only");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (shortChecksumUntil !== 0) {
|
|
110
|
+
if (!Number.isInteger(profile.shortChecksumUntil) || shortChecksumUntil < minLength) {
|
|
111
|
+
fail("shortChecksumUntil must be an integer of at least minLength");
|
|
112
|
+
}
|
|
113
|
+
// Beyond 8 the window would swallow nearly every practical code, and
|
|
114
|
+
// long codes genuinely want two checksum symbols.
|
|
115
|
+
if (shortChecksumUntil > 8) {
|
|
116
|
+
fail("shortChecksumUntil must be at most 8");
|
|
117
|
+
}
|
|
118
|
+
if (!Number.isInteger(shortChecksumLength) ||
|
|
119
|
+
shortChecksumLength < 0 ||
|
|
120
|
+
shortChecksumLength >= profile.checksumLength) {
|
|
121
|
+
fail("shortChecksumLength must be an integer from 0 through checksumLength - 1");
|
|
122
|
+
}
|
|
123
|
+
if (minLength <= shortChecksumLength) {
|
|
124
|
+
fail("minLength must be greater than shortChecksumLength");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (shortChecksumLength !== 0) {
|
|
128
|
+
fail("shortChecksumLength requires shortChecksumUntil");
|
|
129
|
+
}
|
|
130
|
+
const checksumAlphabet = profile.checksumAlphabet ?? "";
|
|
131
|
+
let checksumNorm = [...checksumAlphabet].map((c) => norm(view, c)).join("");
|
|
132
|
+
if (mode === "expandable") {
|
|
133
|
+
// Spec 19.3: the checksum alphabet is derived, "0" followed by the body
|
|
134
|
+
// alphabet in order. The configured checksumAlphabet is not consulted.
|
|
135
|
+
checksumNorm = "";
|
|
136
|
+
}
|
|
137
|
+
else if (profile.checksumLength > 0) {
|
|
138
|
+
if (typeof checksumAlphabet !== "string" || checksumAlphabet.length < 2) {
|
|
139
|
+
fail("checksumAlphabet needs at least two symbols when checksumLength is positive");
|
|
140
|
+
}
|
|
141
|
+
for (const ch of checksumAlphabet) {
|
|
142
|
+
if (!isAsciiChar(ch))
|
|
143
|
+
fail(`checksum alphabet symbol is not single ASCII: ${JSON.stringify(ch)}`);
|
|
144
|
+
}
|
|
145
|
+
if (new Set(checksumNorm).size !== checksumNorm.length) {
|
|
146
|
+
fail("checksum alphabet symbols must be unique after case normalization");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Spec 18. no-vowels strips vowels before every downstream rule; blocklist
|
|
150
|
+
// only arms the encode-time scan.
|
|
151
|
+
const profanity = profile.profanity ?? { mode: "none" };
|
|
152
|
+
if (!["none", "no-vowels", "blocklist"].includes(profanity.mode)) {
|
|
153
|
+
fail("profanity mode must be none, no-vowels or blocklist");
|
|
154
|
+
}
|
|
155
|
+
if (profanity.mode === "no-vowels") {
|
|
156
|
+
bodyNorm = (0, blocklist_js_1.stripVowels)(bodyNorm);
|
|
157
|
+
checksumNorm = (0, blocklist_js_1.stripVowels)(checksumNorm);
|
|
158
|
+
if (bodyNorm.length < 2) {
|
|
159
|
+
fail("no-vowels mode leaves the body alphabet with fewer than two symbols");
|
|
160
|
+
}
|
|
161
|
+
if (mode === "fixed" && profile.checksumLength > 0 && checksumNorm.length < 2) {
|
|
162
|
+
fail("no-vowels mode leaves the checksum alphabet with fewer than two symbols");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (mode === "expandable") {
|
|
166
|
+
// Derived after every body strip (zero ban, no-vowels) so all downstream
|
|
167
|
+
// rules — modulus, separator collision, alias targets — see the final
|
|
168
|
+
// alphabets.
|
|
169
|
+
checksumNorm = "0" + bodyNorm;
|
|
170
|
+
}
|
|
171
|
+
if (bodyNorm.length < 2) {
|
|
172
|
+
fail("body alphabet needs at least two symbols after preparation");
|
|
173
|
+
}
|
|
174
|
+
const blocklist = profanity.mode === "blocklist" ? (0, blocklist_js_1.effectiveBlocklist)(profanity) : [];
|
|
175
|
+
// Spec 21: 0 disables the filter; an active filter needs a floor of 3 —
|
|
176
|
+
// banning pairs (2) would destroy roughly 9% of every generation.
|
|
177
|
+
const maxRepetition = profile.maxRepetition ?? 0;
|
|
178
|
+
if (!Number.isInteger(maxRepetition) || maxRepetition < 0 || (maxRepetition > 0 && maxRepetition < 3)) {
|
|
179
|
+
fail("maxRepetition must be 0 (off) or an integer of at least 3");
|
|
180
|
+
}
|
|
181
|
+
const separator = profile.separator ?? "";
|
|
182
|
+
for (const ch of separator) {
|
|
183
|
+
if (bodyNorm.includes(ch) || checksumNorm.includes(ch)) {
|
|
184
|
+
fail("separator must not occur in either alphabet");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const aliases = profile.aliases ?? {};
|
|
188
|
+
const aliasesNorm = {};
|
|
189
|
+
const canonicalSet = new Set([...bodyNorm, ...checksumNorm]);
|
|
190
|
+
for (const [src, tgt] of Object.entries(aliases)) {
|
|
191
|
+
if (!isAsciiChar(src))
|
|
192
|
+
fail(`alias source is not single ASCII: ${JSON.stringify(src)}`);
|
|
193
|
+
if (!isAsciiChar(tgt))
|
|
194
|
+
fail(`alias target is not single ASCII: ${JSON.stringify(tgt)}`);
|
|
195
|
+
const sNorm = norm(view, src);
|
|
196
|
+
const tNorm = norm(view, tgt);
|
|
197
|
+
if (!canonicalSet.has(tNorm)) {
|
|
198
|
+
fail(`alias target ${JSON.stringify(tgt)} is not a canonical symbol`);
|
|
199
|
+
}
|
|
200
|
+
// Spec 3.2: an alias must never map two distinct canonical symbols into
|
|
201
|
+
// one value. Fixed mode rejects a canonical alias source outright. In
|
|
202
|
+
// expandable mode the frozen tier (spec 17.1) carries aliases whose
|
|
203
|
+
// sources are canonical body symbols (T, N, W stay in the body
|
|
204
|
+
// alphabet); the canonical symbol wins at normalization, making those
|
|
205
|
+
// entries inert instead of destructive.
|
|
206
|
+
if (mode === "fixed" && canonicalSet.has(sNorm)) {
|
|
207
|
+
fail(`alias source ${JSON.stringify(src)} is already a canonical symbol`);
|
|
208
|
+
}
|
|
209
|
+
if (sNorm in aliasesNorm)
|
|
210
|
+
fail(`duplicate alias source ${JSON.stringify(sNorm)} after case normalization`);
|
|
211
|
+
if (tNorm in aliases || [...Object.keys(aliases)].some((k) => norm(view, k) === tNorm)) {
|
|
212
|
+
fail(`alias chain forbidden: target ${tNorm} is also an alias source`);
|
|
213
|
+
}
|
|
214
|
+
aliasesNorm[sNorm] = tNorm;
|
|
215
|
+
}
|
|
216
|
+
const total = bodySum(profile.grouping);
|
|
217
|
+
if (separator.length === 0) {
|
|
218
|
+
if (profile.grouping.length !== 0)
|
|
219
|
+
fail("grouping must be empty when separator is empty");
|
|
220
|
+
}
|
|
221
|
+
else if (mode === "expandable") {
|
|
222
|
+
// Spec 19.5: the balanced grouping rule is a pure function of the total
|
|
223
|
+
// length, so a configurable grouping is meaningless in expandable mode.
|
|
224
|
+
if (profile.grouping.length !== 0)
|
|
225
|
+
fail("grouping must be empty in expandable mode");
|
|
226
|
+
}
|
|
227
|
+
else if (total !== profile.bodyLength + profile.checksumLength) {
|
|
228
|
+
fail("group sizes must sum to bodyLength + checksumLength");
|
|
229
|
+
}
|
|
230
|
+
const permutation = profile.permutation ?? { enabled: false };
|
|
231
|
+
if (permutation.enabled) {
|
|
232
|
+
if (permutation.algorithm !== "feistel-v1")
|
|
233
|
+
fail("unknown permutation algorithm");
|
|
234
|
+
if (typeof permutation.keyId !== "string" || permutation.keyId.length === 0) {
|
|
235
|
+
fail("permutation requires a keyId");
|
|
236
|
+
}
|
|
237
|
+
if (!(permutation.keyBytes instanceof Uint8Array) || permutation.keyBytes.length === 0) {
|
|
238
|
+
fail("permutation requires key material");
|
|
239
|
+
}
|
|
240
|
+
if (!Number.isInteger(permutation.rounds) ||
|
|
241
|
+
permutation.rounds < 4 ||
|
|
242
|
+
permutation.rounds > 16 ||
|
|
243
|
+
permutation.rounds % 2 !== 0) {
|
|
244
|
+
fail("Feistel rounds must be an even integer from 4 through 16");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
...profile,
|
|
249
|
+
mode,
|
|
250
|
+
minLength,
|
|
251
|
+
separatorMinLength,
|
|
252
|
+
caseSensitive,
|
|
253
|
+
checksumAlphabet,
|
|
254
|
+
separator,
|
|
255
|
+
grouping: [...profile.grouping],
|
|
256
|
+
aliases: { ...aliases },
|
|
257
|
+
permutation,
|
|
258
|
+
bodyAlphabetNorm: bodyNorm,
|
|
259
|
+
checksumAlphabetNorm: checksumNorm,
|
|
260
|
+
aliasesNorm,
|
|
261
|
+
checksumModulus: (0, basen_js_1.powBigInt)(BigInt(checksumNorm.length || 1), profile.checksumLength),
|
|
262
|
+
capacity: (0, basen_js_1.powBigInt)(BigInt(bodyNorm.length), profile.bodyLength ?? 0),
|
|
263
|
+
blocklist,
|
|
264
|
+
maxRepetition,
|
|
265
|
+
shortChecksumLength,
|
|
266
|
+
shortChecksumUntil
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function bodySum(grouping) {
|
|
270
|
+
if (!Array.isArray(grouping))
|
|
271
|
+
return -1;
|
|
272
|
+
let sum = 0;
|
|
273
|
+
for (const g of grouping) {
|
|
274
|
+
if (!Number.isInteger(g) || g < 1)
|
|
275
|
+
return -1;
|
|
276
|
+
sum += g;
|
|
277
|
+
}
|
|
278
|
+
return sum;
|
|
279
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BasehProfile } from "./profile.js";
|
|
2
|
+
/**
|
|
3
|
+
* The frozen published permutation key. Public by design: it makes issued
|
|
4
|
+
* codes look non-sequential but offers no secrecy, since anyone can read it
|
|
5
|
+
* here. Never swap it on a live namespace; codes only decode with the key
|
|
6
|
+
* they were issued under. Use the -p variants to supply private key material.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FROZEN_KEY_BYTES: Uint8Array;
|
|
9
|
+
export interface FrozenKeyOptions {
|
|
10
|
+
keyBytes: Uint8Array;
|
|
11
|
+
keyId?: string;
|
|
12
|
+
rounds?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Alphanumeric, no safety strips, no checksum, hyphen-delimited XXX-XXX. */
|
|
15
|
+
export declare function basehMinimumV1(): BasehProfile;
|
|
16
|
+
/** baseh-minimum permuted with caller-supplied key material. */
|
|
17
|
+
export declare function basehMinimumPV1(options: FrozenKeyOptions): BasehProfile;
|
|
18
|
+
/** Visual light plus spoken light, two checksum symbols, hyphen-delimited. */
|
|
19
|
+
export declare function basehLightV1(): BasehProfile;
|
|
20
|
+
/** baseh-light permuted with caller-supplied key material. */
|
|
21
|
+
export declare function basehLightPV1(options: FrozenKeyOptions): BasehProfile;
|
|
22
|
+
/** Visual medium plus spoken medium, two checksum symbols, hyphen-delimited. The default. */
|
|
23
|
+
export declare function basehMediumV1(): BasehProfile;
|
|
24
|
+
/** baseh-medium permuted with caller-supplied key material. */
|
|
25
|
+
export declare function basehMediumPV1(options: FrozenKeyOptions): BasehProfile;
|
|
26
|
+
/** Conservative alphabet plus spoken heavy, two checksum symbols, hyphen-delimited. */
|
|
27
|
+
export declare function basehHeavyV1(): BasehProfile;
|
|
28
|
+
/** The frozen expandable tier; the recommended starting point for new namespaces. */
|
|
29
|
+
export declare function basehExpandableV1(): BasehProfile;
|
|
30
|
+
/** baseh-expandable permuted with caller-supplied key material. */
|
|
31
|
+
export declare function basehExpandablePV1(options: FrozenKeyOptions): BasehProfile;
|
|
32
|
+
/** baseh-heavy permuted with caller-supplied key material. */
|
|
33
|
+
export declare function basehHeavyPV1(options: FrozenKeyOptions): BasehProfile;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FROZEN_KEY_BYTES = void 0;
|
|
4
|
+
exports.basehMinimumV1 = basehMinimumV1;
|
|
5
|
+
exports.basehMinimumPV1 = basehMinimumPV1;
|
|
6
|
+
exports.basehLightV1 = basehLightV1;
|
|
7
|
+
exports.basehLightPV1 = basehLightPV1;
|
|
8
|
+
exports.basehMediumV1 = basehMediumV1;
|
|
9
|
+
exports.basehMediumPV1 = basehMediumPV1;
|
|
10
|
+
exports.basehHeavyV1 = basehHeavyV1;
|
|
11
|
+
exports.basehExpandableV1 = basehExpandableV1;
|
|
12
|
+
exports.basehExpandablePV1 = basehExpandablePV1;
|
|
13
|
+
exports.basehHeavyPV1 = basehHeavyPV1;
|
|
14
|
+
/**
|
|
15
|
+
* Frozen tiers. Each is built from the full alphanumeric set with cumulative
|
|
16
|
+
* visual and spoken strips; the spoken strips interact with the visual ones
|
|
17
|
+
* exactly as the web tools derive them, so the tool capacities match.
|
|
18
|
+
*
|
|
19
|
+
* Minimum 36 symbols, no checksum, XXX-XXX 2,176,782,336 ids
|
|
20
|
+
* Light 31 symbols, 2 checksums, XXXX-XXXX 887,503,681 ids
|
|
21
|
+
* Medium 28 symbols, 2 checksums, XXXX-XXXX 481,890,304 ids (default)
|
|
22
|
+
* Heavy 26 symbols, 2 checksums, XXXX-XXXX 308,915,776 ids
|
|
23
|
+
*
|
|
24
|
+
* All four keep the typed O/I/L aliases where possible, use a hyphen
|
|
25
|
+
* delimiter at the midpoint and run the default profanity blocklist. Every
|
|
26
|
+
* tier permutes with the frozen published key (FROZEN_KEY_BYTES below): the
|
|
27
|
+
* key is public, so the permutation obscures sequence but is not secrecy.
|
|
28
|
+
* The -p variants are identical but permute with caller-supplied key
|
|
29
|
+
* material instead.
|
|
30
|
+
*/
|
|
31
|
+
const OIL_ALIASES = { O: "0", I: "1", L: "1" };
|
|
32
|
+
const MINIMUM_BODY = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
33
|
+
const LIGHT_BODY = "0123456789ABCEFGHJKMNPQRSUVWXYZ";
|
|
34
|
+
const MEDIUM_BODY = "0123456789ACDEFGHJKMPQRUVXYZ";
|
|
35
|
+
const HEAVY_BODY = "0123456789ABCEFHJKMPQRVXYZ";
|
|
36
|
+
const LIGHT_CHECK = "234679ACEFGHJKMNPQRUVWXY";
|
|
37
|
+
const MEDIUM_CHECK = "234679ACDEFGHJKMPQRUVXY";
|
|
38
|
+
const HEAVY_CHECK = "234679ACEFHJKMPQRUVXY";
|
|
39
|
+
/**
|
|
40
|
+
* The frozen published permutation key. Public by design: it makes issued
|
|
41
|
+
* codes look non-sequential but offers no secrecy, since anyone can read it
|
|
42
|
+
* here. Never swap it on a live namespace; codes only decode with the key
|
|
43
|
+
* they were issued under. Use the -p variants to supply private key material.
|
|
44
|
+
*/
|
|
45
|
+
exports.FROZEN_KEY_BYTES = new TextEncoder().encode("baseh-frozen-key-v1");
|
|
46
|
+
function keyedPermutation(options) {
|
|
47
|
+
return {
|
|
48
|
+
enabled: true,
|
|
49
|
+
algorithm: "feistel-v1",
|
|
50
|
+
keyId: options.keyId ?? "default",
|
|
51
|
+
keyBytes: options.keyBytes,
|
|
52
|
+
rounds: options.rounds ?? 8
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function tier(shape, permutation, pSuffix) {
|
|
56
|
+
return {
|
|
57
|
+
profileId: shape.profileId + (pSuffix ? "-p" : "") + "-v1",
|
|
58
|
+
mode: "fixed",
|
|
59
|
+
bodyAlphabet: shape.bodyAlphabet,
|
|
60
|
+
bodyLength: 6,
|
|
61
|
+
checksumAlphabet: shape.checksumAlphabet,
|
|
62
|
+
checksumLength: shape.checksumLength,
|
|
63
|
+
caseSensitive: false,
|
|
64
|
+
separator: shape.separator,
|
|
65
|
+
grouping: shape.grouping,
|
|
66
|
+
aliases: { ...shape.aliases },
|
|
67
|
+
permutation,
|
|
68
|
+
profanity: { mode: "blocklist" },
|
|
69
|
+
maxRepetition: 4
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const MINIMUM = {
|
|
73
|
+
profileId: "baseh-minimum",
|
|
74
|
+
bodyAlphabet: MINIMUM_BODY,
|
|
75
|
+
checksumAlphabet: "",
|
|
76
|
+
checksumLength: 0,
|
|
77
|
+
separator: "-",
|
|
78
|
+
grouping: [3, 3],
|
|
79
|
+
aliases: {}
|
|
80
|
+
};
|
|
81
|
+
const LIGHT = {
|
|
82
|
+
profileId: "baseh-light",
|
|
83
|
+
bodyAlphabet: LIGHT_BODY,
|
|
84
|
+
checksumAlphabet: LIGHT_CHECK,
|
|
85
|
+
checksumLength: 2,
|
|
86
|
+
separator: "-",
|
|
87
|
+
grouping: [4, 4],
|
|
88
|
+
aliases: { ...OIL_ALIASES, D: "B", T: "P" }
|
|
89
|
+
};
|
|
90
|
+
const MEDIUM = {
|
|
91
|
+
profileId: "baseh-medium",
|
|
92
|
+
bodyAlphabet: MEDIUM_BODY,
|
|
93
|
+
checksumAlphabet: MEDIUM_CHECK,
|
|
94
|
+
checksumLength: 2,
|
|
95
|
+
separator: "-",
|
|
96
|
+
grouping: [4, 4],
|
|
97
|
+
// B and S are dropped for looking like 8 and 5; since they can never be
|
|
98
|
+
// issued, a typed B is always an 8 and a typed S always a 5.
|
|
99
|
+
aliases: { ...OIL_ALIASES, B: "8", S: "5", T: "P", N: "M", W: "V" }
|
|
100
|
+
};
|
|
101
|
+
const HEAVY = {
|
|
102
|
+
profileId: "baseh-heavy",
|
|
103
|
+
bodyAlphabet: HEAVY_BODY,
|
|
104
|
+
checksumAlphabet: HEAVY_CHECK,
|
|
105
|
+
checksumLength: 2,
|
|
106
|
+
separator: "-",
|
|
107
|
+
grouping: [4, 4],
|
|
108
|
+
aliases: { ...OIL_ALIASES, D: "B", T: "P", N: "M", W: "V", S: "F", G: "C" }
|
|
109
|
+
};
|
|
110
|
+
/** Permutation every plain tier applies, built from the frozen published key. */
|
|
111
|
+
function frozenPermutation() {
|
|
112
|
+
return keyedPermutation({ keyBytes: exports.FROZEN_KEY_BYTES, keyId: "frozen" });
|
|
113
|
+
}
|
|
114
|
+
/** Alphanumeric, no safety strips, no checksum, hyphen-delimited XXX-XXX. */
|
|
115
|
+
function basehMinimumV1() {
|
|
116
|
+
return tier(MINIMUM, frozenPermutation(), false);
|
|
117
|
+
}
|
|
118
|
+
/** baseh-minimum permuted with caller-supplied key material. */
|
|
119
|
+
function basehMinimumPV1(options) {
|
|
120
|
+
return tier(MINIMUM, keyedPermutation(options), true);
|
|
121
|
+
}
|
|
122
|
+
/** Visual light plus spoken light, two checksum symbols, hyphen-delimited. */
|
|
123
|
+
function basehLightV1() {
|
|
124
|
+
return tier(LIGHT, frozenPermutation(), false);
|
|
125
|
+
}
|
|
126
|
+
/** baseh-light permuted with caller-supplied key material. */
|
|
127
|
+
function basehLightPV1(options) {
|
|
128
|
+
return tier(LIGHT, keyedPermutation(options), true);
|
|
129
|
+
}
|
|
130
|
+
/** Visual medium plus spoken medium, two checksum symbols, hyphen-delimited. The default. */
|
|
131
|
+
function basehMediumV1() {
|
|
132
|
+
return tier(MEDIUM, frozenPermutation(), false);
|
|
133
|
+
}
|
|
134
|
+
/** baseh-medium permuted with caller-supplied key material. */
|
|
135
|
+
function basehMediumPV1(options) {
|
|
136
|
+
return tier(MEDIUM, keyedPermutation(options), true);
|
|
137
|
+
}
|
|
138
|
+
/** Conservative alphabet plus spoken heavy, two checksum symbols, hyphen-delimited. */
|
|
139
|
+
function basehHeavyV1() {
|
|
140
|
+
return tier(HEAVY, frozenPermutation(), false);
|
|
141
|
+
}
|
|
142
|
+
// Spec 17.1. The expandable recommended default carries the same safety
|
|
143
|
+
// posture as baseh-medium-v1: the medium visual strips (O, I, L, B, S) and
|
|
144
|
+
// the medium spoken strips (T, N, W), so an issued code never emits a
|
|
145
|
+
// visual or spoken confusable. The zero ban of section 19.2 then removes 0
|
|
146
|
+
// (and O, already gone), leaving a 27-symbol body. The checksum alphabet
|
|
147
|
+
// derives as "0" plus the body (28 symbols).
|
|
148
|
+
const EXPANDABLE_BODY = "123456789ACDEFGHJKMPQRUVXYZ";
|
|
149
|
+
/**
|
|
150
|
+
* Spec 17.1. The frozen expandable tier: four characters while the namespace
|
|
151
|
+
* is small, gaining one symbol automatically as issuance climbs past each
|
|
152
|
+
* generation's capacity. The body alphabet is the medium safety set (the
|
|
153
|
+
* full alphanumeric set minus the visual look-alikes O, I, L, B, S and the
|
|
154
|
+
* spoken-confusable T, N, W), with the zero ban of spec 19.2 removing 0 (27
|
|
155
|
+
* symbols); the checksum alphabet derives as "0" plus the body (28 symbols).
|
|
156
|
+
* The short checksum of spec 22 is on: one checksum symbol through five
|
|
157
|
+
* characters (27^3 = 19,683 ids at length 4, 27^4 = 531,441 at length 5),
|
|
158
|
+
* two from six characters up. The hyphen appears from six characters up,
|
|
159
|
+
* split by the balanced grouping rule of spec 19.5.
|
|
160
|
+
*/
|
|
161
|
+
function expandableTier(permutation, pSuffix) {
|
|
162
|
+
return {
|
|
163
|
+
profileId: "baseh-expandable" + (pSuffix ? "-p" : "") + "-v1",
|
|
164
|
+
mode: "expandable",
|
|
165
|
+
bodyAlphabet: EXPANDABLE_BODY,
|
|
166
|
+
minLength: 4,
|
|
167
|
+
checksumAlphabet: "0" + EXPANDABLE_BODY,
|
|
168
|
+
checksumLength: 2,
|
|
169
|
+
shortChecksumLength: 1,
|
|
170
|
+
shortChecksumUntil: 5,
|
|
171
|
+
caseSensitive: false,
|
|
172
|
+
separator: "-",
|
|
173
|
+
separatorMinLength: 6,
|
|
174
|
+
grouping: [],
|
|
175
|
+
aliases: { ...OIL_ALIASES, B: "8", S: "5", T: "P", N: "M", W: "V" },
|
|
176
|
+
permutation,
|
|
177
|
+
profanity: { mode: "blocklist" },
|
|
178
|
+
maxRepetition: 4
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** The frozen expandable tier; the recommended starting point for new namespaces. */
|
|
182
|
+
function basehExpandableV1() {
|
|
183
|
+
return expandableTier(frozenPermutation(), false);
|
|
184
|
+
}
|
|
185
|
+
/** baseh-expandable permuted with caller-supplied key material. */
|
|
186
|
+
function basehExpandablePV1(options) {
|
|
187
|
+
return expandableTier(keyedPermutation(options), true);
|
|
188
|
+
}
|
|
189
|
+
/** baseh-heavy permuted with caller-supplied key material. */
|
|
190
|
+
function basehHeavyPV1(options) {
|
|
191
|
+
return tier(HEAVY, keyedPermutation(options), true);
|
|
192
|
+
}
|