@datumesh/verify 0.1.1 → 0.2.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 +7 -5
- package/dist/cjs/ed25519.js +2 -0
- package/dist/cjs/envelope.d.ts +38 -0
- package/dist/cjs/envelope.js +243 -0
- package/dist/cjs/errors.d.ts +2 -2
- package/dist/cjs/errors.js +5 -1
- package/dist/cjs/index.d.ts +13 -3
- package/dist/cjs/index.js +22 -3
- package/dist/cjs/json.d.ts +6 -7
- package/dist/cjs/json.js +49 -18
- package/dist/cjs/keys.d.ts +2 -0
- package/dist/cjs/keys.js +55 -2
- package/dist/cjs/lexical.d.ts +11 -1
- package/dist/cjs/lexical.js +28 -4
- package/dist/cjs/units-data.d.ts +4 -0
- package/dist/cjs/units-data.js +11 -0
- package/dist/cjs/units.d.ts +24 -0
- package/dist/cjs/units.js +131 -0
- package/dist/cjs/uri.d.ts +14 -2
- package/dist/cjs/uri.js +27 -14
- package/dist/cjs/validate.d.ts +8 -0
- package/dist/cjs/validate.js +63 -4
- package/dist/cjs/verify.d.ts +21 -2
- package/dist/cjs/verify.js +47 -7
- package/dist/esm/ed25519.js +2 -0
- package/dist/esm/envelope.d.ts +38 -0
- package/dist/esm/envelope.js +240 -0
- package/dist/esm/errors.d.ts +2 -2
- package/dist/esm/errors.js +5 -1
- package/dist/esm/index.d.ts +13 -3
- package/dist/esm/index.js +20 -4
- package/dist/esm/json.d.ts +6 -7
- package/dist/esm/json.js +49 -18
- package/dist/esm/keys.d.ts +2 -0
- package/dist/esm/keys.js +54 -1
- package/dist/esm/lexical.d.ts +11 -1
- package/dist/esm/lexical.js +24 -2
- package/dist/esm/units-data.d.ts +4 -0
- package/dist/esm/units-data.js +8 -0
- package/dist/esm/units.d.ts +24 -0
- package/dist/esm/units.js +123 -0
- package/dist/esm/uri.d.ts +14 -2
- package/dist/esm/uri.js +31 -18
- package/dist/esm/validate.d.ts +8 -0
- package/dist/esm/validate.js +61 -4
- package/dist/esm/verify.d.ts +21 -2
- package/dist/esm/verify.js +46 -8
- package/package.json +3 -3
package/dist/esm/validate.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { fail } from './errors.js';
|
|
5
5
|
import { has, isObject, memberPath, sortedKeys } from './json.js';
|
|
6
6
|
import { LIMITS, VALID_TIME_TYPES, checkArgName, checkFunctionName, checkHex, checkInstant, checkKeyRef, checkLicence, checkName, checkNamespace, checkString, checkTypedValue, isTypeName, } from './lexical.js';
|
|
7
|
+
import { canonUnit, resolveUnit, structUnit } from './units.js';
|
|
7
8
|
export const KINDS = ['scalar', 'cell', 'ref', 'function', 'receipt', 'jcs'];
|
|
8
9
|
const SLICE = '*';
|
|
9
10
|
// ---------------------------------------------------------------- step 2: structure
|
|
@@ -76,12 +77,24 @@ export function structSignature(v, path) {
|
|
|
76
77
|
for (const k of sortedKeys(args)) {
|
|
77
78
|
const p = memberPath(argsPath, k);
|
|
78
79
|
const d = expectObject(args[k], p);
|
|
79
|
-
members(d, ['type', 'valid'], [], p);
|
|
80
|
+
members(d, ['type', 'valid'], ['unit'], p);
|
|
80
81
|
expectString(d, 'type', p);
|
|
82
|
+
if (has(d, 'unit'))
|
|
83
|
+
structUnit(d.unit, memberPath(p, 'unit'));
|
|
81
84
|
if (typeof d.valid !== 'boolean')
|
|
82
85
|
fail('E_SCHEMA', 'valid must be a boolean', memberPath(p, 'valid'));
|
|
83
86
|
}
|
|
84
|
-
|
|
87
|
+
// SPEC 7: a type name, or {"type", "unit"} (the unit's absence is E_CANON_UNIT, in step 3).
|
|
88
|
+
const rp = memberPath(path, 'returns');
|
|
89
|
+
const r = sig.returns;
|
|
90
|
+
if (typeof r === 'string')
|
|
91
|
+
return;
|
|
92
|
+
if (!isObject(r))
|
|
93
|
+
fail('E_SCHEMA', 'returns must be a type name or an object', rp);
|
|
94
|
+
members(r, ['type'], ['unit'], rp);
|
|
95
|
+
expectString(r, 'type', rp);
|
|
96
|
+
if (has(r, 'unit'))
|
|
97
|
+
structUnit(r.unit, memberPath(rp, 'unit'));
|
|
85
98
|
}
|
|
86
99
|
const RECEIPT_MEMBERS = [
|
|
87
100
|
'asof',
|
|
@@ -185,14 +198,43 @@ export function canonSignature(sig, path) {
|
|
|
185
198
|
const p = memberPath(argsPath, k);
|
|
186
199
|
checkArgName(k, p);
|
|
187
200
|
const d = args[k];
|
|
201
|
+
// SPEC 7: type, then unit, then valid.
|
|
188
202
|
if (!isTypeName(d.type))
|
|
189
203
|
fail('E_CANON_TYPE', `unknown type ${JSON.stringify(d.type)}`, memberPath(p, 'type'));
|
|
204
|
+
if (d.type === 'number') {
|
|
205
|
+
if (!has(d, 'unit'))
|
|
206
|
+
fail('E_CANON_UNIT', 'a number argument declares its unit', p);
|
|
207
|
+
canonUnit(d.unit, args, memberPath(p, 'unit'));
|
|
208
|
+
}
|
|
209
|
+
else if (has(d, 'unit')) {
|
|
210
|
+
fail('E_CANON_UNIT', 'only number arguments have a unit', memberPath(p, 'unit'));
|
|
211
|
+
}
|
|
190
212
|
if (d.valid === true && !VALID_TIME_TYPES.includes(d.type)) {
|
|
191
213
|
fail('E_CANON_TYPE', 'a valid-time argument must be a date, instant or string', p);
|
|
192
214
|
}
|
|
193
215
|
}
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
const rp = memberPath(path, 'returns');
|
|
217
|
+
const r = sig.returns;
|
|
218
|
+
if (typeof r === 'string') {
|
|
219
|
+
if (!isTypeName(r))
|
|
220
|
+
fail('E_CANON_TYPE', `unknown type ${JSON.stringify(r)}`, rp);
|
|
221
|
+
if (r === 'number')
|
|
222
|
+
fail('E_CANON_UNIT', 'a number return is {"type": "number", "unit": ...}, not the bare name', rp);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const o = r;
|
|
226
|
+
if (!isTypeName(o.type))
|
|
227
|
+
fail('E_CANON_TYPE', `unknown type ${JSON.stringify(o.type)}`, memberPath(rp, 'type'));
|
|
228
|
+
if (o.type !== 'number')
|
|
229
|
+
fail('E_CANON_UNIT', 'only a number return is written as an object', rp);
|
|
230
|
+
if (!has(o, 'unit'))
|
|
231
|
+
fail('E_CANON_UNIT', 'a number return declares its unit', rp);
|
|
232
|
+
canonUnit(o.unit, args, memberPath(rp, 'unit'));
|
|
233
|
+
}
|
|
234
|
+
/** The return type named by a valid signature's `returns` (SPEC 10.1 step 7.4). */
|
|
235
|
+
export function returnType(sig) {
|
|
236
|
+
const r = sig.returns;
|
|
237
|
+
return typeof r === 'string' ? r : r.type;
|
|
196
238
|
}
|
|
197
239
|
function canonFunction(o, path) {
|
|
198
240
|
const code = o.code;
|
|
@@ -308,6 +350,21 @@ export function validate(v, kind, path = '$') {
|
|
|
308
350
|
return;
|
|
309
351
|
}
|
|
310
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* The unit of a cell's value (SPEC 7.1, units.md 3). Order: the cell (structure, canonical
|
|
355
|
+
* form), fn_hash against the descriptor (E_FN_HASH_MISMATCH), the descriptor (structure,
|
|
356
|
+
* canonical form), then substitution.
|
|
357
|
+
*/
|
|
358
|
+
export function cellUnit(desc, cell, fnHash) {
|
|
359
|
+
structCell(cell, 'cell', false);
|
|
360
|
+
canonCell(cell, 'cell');
|
|
361
|
+
if (fnHash(desc) !== cell.fn_hash)
|
|
362
|
+
fail('E_FN_HASH_MISMATCH', 'the descriptor does not hash to cell.fn_hash', 'descriptor');
|
|
363
|
+
structFunction(desc, 'descriptor');
|
|
364
|
+
canonFunction(desc, 'descriptor');
|
|
365
|
+
const sig = desc.signature;
|
|
366
|
+
return resolveUnit(sig.returns, cell.args, 'descriptor.signature.returns', 'cell.args');
|
|
367
|
+
}
|
|
311
368
|
/** Receipt step 2 only: true when the receipt has the right members and JSON types. */
|
|
312
369
|
export function receiptStructure(v) {
|
|
313
370
|
structReceipt(v, '$');
|
package/dist/esm/verify.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ErrorCode } from './errors.js';
|
|
2
|
+
import { type JsonObject, type JsonValue } from './json.js';
|
|
3
|
+
import { type Sources } from './keys.js';
|
|
2
4
|
export interface VerifyResult {
|
|
3
5
|
/** True when the receipt is signature-valid (and, if recompute was given, fully verified). */
|
|
4
6
|
ok: boolean;
|
|
@@ -14,6 +16,8 @@ export interface VerifyResult {
|
|
|
14
16
|
detail: string;
|
|
15
17
|
/** Where the failure was found (a JSON path like `$.cell.args.base`), or null. */
|
|
16
18
|
path: string | null;
|
|
19
|
+
/** True only when blob content was supplied and step 7.7 ran and passed. Does not change `level`. */
|
|
20
|
+
blobChecked: boolean;
|
|
17
21
|
}
|
|
18
22
|
/** Input for SPEC 10.1 step 7. The library does not evaluate functions; `value` is the result. */
|
|
19
23
|
export interface Recompute {
|
|
@@ -27,12 +31,27 @@ export interface Recompute {
|
|
|
27
31
|
value: string | boolean;
|
|
28
32
|
};
|
|
29
33
|
}
|
|
34
|
+
declare class Stop extends Error {
|
|
35
|
+
readonly code: ErrorCode;
|
|
36
|
+
readonly detail: string;
|
|
37
|
+
readonly path: string | null;
|
|
38
|
+
constructor(code: ErrorCode, detail: string, path: string | null);
|
|
39
|
+
}
|
|
30
40
|
/**
|
|
31
41
|
* Verify a receipt (SPEC 10.1). `receipt` is JSON text, UTF-8 bytes or a parsed object (only
|
|
32
42
|
* text and bytes allow duplicate-member detection). `keys` is a key document, a KeysResult, a
|
|
33
|
-
* {keyId: publicKeyHex} map, or an array of these. With `recompute`, step 7 runs too.
|
|
43
|
+
* {keyId: publicKeyHex} map, or an array of these. With `recompute`, step 7 runs too. With
|
|
44
|
+
* `blob`, the content of a blob value, step 7.7 runs last (after step 7 when recomputing,
|
|
45
|
+
* otherwise right after step 6): E_SCHEMA if the value is not a blob, E_BLOB_MISMATCH if the
|
|
46
|
+
* content does not match. It does not change `level`; `blobChecked` reports it.
|
|
34
47
|
*
|
|
35
48
|
* Never throws for an invalid receipt or bad key material; throws TypeError only when an
|
|
36
49
|
* argument is of the wrong kind altogether.
|
|
37
50
|
*/
|
|
38
|
-
export declare function verify(receipt: unknown, keys: unknown, recomputeArg?: Recompute | null): VerifyResult;
|
|
51
|
+
export declare function verify(receipt: unknown, keys: unknown, recomputeArg?: Recompute | null, blob?: Uint8Array | ArrayBuffer | null): VerifyResult;
|
|
52
|
+
/** Steps 1-6 for an already-parsed receipt (used by checkEnvelope). Throws a Stop. */
|
|
53
|
+
export declare function verifyEmbedded(receipt: JsonValue, sources: Sources): {
|
|
54
|
+
receipt: JsonObject;
|
|
55
|
+
hash: string;
|
|
56
|
+
};
|
|
57
|
+
export { Stop };
|
package/dist/esm/verify.js
CHANGED
|
@@ -5,7 +5,8 @@ import { fromHex, sha256Hex } from './hash.js';
|
|
|
5
5
|
import { has, newObject, sortedKeys, toJson } from './json.js';
|
|
6
6
|
import { jcsBytes } from './jcs.js';
|
|
7
7
|
import { collectSources, lookupKey } from './keys.js';
|
|
8
|
-
import {
|
|
8
|
+
import { checkBlob } from './lexical.js';
|
|
9
|
+
import { receiptCanonicalAndConsistent, receiptStructure, returnType, validate } from './validate.js';
|
|
9
10
|
class Stop extends Error {
|
|
10
11
|
constructor(code, detail, path) {
|
|
11
12
|
super(detail);
|
|
@@ -103,9 +104,10 @@ function recompute(receipt, rc, sources) {
|
|
|
103
104
|
if (validNames.join('\u0000') !== declaredValid.join('\u0000')) {
|
|
104
105
|
stop('E_VALID_MISMATCH', 'valid must echo exactly the declared valid-time arguments', '$.valid');
|
|
105
106
|
}
|
|
106
|
-
// 7.4 Return type.
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// 7.4 Return type: `returns`, or its `type` member when it is an object.
|
|
108
|
+
const rt = returnType(sig);
|
|
109
|
+
if (receipt.type !== rt)
|
|
110
|
+
stop('E_RETURN_TYPE', `function returns ${rt}`, '$.type');
|
|
109
111
|
// 7.5 Inputs.
|
|
110
112
|
const declaredInputs = desc.inputs;
|
|
111
113
|
const supplied = rc.inputs ?? {};
|
|
@@ -141,6 +143,28 @@ function recompute(receipt, rc, sources) {
|
|
|
141
143
|
if (v.type !== receipt.type || v.value !== receipt.value)
|
|
142
144
|
stop('E_VALUE_MISMATCH', 'recomputed value differs from the receipt', '$.value');
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Step 7.7: content is only for a blob value (E_SCHEMA otherwise); it has exactly `length`
|
|
148
|
+
* bytes, then a SHA-256 equal to the blob's hash (E_BLOB_MISMATCH).
|
|
149
|
+
*/
|
|
150
|
+
function checkContent(receipt, content) {
|
|
151
|
+
if (receipt.type !== 'blob')
|
|
152
|
+
stop('E_SCHEMA', `content was supplied, but the value is a ${String(receipt.type)}, not a blob`, '$.type');
|
|
153
|
+
const blob = checkBlob(receipt.value, '$.value'); // already checked in step 3
|
|
154
|
+
if (content.length !== blob.length)
|
|
155
|
+
stop('E_BLOB_MISMATCH', `content is ${content.length} bytes, the blob says ${blob.length}`, '$.value');
|
|
156
|
+
if (sha256Hex(content) !== blob.sha256)
|
|
157
|
+
stop('E_BLOB_MISMATCH', "the content's SHA-256 is not the blob's hash", '$.value');
|
|
158
|
+
}
|
|
159
|
+
function checkBlobArg(b) {
|
|
160
|
+
if (b === undefined || b === null)
|
|
161
|
+
return null;
|
|
162
|
+
if (b instanceof Uint8Array)
|
|
163
|
+
return b;
|
|
164
|
+
if (b instanceof ArrayBuffer)
|
|
165
|
+
return new Uint8Array(b);
|
|
166
|
+
throw new TypeError('blob must be the content bytes (Uint8Array or ArrayBuffer)');
|
|
167
|
+
}
|
|
144
168
|
function checkRecomputeArg(rc) {
|
|
145
169
|
if (rc === undefined || rc === null)
|
|
146
170
|
return null;
|
|
@@ -158,32 +182,46 @@ function checkRecomputeArg(rc) {
|
|
|
158
182
|
/**
|
|
159
183
|
* Verify a receipt (SPEC 10.1). `receipt` is JSON text, UTF-8 bytes or a parsed object (only
|
|
160
184
|
* text and bytes allow duplicate-member detection). `keys` is a key document, a KeysResult, a
|
|
161
|
-
* {keyId: publicKeyHex} map, or an array of these. With `recompute`, step 7 runs too.
|
|
185
|
+
* {keyId: publicKeyHex} map, or an array of these. With `recompute`, step 7 runs too. With
|
|
186
|
+
* `blob`, the content of a blob value, step 7.7 runs last (after step 7 when recomputing,
|
|
187
|
+
* otherwise right after step 6): E_SCHEMA if the value is not a blob, E_BLOB_MISMATCH if the
|
|
188
|
+
* content does not match. It does not change `level`; `blobChecked` reports it.
|
|
162
189
|
*
|
|
163
190
|
* Never throws for an invalid receipt or bad key material; throws TypeError only when an
|
|
164
191
|
* argument is of the wrong kind altogether.
|
|
165
192
|
*/
|
|
166
|
-
export function verify(receipt, keys, recomputeArg) {
|
|
193
|
+
export function verify(receipt, keys, recomputeArg, blob) {
|
|
167
194
|
const sources = collectSources(keys);
|
|
168
195
|
const rc = checkRecomputeArg(recomputeArg);
|
|
196
|
+
const content = checkBlobArg(blob);
|
|
169
197
|
let hash = null;
|
|
170
198
|
try {
|
|
171
199
|
const { receipt: r } = verifySigned(receipt, sources, (h) => (hash = h));
|
|
172
200
|
if (rc)
|
|
173
201
|
recompute(r, rc, sources);
|
|
202
|
+
const checked = content !== null;
|
|
203
|
+
if (checked)
|
|
204
|
+
checkContent(r, content);
|
|
205
|
+
const what = rc ? 'fully verified: signature valid and value recomputed' : `signature-valid: signed by ${r.computed_by}`;
|
|
174
206
|
return {
|
|
175
207
|
ok: true,
|
|
176
208
|
code: 'OK',
|
|
177
209
|
level: rc ? 'recomputed' : 'signed',
|
|
178
210
|
hash,
|
|
179
211
|
key: r.computed_by,
|
|
180
|
-
detail:
|
|
212
|
+
detail: checked ? `${what}; blob content matches` : what,
|
|
181
213
|
path: null,
|
|
214
|
+
blobChecked: checked,
|
|
182
215
|
};
|
|
183
216
|
}
|
|
184
217
|
catch (e) {
|
|
185
218
|
if (e instanceof Stop)
|
|
186
|
-
return { ok: false, code: e.code, level: null, hash, key: null, detail: e.detail, path: e.path };
|
|
219
|
+
return { ok: false, code: e.code, level: null, hash, key: null, detail: e.detail, path: e.path, blobChecked: false };
|
|
187
220
|
throw e; // a bug in this library, not a property of the receipt
|
|
188
221
|
}
|
|
189
222
|
}
|
|
223
|
+
/** Steps 1-6 for an already-parsed receipt (used by checkEnvelope). Throws a Stop. */
|
|
224
|
+
export function verifyEmbedded(receipt, sources) {
|
|
225
|
+
return verifySigned(receipt, sources, () => undefined);
|
|
226
|
+
}
|
|
227
|
+
export { Stop };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datumesh/verify",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Verifier for Datumesh receipts: strict canonical JSON (RFC 8785), cell URIs, key documents and Ed25519 signatures.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Verifier for Datumesh receipts: strict canonical JSON (RFC 8785), cell URIs, units, blobs, response envelopes, key documents and Ed25519 signatures.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"homepage": "https://datumesh.com",
|
|
11
11
|
"keywords": ["datumesh", "receipt", "verify", "ed25519", "jcs", "rfc8785", "canonical-json"],
|
|
12
12
|
"datumesh": {
|
|
13
|
-
"spec": "spec-0.
|
|
13
|
+
"spec": "spec-0.2.1"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "./dist/cjs/index.js",
|