@tnid/encryption 0.0.4

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/script/aes.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * AES-128 block cipher wrapper using Web Crypto API.
4
+ *
5
+ * FF1 requires raw AES block cipher (AES-ECB), which Web Crypto doesn't expose.
6
+ * We simulate AES-ECB using AES-CBC with a zero IV for single-block operations.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.Aes128 = void 0;
43
+ // Web Crypto API accessor (same pattern as @tnid/core)
44
+ // deno-lint-ignore no-explicit-any
45
+ const dntShim = __importStar(require("./_dnt.shims.js"));
46
+ const crypto = dntShim.dntGlobalThis.crypto;
47
+ /**
48
+ * AES-128 block cipher for FF1.
49
+ * Caches the imported CryptoKey for efficiency.
50
+ */
51
+ class Aes128 {
52
+ constructor(keyBytes) {
53
+ Object.defineProperty(this, "keyPromise", {
54
+ enumerable: true,
55
+ configurable: true,
56
+ writable: true,
57
+ value: void 0
58
+ });
59
+ if (keyBytes.length !== 16) {
60
+ throw new Error(`AES-128 key must be 16 bytes, got ${keyBytes.length}`);
61
+ }
62
+ // Import key once and cache the promise
63
+ this.keyPromise = crypto.subtle.importKey("raw", keyBytes, { name: "AES-CBC" }, false, ["encrypt"]);
64
+ }
65
+ /**
66
+ * Encrypts a single 16-byte block using AES-128.
67
+ * Uses AES-CBC with zero IV, which is equivalent to AES-ECB for single blocks.
68
+ */
69
+ async encryptBlock(block) {
70
+ if (block.length !== 16) {
71
+ throw new Error(`AES block must be 16 bytes, got ${block.length}`);
72
+ }
73
+ const key = await this.keyPromise;
74
+ const iv = new Uint8Array(16); // Zero IV
75
+ // AES-CBC with zero IV for single block = AES-ECB
76
+ const result = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, key, block);
77
+ // Result includes padding; we only need the first 16 bytes
78
+ return new Uint8Array(result, 0, 16);
79
+ }
80
+ /**
81
+ * AES-CBC-MAC: Chain multiple blocks using CBC mode.
82
+ * Returns the final encrypted block (the MAC).
83
+ */
84
+ async cbcMac(blocks) {
85
+ let state = new Uint8Array(16); // Start with zero block
86
+ for (const block of blocks) {
87
+ // XOR state with block
88
+ const xored = new Uint8Array(16);
89
+ for (let i = 0; i < 16; i++) {
90
+ xored[i] = state[i] ^ block[i];
91
+ }
92
+ // Encrypt and copy to new array to satisfy TypeScript
93
+ const encrypted = await this.encryptBlock(xored);
94
+ state = new Uint8Array(encrypted);
95
+ }
96
+ return state;
97
+ }
98
+ }
99
+ exports.Aes128 = Aes128;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Bit manipulation for TNID encryption.
3
+ *
4
+ * These functions extract and expand the 100 Payload bits that are
5
+ * encrypted/decrypted, matching the Rust implementation exactly.
6
+ */
7
+ export declare const RIGHT_SECRET_DATA_SECTION_MASK = 1152921504606846975n;
8
+ export declare const MIDDLE_SECRET_DATA_SECTION_MASK = 75539416981840613867520n;
9
+ export declare const LEFT_SECRET_DATA_SECTION_MASK = 324518552449500907168526845870080n;
10
+ export declare const COMPLETE_SECRET_DATA_MASK: bigint;
11
+ export declare const SECRET_DATA_BIT_NUM = 100;
12
+ export declare const HEX_DIGIT_COUNT = 25;
13
+ /**
14
+ * Extracts Payload bits (excludes Name bits, UUID-specific bits, and TNID Variant bits).
15
+ *
16
+ * Compacts the Payload bits from the three sections into a single 100-bit value.
17
+ * The returned bigint will have its lowest 100 bits populated with data,
18
+ * and the highest bits set to zero.
19
+ */
20
+ export declare function extractSecretDataBits(id: bigint): bigint;
21
+ /**
22
+ * Expands compacted Payload bits back into their positions.
23
+ *
24
+ * This is the inverse of extractSecretDataBits.
25
+ * `bits` should have its lowest 100 bits populated with Payload data.
26
+ */
27
+ export declare function expandSecretDataBits(bits: bigint): bigint;
28
+ /**
29
+ * Convert 100-bit value to 25 hex digits (each 0-15).
30
+ * Most significant digit first.
31
+ */
32
+ export declare function toHexDigits(data: bigint): number[];
33
+ /**
34
+ * Convert 25 hex digits back to 100-bit value.
35
+ */
36
+ export declare function fromHexDigits(digits: number[]): bigint;
37
+ /**
38
+ * Get the TNID variant from a 128-bit ID value.
39
+ */
40
+ export declare function getVariant(id: bigint): "v0" | "v1" | "v2" | "v3";
41
+ /**
42
+ * Change the variant bits in a 128-bit ID.
43
+ */
44
+ export declare function setVariant(id: bigint, variant: "v0" | "v1"): bigint;
45
+ //# sourceMappingURL=bits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bits.d.ts","sourceRoot":"","sources":["../src/bits.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,eAAO,MAAM,8BAA8B,uBAA0C,CAAC;AAGtF,eAAO,MAAM,+BAA+B,2BAA0C,CAAC;AAGvF,eAAO,MAAM,6BAA6B,qCAA0C,CAAC;AAGrF,eAAO,MAAM,yBAAyB,QAGP,CAAC;AAGhC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAGvC,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAaxD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAezD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOlD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAMtD;AAOD;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAchE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAInE"}
package/script/bits.js ADDED
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * Bit manipulation for TNID encryption.
4
+ *
5
+ * These functions extract and expand the 100 Payload bits that are
6
+ * encrypted/decrypted, matching the Rust implementation exactly.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.HEX_DIGIT_COUNT = exports.SECRET_DATA_BIT_NUM = exports.COMPLETE_SECRET_DATA_MASK = exports.LEFT_SECRET_DATA_SECTION_MASK = exports.MIDDLE_SECRET_DATA_SECTION_MASK = exports.RIGHT_SECRET_DATA_SECTION_MASK = void 0;
10
+ exports.extractSecretDataBits = extractSecretDataBits;
11
+ exports.expandSecretDataBits = expandSecretDataBits;
12
+ exports.toHexDigits = toHexDigits;
13
+ exports.fromHexDigits = fromHexDigits;
14
+ exports.getVariant = getVariant;
15
+ exports.setVariant = setVariant;
16
+ // Mask for the right-most Payload bits section (bits 0-51, 52 bits)
17
+ exports.RIGHT_SECRET_DATA_SECTION_MASK = 0x00000000000000000fffffffffffffffn;
18
+ // Mask for the middle Payload bits section (bits 64-75, 12 bits)
19
+ exports.MIDDLE_SECRET_DATA_SECTION_MASK = 0x0000000000000fff0000000000000000n;
20
+ // Mask for the left-most Payload bits section (bits 80-107, 28 bits)
21
+ exports.LEFT_SECRET_DATA_SECTION_MASK = 0x00000fffffff00000000000000000000n;
22
+ // Complete mask for all Payload bits (100 bits)
23
+ exports.COMPLETE_SECRET_DATA_MASK = exports.RIGHT_SECRET_DATA_SECTION_MASK |
24
+ exports.MIDDLE_SECRET_DATA_SECTION_MASK |
25
+ exports.LEFT_SECRET_DATA_SECTION_MASK;
26
+ // Number of Payload bits
27
+ exports.SECRET_DATA_BIT_NUM = 100;
28
+ // Number of hex digits for FF1 (100 bits / 4 bits per hex digit)
29
+ exports.HEX_DIGIT_COUNT = 25;
30
+ /**
31
+ * Extracts Payload bits (excludes Name bits, UUID-specific bits, and TNID Variant bits).
32
+ *
33
+ * Compacts the Payload bits from the three sections into a single 100-bit value.
34
+ * The returned bigint will have its lowest 100 bits populated with data,
35
+ * and the highest bits set to zero.
36
+ */
37
+ function extractSecretDataBits(id) {
38
+ // Right section stays in place
39
+ let extracted = id & exports.RIGHT_SECRET_DATA_SECTION_MASK;
40
+ // Middle section: shift right by 4 to compact
41
+ const BETWEEN_MIDDLE_RIGHT = 4n;
42
+ extracted = extracted | ((id & exports.MIDDLE_SECRET_DATA_SECTION_MASK) >> BETWEEN_MIDDLE_RIGHT);
43
+ // Left section: shift right by 8 to compact
44
+ const BETWEEN_LEFT_MIDDLE = BETWEEN_MIDDLE_RIGHT + 4n;
45
+ extracted = extracted | ((id & exports.LEFT_SECRET_DATA_SECTION_MASK) >> BETWEEN_LEFT_MIDDLE);
46
+ return extracted;
47
+ }
48
+ /**
49
+ * Expands compacted Payload bits back into their positions.
50
+ *
51
+ * This is the inverse of extractSecretDataBits.
52
+ * `bits` should have its lowest 100 bits populated with Payload data.
53
+ */
54
+ function expandSecretDataBits(bits) {
55
+ // Right section stays in place
56
+ let expanded = bits & exports.RIGHT_SECRET_DATA_SECTION_MASK;
57
+ // Middle section shifts left
58
+ const BETWEEN_MIDDLE_RIGHT = 4n;
59
+ const middleMask = exports.MIDDLE_SECRET_DATA_SECTION_MASK >> BETWEEN_MIDDLE_RIGHT;
60
+ expanded = expanded | ((bits & middleMask) << BETWEEN_MIDDLE_RIGHT);
61
+ // Left section shifts left
62
+ const BETWEEN_LEFT_MIDDLE = BETWEEN_MIDDLE_RIGHT + 4n;
63
+ const leftMask = exports.LEFT_SECRET_DATA_SECTION_MASK >> BETWEEN_LEFT_MIDDLE;
64
+ expanded = expanded | ((bits & leftMask) << BETWEEN_LEFT_MIDDLE);
65
+ return expanded;
66
+ }
67
+ /**
68
+ * Convert 100-bit value to 25 hex digits (each 0-15).
69
+ * Most significant digit first.
70
+ */
71
+ function toHexDigits(data) {
72
+ const hexDigits = new Array(exports.HEX_DIGIT_COUNT);
73
+ for (let i = 0; i < exports.HEX_DIGIT_COUNT; i++) {
74
+ const shift = BigInt((exports.HEX_DIGIT_COUNT - 1 - i) * 4);
75
+ hexDigits[i] = Number((data >> shift) & 0xfn);
76
+ }
77
+ return hexDigits;
78
+ }
79
+ /**
80
+ * Convert 25 hex digits back to 100-bit value.
81
+ */
82
+ function fromHexDigits(digits) {
83
+ let result = 0n;
84
+ for (const digit of digits) {
85
+ result = (result << 4n) | BigInt(digit);
86
+ }
87
+ return result;
88
+ }
89
+ // Variant bit positions (bits 60-61 in the 128-bit TNID)
90
+ const VARIANT_MASK = 3n << 60n;
91
+ const V0_VARIANT = 0n << 60n;
92
+ const V1_VARIANT = 1n << 60n;
93
+ /**
94
+ * Get the TNID variant from a 128-bit ID value.
95
+ */
96
+ function getVariant(id) {
97
+ const variantBits = (id >> 60n) & 3n;
98
+ switch (variantBits) {
99
+ case 0n:
100
+ return "v0";
101
+ case 1n:
102
+ return "v1";
103
+ case 2n:
104
+ return "v2";
105
+ case 3n:
106
+ return "v3";
107
+ default:
108
+ throw new Error("Unreachable");
109
+ }
110
+ }
111
+ /**
112
+ * Change the variant bits in a 128-bit ID.
113
+ */
114
+ function setVariant(id, variant) {
115
+ const cleared = id & ~VARIANT_MASK;
116
+ const variantBits = variant === "v0" ? V0_VARIANT : V1_VARIANT;
117
+ return cleared | variantBits;
118
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TNID encryption using FF1 Format-Preserving Encryption.
3
+ *
4
+ * Provides functions to convert V0 (time-ordered) TNIDs to V1 (random-looking)
5
+ * TNIDs and vice versa, hiding timestamp information while remaining reversible.
6
+ */
7
+ /**
8
+ * Error when creating an EncryptionKey.
9
+ */
10
+ export declare class EncryptionKeyError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ /**
14
+ * Error when encrypting or decrypting a TNID.
15
+ */
16
+ export declare class EncryptionError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ /**
20
+ * A 128-bit (16 byte) encryption key for TNID encryption.
21
+ */
22
+ export declare class EncryptionKey {
23
+ private readonly bytes;
24
+ private constructor();
25
+ /**
26
+ * Creates a new encryption key from raw bytes.
27
+ */
28
+ static fromBytes(bytes: Uint8Array): EncryptionKey;
29
+ /**
30
+ * Creates an encryption key from a 32-character hex string.
31
+ */
32
+ static fromHex(hex: string): EncryptionKey;
33
+ /**
34
+ * Returns the key as a byte array.
35
+ */
36
+ asBytes(): Uint8Array;
37
+ }
38
+ /**
39
+ * Encrypts a V0 TNID to V1, hiding timestamp information.
40
+ *
41
+ * @param tnid The V0 TNID string to encrypt
42
+ * @param key The encryption key
43
+ * @returns The encrypted V1 TNID string
44
+ * @throws EncryptionError if the TNID is not V0 or is invalid
45
+ */
46
+ export declare function encryptV0ToV1(tnid: string, key: EncryptionKey): Promise<string>;
47
+ /**
48
+ * Decrypts a V1 TNID back to V0, recovering timestamp information.
49
+ *
50
+ * @param tnid The V1 TNID string to decrypt
51
+ * @param key The encryption key (must match the one used for encryption)
52
+ * @returns The decrypted V0 TNID string
53
+ * @throws EncryptionError if the TNID is not V1 or is invalid
54
+ */
55
+ export declare function decryptV1ToV0(tnid: string, key: EncryptionKey): Promise<string>;
56
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../src/encryption.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IAEnC,OAAO;IAIP;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa;IASlD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAsB1C;;OAEG;IACH,OAAO,IAAI,UAAU;CAGtB;AA6DD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCrF;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCrF"}
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ /**
3
+ * TNID encryption using FF1 Format-Preserving Encryption.
4
+ *
5
+ * Provides functions to convert V0 (time-ordered) TNIDs to V1 (random-looking)
6
+ * TNIDs and vice versa, hiding timestamp information while remaining reversible.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EncryptionKey = exports.EncryptionError = exports.EncryptionKeyError = void 0;
10
+ exports.encryptV0ToV1 = encryptV0ToV1;
11
+ exports.decryptV1ToV0 = decryptV1ToV0;
12
+ const core_1 = require("@tnid/core");
13
+ const uuid_1 = require("@tnid/core/uuid");
14
+ const ff1_js_1 = require("./ff1.js");
15
+ const bits_js_1 = require("./bits.js");
16
+ /**
17
+ * Error when creating an EncryptionKey.
18
+ */
19
+ class EncryptionKeyError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "EncryptionKeyError";
23
+ }
24
+ }
25
+ exports.EncryptionKeyError = EncryptionKeyError;
26
+ /**
27
+ * Error when encrypting or decrypting a TNID.
28
+ */
29
+ class EncryptionError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "EncryptionError";
33
+ }
34
+ }
35
+ exports.EncryptionError = EncryptionError;
36
+ /**
37
+ * A 128-bit (16 byte) encryption key for TNID encryption.
38
+ */
39
+ class EncryptionKey {
40
+ constructor(bytes) {
41
+ Object.defineProperty(this, "bytes", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: void 0
46
+ });
47
+ this.bytes = bytes;
48
+ }
49
+ /**
50
+ * Creates a new encryption key from raw bytes.
51
+ */
52
+ static fromBytes(bytes) {
53
+ if (bytes.length !== 16) {
54
+ throw new EncryptionKeyError(`Encryption key must be 16 bytes, got ${bytes.length}`);
55
+ }
56
+ return new EncryptionKey(new Uint8Array(bytes));
57
+ }
58
+ /**
59
+ * Creates an encryption key from a 32-character hex string.
60
+ */
61
+ static fromHex(hex) {
62
+ if (hex.length !== 32) {
63
+ throw new EncryptionKeyError(`Encryption key hex string must be 32 characters, got ${hex.length}`);
64
+ }
65
+ const bytes = new Uint8Array(16);
66
+ for (let i = 0; i < 16; i++) {
67
+ const hexByte = hex.slice(i * 2, i * 2 + 2);
68
+ const value = parseInt(hexByte, 16);
69
+ if (isNaN(value)) {
70
+ throw new EncryptionKeyError(`Invalid hex character at position ${i * 2}`);
71
+ }
72
+ bytes[i] = value;
73
+ }
74
+ return new EncryptionKey(bytes);
75
+ }
76
+ /**
77
+ * Returns the key as a byte array.
78
+ */
79
+ asBytes() {
80
+ return new Uint8Array(this.bytes);
81
+ }
82
+ }
83
+ exports.EncryptionKey = EncryptionKey;
84
+ /**
85
+ * Convert TNID string to 128-bit value.
86
+ */
87
+ function tnidToValue(tnid) {
88
+ // Use @tnid/core to convert to UUID, then parse UUID to value
89
+ const parsed = core_1.DynamicTnid.parse(tnid);
90
+ const uuid = core_1.DynamicTnid.toUuidString(parsed);
91
+ return (0, uuid_1.parseUuidStringToValue)(uuid);
92
+ }
93
+ /**
94
+ * Convert 128-bit value back to TNID string.
95
+ */
96
+ function valueToTnid(value) {
97
+ return (0, uuid_1.valueToTnidString)(value);
98
+ }
99
+ /**
100
+ * Encrypts the 100-bit Payload using FF1.
101
+ */
102
+ async function encryptPayload(payload, key) {
103
+ // Mask to 100 bits
104
+ const mask = (1n << 100n) - 1n;
105
+ const data = payload & mask;
106
+ // Convert to hex digits
107
+ const hexDigits = (0, bits_js_1.toHexDigits)(data);
108
+ // Create FF1 cipher with radix 16
109
+ const ff1 = new ff1_js_1.FF1(key.asBytes(), 16);
110
+ // Encrypt with empty tweak
111
+ const encrypted = await ff1.encrypt(new Uint8Array(0), hexDigits);
112
+ // Convert back to bigint
113
+ return (0, bits_js_1.fromHexDigits)(encrypted);
114
+ }
115
+ /**
116
+ * Decrypts the 100-bit Payload using FF1.
117
+ */
118
+ async function decryptPayload(payload, key) {
119
+ // Mask to 100 bits
120
+ const mask = (1n << 100n) - 1n;
121
+ const data = payload & mask;
122
+ // Convert to hex digits
123
+ const hexDigits = (0, bits_js_1.toHexDigits)(data);
124
+ // Create FF1 cipher with radix 16
125
+ const ff1 = new ff1_js_1.FF1(key.asBytes(), 16);
126
+ // Decrypt with empty tweak
127
+ const decrypted = await ff1.decrypt(new Uint8Array(0), hexDigits);
128
+ // Convert back to bigint
129
+ return (0, bits_js_1.fromHexDigits)(decrypted);
130
+ }
131
+ /**
132
+ * Encrypts a V0 TNID to V1, hiding timestamp information.
133
+ *
134
+ * @param tnid The V0 TNID string to encrypt
135
+ * @param key The encryption key
136
+ * @returns The encrypted V1 TNID string
137
+ * @throws EncryptionError if the TNID is not V0 or is invalid
138
+ */
139
+ async function encryptV0ToV1(tnid, key) {
140
+ let value;
141
+ try {
142
+ value = tnidToValue(tnid);
143
+ }
144
+ catch (e) {
145
+ throw new EncryptionError(`Invalid TNID: ${e.message}`);
146
+ }
147
+ const variant = (0, uuid_1.extractVariantFromValue)(value);
148
+ if (variant === "v1") {
149
+ // Already V1, return unchanged
150
+ return tnid;
151
+ }
152
+ if (variant !== "v0") {
153
+ throw new EncryptionError(`TNID variant ${variant} is not supported for encryption`);
154
+ }
155
+ // Extract the 100 Payload bits
156
+ const secretData = (0, bits_js_1.extractSecretDataBits)(value);
157
+ // Encrypt the Payload
158
+ const encryptedData = await encryptPayload(secretData, key);
159
+ // Expand back to proper bit positions
160
+ const expanded = (0, bits_js_1.expandSecretDataBits)(encryptedData);
161
+ // Preserve Name bits and UUID-specific bits, replace Payload bits
162
+ let result = (value & ~bits_js_1.COMPLETE_SECRET_DATA_MASK) | expanded;
163
+ // Change variant from V0 to V1
164
+ result = (0, bits_js_1.setVariant)(result, "v1");
165
+ return valueToTnid(result);
166
+ }
167
+ /**
168
+ * Decrypts a V1 TNID back to V0, recovering timestamp information.
169
+ *
170
+ * @param tnid The V1 TNID string to decrypt
171
+ * @param key The encryption key (must match the one used for encryption)
172
+ * @returns The decrypted V0 TNID string
173
+ * @throws EncryptionError if the TNID is not V1 or is invalid
174
+ */
175
+ async function decryptV1ToV0(tnid, key) {
176
+ let value;
177
+ try {
178
+ value = tnidToValue(tnid);
179
+ }
180
+ catch (e) {
181
+ throw new EncryptionError(`Invalid TNID: ${e.message}`);
182
+ }
183
+ const variant = (0, uuid_1.extractVariantFromValue)(value);
184
+ if (variant === "v0") {
185
+ // Already V0, return unchanged
186
+ return tnid;
187
+ }
188
+ if (variant !== "v1") {
189
+ throw new EncryptionError(`TNID variant ${variant} is not supported for decryption`);
190
+ }
191
+ // Extract the 100 Payload bits
192
+ const encryptedData = (0, bits_js_1.extractSecretDataBits)(value);
193
+ // Decrypt the Payload
194
+ const decryptedData = await decryptPayload(encryptedData, key);
195
+ // Expand back to proper bit positions
196
+ const expanded = (0, bits_js_1.expandSecretDataBits)(decryptedData);
197
+ // Preserve Name bits and UUID-specific bits, replace Payload bits
198
+ let result = (value & ~bits_js_1.COMPLETE_SECRET_DATA_MASK) | expanded;
199
+ // Change variant from V1 to V0
200
+ result = (0, bits_js_1.setVariant)(result, "v0");
201
+ return valueToTnid(result);
202
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * FF1 Format-Preserving Encryption (NIST SP 800-38G).
3
+ *
4
+ * This implementation uses AES-128 as the underlying cipher.
5
+ * FF1 is a Feistel cipher that encrypts strings of numerals
6
+ * while preserving their format (length and radix).
7
+ */
8
+ /**
9
+ * FF1 Format-Preserving Encryption cipher.
10
+ *
11
+ * Encrypts/decrypts a string of numerals (digits in base `radix`)
12
+ * while preserving the format.
13
+ */
14
+ export declare class FF1 {
15
+ private aes;
16
+ private radix;
17
+ /**
18
+ * Create an FF1 cipher with the given key and radix.
19
+ *
20
+ * @param key 16-byte AES key
21
+ * @param radix Base of the numeral system (2-65536)
22
+ */
23
+ constructor(key: Uint8Array, radix: number);
24
+ /**
25
+ * FF1 encryption.
26
+ *
27
+ * @param tweak Additional data (can be empty)
28
+ * @param plaintext Array of numerals (each in range [0, radix))
29
+ * @returns Encrypted numeral array of same length
30
+ */
31
+ encrypt(tweak: Uint8Array, plaintext: number[]): Promise<number[]>;
32
+ /**
33
+ * FF1 decryption.
34
+ *
35
+ * @param tweak Additional data (must match encryption)
36
+ * @param ciphertext Array of numerals
37
+ * @returns Decrypted numeral array
38
+ */
39
+ decrypt(tweak: Uint8Array, ciphertext: number[]): Promise<number[]>;
40
+ /**
41
+ * Core FF1 Feistel cipher (10 rounds).
42
+ */
43
+ private cipher;
44
+ }
45
+ //# sourceMappingURL=ff1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ff1.d.ts","sourceRoot":"","sources":["../src/ff1.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+EH;;;;;GAKG;AACH,qBAAa,GAAG;IACd,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,KAAK,CAAS;IAEtB;;;;;OAKG;gBACS,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM;IAQ1C;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAIlE;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAInE;;OAEG;YACW,MAAM;CAsIrB"}