@metalabel/dfos-protocol 0.0.2 → 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 +64 -23
- package/DID-METHOD.md +7 -8
- package/PROTOCOL.md +194 -123
- package/README.md +9 -11
- package/dist/chain/index.d.ts +97 -5
- package/dist/chain/index.js +15 -3
- package/dist/{chunk-LWC4PWGZ.js → chunk-ASGEXSVT.js} +177 -7
- package/dist/chunk-E5CFQG2B.js +99 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +28 -34
- 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-4MMWM2PC.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/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;
|
|
@@ -124,12 +138,12 @@ declare const decodeMultikey: (multibase: string) => {
|
|
|
124
138
|
*/
|
|
125
139
|
declare const deriveChainIdentifier: (cidBytes: Uint8Array, prefix: string) => string;
|
|
126
140
|
/**
|
|
127
|
-
* Derive a bare
|
|
141
|
+
* Derive a bare content identifier from CID bytes
|
|
128
142
|
*
|
|
129
143
|
* Returns the raw 22-char hash with no prefix. Applications may add
|
|
130
144
|
* their own prefix for routing (e.g., post_xxxx) — that's semantic sugar.
|
|
131
145
|
*/
|
|
132
|
-
declare const
|
|
146
|
+
declare const deriveContentId: (cidBytes: Uint8Array) => string;
|
|
133
147
|
|
|
134
148
|
/**
|
|
135
149
|
* Sign an identity operation as a JWS and derive the operation CID
|
|
@@ -156,8 +170,8 @@ declare const verifyIdentityChain: (input: {
|
|
|
156
170
|
}) => Promise<VerifiedIdentity>;
|
|
157
171
|
|
|
158
172
|
interface VerifiedContentChain {
|
|
159
|
-
/**
|
|
160
|
-
|
|
173
|
+
/** Content identifier — bare 22-char hash derived from genesis CID */
|
|
174
|
+
contentId: string;
|
|
161
175
|
/** CID of the genesis operation */
|
|
162
176
|
genesisCID: string;
|
|
163
177
|
/** CID of the most recent operation */
|
|
@@ -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,
|
|
@@ -7,15 +8,21 @@ import {
|
|
|
7
8
|
VerifiedIdentity,
|
|
8
9
|
decodeMultikey,
|
|
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,
|
|
@@ -24,10 +31,15 @@ export {
|
|
|
24
31
|
VerifiedIdentity,
|
|
25
32
|
decodeMultikey,
|
|
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";
|
|
@@ -127,7 +140,7 @@ var deriveChainIdentifier = (cidBytes, prefix) => {
|
|
|
127
140
|
const id = generateIdNoPrefix({ seed: cidBytes });
|
|
128
141
|
return `${prefix}:${id}`;
|
|
129
142
|
};
|
|
130
|
-
var
|
|
143
|
+
var deriveContentId = (cidBytes) => {
|
|
131
144
|
return generateIdNoPrefix({ seed: cidBytes });
|
|
132
145
|
};
|
|
133
146
|
|
|
@@ -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`);
|
|
@@ -289,7 +305,7 @@ var signContentOperation = async (input) => {
|
|
|
289
305
|
var verifyContentChain = async (input) => {
|
|
290
306
|
if (input.log.length === 0) throw new Error("log must have at least one operation");
|
|
291
307
|
const state = {
|
|
292
|
-
|
|
308
|
+
contentId: null,
|
|
293
309
|
genesisCID: null,
|
|
294
310
|
headCID: null,
|
|
295
311
|
isDeleted: false,
|
|
@@ -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 });
|
|
@@ -339,7 +364,7 @@ var verifyContentChain = async (input) => {
|
|
|
339
364
|
}
|
|
340
365
|
if (idx === 0) {
|
|
341
366
|
state.genesisCID = operationCID;
|
|
342
|
-
state.
|
|
367
|
+
state.contentId = deriveContentId(encoded.cid.bytes);
|
|
343
368
|
}
|
|
344
369
|
state.headCID = operationCID;
|
|
345
370
|
state.previousCID = operationCID;
|
|
@@ -358,7 +383,7 @@ var verifyContentChain = async (input) => {
|
|
|
358
383
|
}
|
|
359
384
|
}
|
|
360
385
|
return {
|
|
361
|
-
|
|
386
|
+
contentId: state.contentId,
|
|
362
387
|
genesisCID: state.genesisCID,
|
|
363
388
|
headCID: state.headCID,
|
|
364
389
|
isDeleted: state.isDeleted,
|
|
@@ -367,19 +392,164 @@ 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,
|
|
378
543
|
decodeMultikey,
|
|
379
544
|
deriveChainIdentifier,
|
|
380
|
-
|
|
545
|
+
deriveContentId,
|
|
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,
|
|
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';
|