@optimystic/quereus-plugin-crypto 0.13.4 → 0.14.1
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/README.md +243 -16
- package/dist/index.d.ts +224 -198
- package/dist/index.js +267 -272
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +17 -1
- package/dist/plugin.js +426 -23
- package/dist/plugin.js.map +1 -1
- package/package.json +29 -3
- package/src/cid.ts +200 -0
- package/src/crypto.ts +244 -35
- package/src/index.ts +30 -5
- package/src/plugin.ts +193 -5
- package/src/sd.ts +250 -0
- package/src/digest.ts +0 -173
- package/src/sign.ts +0 -235
- package/src/signature-valid.ts +0 -262
package/dist/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { sha512, sha256 } from '@noble/hashes/sha2.js';
|
|
2
2
|
import { blake3 } from '@noble/hashes/blake3.js';
|
|
3
|
-
import { randomBytes as randomBytes$1,
|
|
3
|
+
import { concatBytes, randomBytes as randomBytes$1, utf8ToBytes } from '@noble/hashes/utils.js';
|
|
4
4
|
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
5
5
|
import { p256 } from '@noble/curves/nist.js';
|
|
6
6
|
import { ed25519 } from '@noble/curves/ed25519.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { bytesToHex, hexToBytes } from '@noble/curves/utils.js';
|
|
8
|
+
import { toString, fromString } from 'uint8arrays';
|
|
9
|
+
import { CID } from 'multiformats/cid';
|
|
10
|
+
import * as Digest from 'multiformats/hashes/digest';
|
|
11
|
+
import { base32 } from 'multiformats/bases/base32';
|
|
12
|
+
import { base58btc } from 'multiformats/bases/base58';
|
|
13
|
+
import { base64url } from 'multiformats/bases/base64';
|
|
14
|
+
import { base16 } from 'multiformats/bases/base16';
|
|
9
15
|
|
|
10
16
|
// src/crypto.ts
|
|
11
17
|
function toBytes(input, encoding = "base64url") {
|
|
@@ -47,29 +53,136 @@ function fromBytes(bytes, encoding = "base64url") {
|
|
|
47
53
|
return toString(bytes, "base64url");
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
var HASHERS = {
|
|
57
|
+
sha256,
|
|
58
|
+
sha512,
|
|
59
|
+
blake3
|
|
60
|
+
};
|
|
61
|
+
var OUTPUT_ENCODERS = {
|
|
62
|
+
base64url: (bytes) => toString(bytes, "base64url"),
|
|
63
|
+
base64: (bytes) => toString(bytes, "base64"),
|
|
64
|
+
hex: (bytes) => bytesToHex(bytes),
|
|
65
|
+
bytes: (bytes) => bytes
|
|
66
|
+
};
|
|
67
|
+
function resolveHasher(algorithm) {
|
|
68
|
+
const hasher = HASHERS[algorithm];
|
|
69
|
+
if (!hasher) {
|
|
70
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
71
|
+
}
|
|
72
|
+
return hasher;
|
|
73
|
+
}
|
|
74
|
+
function resolveOutputEncoder(encoding) {
|
|
75
|
+
const encoder = OUTPUT_ENCODERS[encoding];
|
|
76
|
+
if (!encoder) {
|
|
77
|
+
throw new Error(`Unsupported output encoding: ${encoding}`);
|
|
78
|
+
}
|
|
79
|
+
return encoder;
|
|
80
|
+
}
|
|
81
|
+
var DIGEST_FORMAT_V1 = 1;
|
|
82
|
+
var TAG_NULL = 0;
|
|
83
|
+
var TAG_INT = 1;
|
|
84
|
+
var TAG_REAL = 2;
|
|
85
|
+
var TAG_TEXT = 3;
|
|
86
|
+
var TAG_BOOL = 4;
|
|
87
|
+
var TAG_BLOB = 5;
|
|
88
|
+
var TAG_JSON = 6;
|
|
89
|
+
function writeVarint(out, value) {
|
|
90
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
91
|
+
throw new Error(`varint expects a non-negative integer, got ${value}`);
|
|
92
|
+
}
|
|
93
|
+
let v = value;
|
|
94
|
+
while (v >= 128) {
|
|
95
|
+
out.push(v & 127 | 128);
|
|
96
|
+
v = Math.floor(v / 128);
|
|
97
|
+
}
|
|
98
|
+
out.push(v);
|
|
99
|
+
}
|
|
100
|
+
function framed(tag, payload) {
|
|
101
|
+
const header = [tag];
|
|
102
|
+
writeVarint(header, payload.length);
|
|
103
|
+
return concatBytes(Uint8Array.from(header), payload);
|
|
104
|
+
}
|
|
105
|
+
function canonicalJson(value) {
|
|
106
|
+
if (value === null) return "null";
|
|
107
|
+
const t = typeof value;
|
|
108
|
+
if (t === "string") return JSON.stringify(value);
|
|
109
|
+
if (t === "boolean") return value ? "true" : "false";
|
|
110
|
+
if (t === "number") {
|
|
111
|
+
if (!Number.isFinite(value)) {
|
|
112
|
+
throw new Error("digest: cannot encode a non-finite number inside a JSON field");
|
|
113
|
+
}
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
}
|
|
116
|
+
if (t === "bigint") {
|
|
117
|
+
throw new Error("digest: bigint is not representable inside a JSON field");
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
return `[${value.map((el) => {
|
|
121
|
+
if (el === void 0) {
|
|
122
|
+
throw new Error("digest: undefined / sparse element inside a JSON field");
|
|
123
|
+
}
|
|
124
|
+
return canonicalJson(el);
|
|
125
|
+
}).join(",")}]`;
|
|
126
|
+
}
|
|
127
|
+
if (t === "object") {
|
|
128
|
+
const proto = Object.getPrototypeOf(value);
|
|
129
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
130
|
+
throw new Error("digest: only plain objects are allowed inside a JSON field");
|
|
131
|
+
}
|
|
132
|
+
const obj = value;
|
|
133
|
+
const keys = Object.keys(obj).sort();
|
|
134
|
+
return `{${keys.map((k) => {
|
|
135
|
+
if (obj[k] === void 0) {
|
|
136
|
+
throw new Error(`digest: undefined value for JSON key '${k}'`);
|
|
137
|
+
}
|
|
138
|
+
return `${JSON.stringify(k)}:${canonicalJson(obj[k])}`;
|
|
139
|
+
}).join(",")}}`;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`digest: unsupported value of type '${t}' inside a JSON field`);
|
|
142
|
+
}
|
|
143
|
+
function encodeField(field) {
|
|
144
|
+
if (field === null || field === void 0) {
|
|
145
|
+
return Uint8Array.of(TAG_NULL);
|
|
146
|
+
}
|
|
147
|
+
switch (typeof field) {
|
|
148
|
+
case "boolean":
|
|
149
|
+
return Uint8Array.of(TAG_BOOL, field ? 1 : 0);
|
|
150
|
+
case "bigint":
|
|
151
|
+
return framed(TAG_INT, utf8ToBytes(field.toString()));
|
|
152
|
+
case "number":
|
|
153
|
+
if (!Number.isFinite(field)) {
|
|
154
|
+
throw new Error("digest: cannot encode a non-finite number");
|
|
155
|
+
}
|
|
156
|
+
return Number.isInteger(field) ? framed(TAG_INT, utf8ToBytes(BigInt(field).toString())) : framed(TAG_REAL, utf8ToBytes(field.toString()));
|
|
157
|
+
case "string":
|
|
158
|
+
return framed(TAG_TEXT, utf8ToBytes(field));
|
|
159
|
+
case "object":
|
|
160
|
+
if (field instanceof Uint8Array) {
|
|
161
|
+
return framed(TAG_BLOB, field);
|
|
162
|
+
}
|
|
163
|
+
return framed(TAG_JSON, utf8ToBytes(canonicalJson(field)));
|
|
63
164
|
default:
|
|
64
|
-
throw new Error(`
|
|
165
|
+
throw new Error(`digest: unsupported field type '${typeof field}'`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function encodeFields(fields) {
|
|
169
|
+
const chunks = [Uint8Array.of(DIGEST_FORMAT_V1)];
|
|
170
|
+
for (const field of fields) {
|
|
171
|
+
chunks.push(encodeField(field));
|
|
65
172
|
}
|
|
66
|
-
return
|
|
173
|
+
return concatBytes(...chunks);
|
|
174
|
+
}
|
|
175
|
+
function digestFields(fields, hasher, encode) {
|
|
176
|
+
return encode(hasher(encodeFields(fields)));
|
|
177
|
+
}
|
|
178
|
+
function digest(fields, algorithm = "sha256", encoding = "base64url") {
|
|
179
|
+
return digestFields(fields, resolveHasher(algorithm), resolveOutputEncoder(encoding));
|
|
67
180
|
}
|
|
68
181
|
function hashMod(data, bits, algorithm = "sha256", inputEncoding = "base64url") {
|
|
69
182
|
if (bits <= 0 || bits > 53) {
|
|
70
183
|
throw new Error("Bits must be between 1 and 53 (JavaScript safe integer limit)");
|
|
71
184
|
}
|
|
72
|
-
const hashBytes = toBytes(
|
|
185
|
+
const hashBytes = resolveHasher(algorithm)(toBytes(data, inputEncoding));
|
|
73
186
|
const view = new DataView(hashBytes.buffer, hashBytes.byteOffset, Math.min(8, hashBytes.length));
|
|
74
187
|
const fullHash = view.getBigUint64(0, false);
|
|
75
188
|
const modulus = BigInt(2) ** BigInt(bits);
|
|
@@ -157,285 +270,167 @@ function getPublicKey(privateKey, curve = "secp256k1", keyEncoding = "base64url"
|
|
|
157
270
|
}
|
|
158
271
|
return fromBytes(pubBytes, outputEncoding);
|
|
159
272
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
if (typeof input === "string") {
|
|
165
|
-
return utf8ToBytes(input);
|
|
166
|
-
}
|
|
167
|
-
if (input instanceof Uint8Array) {
|
|
168
|
-
return input;
|
|
169
|
-
}
|
|
170
|
-
if (typeof input === "number") {
|
|
171
|
-
const buffer = new ArrayBuffer(8);
|
|
172
|
-
const view = new DataView(buffer);
|
|
173
|
-
view.setFloat64(0, input, false);
|
|
174
|
-
return new Uint8Array(buffer);
|
|
175
|
-
}
|
|
176
|
-
if (typeof input === "boolean") {
|
|
177
|
-
return new Uint8Array([input ? 1 : 0]);
|
|
178
|
-
}
|
|
179
|
-
return utf8ToBytes(String(input));
|
|
180
|
-
}
|
|
181
|
-
function getHashFunction(algorithm) {
|
|
182
|
-
switch (algorithm) {
|
|
183
|
-
case "sha256":
|
|
184
|
-
return sha256;
|
|
185
|
-
case "sha512":
|
|
186
|
-
return sha512;
|
|
187
|
-
case "blake3":
|
|
188
|
-
return blake3;
|
|
189
|
-
default:
|
|
190
|
-
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function Digest(...args) {
|
|
194
|
-
return DigestWithOptions({ algorithm: "sha256", output: "uint8array" }, ...args);
|
|
195
|
-
}
|
|
196
|
-
function DigestWithOptions(options, ...args) {
|
|
197
|
-
const algorithm = options.algorithm || "sha256";
|
|
198
|
-
const output = options.output || "uint8array";
|
|
199
|
-
const byteArrays = args.map(inputToBytes);
|
|
200
|
-
const combined = concatBytes(...byteArrays);
|
|
201
|
-
const hashFunction = getHashFunction(algorithm);
|
|
202
|
-
const hash = hashFunction(combined);
|
|
203
|
-
if (output === "hex") {
|
|
204
|
-
return Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
205
|
-
}
|
|
206
|
-
return hash;
|
|
207
|
-
}
|
|
208
|
-
Digest.withOptions = (options) => {
|
|
209
|
-
return (...args) => DigestWithOptions(options, ...args);
|
|
210
|
-
};
|
|
211
|
-
Digest.sha256 = (...args) => {
|
|
212
|
-
return DigestWithOptions({ algorithm: "sha256" }, ...args);
|
|
213
|
-
};
|
|
214
|
-
Digest.sha512 = (...args) => {
|
|
215
|
-
return DigestWithOptions({ algorithm: "sha512" }, ...args);
|
|
273
|
+
var MULTICODEC_CODES = {
|
|
274
|
+
"raw": 85,
|
|
275
|
+
"dag-cbor": 113
|
|
216
276
|
};
|
|
217
|
-
|
|
218
|
-
|
|
277
|
+
var MULTIHASH_CODES = {
|
|
278
|
+
"sha2-256": 18,
|
|
279
|
+
"sha2-512": 19,
|
|
280
|
+
"blake3": 30
|
|
219
281
|
};
|
|
220
|
-
|
|
221
|
-
|
|
282
|
+
var MULTIHASH_TO_ALGORITHM = {
|
|
283
|
+
"sha2-256": "sha256",
|
|
284
|
+
"sha2-512": "sha512",
|
|
285
|
+
"blake3": "blake3"
|
|
222
286
|
};
|
|
223
|
-
|
|
224
|
-
|
|
287
|
+
var MULTIHASH_DIGEST_LENGTHS = {
|
|
288
|
+
"sha2-256": 32,
|
|
289
|
+
"sha2-512": 64,
|
|
290
|
+
"blake3": 32
|
|
225
291
|
};
|
|
226
|
-
|
|
227
|
-
|
|
292
|
+
var MULTICODEC_NAMES = new Map(
|
|
293
|
+
Object.entries(MULTICODEC_CODES).map(([name, code]) => [code, name])
|
|
294
|
+
);
|
|
295
|
+
var MULTIHASH_NAMES = new Map(
|
|
296
|
+
Object.entries(MULTIHASH_CODES).map(([name, code]) => [code, name])
|
|
297
|
+
);
|
|
298
|
+
var MULTIBASE_ENCODERS = {
|
|
299
|
+
"base32": base32,
|
|
300
|
+
"base58btc": base58btc,
|
|
301
|
+
"base64url": base64url,
|
|
302
|
+
"base16": base16
|
|
228
303
|
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return privateKey;
|
|
235
|
-
}
|
|
236
|
-
if (typeof privateKey === "string") {
|
|
237
|
-
return hexToBytes(privateKey);
|
|
238
|
-
}
|
|
239
|
-
if (typeof privateKey === "bigint") {
|
|
240
|
-
const hex = privateKey.toString(16).padStart(64, "0");
|
|
241
|
-
return hexToBytes(hex);
|
|
304
|
+
var MULTIBASE_DECODER = base32.decoder.or(base58btc.decoder).or(base64url.decoder).or(base16.decoder);
|
|
305
|
+
function resolveCodecCode(codec) {
|
|
306
|
+
const code = MULTICODEC_CODES[codec];
|
|
307
|
+
if (code === void 0) {
|
|
308
|
+
throw new Error(`cid: unsupported multicodec '${codec}' (expected one of ${Object.keys(MULTICODEC_CODES).join(", ")})`);
|
|
242
309
|
}
|
|
243
|
-
|
|
310
|
+
return code;
|
|
244
311
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
312
|
+
function resolveBaseEncoder(base) {
|
|
313
|
+
const encoder = MULTIBASE_ENCODERS[base];
|
|
314
|
+
if (!encoder) {
|
|
315
|
+
throw new Error(`cid: unsupported multibase '${base}' (expected one of ${Object.keys(MULTIBASE_ENCODERS).join(", ")})`);
|
|
248
316
|
}
|
|
249
|
-
|
|
250
|
-
return hexToBytes(digest2);
|
|
251
|
-
}
|
|
252
|
-
throw new Error("Invalid digest format");
|
|
317
|
+
return encoder;
|
|
253
318
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
throw new Error(`Unsupported signature format: ${format}`);
|
|
268
|
-
}
|
|
319
|
+
function cidV1(digest2, hash, codec = "raw", base = "base32") {
|
|
320
|
+
const hashCode = MULTIHASH_CODES[hash];
|
|
321
|
+
if (hashCode === void 0) {
|
|
322
|
+
throw new Error(`cid: unsupported multihash code '${hash}' (expected one of ${Object.keys(MULTIHASH_CODES).join(", ")})`);
|
|
323
|
+
}
|
|
324
|
+
const expectedLength = MULTIHASH_DIGEST_LENGTHS[hash];
|
|
325
|
+
if (digest2.length !== expectedLength) {
|
|
326
|
+
throw new Error(`cid: digest length ${digest2.length} does not match asserted hash '${hash}' (expected ${expectedLength} bytes)`);
|
|
327
|
+
}
|
|
328
|
+
const codecCode = resolveCodecCode(codec);
|
|
329
|
+
const encoder = resolveBaseEncoder(base);
|
|
330
|
+
const multihash = Digest.create(hashCode, digest2);
|
|
331
|
+
return CID.createV1(codecCode, multihash).toString(encoder);
|
|
269
332
|
}
|
|
270
|
-
function
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
extraEntropy = false,
|
|
275
|
-
lowS = true
|
|
276
|
-
} = options;
|
|
277
|
-
const normalizedDigest = normalizeDigest(digest2);
|
|
278
|
-
const normalizedPrivateKey = normalizePrivateKey(privateKey);
|
|
279
|
-
let signature;
|
|
280
|
-
switch (curve) {
|
|
281
|
-
case "secp256k1": {
|
|
282
|
-
const signOptions = { lowS };
|
|
283
|
-
if (extraEntropy) {
|
|
284
|
-
signOptions.extraEntropy = extraEntropy;
|
|
285
|
-
}
|
|
286
|
-
signature = secp256k1.sign(normalizedDigest, normalizedPrivateKey, signOptions);
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
case "p256": {
|
|
290
|
-
const signOptions = { lowS };
|
|
291
|
-
if (extraEntropy) {
|
|
292
|
-
signOptions.extraEntropy = extraEntropy;
|
|
293
|
-
}
|
|
294
|
-
signature = p256.sign(normalizedDigest, normalizedPrivateKey, signOptions);
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
case "ed25519": {
|
|
298
|
-
signature = ed25519.sign(normalizedDigest, normalizedPrivateKey);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
default:
|
|
302
|
-
throw new Error(`Unsupported curve: ${curve}`);
|
|
333
|
+
function cid(data, codec = "raw", hash = "sha2-256", base = "base32") {
|
|
334
|
+
const algorithm = MULTIHASH_TO_ALGORITHM[hash];
|
|
335
|
+
if (!algorithm) {
|
|
336
|
+
throw new Error(`cid: unsupported multihash code '${hash}' (expected one of ${Object.keys(MULTIHASH_CODES).join(", ")})`);
|
|
303
337
|
}
|
|
304
|
-
|
|
338
|
+
const digest2 = resolveHasher(algorithm)(data);
|
|
339
|
+
return cidV1(digest2, hash, codec, base);
|
|
305
340
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
};
|
|
315
|
-
Sign.generatePrivateKey = (curve = "secp256k1") => {
|
|
316
|
-
switch (curve) {
|
|
317
|
-
case "secp256k1":
|
|
318
|
-
return secp256k1.utils.randomSecretKey();
|
|
319
|
-
case "p256":
|
|
320
|
-
return p256.utils.randomSecretKey();
|
|
321
|
-
case "ed25519":
|
|
322
|
-
return ed25519.utils.randomSecretKey();
|
|
323
|
-
default:
|
|
324
|
-
throw new Error(`Unsupported curve: ${curve}`);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
Sign.getPublicKey = (privateKey, curve = "secp256k1") => {
|
|
328
|
-
const normalizedPrivateKey = normalizePrivateKey(privateKey);
|
|
329
|
-
switch (curve) {
|
|
330
|
-
case "secp256k1":
|
|
331
|
-
return secp256k1.getPublicKey(normalizedPrivateKey);
|
|
332
|
-
case "p256":
|
|
333
|
-
return p256.getPublicKey(normalizedPrivateKey);
|
|
334
|
-
case "ed25519":
|
|
335
|
-
return ed25519.getPublicKey(normalizedPrivateKey);
|
|
336
|
-
default:
|
|
337
|
-
throw new Error(`Unsupported curve: ${curve}`);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
function normalizeBytes(input) {
|
|
341
|
-
if (input instanceof Uint8Array) {
|
|
342
|
-
return input;
|
|
343
|
-
}
|
|
344
|
-
if (typeof input === "string") {
|
|
345
|
-
return hexToBytes(input);
|
|
346
|
-
}
|
|
347
|
-
throw new Error("Invalid input format - expected Uint8Array or hex string");
|
|
341
|
+
function cidDecode(value) {
|
|
342
|
+
const parsed = CID.parse(value, MULTIBASE_DECODER);
|
|
343
|
+
return {
|
|
344
|
+
version: parsed.version,
|
|
345
|
+
codec: MULTICODEC_NAMES.get(parsed.code) ?? parsed.code,
|
|
346
|
+
hashCode: MULTIHASH_NAMES.get(parsed.multihash.code) ?? parsed.multihash.code,
|
|
347
|
+
digest: parsed.multihash.digest
|
|
348
|
+
};
|
|
348
349
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
350
|
+
var SD_LEAF_DOMAIN_V1 = "optimystic/sd-leaf/v1";
|
|
351
|
+
var SD_SET_DOMAIN_V1 = "optimystic/sd-set/v1";
|
|
352
|
+
var HIDDEN_ENCODING = "base64url";
|
|
353
|
+
function compareBytes(a, b) {
|
|
354
|
+
const len = Math.min(a.length, b.length);
|
|
355
|
+
for (let i = 0; i < len; i++) {
|
|
356
|
+
const d = a[i] - b[i];
|
|
357
|
+
if (d !== 0) return d;
|
|
358
|
+
}
|
|
359
|
+
return a.length - b.length;
|
|
360
|
+
}
|
|
361
|
+
function bytesEqual(a, b) {
|
|
362
|
+
if (a.length !== b.length) return false;
|
|
363
|
+
for (let i = 0; i < a.length; i++) {
|
|
364
|
+
if (a[i] !== b[i]) return false;
|
|
353
365
|
}
|
|
354
|
-
|
|
355
|
-
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
function requireSaltBytes(leaf) {
|
|
369
|
+
const { salt } = leaf;
|
|
370
|
+
if (salt == null) {
|
|
371
|
+
throw new Error(`set commitment: leaf '${leaf.name}' is missing a salt (an unsalted leaf is brute-forceable)`);
|
|
356
372
|
}
|
|
357
|
-
|
|
358
|
-
|
|
373
|
+
const bytes = salt instanceof Uint8Array ? salt : fromString(salt, HIDDEN_ENCODING);
|
|
374
|
+
if (bytes.length === 0) {
|
|
375
|
+
throw new Error(`set commitment: leaf '${leaf.name}' has an empty salt (an unsalted leaf is brute-forceable)`);
|
|
359
376
|
}
|
|
360
|
-
return
|
|
377
|
+
return bytes;
|
|
361
378
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
379
|
+
function assertUniqueNames(leaves) {
|
|
380
|
+
const seen = /* @__PURE__ */ new Set();
|
|
381
|
+
for (const leaf of leaves) {
|
|
382
|
+
if (seen.has(leaf.name)) {
|
|
383
|
+
throw new Error(`set commitment: duplicate leaf name '${leaf.name}'`);
|
|
384
|
+
}
|
|
385
|
+
seen.add(leaf.name);
|
|
365
386
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
387
|
+
}
|
|
388
|
+
function leafDigest(leaf, hasher) {
|
|
389
|
+
const saltBytes = requireSaltBytes(leaf);
|
|
390
|
+
return hasher(encodeFields([SD_LEAF_DOMAIN_V1, leaf.name, leaf.value, saltBytes]));
|
|
391
|
+
}
|
|
392
|
+
function setCommit(leaves, hasher = resolveHasher("sha256"), encode = resolveOutputEncoder("base64url")) {
|
|
393
|
+
assertUniqueNames(leaves);
|
|
394
|
+
const leafDigests = leaves.map((leaf) => leafDigest(leaf, hasher));
|
|
395
|
+
leafDigests.sort(compareBytes);
|
|
396
|
+
return encode(hasher(encodeFields([SD_SET_DOMAIN_V1, ...leafDigests])));
|
|
397
|
+
}
|
|
398
|
+
function setDisclose(leaves, revealNames, hasher = resolveHasher("sha256")) {
|
|
399
|
+
assertUniqueNames(leaves);
|
|
400
|
+
const reveal = new Set(revealNames);
|
|
401
|
+
const disclosed = [];
|
|
402
|
+
const hidden = [];
|
|
403
|
+
for (const leaf of leaves) {
|
|
404
|
+
if (reveal.has(leaf.name)) {
|
|
405
|
+
disclosed.push(leaf);
|
|
406
|
+
} else {
|
|
407
|
+
hidden.push(toString(leafDigest(leaf, hasher), HIDDEN_ENCODING));
|
|
408
|
+
}
|
|
371
409
|
}
|
|
372
|
-
|
|
410
|
+
return { disclosed, hidden };
|
|
373
411
|
}
|
|
374
|
-
function
|
|
412
|
+
function setVerify(root, disclosure, hasher = resolveHasher("sha256"), encode = resolveOutputEncoder("base64url")) {
|
|
375
413
|
try {
|
|
376
|
-
const {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
} = options;
|
|
381
|
-
const normalizedDigest = normalizeBytes(digest2);
|
|
382
|
-
const normalizedSignature = normalizeBytes(signature);
|
|
383
|
-
const normalizedPublicKey = normalizeBytes(publicKey);
|
|
384
|
-
const detectedFormat = signatureFormat || detectSignatureFormat(normalizedSignature, curve);
|
|
385
|
-
const parsedSignature = parseSignature(normalizedSignature, detectedFormat, curve);
|
|
386
|
-
const verifyOptions = {};
|
|
387
|
-
if (curve !== "ed25519" && allowMalleableSignatures !== void 0) {
|
|
388
|
-
verifyOptions.lowS = !allowMalleableSignatures;
|
|
414
|
+
const { disclosed, hidden } = disclosure;
|
|
415
|
+
const digests = [];
|
|
416
|
+
for (const leaf of disclosed) {
|
|
417
|
+
digests.push(leafDigest(leaf, hasher));
|
|
389
418
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return secp256k1.verify(parsedSignature, normalizedDigest, normalizedPublicKey, verifyOptions);
|
|
393
|
-
case "p256":
|
|
394
|
-
return p256.verify(parsedSignature, normalizedDigest, normalizedPublicKey, verifyOptions);
|
|
395
|
-
case "ed25519":
|
|
396
|
-
return ed25519.verify(parsedSignature, normalizedDigest, normalizedPublicKey);
|
|
397
|
-
default:
|
|
398
|
-
throw new Error(`Unsupported curve: ${curve}`);
|
|
419
|
+
for (const h of hidden) {
|
|
420
|
+
digests.push(fromString(h, HIDDEN_ENCODING));
|
|
399
421
|
}
|
|
400
|
-
|
|
422
|
+
digests.sort(compareBytes);
|
|
423
|
+
const recomputed = hasher(encodeFields([SD_SET_DOMAIN_V1, ...digests]));
|
|
424
|
+
if (root instanceof Uint8Array) {
|
|
425
|
+
return bytesEqual(recomputed, root);
|
|
426
|
+
}
|
|
427
|
+
const encoded = encode(recomputed);
|
|
428
|
+
return typeof encoded === "string" && encoded === root;
|
|
429
|
+
} catch {
|
|
401
430
|
return false;
|
|
402
431
|
}
|
|
403
432
|
}
|
|
404
|
-
SignatureValid.secp256k1 = (digest2, signature, publicKey, options = {}) => {
|
|
405
|
-
return SignatureValid(digest2, signature, publicKey, { ...options, curve: "secp256k1" });
|
|
406
|
-
};
|
|
407
|
-
SignatureValid.p256 = (digest2, signature, publicKey, options = {}) => {
|
|
408
|
-
return SignatureValid(digest2, signature, publicKey, { ...options, curve: "p256" });
|
|
409
|
-
};
|
|
410
|
-
SignatureValid.ed25519 = (digest2, signature, publicKey, options = {}) => {
|
|
411
|
-
return SignatureValid(digest2, signature, publicKey, { ...options, curve: "ed25519" });
|
|
412
|
-
};
|
|
413
|
-
SignatureValid.batch = (verifications) => {
|
|
414
|
-
return verifications.map(
|
|
415
|
-
({ digest: digest2, signature, publicKey, options }) => SignatureValid(digest2, signature, publicKey, options)
|
|
416
|
-
);
|
|
417
|
-
};
|
|
418
|
-
SignatureValid.detailed = (digest2, signature, publicKey, options = {}) => {
|
|
419
|
-
const curve = options.curve || "secp256k1";
|
|
420
|
-
try {
|
|
421
|
-
const normalizedSignature = normalizeBytes(signature);
|
|
422
|
-
const detectedFormat = options.signatureFormat || detectSignatureFormat(normalizedSignature, curve);
|
|
423
|
-
const valid = SignatureValid(digest2, signature, publicKey, options);
|
|
424
|
-
return {
|
|
425
|
-
valid,
|
|
426
|
-
curve,
|
|
427
|
-
signatureFormat: detectedFormat
|
|
428
|
-
};
|
|
429
|
-
} catch (error) {
|
|
430
|
-
return {
|
|
431
|
-
valid: false,
|
|
432
|
-
curve,
|
|
433
|
-
signatureFormat: "unknown",
|
|
434
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
433
|
|
|
439
|
-
export {
|
|
434
|
+
export { cid, cidDecode, cidV1, digest, digestFields, encodeFields, generatePrivateKey, getPublicKey, hashMod, leafDigest, randomBytes, resolveHasher, resolveOutputEncoder, setCommit, setDisclose, setVerify, sign, verify };
|
|
440
435
|
//# sourceMappingURL=index.js.map
|
|
441
436
|
//# sourceMappingURL=index.js.map
|