@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Edlinger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @tnid/encryption
2
+
3
+ Format-preserving encryption for TNIDs - hide timestamp information by encrypting V0 TNIDs to V1.
4
+
5
+ ## Why Encrypt TNIDs?
6
+
7
+ V0 TNIDs contain a timestamp (like UUIDv7), which reveals when the ID was created. This can leak information you may not want to expose publicly, such as:
8
+
9
+ - When a user account was created
10
+ - The order in which records were created
11
+ - Approximate creation rates
12
+
13
+ By encrypting V0 to V1, you get a valid high-entropy V1 TNID that hides this information while remaining decryptable on the backend.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ # npm
19
+ npm install @tnid/encryption @tnid/core
20
+
21
+ # pnpm
22
+ pnpm add @tnid/encryption @tnid/core
23
+
24
+ # bun
25
+ bun add @tnid/encryption @tnid/core
26
+
27
+ # deno
28
+ deno add npm:@tnid/encryption npm:@tnid/core
29
+ ```
30
+
31
+ ## Platform Support
32
+
33
+ Requires `globalThis.crypto` (Web Crypto API):
34
+
35
+ - Node.js 20+
36
+ - Deno 1.0+
37
+ - Bun 1.0+
38
+ - Modern browsers (ES2020+)
39
+
40
+ ## Quick Start
41
+
42
+ ```typescript
43
+ import { Tnid, TnidType } from "@tnid/core";
44
+ import { EncryptionKey, encryptV0ToV1, decryptV1ToV0 } from "@tnid/encryption";
45
+
46
+ const UserId = Tnid("user");
47
+ type UserId = TnidType<typeof UserId>;
48
+
49
+ // Create an encryption key (16 bytes / 128 bits)
50
+ const key = EncryptionKey.fromHex("0102030405060708090a0b0c0d0e0f10");
51
+
52
+ // Create a time-ordered V0 ID
53
+ const v0 = UserId.new_v0();
54
+
55
+ // Encrypt to V1 before sending to client
56
+ const v1 = await encryptV0ToV1(v0, key);
57
+
58
+ // Decrypt on the backend to recover the original
59
+ const decrypted = await decryptV1ToV0(v1, key);
60
+ // decrypted === v0
61
+ ```
62
+
63
+ ## How It Works
64
+
65
+ The encryption uses Format-Preserving Encryption (FPE) with AES-128 in FF1 mode (NIST SP 800-38G). This encrypts the 100 Payload bits while preserving:
66
+
67
+ - The TNID name (unchanged)
68
+ - The UUID version/variant bits (valid UUIDv8)
69
+ - The overall 128-bit structure
70
+
71
+ The TNID variant changes from V0 to V1, making the encrypted ID indistinguishable from a randomly generated V1 TNID.
72
+
73
+ ## API Reference
74
+
75
+ ### `EncryptionKey`
76
+
77
+ A 128-bit (16 byte) encryption key.
78
+
79
+ ```typescript
80
+ // From 32-character hex string
81
+ const key = EncryptionKey.fromHex("0102030405060708090a0b0c0d0e0f10");
82
+
83
+ // From raw bytes
84
+ const key = EncryptionKey.fromBytes(
85
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
86
+ );
87
+
88
+ // Get key bytes (returns a copy)
89
+ const bytes: Uint8Array = key.asBytes();
90
+ ```
91
+
92
+ ### `encryptV0ToV1(tnid, key)`
93
+
94
+ Encrypts a V0 TNID to V1, hiding timestamp information.
95
+
96
+ ```typescript
97
+ const v1 = await encryptV0ToV1("user.Br2flcNDfF6LYICnT", key);
98
+ ```
99
+
100
+ - **Input**: V0 TNID string
101
+ - **Output**: V1 TNID string (same name, encrypted payload)
102
+ - **Idempotent**: If input is already V1, returns it unchanged
103
+ - **Throws**: `EncryptionError` if input is invalid or unsupported variant
104
+
105
+ ### `decryptV1ToV0(tnid, key)`
106
+
107
+ Decrypts a V1 TNID back to V0, recovering timestamp information.
108
+
109
+ ```typescript
110
+ const v0 = await decryptV1ToV0("user.X3Wxwp0wOy4OZp_rP", key);
111
+ ```
112
+
113
+ - **Input**: V1 TNID string (encrypted)
114
+ - **Output**: V0 TNID string (original with timestamp)
115
+ - **Idempotent**: If input is already V0, returns it unchanged
116
+ - **Throws**: `EncryptionError` if input is invalid or unsupported variant
117
+
118
+ ### Error Classes
119
+
120
+ ```typescript
121
+ import { EncryptionKeyError, EncryptionError } from "@tnid/encryption";
122
+
123
+ // EncryptionKeyError - invalid key format
124
+ try {
125
+ EncryptionKey.fromHex("invalid");
126
+ } catch (e) {
127
+ if (e instanceof EncryptionKeyError) {
128
+ console.log("Invalid key:", e.message);
129
+ }
130
+ }
131
+
132
+ // EncryptionError - encryption/decryption failed
133
+ try {
134
+ await encryptV0ToV1("invalid-tnid", key);
135
+ } catch (e) {
136
+ if (e instanceof EncryptionError) {
137
+ console.log("Encryption failed:", e.message);
138
+ }
139
+ }
140
+ ```
141
+
142
+ ## Note
143
+
144
+ The encryption functionality is not part of the TNID specification. Encrypted TNIDs are standard V1 TNIDs and remain fully compatible with any TNID implementation.
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,2 @@
1
+ export declare const dntGlobalThis: Omit<typeof globalThis, never>;
2
+ //# sourceMappingURL=_dnt.shims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.shims.d.ts","sourceRoot":"","sources":["../src/_dnt.shims.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,gCAA2C,CAAC"}
@@ -0,0 +1,57 @@
1
+ const dntGlobals = {};
2
+ export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
3
+ function createMergeProxy(baseObj, extObj) {
4
+ return new Proxy(baseObj, {
5
+ get(_target, prop, _receiver) {
6
+ if (prop in extObj) {
7
+ return extObj[prop];
8
+ }
9
+ else {
10
+ return baseObj[prop];
11
+ }
12
+ },
13
+ set(_target, prop, value) {
14
+ if (prop in extObj) {
15
+ delete extObj[prop];
16
+ }
17
+ baseObj[prop] = value;
18
+ return true;
19
+ },
20
+ deleteProperty(_target, prop) {
21
+ let success = false;
22
+ if (prop in extObj) {
23
+ delete extObj[prop];
24
+ success = true;
25
+ }
26
+ if (prop in baseObj) {
27
+ delete baseObj[prop];
28
+ success = true;
29
+ }
30
+ return success;
31
+ },
32
+ ownKeys(_target) {
33
+ const baseKeys = Reflect.ownKeys(baseObj);
34
+ const extKeys = Reflect.ownKeys(extObj);
35
+ const extKeysSet = new Set(extKeys);
36
+ return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
37
+ },
38
+ defineProperty(_target, prop, desc) {
39
+ if (prop in extObj) {
40
+ delete extObj[prop];
41
+ }
42
+ Reflect.defineProperty(baseObj, prop, desc);
43
+ return true;
44
+ },
45
+ getOwnPropertyDescriptor(_target, prop) {
46
+ if (prop in extObj) {
47
+ return Reflect.getOwnPropertyDescriptor(extObj, prop);
48
+ }
49
+ else {
50
+ return Reflect.getOwnPropertyDescriptor(baseObj, prop);
51
+ }
52
+ },
53
+ has(_target, prop) {
54
+ return prop in extObj || prop in baseObj;
55
+ },
56
+ });
57
+ }
package/esm/aes.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * AES-128 block cipher wrapper using Web Crypto API.
3
+ *
4
+ * FF1 requires raw AES block cipher (AES-ECB), which Web Crypto doesn't expose.
5
+ * We simulate AES-ECB using AES-CBC with a zero IV for single-block operations.
6
+ */
7
+ /**
8
+ * AES-128 block cipher for FF1.
9
+ * Caches the imported CryptoKey for efficiency.
10
+ */
11
+ export declare class Aes128 {
12
+ private keyPromise;
13
+ constructor(keyBytes: Uint8Array);
14
+ /**
15
+ * Encrypts a single 16-byte block using AES-128.
16
+ * Uses AES-CBC with zero IV, which is equivalent to AES-ECB for single blocks.
17
+ */
18
+ encryptBlock(block: Uint8Array): Promise<Uint8Array>;
19
+ /**
20
+ * AES-CBC-MAC: Chain multiple blocks using CBC mode.
21
+ * Returns the final encrypted block (the MAC).
22
+ */
23
+ cbcMac(blocks: Uint8Array[]): Promise<Uint8Array>;
24
+ }
25
+ //# sourceMappingURL=aes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aes.d.ts","sourceRoot":"","sources":["../src/aes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH;;;GAGG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,UAAU,CAAqB;gBAE3B,QAAQ,EAAE,UAAU;IAchC;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAmB1D;;;OAGG;IACG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;CAgBxD"}
package/esm/aes.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * AES-128 block cipher wrapper using Web Crypto API.
3
+ *
4
+ * FF1 requires raw AES block cipher (AES-ECB), which Web Crypto doesn't expose.
5
+ * We simulate AES-ECB using AES-CBC with a zero IV for single-block operations.
6
+ */
7
+ // Web Crypto API accessor (same pattern as @tnid/core)
8
+ // deno-lint-ignore no-explicit-any
9
+ import * as dntShim from "./_dnt.shims.js";
10
+ const crypto = dntShim.dntGlobalThis.crypto;
11
+ /**
12
+ * AES-128 block cipher for FF1.
13
+ * Caches the imported CryptoKey for efficiency.
14
+ */
15
+ export class Aes128 {
16
+ constructor(keyBytes) {
17
+ Object.defineProperty(this, "keyPromise", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: void 0
22
+ });
23
+ if (keyBytes.length !== 16) {
24
+ throw new Error(`AES-128 key must be 16 bytes, got ${keyBytes.length}`);
25
+ }
26
+ // Import key once and cache the promise
27
+ this.keyPromise = crypto.subtle.importKey("raw", keyBytes, { name: "AES-CBC" }, false, ["encrypt"]);
28
+ }
29
+ /**
30
+ * Encrypts a single 16-byte block using AES-128.
31
+ * Uses AES-CBC with zero IV, which is equivalent to AES-ECB for single blocks.
32
+ */
33
+ async encryptBlock(block) {
34
+ if (block.length !== 16) {
35
+ throw new Error(`AES block must be 16 bytes, got ${block.length}`);
36
+ }
37
+ const key = await this.keyPromise;
38
+ const iv = new Uint8Array(16); // Zero IV
39
+ // AES-CBC with zero IV for single block = AES-ECB
40
+ const result = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, key, block);
41
+ // Result includes padding; we only need the first 16 bytes
42
+ return new Uint8Array(result, 0, 16);
43
+ }
44
+ /**
45
+ * AES-CBC-MAC: Chain multiple blocks using CBC mode.
46
+ * Returns the final encrypted block (the MAC).
47
+ */
48
+ async cbcMac(blocks) {
49
+ let state = new Uint8Array(16); // Start with zero block
50
+ for (const block of blocks) {
51
+ // XOR state with block
52
+ const xored = new Uint8Array(16);
53
+ for (let i = 0; i < 16; i++) {
54
+ xored[i] = state[i] ^ block[i];
55
+ }
56
+ // Encrypt and copy to new array to satisfy TypeScript
57
+ const encrypted = await this.encryptBlock(xored);
58
+ state = new Uint8Array(encrypted);
59
+ }
60
+ return state;
61
+ }
62
+ }
package/esm/bits.d.ts ADDED
@@ -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/esm/bits.js ADDED
@@ -0,0 +1,109 @@
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
+ // Mask for the right-most Payload bits section (bits 0-51, 52 bits)
8
+ export const RIGHT_SECRET_DATA_SECTION_MASK = 0x00000000000000000fffffffffffffffn;
9
+ // Mask for the middle Payload bits section (bits 64-75, 12 bits)
10
+ export const MIDDLE_SECRET_DATA_SECTION_MASK = 0x0000000000000fff0000000000000000n;
11
+ // Mask for the left-most Payload bits section (bits 80-107, 28 bits)
12
+ export const LEFT_SECRET_DATA_SECTION_MASK = 0x00000fffffff00000000000000000000n;
13
+ // Complete mask for all Payload bits (100 bits)
14
+ export const COMPLETE_SECRET_DATA_MASK = RIGHT_SECRET_DATA_SECTION_MASK |
15
+ MIDDLE_SECRET_DATA_SECTION_MASK |
16
+ LEFT_SECRET_DATA_SECTION_MASK;
17
+ // Number of Payload bits
18
+ export const SECRET_DATA_BIT_NUM = 100;
19
+ // Number of hex digits for FF1 (100 bits / 4 bits per hex digit)
20
+ export const HEX_DIGIT_COUNT = 25;
21
+ /**
22
+ * Extracts Payload bits (excludes Name bits, UUID-specific bits, and TNID Variant bits).
23
+ *
24
+ * Compacts the Payload bits from the three sections into a single 100-bit value.
25
+ * The returned bigint will have its lowest 100 bits populated with data,
26
+ * and the highest bits set to zero.
27
+ */
28
+ export function extractSecretDataBits(id) {
29
+ // Right section stays in place
30
+ let extracted = id & RIGHT_SECRET_DATA_SECTION_MASK;
31
+ // Middle section: shift right by 4 to compact
32
+ const BETWEEN_MIDDLE_RIGHT = 4n;
33
+ extracted = extracted | ((id & MIDDLE_SECRET_DATA_SECTION_MASK) >> BETWEEN_MIDDLE_RIGHT);
34
+ // Left section: shift right by 8 to compact
35
+ const BETWEEN_LEFT_MIDDLE = BETWEEN_MIDDLE_RIGHT + 4n;
36
+ extracted = extracted | ((id & LEFT_SECRET_DATA_SECTION_MASK) >> BETWEEN_LEFT_MIDDLE);
37
+ return extracted;
38
+ }
39
+ /**
40
+ * Expands compacted Payload bits back into their positions.
41
+ *
42
+ * This is the inverse of extractSecretDataBits.
43
+ * `bits` should have its lowest 100 bits populated with Payload data.
44
+ */
45
+ export function expandSecretDataBits(bits) {
46
+ // Right section stays in place
47
+ let expanded = bits & RIGHT_SECRET_DATA_SECTION_MASK;
48
+ // Middle section shifts left
49
+ const BETWEEN_MIDDLE_RIGHT = 4n;
50
+ const middleMask = MIDDLE_SECRET_DATA_SECTION_MASK >> BETWEEN_MIDDLE_RIGHT;
51
+ expanded = expanded | ((bits & middleMask) << BETWEEN_MIDDLE_RIGHT);
52
+ // Left section shifts left
53
+ const BETWEEN_LEFT_MIDDLE = BETWEEN_MIDDLE_RIGHT + 4n;
54
+ const leftMask = LEFT_SECRET_DATA_SECTION_MASK >> BETWEEN_LEFT_MIDDLE;
55
+ expanded = expanded | ((bits & leftMask) << BETWEEN_LEFT_MIDDLE);
56
+ return expanded;
57
+ }
58
+ /**
59
+ * Convert 100-bit value to 25 hex digits (each 0-15).
60
+ * Most significant digit first.
61
+ */
62
+ export function toHexDigits(data) {
63
+ const hexDigits = new Array(HEX_DIGIT_COUNT);
64
+ for (let i = 0; i < HEX_DIGIT_COUNT; i++) {
65
+ const shift = BigInt((HEX_DIGIT_COUNT - 1 - i) * 4);
66
+ hexDigits[i] = Number((data >> shift) & 0xfn);
67
+ }
68
+ return hexDigits;
69
+ }
70
+ /**
71
+ * Convert 25 hex digits back to 100-bit value.
72
+ */
73
+ export function fromHexDigits(digits) {
74
+ let result = 0n;
75
+ for (const digit of digits) {
76
+ result = (result << 4n) | BigInt(digit);
77
+ }
78
+ return result;
79
+ }
80
+ // Variant bit positions (bits 60-61 in the 128-bit TNID)
81
+ const VARIANT_MASK = 3n << 60n;
82
+ const V0_VARIANT = 0n << 60n;
83
+ const V1_VARIANT = 1n << 60n;
84
+ /**
85
+ * Get the TNID variant from a 128-bit ID value.
86
+ */
87
+ export function getVariant(id) {
88
+ const variantBits = (id >> 60n) & 3n;
89
+ switch (variantBits) {
90
+ case 0n:
91
+ return "v0";
92
+ case 1n:
93
+ return "v1";
94
+ case 2n:
95
+ return "v2";
96
+ case 3n:
97
+ return "v3";
98
+ default:
99
+ throw new Error("Unreachable");
100
+ }
101
+ }
102
+ /**
103
+ * Change the variant bits in a 128-bit ID.
104
+ */
105
+ export function setVariant(id, variant) {
106
+ const cleared = id & ~VARIANT_MASK;
107
+ const variantBits = variant === "v0" ? V0_VARIANT : V1_VARIANT;
108
+ return cleared | variantBits;
109
+ }
@@ -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"}