@optimystic/quereus-plugin-crypto 0.13.5 → 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.d.ts
CHANGED
|
@@ -4,31 +4,79 @@
|
|
|
4
4
|
* Idiomatic ES module exports with base64url as default encoding.
|
|
5
5
|
* All functions accept and return base64url strings by default for SQL compatibility.
|
|
6
6
|
*/
|
|
7
|
-
type HashAlgorithm
|
|
8
|
-
type CurveType
|
|
7
|
+
type HashAlgorithm = 'sha256' | 'sha512' | 'blake3';
|
|
8
|
+
type CurveType = 'secp256k1' | 'p256' | 'ed25519';
|
|
9
9
|
type Encoding = 'base64url' | 'base64' | 'hex' | 'utf8' | 'bytes';
|
|
10
|
+
/** Encodings valid for hash *output* (no 'utf8' — a digest is not UTF-8 text). */
|
|
11
|
+
type OutputEncoding = 'base64url' | 'base64' | 'hex' | 'bytes';
|
|
12
|
+
/** A single value in a multi-field digest. Mirrors the SQL value space. */
|
|
13
|
+
type DigestField = string | number | bigint | boolean | Uint8Array | null | undefined | {
|
|
14
|
+
readonly [key: string]: unknown;
|
|
15
|
+
} | readonly unknown[];
|
|
16
|
+
/** A resolved hash function: raw bytes in, digest bytes out. */
|
|
17
|
+
type DigestHasher = (input: Uint8Array) => Uint8Array;
|
|
18
|
+
/** A resolved output encoder: digest bytes in, encoded form out. */
|
|
19
|
+
type OutputEncoder = (bytes: Uint8Array) => string | Uint8Array;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a hash algorithm name to its hasher. Throws on unknown algorithm.
|
|
22
|
+
* Call once (e.g. at plugin registration) and capture the result so the digest
|
|
23
|
+
* hot path performs no per-call algorithm branching.
|
|
24
|
+
*/
|
|
25
|
+
declare function resolveHasher(algorithm: HashAlgorithm): DigestHasher;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an output encoding name to its encoder. Throws on unknown encoding.
|
|
28
|
+
*/
|
|
29
|
+
declare function resolveOutputEncoder(encoding: OutputEncoding): OutputEncoder;
|
|
30
|
+
/**
|
|
31
|
+
* Canonically encode an ordered tuple of fields into bytes such that distinct
|
|
32
|
+
* tuples never collide (injective framing).
|
|
33
|
+
*
|
|
34
|
+
* Layout: `version ‖ field*` where each field is `tag ‖ varint(len) ‖ payload`
|
|
35
|
+
* (NULL is a bare tag). Properties:
|
|
36
|
+
* - order-preserving and arity-safe (self-delimiting fields → uniquely decodable),
|
|
37
|
+
* - NULL distinguishable from empty string,
|
|
38
|
+
* - type distinguishable (INTEGER 123 ≠ TEXT '123' ≠ BOOL true ≠ BLOB),
|
|
39
|
+
* - delimiter-safe (a separator inside a string is just payload under its length).
|
|
40
|
+
*
|
|
41
|
+
* Replicability notes:
|
|
42
|
+
* - Integer `number` and `bigint` of equal value encode identically (both via
|
|
43
|
+
* `BigInt(...).toString()`); a non-integer REAL uses ECMAScript `Number::toString`
|
|
44
|
+
* (deterministic across JS engines, but not guaranteed across other languages).
|
|
45
|
+
* - INT vs REAL is derived from the JS value, not SQL affinity: an integer-valued
|
|
46
|
+
* REAL (e.g. 2.0 → number 2) encodes as INTEGER, so INTEGER 2 and REAL 2.0 collide.
|
|
47
|
+
* - A native JSON object/array field must contain only valid JSON (no `undefined`,
|
|
48
|
+
* non-finite numbers, `bigint`, or non-plain objects) — otherwise it throws.
|
|
49
|
+
*/
|
|
50
|
+
declare function encodeFields(fields: readonly DigestField[]): Uint8Array;
|
|
10
51
|
/**
|
|
11
|
-
*
|
|
52
|
+
* Low-level multi-field digest: canonically encode the fields, then hash and
|
|
53
|
+
* encode with the supplied (pre-resolved) hasher/encoder. No per-call branching
|
|
54
|
+
* on algorithm or encoding — resolve once via {@link resolveHasher} /
|
|
55
|
+
* {@link resolveOutputEncoder} and reuse.
|
|
56
|
+
*/
|
|
57
|
+
declare function digestFields(fields: readonly DigestField[], hasher: DigestHasher, encode: OutputEncoder): string | Uint8Array;
|
|
58
|
+
/**
|
|
59
|
+
* Compute an injective digest over an ordered tuple of fields.
|
|
12
60
|
*
|
|
13
|
-
* @param
|
|
61
|
+
* @param fields - Ordered tuple of values to hash (any SQL value type)
|
|
14
62
|
* @param algorithm - Hash algorithm (default: 'sha256')
|
|
15
|
-
* @param
|
|
16
|
-
* @
|
|
17
|
-
* @returns Hash digest in specified encoding
|
|
63
|
+
* @param encoding - Output encoding (default: 'base64url')
|
|
64
|
+
* @returns Hash digest in the specified encoding
|
|
18
65
|
*
|
|
19
66
|
* @example
|
|
20
67
|
* ```typescript
|
|
21
|
-
* // Hash
|
|
22
|
-
* const
|
|
68
|
+
* // Hash a tuple of fields — distinct tuples never collide
|
|
69
|
+
* const h = digest(['alice', 42, null, true]);
|
|
23
70
|
*
|
|
24
|
-
* //
|
|
25
|
-
* const
|
|
26
|
-
*
|
|
27
|
-
* // Get raw bytes
|
|
28
|
-
* const bytes = digest('data', 'blake3', 'utf8', 'bytes');
|
|
71
|
+
* // Pick algorithm / output encoding
|
|
72
|
+
* const h512 = digest(['a', 'b'], 'sha512', 'hex');
|
|
29
73
|
* ```
|
|
74
|
+
*
|
|
75
|
+
* Note: this is a *framed* digest, not a bare hash of raw bytes —
|
|
76
|
+
* `digest(['hello'])` is not `sha256("hello")`. Use `hashMod` for sharding a
|
|
77
|
+
* single value.
|
|
30
78
|
*/
|
|
31
|
-
declare function digest(
|
|
79
|
+
declare function digest(fields: readonly DigestField[], algorithm?: HashAlgorithm, encoding?: OutputEncoding): string | Uint8Array;
|
|
32
80
|
/**
|
|
33
81
|
* Hash data and return modulo of specified bit length
|
|
34
82
|
* Useful for generating fixed-size hash values (e.g., 16-bit, 32-bit)
|
|
@@ -48,7 +96,7 @@ declare function digest(data: string | Uint8Array, algorithm?: HashAlgorithm$1,
|
|
|
48
96
|
* const hash32 = hashMod('world', 32, 'sha256', 'utf8');
|
|
49
97
|
* ```
|
|
50
98
|
*/
|
|
51
|
-
declare function hashMod(data: string | Uint8Array, bits: number, algorithm?: HashAlgorithm
|
|
99
|
+
declare function hashMod(data: string | Uint8Array, bits: number, algorithm?: HashAlgorithm, inputEncoding?: Encoding): number;
|
|
52
100
|
/**
|
|
53
101
|
* Sign data with a private key
|
|
54
102
|
*
|
|
@@ -69,7 +117,7 @@ declare function hashMod(data: string | Uint8Array, bits: number, algorithm?: Ha
|
|
|
69
117
|
* const sig2 = sign(hashData, privateKey, 'ed25519');
|
|
70
118
|
* ```
|
|
71
119
|
*/
|
|
72
|
-
declare function sign(data: string | Uint8Array, privateKey: string | Uint8Array, curve?: CurveType
|
|
120
|
+
declare function sign(data: string | Uint8Array, privateKey: string | Uint8Array, curve?: CurveType, inputEncoding?: Encoding, keyEncoding?: Encoding, outputEncoding?: Encoding): string | Uint8Array;
|
|
73
121
|
/**
|
|
74
122
|
* Verify a signature
|
|
75
123
|
*
|
|
@@ -91,7 +139,7 @@ declare function sign(data: string | Uint8Array, privateKey: string | Uint8Array
|
|
|
91
139
|
* const isValid2 = verify(hashData, signature, publicKey, 'ed25519');
|
|
92
140
|
* ```
|
|
93
141
|
*/
|
|
94
|
-
declare function verify(data: string | Uint8Array, signature: string | Uint8Array, publicKey: string | Uint8Array, curve?: CurveType
|
|
142
|
+
declare function verify(data: string | Uint8Array, signature: string | Uint8Array, publicKey: string | Uint8Array, curve?: CurveType, inputEncoding?: Encoding, sigEncoding?: Encoding, keyEncoding?: Encoding): boolean;
|
|
95
143
|
/**
|
|
96
144
|
* Generate cryptographically secure random bytes
|
|
97
145
|
*
|
|
@@ -103,215 +151,193 @@ declare function randomBytes(bits?: number, encoding?: Encoding): string | Uint8
|
|
|
103
151
|
/**
|
|
104
152
|
* Generate a random private key
|
|
105
153
|
*/
|
|
106
|
-
declare function generatePrivateKey(curve?: CurveType
|
|
154
|
+
declare function generatePrivateKey(curve?: CurveType, encoding?: Encoding): string | Uint8Array;
|
|
107
155
|
/**
|
|
108
156
|
* Get public key from private key
|
|
109
157
|
*/
|
|
110
|
-
declare function getPublicKey(privateKey: string | Uint8Array, curve?: CurveType
|
|
158
|
+
declare function getPublicKey(privateKey: string | Uint8Array, curve?: CurveType, keyEncoding?: Encoding, outputEncoding?: Encoding): string | Uint8Array;
|
|
111
159
|
|
|
112
160
|
/**
|
|
113
|
-
*
|
|
161
|
+
* Self-describing content identifiers (CIDv1) for Quereus.
|
|
114
162
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*/
|
|
119
|
-
/**
|
|
120
|
-
* Hash algorithm options
|
|
121
|
-
*/
|
|
122
|
-
type HashAlgorithm = 'sha256' | 'sha512' | 'blake3';
|
|
123
|
-
/**
|
|
124
|
-
* Input type for digest function - can be string, Uint8Array, or number
|
|
125
|
-
*/
|
|
126
|
-
type DigestInput$1 = string | Uint8Array | number | boolean | null | undefined;
|
|
127
|
-
/**
|
|
128
|
-
* Options for the digest function
|
|
129
|
-
*/
|
|
130
|
-
interface DigestOptions {
|
|
131
|
-
/** Hash algorithm to use (default: sha256) */
|
|
132
|
-
algorithm?: HashAlgorithm;
|
|
133
|
-
/** Output format (default: uint8array) */
|
|
134
|
-
output?: 'uint8array' | 'hex';
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Computes the hash of all arguments
|
|
163
|
+
* Where {@link ./crypto.ts | digest} emits a *bare* hash (raw digest bytes in
|
|
164
|
+
* some text encoding), this module emits an interoperable, self-describing
|
|
165
|
+
* CIDv1:
|
|
138
166
|
*
|
|
139
|
-
* @param {...DigestInput} args - Variable number of arguments to hash
|
|
140
|
-
* @returns {Uint8Array} The computed hash as a Uint8Array
|
|
141
|
-
*
|
|
142
|
-
* @example
|
|
143
|
-
* ```typescript
|
|
144
|
-
* // Hash a string
|
|
145
|
-
* const hash1 = Digest('hello world');
|
|
146
|
-
*
|
|
147
|
-
* // Hash multiple arguments
|
|
148
|
-
* const hash2 = Digest('user:', 123, 'session');
|
|
149
|
-
*
|
|
150
|
-
* // Hash with specific algorithm
|
|
151
|
-
* const hash3 = Digest.withOptions({ algorithm: 'sha512' })('data1', 'data2');
|
|
152
|
-
*
|
|
153
|
-
* // Get hex output
|
|
154
|
-
* const hexHash = Digest.withOptions({ output: 'hex' })('hello');
|
|
155
167
|
* ```
|
|
168
|
+
* CIDv1 = multibase( version ‖ multicodec(content-type) ‖ multihash )
|
|
169
|
+
* multihash = hashFnCode ‖ digestLength ‖ digestBytes
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* The value carries its own multibase, multicodec (content type), and multihash
|
|
173
|
+
* (hash algorithm + length), so a consumer can decode it without out-of-band
|
|
174
|
+
* knowledge, and an algorithm migration (e.g. sha2-256 → another hash) is
|
|
175
|
+
* unambiguous because the hash code is recorded *in the value*.
|
|
176
|
+
*
|
|
177
|
+
* All framing/parsing comes from the audited `multiformats` library — there is
|
|
178
|
+
* no bespoke byte-pushing here. The actual hashing reuses the same synchronous,
|
|
179
|
+
* cross-platform `@noble/hashes` functions the rest of the plugin uses (via
|
|
180
|
+
* {@link resolveHasher}), so the output is byte-identical to the CID an external
|
|
181
|
+
* content-addressed store (IPFS/IPLD) computes for the same bytes:
|
|
182
|
+
* `cid(utf8('hello world'))` === `bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e`.
|
|
156
183
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
/** Content-type multicodec selectable for the CID. Extensible. */
|
|
185
|
+
type Multicodec = 'raw' | 'dag-cbor';
|
|
186
|
+
/** Hash-algorithm multihash code selectable for the CID. */
|
|
187
|
+
type MultihashCode = 'sha2-256' | 'sha2-512' | 'blake3';
|
|
188
|
+
/** Multibase the CID string is rendered in. */
|
|
189
|
+
type Multibase = 'base32' | 'base58btc' | 'base64url' | 'base16';
|
|
190
|
+
/** Parsed parts of a CIDv1 (or CIDv0), as returned by {@link cidDecode}. */
|
|
191
|
+
interface CidParts {
|
|
192
|
+
/** CID version (1 for the values this module produces; 0 for legacy CIDv0). */
|
|
193
|
+
readonly version: number;
|
|
194
|
+
/** Content-type codec name when recognized, else the raw multicodec number. */
|
|
195
|
+
readonly codec: Multicodec | number;
|
|
196
|
+
/** Hash-algorithm code name when recognized, else the raw multihash number. */
|
|
197
|
+
readonly hashCode: MultihashCode | number;
|
|
198
|
+
/** Raw digest bytes (without the multihash code/length prefix). */
|
|
199
|
+
readonly digest: Uint8Array;
|
|
167
200
|
}
|
|
168
|
-
|
|
169
201
|
/**
|
|
170
|
-
*
|
|
202
|
+
* Frame an **already-computed** digest as a CIDv1 string. The caller asserts
|
|
203
|
+
* which `hash` produced the digest; the digest length is validated against that
|
|
204
|
+
* hash so a mismatched assertion is rejected rather than silently mis-framed.
|
|
171
205
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
*
|
|
178
|
-
|
|
179
|
-
type CurveType$1 = 'secp256k1' | 'p256' | 'ed25519';
|
|
180
|
-
/**
|
|
181
|
-
* Private key input - can be Uint8Array, hex string, or bigint
|
|
182
|
-
*/
|
|
183
|
-
type PrivateKeyInput = Uint8Array | string | bigint;
|
|
184
|
-
/**
|
|
185
|
-
* Digest input - can be Uint8Array or hex string
|
|
206
|
+
* Use this to turn an existing field-tuple digest into a CID without re-hashing,
|
|
207
|
+
* e.g. `cidV1(digest(fields, 'sha256', 'bytes'), 'sha2-256')`.
|
|
208
|
+
*
|
|
209
|
+
* @param digest - Raw digest bytes (no multihash prefix).
|
|
210
|
+
* @param hash - The multihash code asserting which algorithm produced `digest`.
|
|
211
|
+
* @param codec - Content-type multicodec (default `'raw'`).
|
|
212
|
+
* @param base - Multibase to render in (default `'base32'`, the IPFS canonical).
|
|
186
213
|
*/
|
|
187
|
-
|
|
214
|
+
declare function cidV1(digest: Uint8Array, hash: MultihashCode, codec?: Multicodec, base?: Multibase): string;
|
|
188
215
|
/**
|
|
189
|
-
*
|
|
216
|
+
* Hash `data`, wrap the digest as a multihash, frame it as a CIDv1, and encode
|
|
217
|
+
* in `base`. The result is the same interoperable address an IPFS/IPLD store
|
|
218
|
+
* computes for the same bytes (for the matching codec/hash).
|
|
219
|
+
*
|
|
220
|
+
* @param data - The content bytes to address.
|
|
221
|
+
* @param codec - Content-type multicodec (default `'raw'`).
|
|
222
|
+
* @param hash - Hash algorithm (default `'sha2-256'`).
|
|
223
|
+
* @param base - Multibase to render in (default `'base32'`, the IPFS canonical).
|
|
190
224
|
*/
|
|
191
|
-
|
|
225
|
+
declare function cid(data: Uint8Array, codec?: Multicodec, hash?: MultihashCode, base?: Multibase): string;
|
|
192
226
|
/**
|
|
193
|
-
*
|
|
227
|
+
* Parse a CID string back into its parts, for schema validation and migration.
|
|
228
|
+
* Recognized codec/hash codes are returned as friendly names; unrecognized ones
|
|
229
|
+
* as their raw numbers. Throws cleanly on malformed input (delegated to
|
|
230
|
+
* `multiformats`), never silently mis-framing.
|
|
194
231
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
curve?: CurveType$1;
|
|
198
|
-
/** Output format for signature (default: uint8array) */
|
|
199
|
-
format?: SignatureFormat;
|
|
200
|
-
/** Additional entropy for signatures (hedged signatures) */
|
|
201
|
-
extraEntropy?: boolean | Uint8Array;
|
|
202
|
-
/** Use low-S canonical signatures (default: true) */
|
|
203
|
-
lowS?: boolean;
|
|
204
|
-
}
|
|
232
|
+
declare function cidDecode(value: string): CidParts;
|
|
233
|
+
|
|
205
234
|
/**
|
|
206
|
-
*
|
|
235
|
+
* Salted-leaf SET COMMITMENT for per-attribute selective disclosure.
|
|
207
236
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
237
|
+
* An authority commits to a whole set of attributes as a single root value (which
|
|
238
|
+
* it signs / persists), then later reveals only a chosen *subset* to a recipient —
|
|
239
|
+
* with a proof that the revealed values are genuinely the committed ones — without
|
|
240
|
+
* leaking the values of the withheld attributes. A flat `digest(whole set)` cannot
|
|
241
|
+
* do this (verifying one field needs the whole pre-image, so it is all-or-nothing);
|
|
242
|
+
* this construction supports *partial opening*.
|
|
243
|
+
*
|
|
244
|
+
* ## Construction (flat salted-leaf set commitment, NOT a Merkle tree)
|
|
245
|
+
*
|
|
246
|
+
* Each disclosable attribute is a salted leaf, and the commitment (root) is the
|
|
247
|
+
* digest of all leaf digests in canonical order:
|
|
212
248
|
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```typescript
|
|
215
|
-
* // Basic usage with secp256k1
|
|
216
|
-
* const digest = new Uint8Array(32).fill(1); // Your hash here
|
|
217
|
-
* const privateKey = 'a'.repeat(64); // Your private key hex
|
|
218
|
-
* const signature = Sign(digest, privateKey);
|
|
219
|
-
*
|
|
220
|
-
* // With specific curve and format
|
|
221
|
-
* const sig = Sign(digest, privateKey, {
|
|
222
|
-
* curve: 'p256',
|
|
223
|
-
* format: 'hex'
|
|
224
|
-
* });
|
|
225
|
-
*
|
|
226
|
-
* // With hedged signatures for extra security
|
|
227
|
-
* const hedgedSig = Sign(digest, privateKey, {
|
|
228
|
-
* extraEntropy: true
|
|
229
|
-
* });
|
|
230
249
|
* ```
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
var secp256k1: (digest: DigestInput, privateKey: PrivateKeyInput, options?: Omit<SignOptions, "curve">) => string | Uint8Array<ArrayBufferLike>;
|
|
235
|
-
var p256: (digest: DigestInput, privateKey: PrivateKeyInput, options?: Omit<SignOptions, "curve">) => string | Uint8Array<ArrayBufferLike>;
|
|
236
|
-
var ed25519: (digest: DigestInput, privateKey: PrivateKeyInput, options?: Omit<SignOptions, "curve">) => string | Uint8Array<ArrayBufferLike>;
|
|
237
|
-
var generatePrivateKey: (curve?: CurveType$1) => Uint8Array;
|
|
238
|
-
var getPublicKey: (privateKey: PrivateKeyInput, curve?: CurveType$1) => Uint8Array;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* SignatureValid Function for Quereus
|
|
250
|
+
* leafDigest = digest([SD_LEAF_DOMAIN_V1, name, value, salt]) // raw digest bytes
|
|
251
|
+
* root = digest([SD_SET_DOMAIN_V1, sortedLeaf_0, sortedLeaf_1, ...])
|
|
252
|
+
* ```
|
|
243
253
|
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
254
|
+
* Both layers compose on the existing canonical {@link encodeFields} framing
|
|
255
|
+
* (injective, type-tagged, length-prefixed, replicable) — the same layering the CID
|
|
256
|
+
* work uses — so a *generic* salted-set primitive is simultaneously reusable and
|
|
257
|
+
* fully DB-enforceable. This is the same shape the IETF SD-JWT standard settled on
|
|
258
|
+
* (flat salted hashes, not a tree); we are NOT wire-compatible with SD-JWT (we reuse
|
|
259
|
+
* Optimystic's own `encodeFields` framing for cross-peer replicability) — SD-JWT is
|
|
260
|
+
* cited only as conceptual precedent that the smaller construction is the right one.
|
|
261
|
+
*
|
|
262
|
+
* Voter selective-disclosure field sets are small (a handful to a few dozen fields),
|
|
263
|
+
* so a tree's only advantage — O(log n) proof size — is marginal, while a tree drags
|
|
264
|
+
* in real footguns we would have to hand-roll and pin (arity, odd-node handling /
|
|
265
|
+
* the CVE-2012-2459 duplicate-leaf forgery class, leaf-vs-internal domain separation,
|
|
266
|
+
* and a separate audit-path proof format). A flat construction avoids all of them.
|
|
267
|
+
*
|
|
268
|
+
* ## Why these specific choices
|
|
269
|
+
*
|
|
270
|
+
* - **`name` is hashed into the leaf** so a disclosed `(value, salt)` proof cannot be
|
|
271
|
+
* replayed against a different attribute slot (e.g. presenting an `over18=true`
|
|
272
|
+
* proof as the `citizen` field). The binding is free given `encodeFields` framing.
|
|
273
|
+
* - **`salt` is per-leaf and mandatory** — low-entropy attributes (DOB, booleans, ZIP)
|
|
274
|
+
* are brute-forceable from a bare hash, and independent salts also defeat cross-
|
|
275
|
+
* registrant equality correlation. Salts come from `random_bytes` (≥128 bits).
|
|
276
|
+
* - **Canonical order is by raw leaf-digest bytes (lexicographic), and this is FORCED,
|
|
277
|
+
* not a preference.** In a disclosure the verifier learns the *names* of only the
|
|
278
|
+
* disclosed leaves; the withheld leaves arrive as opaque digests with no name. So the
|
|
279
|
+
* verifier can re-derive the root only if the ordering key is something it holds for
|
|
280
|
+
* *every* leaf — the leaf digest itself. Sorting by name would be unverifiable for
|
|
281
|
+
* hidden leaves. Do NOT "tidy" this into a name sort.
|
|
282
|
+
* - Sort is over **raw digest bytes**, never over encoded strings — an encoding-
|
|
283
|
+
* dependent ordering would break cross-peer agreement. Output encoding applies only
|
|
284
|
+
* to the final root.
|
|
285
|
+
*
|
|
286
|
+
* Because leaf and root reuse `encodeFields`, a future `DIGEST_FORMAT_V1` bump changes
|
|
287
|
+
* `setCommit` output too; this coupling is intentional (one canonical framing).
|
|
247
288
|
*/
|
|
289
|
+
|
|
290
|
+
/** One disclosable attribute. `value` spans the SQL value space ({@link DigestField}). */
|
|
291
|
+
interface SaltedLeaf {
|
|
292
|
+
readonly name: string;
|
|
293
|
+
readonly value: DigestField;
|
|
294
|
+
/** base64url text (e.g. from `random_bytes`) or raw bytes. Mandatory, non-empty. */
|
|
295
|
+
readonly salt: string | Uint8Array;
|
|
296
|
+
}
|
|
297
|
+
/** A disclosure payload sent to a recipient. */
|
|
298
|
+
interface SetDisclosure {
|
|
299
|
+
/** The opened `(name, value, salt)` triples. */
|
|
300
|
+
readonly disclosed: readonly SaltedLeaf[];
|
|
301
|
+
/** Opaque leaf digests (base64url) of the withheld leaves — no name, no value, no salt. */
|
|
302
|
+
readonly hidden: readonly string[];
|
|
303
|
+
}
|
|
248
304
|
/**
|
|
249
|
-
*
|
|
305
|
+
* Raw leaf digest bytes for one salted leaf: `digest([SD_LEAF_DOMAIN_V1, name,
|
|
306
|
+
* value, salt])`. Domain-separated (can never equal a root) and name-bound (a
|
|
307
|
+
* `(value, salt)` proof cannot be replayed under another attribute name). THROWS on
|
|
308
|
+
* a missing/empty salt.
|
|
250
309
|
*/
|
|
251
|
-
|
|
310
|
+
declare function leafDigest(leaf: SaltedLeaf, hasher: DigestHasher): Uint8Array;
|
|
252
311
|
/**
|
|
253
|
-
*
|
|
312
|
+
* Commit to a SET of salted leaves → a single root (the signed/persisted value).
|
|
313
|
+
* Sorts leaves by raw leaf-digest bytes, then digests them under `SD_SET_DOMAIN_V1`.
|
|
314
|
+
* Like `digest`, this emits a BARE digest — apply `cid()` on top for the self-
|
|
315
|
+
* describing column representation (`cid(set_commit(...))`).
|
|
316
|
+
*
|
|
317
|
+
* The empty set is well-defined (the digest of `[SD_SET_DOMAIN_V1]`), not an error.
|
|
318
|
+
* THROWS on a duplicate `name` or a missing/empty `salt` (invalid states made
|
|
319
|
+
* impossible). Resolve `hasher`/`encode` once and reuse — no per-call branching.
|
|
254
320
|
*/
|
|
255
|
-
|
|
321
|
+
declare function setCommit(leaves: readonly SaltedLeaf[], hasher?: DigestHasher, encode?: OutputEncoder): string | Uint8Array;
|
|
256
322
|
/**
|
|
257
|
-
*
|
|
323
|
+
* Split a leaf set into the revealed `(name, value, salt)` triples plus the opaque
|
|
324
|
+
* leaf digests (base64url) of the rest. Withheld `value`/`salt` never appear in the
|
|
325
|
+
* output. Names in `revealNames` that match no leaf are simply not disclosed.
|
|
326
|
+
* THROWS on a duplicate `name` or a missing/empty salt of a withheld leaf.
|
|
258
327
|
*/
|
|
259
|
-
|
|
260
|
-
/** Elliptic curve to use (default: secp256k1) */
|
|
261
|
-
curve?: CurveType;
|
|
262
|
-
/** Signature format (default: auto-detect) */
|
|
263
|
-
signatureFormat?: 'compact' | 'der' | 'raw';
|
|
264
|
-
/** Allow malleable signatures (default: false for ECDSA, true for EdDSA) */
|
|
265
|
-
allowMalleableSignatures?: boolean;
|
|
266
|
-
}
|
|
328
|
+
declare function setDisclose(leaves: readonly SaltedLeaf[], revealNames: readonly string[], hasher?: DigestHasher): SetDisclosure;
|
|
267
329
|
/**
|
|
268
|
-
* Verify
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* // Basic usage with secp256k1
|
|
279
|
-
* const isValid = SignatureValid(digest, signature, publicKey);
|
|
280
|
-
*
|
|
281
|
-
* // With specific curve
|
|
282
|
-
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
283
|
-
* curve: 'p256'
|
|
284
|
-
* });
|
|
285
|
-
*
|
|
286
|
-
* // With specific signature format
|
|
287
|
-
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
288
|
-
* curve: 'secp256k1',
|
|
289
|
-
* signatureFormat: 'der'
|
|
290
|
-
* });
|
|
291
|
-
*
|
|
292
|
-
* // Allow malleable signatures
|
|
293
|
-
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
294
|
-
* allowMalleableSignatures: true
|
|
295
|
-
* });
|
|
296
|
-
* ```
|
|
330
|
+
* Verify a disclosure against a signed root. Recomputes the disclosed leaves'
|
|
331
|
+
* digests, unions them with the supplied hidden digests, sorts by bytes, recomputes
|
|
332
|
+
* the root, and compares to `root`. This reconstructs the ENTIRE root, so it proves
|
|
333
|
+
* the disclosed leaves belong to *exactly* this committed set — the holder cannot
|
|
334
|
+
* add, drop, or swap a leaf (the leaf count is bound too).
|
|
335
|
+
*
|
|
336
|
+
* `encode` is how the signed `root` is rendered (so the recomputed root is encoded
|
|
337
|
+
* the same way before comparison); for a `Uint8Array` root the raw bytes are compared
|
|
338
|
+
* directly. Returns `false` on mismatch or malformed input — mirroring `verify`'s
|
|
339
|
+
* forgiving contract rather than throwing.
|
|
297
340
|
*/
|
|
298
|
-
declare function
|
|
299
|
-
declare namespace SignatureValid {
|
|
300
|
-
var secp256k1: (digest: BytesInput, signature: BytesInput, publicKey: BytesInput, options?: Omit<VerifyOptions, "curve">) => boolean;
|
|
301
|
-
var p256: (digest: BytesInput, signature: BytesInput, publicKey: BytesInput, options?: Omit<VerifyOptions, "curve">) => boolean;
|
|
302
|
-
var ed25519: (digest: BytesInput, signature: BytesInput, publicKey: BytesInput, options?: Omit<VerifyOptions, "curve">) => boolean;
|
|
303
|
-
var batch: (verifications: Array<{
|
|
304
|
-
digest: BytesInput;
|
|
305
|
-
signature: BytesInput;
|
|
306
|
-
publicKey: BytesInput;
|
|
307
|
-
options?: VerifyOptions;
|
|
308
|
-
}>) => boolean[];
|
|
309
|
-
var detailed: (digest: BytesInput, signature: BytesInput, publicKey: BytesInput, options?: VerifyOptions) => {
|
|
310
|
-
valid: boolean;
|
|
311
|
-
curve: CurveType;
|
|
312
|
-
signatureFormat: string;
|
|
313
|
-
error?: string;
|
|
314
|
-
};
|
|
315
|
-
}
|
|
341
|
+
declare function setVerify(root: string | Uint8Array, disclosure: SetDisclosure, hasher?: DigestHasher, encode?: OutputEncoder): boolean;
|
|
316
342
|
|
|
317
|
-
export { type
|
|
343
|
+
export { type CidParts, type CurveType, type DigestField, type DigestHasher, type Encoding, type HashAlgorithm, type Multibase, type Multicodec, type MultihashCode, type OutputEncoder, type OutputEncoding, type SaltedLeaf, type SetDisclosure, cid, cidDecode, cidV1, digest, digestFields, encodeFields, generatePrivateKey, getPublicKey, hashMod, leafDigest, randomBytes, resolveHasher, resolveOutputEncoder, setCommit, setDisclose, setVerify, sign, verify };
|