@metalabel/dfos-protocol 0.25.0 → 0.27.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/README.md CHANGED
@@ -5,9 +5,14 @@ Ed25519 signed chain primitives for cryptographic identity and verifiable conten
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @metalabel/dfos-protocol
8
+ npm install @metalabel/dfos-protocol zod
9
9
  ```
10
10
 
11
+ `zod` is a peer dependency. The package exports Zod schema objects directly
12
+ (`IdentityOperation`, `ContentOperation`, `MultikeyPublicKey`, …) so you can
13
+ compose them into your own schemas — which only works if your app and this
14
+ package share one Zod install.
15
+
11
16
  ## Usage
12
17
 
13
18
  ```ts
@@ -1,5 +1,5 @@
1
- import { I as IdentityOperation, q as Signer, s as VerifiedIdentity, S as ServiceEntry, d as ContentOperation, R as RevocationChecker, e as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-CAYCqUCP.js';
2
- export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, g as CreditClaimPayload, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_OPERATION_SIZE, l as MAX_SERVICES_ENTRIES, m as MAX_SERVICES_PAYLOAD_SIZE, n as MultikeyPublicKey, o as RevocationPayload, p as ServicesArray, x as parseProtocolTimestampUnix } from '../dfos-credential-CAYCqUCP.js';
1
+ import { I as IdentityOperation, t as Signer, v as VerifiedIdentity, S as ServiceEntry, d as ContentOperation, R as RevocationChecker, e as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-DFZRl0FD.js';
2
+ export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, g as CreditClaimPayload, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_OPERATION_SIZE, l as MAX_SERVICES_ENTRIES, m as MAX_SERVICES_PAYLOAD_SIZE, n as MAX_SIGN_REQUEST_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_SIZE, p as MultikeyPublicKey, q as RevocationPayload, r as ServicesArray, s as SignRequestPayload, B as parseProtocolTimestampUnix } from '../dfos-credential-DFZRl0FD.js';
3
3
  import 'zod';
4
4
 
5
5
  /** Ed25519 public key multicodec value */
@@ -437,4 +437,69 @@ declare const verifyCreditEntry: (entry: CreditEntry, options: {
437
437
  contentId: string;
438
438
  }) => Promise<VerifiedCreditEntry>;
439
439
 
440
- export { type AnchorKind, ArtifactPayload, ContentOperation, CountersignPayload, type CreditClaimFailureReason, CreditClaimVerifyError, type CreditEntry, type CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, RECOGNIZED_SERVICE_TYPES, ServiceEntry, Signer, type VerifiedArtifact, type VerifiedContentChain, type VerifiedCountersignature, type VerifiedCreditClaim, type VerifiedCreditEntry, VerifiedIdentity, type VerifiedRevocation, anchorsByLabel, assertServicesWithinCap, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation };
440
+ interface VerifiedSignRequest {
441
+ /** Requester DID */
442
+ did: string;
443
+ /** The one identity being asked to sign */
444
+ subject: string;
445
+ /** JWS typ the produced artifact must carry */
446
+ payloadTyp: string;
447
+ /** Exact decoded target bytes; never re-serialized by the envelope path */
448
+ payloadBytes: Uint8Array;
449
+ createdAt: string;
450
+ expiresAt: string;
451
+ /** kid from the JWS header */
452
+ signerKeyId: string;
453
+ /** CID of the sign-request payload */
454
+ requestCID: string;
455
+ }
456
+ type SignRequestFailureReason = 'invalid' | 'unverifiable';
457
+ /**
458
+ * Thrown by sign-request verification and canonical-payload refusal paths.
459
+ * Consumers branch on `reason`, never on diagnostic message text.
460
+ */
461
+ declare class SignRequestVerifyError extends Error {
462
+ readonly reason: SignRequestFailureReason;
463
+ constructor(reason: SignRequestFailureReason, message: string);
464
+ }
465
+ /**
466
+ * Build a sign-request envelope around exact target bytes.
467
+ *
468
+ * The kid is derived from `did` + `keyId`, so a requester mismatch is
469
+ * unrepresentable on the build path. Both timestamps, including overrides, are
470
+ * floor-normalized to whole seconds before signing.
471
+ */
472
+ declare const buildSignRequest: (input: {
473
+ did: string;
474
+ subject: string;
475
+ payloadTyp: string;
476
+ payload: Uint8Array;
477
+ expiresAt: string;
478
+ createdAt?: string;
479
+ signer: Signer;
480
+ keyId: string;
481
+ }) => Promise<{
482
+ jwsToken: string;
483
+ requestCID: string;
484
+ }>;
485
+ /**
486
+ * Verify a sign-request envelope in SIGNING.md's exact 1–9 order.
487
+ *
488
+ * `resolveIdentity` supplies CURRENT identity state. A missing identity or
489
+ * resolver failure is `unverifiable`; a deleted identity, missing current key,
490
+ * or any checked-and-failed condition is `invalid`.
491
+ */
492
+ declare const verifySignRequest: (jwsToken: string, options: {
493
+ resolveIdentity: (did: string) => Promise<VerifiedIdentity | undefined>;
494
+ /** Unix milliseconds; defaults to Date.now() */
495
+ now?: number;
496
+ }) => Promise<VerifiedSignRequest>;
497
+ /**
498
+ * Assert that exact target bytes are the unique canonical representation the
499
+ * signer understands. SIGNING 0.1 implements only `did:dfos:credit-claim`.
500
+ */
501
+ declare const assertCanonicalSignRequestPayload: (payloadTyp: string, payloadBytes: Uint8Array, context: {
502
+ subject: string;
503
+ }) => void;
504
+
505
+ export { type AnchorKind, ArtifactPayload, ContentOperation, CountersignPayload, type CreditClaimFailureReason, CreditClaimVerifyError, type CreditEntry, type CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, RECOGNIZED_SERVICE_TYPES, ServiceEntry, type SignRequestFailureReason, SignRequestVerifyError, Signer, type VerifiedArtifact, type VerifiedContentChain, type VerifiedCountersignature, type VerifiedCreditClaim, type VerifiedCreditEntry, VerifiedIdentity, type VerifiedRevocation, type VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest };
@@ -13,14 +13,20 @@ import {
13
13
  MAX_OPERATION_SIZE,
14
14
  MAX_SERVICES_ENTRIES,
15
15
  MAX_SERVICES_PAYLOAD_SIZE,
16
+ MAX_SIGN_REQUEST_PAYLOAD_SIZE,
17
+ MAX_SIGN_REQUEST_SIZE,
16
18
  MultikeyPublicKey,
17
19
  RECOGNIZED_SERVICE_TYPES,
18
20
  RevocationPayload,
19
21
  ServiceEntry,
20
22
  ServicesArray,
23
+ SignRequestPayload,
24
+ SignRequestVerifyError,
21
25
  VerifiedIdentity,
22
26
  anchorsByLabel,
27
+ assertCanonicalSignRequestPayload,
23
28
  assertServicesWithinCap,
29
+ buildSignRequest,
24
30
  classifyAnchor,
25
31
  deriveChainIdentifier,
26
32
  deriveContentId,
@@ -41,14 +47,15 @@ import {
41
47
  verifyCreditEntry,
42
48
  verifyIdentityChain,
43
49
  verifyIdentityExtensionFromTrustedState,
44
- verifyRevocation
45
- } from "../chunk-3USIENKC.js";
50
+ verifyRevocation,
51
+ verifySignRequest
52
+ } from "../chunk-Q376VG5Z.js";
46
53
  import {
47
54
  ED25519_PRIV_MULTICODEC,
48
55
  ED25519_PUB_MULTICODEC,
49
56
  decodeMultikey,
50
57
  encodeEd25519Multikey
51
- } from "../chunk-PGFHCBGH.js";
58
+ } from "../chunk-6HVYO5OS.js";
52
59
  import "../chunk-4QQ5HK5M.js";
53
60
  export {
54
61
  ARTIFACT_CID_ANCHOR_RE,
@@ -67,14 +74,20 @@ export {
67
74
  MAX_OPERATION_SIZE,
68
75
  MAX_SERVICES_ENTRIES,
69
76
  MAX_SERVICES_PAYLOAD_SIZE,
77
+ MAX_SIGN_REQUEST_PAYLOAD_SIZE,
78
+ MAX_SIGN_REQUEST_SIZE,
70
79
  MultikeyPublicKey,
71
80
  RECOGNIZED_SERVICE_TYPES,
72
81
  RevocationPayload,
73
82
  ServiceEntry,
74
83
  ServicesArray,
84
+ SignRequestPayload,
85
+ SignRequestVerifyError,
75
86
  VerifiedIdentity,
76
87
  anchorsByLabel,
88
+ assertCanonicalSignRequestPayload,
77
89
  assertServicesWithinCap,
90
+ buildSignRequest,
78
91
  classifyAnchor,
79
92
  decodeMultikey,
80
93
  deriveChainIdentifier,
@@ -97,5 +110,6 @@ export {
97
110
  verifyCreditEntry,
98
111
  verifyIdentityChain,
99
112
  verifyIdentityExtensionFromTrustedState,
100
- verifyRevocation
113
+ verifyRevocation,
114
+ verifySignRequest
101
115
  };
@@ -323,6 +323,9 @@ var isAttenuated = (parentAtt, childAtt) => {
323
323
  if (childRes.type === "chain" && parentRes.type === "chain") {
324
324
  return childRes.id === parentRes.id;
325
325
  }
326
+ if (childRes.type !== "chain" && parentRes.type !== "chain") {
327
+ return childEntry.resource === parentEntry.resource;
328
+ }
326
329
  return false;
327
330
  });
328
331
  });
@@ -5,8 +5,11 @@ import {
5
5
  matchesResource,
6
6
  verifyDFOSCredential,
7
7
  verifyDelegationChain
8
- } from "./chunk-PGFHCBGH.js";
8
+ } from "./chunk-6HVYO5OS.js";
9
9
  import {
10
+ assertJwsProfile,
11
+ base64urlDecode,
12
+ base64urlEncode,
10
13
  createJws,
11
14
  dagCborCanonicalEncode,
12
15
  decodeJwsUnsafe,
@@ -174,6 +177,18 @@ var CreditClaimPayload = z.looseObject({
174
177
  createdAt: Iso8601,
175
178
  asOfDocumentCID: CIDString.min(1, "asOfDocumentCID must be non-empty when present").optional()
176
179
  });
180
+ var MAX_SIGN_REQUEST_SIZE = 8192;
181
+ var MAX_SIGN_REQUEST_PAYLOAD_SIZE = 4096;
182
+ var SignRequestPayload = z.looseObject({
183
+ version: z.literal(1),
184
+ type: z.literal("sign-request"),
185
+ did: z.string().regex(/^did:/, "did must be a DID (did: prefix)"),
186
+ subject: z.string().regex(/^did:/, "subject must be a DID (did: prefix)"),
187
+ payloadTyp: z.string().min(1),
188
+ payload: z.string().min(1),
189
+ createdAt: Iso8601,
190
+ expiresAt: Iso8601
191
+ });
177
192
 
178
193
  // src/chain/derivation.ts
179
194
  var deriveChainIdentifier = (cidBytes, prefix) => {
@@ -1045,6 +1060,248 @@ var verifyCreditEntry = async (entry, options) => {
1045
1060
  };
1046
1061
  };
1047
1062
 
1063
+ // src/chain/sign-request.ts
1064
+ var SignRequestVerifyError = class extends Error {
1065
+ reason;
1066
+ constructor(reason, message) {
1067
+ super(message);
1068
+ this.name = "SignRequestVerifyError";
1069
+ this.reason = reason;
1070
+ }
1071
+ };
1072
+ var invalid2 = (message) => new SignRequestVerifyError("invalid", message);
1073
+ var unverifiable2 = (message) => new SignRequestVerifyError("unverifiable", message);
1074
+ var normalizeSignRequestTimestamp = (value, field) => {
1075
+ if (value === void 0) {
1076
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\d{3}Z$/, "000Z");
1077
+ }
1078
+ const ms = Date.parse(value);
1079
+ if (Number.isNaN(ms)) {
1080
+ throw new Error(`invalid sign request payload: unparseable ${field}: ${value}`);
1081
+ }
1082
+ return new Date(Math.floor(ms / 1e3) * 1e3).toISOString();
1083
+ };
1084
+ var assertTemporalWindow = (createdAt, expiresAt, makeError) => {
1085
+ const createdMs = Date.parse(createdAt);
1086
+ const expiresMs = Date.parse(expiresAt);
1087
+ if (expiresMs <= createdMs) {
1088
+ throw makeError("sign request expiresAt must be strictly after createdAt");
1089
+ }
1090
+ if (expiresMs - createdMs > 6048e5) {
1091
+ throw makeError("sign request validity window exceeds 604800 seconds");
1092
+ }
1093
+ };
1094
+ var decodeTargetPayload = (encoded) => {
1095
+ if (!/^[A-Za-z0-9_-]+$/.test(encoded)) {
1096
+ throw invalid2("sign request payload must be unpadded base64url");
1097
+ }
1098
+ let decoded;
1099
+ try {
1100
+ decoded = base64urlDecode(encoded);
1101
+ } catch {
1102
+ throw invalid2("sign request payload is not valid base64url");
1103
+ }
1104
+ if (base64urlEncode(decoded) !== encoded) {
1105
+ throw invalid2("sign request payload is not canonical unpadded base64url");
1106
+ }
1107
+ if (decoded.length === 0) {
1108
+ throw invalid2("sign request payload must decode to non-empty bytes");
1109
+ }
1110
+ if (decoded.length > MAX_SIGN_REQUEST_PAYLOAD_SIZE) {
1111
+ throw invalid2(
1112
+ `sign request payload exceeds max decoded size: ${decoded.length} > ${MAX_SIGN_REQUEST_PAYLOAD_SIZE}`
1113
+ );
1114
+ }
1115
+ return decoded;
1116
+ };
1117
+ var resolveCurrentKey = (identity, kid) => {
1118
+ const hashIdx = kid.indexOf("#");
1119
+ if (hashIdx < 0) throw invalid2("sign request kid must be a DID URL");
1120
+ const keyId = kid.substring(hashIdx + 1);
1121
+ const key = [...identity.authKeys, ...identity.assertKeys, ...identity.controllerKeys].find(
1122
+ (candidate) => candidate.id === keyId
1123
+ );
1124
+ if (!key) throw invalid2(`key ${keyId} not found on current identity ${identity.did}`);
1125
+ try {
1126
+ return decodeMultikey(key.publicKeyMultibase).keyBytes;
1127
+ } catch {
1128
+ throw invalid2(`key ${keyId} on identity ${identity.did} is not a valid Ed25519 multikey`);
1129
+ }
1130
+ };
1131
+ var buildSignRequest = async (input) => {
1132
+ if (input.payload.length === 0) {
1133
+ throw new Error("invalid sign request payload: target payload must be non-empty");
1134
+ }
1135
+ if (input.payload.length > MAX_SIGN_REQUEST_PAYLOAD_SIZE) {
1136
+ throw new Error(
1137
+ `sign request payload exceeds max decoded size: ${input.payload.length} > ${MAX_SIGN_REQUEST_PAYLOAD_SIZE}`
1138
+ );
1139
+ }
1140
+ const createdAt = normalizeSignRequestTimestamp(input.createdAt, "createdAt");
1141
+ const expiresAt = normalizeSignRequestTimestamp(input.expiresAt, "expiresAt");
1142
+ assertTemporalWindow(createdAt, expiresAt, (message) => new Error(message));
1143
+ const payload = {
1144
+ version: 1,
1145
+ type: "sign-request",
1146
+ did: input.did,
1147
+ subject: input.subject,
1148
+ payloadTyp: input.payloadTyp,
1149
+ payload: base64urlEncode(input.payload),
1150
+ createdAt,
1151
+ expiresAt
1152
+ };
1153
+ const parsed = SignRequestPayload.safeParse(payload);
1154
+ if (!parsed.success) {
1155
+ const messages = parsed.error.issues.map((issue) => issue.message).join(", ");
1156
+ throw new Error(`invalid sign request payload: ${messages}`);
1157
+ }
1158
+ const encoded = await dagCborCanonicalEncode(payload);
1159
+ const requestCID = encoded.cid.toString();
1160
+ const jwsToken = await createJws({
1161
+ header: {
1162
+ alg: "EdDSA",
1163
+ typ: "did:dfos:sign-request",
1164
+ kid: `${input.did}#${input.keyId}`,
1165
+ cid: requestCID
1166
+ },
1167
+ payload,
1168
+ sign: input.signer
1169
+ });
1170
+ if (new TextEncoder().encode(jwsToken).length > MAX_SIGN_REQUEST_SIZE) {
1171
+ throw new Error(
1172
+ `sign request exceeds max size: ${new TextEncoder().encode(jwsToken).length} > ${MAX_SIGN_REQUEST_SIZE}`
1173
+ );
1174
+ }
1175
+ return { jwsToken, requestCID };
1176
+ };
1177
+ var verifySignRequest = async (jwsToken, options) => {
1178
+ try {
1179
+ const tokenSize = new TextEncoder().encode(jwsToken).length;
1180
+ if (tokenSize > MAX_SIGN_REQUEST_SIZE) {
1181
+ throw invalid2(`sign request exceeds max size: ${tokenSize} > ${MAX_SIGN_REQUEST_SIZE}`);
1182
+ }
1183
+ const decoded = decodeJwsUnsafe(jwsToken);
1184
+ if (!decoded) throw invalid2("failed to decode sign request JWS");
1185
+ const rawHeader = decoded.header;
1186
+ if (typeof rawHeader !== "object" || rawHeader === null || Array.isArray(rawHeader)) {
1187
+ throw invalid2("sign request protected header must be an object");
1188
+ }
1189
+ assertJwsProfile(rawHeader, invalid2);
1190
+ if (typeof rawHeader["typ"] !== "string" || typeof rawHeader["kid"] !== "string") {
1191
+ throw invalid2("sign request header must carry a string typ and kid");
1192
+ }
1193
+ if (decoded.header.typ !== "did:dfos:sign-request") {
1194
+ throw invalid2(`invalid sign request typ: ${decoded.header.typ}`);
1195
+ }
1196
+ const parsed = SignRequestPayload.safeParse(decoded.payload);
1197
+ if (!parsed.success) {
1198
+ const messages = parsed.error.issues.map((issue) => issue.message).join(", ");
1199
+ throw invalid2(`invalid sign request payload: ${messages}`);
1200
+ }
1201
+ const payload = parsed.data;
1202
+ const payloadBytes = decodeTargetPayload(payload.payload);
1203
+ const kid = decoded.header.kid;
1204
+ const hashIdx = kid.indexOf("#");
1205
+ if (hashIdx < 0) throw invalid2("sign request kid must be a DID URL");
1206
+ if (kid.substring(0, hashIdx) !== payload.did) {
1207
+ throw invalid2("sign request kid DID does not match payload did");
1208
+ }
1209
+ let identity;
1210
+ try {
1211
+ identity = await options.resolveIdentity(payload.did);
1212
+ } catch (error) {
1213
+ const detail = error instanceof Error ? error.message : String(error);
1214
+ throw unverifiable2(`could not resolve requester identity ${payload.did}: ${detail}`);
1215
+ }
1216
+ if (!identity) throw unverifiable2(`requester identity not found: ${payload.did}`);
1217
+ if (identity.isDeleted) throw invalid2(`requester identity is deleted: ${payload.did}`);
1218
+ const publicKey = resolveCurrentKey(identity, kid);
1219
+ try {
1220
+ verifyJws({ token: jwsToken, publicKey });
1221
+ } catch {
1222
+ throw invalid2("invalid sign request signature");
1223
+ }
1224
+ let encoded;
1225
+ try {
1226
+ encoded = await dagCborCanonicalEncode(payload);
1227
+ } catch (error) {
1228
+ const detail = error instanceof Error ? error.message : String(error);
1229
+ throw invalid2(`failed to derive sign request CID: ${detail}`);
1230
+ }
1231
+ const requestCID = encoded.cid.toString();
1232
+ if (decoded.header.cid !== requestCID) throw invalid2("sign request cid mismatch");
1233
+ assertTemporalWindow(payload.createdAt, payload.expiresAt, invalid2);
1234
+ if ((options.now ?? Date.now()) >= Date.parse(payload.expiresAt)) {
1235
+ throw invalid2("sign request is expired");
1236
+ }
1237
+ return {
1238
+ did: payload.did,
1239
+ subject: payload.subject,
1240
+ payloadTyp: payload.payloadTyp,
1241
+ payloadBytes,
1242
+ createdAt: payload.createdAt,
1243
+ expiresAt: payload.expiresAt,
1244
+ signerKeyId: kid,
1245
+ requestCID
1246
+ };
1247
+ } catch (error) {
1248
+ if (error instanceof SignRequestVerifyError) throw error;
1249
+ const detail = error instanceof Error ? error.message : String(error);
1250
+ throw unverifiable2(`sign request verification could not complete: ${detail}`);
1251
+ }
1252
+ };
1253
+ var assertCanonicalSignRequestPayload = (payloadTyp, payloadBytes, context) => {
1254
+ if (payloadTyp !== "did:dfos:credit-claim") {
1255
+ throw invalid2(`unsupported sign request payloadTyp: ${payloadTyp}`);
1256
+ }
1257
+ let source;
1258
+ try {
1259
+ source = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
1260
+ } catch {
1261
+ throw invalid2("sign request payload is not valid UTF-8 JSON");
1262
+ }
1263
+ if (source.startsWith("\uFEFF")) {
1264
+ throw invalid2("sign request payload must not carry a UTF-8 BOM");
1265
+ }
1266
+ let raw;
1267
+ try {
1268
+ raw = JSON.parse(source);
1269
+ } catch {
1270
+ throw invalid2("sign request payload is not valid JSON");
1271
+ }
1272
+ const parsed = CreditClaimPayload.strict().safeParse(raw);
1273
+ if (!parsed.success) {
1274
+ const messages = parsed.error.issues.map((issue) => issue.message).join(", ");
1275
+ throw invalid2(`invalid canonical credit-claim payload: ${messages}`);
1276
+ }
1277
+ const payload = parsed.data;
1278
+ if (!payload.createdAt.endsWith(".000Z")) {
1279
+ throw invalid2("credit-claim createdAt must be normalized to whole seconds");
1280
+ }
1281
+ if (payload.asOfDocumentCID !== void 0 && payload.asOfDocumentCID.length === 0) {
1282
+ throw invalid2("credit-claim asOfDocumentCID must be non-empty when present");
1283
+ }
1284
+ if (payload.did !== context.subject) {
1285
+ throw invalid2("credit-claim payload did does not match sign request subject");
1286
+ }
1287
+ if (/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(payload.role)) {
1288
+ throw invalid2("credit-claim role is not well-formed unicode (lone surrogate)");
1289
+ }
1290
+ const canonical = JSON.stringify({
1291
+ version: payload.version,
1292
+ type: payload.type,
1293
+ contentId: payload.contentId,
1294
+ did: payload.did,
1295
+ role: payload.role,
1296
+ createdAt: payload.createdAt,
1297
+ ...payload.asOfDocumentCID !== void 0 ? { asOfDocumentCID: payload.asOfDocumentCID } : {}
1298
+ });
1299
+ const canonicalBytes = new TextEncoder().encode(canonical);
1300
+ if (canonicalBytes.length !== payloadBytes.length || !canonicalBytes.every((byte, index) => byte === payloadBytes[index])) {
1301
+ throw invalid2("credit-claim payload bytes are not canonical");
1302
+ }
1303
+ };
1304
+
1048
1305
  export {
1049
1306
  MAX_SERVICES_ENTRIES,
1050
1307
  MAX_SERVICES_PAYLOAD_SIZE,
@@ -1065,6 +1322,9 @@ export {
1065
1322
  RevocationPayload,
1066
1323
  MAX_CREDIT_CLAIM_SIZE,
1067
1324
  CreditClaimPayload,
1325
+ MAX_SIGN_REQUEST_SIZE,
1326
+ MAX_SIGN_REQUEST_PAYLOAD_SIZE,
1327
+ SignRequestPayload,
1068
1328
  deriveChainIdentifier,
1069
1329
  deriveContentId,
1070
1330
  assertServicesWithinCap,
@@ -1088,5 +1348,9 @@ export {
1088
1348
  CreditClaimVerifyError,
1089
1349
  signCreditClaim,
1090
1350
  verifyCreditClaim,
1091
- verifyCreditEntry
1351
+ verifyCreditEntry,
1352
+ SignRequestVerifyError,
1353
+ buildSignRequest,
1354
+ verifySignRequest,
1355
+ assertCanonicalSignRequestPayload
1092
1356
  };
@@ -1,4 +1,4 @@
1
- export { b as Attenuation, c as AuthTokenClaims, f as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as RevocationChecker, V as VerifiedDFOSCredential, r as VerifiedDelegationChain, t as createDFOSCredential, u as decodeDFOSCredentialUnsafe, v as isAttenuated, w as matchesResource, y as verifyDFOSCredential, z as verifyDelegationChain } from '../dfos-credential-CAYCqUCP.js';
1
+ export { b as Attenuation, c as AuthTokenClaims, f as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as RevocationChecker, V as VerifiedDFOSCredential, u as VerifiedDelegationChain, w as createDFOSCredential, x as decodeDFOSCredentialUnsafe, y as isAttenuated, z as matchesResource, E as verifyDFOSCredential, F as verifyDelegationChain } from '../dfos-credential-DFZRl0FD.js';
2
2
  import 'zod';
3
3
 
4
4
  interface AuthTokenCreateOptions {
@@ -13,7 +13,7 @@ import {
13
13
  verifyAuthToken,
14
14
  verifyDFOSCredential,
15
15
  verifyDelegationChain
16
- } from "../chunk-PGFHCBGH.js";
16
+ } from "../chunk-6HVYO5OS.js";
17
17
  import "../chunk-4QQ5HK5M.js";
18
18
  export {
19
19
  Attenuation,
@@ -295,6 +295,37 @@ declare const CreditClaimPayload: z.ZodObject<{
295
295
  asOfDocumentCID: z.ZodOptional<z.ZodString>;
296
296
  }, z.core.$loose>;
297
297
  type CreditClaimPayload = z.infer<typeof CreditClaimPayload>;
298
+ /**
299
+ * Max byte length of a sign-request JWS token. Checked before any decode on the
300
+ * verify path and after construction on the build path. VALIDITY-determining:
301
+ * MUST match maxSignRequestSize in the Go reference.
302
+ */
303
+ declare const MAX_SIGN_REQUEST_SIZE = 8192;
304
+ /**
305
+ * Max decoded byte length of the exact target payload carried by a sign request.
306
+ * This is the single aggregate cap on target bytes; there are no per-field caps.
307
+ * VALIDITY-determining: MUST match maxSignRequestPayloadSize in Go.
308
+ */
309
+ declare const MAX_SIGN_REQUEST_PAYLOAD_SIZE = 4096;
310
+ /**
311
+ * Sign request: a requester's signed ask that one subject sign exact target bytes
312
+ * as one named artifact type before a bounded deadline.
313
+ *
314
+ * Unknown envelope fields are preserved-and-ignored, matching every other wire
315
+ * payload in this file. The signer-side target-payload check is deliberately
316
+ * strict instead: a signer refuses fields it cannot render (see SIGNING.md).
317
+ */
318
+ declare const SignRequestPayload: z.ZodObject<{
319
+ version: z.ZodLiteral<1>;
320
+ type: z.ZodLiteral<"sign-request">;
321
+ did: z.ZodString;
322
+ subject: z.ZodString;
323
+ payloadTyp: z.ZodString;
324
+ payload: z.ZodString;
325
+ createdAt: z.ZodISODateTime;
326
+ expiresAt: z.ZodISODateTime;
327
+ }, z.core.$loose>;
328
+ type SignRequestPayload = z.infer<typeof SignRequestPayload>;
298
329
 
299
330
  /**
300
331
  * Max byte length of a credential JWS token — the credential's analog of
@@ -462,6 +493,11 @@ declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, option
462
493
  * - `chain:X` covered by `chain:*` (narrowing from wildcard — valid)
463
494
  * - `chain:*` covered by `chain:*` (exact match)
464
495
  * - `chain:*` NOT covered by `chain:X` (widening — invalid)
496
+ * - Non-`chain` types (`mailbox:<id>`, and any form a future capability
497
+ * registers): exact byte equality of the full resource string, nothing else.
498
+ * The wildcard is a `chain:`-only concept — a literal `*` id in any other
499
+ * type is an ordinary id covering only itself — and coverage never crosses
500
+ * resource types. See CREDENTIALS.md "Resource Types".
465
501
  * - Actions: child action set must be a subset of parent action set
466
502
  */
467
503
  declare const isAttenuated: (parentAtt: Attenuation[], childAtt: Attenuation[]) => boolean;
@@ -490,4 +526,4 @@ declare class CredentialVerificationError extends Error {
490
526
  constructor(message: string);
491
527
  }
492
528
 
493
- export { ARTIFACT_CID_ANCHOR_RE as A, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, IdentityOperation as I, MAX_ARTIFACT_PAYLOAD_SIZE as M, type RevocationChecker as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, AuthTokenClaims as c, ContentOperation as d, CountersignPayload as e, CredentialVerificationError as f, CreditClaimPayload as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_OPERATION_SIZE as k, MAX_SERVICES_ENTRIES as l, MAX_SERVICES_PAYLOAD_SIZE as m, MultikeyPublicKey as n, RevocationPayload as o, ServicesArray as p, type Signer as q, type VerifiedDelegationChain as r, VerifiedIdentity as s, createDFOSCredential as t, decodeDFOSCredentialUnsafe as u, isAttenuated as v, matchesResource as w, parseProtocolTimestampUnix as x, verifyDFOSCredential as y, verifyDelegationChain as z };
529
+ export { ARTIFACT_CID_ANCHOR_RE as A, parseProtocolTimestampUnix as B, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, verifyDFOSCredential as E, verifyDelegationChain as F, IdentityOperation as I, MAX_ARTIFACT_PAYLOAD_SIZE as M, type RevocationChecker as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, AuthTokenClaims as c, ContentOperation as d, CountersignPayload as e, CredentialVerificationError as f, CreditClaimPayload as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_OPERATION_SIZE as k, MAX_SERVICES_ENTRIES as l, MAX_SERVICES_PAYLOAD_SIZE as m, MAX_SIGN_REQUEST_PAYLOAD_SIZE as n, MAX_SIGN_REQUEST_SIZE as o, MultikeyPublicKey as p, RevocationPayload as q, ServicesArray as r, SignRequestPayload as s, type Signer as t, type VerifiedDelegationChain as u, VerifiedIdentity as v, createDFOSCredential as w, decodeDFOSCredentialUnsafe as x, isAttenuated as y, matchesResource as z };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertJwsProfile, 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 { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, c as AuthTokenClaims, C as CONTENT_ID_ANCHOR_RE, d as ContentOperation, e as CountersignPayload, f as CredentialVerificationError, g as CreditClaimPayload, D as DFOSCredentialPayload, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_OPERATION_SIZE, l as MAX_SERVICES_ENTRIES, m as MAX_SERVICES_PAYLOAD_SIZE, n as MultikeyPublicKey, R as RevocationChecker, o as RevocationPayload, S as ServiceEntry, p as ServicesArray, q as Signer, V as VerifiedDFOSCredential, r as VerifiedDelegationChain, s as VerifiedIdentity, t as createDFOSCredential, u as decodeDFOSCredentialUnsafe, v as isAttenuated, w as matchesResource, x as parseProtocolTimestampUnix, y as verifyDFOSCredential, z as verifyDelegationChain } from './dfos-credential-CAYCqUCP.js';
3
- export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, anchorsByLabel, assertServicesWithinCap, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation } from './chain/index.js';
2
+ export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, c as AuthTokenClaims, C as CONTENT_ID_ANCHOR_RE, d as ContentOperation, e as CountersignPayload, f as CredentialVerificationError, g as CreditClaimPayload, D as DFOSCredentialPayload, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_OPERATION_SIZE, l as MAX_SERVICES_ENTRIES, m as MAX_SERVICES_PAYLOAD_SIZE, n as MAX_SIGN_REQUEST_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_SIZE, p as MultikeyPublicKey, R as RevocationChecker, q as RevocationPayload, S as ServiceEntry, r as ServicesArray, s as SignRequestPayload, t as Signer, V as VerifiedDFOSCredential, u as VerifiedDelegationChain, v as VerifiedIdentity, w as createDFOSCredential, x as decodeDFOSCredentialUnsafe, y as isAttenuated, z as matchesResource, B as parseProtocolTimestampUnix, E as verifyDFOSCredential, F as verifyDelegationChain } from './dfos-credential-DFZRl0FD.js';
3
+ export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, SignRequestFailureReason, SignRequestVerifyError, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest } from './chain/index.js';
4
4
  export { AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, VerifiedAuthToken, createAuthToken, verifyAuthToken } from './credentials/index.js';
5
5
  export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
6
6
  import 'multiformats';
package/dist/index.js CHANGED
@@ -13,14 +13,20 @@ import {
13
13
  MAX_OPERATION_SIZE,
14
14
  MAX_SERVICES_ENTRIES,
15
15
  MAX_SERVICES_PAYLOAD_SIZE,
16
+ MAX_SIGN_REQUEST_PAYLOAD_SIZE,
17
+ MAX_SIGN_REQUEST_SIZE,
16
18
  MultikeyPublicKey,
17
19
  RECOGNIZED_SERVICE_TYPES,
18
20
  RevocationPayload,
19
21
  ServiceEntry,
20
22
  ServicesArray,
23
+ SignRequestPayload,
24
+ SignRequestVerifyError,
21
25
  VerifiedIdentity,
22
26
  anchorsByLabel,
27
+ assertCanonicalSignRequestPayload,
23
28
  assertServicesWithinCap,
29
+ buildSignRequest,
24
30
  classifyAnchor,
25
31
  deriveChainIdentifier,
26
32
  deriveContentId,
@@ -41,8 +47,9 @@ import {
41
47
  verifyCreditEntry,
42
48
  verifyIdentityChain,
43
49
  verifyIdentityExtensionFromTrustedState,
44
- verifyRevocation
45
- } from "./chunk-3USIENKC.js";
50
+ verifyRevocation,
51
+ verifySignRequest
52
+ } from "./chunk-Q376VG5Z.js";
46
53
  import {
47
54
  Attenuation,
48
55
  AuthTokenClaims,
@@ -62,7 +69,7 @@ import {
62
69
  verifyAuthToken,
63
70
  verifyDFOSCredential,
64
71
  verifyDelegationChain
65
- } from "./chunk-PGFHCBGH.js";
72
+ } from "./chunk-6HVYO5OS.js";
66
73
  import {
67
74
  JwsVerificationError,
68
75
  JwtVerificationError,
@@ -122,17 +129,23 @@ export {
122
129
  MAX_OPERATION_SIZE,
123
130
  MAX_SERVICES_ENTRIES,
124
131
  MAX_SERVICES_PAYLOAD_SIZE,
132
+ MAX_SIGN_REQUEST_PAYLOAD_SIZE,
133
+ MAX_SIGN_REQUEST_SIZE,
125
134
  MultikeyPublicKey,
126
135
  RECOGNIZED_SERVICE_TYPES,
127
136
  RevocationPayload,
128
137
  ServiceEntry,
129
138
  ServicesArray,
139
+ SignRequestPayload,
140
+ SignRequestVerifyError,
130
141
  VerifiedIdentity,
131
142
  anchorsByLabel,
143
+ assertCanonicalSignRequestPayload,
132
144
  assertJwsProfile,
133
145
  assertServicesWithinCap,
134
146
  base64urlDecode,
135
147
  base64urlEncode,
148
+ buildSignRequest,
136
149
  byteCompare,
137
150
  classifyAnchor,
138
151
  compareHeadPreference,
@@ -186,5 +199,6 @@ export {
186
199
  verifyIdentityExtensionFromTrustedState,
187
200
  verifyJws,
188
201
  verifyJwt,
189
- verifyRevocation
202
+ verifyRevocation,
203
+ verifySignRequest
190
204
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-protocol",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Protocol — Ed25519 signed chain primitives, services, credentials, and verification",
6
6
  "license": "MIT",
@@ -57,7 +57,9 @@
57
57
  "@ipld/dag-cbor": "^10.0.1",
58
58
  "@noble/curves": "^2.2.0",
59
59
  "@noble/hashes": "^2.2.0",
60
- "multiformats": "^14.0.0",
60
+ "multiformats": "^14.0.0"
61
+ },
62
+ "peerDependencies": {
61
63
  "zod": "^4.4.3"
62
64
  },
63
65
  "devDependencies": {
@@ -66,7 +68,8 @@
66
68
  "ajv-formats": "^3.0.1",
67
69
  "tsup": "^8.5.1",
68
70
  "tsx": "^4.22.4",
69
- "vitest": "^4.1.8"
71
+ "vitest": "^4.1.8",
72
+ "zod": "^4.4.3"
70
73
  },
71
74
  "scripts": {
72
75
  "build": "tsup",