@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 +20 -0
- package/README.md +13 -0
- package/dist/V1/sha256v1.d.mts +52 -7
- package/dist/V1/sha256v1.d.mts.map +1 -1
- package/dist/V1/sha256v1.mjs +175 -291
- package/dist/V1/sha256v1.mjs.map +1 -1
- package/dist/helper.d.mts +2 -21
- package/dist/helper.d.mts.map +1 -1
- package/dist/helper.mjs +2 -43
- package/dist/helper.mjs.map +1 -1
- package/package.json +7 -29
- package/src/V1/sha256v1.mts +183 -279
- package/src/V1/sha256v1.respec.mts +45 -2
- package/src/helper.mts +2 -46
- package/.vscode/launch.json +0 -24
- package/.vscode/settings.json +0 -37
- package/.vscode/tasks.json +0 -37
- package/.vscode/ts-gib-snippets.code-snippets +0 -293
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_.
|
package/dist/V1/sha256v1.d.mts
CHANGED
|
@@ -1,16 +1,61 @@
|
|
|
1
1
|
import { IbGib_V1 } from "./types.mjs";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
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;
|
|
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"}
|
package/dist/V1/sha256v1.mjs
CHANGED
|
@@ -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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
34
|
+
* @param value The value to normalize.
|
|
35
|
+
* @returns The normalized value.
|
|
14
36
|
*/
|
|
15
|
-
export function
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
const hashAsBuffer = await subtle.digest('SHA-256', tohashUint8Array);
|
|
80
|
+
return bufToHex(hashAsBuffer);
|
|
130
81
|
}
|
|
131
|
-
export
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
109
|
+
else if (typeof data === 'object') {
|
|
110
|
+
hasData = Object.keys((data) || {}).length > 0;
|
|
156
111
|
}
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
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
|
-
|
|
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
|