@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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # @ibgib/ts-gib changelog
2
+
3
+ _note: as you implement features/fixes/etc., please document them here under the "Working Version" section. These will be moved to a concrete version number during the next publish._
4
+
5
+ ## Working Version
6
+
7
+ ## 0.5.33
8
+ * performance: optimized `sha256v1` hashing function in `V1/sha256v1.mts`
9
+ * moved `toNormalizedForHashing` from internal closure to module scope and exported it, de-duplicating it in `helper.mts`.
10
+ * introduced pre-computed `BYTE_TO_HEX` static lookup table for zero-allocation hex conversion (`bufToHex` replaces slow `.map` chain).
11
+ * moved helper functions `hashToHex` and `hashToHex_Uint8Array` to module scope to avoid garbage collection and JIT re-compilation.
12
+ * collapsed dual-branch closure architecture (`if (salt) ... else ...`) into a unified, linear, non-branching execution flow.
13
+ * removed unused, deprecated `sha256v1_old` function.
14
+ * measured ~21.5% average speedup on KB-magnitude payloads and ~3% speedup on MB-magnitude payloads.
15
+ * chore: centralized build orchestration
16
+ * migrated build, clean, and test logic to the root `@ibgib/build-gib` orchestrator.
17
+ * pruned legacy `package.json` scripts and archived them in `docs/ARCHIVE_SCRIPTS.md`.
18
+ * updated `prepare:publish` to use the centralized build engine.
19
+ * documented the new **Monorepo Build Policy** in the README.
20
+
1
21
  ## 0.5.28/29
2
22
  * added `squash` param to `firstGen`
3
23
  * for use when not using `dna` and you want the firstGen to output a single
package/README.md CHANGED
@@ -21,6 +21,19 @@ Each quantum node has up to four fields, and each field in one word:
21
21
  This is extremely terse and naive of course. Here are slight more fleshed out
22
22
  descriptions of each field.
23
23
 
24
+ ## Build (Monorepo Policy)
25
+
26
+ > [!IMPORTANT]
27
+ > This project is part of a monorepo. Build and development tasks are centralized in the monorepo root via the `@ibgib/build-gib` orchestrator.
28
+
29
+ ### Development & Build
30
+ Run these from the monorepo root:
31
+ * `npm run build:ts-gib` - Performs a full clean and build.
32
+ * `npm run test:ts-gib` - Runs the full respec-gib test suite for this library.
33
+
34
+ ### Legacy Scripts
35
+ Individual package scripts have been streamlined to avoid redundancy. Previous scripts are archived in `docs/ARCHIVE_SCRIPTS.md` at the monorepo root.
36
+
24
37
  * `ib`
25
38
  * data and metadata
26
39
  * contains simple, core data you want to see _without loading the entire datum into memory_.
@@ -1,16 +1,61 @@
1
1
  import { IbGib_V1 } from "./types.mjs";
2
2
  /**
3
- * Performs the gib hash like V1
3
+ * Normalizes an object/value for consistent hashing.
4
+ * - Recursively processes objects and arrays.
5
+ * - For objects:
6
+ * - Sorts keys alphabetically.
7
+ * - Removes properties whose values are `undefined`.
8
+ * - Keeps properties whose values are `null`.
9
+ * - For arrays:
10
+ * - Preserves element order.
11
+ * - Recursively normalizes each element.
12
+ * - Primitives (strings, numbers, booleans) and `null` are returned as is.
4
13
  *
5
- * I have it all in one function for smallest, most independent version possible.
14
+ * @param value The value to normalize.
15
+ * @returns The normalized value.
16
+ */
17
+ export declare function toNormalizedForHashing(value: any): any;
18
+ export declare const PROFILE_SHA256 = false;
19
+ /**
20
+ * Computes the composite hash algorithm that generates a single **punctiliar**
21
+ * `gib` for an incoming ibGib object.
22
+ *
23
+ * IMPORTANT: This function does not calculate the tjpGib (timeline junction point)
24
+ * or the complete, fully resolved timeline `gib`. It is the primitive hashing mechanism
25
+ * that only calculates the punctiliar (point-in-time) hash of this specific ibgib's structure.
26
+ * Consumers should generally use `getGib` which handles complete timeline gib calculations
27
+ * and delegates to this function under the hood.
28
+ *
29
+ * ### How the V1 Punctiliar Hashing Algorithm Works:
30
+ * 1. **Normalization**: The incoming `data` and `rel8ns` objects are recursively normalized
31
+ * using {@link toNormalizedForHashing} to ensure deterministic, reproducible results:
32
+ * - Plain objects have their keys sorted alphabetically, and properties with `undefined`
33
+ * values are omitted (while `null` values are kept).
34
+ * - Arrays preserve their element order but recursively normalize their elements.
35
+ * 2. **Salting**: An optional string `salt` is prepended to the encoded strings during hashing
36
+ * (or combined as raw bytes for binary data).
37
+ * 3. **Intermediate Hashes**: The components are hashed individually using SHA-256 and converted
38
+ * to uppercase hex strings:
39
+ * - `ibHash`: Computed from the string `[salt] + ib`.
40
+ * - `rel8nsHash`: If relations exist, computed from the string `[salt] + JSON.stringify(normalizedRel8ns)`. Otherwise, resolves to `""`.
41
+ * - `dataHash`: If data exists, computed as follows:
42
+ * - If the data is a `Uint8Array` (binary), hashed directly by combining the salted bytes and data bytes.
43
+ * - Otherwise, computed from the string `[salt] + JSON.stringify(normalizedData)`.
44
+ * - If data does not exist, resolves to `""`.
45
+ * 4. **Composite (Final) Hash**:
46
+ * - If relations or data exist: Computed from the string `[salt] + ibHash + rel8nsHash + dataHash`.
47
+ * - Otherwise, computed from the string `[salt] + ibHash`.
6
48
  *
7
- * #thanks https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
8
- * #thanks https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
9
- * #thanks https://stackoverflow.com/a/49129872/3897838 (answer to above)
49
+ * Implementation details:
50
+ * - Eliminates internal closures by using module-scoped helpers.
51
+ * - Utilizes a pre-computed byte-to-hex lookup table to prevent GC thrashing.
52
+ * - Collapses branching logic into a unified, linear, non-branching execution flow.
53
+ * - Optional high-resolution telemetry profiling is toggleable via `PROFILE_SHA256`.
10
54
  *
11
- * @param ibGib ibGib for which to calculate the gib
55
+ * @param ibGib The ibGib object whose punctiliar hash is to be computed.
56
+ * @param salt An optional cryptographic salt string.
57
+ * @returns A promise resolving to the canonical punctiliar `gib` hash string in uppercase.
12
58
  */
13
- export declare function sha256v1_old(ibGib: IbGib_V1, salt?: string): Promise<string>;
14
59
  export declare function sha256v1(ibGib: IbGib_V1, salt?: string): Promise<string>;
15
60
  /**
16
61
  * I have one large-ish sha256 function for gibbing purposes
@@ -1 +1 @@
1
- {"version":3,"file":"sha256v1.d.mts","sourceRoot":"","sources":["../../src/V1/sha256v1.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAgC,MAAM,aAAa,CAAC;AAMrE;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAyGhF;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAoL5E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B"}
1
+ {"version":3,"file":"sha256v1.d.mts","sourceRoot":"","sources":["../../src/V1/sha256v1.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAgC,MAAM,aAAa,CAAC;AA0BrE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAyBtD;AAuBD,eAAO,MAAM,cAAc,QAAQ,CAAC;AAmEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAalF;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B"}
@@ -2,312 +2,198 @@ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs
2
2
  let crypto = globalThis.crypto;
3
3
  let { subtle } = crypto;
4
4
  /**
5
- * Performs the gib hash like V1
6
- *
7
- * I have it all in one function for smallest, most independent version possible.
8
- *
9
- * #thanks https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
10
- * #thanks https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
11
- * #thanks https://stackoverflow.com/a/49129872/3897838 (answer to above)
5
+ * Pre-computed lookup table mapping byte values (0-255) to their 2-digit padded
6
+ * hex string representations. Used by {@link bufToHex} to convert ArrayBuffers
7
+ * to hex strings with zero string/array allocations per byte, significantly
8
+ * reducing garbage collection thrashing in high-frequency hashing paths.
9
+ */
10
+ const BYTE_TO_HEX = new Array(256);
11
+ for (let n = 0; n < 256; ++n) {
12
+ BYTE_TO_HEX[n] = n.toString(16).padStart(2, '0');
13
+ }
14
+ function bufToHex(buffer) {
15
+ const bytes = new Uint8Array(buffer);
16
+ let hex = '';
17
+ for (let i = 0; i < bytes.length; i++) {
18
+ hex += BYTE_TO_HEX[bytes[i]];
19
+ }
20
+ return hex;
21
+ }
22
+ /**
23
+ * Normalizes an object/value for consistent hashing.
24
+ * - Recursively processes objects and arrays.
25
+ * - For objects:
26
+ * - Sorts keys alphabetically.
27
+ * - Removes properties whose values are `undefined`.
28
+ * - Keeps properties whose values are `null`.
29
+ * - For arrays:
30
+ * - Preserves element order.
31
+ * - Recursively normalizes each element.
32
+ * - Primitives (strings, numbers, booleans) and `null` are returned as is.
12
33
  *
13
- * @param ibGib ibGib for which to calculate the gib
34
+ * @param value The value to normalize.
35
+ * @returns The normalized value.
14
36
  */
15
- export function sha256v1_old(ibGib, salt = "") {
16
- // console.log('func_gib_sha256v1 executed');
17
- if (!salt) {
18
- salt = "";
37
+ export function toNormalizedForHashing(value) {
38
+ // Handle null, primitives (string, number, boolean) directly.
39
+ // `undefined` at the level will be handled by the caller or become part of an object/array.
40
+ if (value === null || typeof value !== 'object') {
41
+ return value;
19
42
  }
20
- let hashToHex = async (message) => {
21
- if (!message) {
22
- return "";
23
- }
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, msgUint8) => {
31
- let tohashUint8Array;
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
- }
38
- else {
39
- tohashUint8Array = msgUint8;
43
+ // Handle Arrays: recursively normalize elements, preserve order
44
+ if (Array.isArray(value)) {
45
+ return value.map(element => toNormalizedForHashing(element));
46
+ }
47
+ // Handle Objects (plain objects)
48
+ const normalizedObject = {};
49
+ const sortedKeys = Object.keys(value).sort();
50
+ for (const key of sortedKeys) {
51
+ const propertyValue = value[key];
52
+ if (propertyValue !== undefined) { // CRITICAL: Only omit if value is undefined
53
+ normalizedObject[key] = toNormalizedForHashing(propertyValue);
40
54
  }
41
- const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
42
- const hashAsArray = Array.from(new Uint8Array(hashAsBuffer));
43
- // return hashAsHex
44
- return hashAsArray.map(b => b.toString(16).padStart(2, '0')).join('');
45
- };
46
- let hashFields;
55
+ // If propertyValue is undefined, it's omitted from normalizedObject.
56
+ // If propertyValue is null, it's included.
57
+ }
58
+ return normalizedObject;
59
+ }
60
+ async function hashToHex(message) {
61
+ if (!message) {
62
+ return "";
63
+ }
64
+ const msgUint8 = new TextEncoder().encode(message);
65
+ const buffer = await subtle.digest('SHA-256', msgUint8);
66
+ return bufToHex(buffer);
67
+ }
68
+ async function hashToHex_Uint8Array(salt, msgUint8) {
69
+ let tohashUint8Array;
47
70
  if (salt) {
48
- hashFields = async (ib, data, rel8ns) => {
49
- const hasRel8ns = 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.length > 0;
55
- }
56
- else if (data instanceof Uint8Array) {
57
- hasData = true;
58
- }
59
- else if (typeof data === 'object') {
60
- hasData = Object.keys((data) || {}).length > 0;
61
- }
62
- else {
63
- hasData = true;
64
- }
65
- }
66
- const ibHash = (await hashToHex(salt + ib)).toUpperCase();
67
- // empty, null, undefined all treated the same at this level.
68
- const rel8nsHash = hasRel8ns ? (await hashToHex(salt + JSON.stringify(rel8ns))).toUpperCase() : "";
69
- // empty, null, undefined all treated the same at this level (though not farther down in data)
70
- // change this for binaries (Uint8Array data)
71
- // const dataHash: string = hasData ? (await hashToHex(salt + JSON.stringify(data))).toUpperCase() : "";
72
- let dataHash = "";
73
- if (hasData) {
74
- if (data instanceof Uint8Array) {
75
- dataHash = (await hashToHex_Uint8Array(salt, data)).toUpperCase();
76
- }
77
- else {
78
- dataHash = (await hashToHex(salt + JSON.stringify(data))).toUpperCase();
79
- }
80
- }
81
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
82
- const allHash = hasRel8ns || hasData ?
83
- (await hashToHex(salt + ibHash + rel8nsHash + dataHash)).toUpperCase() :
84
- (await hashToHex(salt + ibHash)).toUpperCase();
85
- return allHash;
86
- };
71
+ const msgUint8_salt = new TextEncoder().encode(salt);
72
+ tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
73
+ tohashUint8Array.set(msgUint8_salt);
74
+ tohashUint8Array.set(msgUint8, msgUint8_salt.length);
87
75
  }
88
76
  else {
89
- hashFields = async (ib, data, rel8ns) => {
90
- const hasRel8ns = Object.keys(rel8ns || {}).length > 0 &&
91
- Object.keys(rel8ns || {}).some(k => rel8ns[k] && rel8ns[k].length > 0);
92
- // const hasData = Object.keys((data) || {}).length > 0;
93
- let hasData = !!data;
94
- if (hasData) {
95
- if (typeof data === 'string') {
96
- hasData = data.length > 0;
97
- }
98
- else if (data instanceof Uint8Array) {
99
- hasData = true;
100
- }
101
- else if (typeof data === 'object') {
102
- hasData = Object.keys((data) || {}).length > 0;
103
- }
104
- else {
105
- hasData = true;
106
- }
107
- }
108
- const ibHash = (await hashToHex(ib)).toUpperCase();
109
- // empty, null, undefined all treated the same at this level.
110
- const rel8nsHash = hasRel8ns ? (await hashToHex(JSON.stringify(rel8ns))).toUpperCase() : "";
111
- // empty, null, undefined all treated the same at this level (though not farther down in data)
112
- // const dataHash: string = hasData ? (await hashToHex(JSON.stringify(data))).toUpperCase() : "";
113
- let dataHash = "";
114
- if (hasData) {
115
- if (data instanceof Uint8Array) {
116
- dataHash = (await hashToHex_Uint8Array('', data)).toUpperCase();
117
- }
118
- else {
119
- dataHash = (await hashToHex(JSON.stringify(data))).toUpperCase();
120
- }
121
- }
122
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
123
- const allHash = hasRel8ns || hasData ?
124
- (await hashToHex(ibHash + rel8nsHash + dataHash)).toUpperCase() :
125
- (await hashToHex(ibHash)).toUpperCase();
126
- return allHash;
127
- };
77
+ tohashUint8Array = msgUint8;
128
78
  }
129
- return hashFields(ibGib.ib, ibGib?.data, ibGib?.rel8ns);
79
+ const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
80
+ return bufToHex(hashAsBuffer);
130
81
  }
131
- export function sha256v1(ibGib, salt = "") {
132
- // console.log('func_gib_sha256v1 executed');
133
- if (!salt) {
134
- salt = "";
135
- }
136
- let hashToHex = async (message) => {
137
- if (!message) {
138
- return "";
82
+ export const PROFILE_SHA256 = false; // Toggle profiling
83
+ let sha256v1CallCount = 0;
84
+ let sha256v1TotalDuration = 0;
85
+ /**
86
+ * The internal, optimized implementation of the V1 punctiliar hashing algorithm.
87
+ * Contains the linear, non-branching execution path that computes the hashes
88
+ * for the individual components (ib, data, rel8ns) and combines them.
89
+ *
90
+ * @param ibGib The ibGib object to hash.
91
+ * @param salt An optional cryptographic salt.
92
+ * @returns A promise that resolves to the calculated punctiliar gib string.
93
+ */
94
+ async function sha256v1_Internal(ibGib, salt = "") {
95
+ const s = salt || "";
96
+ const ib = ibGib.ib;
97
+ const data = ibGib.data;
98
+ const rel8ns = ibGib.rel8ns;
99
+ const hasRel8ns = Object.keys(rel8ns || {}).length > 0 &&
100
+ Object.keys(rel8ns || {}).some(k => rel8ns[k] && rel8ns[k].length > 0);
101
+ let hasData = !!data;
102
+ if (hasData) {
103
+ if (typeof data === 'string') {
104
+ hasData = data.length > 0;
139
105
  }
140
- const msgUint8 = new TextEncoder().encode(message);
141
- const buffer = await subtle.digest('SHA-256', msgUint8);
142
- const asArray = Array.from(new Uint8Array(buffer));
143
- // return hashAsHex
144
- return asArray.map(b => b.toString(16).padStart(2, '0')).join('');
145
- };
146
- let hashToHex_Uint8Array = async (salt, msgUint8) => {
147
- let tohashUint8Array;
148
- if (salt) {
149
- const msgUint8_salt = new TextEncoder().encode(salt);
150
- tohashUint8Array = new Uint8Array(msgUint8_salt.length + msgUint8.length);
151
- tohashUint8Array.set(msgUint8_salt);
152
- tohashUint8Array.set(msgUint8, msgUint8_salt.length);
106
+ else if (data instanceof Uint8Array) {
107
+ hasData = true;
153
108
  }
154
- else {
155
- tohashUint8Array = msgUint8;
109
+ else if (typeof data === 'object') {
110
+ hasData = Object.keys((data) || {}).length > 0;
156
111
  }
157
- const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
158
- const hashAsArray = Array.from(new Uint8Array(hashAsBuffer));
159
- // return hashAsHex
160
- return hashAsArray.map(b => b.toString(16).padStart(2, '0')).join('');
161
- };
162
- /**
163
- * THIS IS DUPLICATED CODE IN THE RESPEC FILE. ANY CHANGES HERE MUST
164
- * MANUALLY BE CHANGED IN THE RESPEC FILE!!!
165
- *
166
- * Normalizes an object/value for consistent hashing.
167
- * - Recursively processes objects and arrays.
168
- * - For objects:
169
- * - Sorts keys alphabetically.
170
- * - Removes properties whose values are `undefined`.
171
- * - Keeps properties whose values are `null`.
172
- * - For arrays:
173
- * - Preserves element order.
174
- * - Recursively normalizes each element.
175
- * - Primitives (strings, numbers, booleans) and `null` are returned as is.
176
- *
177
- * THIS IS DUPLICATED CODE IN THE RESPEC FILE. ANY CHANGES HERE MUST
178
- * MANUALLY BE CHANGED IN THE RESPEC FILE!!!
179
- *
180
- * @param value The value to normalize.
181
- * @returns The normalized value.
182
- */
183
- const toNormalizedForHashing = (value) => {
184
- // Handle null, primitives (string, number, boolean) directly.
185
- // `undefined` at the top level will be handled by the caller or become part of an object/array.
186
- if (value === null || typeof value !== 'object') {
187
- return value;
112
+ else {
113
+ hasData = true;
188
114
  }
189
- // Handle Arrays: recursively normalize elements, preserve order
190
- if (Array.isArray(value)) {
191
- return value.map(element => toNormalizedForHashing(element));
115
+ }
116
+ const ibHash = (await hashToHex(s ? s + ib : ib)).toUpperCase();
117
+ let rel8nsHash = "";
118
+ if (hasRel8ns) {
119
+ const normalizedRel8ns = toNormalizedForHashing(rel8ns);
120
+ const rel8nsStr = JSON.stringify(normalizedRel8ns);
121
+ rel8nsHash = (await hashToHex(s ? s + rel8nsStr : rel8nsStr)).toUpperCase();
122
+ }
123
+ let dataHash = "";
124
+ if (hasData) {
125
+ if (data instanceof Uint8Array) {
126
+ dataHash = (await hashToHex_Uint8Array(s, data)).toUpperCase();
192
127
  }
193
- // Handle Objects (plain objects)
194
- const normalizedObject = {};
195
- const sortedKeys = Object.keys(value).sort();
196
- for (const key of sortedKeys) {
197
- const propertyValue = value[key];
198
- if (propertyValue !== undefined) { // CRITICAL: Only omit if value is undefined
199
- normalizedObject[key] = toNormalizedForHashing(propertyValue);
200
- }
201
- // If propertyValue is undefined, it's omitted from normalizedObject.
202
- // If propertyValue is null, it's included.
128
+ else {
129
+ const normalizedData = toNormalizedForHashing(data);
130
+ const dataStr = JSON.stringify(normalizedData);
131
+ dataHash = (await hashToHex(s ? s + dataStr : dataStr)).toUpperCase();
203
132
  }
204
- return normalizedObject;
205
- // if (!obj) {
206
- // return obj; /* <<<< returns early */
207
- // } else if (typeof obj === 'string' || Array.isArray(obj)) {
208
- // return obj.concat(); /* <<<< returns early */
209
- // } else if (typeof obj !== 'object') {
210
- // // Return non-objects as is, we don't know how to concat/copy it...hmm...
211
- // return obj; /* <<<< returns early */
212
- // }
213
- // // we have an object. we will create a new object and populate it.
214
- // const normalized = {};
215
- // // sort keys alphabetically
216
- // // NOTE: this does NOT mutate obj as Object.keys produces a new array
217
- // const keys = Object.keys(obj).sort();
218
- // for (const key of keys) {
219
- // const value = obj[key];
220
- // if (value !== undefined) {
221
- // // Recursively normalize if the value is an object
222
- // normalized[key] = (typeof value === 'object' && value !== null) ? toNormalizedForHashing(value) : value;
223
- // }
224
- // }
225
- // return normalized;
226
- };
227
- let hashFields;
228
- if (salt) {
229
- hashFields = async (ib, data, rel8ns) => {
230
- const hasRel8ns = Object.keys(rel8ns || {}).length > 0 &&
231
- Object.keys(rel8ns || {}).some(k => rel8ns[k] && rel8ns[k].length > 0);
232
- let hasData = !!data;
233
- if (hasData) {
234
- if (typeof data === 'string') {
235
- hasData = data.length > 0;
236
- }
237
- else if (data instanceof Uint8Array) {
238
- hasData = true;
239
- }
240
- else if (typeof data === 'object') {
241
- hasData = Object.keys((data) || {}).length > 0;
242
- }
243
- else {
244
- hasData = true;
245
- }
246
- }
247
- const ibHash = (await hashToHex(salt + ib)).toUpperCase();
248
- // empty, null, undefined all treated the same at this level.
249
- const rel8nsHash = hasRel8ns ? (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
250
- // empty, null, undefined all treated the same at this level (though not farther down in data)
251
- // change this for binaries (Uint8Array data)
252
- // const dataHash: string = hasData ? (await hashToHex(salt + JSON.stringify(data))).toUpperCase() : "";
253
- let dataHash = "";
254
- if (hasData) {
255
- if (data instanceof Uint8Array) {
256
- dataHash = (await hashToHex_Uint8Array(salt, data)).toUpperCase();
257
- }
258
- else {
259
- dataHash = (await hashToHex(salt + JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
260
- }
261
- }
262
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
263
- const allHash = hasRel8ns || hasData ?
264
- (await hashToHex(salt + ibHash + rel8nsHash + dataHash)).toUpperCase() :
265
- (await hashToHex(salt + ibHash)).toUpperCase();
266
- return allHash;
267
- };
133
+ }
134
+ let allHash;
135
+ if (hasRel8ns || hasData) {
136
+ const combinedMsg = ibHash + rel8nsHash + dataHash;
137
+ allHash = (await hashToHex(s ? s + combinedMsg : combinedMsg)).toUpperCase();
268
138
  }
269
139
  else {
270
- hashFields = async (ib, data, rel8ns) => {
271
- const hasRel8ns = Object.keys(rel8ns || {}).length > 0 &&
272
- Object.keys(rel8ns || {}).some(k => rel8ns[k] && rel8ns[k].length > 0);
273
- // const hasData = Object.keys((data) || {}).length > 0;
274
- let hasData = !!data;
275
- if (hasData) {
276
- if (typeof data === 'string') {
277
- hasData = data.length > 0;
278
- }
279
- else if (data instanceof Uint8Array) {
280
- hasData = true;
281
- }
282
- else if (typeof data === 'object') {
283
- hasData = Object.keys((data) || {}).length > 0;
284
- }
285
- else {
286
- hasData = true;
287
- }
288
- }
289
- const ibHash = (await hashToHex(ib)).toUpperCase();
290
- // empty, null, undefined all treated the same at this level.
291
- const rel8nsHash = hasRel8ns ? (await hashToHex(JSON.stringify(toNormalizedForHashing(rel8ns)))).toUpperCase() : "";
292
- // empty, null, undefined all treated the same at this level (though not farther down in data)
293
- // const dataHash: string = hasData ? (await hashToHex(JSON.stringify(data))).toUpperCase() : "";
294
- let dataHash = "";
295
- if (hasData) {
296
- if (data instanceof Uint8Array) {
297
- dataHash = (await hashToHex_Uint8Array('', data)).toUpperCase();
298
- }
299
- else {
300
- dataHash = (await hashToHex(JSON.stringify(toNormalizedForHashing(data)))).toUpperCase();
301
- }
302
- }
303
- // if the ibgib has no rel8ns or data, the hash should be just of the ib itself.
304
- const allHash = hasRel8ns || hasData ?
305
- (await hashToHex(ibHash + rel8nsHash + dataHash)).toUpperCase() :
306
- (await hashToHex(ibHash)).toUpperCase();
307
- return allHash;
308
- };
140
+ allHash = (await hashToHex(s ? s + ibHash : ibHash)).toUpperCase();
141
+ }
142
+ return allHash;
143
+ }
144
+ /**
145
+ * Computes the composite hash algorithm that generates a single **punctiliar**
146
+ * `gib` for an incoming ibGib object.
147
+ *
148
+ * IMPORTANT: This function does not calculate the tjpGib (timeline junction point)
149
+ * or the complete, fully resolved timeline `gib`. It is the primitive hashing mechanism
150
+ * that only calculates the punctiliar (point-in-time) hash of this specific ibgib's structure.
151
+ * Consumers should generally use `getGib` which handles complete timeline gib calculations
152
+ * and delegates to this function under the hood.
153
+ *
154
+ * ### How the V1 Punctiliar Hashing Algorithm Works:
155
+ * 1. **Normalization**: The incoming `data` and `rel8ns` objects are recursively normalized
156
+ * using {@link toNormalizedForHashing} to ensure deterministic, reproducible results:
157
+ * - Plain objects have their keys sorted alphabetically, and properties with `undefined`
158
+ * values are omitted (while `null` values are kept).
159
+ * - Arrays preserve their element order but recursively normalize their elements.
160
+ * 2. **Salting**: An optional string `salt` is prepended to the encoded strings during hashing
161
+ * (or combined as raw bytes for binary data).
162
+ * 3. **Intermediate Hashes**: The components are hashed individually using SHA-256 and converted
163
+ * to uppercase hex strings:
164
+ * - `ibHash`: Computed from the string `[salt] + ib`.
165
+ * - `rel8nsHash`: If relations exist, computed from the string `[salt] + JSON.stringify(normalizedRel8ns)`. Otherwise, resolves to `""`.
166
+ * - `dataHash`: If data exists, computed as follows:
167
+ * - If the data is a `Uint8Array` (binary), hashed directly by combining the salted bytes and data bytes.
168
+ * - Otherwise, computed from the string `[salt] + JSON.stringify(normalizedData)`.
169
+ * - If data does not exist, resolves to `""`.
170
+ * 4. **Composite (Final) Hash**:
171
+ * - If relations or data exist: Computed from the string `[salt] + ibHash + rel8nsHash + dataHash`.
172
+ * - Otherwise, computed from the string `[salt] + ibHash`.
173
+ *
174
+ * Implementation details:
175
+ * - Eliminates internal closures by using module-scoped helpers.
176
+ * - Utilizes a pre-computed byte-to-hex lookup table to prevent GC thrashing.
177
+ * - Collapses branching logic into a unified, linear, non-branching execution flow.
178
+ * - Optional high-resolution telemetry profiling is toggleable via `PROFILE_SHA256`.
179
+ *
180
+ * @param ibGib The ibGib object whose punctiliar hash is to be computed.
181
+ * @param salt An optional cryptographic salt string.
182
+ * @returns A promise resolving to the canonical punctiliar `gib` hash string in uppercase.
183
+ */
184
+ export async function sha256v1(ibGib, salt = "") {
185
+ if (PROFILE_SHA256) {
186
+ const start = globalThis.performance ? globalThis.performance.now() : Date.now();
187
+ const res = await sha256v1_Internal(ibGib, salt);
188
+ const duration = (globalThis.performance ? globalThis.performance.now() : Date.now()) - start;
189
+ sha256v1CallCount++;
190
+ sha256v1TotalDuration += duration;
191
+ if (sha256v1CallCount % 1000 === 0) {
192
+ console.log(`[sha256v1 Profile] Total Runs: ${sha256v1CallCount} | Average Duration: ${(sha256v1TotalDuration / sha256v1CallCount).toFixed(4)} ms`);
193
+ }
194
+ return res;
309
195
  }
310
- return hashFields(ibGib.ib, ibGib?.data, ibGib?.rel8ns); // conditional nav ibGib?.data and ibGib?.rel8ns
196
+ return sha256v1_Internal(ibGib, salt);
311
197
  }
312
198
  /**
313
199
  * I have one large-ish sha256 function for gibbing purposes
@@ -322,13 +208,11 @@ export async function hashToHexCopy(message) {
322
208
  try {
323
209
  const msgUint8 = new TextEncoder().encode(message);
324
210
  const buffer = await subtle.digest('SHA-256', msgUint8);
325
- const asArray = Array.from(new Uint8Array(buffer));
326
- return asArray.map(b => b.toString(16).padStart(2, '0')).join('');
211
+ return bufToHex(buffer);
327
212
  }
328
213
  catch (e) {
329
214
  console.error(extractErrorMsg(e.message));
330
215
  return undefined;
331
216
  }
332
217
  }
333
- ;
334
218
  //# sourceMappingURL=sha256v1.mjs.map