@ibgib/ts-gib 0.5.31 → 0.5.33

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.
@@ -6,303 +6,208 @@ let crypto: any = globalThis.crypto;
6
6
  let { subtle } = crypto;
7
7
 
8
8
  /**
9
- * Performs the gib hash like V1
10
- *
11
- * I have it all in one function for smallest, most independent version possible.
12
- *
13
- * #thanks https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
14
- * #thanks https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
15
- * #thanks https://stackoverflow.com/a/49129872/3897838 (answer to above)
16
- *
17
- * @param ibGib ibGib for which to calculate the gib
9
+ * Pre-computed lookup table mapping byte values (0-255) to their 2-digit padded
10
+ * hex string representations. Used by {@link bufToHex} to convert ArrayBuffers
11
+ * to hex strings with zero string/array allocations per byte, significantly
12
+ * reducing garbage collection thrashing in high-frequency hashing paths.
18
13
  */
19
- export function sha256v1_old(ibGib: IbGib_V1, salt: string = ""): Promise<string> {
20
- // console.log('func_gib_sha256v1 executed');
21
- if (!salt) { salt = ""; }
22
- let hashToHex = async (message: string | undefined) => {
23
- if (!message) { return ""; }
24
- const msgUint8 = new TextEncoder().encode(message);
25
- const buffer = await subtle.digest('SHA-256', msgUint8);
26
- const asArray = Array.from(new Uint8Array(buffer));
27
- // return hashAsHex
28
- return asArray.map(b => b.toString(16).padStart(2, '0')).join('');
29
- };
30
- let hashToHex_Uint8Array = async (salt: string, msgUint8: Uint8Array) => {
31
- let tohashUint8Array: Uint8Array;
32
- if (salt) {
33
- const msgUint8_salt = new TextEncoder().encode(salt);
34
- tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
35
- tohashUint8Array.set(msgUint8_salt);
36
- tohashUint8Array.set(msgUint8, msgUint8_salt.length);
37
- } else {
38
- tohashUint8Array = msgUint8;
39
- }
40
- const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
41
- const hashAsArray = Array.from(new Uint8Array(hashAsBuffer));
42
- // return hashAsHex
43
- return hashAsArray.map(b => b.toString(16).padStart(2, '0')).join('');
44
- };
45
- let hashFields;
46
- if (salt) {
47
- hashFields = async (ib: Ib, data: IbGibData_V1 | undefined, rel8ns: IbGibRel8ns_V1 | undefined) => {
48
- const hasRel8ns =
49
- Object.keys(rel8ns || {}).length > 0 &&
50
- Object.keys(rel8ns || {}).some(k => rel8ns![k] && rel8ns![k]!.length > 0);
51
- let hasData = !!data;
52
- if (hasData) {
53
- if (typeof data === 'string') {
54
- hasData = (data as string).length > 0;
55
- } else if (data instanceof Uint8Array) {
56
- hasData = true;
57
- } else if (typeof data === 'object') {
58
- hasData = Object.keys((data) || {}).length > 0;
59
- } else {
60
- hasData = true;
61
- }
62
- }
63
- const ibHash = (await hashToHex(salt + ib)).toUpperCase();
64
- // empty, null, undefined all treated the same at this level.
65
- const rel8nsHash: string = hasRel8ns ? (await hashToHex(salt + JSON.stringify(rel8ns))).toUpperCase() : "";
66
- // empty, null, undefined all treated the same at this level (though not farther down in data)
67
-
68
- // change this for binaries (Uint8Array data)
69
- // const dataHash: string = hasData ? (await hashToHex(salt + JSON.stringify(data))).toUpperCase() : "";
70
- let dataHash: string = "";
71
- if (hasData) {
72
- if (data instanceof Uint8Array) {
73
- dataHash = (await hashToHex_Uint8Array(salt, data)).toUpperCase();
74
- } else {
75
- dataHash = (await hashToHex(salt + JSON.stringify(data))).toUpperCase();
76
- }
77
- }
14
+ const BYTE_TO_HEX: string[] = new Array(256);
15
+ for (let n = 0; n < 256; ++n) {
16
+ BYTE_TO_HEX[n] = n.toString(16).padStart(2, '0');
17
+ }
78
18
 
79
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
80
- const allHash = hasRel8ns || hasData ?
81
- (await hashToHex(salt + ibHash + rel8nsHash + dataHash)).toUpperCase() :
82
- (await hashToHex(salt + ibHash)).toUpperCase();
83
- return allHash;
84
- };
85
- } else {
86
- hashFields = async (ib: Ib, data: IbGibData_V1 | undefined, rel8ns: IbGibRel8ns_V1 | undefined) => {
87
- const hasRel8ns =
88
- Object.keys(rel8ns || {}).length > 0 &&
89
- Object.keys(rel8ns || {}).some(k => rel8ns![k] && rel8ns![k]!.length > 0);
90
- // const hasData = Object.keys((data) || {}).length > 0;
91
- let hasData = !!data;
92
- if (hasData) {
93
- if (typeof data === 'string') {
94
- hasData = (data as string).length > 0;
95
- } else if (data instanceof Uint8Array) {
96
- hasData = true;
97
- } else if (typeof data === 'object') {
98
- hasData = Object.keys((data) || {}).length > 0;
99
- } else {
100
- hasData = true;
101
- }
102
- }
103
- const ibHash = (await hashToHex(ib)).toUpperCase();
104
- // empty, null, undefined all treated the same at this level.
105
- const rel8nsHash: string = hasRel8ns ? (await hashToHex(JSON.stringify(rel8ns))).toUpperCase() : "";
106
- // empty, null, undefined all treated the same at this level (though not farther down in data)
107
- // const dataHash: string = hasData ? (await hashToHex(JSON.stringify(data))).toUpperCase() : "";
108
- let dataHash: string = "";
109
- if (hasData) {
110
- if (data instanceof Uint8Array) {
111
- dataHash = (await hashToHex_Uint8Array('', data)).toUpperCase();
112
- } else {
113
- dataHash = (await hashToHex(JSON.stringify(data))).toUpperCase();
114
- }
115
- }
116
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
117
- const allHash = hasRel8ns || hasData ?
118
- (await hashToHex(ibHash + rel8nsHash + dataHash)).toUpperCase() :
119
- (await hashToHex(ibHash)).toUpperCase();
120
- return allHash;
121
- }
19
+ function bufToHex(buffer: ArrayBuffer): string {
20
+ const bytes = new Uint8Array(buffer);
21
+ let hex = '';
22
+ for (let i = 0; i < bytes.length; i++) {
23
+ hex += BYTE_TO_HEX[bytes[i]];
122
24
  }
123
- return hashFields(ibGib.ib, ibGib?.data, ibGib?.rel8ns);
25
+ return hex;
124
26
  }
125
27
 
126
- export function sha256v1(ibGib: IbGib_V1, salt: string = ""): Promise<string> {
127
- // console.log('func_gib_sha256v1 executed');
128
- if (!salt) { salt = ""; }
129
- let hashToHex = async (message: string | undefined) => {
130
- if (!message) { return ""; }
131
- const msgUint8 = new TextEncoder().encode(message);
132
- const buffer = await subtle.digest('SHA-256', msgUint8);
133
- const asArray = Array.from(new Uint8Array(buffer));
134
- // return hashAsHex
135
- return asArray.map(b => b.toString(16).padStart(2, '0')).join('');
136
- };
137
- let hashToHex_Uint8Array = async (salt: string, msgUint8: Uint8Array) => {
138
- let tohashUint8Array: Uint8Array;
139
- if (salt) {
140
- const msgUint8_salt = new TextEncoder().encode(salt);
141
- tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
142
- tohashUint8Array.set(msgUint8_salt);
143
- tohashUint8Array.set(msgUint8, msgUint8_salt.length);
144
- } else {
145
- tohashUint8Array = msgUint8;
146
- }
147
- const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
148
- const hashAsArray = Array.from(new Uint8Array(hashAsBuffer));
149
- // return hashAsHex
150
- return hashAsArray.map(b => b.toString(16).padStart(2, '0')).join('');
151
- };
152
-
153
- /**
154
- * THIS IS DUPLICATED CODE IN THE RESPEC FILE. ANY CHANGES HERE MUST
155
- * MANUALLY BE CHANGED IN THE RESPEC FILE!!!
156
- *
157
- * Normalizes an object/value for consistent hashing.
158
- * - Recursively processes objects and arrays.
159
- * - For objects:
160
- * - Sorts keys alphabetically.
161
- * - Removes properties whose values are `undefined`.
162
- * - Keeps properties whose values are `null`.
163
- * - For arrays:
164
- * - Preserves element order.
165
- * - Recursively normalizes each element.
166
- * - Primitives (strings, numbers, booleans) and `null` are returned as is.
167
- *
168
- * THIS IS DUPLICATED CODE IN THE RESPEC FILE. ANY CHANGES HERE MUST
169
- * MANUALLY BE CHANGED IN THE RESPEC FILE!!!
170
- *
171
- * @param value The value to normalize.
172
- * @returns The normalized value.
173
- */
174
- const toNormalizedForHashing = (value: any) => {
175
- // Handle null, primitives (string, number, boolean) directly.
176
- // `undefined` at the top level will be handled by the caller or become part of an object/array.
177
- if (value === null || typeof value !== 'object') {
178
- return value;
179
- }
28
+ /**
29
+ * Normalizes an object/value for consistent hashing.
30
+ * - Recursively processes objects and arrays.
31
+ * - For objects:
32
+ * - Sorts keys alphabetically.
33
+ * - Removes properties whose values are `undefined`.
34
+ * - Keeps properties whose values are `null`.
35
+ * - For arrays:
36
+ * - Preserves element order.
37
+ * - Recursively normalizes each element.
38
+ * - Primitives (strings, numbers, booleans) and `null` are returned as is.
39
+ *
40
+ * @param value The value to normalize.
41
+ * @returns The normalized value.
42
+ */
43
+ export function toNormalizedForHashing(value: any): any {
44
+ // Handle null, primitives (string, number, boolean) directly.
45
+ // `undefined` at the level will be handled by the caller or become part of an object/array.
46
+ if (value === null || typeof value !== 'object') {
47
+ return value;
48
+ }
180
49
 
181
- // Handle Arrays: recursively normalize elements, preserve order
182
- if (Array.isArray(value)) {
183
- return value.map(element => toNormalizedForHashing(element));
184
- }
50
+ // Handle Arrays: recursively normalize elements, preserve order
51
+ if (Array.isArray(value)) {
52
+ return value.map(element => toNormalizedForHashing(element));
53
+ }
185
54
 
186
- // Handle Objects (plain objects)
187
- const normalizedObject: { [key: string]: any } = {};
188
- const sortedKeys = Object.keys(value).sort();
55
+ // Handle Objects (plain objects)
56
+ const normalizedObject: { [key: string]: any } = {};
57
+ const sortedKeys = Object.keys(value).sort();
189
58
 
190
- for (const key of sortedKeys) {
191
- const propertyValue = value[key];
192
- if (propertyValue !== undefined) { // CRITICAL: Only omit if value is undefined
193
- normalizedObject[key] = toNormalizedForHashing(propertyValue);
194
- }
195
- // If propertyValue is undefined, it's omitted from normalizedObject.
196
- // If propertyValue is null, it's included.
59
+ for (const key of sortedKeys) {
60
+ const propertyValue = value[key];
61
+ if (propertyValue !== undefined) { // CRITICAL: Only omit if value is undefined
62
+ normalizedObject[key] = toNormalizedForHashing(propertyValue);
197
63
  }
198
- return normalizedObject;
64
+ // If propertyValue is undefined, it's omitted from normalizedObject.
65
+ // If propertyValue is null, it's included.
66
+ }
67
+ return normalizedObject;
68
+ }
199
69
 
200
- // if (!obj) {
201
- // return obj; /* <<<< returns early */
202
- // } else if (typeof obj === 'string' || Array.isArray(obj)) {
203
- // return obj.concat(); /* <<<< returns early */
204
- // } else if (typeof obj !== 'object') {
205
- // // Return non-objects as is, we don't know how to concat/copy it...hmm...
206
- // return obj; /* <<<< returns early */
207
- // }
70
+ async function hashToHex(message: string | undefined): Promise<string> {
71
+ if (!message) { return ""; }
72
+ const msgUint8 = new TextEncoder().encode(message);
73
+ const buffer = await subtle.digest('SHA-256', msgUint8);
74
+ return bufToHex(buffer);
75
+ }
208
76
 
209
- // // we have an object. we will create a new object and populate it.
210
- // const normalized = {};
77
+ async function hashToHex_Uint8Array(salt: string, msgUint8: Uint8Array): Promise<string> {
78
+ let tohashUint8Array: Uint8Array;
79
+ if (salt) {
80
+ const msgUint8_salt = new TextEncoder().encode(salt);
81
+ tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
82
+ tohashUint8Array.set(msgUint8_salt);
83
+ tohashUint8Array.set(msgUint8, msgUint8_salt.length);
84
+ } else {
85
+ tohashUint8Array = msgUint8;
86
+ }
87
+ const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
88
+ return bufToHex(hashAsBuffer);
89
+ }
211
90
 
212
- // // sort keys alphabetically
213
- // // NOTE: this does NOT mutate obj as Object.keys produces a new array
214
- // const keys = Object.keys(obj).sort();
91
+ export const PROFILE_SHA256 = false; // Toggle profiling
92
+ let sha256v1CallCount = 0;
93
+ let sha256v1TotalDuration = 0;
215
94
 
216
- // for (const key of keys) {
217
- // const value = obj[key];
218
- // if (value !== undefined) {
219
- // // Recursively normalize if the value is an object
220
- // normalized[key] = (typeof value === 'object' && value !== null) ? toNormalizedForHashing(value) : value;
221
- // }
222
- // }
223
- // return normalized;
95
+ /**
96
+ * The internal, optimized implementation of the V1 punctiliar hashing algorithm.
97
+ * Contains the linear, non-branching execution path that computes the hashes
98
+ * for the individual components (ib, data, rel8ns) and combines them.
99
+ *
100
+ * @param ibGib The ibGib object to hash.
101
+ * @param salt An optional cryptographic salt.
102
+ * @returns A promise that resolves to the calculated punctiliar gib string.
103
+ */
104
+ async function sha256v1_Internal(ibGib: IbGib_V1, salt: string = ""): Promise<string> {
105
+ const s = salt || "";
106
+ const ib = ibGib.ib;
107
+ const data = ibGib.data;
108
+ const rel8ns = ibGib.rel8ns;
109
+
110
+ const hasRel8ns =
111
+ Object.keys(rel8ns || {}).length > 0 &&
112
+ Object.keys(rel8ns || {}).some(k => rel8ns![k] && rel8ns![k]!.length > 0);
113
+
114
+ let hasData = !!data;
115
+ if (hasData) {
116
+ if (typeof data === 'string') {
117
+ hasData = (data as string).length > 0;
118
+ } else if (data instanceof Uint8Array) {
119
+ hasData = true;
120
+ } else if (typeof data === 'object') {
121
+ hasData = Object.keys((data) || {}).length > 0;
122
+ } else {
123
+ hasData = true;
124
+ }
224
125
  }
225
126
 
127
+ const ibHash = (await hashToHex(s ? s + ib : ib)).toUpperCase();
226
128
 
227
- let hashFields;
228
- if (salt) {
229
- hashFields = async (ib: Ib, data: IbGibData_V1 | undefined, rel8ns: IbGibRel8ns_V1 | undefined) => {
230
- const hasRel8ns =
231
- Object.keys(rel8ns || {}).length > 0 &&
232
- Object.keys(rel8ns || {}).some(k => rel8ns![k] && rel8ns![k]!.length > 0);
233
- let hasData = !!data;
234
- if (hasData) {
235
- if (typeof data === 'string') {
236
- hasData = (data as string).length > 0;
237
- } else if (data instanceof Uint8Array) {
238
- hasData = true;
239
- } else if (typeof data === 'object') {
240
- hasData = Object.keys((data) || {}).length > 0;
241
- } else {
242
- hasData = true;
243
- }
244
- }
245
- const ibHash = (await hashToHex(salt + ib)).toUpperCase();
246
- // empty, null, undefined all treated the same at this level.
247
- const rel8nsHash: string = hasRel8ns ? (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
248
- // empty, null, undefined all treated the same at this level (though not farther down in data)
129
+ let rel8nsHash = "";
130
+ if (hasRel8ns) {
131
+ const normalizedRel8ns = toNormalizedForHashing(rel8ns);
132
+ const rel8nsStr = JSON.stringify(normalizedRel8ns);
133
+ rel8nsHash = (await hashToHex(s ? s + rel8nsStr : rel8nsStr)).toUpperCase();
134
+ }
249
135
 
250
- // change this for binaries (Uint8Array data)
251
- // const dataHash: string = hasData ? (await hashToHex(salt + JSON.stringify(data))).toUpperCase() : "";
252
- let dataHash: string = "";
253
- if (hasData) {
254
- if (data instanceof Uint8Array) {
255
- dataHash = (await hashToHex_Uint8Array(salt, data)).toUpperCase();
256
- } else {
257
- dataHash = (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
258
- }
259
- }
136
+ let dataHash = "";
137
+ if (hasData) {
138
+ if (data instanceof Uint8Array) {
139
+ dataHash = (await hashToHex_Uint8Array(s, data)).toUpperCase();
140
+ } else {
141
+ const normalizedData = toNormalizedForHashing(data);
142
+ const dataStr = JSON.stringify(normalizedData);
143
+ dataHash = (await hashToHex(s ? s + dataStr : dataStr)).toUpperCase();
144
+ }
145
+ }
260
146
 
261
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
262
- const allHash = hasRel8ns || hasData ?
263
- (await hashToHex(salt + ibHash + rel8nsHash + dataHash)).toUpperCase() :
264
- (await hashToHex(salt + ibHash)).toUpperCase();
265
- return allHash;
266
- };
147
+ let allHash: string;
148
+ if (hasRel8ns || hasData) {
149
+ const combinedMsg = ibHash + rel8nsHash + dataHash;
150
+ allHash = (await hashToHex(s ? s + combinedMsg : combinedMsg)).toUpperCase();
267
151
  } else {
268
- hashFields = async (ib: Ib, data: IbGibData_V1 | undefined, rel8ns: IbGibRel8ns_V1 | undefined) => {
269
- const hasRel8ns =
270
- Object.keys(rel8ns || {}).length > 0 &&
271
- Object.keys(rel8ns || {}).some(k => rel8ns![k] && rel8ns![k]!.length > 0);
272
- // const hasData = Object.keys((data) || {}).length > 0;
273
- let hasData = !!data;
274
- if (hasData) {
275
- if (typeof data === 'string') {
276
- hasData = (data as string).length > 0;
277
- } else if (data instanceof Uint8Array) {
278
- hasData = true;
279
- } else if (typeof data === 'object') {
280
- hasData = Object.keys((data) || {}).length > 0;
281
- } else {
282
- hasData = true;
283
- }
284
- }
285
- const ibHash = (await hashToHex(ib)).toUpperCase();
286
- // empty, null, undefined all treated the same at this level.
287
- const rel8nsHash: string = hasRel8ns ? (await hashToHex(JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
288
- // empty, null, undefined all treated the same at this level (though not farther down in data)
289
- // const dataHash: string = hasData ? (await hashToHex(JSON.stringify(data))).toUpperCase() : "";
290
- let dataHash: string = "";
291
- if (hasData) {
292
- if (data instanceof Uint8Array) {
293
- dataHash = (await hashToHex_Uint8Array('', data)).toUpperCase();
294
- } else {
295
- dataHash = (await hashToHex(JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
296
- }
297
- }
298
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
299
- const allHash = hasRel8ns || hasData ?
300
- (await hashToHex(ibHash + rel8nsHash + dataHash)).toUpperCase() :
301
- (await hashToHex(ibHash)).toUpperCase();
302
- return allHash;
152
+ allHash = (await hashToHex(s ? s + ibHash : ibHash)).toUpperCase();
153
+ }
154
+
155
+ return allHash;
156
+ }
157
+
158
+ /**
159
+ * Computes the composite hash algorithm that generates a single **punctiliar**
160
+ * `gib` for an incoming ibGib object.
161
+ *
162
+ * IMPORTANT: This function does not calculate the tjpGib (timeline junction point)
163
+ * or the complete, fully resolved timeline `gib`. It is the primitive hashing mechanism
164
+ * that only calculates the punctiliar (point-in-time) hash of this specific ibgib's structure.
165
+ * Consumers should generally use `getGib` which handles complete timeline gib calculations
166
+ * and delegates to this function under the hood.
167
+ *
168
+ * ### How the V1 Punctiliar Hashing Algorithm Works:
169
+ * 1. **Normalization**: The incoming `data` and `rel8ns` objects are recursively normalized
170
+ * using {@link toNormalizedForHashing} to ensure deterministic, reproducible results:
171
+ * - Plain objects have their keys sorted alphabetically, and properties with `undefined`
172
+ * values are omitted (while `null` values are kept).
173
+ * - Arrays preserve their element order but recursively normalize their elements.
174
+ * 2. **Salting**: An optional string `salt` is prepended to the encoded strings during hashing
175
+ * (or combined as raw bytes for binary data).
176
+ * 3. **Intermediate Hashes**: The components are hashed individually using SHA-256 and converted
177
+ * to uppercase hex strings:
178
+ * - `ibHash`: Computed from the string `[salt] + ib`.
179
+ * - `rel8nsHash`: If relations exist, computed from the string `[salt] + JSON.stringify(normalizedRel8ns)`. Otherwise, resolves to `""`.
180
+ * - `dataHash`: If data exists, computed as follows:
181
+ * - If the data is a `Uint8Array` (binary), hashed directly by combining the salted bytes and data bytes.
182
+ * - Otherwise, computed from the string `[salt] + JSON.stringify(normalizedData)`.
183
+ * - If data does not exist, resolves to `""`.
184
+ * 4. **Composite (Final) Hash**:
185
+ * - If relations or data exist: Computed from the string `[salt] + ibHash + rel8nsHash + dataHash`.
186
+ * - Otherwise, computed from the string `[salt] + ibHash`.
187
+ *
188
+ * Implementation details:
189
+ * - Eliminates internal closures by using module-scoped helpers.
190
+ * - Utilizes a pre-computed byte-to-hex lookup table to prevent GC thrashing.
191
+ * - Collapses branching logic into a unified, linear, non-branching execution flow.
192
+ * - Optional high-resolution telemetry profiling is toggleable via `PROFILE_SHA256`.
193
+ *
194
+ * @param ibGib The ibGib object whose punctiliar hash is to be computed.
195
+ * @param salt An optional cryptographic salt string.
196
+ * @returns A promise resolving to the canonical punctiliar `gib` hash string in uppercase.
197
+ */
198
+ export async function sha256v1(ibGib: IbGib_V1, salt: string = ""): Promise<string> {
199
+ if (PROFILE_SHA256) {
200
+ const start = globalThis.performance ? globalThis.performance.now() : Date.now();
201
+ const res = await sha256v1_Internal(ibGib, salt);
202
+ const duration = (globalThis.performance ? globalThis.performance.now() : Date.now()) - start;
203
+ sha256v1CallCount++;
204
+ sha256v1TotalDuration += duration;
205
+ if (sha256v1CallCount % 1000 === 0) {
206
+ console.log(`[sha256v1 Profile] Total Runs: ${sha256v1CallCount} | Average Duration: ${(sha256v1TotalDuration / sha256v1CallCount).toFixed(4)} ms`);
303
207
  }
208
+ return res;
304
209
  }
305
- return hashFields(ibGib.ib, ibGib?.data, ibGib?.rel8ns); // conditional nav ibGib?.data and ibGib?.rel8ns
210
+ return sha256v1_Internal(ibGib, salt);
306
211
  }
307
212
 
308
213
  /**
@@ -318,10 +223,9 @@ export async function hashToHexCopy(
318
223
  try {
319
224
  const msgUint8 = new TextEncoder().encode(message);
320
225
  const buffer = await subtle.digest('SHA-256', msgUint8);
321
- const asArray = Array.from(new Uint8Array(buffer));
322
- return asArray.map(b => b.toString(16).padStart(2, '0')).join('');
226
+ return bufToHex(buffer);
323
227
  } catch (e) {
324
228
  console.error(extractErrorMsg(e.message));
325
229
  return undefined;
326
230
  }
327
- };
231
+ }
@@ -17,12 +17,11 @@ import {
17
17
  const maam = `[${import.meta.url}]`, sir = maam;
18
18
 
19
19
  import { IbGib_V1, IbGibRel8ns_V1 } from './types.mjs';
20
- import { sha256v1, hashToHexCopy } from './sha256v1.mjs';
20
+ import { sha256v1, hashToHexCopy, toNormalizedForHashing } from './sha256v1.mjs';
21
21
  import { IbGibWithDataAndRel8ns, IbGibRel8ns } from '../types.mjs';
22
22
  import { getGib, getGibInfo } from './transforms/transform-helper.mjs';
23
23
 
24
24
  import { Factory_V1 as factory } from './factory.mjs';
25
- import { toNormalizedForHashing } from '../helper.mjs';
26
25
 
27
26
  // #region Test Data
28
27
 
@@ -587,4 +586,48 @@ await respecfully(sir, `Python Edge Case Tests for sha256v1`, async () => {
587
586
  iReckon(sir, calculatedGib_ts).asTo(`calculatedGib_ts for s10b (from sha256v1 call)`).isGonnaBe(expected_gib_s10b);
588
587
  });
589
588
 
589
+ await ifWe(sir, `performance profiling test`, async () => {
590
+ // MB-magnitude data test (100 iterations)
591
+ const mbString = "A".repeat(1024 * 1024); // 1 MB
592
+ const ibGibMb: IbGib_V1 = {
593
+ ib: 'performance_test_mb',
594
+ data: {
595
+ large_str: mbString
596
+ }
597
+ };
598
+ const startMb = globalThis.performance ? globalThis.performance.now() : Date.now();
599
+ const iterationsMb = 100;
600
+ let lastGibMb = "";
601
+ for (let i = 0; i < iterationsMb; i++) {
602
+ lastGibMb = await sha256v1(ibGibMb);
603
+ }
604
+ const durationMb = (globalThis.performance ? globalThis.performance.now() : Date.now()) - startMb;
605
+ console.log(`\n[PROFILE] MB Payload (${iterationsMb} iterations): Total Time = ${durationMb.toFixed(2)}ms | Avg = ${(durationMb / iterationsMb).toFixed(4)}ms (last gib: ${lastGibMb})`);
606
+
607
+ // KB-magnitude data test (1000 iterations)
608
+ const kbData: any = {};
609
+ for (let i = 0; i < 100; i++) {
610
+ kbData[`key_${i}`] = "some string value that is relatively long to accumulate size. ".repeat(5); // ~30KB total
611
+ }
612
+ const ibGibKb: IbGib_V1 = {
613
+ ib: 'performance_test_kb',
614
+ data: kbData,
615
+ rel8ns: {
616
+ some_rel: ['addr1', 'addr2', 'addr3', 'addr4']
617
+ }
618
+ };
619
+
620
+ const startKb = globalThis.performance ? globalThis.performance.now() : Date.now();
621
+ const iterationsKb = 1000;
622
+ let lastGibKb = "";
623
+ for (let i = 0; i < iterationsKb; i++) {
624
+ lastGibKb = await sha256v1(ibGibKb);
625
+ }
626
+ const durationKb = (globalThis.performance ? globalThis.performance.now() : Date.now()) - startKb;
627
+ console.log(`[PROFILE] KB Payload (${iterationsKb} iterations): Total Time = ${durationKb.toFixed(2)}ms | Avg = ${(durationKb / iterationsKb).toFixed(4)}ms (last gib: ${lastGibKb})\n`);
628
+
629
+ iReckon(sir, !!lastGibKb).isGonnaBe(true);
630
+ iReckon(sir, !!lastGibMb).isGonnaBe(true);
631
+ });
632
+
590
633
  });
package/src/helper.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Ib, Gib, IbGib, IbGibAddr, IbAndGib } from './types.mjs';
2
+ import { toNormalizedForHashing } from './V1/sha256v1.mjs';
2
3
 
3
4
 
4
5
  /**
@@ -76,49 +77,4 @@ export function getIbAndGib({
76
77
  }
77
78
 
78
79
 
79
- /**
80
- * Normalizes an object/value for consistent hashing.
81
- * - Recursively processes objects and arrays.
82
- * - For objects:
83
- * - Sorts keys alphabetically.
84
- * - Removes properties whose values are `undefined`.
85
- * - Keeps properties whose values are `null`.
86
- * - For arrays:
87
- * - Preserves element order.
88
- * - Recursively normalizes each element.
89
- * - Primitives (strings, numbers, booleans) and `null` are returned as is.
90
- *
91
- * @param value The value to normalize.
92
- * @returns The normalized value.
93
- *
94
- * This has been adjusted due to conversation with Gemini and working on python
95
- * port. The main thing here is that we normalize array members, but not the
96
- * array itself. This way the array's order is preserved, but any object members
97
- * are themselves normalized.
98
- */
99
- export function toNormalizedForHashing(value: any): any {
100
- // Handle null, primitives (string, number, boolean) directly.
101
- // `undefined` at the top level will be handled by the caller or become part of an object/array.
102
- if (value === null || typeof value !== 'object') {
103
- return value;
104
- }
105
-
106
- // Handle Arrays: recursively normalize elements, preserve order
107
- if (Array.isArray(value)) {
108
- return value.map(element => toNormalizedForHashing(element));
109
- }
110
-
111
- // Handle Objects (plain objects)
112
- const normalizedObject: { [key: string]: any } = {};
113
- const sortedKeys = Object.keys(value).sort();
114
-
115
- for (const key of sortedKeys) {
116
- const propertyValue = value[key];
117
- if (propertyValue !== undefined) { // CRITICAL: Only omit if value is undefined
118
- normalizedObject[key] = toNormalizedForHashing(propertyValue);
119
- }
120
- // If propertyValue is undefined, it's omitted from normalizedObject.
121
- // If propertyValue is null, it's included.
122
- }
123
- return normalizedObject;
124
- }
80
+ export { toNormalizedForHashing };
@@ -1,24 +0,0 @@
1
- {
2
- // Use IntelliSense to learn about possible attributes.
3
- // Hover to view descriptions of existing attributes.
4
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
- "version": "0.2.0",
6
- "configurations": [
7
- {
8
- "type": "node",
9
- "request": "launch",
10
- "name": "npm debug",
11
- "runtimeExecutable": "npm",
12
- "runtimeArgs": [
13
- "run",
14
- "debug",
15
- ],
16
- "port": 9229,
17
- "sourceMaps": true,
18
- "skipFiles": [
19
- "<node_internals>/**"
20
- ]
21
- },
22
-
23
- ]
24
- }