@andamio/core 0.1.0
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/LICENSE +190 -0
- package/README.md +83 -0
- package/dist/constants/index.d.mts +102 -0
- package/dist/constants/index.d.ts +102 -0
- package/dist/constants/index.js +123 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/index.mjs +108 -0
- package/dist/constants/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +432 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +381 -0
- package/dist/index.mjs.map +1 -0
- package/dist/utils/hashing/index.d.mts +276 -0
- package/dist/utils/hashing/index.d.ts +276 -0
- package/dist/utils/hashing/index.js +313 -0
- package/dist/utils/hashing/index.js.map +1 -0
- package/dist/utils/hashing/index.mjs +276 -0
- package/dist/utils/hashing/index.mjs.map +1 -0
- package/package.json +58 -0
- package/src/constants/cardano.ts +90 -0
- package/src/constants/index.ts +31 -0
- package/src/constants/policies.ts +122 -0
- package/src/index.ts +14 -0
- package/src/utils/hashing/commitment-hash.ts +223 -0
- package/src/utils/hashing/index.ts +43 -0
- package/src/utils/hashing/slt-hash.ts +193 -0
- package/src/utils/hashing/task-hash.ts +308 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var blake = require('blakejs');
|
|
4
|
+
|
|
5
|
+
function _interopNamespace(e) {
|
|
6
|
+
if (e && e.__esModule) return e;
|
|
7
|
+
var n = Object.create(null);
|
|
8
|
+
if (e) {
|
|
9
|
+
Object.keys(e).forEach(function (k) {
|
|
10
|
+
if (k !== 'default') {
|
|
11
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return e[k]; }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var blake__namespace = /*#__PURE__*/_interopNamespace(blake);
|
|
24
|
+
|
|
25
|
+
// src/utils/hashing/slt-hash.ts
|
|
26
|
+
var PLUTUS_CHUNK_SIZE = 64;
|
|
27
|
+
function computeSltHash(slts) {
|
|
28
|
+
const sltBytes = slts.map((slt) => new TextEncoder().encode(slt));
|
|
29
|
+
const cborData = encodeAsPlutusArray(sltBytes);
|
|
30
|
+
return blake__namespace.blake2bHex(cborData, void 0, 32);
|
|
31
|
+
}
|
|
32
|
+
var computeSltHashDefinite = computeSltHash;
|
|
33
|
+
function verifySltHash(slts, expectedHash) {
|
|
34
|
+
const computedHash = computeSltHash(slts);
|
|
35
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
function isValidSltHash(hash) {
|
|
38
|
+
if (hash.length !== 64) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
42
|
+
}
|
|
43
|
+
function encodeAsPlutusArray(items) {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
chunks.push(new Uint8Array([159]));
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
chunks.push(encodePlutusBuiltinByteString(item));
|
|
48
|
+
}
|
|
49
|
+
chunks.push(new Uint8Array([255]));
|
|
50
|
+
return concatUint8Arrays(chunks);
|
|
51
|
+
}
|
|
52
|
+
function encodePlutusBuiltinByteString(buffer) {
|
|
53
|
+
if (buffer.length <= PLUTUS_CHUNK_SIZE) {
|
|
54
|
+
return encodeCBORByteString(buffer);
|
|
55
|
+
}
|
|
56
|
+
const chunks = [];
|
|
57
|
+
chunks.push(new Uint8Array([95]));
|
|
58
|
+
for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE) {
|
|
59
|
+
const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE, buffer.length));
|
|
60
|
+
chunks.push(encodeCBORByteString(chunk));
|
|
61
|
+
}
|
|
62
|
+
chunks.push(new Uint8Array([255]));
|
|
63
|
+
return concatUint8Arrays(chunks);
|
|
64
|
+
}
|
|
65
|
+
function encodeCBORByteString(buffer) {
|
|
66
|
+
const len = buffer.length;
|
|
67
|
+
if (len <= 23) {
|
|
68
|
+
const result = new Uint8Array(1 + len);
|
|
69
|
+
result[0] = 64 + len;
|
|
70
|
+
result.set(buffer, 1);
|
|
71
|
+
return result;
|
|
72
|
+
} else if (len <= 255) {
|
|
73
|
+
const result = new Uint8Array(2 + len);
|
|
74
|
+
result[0] = 88;
|
|
75
|
+
result[1] = len;
|
|
76
|
+
result.set(buffer, 2);
|
|
77
|
+
return result;
|
|
78
|
+
} else if (len <= 65535) {
|
|
79
|
+
const result = new Uint8Array(3 + len);
|
|
80
|
+
result[0] = 89;
|
|
81
|
+
result[1] = len >> 8;
|
|
82
|
+
result[2] = len & 255;
|
|
83
|
+
result.set(buffer, 3);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Byte string too long for CBOR encoding");
|
|
87
|
+
}
|
|
88
|
+
function concatUint8Arrays(arrays) {
|
|
89
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
90
|
+
const result = new Uint8Array(totalLength);
|
|
91
|
+
let offset = 0;
|
|
92
|
+
for (const arr of arrays) {
|
|
93
|
+
result.set(arr, offset);
|
|
94
|
+
offset += arr.length;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
var PLUTUS_CHUNK_SIZE2 = 64;
|
|
99
|
+
function computeTaskHash(task) {
|
|
100
|
+
const cborData = encodeTaskAsPlutusData(task);
|
|
101
|
+
return blake__namespace.blake2bHex(cborData, void 0, 32);
|
|
102
|
+
}
|
|
103
|
+
function verifyTaskHash(task, expectedHash) {
|
|
104
|
+
const computedHash = computeTaskHash(task);
|
|
105
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
106
|
+
}
|
|
107
|
+
function isValidTaskHash(hash) {
|
|
108
|
+
if (hash.length !== 64) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
112
|
+
}
|
|
113
|
+
function debugTaskCBOR(task) {
|
|
114
|
+
const cborData = encodeTaskAsPlutusData(task);
|
|
115
|
+
return uint8ArrayToHex(cborData);
|
|
116
|
+
}
|
|
117
|
+
function encodeTaskAsPlutusData(task) {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
chunks.push(new Uint8Array([216, 121]));
|
|
120
|
+
chunks.push(new Uint8Array([159]));
|
|
121
|
+
chunks.push(encodePlutusBuiltinByteString2(new TextEncoder().encode(task.project_content)));
|
|
122
|
+
chunks.push(encodePlutusInteger(task.expiration_time));
|
|
123
|
+
chunks.push(encodePlutusInteger(task.lovelace_amount));
|
|
124
|
+
chunks.push(encodeNativeAssets(task.native_assets));
|
|
125
|
+
chunks.push(new Uint8Array([255]));
|
|
126
|
+
return concatUint8Arrays2(chunks);
|
|
127
|
+
}
|
|
128
|
+
function encodeNativeAssets(assets) {
|
|
129
|
+
if (assets.length === 0) {
|
|
130
|
+
return new Uint8Array([128]);
|
|
131
|
+
}
|
|
132
|
+
const chunks = [];
|
|
133
|
+
chunks.push(new Uint8Array([159]));
|
|
134
|
+
for (const [assetClass, quantity] of assets) {
|
|
135
|
+
chunks.push(new Uint8Array([130]));
|
|
136
|
+
chunks.push(encodePlutusBuiltinByteString2(new TextEncoder().encode(assetClass)));
|
|
137
|
+
chunks.push(encodePlutusInteger(quantity));
|
|
138
|
+
}
|
|
139
|
+
chunks.push(new Uint8Array([255]));
|
|
140
|
+
return concatUint8Arrays2(chunks);
|
|
141
|
+
}
|
|
142
|
+
function encodePlutusBuiltinByteString2(buffer) {
|
|
143
|
+
if (buffer.length <= PLUTUS_CHUNK_SIZE2) {
|
|
144
|
+
return encodeCBORByteString2(buffer);
|
|
145
|
+
}
|
|
146
|
+
const chunks = [];
|
|
147
|
+
chunks.push(new Uint8Array([95]));
|
|
148
|
+
for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE2) {
|
|
149
|
+
const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE2, buffer.length));
|
|
150
|
+
chunks.push(encodeCBORByteString2(chunk));
|
|
151
|
+
}
|
|
152
|
+
chunks.push(new Uint8Array([255]));
|
|
153
|
+
return concatUint8Arrays2(chunks);
|
|
154
|
+
}
|
|
155
|
+
function encodePlutusInteger(n) {
|
|
156
|
+
if (n >= 0) {
|
|
157
|
+
if (n <= 23) {
|
|
158
|
+
return new Uint8Array([n]);
|
|
159
|
+
} else if (n <= 255) {
|
|
160
|
+
return new Uint8Array([24, n]);
|
|
161
|
+
} else if (n <= 65535) {
|
|
162
|
+
return new Uint8Array([25, n >> 8, n & 255]);
|
|
163
|
+
} else if (n <= 4294967295) {
|
|
164
|
+
return new Uint8Array([26, n >> 24 & 255, n >> 16 & 255, n >> 8 & 255, n & 255]);
|
|
165
|
+
} else {
|
|
166
|
+
const buf = new Uint8Array(9);
|
|
167
|
+
buf[0] = 27;
|
|
168
|
+
const big = BigInt(n);
|
|
169
|
+
const view = new DataView(buf.buffer);
|
|
170
|
+
view.setBigUint64(1, big, false);
|
|
171
|
+
return buf;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const absVal = -1 - n;
|
|
175
|
+
if (absVal <= 23) {
|
|
176
|
+
return new Uint8Array([32 + absVal]);
|
|
177
|
+
} else if (absVal <= 255) {
|
|
178
|
+
return new Uint8Array([56, absVal]);
|
|
179
|
+
} else if (absVal <= 65535) {
|
|
180
|
+
return new Uint8Array([57, absVal >> 8, absVal & 255]);
|
|
181
|
+
} else if (absVal <= 4294967295) {
|
|
182
|
+
return new Uint8Array([58, absVal >> 24 & 255, absVal >> 16 & 255, absVal >> 8 & 255, absVal & 255]);
|
|
183
|
+
} else {
|
|
184
|
+
const buf = new Uint8Array(9);
|
|
185
|
+
buf[0] = 59;
|
|
186
|
+
const big = BigInt(absVal);
|
|
187
|
+
const view = new DataView(buf.buffer);
|
|
188
|
+
view.setBigUint64(1, big, false);
|
|
189
|
+
return buf;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function encodeCBORByteString2(buffer) {
|
|
194
|
+
const len = buffer.length;
|
|
195
|
+
if (len <= 23) {
|
|
196
|
+
const result = new Uint8Array(1 + len);
|
|
197
|
+
result[0] = 64 + len;
|
|
198
|
+
result.set(buffer, 1);
|
|
199
|
+
return result;
|
|
200
|
+
} else if (len <= 255) {
|
|
201
|
+
const result = new Uint8Array(2 + len);
|
|
202
|
+
result[0] = 88;
|
|
203
|
+
result[1] = len;
|
|
204
|
+
result.set(buffer, 2);
|
|
205
|
+
return result;
|
|
206
|
+
} else if (len <= 65535) {
|
|
207
|
+
const result = new Uint8Array(3 + len);
|
|
208
|
+
result[0] = 89;
|
|
209
|
+
result[1] = len >> 8;
|
|
210
|
+
result[2] = len & 255;
|
|
211
|
+
result.set(buffer, 3);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
throw new Error("Byte string too long for CBOR encoding");
|
|
215
|
+
}
|
|
216
|
+
function concatUint8Arrays2(arrays) {
|
|
217
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
218
|
+
const result = new Uint8Array(totalLength);
|
|
219
|
+
let offset = 0;
|
|
220
|
+
for (const arr of arrays) {
|
|
221
|
+
result.set(arr, offset);
|
|
222
|
+
offset += arr.length;
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
function uint8ArrayToHex(arr) {
|
|
227
|
+
return Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
228
|
+
}
|
|
229
|
+
function normalizeForHashing(value) {
|
|
230
|
+
if (value === null || value === void 0) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (typeof value === "string") {
|
|
234
|
+
return value.trim();
|
|
235
|
+
}
|
|
236
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
return value.map(normalizeForHashing);
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "object") {
|
|
243
|
+
const sorted = {};
|
|
244
|
+
const keys = Object.keys(value).sort();
|
|
245
|
+
for (const key of keys) {
|
|
246
|
+
const val = value[key];
|
|
247
|
+
if (val !== void 0) {
|
|
248
|
+
sorted[key] = normalizeForHashing(val);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return sorted;
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
function computeCommitmentHash(evidence) {
|
|
256
|
+
const normalized = normalizeForHashing(evidence);
|
|
257
|
+
const jsonString = JSON.stringify(normalized);
|
|
258
|
+
const bytes = new TextEncoder().encode(jsonString);
|
|
259
|
+
return blake__namespace.blake2bHex(bytes, void 0, 32);
|
|
260
|
+
}
|
|
261
|
+
function verifyCommitmentHash(evidence, expectedHash) {
|
|
262
|
+
const computedHash = computeCommitmentHash(evidence);
|
|
263
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
264
|
+
}
|
|
265
|
+
function isValidCommitmentHash(hash) {
|
|
266
|
+
if (typeof hash !== "string") {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (hash.length !== 64) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
273
|
+
}
|
|
274
|
+
function verifyEvidenceDetailed(evidence, onChainHash) {
|
|
275
|
+
if (!isValidCommitmentHash(onChainHash)) {
|
|
276
|
+
return {
|
|
277
|
+
isValid: false,
|
|
278
|
+
computedHash: "",
|
|
279
|
+
expectedHash: onChainHash,
|
|
280
|
+
message: `Invalid on-chain hash format: expected 64 hex characters, got "${onChainHash}"`
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const computedHash = computeCommitmentHash(evidence);
|
|
284
|
+
const isValid = computedHash.toLowerCase() === onChainHash.toLowerCase();
|
|
285
|
+
return {
|
|
286
|
+
isValid,
|
|
287
|
+
computedHash,
|
|
288
|
+
expectedHash: onChainHash.toLowerCase(),
|
|
289
|
+
message: isValid ? "Evidence matches on-chain commitment" : "Evidence does not match on-chain commitment - content may have been modified"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
var computeAssignmentInfoHash = computeCommitmentHash;
|
|
293
|
+
var verifyAssignmentInfoHash = verifyCommitmentHash;
|
|
294
|
+
var isValidAssignmentInfoHash = isValidCommitmentHash;
|
|
295
|
+
|
|
296
|
+
exports.computeAssignmentInfoHash = computeAssignmentInfoHash;
|
|
297
|
+
exports.computeCommitmentHash = computeCommitmentHash;
|
|
298
|
+
exports.computeSltHash = computeSltHash;
|
|
299
|
+
exports.computeSltHashDefinite = computeSltHashDefinite;
|
|
300
|
+
exports.computeTaskHash = computeTaskHash;
|
|
301
|
+
exports.debugTaskCBOR = debugTaskCBOR;
|
|
302
|
+
exports.isValidAssignmentInfoHash = isValidAssignmentInfoHash;
|
|
303
|
+
exports.isValidCommitmentHash = isValidCommitmentHash;
|
|
304
|
+
exports.isValidSltHash = isValidSltHash;
|
|
305
|
+
exports.isValidTaskHash = isValidTaskHash;
|
|
306
|
+
exports.normalizeForHashing = normalizeForHashing;
|
|
307
|
+
exports.verifyAssignmentInfoHash = verifyAssignmentInfoHash;
|
|
308
|
+
exports.verifyCommitmentHash = verifyCommitmentHash;
|
|
309
|
+
exports.verifyEvidenceDetailed = verifyEvidenceDetailed;
|
|
310
|
+
exports.verifySltHash = verifySltHash;
|
|
311
|
+
exports.verifyTaskHash = verifyTaskHash;
|
|
312
|
+
//# sourceMappingURL=index.js.map
|
|
313
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/hashing/slt-hash.ts","../../../src/utils/hashing/task-hash.ts","../../../src/utils/hashing/commitment-hash.ts"],"names":["blake","PLUTUS_CHUNK_SIZE","blake2","encodePlutusBuiltinByteString","concatUint8Arrays","encodeCBORByteString","blake3"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SLT Hash Utility\n *\n * Computes the module token name (hash) from a list of Student Learning Targets (SLTs).\n * This hash is used as the token name when minting module tokens on-chain.\n *\n * The algorithm matches the on-chain Plutus validator:\n * ```haskell\n * sltsToBbs MintModuleV2{slts} = blake2b_256 $ serialiseData $ toBuiltinData $ map stringToBuiltinByteString slts\n * ```\n *\n * Serialization format:\n * 1. Convert each SLT string to UTF-8 bytes\n * 2. Encode as CBOR indefinite-length array of byte strings\n * 3. Hash with Blake2b-256 (32 bytes / 256 bits)\n *\n * @module @andamio/core/hashing\n */\n\nimport * as blake from \"blakejs\";\n\n/**\n * Plutus chunk size for byte strings.\n * Strings longer than this are encoded as indefinite-length chunked byte strings.\n */\nconst PLUTUS_CHUNK_SIZE = 64;\n\n/**\n * Compute the module hash matching Plutus on-chain encoding.\n *\n * Plutus's `stringToBuiltinByteString` chunks byte strings at 64 bytes.\n * This function replicates that behavior:\n * - Strings <= 64 bytes: encoded as regular CBOR byte strings\n * - Strings > 64 bytes: encoded as indefinite-length chunked byte strings\n *\n * @param slts - Array of Student Learning Target strings\n * @returns 64-character hex string (256-bit Blake2b hash)\n *\n * @example\n * ```typescript\n * import { computeSltHash } from \"@andamio/core/hashing\";\n *\n * const slts = [\n * \"I can mint an access token.\",\n * \"I can complete an assignment to earn a credential.\"\n * ];\n *\n * const moduleHash = computeSltHash(slts);\n * // Returns: \"8dcbe1b925d87e6c547bbd8071c23a712db4c32751454b0948f8c846e9246b5c\"\n * ```\n */\nexport function computeSltHash(slts: string[]): string {\n const sltBytes = slts.map((slt) => new TextEncoder().encode(slt));\n const cborData = encodeAsPlutusArray(sltBytes);\n return blake.blake2bHex(cborData, undefined, 32);\n}\n\n/**\n * @deprecated Use `computeSltHash` instead. This alias is kept for backwards compatibility.\n */\nexport const computeSltHashDefinite = computeSltHash;\n\n/**\n * Verify that a given hash matches the computed hash for SLTs.\n *\n * @param slts - Array of Student Learning Target strings\n * @param expectedHash - The hash to verify (64-character hex string)\n * @returns true if the computed hash matches the expected hash\n */\nexport function verifySltHash(slts: string[], expectedHash: string): boolean {\n const computedHash = computeSltHash(slts);\n return computedHash.toLowerCase() === expectedHash.toLowerCase();\n}\n\n/**\n * Validate that a string is a valid SLT hash format.\n *\n * SLT hashes are 64-character hexadecimal strings (256-bit Blake2b hash).\n *\n * @param hash - String to validate\n * @returns true if the string is a valid SLT hash format\n */\nexport function isValidSltHash(hash: string): boolean {\n if (hash.length !== 64) {\n return false;\n }\n return /^[0-9a-fA-F]{64}$/.test(hash);\n}\n\n// =============================================================================\n// Plutus-Compatible Encoding (Internal)\n// =============================================================================\n\n/**\n * Encode an array of byte buffers matching Plutus serialization.\n *\n * Uses indefinite-length array with chunked byte strings for long values.\n *\n * @internal\n */\nfunction encodeAsPlutusArray(items: Uint8Array[]): Uint8Array {\n const chunks: Uint8Array[] = [];\n\n // Start indefinite array\n chunks.push(new Uint8Array([0x9f]));\n\n // Encode each item (with chunking for long strings)\n for (const item of items) {\n chunks.push(encodePlutusBuiltinByteString(item));\n }\n\n // End indefinite array\n chunks.push(new Uint8Array([0xff]));\n\n return concatUint8Arrays(chunks);\n}\n\n/**\n * Encode a byte buffer matching Plutus's stringToBuiltinByteString.\n *\n * - Strings <= 64 bytes: regular CBOR byte string\n * - Strings > 64 bytes: indefinite-length chunked byte string (64-byte chunks)\n *\n * @internal\n */\nfunction encodePlutusBuiltinByteString(buffer: Uint8Array): Uint8Array {\n if (buffer.length <= PLUTUS_CHUNK_SIZE) {\n // Short string: encode normally\n return encodeCBORByteString(buffer);\n }\n\n // Long string: use indefinite-length chunked encoding\n const chunks: Uint8Array[] = [];\n chunks.push(new Uint8Array([0x5f])); // Start indefinite byte string\n\n for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE) {\n const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE, buffer.length));\n chunks.push(encodeCBORByteString(chunk));\n }\n\n chunks.push(new Uint8Array([0xff])); // Break\n return concatUint8Arrays(chunks);\n}\n\n/**\n * Encode a byte buffer as a CBOR byte string (definite length).\n *\n * @internal\n */\nfunction encodeCBORByteString(buffer: Uint8Array): Uint8Array {\n const len = buffer.length;\n\n // CBOR byte string encoding (major type 2 = 0x40):\n // - 0-23 bytes: length inline (0x40 + len)\n // - 24-255 bytes: 0x58 + 1-byte length\n // - 256-65535 bytes: 0x59 + 2-byte length (big-endian)\n if (len <= 23) {\n const result = new Uint8Array(1 + len);\n result[0] = 0x40 + len;\n result.set(buffer, 1);\n return result;\n } else if (len <= 255) {\n const result = new Uint8Array(2 + len);\n result[0] = 0x58;\n result[1] = len;\n result.set(buffer, 2);\n return result;\n } else if (len <= 65535) {\n const result = new Uint8Array(3 + len);\n result[0] = 0x59;\n result[1] = len >> 8;\n result[2] = len & 0xff;\n result.set(buffer, 3);\n return result;\n }\n throw new Error(\"Byte string too long for CBOR encoding\");\n}\n\n/**\n * Concatenate multiple Uint8Arrays into one\n *\n * @internal\n */\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n","/**\n * Task Hash Utility\n *\n * Computes the task token name (hash) from task data.\n * This hash is used as the task_hash on-chain (content-addressed identifier).\n *\n * The algorithm matches the on-chain Plutus validator serialization.\n *\n * @module @andamio/core/hashing\n */\n\nimport * as blake from \"blakejs\";\n\n/**\n * Native asset in ListValue format: [policyId.tokenName, quantity]\n */\ntype NativeAsset = [string, number];\n\n/**\n * Task data structure matching the Atlas TX API ManageTasksTxRequest\n *\n * Fields must be arranged in this specific order for hashing:\n * 1. project_content (string, max 140 chars)\n * 2. expiration_time (number, Unix timestamp in milliseconds)\n * 3. lovelace_amount (number)\n * 4. native_assets (array of [asset_class, quantity] tuples)\n */\nexport interface TaskData {\n project_content: string;\n expiration_time: number;\n lovelace_amount: number;\n native_assets: NativeAsset[];\n}\n\n/**\n * Plutus chunk size for byte strings.\n */\nconst PLUTUS_CHUNK_SIZE = 64;\n\n/**\n * Compute the task hash (token name / task_hash) from task data.\n *\n * This produces the same hash as the on-chain Plutus validator, allowing\n * clients to pre-compute or verify task hashes.\n *\n * @param task - Task data object\n * @returns 64-character hex string (256-bit Blake2b hash)\n *\n * @example\n * ```typescript\n * import { computeTaskHash } from \"@andamio/core/hashing\";\n *\n * const task = {\n * project_content: \"Open Task #1\",\n * expiration_time: 1769027280000,\n * lovelace_amount: 15000000,\n * native_assets: []\n * };\n *\n * const taskHash = computeTaskHash(task);\n * // Returns the on-chain task_hash\n * ```\n */\nexport function computeTaskHash(task: TaskData): string {\n // Serialize task data matching Plutus format\n const cborData = encodeTaskAsPlutusData(task);\n\n // Hash with Blake2b-256\n return blake.blake2bHex(cborData, undefined, 32);\n}\n\n/**\n * Verify that a given hash matches the computed hash for a task.\n *\n * @param task - Task data object\n * @param expectedHash - The hash to verify (64-character hex string)\n * @returns true if the computed hash matches the expected hash\n */\nexport function verifyTaskHash(task: TaskData, expectedHash: string): boolean {\n const computedHash = computeTaskHash(task);\n return computedHash.toLowerCase() === expectedHash.toLowerCase();\n}\n\n/**\n * Validate that a string is a valid task hash format.\n *\n * Task hashes are 64-character hexadecimal strings (256-bit Blake2b hash).\n */\nexport function isValidTaskHash(hash: string): boolean {\n if (hash.length !== 64) {\n return false;\n }\n return /^[0-9a-fA-F]{64}$/.test(hash);\n}\n\n/**\n * Debug function to show the CBOR encoding of a task.\n * Useful for comparing against on-chain data.\n *\n * @param task - Task data object\n * @returns Hex string of the CBOR-encoded data (before hashing)\n */\nexport function debugTaskCBOR(task: TaskData): string {\n const cborData = encodeTaskAsPlutusData(task);\n return uint8ArrayToHex(cborData);\n}\n\n// =============================================================================\n// Plutus Data Encoding (Internal)\n// =============================================================================\n\n/**\n * Encode task data matching Plutus serialiseData $ toBuiltinData format.\n *\n * Plutus represents this as a constructor with fields in an INDEFINITE array:\n * Constr 0 [project_content, expiration_time, lovelace_amount, native_assets]\n *\n * IMPORTANT: Plutus uses indefinite-length arrays (0x9f...0xff) not definite (0x84).\n *\n * @internal\n */\nfunction encodeTaskAsPlutusData(task: TaskData): Uint8Array {\n const chunks: Uint8Array[] = [];\n\n // Plutus Constr 0 with indefinite array\n // CBOR tag 121 (0xd879) = Constr 0 in Plutus Data\n chunks.push(new Uint8Array([0xd8, 0x79])); // Tag 121 (Constr 0)\n chunks.push(new Uint8Array([0x9f])); // Start indefinite array\n\n // Field 1: project_content as BuiltinByteString\n chunks.push(encodePlutusBuiltinByteString(new TextEncoder().encode(task.project_content)));\n\n // Field 2: expiration_time as Integer\n chunks.push(encodePlutusInteger(task.expiration_time));\n\n // Field 3: lovelace_amount as Integer\n chunks.push(encodePlutusInteger(task.lovelace_amount));\n\n // Field 4: native_assets as List of pairs\n chunks.push(encodeNativeAssets(task.native_assets));\n\n // End indefinite array\n chunks.push(new Uint8Array([0xff])); // Break\n\n return concatUint8Arrays(chunks);\n}\n\n/**\n * Encode native assets as Plutus List of (AssetClass, Integer) pairs.\n *\n * Each asset is a pair: (policyId.tokenName, quantity)\n * In Plutus, this is: List [(ByteString, Integer)]\n *\n * @internal\n */\nfunction encodeNativeAssets(assets: NativeAsset[]): Uint8Array {\n if (assets.length === 0) {\n // Empty list: definite-length array of 0 elements\n return new Uint8Array([0x80]); // Array(0)\n }\n\n const chunks: Uint8Array[] = [];\n\n // Start indefinite array\n chunks.push(new Uint8Array([0x9f]));\n\n for (const [assetClass, quantity] of assets) {\n // Each asset is a 2-element array: [bytestring, integer]\n chunks.push(new Uint8Array([0x82])); // Array of 2 elements\n chunks.push(encodePlutusBuiltinByteString(new TextEncoder().encode(assetClass)));\n chunks.push(encodePlutusInteger(quantity));\n }\n\n // End indefinite array\n chunks.push(new Uint8Array([0xff]));\n\n return concatUint8Arrays(chunks);\n}\n\n/**\n * Encode a byte buffer matching Plutus's stringToBuiltinByteString.\n *\n * - Strings <= 64 bytes: regular CBOR byte string\n * - Strings > 64 bytes: indefinite-length chunked byte string (64-byte chunks)\n *\n * @internal\n */\nfunction encodePlutusBuiltinByteString(buffer: Uint8Array): Uint8Array {\n if (buffer.length <= PLUTUS_CHUNK_SIZE) {\n return encodeCBORByteString(buffer);\n }\n\n // Long string: use indefinite-length chunked encoding\n const chunks: Uint8Array[] = [];\n chunks.push(new Uint8Array([0x5f])); // Start indefinite byte string\n\n for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE) {\n const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE, buffer.length));\n chunks.push(encodeCBORByteString(chunk));\n }\n\n chunks.push(new Uint8Array([0xff])); // Break\n return concatUint8Arrays(chunks);\n}\n\n/**\n * Encode a number as a CBOR integer (Plutus Integer).\n *\n * @internal\n */\nfunction encodePlutusInteger(n: number): Uint8Array {\n // CBOR integer encoding (major type 0 for positive, 1 for negative)\n if (n >= 0) {\n if (n <= 23) {\n return new Uint8Array([n]);\n } else if (n <= 0xff) {\n return new Uint8Array([0x18, n]);\n } else if (n <= 0xffff) {\n return new Uint8Array([0x19, n >> 8, n & 0xff]);\n } else if (n <= 0xffffffff) {\n return new Uint8Array([0x1a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]);\n } else {\n // 64-bit integer - use BigInt for precision\n const buf = new Uint8Array(9);\n buf[0] = 0x1b;\n const big = BigInt(n);\n const view = new DataView(buf.buffer);\n view.setBigUint64(1, big, false); // false = big-endian\n return buf;\n }\n } else {\n // Negative integers: major type 1, encode (-1 - n)\n const absVal = -1 - n;\n if (absVal <= 23) {\n return new Uint8Array([0x20 + absVal]);\n } else if (absVal <= 0xff) {\n return new Uint8Array([0x38, absVal]);\n } else if (absVal <= 0xffff) {\n return new Uint8Array([0x39, absVal >> 8, absVal & 0xff]);\n } else if (absVal <= 0xffffffff) {\n return new Uint8Array([0x3a, (absVal >> 24) & 0xff, (absVal >> 16) & 0xff, (absVal >> 8) & 0xff, absVal & 0xff]);\n } else {\n const buf = new Uint8Array(9);\n buf[0] = 0x3b;\n const big = BigInt(absVal);\n const view = new DataView(buf.buffer);\n view.setBigUint64(1, big, false);\n return buf;\n }\n }\n}\n\n/**\n * Encode a byte buffer as a CBOR byte string (definite length).\n *\n * @internal\n */\nfunction encodeCBORByteString(buffer: Uint8Array): Uint8Array {\n const len = buffer.length;\n\n if (len <= 23) {\n const result = new Uint8Array(1 + len);\n result[0] = 0x40 + len;\n result.set(buffer, 1);\n return result;\n } else if (len <= 255) {\n const result = new Uint8Array(2 + len);\n result[0] = 0x58;\n result[1] = len;\n result.set(buffer, 2);\n return result;\n } else if (len <= 65535) {\n const result = new Uint8Array(3 + len);\n result[0] = 0x59;\n result[1] = len >> 8;\n result[2] = len & 0xff;\n result.set(buffer, 3);\n return result;\n }\n throw new Error(\"Byte string too long for CBOR encoding\");\n}\n\n/**\n * Concatenate multiple Uint8Arrays into one\n *\n * @internal\n */\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\n/**\n * Convert Uint8Array to hex string\n *\n * @internal\n */\nfunction uint8ArrayToHex(arr: Uint8Array): string {\n return Array.from(arr)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n","/**\n * Commitment Hash Utility\n *\n * Functions for computing and verifying hashes of commitment evidence.\n * The hash is stored on-chain as `commitment_hash` in the course state datum,\n * while the full evidence (Tiptap JSON document) is stored in the database.\n *\n * This provides:\n * - Compact on-chain storage (64-char hex hash vs full JSON)\n * - Tamper-evidence (can verify DB content matches on-chain commitment)\n * - Privacy (evidence details not exposed on-chain)\n *\n * @module @andamio/core/hashing\n */\n\nimport * as blake from \"blakejs\";\n\n/**\n * Tiptap document structure (simplified)\n * The actual structure can be more complex with nested content\n */\nexport type TiptapDoc = {\n type: \"doc\";\n content?: TiptapNode[];\n [key: string]: unknown;\n};\n\nexport type TiptapNode = {\n type: string;\n content?: TiptapNode[];\n text?: string;\n marks?: TiptapMark[];\n attrs?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type TiptapMark = {\n type: string;\n attrs?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\n/**\n * Normalizes a value for consistent hashing.\n *\n * Normalization rules:\n * - Objects: Sort keys alphabetically, recursively normalize values\n * - Arrays: Preserve order, recursively normalize items\n * - Strings: Trim whitespace\n * - Numbers/Booleans/null: Keep as-is\n * - undefined: Convert to null\n *\n * @param value - Any JSON-serializable value\n * @returns Normalized value\n */\nexport function normalizeForHashing(value: unknown): unknown {\n if (value === null || value === undefined) {\n return null;\n }\n\n if (typeof value === \"string\") {\n return value.trim();\n }\n\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(normalizeForHashing);\n }\n\n if (typeof value === \"object\") {\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(value as Record<string, unknown>).sort();\n for (const key of keys) {\n const val = (value as Record<string, unknown>)[key];\n if (val !== undefined) {\n sorted[key] = normalizeForHashing(val);\n }\n }\n return sorted;\n }\n\n return value;\n}\n\n/**\n * Computes the commitment hash from evidence content.\n *\n * The hash is computed as:\n * 1. Normalize the evidence (sort keys, trim strings, etc.)\n * 2. Serialize to JSON string (deterministic due to normalization)\n * 3. Apply Blake2b-256 hash\n *\n * @param evidence - The evidence content (Tiptap JSON document or any JSON-serializable data)\n * @returns 64-character lowercase hex string (Blake2b-256 hash)\n *\n * @example\n * ```typescript\n * import { computeCommitmentHash } from \"@andamio/core/hashing\";\n *\n * const evidence = {\n * type: \"doc\",\n * content: [\n * { type: \"paragraph\", content: [{ type: \"text\", text: \"My submission\" }] }\n * ]\n * };\n *\n * const hash = computeCommitmentHash(evidence);\n * // Use this hash as commitment_hash in the transaction\n * ```\n */\nexport function computeCommitmentHash(evidence: unknown): string {\n const normalized = normalizeForHashing(evidence);\n const jsonString = JSON.stringify(normalized);\n const bytes = new TextEncoder().encode(jsonString);\n return blake.blake2bHex(bytes, undefined, 32);\n}\n\n/**\n * Verifies that evidence content matches an expected hash.\n *\n * Use this to verify that database evidence matches the on-chain commitment.\n *\n * @param evidence - The evidence content to verify\n * @param expectedHash - The expected hash (from on-chain data)\n * @returns True if the evidence produces the expected hash\n */\nexport function verifyCommitmentHash(\n evidence: unknown,\n expectedHash: string\n): boolean {\n const computedHash = computeCommitmentHash(evidence);\n return computedHash.toLowerCase() === expectedHash.toLowerCase();\n}\n\n/**\n * Validates that a string is a valid commitment hash format.\n *\n * A valid hash is a 64-character hexadecimal string (Blake2b-256 output).\n *\n * @param hash - The string to validate\n * @returns True if the string is a valid hash format\n */\nexport function isValidCommitmentHash(hash: string): boolean {\n if (typeof hash !== \"string\") {\n return false;\n }\n if (hash.length !== 64) {\n return false;\n }\n return /^[0-9a-fA-F]{64}$/.test(hash);\n}\n\n/**\n * Result of comparing evidence with an on-chain hash\n */\nexport type EvidenceVerificationResult = {\n /** Whether the evidence matches the on-chain hash */\n isValid: boolean;\n /** The hash computed from the evidence */\n computedHash: string;\n /** The expected hash (from on-chain) */\n expectedHash: string;\n /** Human-readable status message */\n message: string;\n};\n\n/**\n * Performs a detailed verification of evidence against an on-chain hash.\n *\n * Returns a detailed result object with both hashes and a status message,\n * useful for debugging and user feedback.\n *\n * @param evidence - The evidence content to verify\n * @param onChainHash - The hash from on-chain data\n * @returns Detailed verification result\n */\nexport function verifyEvidenceDetailed(\n evidence: unknown,\n onChainHash: string\n): EvidenceVerificationResult {\n if (!isValidCommitmentHash(onChainHash)) {\n return {\n isValid: false,\n computedHash: \"\",\n expectedHash: onChainHash,\n message: `Invalid on-chain hash format: expected 64 hex characters, got \"${onChainHash}\"`,\n };\n }\n\n const computedHash = computeCommitmentHash(evidence);\n const isValid = computedHash.toLowerCase() === onChainHash.toLowerCase();\n\n return {\n isValid,\n computedHash,\n expectedHash: onChainHash.toLowerCase(),\n message: isValid\n ? \"Evidence matches on-chain commitment\"\n : \"Evidence does not match on-chain commitment - content may have been modified\",\n };\n}\n\n// =============================================================================\n// Backwards Compatibility Aliases (deprecated)\n// =============================================================================\n\n/**\n * @deprecated Use `computeCommitmentHash` instead.\n */\nexport const computeAssignmentInfoHash = computeCommitmentHash;\n\n/**\n * @deprecated Use `verifyCommitmentHash` instead.\n */\nexport const verifyAssignmentInfoHash = verifyCommitmentHash;\n\n/**\n * @deprecated Use `isValidCommitmentHash` instead.\n */\nexport const isValidAssignmentInfoHash = isValidCommitmentHash;\n"]}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import * as blake from 'blakejs';
|
|
2
|
+
|
|
3
|
+
// src/utils/hashing/slt-hash.ts
|
|
4
|
+
var PLUTUS_CHUNK_SIZE = 64;
|
|
5
|
+
function computeSltHash(slts) {
|
|
6
|
+
const sltBytes = slts.map((slt) => new TextEncoder().encode(slt));
|
|
7
|
+
const cborData = encodeAsPlutusArray(sltBytes);
|
|
8
|
+
return blake.blake2bHex(cborData, void 0, 32);
|
|
9
|
+
}
|
|
10
|
+
var computeSltHashDefinite = computeSltHash;
|
|
11
|
+
function verifySltHash(slts, expectedHash) {
|
|
12
|
+
const computedHash = computeSltHash(slts);
|
|
13
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function isValidSltHash(hash) {
|
|
16
|
+
if (hash.length !== 64) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
20
|
+
}
|
|
21
|
+
function encodeAsPlutusArray(items) {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
chunks.push(new Uint8Array([159]));
|
|
24
|
+
for (const item of items) {
|
|
25
|
+
chunks.push(encodePlutusBuiltinByteString(item));
|
|
26
|
+
}
|
|
27
|
+
chunks.push(new Uint8Array([255]));
|
|
28
|
+
return concatUint8Arrays(chunks);
|
|
29
|
+
}
|
|
30
|
+
function encodePlutusBuiltinByteString(buffer) {
|
|
31
|
+
if (buffer.length <= PLUTUS_CHUNK_SIZE) {
|
|
32
|
+
return encodeCBORByteString(buffer);
|
|
33
|
+
}
|
|
34
|
+
const chunks = [];
|
|
35
|
+
chunks.push(new Uint8Array([95]));
|
|
36
|
+
for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE) {
|
|
37
|
+
const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE, buffer.length));
|
|
38
|
+
chunks.push(encodeCBORByteString(chunk));
|
|
39
|
+
}
|
|
40
|
+
chunks.push(new Uint8Array([255]));
|
|
41
|
+
return concatUint8Arrays(chunks);
|
|
42
|
+
}
|
|
43
|
+
function encodeCBORByteString(buffer) {
|
|
44
|
+
const len = buffer.length;
|
|
45
|
+
if (len <= 23) {
|
|
46
|
+
const result = new Uint8Array(1 + len);
|
|
47
|
+
result[0] = 64 + len;
|
|
48
|
+
result.set(buffer, 1);
|
|
49
|
+
return result;
|
|
50
|
+
} else if (len <= 255) {
|
|
51
|
+
const result = new Uint8Array(2 + len);
|
|
52
|
+
result[0] = 88;
|
|
53
|
+
result[1] = len;
|
|
54
|
+
result.set(buffer, 2);
|
|
55
|
+
return result;
|
|
56
|
+
} else if (len <= 65535) {
|
|
57
|
+
const result = new Uint8Array(3 + len);
|
|
58
|
+
result[0] = 89;
|
|
59
|
+
result[1] = len >> 8;
|
|
60
|
+
result[2] = len & 255;
|
|
61
|
+
result.set(buffer, 3);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Byte string too long for CBOR encoding");
|
|
65
|
+
}
|
|
66
|
+
function concatUint8Arrays(arrays) {
|
|
67
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
68
|
+
const result = new Uint8Array(totalLength);
|
|
69
|
+
let offset = 0;
|
|
70
|
+
for (const arr of arrays) {
|
|
71
|
+
result.set(arr, offset);
|
|
72
|
+
offset += arr.length;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
var PLUTUS_CHUNK_SIZE2 = 64;
|
|
77
|
+
function computeTaskHash(task) {
|
|
78
|
+
const cborData = encodeTaskAsPlutusData(task);
|
|
79
|
+
return blake.blake2bHex(cborData, void 0, 32);
|
|
80
|
+
}
|
|
81
|
+
function verifyTaskHash(task, expectedHash) {
|
|
82
|
+
const computedHash = computeTaskHash(task);
|
|
83
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
function isValidTaskHash(hash) {
|
|
86
|
+
if (hash.length !== 64) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
90
|
+
}
|
|
91
|
+
function debugTaskCBOR(task) {
|
|
92
|
+
const cborData = encodeTaskAsPlutusData(task);
|
|
93
|
+
return uint8ArrayToHex(cborData);
|
|
94
|
+
}
|
|
95
|
+
function encodeTaskAsPlutusData(task) {
|
|
96
|
+
const chunks = [];
|
|
97
|
+
chunks.push(new Uint8Array([216, 121]));
|
|
98
|
+
chunks.push(new Uint8Array([159]));
|
|
99
|
+
chunks.push(encodePlutusBuiltinByteString2(new TextEncoder().encode(task.project_content)));
|
|
100
|
+
chunks.push(encodePlutusInteger(task.expiration_time));
|
|
101
|
+
chunks.push(encodePlutusInteger(task.lovelace_amount));
|
|
102
|
+
chunks.push(encodeNativeAssets(task.native_assets));
|
|
103
|
+
chunks.push(new Uint8Array([255]));
|
|
104
|
+
return concatUint8Arrays2(chunks);
|
|
105
|
+
}
|
|
106
|
+
function encodeNativeAssets(assets) {
|
|
107
|
+
if (assets.length === 0) {
|
|
108
|
+
return new Uint8Array([128]);
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
chunks.push(new Uint8Array([159]));
|
|
112
|
+
for (const [assetClass, quantity] of assets) {
|
|
113
|
+
chunks.push(new Uint8Array([130]));
|
|
114
|
+
chunks.push(encodePlutusBuiltinByteString2(new TextEncoder().encode(assetClass)));
|
|
115
|
+
chunks.push(encodePlutusInteger(quantity));
|
|
116
|
+
}
|
|
117
|
+
chunks.push(new Uint8Array([255]));
|
|
118
|
+
return concatUint8Arrays2(chunks);
|
|
119
|
+
}
|
|
120
|
+
function encodePlutusBuiltinByteString2(buffer) {
|
|
121
|
+
if (buffer.length <= PLUTUS_CHUNK_SIZE2) {
|
|
122
|
+
return encodeCBORByteString2(buffer);
|
|
123
|
+
}
|
|
124
|
+
const chunks = [];
|
|
125
|
+
chunks.push(new Uint8Array([95]));
|
|
126
|
+
for (let i = 0; i < buffer.length; i += PLUTUS_CHUNK_SIZE2) {
|
|
127
|
+
const chunk = buffer.subarray(i, Math.min(i + PLUTUS_CHUNK_SIZE2, buffer.length));
|
|
128
|
+
chunks.push(encodeCBORByteString2(chunk));
|
|
129
|
+
}
|
|
130
|
+
chunks.push(new Uint8Array([255]));
|
|
131
|
+
return concatUint8Arrays2(chunks);
|
|
132
|
+
}
|
|
133
|
+
function encodePlutusInteger(n) {
|
|
134
|
+
if (n >= 0) {
|
|
135
|
+
if (n <= 23) {
|
|
136
|
+
return new Uint8Array([n]);
|
|
137
|
+
} else if (n <= 255) {
|
|
138
|
+
return new Uint8Array([24, n]);
|
|
139
|
+
} else if (n <= 65535) {
|
|
140
|
+
return new Uint8Array([25, n >> 8, n & 255]);
|
|
141
|
+
} else if (n <= 4294967295) {
|
|
142
|
+
return new Uint8Array([26, n >> 24 & 255, n >> 16 & 255, n >> 8 & 255, n & 255]);
|
|
143
|
+
} else {
|
|
144
|
+
const buf = new Uint8Array(9);
|
|
145
|
+
buf[0] = 27;
|
|
146
|
+
const big = BigInt(n);
|
|
147
|
+
const view = new DataView(buf.buffer);
|
|
148
|
+
view.setBigUint64(1, big, false);
|
|
149
|
+
return buf;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
const absVal = -1 - n;
|
|
153
|
+
if (absVal <= 23) {
|
|
154
|
+
return new Uint8Array([32 + absVal]);
|
|
155
|
+
} else if (absVal <= 255) {
|
|
156
|
+
return new Uint8Array([56, absVal]);
|
|
157
|
+
} else if (absVal <= 65535) {
|
|
158
|
+
return new Uint8Array([57, absVal >> 8, absVal & 255]);
|
|
159
|
+
} else if (absVal <= 4294967295) {
|
|
160
|
+
return new Uint8Array([58, absVal >> 24 & 255, absVal >> 16 & 255, absVal >> 8 & 255, absVal & 255]);
|
|
161
|
+
} else {
|
|
162
|
+
const buf = new Uint8Array(9);
|
|
163
|
+
buf[0] = 59;
|
|
164
|
+
const big = BigInt(absVal);
|
|
165
|
+
const view = new DataView(buf.buffer);
|
|
166
|
+
view.setBigUint64(1, big, false);
|
|
167
|
+
return buf;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function encodeCBORByteString2(buffer) {
|
|
172
|
+
const len = buffer.length;
|
|
173
|
+
if (len <= 23) {
|
|
174
|
+
const result = new Uint8Array(1 + len);
|
|
175
|
+
result[0] = 64 + len;
|
|
176
|
+
result.set(buffer, 1);
|
|
177
|
+
return result;
|
|
178
|
+
} else if (len <= 255) {
|
|
179
|
+
const result = new Uint8Array(2 + len);
|
|
180
|
+
result[0] = 88;
|
|
181
|
+
result[1] = len;
|
|
182
|
+
result.set(buffer, 2);
|
|
183
|
+
return result;
|
|
184
|
+
} else if (len <= 65535) {
|
|
185
|
+
const result = new Uint8Array(3 + len);
|
|
186
|
+
result[0] = 89;
|
|
187
|
+
result[1] = len >> 8;
|
|
188
|
+
result[2] = len & 255;
|
|
189
|
+
result.set(buffer, 3);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
throw new Error("Byte string too long for CBOR encoding");
|
|
193
|
+
}
|
|
194
|
+
function concatUint8Arrays2(arrays) {
|
|
195
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
196
|
+
const result = new Uint8Array(totalLength);
|
|
197
|
+
let offset = 0;
|
|
198
|
+
for (const arr of arrays) {
|
|
199
|
+
result.set(arr, offset);
|
|
200
|
+
offset += arr.length;
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function uint8ArrayToHex(arr) {
|
|
205
|
+
return Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
206
|
+
}
|
|
207
|
+
function normalizeForHashing(value) {
|
|
208
|
+
if (value === null || value === void 0) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (typeof value === "string") {
|
|
212
|
+
return value.trim();
|
|
213
|
+
}
|
|
214
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
return value.map(normalizeForHashing);
|
|
219
|
+
}
|
|
220
|
+
if (typeof value === "object") {
|
|
221
|
+
const sorted = {};
|
|
222
|
+
const keys = Object.keys(value).sort();
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
const val = value[key];
|
|
225
|
+
if (val !== void 0) {
|
|
226
|
+
sorted[key] = normalizeForHashing(val);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return sorted;
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
function computeCommitmentHash(evidence) {
|
|
234
|
+
const normalized = normalizeForHashing(evidence);
|
|
235
|
+
const jsonString = JSON.stringify(normalized);
|
|
236
|
+
const bytes = new TextEncoder().encode(jsonString);
|
|
237
|
+
return blake.blake2bHex(bytes, void 0, 32);
|
|
238
|
+
}
|
|
239
|
+
function verifyCommitmentHash(evidence, expectedHash) {
|
|
240
|
+
const computedHash = computeCommitmentHash(evidence);
|
|
241
|
+
return computedHash.toLowerCase() === expectedHash.toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
function isValidCommitmentHash(hash) {
|
|
244
|
+
if (typeof hash !== "string") {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (hash.length !== 64) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
return /^[0-9a-fA-F]{64}$/.test(hash);
|
|
251
|
+
}
|
|
252
|
+
function verifyEvidenceDetailed(evidence, onChainHash) {
|
|
253
|
+
if (!isValidCommitmentHash(onChainHash)) {
|
|
254
|
+
return {
|
|
255
|
+
isValid: false,
|
|
256
|
+
computedHash: "",
|
|
257
|
+
expectedHash: onChainHash,
|
|
258
|
+
message: `Invalid on-chain hash format: expected 64 hex characters, got "${onChainHash}"`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const computedHash = computeCommitmentHash(evidence);
|
|
262
|
+
const isValid = computedHash.toLowerCase() === onChainHash.toLowerCase();
|
|
263
|
+
return {
|
|
264
|
+
isValid,
|
|
265
|
+
computedHash,
|
|
266
|
+
expectedHash: onChainHash.toLowerCase(),
|
|
267
|
+
message: isValid ? "Evidence matches on-chain commitment" : "Evidence does not match on-chain commitment - content may have been modified"
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
var computeAssignmentInfoHash = computeCommitmentHash;
|
|
271
|
+
var verifyAssignmentInfoHash = verifyCommitmentHash;
|
|
272
|
+
var isValidAssignmentInfoHash = isValidCommitmentHash;
|
|
273
|
+
|
|
274
|
+
export { computeAssignmentInfoHash, computeCommitmentHash, computeSltHash, computeSltHashDefinite, computeTaskHash, debugTaskCBOR, isValidAssignmentInfoHash, isValidCommitmentHash, isValidSltHash, isValidTaskHash, normalizeForHashing, verifyAssignmentInfoHash, verifyCommitmentHash, verifyEvidenceDetailed, verifySltHash, verifyTaskHash };
|
|
275
|
+
//# sourceMappingURL=index.mjs.map
|
|
276
|
+
//# sourceMappingURL=index.mjs.map
|