@optimystic/quereus-plugin-crypto 0.13.5 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +259 -16
- package/dist/index.d.ts +224 -198
- package/dist/index.js +272 -272
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +17 -1
- package/dist/plugin.js +435 -23
- package/dist/plugin.js.map +1 -1
- package/package.json +32 -6
- package/src/cid.ts +200 -0
- package/src/crypto.ts +252 -34
- package/src/index.ts +30 -5
- package/src/plugin.ts +202 -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,141 @@ 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));
|
|
172
|
+
}
|
|
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
|
+
if (!Array.isArray(fields)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`digest(fields, algorithm?, encoding?): 'fields' must be an array of values. The digest API changed in v0.14: it is now variadic/injective over fields, the per-call inputEncoding was removed, and algorithm + output encoding are bound at plugin load time. Migrate digest(value, algo, inputEncoding, outputEncoding) \u2192 digest([value], algo, outputEncoding) \u2014 note the result is now a *framed* digest, not a bare hash of the bytes.`
|
|
182
|
+
);
|
|
65
183
|
}
|
|
66
|
-
return
|
|
184
|
+
return digestFields(fields, resolveHasher(algorithm), resolveOutputEncoder(encoding));
|
|
67
185
|
}
|
|
68
186
|
function hashMod(data, bits, algorithm = "sha256", inputEncoding = "base64url") {
|
|
69
187
|
if (bits <= 0 || bits > 53) {
|
|
70
188
|
throw new Error("Bits must be between 1 and 53 (JavaScript safe integer limit)");
|
|
71
189
|
}
|
|
72
|
-
const hashBytes = toBytes(
|
|
190
|
+
const hashBytes = resolveHasher(algorithm)(toBytes(data, inputEncoding));
|
|
73
191
|
const view = new DataView(hashBytes.buffer, hashBytes.byteOffset, Math.min(8, hashBytes.length));
|
|
74
192
|
const fullHash = view.getBigUint64(0, false);
|
|
75
193
|
const modulus = BigInt(2) ** BigInt(bits);
|
|
@@ -157,285 +275,167 @@ function getPublicKey(privateKey, curve = "secp256k1", keyEncoding = "base64url"
|
|
|
157
275
|
}
|
|
158
276
|
return fromBytes(pubBytes, outputEncoding);
|
|
159
277
|
}
|
|
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);
|
|
278
|
+
var MULTICODEC_CODES = {
|
|
279
|
+
"raw": 85,
|
|
280
|
+
"dag-cbor": 113
|
|
216
281
|
};
|
|
217
|
-
|
|
218
|
-
|
|
282
|
+
var MULTIHASH_CODES = {
|
|
283
|
+
"sha2-256": 18,
|
|
284
|
+
"sha2-512": 19,
|
|
285
|
+
"blake3": 30
|
|
219
286
|
};
|
|
220
|
-
|
|
221
|
-
|
|
287
|
+
var MULTIHASH_TO_ALGORITHM = {
|
|
288
|
+
"sha2-256": "sha256",
|
|
289
|
+
"sha2-512": "sha512",
|
|
290
|
+
"blake3": "blake3"
|
|
222
291
|
};
|
|
223
|
-
|
|
224
|
-
|
|
292
|
+
var MULTIHASH_DIGEST_LENGTHS = {
|
|
293
|
+
"sha2-256": 32,
|
|
294
|
+
"sha2-512": 64,
|
|
295
|
+
"blake3": 32
|
|
225
296
|
};
|
|
226
|
-
|
|
227
|
-
|
|
297
|
+
var MULTICODEC_NAMES = new Map(
|
|
298
|
+
Object.entries(MULTICODEC_CODES).map(([name, code]) => [code, name])
|
|
299
|
+
);
|
|
300
|
+
var MULTIHASH_NAMES = new Map(
|
|
301
|
+
Object.entries(MULTIHASH_CODES).map(([name, code]) => [code, name])
|
|
302
|
+
);
|
|
303
|
+
var MULTIBASE_ENCODERS = {
|
|
304
|
+
"base32": base32,
|
|
305
|
+
"base58btc": base58btc,
|
|
306
|
+
"base64url": base64url,
|
|
307
|
+
"base16": base16
|
|
228
308
|
};
|
|
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);
|
|
309
|
+
var MULTIBASE_DECODER = base32.decoder.or(base58btc.decoder).or(base64url.decoder).or(base16.decoder);
|
|
310
|
+
function resolveCodecCode(codec) {
|
|
311
|
+
const code = MULTICODEC_CODES[codec];
|
|
312
|
+
if (code === void 0) {
|
|
313
|
+
throw new Error(`cid: unsupported multicodec '${codec}' (expected one of ${Object.keys(MULTICODEC_CODES).join(", ")})`);
|
|
242
314
|
}
|
|
243
|
-
|
|
315
|
+
return code;
|
|
244
316
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
317
|
+
function resolveBaseEncoder(base) {
|
|
318
|
+
const encoder = MULTIBASE_ENCODERS[base];
|
|
319
|
+
if (!encoder) {
|
|
320
|
+
throw new Error(`cid: unsupported multibase '${base}' (expected one of ${Object.keys(MULTIBASE_ENCODERS).join(", ")})`);
|
|
248
321
|
}
|
|
249
|
-
|
|
250
|
-
return hexToBytes(digest2);
|
|
251
|
-
}
|
|
252
|
-
throw new Error("Invalid digest format");
|
|
322
|
+
return encoder;
|
|
253
323
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
throw new Error(`Unsupported signature format: ${format}`);
|
|
268
|
-
}
|
|
324
|
+
function cidV1(digest2, hash, codec = "raw", base = "base32") {
|
|
325
|
+
const hashCode = MULTIHASH_CODES[hash];
|
|
326
|
+
if (hashCode === void 0) {
|
|
327
|
+
throw new Error(`cid: unsupported multihash code '${hash}' (expected one of ${Object.keys(MULTIHASH_CODES).join(", ")})`);
|
|
328
|
+
}
|
|
329
|
+
const expectedLength = MULTIHASH_DIGEST_LENGTHS[hash];
|
|
330
|
+
if (digest2.length !== expectedLength) {
|
|
331
|
+
throw new Error(`cid: digest length ${digest2.length} does not match asserted hash '${hash}' (expected ${expectedLength} bytes)`);
|
|
332
|
+
}
|
|
333
|
+
const codecCode = resolveCodecCode(codec);
|
|
334
|
+
const encoder = resolveBaseEncoder(base);
|
|
335
|
+
const multihash = Digest.create(hashCode, digest2);
|
|
336
|
+
return CID.createV1(codecCode, multihash).toString(encoder);
|
|
269
337
|
}
|
|
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}`);
|
|
338
|
+
function cid(data, codec = "raw", hash = "sha2-256", base = "base32") {
|
|
339
|
+
const algorithm = MULTIHASH_TO_ALGORITHM[hash];
|
|
340
|
+
if (!algorithm) {
|
|
341
|
+
throw new Error(`cid: unsupported multihash code '${hash}' (expected one of ${Object.keys(MULTIHASH_CODES).join(", ")})`);
|
|
303
342
|
}
|
|
304
|
-
|
|
343
|
+
const digest2 = resolveHasher(algorithm)(data);
|
|
344
|
+
return cidV1(digest2, hash, codec, base);
|
|
305
345
|
}
|
|
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");
|
|
346
|
+
function cidDecode(value) {
|
|
347
|
+
const parsed = CID.parse(value, MULTIBASE_DECODER);
|
|
348
|
+
return {
|
|
349
|
+
version: parsed.version,
|
|
350
|
+
codec: MULTICODEC_NAMES.get(parsed.code) ?? parsed.code,
|
|
351
|
+
hashCode: MULTIHASH_NAMES.get(parsed.multihash.code) ?? parsed.multihash.code,
|
|
352
|
+
digest: parsed.multihash.digest
|
|
353
|
+
};
|
|
348
354
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
355
|
+
var SD_LEAF_DOMAIN_V1 = "optimystic/sd-leaf/v1";
|
|
356
|
+
var SD_SET_DOMAIN_V1 = "optimystic/sd-set/v1";
|
|
357
|
+
var HIDDEN_ENCODING = "base64url";
|
|
358
|
+
function compareBytes(a, b) {
|
|
359
|
+
const len = Math.min(a.length, b.length);
|
|
360
|
+
for (let i = 0; i < len; i++) {
|
|
361
|
+
const d = a[i] - b[i];
|
|
362
|
+
if (d !== 0) return d;
|
|
363
|
+
}
|
|
364
|
+
return a.length - b.length;
|
|
365
|
+
}
|
|
366
|
+
function bytesEqual(a, b) {
|
|
367
|
+
if (a.length !== b.length) return false;
|
|
368
|
+
for (let i = 0; i < a.length; i++) {
|
|
369
|
+
if (a[i] !== b[i]) return false;
|
|
353
370
|
}
|
|
354
|
-
|
|
355
|
-
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
function requireSaltBytes(leaf) {
|
|
374
|
+
const { salt } = leaf;
|
|
375
|
+
if (salt == null) {
|
|
376
|
+
throw new Error(`set commitment: leaf '${leaf.name}' is missing a salt (an unsalted leaf is brute-forceable)`);
|
|
356
377
|
}
|
|
357
|
-
|
|
358
|
-
|
|
378
|
+
const bytes = salt instanceof Uint8Array ? salt : fromString(salt, HIDDEN_ENCODING);
|
|
379
|
+
if (bytes.length === 0) {
|
|
380
|
+
throw new Error(`set commitment: leaf '${leaf.name}' has an empty salt (an unsalted leaf is brute-forceable)`);
|
|
359
381
|
}
|
|
360
|
-
return
|
|
382
|
+
return bytes;
|
|
361
383
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
384
|
+
function assertUniqueNames(leaves) {
|
|
385
|
+
const seen = /* @__PURE__ */ new Set();
|
|
386
|
+
for (const leaf of leaves) {
|
|
387
|
+
if (seen.has(leaf.name)) {
|
|
388
|
+
throw new Error(`set commitment: duplicate leaf name '${leaf.name}'`);
|
|
389
|
+
}
|
|
390
|
+
seen.add(leaf.name);
|
|
365
391
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
392
|
+
}
|
|
393
|
+
function leafDigest(leaf, hasher) {
|
|
394
|
+
const saltBytes = requireSaltBytes(leaf);
|
|
395
|
+
return hasher(encodeFields([SD_LEAF_DOMAIN_V1, leaf.name, leaf.value, saltBytes]));
|
|
396
|
+
}
|
|
397
|
+
function setCommit(leaves, hasher = resolveHasher("sha256"), encode = resolveOutputEncoder("base64url")) {
|
|
398
|
+
assertUniqueNames(leaves);
|
|
399
|
+
const leafDigests = leaves.map((leaf) => leafDigest(leaf, hasher));
|
|
400
|
+
leafDigests.sort(compareBytes);
|
|
401
|
+
return encode(hasher(encodeFields([SD_SET_DOMAIN_V1, ...leafDigests])));
|
|
402
|
+
}
|
|
403
|
+
function setDisclose(leaves, revealNames, hasher = resolveHasher("sha256")) {
|
|
404
|
+
assertUniqueNames(leaves);
|
|
405
|
+
const reveal = new Set(revealNames);
|
|
406
|
+
const disclosed = [];
|
|
407
|
+
const hidden = [];
|
|
408
|
+
for (const leaf of leaves) {
|
|
409
|
+
if (reveal.has(leaf.name)) {
|
|
410
|
+
disclosed.push(leaf);
|
|
411
|
+
} else {
|
|
412
|
+
hidden.push(toString(leafDigest(leaf, hasher), HIDDEN_ENCODING));
|
|
413
|
+
}
|
|
371
414
|
}
|
|
372
|
-
|
|
415
|
+
return { disclosed, hidden };
|
|
373
416
|
}
|
|
374
|
-
function
|
|
417
|
+
function setVerify(root, disclosure, hasher = resolveHasher("sha256"), encode = resolveOutputEncoder("base64url")) {
|
|
375
418
|
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;
|
|
419
|
+
const { disclosed, hidden } = disclosure;
|
|
420
|
+
const digests = [];
|
|
421
|
+
for (const leaf of disclosed) {
|
|
422
|
+
digests.push(leafDigest(leaf, hasher));
|
|
389
423
|
}
|
|
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}`);
|
|
424
|
+
for (const h of hidden) {
|
|
425
|
+
digests.push(fromString(h, HIDDEN_ENCODING));
|
|
399
426
|
}
|
|
400
|
-
|
|
427
|
+
digests.sort(compareBytes);
|
|
428
|
+
const recomputed = hasher(encodeFields([SD_SET_DOMAIN_V1, ...digests]));
|
|
429
|
+
if (root instanceof Uint8Array) {
|
|
430
|
+
return bytesEqual(recomputed, root);
|
|
431
|
+
}
|
|
432
|
+
const encoded = encode(recomputed);
|
|
433
|
+
return typeof encoded === "string" && encoded === root;
|
|
434
|
+
} catch {
|
|
401
435
|
return false;
|
|
402
436
|
}
|
|
403
437
|
}
|
|
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
438
|
|
|
439
|
-
export {
|
|
439
|
+
export { cid, cidDecode, cidV1, digest, digestFields, encodeFields, generatePrivateKey, getPublicKey, hashMod, leafDigest, randomBytes, resolveHasher, resolveOutputEncoder, setCommit, setDisclose, setVerify, sign, verify };
|
|
440
440
|
//# sourceMappingURL=index.js.map
|
|
441
441
|
//# sourceMappingURL=index.js.map
|