@aidc-toolkit/utility 0.0.1

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.
@@ -0,0 +1,654 @@
1
+ import { IterationHelper, type IterationSource } from "./iteration.js";
2
+ import i18next, { utilityNS } from "./locale/i18n.js";
3
+
4
+ /**
5
+ * Transformation callback, used to convert transformed value to its final value.
6
+ *
7
+ * @template T
8
+ * Type returned by callback.
9
+ *
10
+ * @param transformedValue
11
+ * Transformed value.
12
+ *
13
+ * @param index
14
+ * Index in sequence transformation (0 for single transformation).
15
+ *
16
+ * @returns
17
+ * Final value.
18
+ */
19
+ export type TransformationCallback<T> = (transformedValue: bigint, index: number) => T;
20
+
21
+ /**
22
+ * Transformer that transforms values in a numeric domain to values in a range equal to the domain or to another range
23
+ * defined by a callback function. In other words, the domain determines valid input values and, without a callback, the
24
+ * range of valid output values.
25
+ *
26
+ * The concept is similar to {@link https://en.wikipedia.org/wiki/Format-preserving_encryption | format-preserving
27
+ * encryption}, where input values within a specified domain (e.g., {@link
28
+ * https://en.wikipedia.org/wiki/Payment_card_number | payment card numbers} ranging from 8-19 digits) are transformed
29
+ * into values in the same domain, typically for storage in a database where the data type and length are already fixed
30
+ * and exfiltration of the data can have significant repercussions.
31
+ *
32
+ * Two subclasses are supported directly by this class: {@link IdentityTransformer} (which operates based on a domain
33
+ * only) and {@link EncryptionTransformer} (which operates based on a domain and a tweak). If an application is expected
34
+ * to make repeated use of a transformer with the same domain and (optional) tweak and can't manage the transformer
35
+ * object, an in-memory cache is available via the {@link get} method. Properties in {@link IdentityTransformer} and
36
+ * {@link EncryptionTransformer} are read-only once constructed, so there is no issue with their shared use.
37
+ */
38
+ export abstract class Transformer {
39
+ /**
40
+ * Transformers cache, mapping a domain to another map, which maps an optional tweak to a transformer.
41
+ */
42
+ private static readonly TRANSFORMER_MAPS_MAP = new Map<bigint, Map<bigint | undefined, Transformer>>();
43
+
44
+ /**
45
+ * Domain.
46
+ */
47
+ private readonly _domain: bigint;
48
+
49
+ /**
50
+ * Constructor.
51
+ *
52
+ * @param domain
53
+ * Domain.
54
+ */
55
+ constructor(domain: bigint) {
56
+ if (domain <= 0n) {
57
+ throw new RangeError(i18next.t("Transformer.domainMustBeGreaterThanZero", {
58
+ ns: utilityNS,
59
+ domain
60
+ }));
61
+ }
62
+
63
+ this._domain = BigInt(domain);
64
+ }
65
+
66
+ /**
67
+ * Get a transformer, constructing it if necessary. The type returned is {@link IdentityTransformer} if tweak is
68
+ * undefined, {@link EncryptionTransformer} if tweak is defined. Note that although an {@link EncryptionTransformer}
69
+ * with a zero tweak operates as an {@link IdentityTransformer}, {@link EncryptionTransformer} is still the type
70
+ * returned if a zero tweak is explicitly specified.
71
+ *
72
+ * @param domain
73
+ * Domain.
74
+ *
75
+ * @param tweak
76
+ * Tweak.
77
+ *
78
+ * @returns
79
+ * {@link IdentityTransformer} if tweak is undefined, {@link EncryptionTransformer} if tweak is defined.
80
+ */
81
+ static get(domain: number | bigint, tweak?: number | bigint): Transformer {
82
+ const domainN = BigInt(domain);
83
+
84
+ let transformersMap = Transformer.TRANSFORMER_MAPS_MAP.get(domainN);
85
+
86
+ if (transformersMap === undefined) {
87
+ transformersMap = new Map();
88
+ Transformer.TRANSFORMER_MAPS_MAP.set(domainN, transformersMap);
89
+ }
90
+
91
+ const tweakN = tweak === undefined ? undefined : BigInt(tweak);
92
+
93
+ let transformer = transformersMap.get(tweakN);
94
+
95
+ if (transformer === undefined) {
96
+ transformer = tweakN === undefined ? new IdentityTransformer(domainN) : new EncryptionTransformer(domainN, tweakN);
97
+ transformersMap.set(tweakN, transformer);
98
+ }
99
+
100
+ return transformer;
101
+ }
102
+
103
+ /**
104
+ * Get the domain.
105
+ */
106
+ get domain(): bigint {
107
+ return this._domain;
108
+ }
109
+
110
+ /**
111
+ * Validate that a start value and count are within the domain.
112
+ *
113
+ * @param startValue
114
+ * Start value of range to validate.
115
+ *
116
+ * @param count
117
+ * Number of entries in the range to validate or 1 if undefined.
118
+ *
119
+ * @throws RangeError
120
+ */
121
+ private validate(startValue: bigint, count?: number): void {
122
+ if (startValue < 0n) {
123
+ throw new RangeError(i18next.t(count === undefined ? "Transformer.valueMustBeGreaterThanOrEqualToZero" : "Transformer.startValueMustBeGreaterThanOrEqualToZero", {
124
+ ns: utilityNS,
125
+ startValue
126
+ }));
127
+ }
128
+
129
+ const endValue = count === undefined ? startValue : startValue + BigInt(count - 1);
130
+
131
+ if (endValue >= this.domain) {
132
+ throw new RangeError(i18next.t(count === undefined ? "Transformer.valueMustBeLessThan" : "Transformer.endValueMustBeLessThan", {
133
+ ns: utilityNS,
134
+ endValue,
135
+ domain: this.domain
136
+ }));
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Do the work of transforming a value forward.
142
+ *
143
+ * @param value
144
+ * Value.
145
+ *
146
+ * @returns
147
+ * Transformed value.
148
+ */
149
+ protected abstract doForward(value: bigint): bigint;
150
+
151
+ /**
152
+ * Transform a value forward.
153
+ *
154
+ * @param value
155
+ * Value.
156
+ *
157
+ * @returns
158
+ * Transformed value.
159
+ */
160
+ forward(value: bigint): bigint;
161
+
162
+ /**
163
+ * Transform a value forward.
164
+ *
165
+ * @template T
166
+ * Type returned by transformation callback or bigint if none.
167
+ *
168
+ * @param value
169
+ * Value.
170
+ *
171
+ * @param transformationCallback
172
+ * Called after the value is transformed to convert it to its final value.
173
+ *
174
+ * @returns
175
+ * Value transformed into object.
176
+ */
177
+ forward<T>(value: bigint, transformationCallback: TransformationCallback<T>): T;
178
+
179
+ forward<T>(value: bigint, transformationCallback?: TransformationCallback<T>): bigint | T {
180
+ this.validate(value);
181
+
182
+ const transformedValue = this.doForward(value);
183
+
184
+ return transformationCallback === undefined ? transformedValue : transformationCallback(transformedValue, 0);
185
+ }
186
+
187
+ /**
188
+ * Do the work for the forward sequence method. Parameters are as defined in the public methods.
189
+ *
190
+ * @template T
191
+ * See public methods.
192
+ *
193
+ * @param startValue
194
+ * See public methods.
195
+ *
196
+ * @param count
197
+ * See public methods.
198
+ *
199
+ * @param transformationCallback
200
+ * See public methods.
201
+ *
202
+ * @yields
203
+ * Transformed value optionally defined by transformation callback.
204
+ */
205
+ private * doForwardSequence<T>(startValue: bigint, count: number, transformationCallback?: TransformationCallback<T>): Generator<bigint | T> {
206
+ for (let index = 0, value = startValue; index < count; index++, value++) {
207
+ const transformedValue = this.doForward(value);
208
+
209
+ yield transformationCallback !== undefined ? transformationCallback(transformedValue, index) : transformedValue;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Transform a sequence of values forward.
215
+ *
216
+ * @param startValue
217
+ * Numerical value of the first object. Objects are created from `startValue` to `startValue + count - 1`.
218
+ *
219
+ * @param count
220
+ * Number of objects to create.
221
+ *
222
+ * @yields
223
+ * Transformed value.
224
+ */
225
+ forwardSequence(startValue: bigint, count: number): IterableIterator<bigint>;
226
+
227
+ /**
228
+ * Transform a sequence of values forward.
229
+ *
230
+ * @template T
231
+ * Type returned by transformation callback or bigint if none.
232
+ *
233
+ * @param startValue
234
+ * Numerical value of the first object. Objects are created from `startValue` to `startValue + count - 1`.
235
+ *
236
+ * @param count
237
+ * Number of objects to create.
238
+ *
239
+ * @param transformationCallback
240
+ * Transformation callback called after the value is transformed to convert it to its final value.
241
+ *
242
+ * @returns
243
+ * Iterable iterator over transformed values as defined by transformation callback.
244
+ */
245
+ forwardSequence<T>(startValue: bigint, count: number, transformationCallback: TransformationCallback<T>): IterableIterator<T>;
246
+
247
+ forwardSequence<T>(startValue: bigint, count: number, transformationCallback?: TransformationCallback<T>): IterableIterator<bigint | T> {
248
+ this.validate(startValue, count);
249
+
250
+ return this.doForwardSequence(startValue, count, transformationCallback);
251
+ }
252
+
253
+ /**
254
+ * Transform multiple values forward.
255
+ *
256
+ * @param valuesSource
257
+ * Source of values.
258
+ *
259
+ * @returns
260
+ * Iterable iterator over transformed values.
261
+ */
262
+ forwardMultiple(valuesSource: IterationSource<bigint>): IterableIterator<bigint>;
263
+
264
+ /**
265
+ * Transform multiple values forward.
266
+ *
267
+ * @template T
268
+ * Type returned by transformation callback or bigint if none.
269
+ *
270
+ * @param valuesSource
271
+ * Source of values.
272
+ *
273
+ * @param transformationCallback
274
+ * Transformation callback called after the value is transformed to convert it to its final value.
275
+ *
276
+ * @returns
277
+ * Iterable iterator over transformed values as defined by transformation callback.
278
+ */
279
+ forwardMultiple<T>(valuesSource: IterationSource<bigint>, transformationCallback: TransformationCallback<T>): IterableIterator<T>;
280
+
281
+ forwardMultiple<T>(valuesSource: IterationSource<bigint>, transformationCallback?: TransformationCallback<T>): IterableIterator<bigint | T> {
282
+ return IterationHelper.from(valuesSource).map((value, index) => {
283
+ this.validate(value);
284
+
285
+ const transformedValue = this.doForward(value);
286
+
287
+ return transformationCallback !== undefined ? transformationCallback(transformedValue, index) : transformedValue;
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Do the work of transforming a value in reverse.
293
+ *
294
+ * @param transformedValue
295
+ * Transformed value.
296
+ *
297
+ * @returns
298
+ * Value.
299
+ */
300
+ protected abstract doReverse(transformedValue: bigint): bigint;
301
+
302
+ /**
303
+ * Transform a value in reverse.
304
+ *
305
+ * @param transformedValue
306
+ * Transformed value.
307
+ *
308
+ * @returns
309
+ * Value.
310
+ */
311
+ reverse(transformedValue: bigint): bigint {
312
+ this.validate(transformedValue);
313
+
314
+ return this.doReverse(transformedValue);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Identity transformer. Values are transformed to themselves.
320
+ */
321
+ export class IdentityTransformer extends Transformer {
322
+ protected doForward(value: bigint): bigint {
323
+ return value;
324
+ }
325
+
326
+ protected doReverse(transformedValue: bigint): bigint {
327
+ return transformedValue;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Encryption transformer. Values are transformed using repeated shuffle and xor operations. The underlying operations
333
+ * are similar to those found in many cryptography algorithms, particularly AES. While sufficient for obfuscation of
334
+ * numeric sequences (e.g., serial number generation, below), if true format-preserving encryption is required, a more
335
+ * robust algorithm such as {@link https://doi.org/10.6028/NIST.SP.800-38Gr1-draft | FF1} is recommended.
336
+ *
337
+ * The purpose of the encryption transformer is to generate pseudo-random values in a deterministic manner to obscure
338
+ * the sequence of values generated over time. A typical example is for serial number generation, where knowledge of the
339
+ * sequence can infer production volumes (e.g., serial number 1000 implies that at least 1,000 units have been
340
+ * manufactured) or can be used in counterfeiting (e.g., a counterfeiter can generate serial numbers 1001, 1002, ...
341
+ * with reasonable confidence that they would be valid if queried).
342
+ *
343
+ * The domain and the tweak together determine the encryption key, which in turn determines the number of rounds of
344
+ * shuffle and xor operations. The minimum number of rounds is 4, except where the domain is less than or equal to 256,
345
+ * which results in single-byte operations. To ensure that the operations are effective for single-byte domains, the
346
+ * number of rounds is 1 and only the xor operation is applied (shuffling a single byte is an identity operation).
347
+ *
348
+ * Another exception is when there is a tweak value of 0; this results in identity operations where the output value is
349
+ * identical to the input value, as no shuffle or xor takes place.
350
+ */
351
+ export class EncryptionTransformer extends Transformer {
352
+ /**
353
+ * Individual bits, pre-calculated for performance.
354
+ */
355
+ private static readonly BITS = new Uint8Array([
356
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
357
+ ]);
358
+
359
+ /**
360
+ * Inverse individual bits, pre-calculated for performance.
361
+ */
362
+ private static readonly INVERSE_BITS = new Uint8Array([
363
+ 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F
364
+ ]);
365
+
366
+ /**
367
+ * Number of bytes covered by the domain.
368
+ */
369
+ private readonly _domainBytes: number;
370
+
371
+ /**
372
+ * Tweak.
373
+ */
374
+ private readonly _tweak: bigint;
375
+
376
+ /**
377
+ * Xor bytes array generated from the domain and tweak.
378
+ */
379
+ private readonly _xorBytes: Uint8Array;
380
+
381
+ /**
382
+ * Bits array generated from the domain and tweak.
383
+ */
384
+ private readonly _bits: Uint8Array;
385
+
386
+ /**
387
+ * Inverse bits array generated from the domain and tweak.
388
+ */
389
+ private readonly _inverseBits: Uint8Array;
390
+
391
+ /**
392
+ * Number of rounds (length of arrays) generated from the domain and tweak.
393
+ */
394
+ private readonly _rounds: number;
395
+
396
+ /**
397
+ * Constructor.
398
+ *
399
+ * @param domain
400
+ * Domain.
401
+ *
402
+ * @param tweak
403
+ * Tweak.
404
+ */
405
+ constructor(domain: bigint, tweak: bigint) {
406
+ super(domain);
407
+
408
+ if (tweak < 0n) {
409
+ throw new RangeError(i18next.t("Transformer.tweakMustBeGreaterThanOrEqualToZero", {
410
+ ns: utilityNS,
411
+ tweak
412
+ }));
413
+ }
414
+
415
+ let domainBytes = 0;
416
+
417
+ // The number of bytes in the domain determines the size of the shuffle and xor operations.
418
+ for (let reducedDomainMinusOne = domain - 1n; reducedDomainMinusOne !== 0n; reducedDomainMinusOne = reducedDomainMinusOne >> 8n) {
419
+ domainBytes++;
420
+ }
421
+
422
+ this._domainBytes = domainBytes;
423
+ this._tweak = tweak;
424
+
425
+ const xorBytes = new Array<number>();
426
+ const bits = new Array<number>();
427
+ const inverseBits = new Array<number>();
428
+
429
+ // Key is the product of domain, tweak, and an 8-digit prime to force at least four rounds.
430
+ for (let reducedKey = domain * tweak * 603868999n; reducedKey !== 0n; reducedKey = reducedKey >> 8n) {
431
+ // Extract least-significant byte.
432
+ const keyByte = Number(reducedKey & 0xFFn);
433
+
434
+ xorBytes.unshift(keyByte);
435
+
436
+ // Bit number is the key byte mod 8.
437
+ const bitNumber = keyByte & 0x07;
438
+
439
+ // Bits are applied in reverse order so that they don't correlate directly with the key bytes at the same index.
440
+ bits.push(EncryptionTransformer.BITS[bitNumber]);
441
+ inverseBits.push(EncryptionTransformer.INVERSE_BITS[bitNumber]);
442
+ }
443
+
444
+ // Domains occupying a single byte will not shuffle and will map all values to themselves for very small domains.
445
+ if (domainBytes === 1) {
446
+ // Determine the lowest possible mask that will cover all values in the domain.
447
+ const domainMask = EncryptionTransformer.BITS.filter(bit => bit < domain).reduce((accumulator, bit) => accumulator | bit, 0);
448
+
449
+ // Reduce all xor bytes to a single byte and strip higher bits.
450
+ this._xorBytes = new Uint8Array([xorBytes.reduce((accumulator, xorByte) => accumulator ^ xorByte, 0) & domainMask]);
451
+
452
+ // Bits and inverse bits are irrelevant as there will be no shuffling; choose first bit arbitrarily.
453
+ this._bits = new Uint8Array([EncryptionTransformer.BITS[0]]);
454
+ this._inverseBits = new Uint8Array([EncryptionTransformer.INVERSE_BITS[0]]);
455
+
456
+ // Everything will be done in one round.
457
+ this._rounds = 1;
458
+ } else {
459
+ this._xorBytes = new Uint8Array(xorBytes);
460
+ this._bits = new Uint8Array(bits);
461
+ this._inverseBits = new Uint8Array(inverseBits);
462
+ this._rounds = xorBytes.length;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Get the tweak.
468
+ */
469
+ get tweak(): bigint {
470
+ return this._tweak;
471
+ }
472
+
473
+ /**
474
+ * Convert a value to a byte array big enough to handle the entire domain.
475
+ *
476
+ * @param value
477
+ * Value.
478
+ *
479
+ * @returns
480
+ * Big-endian byte array equivalent to the value.
481
+ */
482
+ private valueToBytes(value: bigint): Uint8Array {
483
+ const bytes = new Uint8Array(this._domainBytes);
484
+
485
+ let reducedValue = value;
486
+
487
+ // Build byte array in reverse order to get as big-endian.
488
+ for (let index = this._domainBytes - 1; index >= 0; index--) {
489
+ bytes[index] = Number(reducedValue & 0xFFn);
490
+
491
+ reducedValue = reducedValue >> 8n;
492
+ }
493
+
494
+ return bytes;
495
+ }
496
+
497
+ /**
498
+ * Convert a byte array to a value.
499
+ *
500
+ * @param bytes
501
+ * Big-endian byte array equivalent to the value.
502
+ *
503
+ * @returns
504
+ * Value.
505
+ */
506
+ private static bytesToValue(bytes: Uint8Array): bigint {
507
+ return bytes.reduce((accumulator, byte) => {
508
+ return accumulator << 8n | BigInt(byte);
509
+ }, 0n);
510
+ }
511
+
512
+ /**
513
+ * Shuffle a byte array.
514
+ *
515
+ * The input array to the forward operation (output from the reverse operation) is `bytes` and the output array from
516
+ * the forward operation (input to the reverse operation) is `bytes'`.
517
+ *
518
+ * The shuffle operation starts by testing the bit at `bits[round]` for each `byte` in `bytes`. The indexes for all
519
+ * bytes with that bit set are put into one array (`shuffleIndexes1`) and the rest are put into another
520
+ * (`shuffleIndexes0`). The two arrays are concatenated and used to shuffle the input array, using their values
521
+ * (`shuffleIndex`) and the indexes of those values (`index`) in the concatenated array.
522
+ *
523
+ * Forward shuffling moves the entry at `shuffleIndex` to the `index` position.
524
+ *
525
+ * Reverse shuffling moves the entry at `index` to the `shuffleIndex` position.
526
+ *
527
+ * As each byte is moved, the bit at `bits[round]` is preserved in its original position. This ensures that the
528
+ * process is reversible.
529
+ *
530
+ * @param bytes
531
+ * Byte array.
532
+ *
533
+ * @param round
534
+ * Round number.
535
+ *
536
+ * @param forward
537
+ * True if operating forward (encrypting), false if operating in reverse (decrypting).
538
+ *
539
+ * @returns
540
+ * Shuffled byte array.
541
+ */
542
+ private shuffle(bytes: Uint8Array, round: number, forward: boolean): Uint8Array {
543
+ const bytesLength = bytes.length;
544
+
545
+ const determinants = new Uint8Array(bytesLength);
546
+
547
+ const shuffleIndexes1 = new Array<number>();
548
+ const shuffleIndexes0 = new Array<number>();
549
+
550
+ const bit = this._bits[round];
551
+
552
+ bytes.forEach((byte, index) => {
553
+ const determinant = byte & bit;
554
+
555
+ determinants[index] = determinant;
556
+
557
+ // Place byte in array chosen by bit state.
558
+ (determinant !== 0 ? shuffleIndexes1 : shuffleIndexes0).push(index);
559
+ });
560
+
561
+ const inverseBit = this._inverseBits[round];
562
+
563
+ const shuffleBytes = new Uint8Array(bytesLength);
564
+
565
+ // Concatenate shuffle indexes arrays and complete shuffle.
566
+ [...shuffleIndexes1, ...shuffleIndexes0].forEach((shuffleIndex, index) => {
567
+ if (forward) {
568
+ shuffleBytes[index] = (bytes[shuffleIndex] & inverseBit) | determinants[index];
569
+ } else {
570
+ shuffleBytes[shuffleIndex] = (bytes[index] & inverseBit) | determinants[shuffleIndex];
571
+ }
572
+ });
573
+
574
+ return shuffleBytes;
575
+ }
576
+
577
+ /**
578
+ * Xor a byte array.
579
+ *
580
+ * The input array to the forward operation (output from the reverse operation) is `bytes` and the output array from
581
+ * the forward operation (input to the reverse operation) is `bytes'`.
582
+ *
583
+ * Forward:
584
+ * - `bytes'[0] = bytes[0] ^ xorBytes[round]`
585
+ * - `bytes'[1] = bytes[1] ^ bytes'[0]`
586
+ * - `bytes'[2] = bytes[2] ^ bytes'[1]`
587
+ * - `...`
588
+ * - `bytes'[domainBytes - 1] = bytes[domainBytes - 1] ^ bytes'[domainBytes - 2]`
589
+ *
590
+ * Reverse:
591
+ * - `bytes[0] = bytes'[0] ^ xorBytes[round]`
592
+ * - `bytes[1] = bytes'[1] ^ bytes'[0]`
593
+ * - `bytes[2] = bytes'[2] ^ bytes'[1]`
594
+ * - `...`
595
+ * - `bytes[domainBytes - 1] = bytes'[domainBytes - 1] ^ bytes'[domainBytes - 2]`
596
+ *
597
+ * @param bytes
598
+ * Byte array.
599
+ *
600
+ * @param round
601
+ * Round number.
602
+ *
603
+ * @param forward
604
+ * True if operating forward (encrypting), false if operating in reverse (decrypting).
605
+ *
606
+ * @returns
607
+ * Xored byte array.
608
+ */
609
+ private xor(bytes: Uint8Array, round: number, forward: boolean): Uint8Array {
610
+ let cumulativeXorByte = this._xorBytes[round];
611
+
612
+ return bytes.map((byte) => {
613
+ const xorByte = byte ^ cumulativeXorByte;
614
+
615
+ cumulativeXorByte = forward ? xorByte : byte;
616
+
617
+ return xorByte;
618
+ });
619
+ }
620
+
621
+ protected doForward(value: bigint): bigint {
622
+ let bytes = this.valueToBytes(value);
623
+ let transformedValue: bigint;
624
+
625
+ // Loop repeats until transformed value is within domain.
626
+ do {
627
+ // Forward operation is shuffle then xor for the number of rounds.
628
+ for (let round = 0; round < this._rounds; round++) {
629
+ bytes = this.xor(this.shuffle(bytes, round, true), round, true);
630
+ }
631
+
632
+ transformedValue = EncryptionTransformer.bytesToValue(bytes);
633
+ } while (transformedValue >= this.domain);
634
+
635
+ return transformedValue;
636
+ }
637
+
638
+ protected doReverse(transformedValue: bigint): bigint {
639
+ let bytes = this.valueToBytes(transformedValue);
640
+ let value: bigint;
641
+
642
+ // Loop repeats until value is within domain.
643
+ do {
644
+ // Reverse operation is xor then shuffle for the number of rounds in reverse.
645
+ for (let round = this._rounds - 1; round >= 0; round--) {
646
+ bytes = this.shuffle(this.xor(bytes, round, false), round, false);
647
+ }
648
+
649
+ value = EncryptionTransformer.bytesToValue(bytes);
650
+ } while (value >= this.domain);
651
+
652
+ return value;
653
+ }
654
+ }