@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/src/V1/sha256v1.mts
CHANGED
|
@@ -6,303 +6,208 @@ let crypto: any = globalThis.crypto;
|
|
|
6
6
|
let { subtle } = crypto;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
25
|
+
return hex;
|
|
124
26
|
}
|
|
125
27
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
55
|
+
// Handle Objects (plain objects)
|
|
56
|
+
const normalizedObject: { [key: string]: any } = {};
|
|
57
|
+
const sortedKeys = Object.keys(value).sort();
|
|
189
58
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
91
|
+
export const PROFILE_SHA256 = false; // Toggle profiling
|
|
92
|
+
let sha256v1CallCount = 0;
|
|
93
|
+
let sha256v1TotalDuration = 0;
|
|
215
94
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 };
|
package/.vscode/launch.json
DELETED
|
@@ -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
|
-
}
|