@metalabel/dfos-protocol 0.0.3 → 0.1.0
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/CONTENT-MODEL.md +63 -22
- package/DID-METHOD.md +7 -8
- package/PROTOCOL.md +174 -86
- package/README.md +8 -10
- package/dist/chain/index.d.ts +93 -1
- package/dist/chain/index.js +13 -1
- package/dist/{chunk-VEBMLR37.js → chunk-ASGEXSVT.js} +172 -2
- package/dist/chunk-E5CFQG2B.js +99 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +26 -32
- package/dist/merkle/index.d.ts +45 -0
- package/dist/merkle/index.js +14 -0
- package/examples/content-delete.json +5 -4
- package/examples/content-lifecycle.json +9 -7
- package/package.json +6 -9
- package/schemas/manifest.v1.json +29 -0
- package/schemas/post.v1.json +5 -0
- package/schemas/profile.v1.json +5 -0
- package/REGISTRY-API.md +0 -242
- package/dist/chunk-U6DANYPT.js +0 -311
- package/dist/registry/index.d.ts +0 -143
- package/dist/registry/index.js +0 -34
- package/openapi.yaml +0 -376
- package/schemas/document-envelope.v1.json +0 -37
package/README.md
CHANGED
|
@@ -15,17 +15,17 @@ npm install @metalabel/dfos-protocol
|
|
|
15
15
|
import { verifyContentChain, verifyIdentityChain } from '@metalabel/dfos-protocol/chain';
|
|
16
16
|
// Crypto primitives
|
|
17
17
|
import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-protocol/crypto';
|
|
18
|
-
//
|
|
19
|
-
import {
|
|
18
|
+
// Merkle trees
|
|
19
|
+
import { buildMerkleTree, verifyMerkleProof } from '@metalabel/dfos-protocol/merkle';
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Subpath Exports
|
|
23
23
|
|
|
24
|
-
| Export
|
|
25
|
-
|
|
|
26
|
-
| `@metalabel/dfos-protocol/chain`
|
|
27
|
-
| `@metalabel/dfos-protocol/crypto`
|
|
28
|
-
| `@metalabel/dfos-protocol/
|
|
24
|
+
| Export | Description |
|
|
25
|
+
| --------------------------------- | ----------------------------------------------------------------------- |
|
|
26
|
+
| `@metalabel/dfos-protocol/chain` | Identity and content chain signing, verification, beacons, countersigns |
|
|
27
|
+
| `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
|
|
28
|
+
| `@metalabel/dfos-protocol/merkle` | SHA-256 binary merkle tree, inclusion proofs |
|
|
29
29
|
|
|
30
30
|
## Specifications
|
|
31
31
|
|
|
@@ -33,9 +33,7 @@ import { createRegistryServer } from '@metalabel/dfos-protocol/registry';
|
|
|
33
33
|
| -------------------------------------- | -------------------------------------------------------------- |
|
|
34
34
|
| [PROTOCOL.md](./PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors |
|
|
35
35
|
| [DID-METHOD.md](./DID-METHOD.md) | W3C DID method specification for `did:dfos` |
|
|
36
|
-
| [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile,
|
|
37
|
-
| [REGISTRY-API.md](./REGISTRY-API.md) | HTTP API for chain storage and resolution |
|
|
38
|
-
| [openapi.yaml](./openapi.yaml) | OpenAPI 3.1 machine-readable registry spec |
|
|
36
|
+
| [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile, manifest) |
|
|
39
37
|
|
|
40
38
|
## Examples
|
|
41
39
|
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -77,24 +77,38 @@ type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
|
|
|
77
77
|
declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
78
78
|
version: z.ZodLiteral<1>;
|
|
79
79
|
type: z.ZodLiteral<"create">;
|
|
80
|
+
did: z.ZodString;
|
|
80
81
|
documentCID: z.ZodString;
|
|
82
|
+
baseDocumentCID: z.ZodNullable<z.ZodString>;
|
|
81
83
|
createdAt: z.ZodISODateTime;
|
|
82
84
|
note: z.ZodNullable<z.ZodString>;
|
|
83
85
|
}, z.core.$strict>, z.ZodObject<{
|
|
84
86
|
version: z.ZodLiteral<1>;
|
|
85
87
|
type: z.ZodLiteral<"update">;
|
|
88
|
+
did: z.ZodString;
|
|
86
89
|
previousOperationCID: z.ZodString;
|
|
87
90
|
documentCID: z.ZodNullable<z.ZodString>;
|
|
91
|
+
baseDocumentCID: z.ZodNullable<z.ZodString>;
|
|
88
92
|
createdAt: z.ZodISODateTime;
|
|
89
93
|
note: z.ZodNullable<z.ZodString>;
|
|
90
94
|
}, z.core.$strict>, z.ZodObject<{
|
|
91
95
|
version: z.ZodLiteral<1>;
|
|
92
96
|
type: z.ZodLiteral<"delete">;
|
|
97
|
+
did: z.ZodString;
|
|
93
98
|
previousOperationCID: z.ZodString;
|
|
94
99
|
createdAt: z.ZodISODateTime;
|
|
95
100
|
note: z.ZodNullable<z.ZodString>;
|
|
96
101
|
}, z.core.$strict>], "type">;
|
|
97
102
|
type ContentOperation = z.infer<typeof ContentOperation>;
|
|
103
|
+
/** Beacon: floating signed merkle root announcement */
|
|
104
|
+
declare const BeaconPayload: z.ZodObject<{
|
|
105
|
+
version: z.ZodLiteral<1>;
|
|
106
|
+
type: z.ZodLiteral<"beacon">;
|
|
107
|
+
did: z.ZodString;
|
|
108
|
+
merkleRoot: z.ZodString;
|
|
109
|
+
createdAt: z.ZodISODateTime;
|
|
110
|
+
}, z.core.$strict>;
|
|
111
|
+
type BeaconPayload = z.infer<typeof BeaconPayload>;
|
|
98
112
|
|
|
99
113
|
/** Ed25519 public key multicodec value */
|
|
100
114
|
declare const ED25519_PUB_MULTICODEC = 237;
|
|
@@ -193,4 +207,82 @@ declare const verifyContentChain: (input: {
|
|
|
193
207
|
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
194
208
|
}) => Promise<VerifiedContentChain>;
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Sign a beacon announcement as a JWS
|
|
212
|
+
*/
|
|
213
|
+
declare const signBeacon: (input: {
|
|
214
|
+
payload: BeaconPayload;
|
|
215
|
+
signer: Signer;
|
|
216
|
+
kid: string;
|
|
217
|
+
}) => Promise<{
|
|
218
|
+
jwsToken: string;
|
|
219
|
+
beaconCID: string;
|
|
220
|
+
}>;
|
|
221
|
+
interface VerifiedBeacon {
|
|
222
|
+
payload: BeaconPayload;
|
|
223
|
+
beaconCID: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Verify a beacon JWS — signature, CID, payload schema, clock skew
|
|
227
|
+
*/
|
|
228
|
+
declare const verifyBeacon: (input: {
|
|
229
|
+
jwsToken: string;
|
|
230
|
+
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
231
|
+
/** Current time for clock skew check (defaults to Date.now()) */
|
|
232
|
+
now?: number;
|
|
233
|
+
}) => Promise<VerifiedBeacon>;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Sign an existing content operation as a countersignature (witness JWS)
|
|
237
|
+
*
|
|
238
|
+
* The witness signs the same payload as the author, producing a different
|
|
239
|
+
* JWS token with their own kid. The CID is identical because the payload
|
|
240
|
+
* is identical.
|
|
241
|
+
*/
|
|
242
|
+
declare const signCountersignature: (input: {
|
|
243
|
+
/** The original operation payload (must include did of the author) */
|
|
244
|
+
operationPayload: ContentOperation;
|
|
245
|
+
/** Witness signer */
|
|
246
|
+
signer: Signer;
|
|
247
|
+
/** Witness kid — DID URL of the witness (must differ from payload.did) */
|
|
248
|
+
kid: string;
|
|
249
|
+
}) => Promise<{
|
|
250
|
+
jwsToken: string;
|
|
251
|
+
operationCID: string;
|
|
252
|
+
}>;
|
|
253
|
+
interface VerifiedCountersignature {
|
|
254
|
+
operationCID: string;
|
|
255
|
+
/** The DID that authored the operation (payload.did) */
|
|
256
|
+
authorDID: string;
|
|
257
|
+
/** The DID that witnessed the operation (kid DID) */
|
|
258
|
+
witnessDID: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Verify a countersignature JWS against an expected operation CID
|
|
262
|
+
*
|
|
263
|
+
* Checks: valid signature, CID matches, kid DID differs from payload did
|
|
264
|
+
*/
|
|
265
|
+
declare const verifyCountersignature: (input: {
|
|
266
|
+
jwsToken: string;
|
|
267
|
+
expectedCID: string;
|
|
268
|
+
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
269
|
+
}) => Promise<VerifiedCountersignature>;
|
|
270
|
+
interface VerifiedBeaconCountersignature {
|
|
271
|
+
beaconCID: string;
|
|
272
|
+
/** The DID that controls the beacon (payload.did) */
|
|
273
|
+
controllerDID: string;
|
|
274
|
+
/** The DID that witnessed the beacon (kid DID) */
|
|
275
|
+
witnessDID: string;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Verify a beacon countersignature JWS against an expected beacon CID
|
|
279
|
+
*
|
|
280
|
+
* Checks: valid signature, CID matches, kid DID differs from payload did
|
|
281
|
+
*/
|
|
282
|
+
declare const verifyBeaconCountersignature: (input: {
|
|
283
|
+
jwsToken: string;
|
|
284
|
+
expectedCID: string;
|
|
285
|
+
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
286
|
+
}) => Promise<VerifiedBeaconCountersignature>;
|
|
287
|
+
|
|
288
|
+
export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedBeacon, type VerifiedBeaconCountersignature, type VerifiedContentChain, type VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyBeacon, verifyBeaconCountersignature, verifyContentChain, verifyCountersignature, verifyIdentityChain };
|
package/dist/chain/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BeaconPayload,
|
|
2
3
|
ContentOperation,
|
|
3
4
|
ED25519_PRIV_MULTICODEC,
|
|
4
5
|
ED25519_PUB_MULTICODEC,
|
|
@@ -9,13 +10,19 @@ import {
|
|
|
9
10
|
deriveChainIdentifier,
|
|
10
11
|
deriveContentId,
|
|
11
12
|
encodeEd25519Multikey,
|
|
13
|
+
signBeacon,
|
|
12
14
|
signContentOperation,
|
|
15
|
+
signCountersignature,
|
|
13
16
|
signIdentityOperation,
|
|
17
|
+
verifyBeacon,
|
|
18
|
+
verifyBeaconCountersignature,
|
|
14
19
|
verifyContentChain,
|
|
20
|
+
verifyCountersignature,
|
|
15
21
|
verifyIdentityChain
|
|
16
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-ASGEXSVT.js";
|
|
17
23
|
import "../chunk-ZXXP5W5N.js";
|
|
18
24
|
export {
|
|
25
|
+
BeaconPayload,
|
|
19
26
|
ContentOperation,
|
|
20
27
|
ED25519_PRIV_MULTICODEC,
|
|
21
28
|
ED25519_PUB_MULTICODEC,
|
|
@@ -26,8 +33,13 @@ export {
|
|
|
26
33
|
deriveChainIdentifier,
|
|
27
34
|
deriveContentId,
|
|
28
35
|
encodeEd25519Multikey,
|
|
36
|
+
signBeacon,
|
|
29
37
|
signContentOperation,
|
|
38
|
+
signCountersignature,
|
|
30
39
|
signIdentityOperation,
|
|
40
|
+
verifyBeacon,
|
|
41
|
+
verifyBeaconCountersignature,
|
|
31
42
|
verifyContentChain,
|
|
43
|
+
verifyCountersignature,
|
|
32
44
|
verifyIdentityChain
|
|
33
45
|
};
|
|
@@ -13,6 +13,7 @@ var MAX_PUBLIC_KEY_MULTIBASE = 128;
|
|
|
13
13
|
var MAX_CID = 256;
|
|
14
14
|
var MAX_NOTE = 256;
|
|
15
15
|
var MAX_KEYS_PER_ROLE = 16;
|
|
16
|
+
var MAX_DID = 256;
|
|
16
17
|
var MultikeyPublicKey = z.strictObject({
|
|
17
18
|
id: z.string().max(MAX_KEY_ID),
|
|
18
19
|
type: z.literal("Multikey"),
|
|
@@ -49,7 +50,7 @@ var IdentityOperation = z.discriminatedUnion("type", [
|
|
|
49
50
|
IdentityDelete
|
|
50
51
|
]);
|
|
51
52
|
var VerifiedIdentity = z.strictObject({
|
|
52
|
-
did: z.string(),
|
|
53
|
+
did: z.string().max(MAX_DID),
|
|
53
54
|
isDeleted: z.boolean(),
|
|
54
55
|
authKeys: z.array(MultikeyPublicKey).max(MAX_KEYS_PER_ROLE),
|
|
55
56
|
assertKeys: z.array(MultikeyPublicKey).max(MAX_KEYS_PER_ROLE),
|
|
@@ -58,21 +59,26 @@ var VerifiedIdentity = z.strictObject({
|
|
|
58
59
|
var ContentCreate = z.strictObject({
|
|
59
60
|
version: z.literal(1),
|
|
60
61
|
type: z.literal("create"),
|
|
62
|
+
did: z.string().max(MAX_DID),
|
|
61
63
|
documentCID: CIDString,
|
|
64
|
+
baseDocumentCID: CIDString.nullable(),
|
|
62
65
|
createdAt: Iso8601,
|
|
63
66
|
note: z.string().max(MAX_NOTE).nullable()
|
|
64
67
|
});
|
|
65
68
|
var ContentUpdate = z.strictObject({
|
|
66
69
|
version: z.literal(1),
|
|
67
70
|
type: z.literal("update"),
|
|
71
|
+
did: z.string().max(MAX_DID),
|
|
68
72
|
previousOperationCID: CIDString,
|
|
69
73
|
documentCID: CIDString.nullable(),
|
|
74
|
+
baseDocumentCID: CIDString.nullable(),
|
|
70
75
|
createdAt: Iso8601,
|
|
71
76
|
note: z.string().max(MAX_NOTE).nullable()
|
|
72
77
|
});
|
|
73
78
|
var ContentDelete = z.strictObject({
|
|
74
79
|
version: z.literal(1),
|
|
75
80
|
type: z.literal("delete"),
|
|
81
|
+
did: z.string().max(MAX_DID),
|
|
76
82
|
previousOperationCID: CIDString,
|
|
77
83
|
createdAt: Iso8601,
|
|
78
84
|
note: z.string().max(MAX_NOTE).nullable()
|
|
@@ -82,6 +88,13 @@ var ContentOperation = z.discriminatedUnion("type", [
|
|
|
82
88
|
ContentUpdate,
|
|
83
89
|
ContentDelete
|
|
84
90
|
]);
|
|
91
|
+
var BeaconPayload = z.strictObject({
|
|
92
|
+
version: z.literal(1),
|
|
93
|
+
type: z.literal("beacon"),
|
|
94
|
+
did: z.string().max(MAX_DID),
|
|
95
|
+
merkleRoot: z.string().regex(/^[0-9a-f]{64}$/),
|
|
96
|
+
createdAt: Iso8601
|
|
97
|
+
});
|
|
85
98
|
|
|
86
99
|
// src/chain/multikey.ts
|
|
87
100
|
import { base58btc } from "multiformats/bases/base58";
|
|
@@ -164,6 +177,9 @@ var verifyIdentityChain = async (input) => {
|
|
|
164
177
|
throw new Error(`log[${idx}]: ${messages}`);
|
|
165
178
|
}
|
|
166
179
|
const op = result.data;
|
|
180
|
+
if (decoded.header.typ !== "did:dfos:identity-op") {
|
|
181
|
+
throw new Error(`log[${idx}]: invalid typ: ${decoded.header.typ}`);
|
|
182
|
+
}
|
|
167
183
|
if (state.isDeleted) throw new Error(`log[${idx}]: cannot modify a deleted identity`);
|
|
168
184
|
if (idx === 0 && op.type !== "create") {
|
|
169
185
|
throw new Error(`log[${idx}]: first operation must be create`);
|
|
@@ -306,6 +322,9 @@ var verifyContentChain = async (input) => {
|
|
|
306
322
|
throw new Error(`log[${idx}]: ${messages}`);
|
|
307
323
|
}
|
|
308
324
|
const op = result.data;
|
|
325
|
+
if (decoded.header.typ !== "did:dfos:content-op") {
|
|
326
|
+
throw new Error(`log[${idx}]: invalid typ: ${decoded.header.typ}`);
|
|
327
|
+
}
|
|
309
328
|
if (state.isDeleted) throw new Error(`log[${idx}]: cannot extend a deleted chain`);
|
|
310
329
|
if (idx === 0 && op.type !== "create") {
|
|
311
330
|
throw new Error(`log[${idx}]: first operation must be create`);
|
|
@@ -323,6 +342,12 @@ var verifyContentChain = async (input) => {
|
|
|
323
342
|
}
|
|
324
343
|
}
|
|
325
344
|
const kid = decoded.header.kid;
|
|
345
|
+
const hashIdx = kid.indexOf("#");
|
|
346
|
+
if (hashIdx < 0) throw new Error(`log[${idx}]: kid must be a DID URL`);
|
|
347
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
348
|
+
if (kidDid !== op.did) {
|
|
349
|
+
throw new Error(`log[${idx}]: kid DID does not match operation did`);
|
|
350
|
+
}
|
|
326
351
|
const publicKey = await input.resolveKey(kid);
|
|
327
352
|
try {
|
|
328
353
|
verifyJws({ token: jwsToken, publicKey });
|
|
@@ -367,11 +392,151 @@ var verifyContentChain = async (input) => {
|
|
|
367
392
|
};
|
|
368
393
|
};
|
|
369
394
|
|
|
395
|
+
// src/chain/beacon.ts
|
|
396
|
+
var signBeacon = async (input) => {
|
|
397
|
+
const encoded = await dagCborCanonicalEncode(input.payload);
|
|
398
|
+
const beaconCID = encoded.cid.toString();
|
|
399
|
+
const jwsToken = await createJws({
|
|
400
|
+
header: { alg: "EdDSA", typ: "did:dfos:beacon", kid: input.kid, cid: beaconCID },
|
|
401
|
+
payload: input.payload,
|
|
402
|
+
sign: input.signer
|
|
403
|
+
});
|
|
404
|
+
return { jwsToken, beaconCID };
|
|
405
|
+
};
|
|
406
|
+
var MAX_FUTURE_MS = 5 * 60 * 1e3;
|
|
407
|
+
var verifyBeacon = async (input) => {
|
|
408
|
+
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
409
|
+
if (!decoded) throw new Error("failed to decode beacon JWS");
|
|
410
|
+
const result = BeaconPayload.safeParse(decoded.payload);
|
|
411
|
+
if (!result.success) {
|
|
412
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
413
|
+
throw new Error(`invalid beacon payload: ${messages}`);
|
|
414
|
+
}
|
|
415
|
+
const payload = result.data;
|
|
416
|
+
if (decoded.header.typ !== "did:dfos:beacon") {
|
|
417
|
+
throw new Error(`invalid beacon typ: ${decoded.header.typ}`);
|
|
418
|
+
}
|
|
419
|
+
const kid = decoded.header.kid;
|
|
420
|
+
const hashIdx = kid.indexOf("#");
|
|
421
|
+
if (hashIdx < 0) throw new Error("beacon kid must be a DID URL");
|
|
422
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
423
|
+
if (kidDid !== payload.did) {
|
|
424
|
+
throw new Error("beacon kid DID does not match payload did");
|
|
425
|
+
}
|
|
426
|
+
const publicKey = await input.resolveKey(kid);
|
|
427
|
+
try {
|
|
428
|
+
verifyJws({ token: input.jwsToken, publicKey });
|
|
429
|
+
} catch {
|
|
430
|
+
throw new Error("invalid beacon signature");
|
|
431
|
+
}
|
|
432
|
+
const encoded = await dagCborCanonicalEncode(payload);
|
|
433
|
+
const beaconCID = encoded.cid.toString();
|
|
434
|
+
if (!decoded.header.cid) throw new Error("missing cid in beacon header");
|
|
435
|
+
if (decoded.header.cid !== beaconCID) throw new Error("beacon cid mismatch");
|
|
436
|
+
const now = input.now ?? Date.now();
|
|
437
|
+
const beaconTime = new Date(payload.createdAt).getTime();
|
|
438
|
+
if (beaconTime > now + MAX_FUTURE_MS) {
|
|
439
|
+
throw new Error("beacon createdAt is too far in the future");
|
|
440
|
+
}
|
|
441
|
+
return { payload, beaconCID };
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/chain/countersign.ts
|
|
445
|
+
var signCountersignature = async (input) => {
|
|
446
|
+
const encoded = await dagCborCanonicalEncode(input.operationPayload);
|
|
447
|
+
const operationCID = encoded.cid.toString();
|
|
448
|
+
const jwsToken = await createJws({
|
|
449
|
+
header: { alg: "EdDSA", typ: "did:dfos:content-op", kid: input.kid, cid: operationCID },
|
|
450
|
+
payload: input.operationPayload,
|
|
451
|
+
sign: input.signer
|
|
452
|
+
});
|
|
453
|
+
return { jwsToken, operationCID };
|
|
454
|
+
};
|
|
455
|
+
var verifyCountersignature = async (input) => {
|
|
456
|
+
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
457
|
+
if (!decoded) throw new Error("failed to decode countersignature JWS");
|
|
458
|
+
if (decoded.header.typ !== "did:dfos:content-op") {
|
|
459
|
+
throw new Error(`invalid countersignature typ: ${decoded.header.typ}`);
|
|
460
|
+
}
|
|
461
|
+
const result = ContentOperation.safeParse(decoded.payload);
|
|
462
|
+
if (!result.success) {
|
|
463
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
464
|
+
throw new Error(`invalid operation payload: ${messages}`);
|
|
465
|
+
}
|
|
466
|
+
const op = result.data;
|
|
467
|
+
const encoded = await dagCborCanonicalEncode(op);
|
|
468
|
+
const operationCID = encoded.cid.toString();
|
|
469
|
+
if (operationCID !== input.expectedCID) {
|
|
470
|
+
throw new Error("countersignature CID does not match expected CID");
|
|
471
|
+
}
|
|
472
|
+
if (decoded.header.cid !== operationCID) {
|
|
473
|
+
throw new Error("countersignature header cid mismatch");
|
|
474
|
+
}
|
|
475
|
+
const kid = decoded.header.kid;
|
|
476
|
+
const publicKey = await input.resolveKey(kid);
|
|
477
|
+
try {
|
|
478
|
+
verifyJws({ token: input.jwsToken, publicKey });
|
|
479
|
+
} catch {
|
|
480
|
+
throw new Error("invalid countersignature");
|
|
481
|
+
}
|
|
482
|
+
const hashIdx = kid.indexOf("#");
|
|
483
|
+
if (hashIdx < 0) throw new Error("countersignature kid must be a DID URL");
|
|
484
|
+
const witnessDID = kid.substring(0, hashIdx);
|
|
485
|
+
if (witnessDID === op.did) {
|
|
486
|
+
throw new Error("countersignature kid DID must differ from operation did (not a witness)");
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
operationCID,
|
|
490
|
+
authorDID: op.did,
|
|
491
|
+
witnessDID
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
var verifyBeaconCountersignature = async (input) => {
|
|
495
|
+
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
496
|
+
if (!decoded) throw new Error("failed to decode beacon countersignature JWS");
|
|
497
|
+
if (decoded.header.typ !== "did:dfos:beacon") {
|
|
498
|
+
throw new Error(`invalid beacon countersignature typ: ${decoded.header.typ}`);
|
|
499
|
+
}
|
|
500
|
+
const result = BeaconPayload.safeParse(decoded.payload);
|
|
501
|
+
if (!result.success) {
|
|
502
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
503
|
+
throw new Error(`invalid beacon payload: ${messages}`);
|
|
504
|
+
}
|
|
505
|
+
const beacon = result.data;
|
|
506
|
+
const encoded = await dagCborCanonicalEncode(beacon);
|
|
507
|
+
const beaconCID = encoded.cid.toString();
|
|
508
|
+
if (beaconCID !== input.expectedCID) {
|
|
509
|
+
throw new Error("beacon countersignature CID does not match expected CID");
|
|
510
|
+
}
|
|
511
|
+
if (decoded.header.cid !== beaconCID) {
|
|
512
|
+
throw new Error("beacon countersignature header cid mismatch");
|
|
513
|
+
}
|
|
514
|
+
const kid = decoded.header.kid;
|
|
515
|
+
const publicKey = await input.resolveKey(kid);
|
|
516
|
+
try {
|
|
517
|
+
verifyJws({ token: input.jwsToken, publicKey });
|
|
518
|
+
} catch {
|
|
519
|
+
throw new Error("invalid beacon countersignature");
|
|
520
|
+
}
|
|
521
|
+
const hashIdx = kid.indexOf("#");
|
|
522
|
+
if (hashIdx < 0) throw new Error("beacon countersignature kid must be a DID URL");
|
|
523
|
+
const witnessDID = kid.substring(0, hashIdx);
|
|
524
|
+
if (witnessDID === beacon.did) {
|
|
525
|
+
throw new Error("beacon countersignature kid DID must differ from beacon did (not a witness)");
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
beaconCID,
|
|
529
|
+
controllerDID: beacon.did,
|
|
530
|
+
witnessDID
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
|
|
370
534
|
export {
|
|
371
535
|
MultikeyPublicKey,
|
|
372
536
|
IdentityOperation,
|
|
373
537
|
VerifiedIdentity,
|
|
374
538
|
ContentOperation,
|
|
539
|
+
BeaconPayload,
|
|
375
540
|
ED25519_PUB_MULTICODEC,
|
|
376
541
|
ED25519_PRIV_MULTICODEC,
|
|
377
542
|
encodeEd25519Multikey,
|
|
@@ -381,5 +546,10 @@ export {
|
|
|
381
546
|
signIdentityOperation,
|
|
382
547
|
verifyIdentityChain,
|
|
383
548
|
signContentOperation,
|
|
384
|
-
verifyContentChain
|
|
549
|
+
verifyContentChain,
|
|
550
|
+
signBeacon,
|
|
551
|
+
verifyBeacon,
|
|
552
|
+
signCountersignature,
|
|
553
|
+
verifyCountersignature,
|
|
554
|
+
verifyBeaconCountersignature
|
|
385
555
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/merkle/tree.ts
|
|
2
|
+
var sha256 = async (data) => {
|
|
3
|
+
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
4
|
+
return new Uint8Array(buf);
|
|
5
|
+
};
|
|
6
|
+
var toHex = (bytes) => {
|
|
7
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8
|
+
};
|
|
9
|
+
var hexToBytes = (hex) => {
|
|
10
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
11
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
12
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
13
|
+
}
|
|
14
|
+
return bytes;
|
|
15
|
+
};
|
|
16
|
+
var concat = (a, b) => {
|
|
17
|
+
const result = new Uint8Array(a.length + b.length);
|
|
18
|
+
result.set(a, 0);
|
|
19
|
+
result.set(b, a.length);
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
var hashLeaf = async (contentId) => {
|
|
23
|
+
return sha256(new TextEncoder().encode(contentId));
|
|
24
|
+
};
|
|
25
|
+
var hashInterior = async (left, right) => {
|
|
26
|
+
return sha256(concat(left, right));
|
|
27
|
+
};
|
|
28
|
+
var buildMerkleTree = async (contentIds) => {
|
|
29
|
+
const sorted = [...new Set(contentIds)].sort();
|
|
30
|
+
if (sorted.length === 0) return { root: null, leafCount: 0 };
|
|
31
|
+
let level = await Promise.all(sorted.map(hashLeaf));
|
|
32
|
+
while (level.length > 1) {
|
|
33
|
+
const nextLevel = [];
|
|
34
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
35
|
+
if (i + 1 < level.length) {
|
|
36
|
+
nextLevel.push(await hashInterior(level[i], level[i + 1]));
|
|
37
|
+
} else {
|
|
38
|
+
nextLevel.push(level[i]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
level = nextLevel;
|
|
42
|
+
}
|
|
43
|
+
return { root: toHex(level[0]), leafCount: sorted.length };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/merkle/proof.ts
|
|
47
|
+
var generateMerkleProof = async (contentIds, targetId) => {
|
|
48
|
+
const sorted = [...new Set(contentIds)].sort();
|
|
49
|
+
const targetIdx = sorted.indexOf(targetId);
|
|
50
|
+
if (targetIdx < 0) return null;
|
|
51
|
+
const { root } = await buildMerkleTree(sorted);
|
|
52
|
+
if (!root) return null;
|
|
53
|
+
const leaves = await Promise.all(sorted.map(hashLeaf));
|
|
54
|
+
const path = [];
|
|
55
|
+
let level = leaves;
|
|
56
|
+
let idx = targetIdx;
|
|
57
|
+
while (level.length > 1) {
|
|
58
|
+
const nextLevel = [];
|
|
59
|
+
const nextIdx = Math.floor(idx / 2);
|
|
60
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
61
|
+
if (i + 1 < level.length) {
|
|
62
|
+
if (i === idx || i + 1 === idx) {
|
|
63
|
+
const siblingIdx = i === idx ? i + 1 : i;
|
|
64
|
+
path.push({
|
|
65
|
+
hash: toHex(level[siblingIdx]),
|
|
66
|
+
position: siblingIdx < idx ? "left" : "right"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const interior = await sha256(concat(level[i], level[i + 1]));
|
|
70
|
+
nextLevel.push(interior);
|
|
71
|
+
} else {
|
|
72
|
+
nextLevel.push(level[i]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
level = nextLevel;
|
|
76
|
+
idx = nextIdx;
|
|
77
|
+
}
|
|
78
|
+
return { contentId: targetId, root, path };
|
|
79
|
+
};
|
|
80
|
+
var verifyMerkleProof = async (proof) => {
|
|
81
|
+
let current = await hashLeaf(proof.contentId);
|
|
82
|
+
for (const step of proof.path) {
|
|
83
|
+
const sibling = hexToBytes(step.hash);
|
|
84
|
+
if (step.position === "left") {
|
|
85
|
+
current = await sha256(concat(sibling, current));
|
|
86
|
+
} else {
|
|
87
|
+
current = await sha256(concat(current, sibling));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return toHex(current) === proof.root;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
hexToBytes,
|
|
95
|
+
hashLeaf,
|
|
96
|
+
buildMerkleTree,
|
|
97
|
+
generateMerkleProof,
|
|
98
|
+
verifyMerkleProof
|
|
99
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
|
|
2
|
-
export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer, VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signContentOperation, signIdentityOperation, verifyContentChain, verifyIdentityChain } from './chain/index.js';
|
|
3
|
-
export {
|
|
2
|
+
export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer, VerifiedBeacon, VerifiedBeaconCountersignature, VerifiedContentChain, VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyBeacon, verifyBeaconCountersignature, verifyContentChain, verifyCountersignature, verifyIdentityChain } from './chain/index.js';
|
|
3
|
+
export { MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof } from './merkle/index.js';
|
|
4
4
|
import 'multiformats';
|
|
5
5
|
import 'multiformats/cid';
|
|
6
6
|
import 'zod';
|
|
7
|
-
import 'hono/types';
|
|
8
|
-
import 'hono';
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
ContentOperationsParams,
|
|
4
|
-
ContentOperationsResponse,
|
|
5
|
-
IdentityOperationsParams,
|
|
6
|
-
IdentityOperationsResponse,
|
|
7
|
-
RegistryError,
|
|
8
|
-
ResolveContentResponse,
|
|
9
|
-
ResolveIdentityResponse,
|
|
10
|
-
ResolveOperationResponse,
|
|
11
|
-
SubmitContentChainRequest,
|
|
12
|
-
SubmitContentChainResponse,
|
|
13
|
-
SubmitIdentityChainRequest,
|
|
14
|
-
SubmitIdentityChainResponse,
|
|
15
|
-
createRegistryServer
|
|
16
|
-
} from "./chunk-U6DANYPT.js";
|
|
17
|
-
import {
|
|
2
|
+
BeaconPayload,
|
|
18
3
|
ContentOperation,
|
|
19
4
|
ED25519_PRIV_MULTICODEC,
|
|
20
5
|
ED25519_PUB_MULTICODEC,
|
|
@@ -25,11 +10,16 @@ import {
|
|
|
25
10
|
deriveChainIdentifier,
|
|
26
11
|
deriveContentId,
|
|
27
12
|
encodeEd25519Multikey,
|
|
13
|
+
signBeacon,
|
|
28
14
|
signContentOperation,
|
|
15
|
+
signCountersignature,
|
|
29
16
|
signIdentityOperation,
|
|
17
|
+
verifyBeacon,
|
|
18
|
+
verifyBeaconCountersignature,
|
|
30
19
|
verifyContentChain,
|
|
20
|
+
verifyCountersignature,
|
|
31
21
|
verifyIdentityChain
|
|
32
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-ASGEXSVT.js";
|
|
33
23
|
import {
|
|
34
24
|
JwsVerificationError,
|
|
35
25
|
JwtVerificationError,
|
|
@@ -53,34 +43,29 @@ import {
|
|
|
53
43
|
verifyJws,
|
|
54
44
|
verifyJwt
|
|
55
45
|
} from "./chunk-ZXXP5W5N.js";
|
|
46
|
+
import {
|
|
47
|
+
buildMerkleTree,
|
|
48
|
+
generateMerkleProof,
|
|
49
|
+
hashLeaf,
|
|
50
|
+
hexToBytes,
|
|
51
|
+
verifyMerkleProof
|
|
52
|
+
} from "./chunk-E5CFQG2B.js";
|
|
56
53
|
export {
|
|
57
|
-
|
|
54
|
+
BeaconPayload,
|
|
58
55
|
ContentOperation,
|
|
59
|
-
ContentOperationsParams,
|
|
60
|
-
ContentOperationsResponse,
|
|
61
56
|
ED25519_PRIV_MULTICODEC,
|
|
62
57
|
ED25519_PUB_MULTICODEC,
|
|
63
58
|
IdentityOperation,
|
|
64
|
-
IdentityOperationsParams,
|
|
65
|
-
IdentityOperationsResponse,
|
|
66
59
|
JwsVerificationError,
|
|
67
60
|
JwtVerificationError,
|
|
68
61
|
MultikeyPublicKey,
|
|
69
|
-
RegistryError,
|
|
70
|
-
ResolveContentResponse,
|
|
71
|
-
ResolveIdentityResponse,
|
|
72
|
-
ResolveOperationResponse,
|
|
73
|
-
SubmitContentChainRequest,
|
|
74
|
-
SubmitContentChainResponse,
|
|
75
|
-
SubmitIdentityChainRequest,
|
|
76
|
-
SubmitIdentityChainResponse,
|
|
77
62
|
VerifiedIdentity,
|
|
78
63
|
base64urlDecode,
|
|
79
64
|
base64urlEncode,
|
|
65
|
+
buildMerkleTree,
|
|
80
66
|
createJws,
|
|
81
67
|
createJwt,
|
|
82
68
|
createNewEd25519Keypair,
|
|
83
|
-
createRegistryServer,
|
|
84
69
|
dagCborCanonicalEncode,
|
|
85
70
|
decodeJwsUnsafe,
|
|
86
71
|
decodeJwtUnsafe,
|
|
@@ -90,17 +75,26 @@ export {
|
|
|
90
75
|
encodeEd25519Multikey,
|
|
91
76
|
generateId,
|
|
92
77
|
generateIdNoPrefix,
|
|
78
|
+
generateMerkleProof,
|
|
79
|
+
hashLeaf,
|
|
80
|
+
hexToBytes,
|
|
93
81
|
importEd25519Keypair,
|
|
94
82
|
isCanonicallyEqual,
|
|
95
83
|
isValidEd25519Signature,
|
|
96
84
|
isValidId,
|
|
97
85
|
normalizedId,
|
|
98
86
|
parseDagCborCID,
|
|
87
|
+
signBeacon,
|
|
99
88
|
signContentOperation,
|
|
89
|
+
signCountersignature,
|
|
100
90
|
signIdentityOperation,
|
|
101
91
|
signPayloadEd25519,
|
|
92
|
+
verifyBeacon,
|
|
93
|
+
verifyBeaconCountersignature,
|
|
102
94
|
verifyContentChain,
|
|
95
|
+
verifyCountersignature,
|
|
103
96
|
verifyIdentityChain,
|
|
104
97
|
verifyJws,
|
|
105
|
-
verifyJwt
|
|
98
|
+
verifyJwt,
|
|
99
|
+
verifyMerkleProof
|
|
106
100
|
};
|