@fedify/vocab-runtime 2.4.0-dev.1634 → 2.4.0-dev.1655
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/deno.json +1 -1
- package/dist/mod.cjs +146 -3
- package/dist/mod.d.cts +82 -1
- package/dist/mod.d.ts +82 -1
- package/dist/mod.js +141 -4
- package/dist/tests/decimal.test.cjs +5 -4
- package/dist/tests/decimal.test.mjs +5 -4
- package/dist/tests/digest-BJlumPNJ.mjs +140 -0
- package/dist/tests/digest-DLwUyHCK.cjs +175 -0
- package/dist/tests/digest.test.cjs +103 -0
- package/dist/tests/digest.test.d.cts +1 -0
- package/dist/tests/digest.test.d.mts +1 -0
- package/dist/tests/digest.test.mjs +103 -0
- package/dist/tests/{docloader-CTd4XwqK.cjs → docloader-DVVWmXLI.cjs} +1 -1
- package/dist/tests/{docloader-CNzoXrLM.mjs → docloader-HbEvzi-t.mjs} +1 -1
- package/dist/tests/docloader.test.cjs +2 -2
- package/dist/tests/docloader.test.mjs +2 -2
- package/dist/tests/{key-_wXwomh_.cjs → key-BcOWDLMm.cjs} +1 -1
- package/dist/tests/{key-CDGDH_vC.mjs → key-Drbw_2tw.mjs} +1 -1
- package/dist/tests/key.test.cjs +2 -2
- package/dist/tests/key.test.mjs +2 -2
- package/dist/tests/langstr.test.cjs +1 -1
- package/dist/tests/langstr.test.mjs +1 -1
- package/dist/tests/multibase/multibase.test.cjs +1 -1
- package/dist/tests/multibase/multibase.test.mjs +1 -1
- package/dist/tests/{multibase-B4bvakyA.mjs → multibase-Cluwl8OM.mjs} +12 -1
- package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-DcDQDNnI.cjs} +17 -0
- package/dist/tests/{request-BWDludWb.mjs → request-B6s_F7bi.mjs} +1 -1
- package/dist/tests/{request-Zpjq9m-W.cjs → request-DvEyU5Zf.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/package.json +1 -1
- package/src/digest.test.ts +220 -0
- package/src/digest.ts +229 -0
- package/src/mod.ts +10 -0
- /package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-BOQHfJDr.cjs} +0 -0
- /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DDLc4833.mjs} +0 -0
|
@@ -303,5 +303,16 @@ function encoding(nameOrCode) {
|
|
|
303
303
|
else if (Object.prototype.hasOwnProperty.call(codes, nameOrCode)) return codes[nameOrCode];
|
|
304
304
|
else throw new Error(`Unsupported encoding: ${nameOrCode}`);
|
|
305
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Get encoding multibase from data
|
|
308
|
+
*
|
|
309
|
+
* @param {string|Uint8Array} data
|
|
310
|
+
* @returns {Base}
|
|
311
|
+
* @throws {Error} Will throw if the encoding is not supported
|
|
312
|
+
*/
|
|
313
|
+
function encodingFromBaseData(data) {
|
|
314
|
+
if (data instanceof Uint8Array) data = decodeText(data);
|
|
315
|
+
return encoding(data[0]);
|
|
316
|
+
}
|
|
306
317
|
//#endregion
|
|
307
|
-
export {
|
|
318
|
+
export { names as a, codes as i, encodeMultibase as n, decodeText as o, encodingFromBaseData as r, encodeText as s, decodeMultibase as t };
|
|
@@ -305,6 +305,17 @@ function encoding(nameOrCode) {
|
|
|
305
305
|
else if (Object.prototype.hasOwnProperty.call(codes, nameOrCode)) return codes[nameOrCode];
|
|
306
306
|
else throw new Error(`Unsupported encoding: ${nameOrCode}`);
|
|
307
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Get encoding multibase from data
|
|
310
|
+
*
|
|
311
|
+
* @param {string|Uint8Array} data
|
|
312
|
+
* @returns {Base}
|
|
313
|
+
* @throws {Error} Will throw if the encoding is not supported
|
|
314
|
+
*/
|
|
315
|
+
function encodingFromBaseData(data) {
|
|
316
|
+
if (data instanceof Uint8Array) data = decodeText(data);
|
|
317
|
+
return encoding(data[0]);
|
|
318
|
+
}
|
|
308
319
|
//#endregion
|
|
309
320
|
Object.defineProperty(exports, "codes", {
|
|
310
321
|
enumerable: true,
|
|
@@ -336,6 +347,12 @@ Object.defineProperty(exports, "encodeText", {
|
|
|
336
347
|
return encodeText;
|
|
337
348
|
}
|
|
338
349
|
});
|
|
350
|
+
Object.defineProperty(exports, "encodingFromBaseData", {
|
|
351
|
+
enumerable: true,
|
|
352
|
+
get: function() {
|
|
353
|
+
return encodingFromBaseData;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
339
356
|
Object.defineProperty(exports, "names", {
|
|
340
357
|
enumerable: true,
|
|
341
358
|
get: function() {
|
|
@@ -3,7 +3,7 @@ let node_process = require("node:process");
|
|
|
3
3
|
node_process = require_chunk.__toESM(node_process, 1);
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.4.0-dev.
|
|
6
|
+
var version = "2.4.0-dev.1655+b8f3c3d5";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-DvEyU5Zf.cjs");
|
|
3
3
|
let node_assert = require("node:assert");
|
|
4
4
|
let node_test = require("node:test");
|
|
5
5
|
let node_process = require("node:process");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as version, r as getUserAgent } from "./request-
|
|
1
|
+
import { o as version, r as getUserAgent } from "./request-B6s_F7bi.mjs";
|
|
2
2
|
import { deepStrictEqual } from "node:assert";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import process from "node:process";
|
package/package.json
CHANGED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { deepStrictEqual, equal, rejects, throws } from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { addMulticodecPrefix } from "./internal/multicodec.ts";
|
|
4
|
+
import { encodeMultibase } from "./multibase/mod.ts";
|
|
5
|
+
import {
|
|
6
|
+
computeDigestMultibase,
|
|
7
|
+
createHashlink,
|
|
8
|
+
parseDigestMultibase,
|
|
9
|
+
parseHashlink,
|
|
10
|
+
verifyDigestMultibase,
|
|
11
|
+
verifyHashlink,
|
|
12
|
+
} from "./digest.ts";
|
|
13
|
+
|
|
14
|
+
const encoder = new TextEncoder();
|
|
15
|
+
const decoder = new TextDecoder();
|
|
16
|
+
const bytes = encoder.encode("Hello World!");
|
|
17
|
+
// Test vector from draft-sporny-hashlink-07, appendix B.1.
|
|
18
|
+
const digestMultibase = "zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e";
|
|
19
|
+
const hashlink = `hl:${digestMultibase}`;
|
|
20
|
+
|
|
21
|
+
test("computeDigestMultibase() computes a SHA-256 multihash", async () => {
|
|
22
|
+
equal(await computeDigestMultibase(bytes), digestMultibase);
|
|
23
|
+
const parsed = parseDigestMultibase(digestMultibase);
|
|
24
|
+
equal(parsed.algorithm, "sha2-256");
|
|
25
|
+
deepStrictEqual(
|
|
26
|
+
parsed.digest,
|
|
27
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("digest helpers accept SharedArrayBuffer-backed bytes", async () => {
|
|
32
|
+
const sharedBytes = new Uint8Array(new SharedArrayBuffer(bytes.length));
|
|
33
|
+
sharedBytes.set(bytes);
|
|
34
|
+
Object.defineProperty(sharedBytes, "slice", {
|
|
35
|
+
value: () => {
|
|
36
|
+
throw new Error("Shared input must not be copied with slice().");
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
equal(await computeDigestMultibase(sharedBytes), digestMultibase);
|
|
40
|
+
equal(await verifyDigestMultibase(sharedBytes, digestMultibase), true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("digest helpers ignore spoofed shared buffer tags", async () => {
|
|
44
|
+
const taggedBytes = bytes.slice();
|
|
45
|
+
Object.defineProperty(taggedBytes.buffer, Symbol.toStringTag, {
|
|
46
|
+
value: "SharedArrayBuffer",
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(taggedBytes, "slice", {
|
|
49
|
+
value: () => {
|
|
50
|
+
throw new Error("ArrayBuffer input must not be copied.");
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
equal(await computeDigestMultibase(taggedBytes), digestMultibase);
|
|
54
|
+
equal(await verifyDigestMultibase(taggedBytes, digestMultibase), true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("createHashlink() and parseHashlink() round-trip simple hashlinks", () => {
|
|
58
|
+
equal(createHashlink(digestMultibase), hashlink);
|
|
59
|
+
deepStrictEqual(parseHashlink(hashlink), { digestMultibase });
|
|
60
|
+
deepStrictEqual(parseHashlink(new URL(hashlink)), { digestMultibase });
|
|
61
|
+
|
|
62
|
+
const base64DigestMultibase = decoder.decode(
|
|
63
|
+
encodeMultibase(
|
|
64
|
+
"base64",
|
|
65
|
+
addMulticodecPrefix(
|
|
66
|
+
0x12,
|
|
67
|
+
addMulticodecPrefix(32, new Uint8Array(32).fill(0xff)),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
const base64Hashlink = createHashlink(base64DigestMultibase);
|
|
72
|
+
equal(
|
|
73
|
+
base64Hashlink,
|
|
74
|
+
"hl:mEiD//////////////////////////////////////////w",
|
|
75
|
+
);
|
|
76
|
+
deepStrictEqual(parseHashlink(base64Hashlink), {
|
|
77
|
+
digestMultibase: base64DigestMultibase,
|
|
78
|
+
});
|
|
79
|
+
deepStrictEqual(parseHashlink(new URL(base64Hashlink)), {
|
|
80
|
+
digestMultibase: base64DigestMultibase,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("digest and hashlink verification accepts matching bytes", async () => {
|
|
85
|
+
equal(await verifyDigestMultibase(bytes, digestMultibase), true);
|
|
86
|
+
equal(await verifyHashlink(bytes, hashlink), true);
|
|
87
|
+
equal(await verifyHashlink(bytes, new URL(hashlink)), true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("digest and hashlink verification rejects non-matching bytes", async () => {
|
|
91
|
+
const different = encoder.encode("Hello World?");
|
|
92
|
+
equal(await verifyDigestMultibase(different, digestMultibase), false);
|
|
93
|
+
equal(await verifyHashlink(different, hashlink), false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("parseDigestMultibase() rejects unsupported algorithms", () => {
|
|
97
|
+
const sha1Multihash = addMulticodecPrefix(
|
|
98
|
+
0x11,
|
|
99
|
+
addMulticodecPrefix(20, new Uint8Array(20)),
|
|
100
|
+
);
|
|
101
|
+
const value = decoder.decode(encodeMultibase("base58btc", sha1Multihash));
|
|
102
|
+
throws(
|
|
103
|
+
() => parseDigestMultibase(value),
|
|
104
|
+
new TypeError("Unsupported digest algorithm: 0x11"),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("parseDigestMultibase() rejects malformed values", async () => {
|
|
109
|
+
throws(
|
|
110
|
+
() => parseDigestMultibase("not-multibase"),
|
|
111
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const missingLength = decoder.decode(
|
|
115
|
+
encodeMultibase("base58btc", Uint8Array.of(0x12)),
|
|
116
|
+
);
|
|
117
|
+
throws(
|
|
118
|
+
() => parseDigestMultibase(missingLength),
|
|
119
|
+
new TypeError("Invalid digestMultibase multihash."),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const multihash = addMulticodecPrefix(
|
|
123
|
+
0x12,
|
|
124
|
+
addMulticodecPrefix(32, new Uint8Array(32)),
|
|
125
|
+
);
|
|
126
|
+
const base2 = decoder.decode(encodeMultibase("base2", multihash));
|
|
127
|
+
equal(base2.length, 273);
|
|
128
|
+
deepStrictEqual(parseDigestMultibase(base2).digest, new Uint8Array(32));
|
|
129
|
+
throws(
|
|
130
|
+
() => parseDigestMultibase(`${base2}0`),
|
|
131
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const padded = decoder.decode(encodeMultibase("base64pad", multihash));
|
|
135
|
+
equal(padded.endsWith("=="), true);
|
|
136
|
+
deepStrictEqual(parseDigestMultibase(padded).digest, new Uint8Array(32));
|
|
137
|
+
throws(
|
|
138
|
+
() => parseDigestMultibase(padded.slice(0, -1)),
|
|
139
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
140
|
+
);
|
|
141
|
+
throws(
|
|
142
|
+
() => createHashlink(`${padded}=`),
|
|
143
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const overlongAlgorithm = decoder.decode(
|
|
147
|
+
encodeMultibase(
|
|
148
|
+
"base58btc",
|
|
149
|
+
Uint8Array.of(0x92, 0x00, 0x20, ...new Uint8Array(32)),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
throws(
|
|
153
|
+
() => parseDigestMultibase(overlongAlgorithm),
|
|
154
|
+
new TypeError("Invalid digestMultibase multihash."),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const overlongLength = decoder.decode(
|
|
158
|
+
encodeMultibase(
|
|
159
|
+
"base58btc",
|
|
160
|
+
Uint8Array.of(0x12, 0xa0, 0x00, ...new Uint8Array(32)),
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
throws(
|
|
164
|
+
() => parseDigestMultibase(overlongLength),
|
|
165
|
+
new TypeError("Invalid digestMultibase multihash."),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const shortDigest = decoder.decode(
|
|
169
|
+
encodeMultibase(
|
|
170
|
+
"base58btc",
|
|
171
|
+
addMulticodecPrefix(0x12, addMulticodecPrefix(31, new Uint8Array(31))),
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
throws(
|
|
175
|
+
() => parseDigestMultibase(shortDigest),
|
|
176
|
+
new TypeError("Invalid SHA-256 digest length."),
|
|
177
|
+
);
|
|
178
|
+
await rejects(
|
|
179
|
+
() => verifyDigestMultibase(bytes, shortDigest),
|
|
180
|
+
new TypeError("Invalid SHA-256 digest length."),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("simple hashlink helpers reject metadata and malformed forms", async () => {
|
|
185
|
+
for (const terminator of ["\n", "\r", "\r\n", "\u2028", "\u2029"]) {
|
|
186
|
+
const malformedHashlink = `${hashlink}${terminator}`;
|
|
187
|
+
throws(
|
|
188
|
+
() => parseHashlink(malformedHashlink),
|
|
189
|
+
new TypeError("Invalid simple hashlink."),
|
|
190
|
+
);
|
|
191
|
+
await rejects(
|
|
192
|
+
() => verifyHashlink(bytes, malformedHashlink),
|
|
193
|
+
new TypeError("Invalid simple hashlink."),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
throws(
|
|
197
|
+
() => parseHashlink(`${hashlink}:zmetadata`),
|
|
198
|
+
new TypeError("Invalid simple hashlink."),
|
|
199
|
+
);
|
|
200
|
+
await rejects(
|
|
201
|
+
() => verifyHashlink(bytes, `${hashlink}:zmetadata`),
|
|
202
|
+
new TypeError("Invalid simple hashlink."),
|
|
203
|
+
);
|
|
204
|
+
throws(
|
|
205
|
+
() => parseHashlink(`https://example.com/file?hl=${digestMultibase}`),
|
|
206
|
+
new TypeError("Invalid simple hashlink."),
|
|
207
|
+
);
|
|
208
|
+
throws(
|
|
209
|
+
() => parseHashlink("hl:not-multibase"),
|
|
210
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
211
|
+
);
|
|
212
|
+
await rejects(
|
|
213
|
+
() => verifyHashlink(bytes, "hl:not-multibase"),
|
|
214
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
215
|
+
);
|
|
216
|
+
throws(
|
|
217
|
+
() => createHashlink("not-multibase"),
|
|
218
|
+
new TypeError("Invalid digestMultibase encoding."),
|
|
219
|
+
);
|
|
220
|
+
});
|
package/src/digest.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addMulticodecPrefix,
|
|
3
|
+
getMulticodecPrefix,
|
|
4
|
+
} from "./internal/multicodec.ts";
|
|
5
|
+
import {
|
|
6
|
+
decodeMultibase,
|
|
7
|
+
encodeMultibase,
|
|
8
|
+
encodingFromBaseData,
|
|
9
|
+
} from "./multibase/mod.ts";
|
|
10
|
+
|
|
11
|
+
const SHA2_256_MULTIHASH_CODE = 0x12;
|
|
12
|
+
const SHA2_256_DIGEST_LENGTH = 32;
|
|
13
|
+
const MAX_DIGEST_MULTIBASE_LENGTH = 1 + (SHA2_256_DIGEST_LENGTH + 2) * 8;
|
|
14
|
+
const textDecoder = new TextDecoder();
|
|
15
|
+
const getArrayBufferByteLength = Object.getOwnPropertyDescriptor(
|
|
16
|
+
ArrayBuffer.prototype,
|
|
17
|
+
"byteLength",
|
|
18
|
+
)?.get;
|
|
19
|
+
|
|
20
|
+
function isCanonicalVarintPrefix(
|
|
21
|
+
data: Uint8Array,
|
|
22
|
+
prefix: ReturnType<typeof getMulticodecPrefix>,
|
|
23
|
+
): boolean {
|
|
24
|
+
const canonical = addMulticodecPrefix(prefix.code, new Uint8Array());
|
|
25
|
+
if (canonical.length !== prefix.prefixLength) return false;
|
|
26
|
+
for (let i = 0; i < canonical.length; i++) {
|
|
27
|
+
if (data[i] !== canonical[i]) return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toWebCryptoBytes(bytes: Uint8Array): Uint8Array<ArrayBuffer> {
|
|
33
|
+
if (getArrayBufferByteLength != null) {
|
|
34
|
+
try {
|
|
35
|
+
getArrayBufferByteLength.call(bytes.buffer);
|
|
36
|
+
return bytes as Uint8Array<ArrayBuffer>;
|
|
37
|
+
} catch {
|
|
38
|
+
// SharedArrayBuffer input needs to be copied for Web Crypto.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return new Uint8Array(bytes);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A parsed SHA-256 resource digest.
|
|
46
|
+
*
|
|
47
|
+
* @since 2.4.0
|
|
48
|
+
*/
|
|
49
|
+
export interface ParsedDigestMultibase {
|
|
50
|
+
/** The multihash algorithm represented by the digest. */
|
|
51
|
+
readonly algorithm: "sha2-256";
|
|
52
|
+
|
|
53
|
+
/** The raw 32-byte SHA-256 digest. */
|
|
54
|
+
readonly digest: Uint8Array;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A parsed simple hashlink.
|
|
59
|
+
*
|
|
60
|
+
* @since 2.4.0
|
|
61
|
+
*/
|
|
62
|
+
export interface ParsedHashlink {
|
|
63
|
+
/** The multibase-encoded multihash carried by the hashlink. */
|
|
64
|
+
readonly digestMultibase: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Computes the SHA-256 digest of a byte sequence and encodes it as a
|
|
69
|
+
* base58-btc multibase multihash suitable for FEP-ef61's
|
|
70
|
+
* `digestMultibase` property.
|
|
71
|
+
*
|
|
72
|
+
* @param bytes The bytes to digest.
|
|
73
|
+
* @returns The base58-btc multibase-encoded SHA-256 multihash.
|
|
74
|
+
* @since 2.4.0
|
|
75
|
+
*/
|
|
76
|
+
export async function computeDigestMultibase(
|
|
77
|
+
bytes: Uint8Array,
|
|
78
|
+
): Promise<string> {
|
|
79
|
+
const digest = new Uint8Array(
|
|
80
|
+
await crypto.subtle.digest("SHA-256", toWebCryptoBytes(bytes)),
|
|
81
|
+
);
|
|
82
|
+
const lengthAndDigest = addMulticodecPrefix(digest.length, digest);
|
|
83
|
+
const multihash = addMulticodecPrefix(
|
|
84
|
+
SHA2_256_MULTIHASH_CODE,
|
|
85
|
+
lengthAndDigest,
|
|
86
|
+
);
|
|
87
|
+
return textDecoder.decode(encodeMultibase("base58btc", multihash));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parses and validates a SHA-256 `digestMultibase` value.
|
|
92
|
+
*
|
|
93
|
+
* @param value The multibase-encoded multihash to parse.
|
|
94
|
+
* @returns The digest algorithm and raw digest bytes.
|
|
95
|
+
* @throws {TypeError} If the value is malformed or uses an unsupported hash
|
|
96
|
+
* algorithm.
|
|
97
|
+
* @since 2.4.0
|
|
98
|
+
*/
|
|
99
|
+
export function parseDigestMultibase(value: string): ParsedDigestMultibase {
|
|
100
|
+
if (value.length > MAX_DIGEST_MULTIBASE_LENGTH) {
|
|
101
|
+
throw new TypeError("Invalid digestMultibase encoding.");
|
|
102
|
+
}
|
|
103
|
+
let multihash: Uint8Array;
|
|
104
|
+
try {
|
|
105
|
+
multihash = decodeMultibase(value);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new TypeError("Invalid digestMultibase encoding.", { cause: error });
|
|
108
|
+
}
|
|
109
|
+
const encoding = encodingFromBaseData(value);
|
|
110
|
+
const canonicalValue = textDecoder.decode(
|
|
111
|
+
encodeMultibase(encoding.name, multihash),
|
|
112
|
+
);
|
|
113
|
+
if (value !== canonicalValue) {
|
|
114
|
+
throw new TypeError("Invalid digestMultibase encoding.");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let algorithmPrefix: ReturnType<typeof getMulticodecPrefix>;
|
|
118
|
+
try {
|
|
119
|
+
algorithmPrefix = getMulticodecPrefix(multihash);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new TypeError("Invalid digestMultibase multihash.", { cause: error });
|
|
122
|
+
}
|
|
123
|
+
if (!isCanonicalVarintPrefix(multihash, algorithmPrefix)) {
|
|
124
|
+
throw new TypeError("Invalid digestMultibase multihash.");
|
|
125
|
+
}
|
|
126
|
+
if (algorithmPrefix.code !== SHA2_256_MULTIHASH_CODE) {
|
|
127
|
+
throw new TypeError(
|
|
128
|
+
`Unsupported digest algorithm: 0x${algorithmPrefix.code.toString(16)}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lengthAndDigest = multihash.subarray(algorithmPrefix.prefixLength);
|
|
133
|
+
let lengthPrefix: ReturnType<typeof getMulticodecPrefix>;
|
|
134
|
+
try {
|
|
135
|
+
lengthPrefix = getMulticodecPrefix(lengthAndDigest);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw new TypeError("Invalid digestMultibase multihash.", { cause: error });
|
|
138
|
+
}
|
|
139
|
+
if (!isCanonicalVarintPrefix(lengthAndDigest, lengthPrefix)) {
|
|
140
|
+
throw new TypeError("Invalid digestMultibase multihash.");
|
|
141
|
+
}
|
|
142
|
+
const digest = lengthAndDigest.slice(lengthPrefix.prefixLength);
|
|
143
|
+
if (
|
|
144
|
+
lengthPrefix.code !== SHA2_256_DIGEST_LENGTH ||
|
|
145
|
+
digest.length !== SHA2_256_DIGEST_LENGTH
|
|
146
|
+
) {
|
|
147
|
+
throw new TypeError("Invalid SHA-256 digest length.");
|
|
148
|
+
}
|
|
149
|
+
return { algorithm: "sha2-256", digest };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function extractDigestMultibase(value: string | URL): string {
|
|
153
|
+
const hashlink = typeof value === "string" ? value : value.href;
|
|
154
|
+
const match = /^hl:([^:\r\n\u2028\u2029]+)$/i.exec(hashlink);
|
|
155
|
+
if (match == null || match[0].length !== hashlink.length) {
|
|
156
|
+
throw new TypeError("Invalid simple hashlink.");
|
|
157
|
+
}
|
|
158
|
+
return match[1];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parses a metadata-free `hl:` hashlink and validates its resource digest.
|
|
163
|
+
*
|
|
164
|
+
* @param value The simple hashlink to parse.
|
|
165
|
+
* @returns The `digestMultibase` value carried by the hashlink.
|
|
166
|
+
* @throws {TypeError} If the hashlink is malformed, contains metadata, or
|
|
167
|
+
* carries an invalid or unsupported digest.
|
|
168
|
+
* @since 2.4.0
|
|
169
|
+
*/
|
|
170
|
+
export function parseHashlink(value: string | URL): ParsedHashlink {
|
|
171
|
+
const digestMultibase = extractDigestMultibase(value);
|
|
172
|
+
parseDigestMultibase(digestMultibase);
|
|
173
|
+
return { digestMultibase };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates a metadata-free `hl:` hashlink from a `digestMultibase` value.
|
|
178
|
+
*
|
|
179
|
+
* @param digestMultibase The SHA-256 multibase multihash to embed.
|
|
180
|
+
* @returns The simple hashlink.
|
|
181
|
+
* @throws {TypeError} If the digest is malformed or unsupported.
|
|
182
|
+
* @since 2.4.0
|
|
183
|
+
*/
|
|
184
|
+
export function createHashlink(digestMultibase: string): string {
|
|
185
|
+
parseDigestMultibase(digestMultibase);
|
|
186
|
+
return `hl:${digestMultibase}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Verifies that bytes match a SHA-256 `digestMultibase` value.
|
|
191
|
+
*
|
|
192
|
+
* @param bytes The bytes to verify.
|
|
193
|
+
* @param digestMultibase The expected digest.
|
|
194
|
+
* @returns Whether the bytes match the digest.
|
|
195
|
+
* @throws {TypeError} If the digest is malformed or unsupported.
|
|
196
|
+
* @since 2.4.0
|
|
197
|
+
*/
|
|
198
|
+
export async function verifyDigestMultibase(
|
|
199
|
+
bytes: Uint8Array,
|
|
200
|
+
digestMultibase: string,
|
|
201
|
+
): Promise<boolean> {
|
|
202
|
+
const expected = parseDigestMultibase(digestMultibase).digest;
|
|
203
|
+
const actual = new Uint8Array(
|
|
204
|
+
await crypto.subtle.digest("SHA-256", toWebCryptoBytes(bytes)),
|
|
205
|
+
);
|
|
206
|
+
if (expected.length !== actual.length) return false;
|
|
207
|
+
let difference = 0;
|
|
208
|
+
for (let i = 0; i < expected.length; i++) {
|
|
209
|
+
difference |= expected[i] ^ actual[i];
|
|
210
|
+
}
|
|
211
|
+
return difference === 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Verifies that bytes match the resource digest in a simple hashlink.
|
|
216
|
+
*
|
|
217
|
+
* @param bytes The bytes to verify.
|
|
218
|
+
* @param hashlink The simple hashlink containing the expected digest.
|
|
219
|
+
* @returns Whether the bytes match the hashlink digest.
|
|
220
|
+
* @throws {TypeError} If the hashlink or digest is malformed or unsupported.
|
|
221
|
+
* @since 2.4.0
|
|
222
|
+
*/
|
|
223
|
+
export async function verifyHashlink(
|
|
224
|
+
bytes: Uint8Array,
|
|
225
|
+
hashlink: string | URL,
|
|
226
|
+
): Promise<boolean> {
|
|
227
|
+
const digestMultibase = extractDigestMultibase(hashlink);
|
|
228
|
+
return await verifyDigestMultibase(bytes, digestMultibase);
|
|
229
|
+
}
|
package/src/mod.ts
CHANGED
|
@@ -34,6 +34,16 @@ export {
|
|
|
34
34
|
isDecimal,
|
|
35
35
|
parseDecimal,
|
|
36
36
|
} from "./decimal.ts";
|
|
37
|
+
export {
|
|
38
|
+
computeDigestMultibase,
|
|
39
|
+
createHashlink,
|
|
40
|
+
type ParsedDigestMultibase,
|
|
41
|
+
type ParsedHashlink,
|
|
42
|
+
parseDigestMultibase,
|
|
43
|
+
parseHashlink,
|
|
44
|
+
verifyDigestMultibase,
|
|
45
|
+
verifyHashlink,
|
|
46
|
+
} from "./digest.ts";
|
|
37
47
|
export { LanguageString } from "./langstr.ts";
|
|
38
48
|
export {
|
|
39
49
|
decodeMultibase,
|
|
File without changes
|
|
File without changes
|