@metalabel/dfos-protocol 0.39.0 → 0.41.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
@@ -22,6 +22,8 @@ import { verifyContentChain, verifyIdentityChain } from '@metalabel/dfos-protoco
22
22
  import { createDFOSCredential, signApiIdentityRequest } from '@metalabel/dfos-protocol/credentials';
23
23
  // Crypto primitives
24
24
  import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-protocol/crypto';
25
+ // KEY-PROOF — the challenge-bound proof that a candidate key is held
26
+ import { signKeyProof, verifyKeyProof } from '@metalabel/dfos-protocol/key-proof';
25
27
  ```
26
28
 
27
29
  ## Subpath Exports
@@ -31,6 +33,7 @@ import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-pr
31
33
  | `@metalabel/dfos-protocol/chain` | Identity & content chains, services, artifacts, countersigns, revocations, credit claims, sign requests |
32
34
  | `@metalabel/dfos-protocol/credentials` | DFOS credentials for authorization, and the API-AUTH request-proof / identity-proof envelopes |
33
35
  | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
36
+ | `@metalabel/dfos-protocol/key-proof` | KEY-PROOF envelopes — compose/sign and verify a challenge-bound proof that a candidate key is held |
34
37
  | `@metalabel/dfos-protocol/fold` | Canonical linearization and LWW-map folds for index documents |
35
38
 
36
39
  ## Specifications
@@ -45,6 +48,7 @@ import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-pr
45
48
  | [SIGNING.md](../../specs/SIGNING.md) | A transport-agnostic way for one party to ask another to produce a DFOS signature |
46
49
  | [SIWD.md](../../specs/SIWD.md) | Sign In With DFOS — cryptographic identity verification for third-party apps |
47
50
  | [API-AUTH.md](../../specs/API-AUTH.md) | Proof-of-possession authentication for credential-gated HTTP APIs |
51
+ | [KEY-PROOF.md](../../specs/KEY-PROOF.md) | Challenge-bound, single-shot proof that a candidate key is held |
48
52
 
49
53
  Release history lives at https://github.com/metalabel/dfos/releases.
50
54
 
@@ -49,13 +49,14 @@ import {
49
49
  verifyIdentityExtensionFromTrustedState,
50
50
  verifyRevocation,
51
51
  verifySignRequest
52
- } from "../chunk-SGPSXM56.js";
52
+ } from "../chunk-4YBXPYEU.js";
53
+ import "../chunk-D4IZXFPM.js";
53
54
  import {
54
55
  ED25519_PRIV_MULTICODEC,
55
56
  ED25519_PUB_MULTICODEC,
56
57
  decodeMultikey,
57
58
  encodeEd25519Multikey
58
- } from "../chunk-NXQW6EBF.js";
59
+ } from "../chunk-IDVYITX7.js";
59
60
  import "../chunk-4LG2GEB2.js";
60
61
  export {
61
62
  ARTIFACT_CID_ANCHOR_RE,
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  MAX_CREDENTIAL_SIZE,
3
3
  decodeDFOSCredentialUnsafe,
4
- decodeMultikey,
5
4
  matchesResource,
6
5
  verifyDFOSCredential,
7
6
  verifyDelegationChain
8
- } from "./chunk-NXQW6EBF.js";
7
+ } from "./chunk-D4IZXFPM.js";
8
+ import {
9
+ decodeMultikey
10
+ } from "./chunk-IDVYITX7.js";
9
11
  import {
10
12
  assertJwsProfile,
11
13
  base64urlDecode,
@@ -0,0 +1,191 @@
1
+ import {
2
+ ED25519_PUB_MULTICODEC,
3
+ decodeMultikey,
4
+ encodeEd25519Multikey
5
+ } from "./chunk-IDVYITX7.js";
6
+ import {
7
+ base64urlDecode,
8
+ base64urlEncode,
9
+ importEd25519Keypair,
10
+ isValidEd25519Signature,
11
+ signPayloadEd25519
12
+ } from "./chunk-4LG2GEB2.js";
13
+
14
+ // src/key-proof/key-proof.ts
15
+ var KEY_ADD_JWS_TYP = "did:dfos:key-add";
16
+ var MAX_KEY_PROOF_SIZE = 4096;
17
+ var DEFAULT_KEY_PROOF_SKEW_SECONDS = 300;
18
+ var KeyProofVerifyError = class extends Error {
19
+ reason;
20
+ constructor(reason, message) {
21
+ super(message);
22
+ this.name = "KeyProofVerifyError";
23
+ this.reason = reason;
24
+ }
25
+ };
26
+ var invalid = (reason, message) => new KeyProofVerifyError(reason, `invalid key proof: ${message}`);
27
+ var encoder = new TextEncoder();
28
+ var KEY_PROOF_MEMBERS = ["nonce", "audience", "publicKeyMultibase", "timestamp"];
29
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
30
+ var WHOLE_SECOND_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000Z$/;
31
+ var PROTOCOL_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
32
+ var validateKeyProofPayload = (value) => {
33
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
34
+ throw invalid("schema", "expected a JSON object");
35
+ }
36
+ const raw = value;
37
+ for (const member of Object.keys(raw)) {
38
+ if (!KEY_PROOF_MEMBERS.includes(member)) {
39
+ throw invalid("schema", `unknown member ${JSON.stringify(member)} \u2014 the payload is closed`);
40
+ }
41
+ }
42
+ for (const member of KEY_PROOF_MEMBERS) {
43
+ if (typeof raw[member] !== "string" || raw[member] === "") {
44
+ throw invalid("schema", `${member} must be a non-empty string`);
45
+ }
46
+ if (LONE_SURROGATE.test(raw[member])) {
47
+ throw invalid("schema", `${member} must be well-formed Unicode`);
48
+ }
49
+ }
50
+ const nonce = raw["nonce"];
51
+ const audience = raw["audience"];
52
+ const publicKeyMultibase = raw["publicKeyMultibase"];
53
+ const timestamp = raw["timestamp"];
54
+ if (audience !== audience.toLowerCase() || /[\s/\\?#]/.test(audience)) {
55
+ throw invalid("schema", "audience must be a lowercase authority, without a scheme or path");
56
+ }
57
+ if (!WHOLE_SECOND_TIMESTAMP.test(timestamp) || !Number.isFinite(Date.parse(timestamp))) {
58
+ throw invalid("schema", "timestamp must be ISO-8601 UTC whole-second .000Z");
59
+ }
60
+ return { nonce, audience, publicKeyMultibase, timestamp };
61
+ };
62
+ var keyProofSigningInput = (payload) => encoder.encode(JSON.stringify(keyProofPayloadObject(validateKeyProofPayload(payload))));
63
+ var keyProofPayloadObject = (payload) => ({
64
+ nonce: payload.nonce,
65
+ audience: payload.audience,
66
+ publicKeyMultibase: payload.publicKeyMultibase,
67
+ timestamp: payload.timestamp
68
+ });
69
+ var normalizeTimestamp = (ms) => new Date(Math.floor(ms / 1e3) * 1e3).toISOString();
70
+ var signKeyProof = async (input) => {
71
+ if (input.typ === "") {
72
+ throw new Error("invalid key proof: typ must be a registered purpose value");
73
+ }
74
+ const { publicKey } = importEd25519Keypair(input.privateKey);
75
+ let timestamp;
76
+ if (input.timestamp === void 0) {
77
+ timestamp = normalizeTimestamp(input.now ? input.now() : Date.now());
78
+ } else {
79
+ const ms = Date.parse(input.timestamp);
80
+ if (!PROTOCOL_TIMESTAMP.test(input.timestamp) || !Number.isFinite(ms) || new Date(ms).toISOString() !== input.timestamp) {
81
+ throw new Error(`invalid key proof: unparseable timestamp: ${input.timestamp}`);
82
+ }
83
+ timestamp = normalizeTimestamp(ms);
84
+ }
85
+ const payload = validateKeyProofPayload({
86
+ nonce: input.nonce,
87
+ audience: input.audience,
88
+ publicKeyMultibase: encodeEd25519Multikey(publicKey),
89
+ timestamp
90
+ });
91
+ const headerB64 = base64urlEncode(JSON.stringify({ alg: "EdDSA", typ: input.typ }));
92
+ const payloadB64 = base64urlEncode(keyProofSigningInput(payload));
93
+ const signingInput = `${headerB64}.${payloadB64}`;
94
+ const signature = signPayloadEd25519(encoder.encode(signingInput), input.privateKey);
95
+ const proof = `${signingInput}.${base64urlEncode(signature)}`;
96
+ if (proof.length > MAX_KEY_PROOF_SIZE) {
97
+ throw new Error(`key proof exceeds max size: ${proof.length} > ${MAX_KEY_PROOF_SIZE}`);
98
+ }
99
+ return { proof, payload };
100
+ };
101
+ var verifyKeyProof = (jws, options) => {
102
+ const maxSkew = options.maxSkewSeconds ?? DEFAULT_KEY_PROOF_SKEW_SECONDS;
103
+ if (!Number.isSafeInteger(maxSkew) || maxSkew < 0) {
104
+ throw new Error("invalid key proof verifier: maxSkewSeconds must be a non-negative integer");
105
+ }
106
+ if (jws.length > MAX_KEY_PROOF_SIZE) {
107
+ throw invalid("size", `envelope exceeds max size: ${jws.length} > ${MAX_KEY_PROOF_SIZE}`);
108
+ }
109
+ const parts = jws.split(".");
110
+ if (parts.length !== 3) throw invalid("header", "failed to decode JWS");
111
+ const [headerB64, payloadB64] = parts;
112
+ let header;
113
+ try {
114
+ const decoded = JSON.parse(
115
+ new TextDecoder("utf-8", { fatal: true }).decode(base64urlDecode(headerB64))
116
+ );
117
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
118
+ throw new Error("protected header must be an object");
119
+ }
120
+ header = decoded;
121
+ } catch (err) {
122
+ throw invalid("header", err instanceof Error ? err.message : "failed to decode header");
123
+ }
124
+ if (header["alg"] !== "EdDSA") {
125
+ throw invalid("header", `unsupported algorithm: ${String(header["alg"])}`);
126
+ }
127
+ if ("crit" in header) throw invalid("header", "crit header is not supported");
128
+ for (const member of ["jwk", "jku", "x5c", "x5u"]) {
129
+ if (member in header) {
130
+ throw invalid("header", `${member} header is not allowed (the key rides in the payload)`);
131
+ }
132
+ }
133
+ if ("kid" in header) {
134
+ throw invalid("header", "kid must be absent \u2014 the candidate key is in no chain");
135
+ }
136
+ const typ = header["typ"];
137
+ if (typeof typ !== "string" || typ !== options.expectedTyp) {
138
+ throw invalid("header", `invalid typ: expected ${options.expectedTyp}, got ${String(typ)}`);
139
+ }
140
+ let payload;
141
+ try {
142
+ const source = new TextDecoder("utf-8", { fatal: true }).decode(base64urlDecode(payloadB64));
143
+ payload = validateKeyProofPayload(JSON.parse(source));
144
+ } catch (err) {
145
+ if (err instanceof KeyProofVerifyError) throw err;
146
+ throw invalid("schema", err instanceof Error ? err.message : "payload is not valid UTF-8 JSON");
147
+ }
148
+ if (payload.audience !== options.expectedAudience) {
149
+ throw invalid("audience", "audience does not match this verifier authority");
150
+ }
151
+ const now = Math.floor((options.now ? options.now() : Date.now()) / 1e3);
152
+ const issued = Math.floor(Date.parse(payload.timestamp) / 1e3);
153
+ if (Math.abs(now - issued) > maxSkew) {
154
+ throw invalid("freshness", "timestamp is outside the acceptance window");
155
+ }
156
+ let keyBytes;
157
+ try {
158
+ const decoded = decodeMultikey(payload.publicKeyMultibase);
159
+ if (decoded.codec !== ED25519_PUB_MULTICODEC) {
160
+ throw new Error("publicKeyMultibase is not an Ed25519 public key");
161
+ }
162
+ keyBytes = decoded.keyBytes;
163
+ } catch (err) {
164
+ throw invalid(
165
+ "signature",
166
+ err instanceof Error ? err.message : "undecodable publicKeyMultibase"
167
+ );
168
+ }
169
+ let verified = false;
170
+ try {
171
+ verified = isValidEd25519Signature(
172
+ encoder.encode(`${headerB64}.${payloadB64}`),
173
+ base64urlDecode(parts[2]),
174
+ keyBytes
175
+ );
176
+ } catch {
177
+ verified = false;
178
+ }
179
+ if (!verified) throw invalid("signature", "signature does not verify against publicKeyMultibase");
180
+ return { payload, typ, now };
181
+ };
182
+
183
+ export {
184
+ KEY_ADD_JWS_TYP,
185
+ MAX_KEY_PROOF_SIZE,
186
+ DEFAULT_KEY_PROOF_SKEW_SECONDS,
187
+ KeyProofVerifyError,
188
+ keyProofSigningInput,
189
+ signKeyProof,
190
+ verifyKeyProof
191
+ };
@@ -1,3 +1,6 @@
1
+ import {
2
+ decodeMultikey
3
+ } from "./chunk-IDVYITX7.js";
1
4
  import {
2
5
  assertJwsProfile,
3
6
  base64urlDecode,
@@ -35,45 +38,6 @@ var DFOSCredentialPayload = z.looseObject({
35
38
  iat: z.number().int().positive()
36
39
  });
37
40
 
38
- // src/chain/multikey.ts
39
- import { base58btc } from "multiformats/bases/base58";
40
- var ED25519_PUB_PREFIX = new Uint8Array([237, 1]);
41
- var ED25519_PRIV_PREFIX = new Uint8Array([128, 38]);
42
- var ED25519_PUB_MULTICODEC = 237;
43
- var ED25519_PRIV_MULTICODEC = 4864;
44
- var encodeEd25519Multikey = (publicKeyBytes) => {
45
- if (publicKeyBytes.length !== 32) {
46
- throw new Error(`expected 32-byte Ed25519 public key, got ${publicKeyBytes.length}`);
47
- }
48
- const prefixed = new Uint8Array(ED25519_PUB_PREFIX.length + publicKeyBytes.length);
49
- prefixed.set(ED25519_PUB_PREFIX);
50
- prefixed.set(publicKeyBytes, ED25519_PUB_PREFIX.length);
51
- return base58btc.encode(prefixed);
52
- };
53
- var decodeMultikey = (multibase) => {
54
- const bytes = base58btc.decode(multibase);
55
- if (bytes.length < 2) {
56
- throw new Error("multikey too short");
57
- }
58
- if (bytes[0] === ED25519_PUB_PREFIX[0] && bytes[1] === ED25519_PUB_PREFIX[1]) {
59
- const keyBytes = bytes.slice(2);
60
- if (keyBytes.length !== 32) {
61
- throw new Error(`expected 32-byte Ed25519 public key, got ${keyBytes.length}`);
62
- }
63
- return { keyBytes, codec: ED25519_PUB_MULTICODEC };
64
- }
65
- if (bytes[0] === ED25519_PRIV_PREFIX[0] && bytes[1] === ED25519_PRIV_PREFIX[1]) {
66
- const keyBytes = bytes.slice(2);
67
- if (keyBytes.length !== 32) {
68
- throw new Error(`expected 32-byte Ed25519 private key, got ${keyBytes.length}`);
69
- }
70
- return { keyBytes, codec: ED25519_PRIV_MULTICODEC };
71
- }
72
- throw new Error(
73
- `unsupported multikey codec: [0x${bytes[0]?.toString(16)}, 0x${bytes[1]?.toString(16)}]`
74
- );
75
- };
76
-
77
41
  // src/credentials/api-auth.ts
78
42
  var REQUEST_PROOF_JWS_TYP = "did:dfos:request-proof";
79
43
  var IDENTITY_PROOF_JWS_TYP = "did:dfos:identity-proof";
@@ -630,10 +594,6 @@ var CredentialVerificationError = class extends Error {
630
594
  };
631
595
 
632
596
  export {
633
- ED25519_PUB_MULTICODEC,
634
- ED25519_PRIV_MULTICODEC,
635
- encodeEd25519Multikey,
636
- decodeMultikey,
637
597
  MAX_CREDENTIAL_SIZE,
638
598
  Attenuation,
639
599
  DFOSCredentialPayload,
@@ -0,0 +1,45 @@
1
+ // src/chain/multikey.ts
2
+ import { base58btc } from "multiformats/bases/base58";
3
+ var ED25519_PUB_PREFIX = new Uint8Array([237, 1]);
4
+ var ED25519_PRIV_PREFIX = new Uint8Array([128, 38]);
5
+ var ED25519_PUB_MULTICODEC = 237;
6
+ var ED25519_PRIV_MULTICODEC = 4864;
7
+ var encodeEd25519Multikey = (publicKeyBytes) => {
8
+ if (publicKeyBytes.length !== 32) {
9
+ throw new Error(`expected 32-byte Ed25519 public key, got ${publicKeyBytes.length}`);
10
+ }
11
+ const prefixed = new Uint8Array(ED25519_PUB_PREFIX.length + publicKeyBytes.length);
12
+ prefixed.set(ED25519_PUB_PREFIX);
13
+ prefixed.set(publicKeyBytes, ED25519_PUB_PREFIX.length);
14
+ return base58btc.encode(prefixed);
15
+ };
16
+ var decodeMultikey = (multibase) => {
17
+ const bytes = base58btc.decode(multibase);
18
+ if (bytes.length < 2) {
19
+ throw new Error("multikey too short");
20
+ }
21
+ if (bytes[0] === ED25519_PUB_PREFIX[0] && bytes[1] === ED25519_PUB_PREFIX[1]) {
22
+ const keyBytes = bytes.slice(2);
23
+ if (keyBytes.length !== 32) {
24
+ throw new Error(`expected 32-byte Ed25519 public key, got ${keyBytes.length}`);
25
+ }
26
+ return { keyBytes, codec: ED25519_PUB_MULTICODEC };
27
+ }
28
+ if (bytes[0] === ED25519_PRIV_PREFIX[0] && bytes[1] === ED25519_PRIV_PREFIX[1]) {
29
+ const keyBytes = bytes.slice(2);
30
+ if (keyBytes.length !== 32) {
31
+ throw new Error(`expected 32-byte Ed25519 private key, got ${keyBytes.length}`);
32
+ }
33
+ return { keyBytes, codec: ED25519_PRIV_MULTICODEC };
34
+ }
35
+ throw new Error(
36
+ `unsupported multikey codec: [0x${bytes[0]?.toString(16)}, 0x${bytes[1]?.toString(16)}]`
37
+ );
38
+ };
39
+
40
+ export {
41
+ ED25519_PUB_MULTICODEC,
42
+ ED25519_PRIV_MULTICODEC,
43
+ encodeEd25519Multikey,
44
+ decodeMultikey
45
+ };
@@ -34,7 +34,8 @@ import {
34
34
  verifyDelegationChain,
35
35
  verifyIdentityProofEnvelope,
36
36
  verifyRequestProofEnvelope
37
- } from "../chunk-NXQW6EBF.js";
37
+ } from "../chunk-D4IZXFPM.js";
38
+ import "../chunk-IDVYITX7.js";
38
39
  import "../chunk-4LG2GEB2.js";
39
40
  export {
40
41
  ApiRequestVerifyError,
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader
2
2
  export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, I as IdentityOperation, g as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, h as MAX_CREDENTIAL_SIZE, i as MAX_CREDIT_CLAIM_SIZE, j as MAX_OPERATION_SIZE, k as MAX_SERVICES_ENTRIES, l as MAX_SERVICES_PAYLOAD_SIZE, m as MAX_SIGN_REQUEST_PAYLOAD_SIZE, n as MAX_SIGN_REQUEST_SIZE, o as MultikeyPublicKey, R as RevocationChecker, p as RevocationPayload, S as ServiceEntry, q as ServicesArray, r as SignRequestPayload, s as Signer, V as VerifiedDFOSCredential, t as VerifiedDelegationChain, u as VerifiedIdentity, v as createDFOSCredential, w as decodeDFOSCredentialUnsafe, x as isAttenuated, y as matchesResource, z as parseProtocolTimestampUnix, B as verifyDFOSCredential, E as verifyDelegationChain } from './dfos-credential-X6uvPIth.js';
3
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 { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ParsedProofPayload, ProofEnvelopeInput, ProofExtraMembers, ProofPresenterState, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, ResolveProofPresenter, SignApiIdentityRequestInput, SignApiRequestInput, VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, invalidProof, misconfiguredProof, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope } from './credentials/index.js';
5
+ export { DEFAULT_KEY_PROOF_SKEW_SECONDS, KEY_ADD_JWS_TYP, KeyProofFailureReason, KeyProofPayload, KeyProofVerifyError, MAX_KEY_PROOF_SIZE, SignKeyProofInput, VerifiedKeyProof, VerifyKeyProofOptions, keyProofSigningInput, signKeyProof, verifyKeyProof } from './key-proof/index.js';
5
6
  export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
6
7
  import 'multiformats';
7
8
  import 'multiformats/cid';
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  verifyIdentityExtensionFromTrustedState,
50
50
  verifyRevocation,
51
51
  verifySignRequest
52
- } from "./chunk-SGPSXM56.js";
52
+ } from "./chunk-4YBXPYEU.js";
53
53
  import {
54
54
  ApiRequestVerifyError,
55
55
  Attenuation,
@@ -58,8 +58,6 @@ import {
58
58
  DEFAULT_PROOF_WINDOW_SECONDS,
59
59
  DFOSCredentialPayload,
60
60
  DFOS_AUTH_SCHEME,
61
- ED25519_PRIV_MULTICODEC,
62
- ED25519_PUB_MULTICODEC,
63
61
  EMPTY_BODY_SHA256,
64
62
  IDENTITY_PROOF_JWS_TYP,
65
63
  MAX_BODY_BYTES,
@@ -75,8 +73,6 @@ import {
75
73
  canonicalExtraMembers,
76
74
  createDFOSCredential,
77
75
  decodeDFOSCredentialUnsafe,
78
- decodeMultikey,
79
- encodeEd25519Multikey,
80
76
  invalidProof,
81
77
  isAttenuated,
82
78
  matchesResource,
@@ -90,7 +86,22 @@ import {
90
86
  verifyDelegationChain,
91
87
  verifyIdentityProofEnvelope,
92
88
  verifyRequestProofEnvelope
93
- } from "./chunk-NXQW6EBF.js";
89
+ } from "./chunk-D4IZXFPM.js";
90
+ import {
91
+ DEFAULT_KEY_PROOF_SKEW_SECONDS,
92
+ KEY_ADD_JWS_TYP,
93
+ KeyProofVerifyError,
94
+ MAX_KEY_PROOF_SIZE,
95
+ keyProofSigningInput,
96
+ signKeyProof,
97
+ verifyKeyProof
98
+ } from "./chunk-73YLK7GK.js";
99
+ import {
100
+ ED25519_PRIV_MULTICODEC,
101
+ ED25519_PUB_MULTICODEC,
102
+ decodeMultikey,
103
+ encodeEd25519Multikey
104
+ } from "./chunk-IDVYITX7.js";
94
105
  import {
95
106
  JwsVerificationError,
96
107
  JwtVerificationError,
@@ -136,6 +147,7 @@ export {
136
147
  CredentialVerificationError,
137
148
  CreditClaimPayload,
138
149
  CreditClaimVerifyError,
150
+ DEFAULT_KEY_PROOF_SKEW_SECONDS,
139
151
  DEFAULT_PROOF_SKEW_SECONDS,
140
152
  DEFAULT_PROOF_WINDOW_SECONDS,
141
153
  DFOSCredentialPayload,
@@ -149,10 +161,13 @@ export {
149
161
  Iso8601,
150
162
  JwsVerificationError,
151
163
  JwtVerificationError,
164
+ KEY_ADD_JWS_TYP,
165
+ KeyProofVerifyError,
152
166
  MAX_ARTIFACT_PAYLOAD_SIZE,
153
167
  MAX_BODY_BYTES,
154
168
  MAX_CREDENTIAL_SIZE,
155
169
  MAX_CREDIT_CLAIM_SIZE,
170
+ MAX_KEY_PROOF_SIZE,
156
171
  MAX_OPERATION_SIZE,
157
172
  MAX_PROOF_FRESHNESS_SPAN_SECONDS,
158
173
  MAX_REQUEST_PROOF_SIZE,
@@ -209,6 +224,7 @@ export {
209
224
  isRecognizedServiceType,
210
225
  isValidEd25519Signature,
211
226
  isValidId,
227
+ keyProofSigningInput,
212
228
  linearize,
213
229
  matchesResource,
214
230
  misconfiguredProof,
@@ -226,6 +242,7 @@ export {
226
242
  signCountersignature,
227
243
  signCreditClaim,
228
244
  signIdentityOperation,
245
+ signKeyProof,
229
246
  signPayloadEd25519,
230
247
  signRevocation,
231
248
  unverifiableProof,
@@ -242,6 +259,7 @@ export {
242
259
  verifyIdentityProofEnvelope,
243
260
  verifyJws,
244
261
  verifyJwt,
262
+ verifyKeyProof,
245
263
  verifyRequestProofEnvelope,
246
264
  verifyRevocation,
247
265
  verifySignRequest
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The first registered purpose in KEY-PROOF.md's purpose registry: the candidate
3
+ * key presents for addition to a ceremony-named identity's `authKeys`/
4
+ * `assertKeys` sets.
5
+ *
6
+ * The `typ` is a PARAMETER everywhere in this module, not a constant baked into
7
+ * the algorithm — the grammar and the verification steps are identical for every
8
+ * registered row, and a new purpose lands by registering a value, never by
9
+ * minting an envelope. This constant is the one row that exists.
10
+ */
11
+ declare const KEY_ADD_JWS_TYP = "did:dfos:key-add";
12
+ /** Size cap on the serialized envelope, checked BEFORE any decode. */
13
+ declare const MAX_KEY_PROOF_SIZE = 4096;
14
+ /**
15
+ * RECOMMENDED acceptance window, in seconds, EITHER SIDE of the verifier's clock
16
+ * — matching a ceremony's own lifetime (KEY-PROOF.md, Verification step 5).
17
+ */
18
+ declare const DEFAULT_KEY_PROOF_SKEW_SECONDS = 300;
19
+ /**
20
+ * The closed payload. Exactly these four members, each a string, in exactly this
21
+ * order — the member set is EXHAUSTIVE and no amendment may introduce a member
22
+ * that carries intent or content.
23
+ */
24
+ interface KeyProofPayload {
25
+ /** The verifier-minted, single-use challenge, exactly as the carriage delivered it. */
26
+ nonce: string;
27
+ /** The completion endpoint's lowercase authority — `host`, or `host:port` off 443. */
28
+ audience: string;
29
+ /** The candidate key's Multikey — and the key that signs this envelope. */
30
+ publicKeyMultibase: string;
31
+ /** ISO 8601 creation time, floor-normalized to whole seconds (`.000Z`). */
32
+ timestamp: string;
33
+ }
34
+ /** The verification step a failure arose in. Branch on this, never on message text. */
35
+ type KeyProofFailureReason = 'size' | 'header' | 'schema' | 'audience' | 'freshness' | 'signature';
36
+ /** Thrown by `verifyKeyProof`. Branch on `reason`, never on message text. */
37
+ declare class KeyProofVerifyError extends Error {
38
+ readonly reason: KeyProofFailureReason;
39
+ constructor(reason: KeyProofFailureReason, message: string);
40
+ }
41
+ /**
42
+ * THE BYTE CONTRACT. Serializes a payload to the canonical bytes that ARE the
43
+ * JWS payload segment: minimal UTF-8 JSON, no insignificant whitespace, members
44
+ * in exactly the order `nonce, audience, publicKeyMultibase, timestamp`.
45
+ *
46
+ * PURE and clientless: import it in a holder's signing tool and in a ceremony
47
+ * operator's verifier alike. Byte-for-byte identical to Go's
48
+ * `KeyProofSigningInput`.
49
+ */
50
+ declare const keyProofSigningInput: (payload: KeyProofPayload) => Uint8Array;
51
+ interface SignKeyProofInput {
52
+ /** The registered purpose this proof is scoped to — e.g. `KEY_ADD_JWS_TYP`. */
53
+ typ: string;
54
+ /** The verifier-minted nonce, exactly as the carriage delivered it. */
55
+ nonce: string;
56
+ /** The completion endpoint's lowercase authority — the one the human confirmed. */
57
+ audience: string;
58
+ /**
59
+ * The candidate key's raw 32-byte Ed25519 private key. `publicKeyMultibase` is
60
+ * DERIVED from it rather than accepted as an input: this envelope is
61
+ * self-proving, and a signer that could name a key it does not hold would be
62
+ * the one construction the artifact exists to foreclose.
63
+ */
64
+ privateKey: Uint8Array;
65
+ /** Timestamp override; floor-normalized to `.000Z` if it carries milliseconds. */
66
+ timestamp?: string;
67
+ /** Clock injection (unix ms) for the default timestamp. Default `Date.now()`. */
68
+ now?: () => number;
69
+ }
70
+ /**
71
+ * Sign one key proof. The producer half of the byte contract.
72
+ *
73
+ * The protected header is EXACTLY `{"alg":"EdDSA","typ":"<purpose>"}` — two
74
+ * members, no `kid` (the key is in no chain and rides in the payload) and no
75
+ * `cid` (there is no operation to bind). It is assembled by hand rather than
76
+ * through `createJws`, whose `JwsHeader` requires a `kid` this envelope must not
77
+ * carry.
78
+ *
79
+ * HOLDER OBLIGATIONS THIS FUNCTION CANNOT DISCHARGE (KEY-PROOF.md, Holder
80
+ * Obligations). A holder MUST show its human the audience and the purpose before
81
+ * calling this, and SHOULD refuse to sign for a key any identity's chain has
82
+ * ever declared — the `key=` reverse index is has-ever-declared, and one key in
83
+ * two chains publishes an irreversible public link between them. Both are
84
+ * decisions about a human and a network, made before there is a signature to
85
+ * make; neither belongs to a pure signer.
86
+ */
87
+ declare const signKeyProof: (input: SignKeyProofInput) => Promise<{
88
+ proof: string;
89
+ payload: KeyProofPayload;
90
+ }>;
91
+ interface VerifyKeyProofOptions {
92
+ /**
93
+ * The registered `typ` THIS ceremony requires. The gate is absolute: it is what
94
+ * keeps a proof signed for one ceremony from ever being presented for another.
95
+ */
96
+ expectedTyp: string;
97
+ /**
98
+ * THE VERIFIER'S OWN CONFIGURED AUTHORITY — a value the deployment holds, NEVER
99
+ * one read from the request. `Host`, `X-Forwarded-Host`, and the request URL's
100
+ * authority are all attacker-supplied; a verifier that compared against one of
101
+ * them would have no audience binding at all, and audience binding is the whole
102
+ * defense against challenge relay.
103
+ */
104
+ expectedAudience: string;
105
+ /** Acceptance window, seconds, EITHER SIDE. Default `DEFAULT_KEY_PROOF_SKEW_SECONDS`. */
106
+ maxSkewSeconds?: number;
107
+ /** Clock injection (unix ms). Default `Date.now()`. */
108
+ now?: () => number;
109
+ }
110
+ /** What a verified key proof hands back. */
111
+ interface VerifiedKeyProof {
112
+ /**
113
+ * The validated payload. THE CALLER MUST NOW RUN STEP 6 against `payload.nonce`:
114
+ * check that it is a nonce this verifier minted, for this ceremony, not yet
115
+ * consumed, and consume it ATOMICALLY (check-and-delete) so two racing
116
+ * completions cannot both pass.
117
+ */
118
+ payload: KeyProofPayload;
119
+ /** The header `typ` — equal to `expectedTyp`, since anything else rejected. */
120
+ typ: string;
121
+ /** The integer unix seconds the freshness check used. */
122
+ now: number;
123
+ }
124
+ /**
125
+ * Verify a key proof — KEY-PROOF.md's verification algorithm steps 1–5 and 7:
126
+ * size cap, header gates, closed payload schema, audience byte-equality,
127
+ * freshness, and the signature against the payload's OWN `publicKeyMultibase`.
128
+ *
129
+ * STEP 6 (NONCE) IS THE CALLER'S, and this function cannot stand in for it. The
130
+ * nonce MUST be one this verifier minted, for this ceremony, not yet consumed,
131
+ * checked and consumed ATOMICALLY — a check-and-delete against the verifier's
132
+ * own store, which is state this pure function does not hold. It is returned on
133
+ * `payload.nonce` precisely so the caller can run that step next. Without it a
134
+ * proof is replayable for the length of the freshness window.
135
+ *
136
+ * What the seven steps together establish is exactly one fact: THE NAMED KEY WAS
137
+ * HELD, AND CONSENTED TO THIS CEREMONY AT THIS VERIFIER, INSIDE THIS WINDOW.
138
+ * Everything after — appending the key to a chain, custody policy, notification
139
+ * — is the ceremony operator's.
140
+ */
141
+ declare const verifyKeyProof: (jws: string, options: VerifyKeyProofOptions) => VerifiedKeyProof;
142
+
143
+ export { DEFAULT_KEY_PROOF_SKEW_SECONDS, KEY_ADD_JWS_TYP, type KeyProofFailureReason, type KeyProofPayload, KeyProofVerifyError, MAX_KEY_PROOF_SIZE, type SignKeyProofInput, type VerifiedKeyProof, type VerifyKeyProofOptions, keyProofSigningInput, signKeyProof, verifyKeyProof };
@@ -0,0 +1,20 @@
1
+ import {
2
+ DEFAULT_KEY_PROOF_SKEW_SECONDS,
3
+ KEY_ADD_JWS_TYP,
4
+ KeyProofVerifyError,
5
+ MAX_KEY_PROOF_SIZE,
6
+ keyProofSigningInput,
7
+ signKeyProof,
8
+ verifyKeyProof
9
+ } from "../chunk-73YLK7GK.js";
10
+ import "../chunk-IDVYITX7.js";
11
+ import "../chunk-4LG2GEB2.js";
12
+ export {
13
+ DEFAULT_KEY_PROOF_SKEW_SECONDS,
14
+ KEY_ADD_JWS_TYP,
15
+ KeyProofVerifyError,
16
+ MAX_KEY_PROOF_SIZE,
17
+ keyProofSigningInput,
18
+ signKeyProof,
19
+ verifyKeyProof
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-protocol",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Protocol — Ed25519 signed chain primitives, services, credentials, and verification",
6
6
  "license": "MIT",
@@ -41,6 +41,10 @@
41
41
  "import": "./dist/credentials/index.js",
42
42
  "types": "./dist/credentials/index.d.ts"
43
43
  },
44
+ "./key-proof": {
45
+ "import": "./dist/key-proof/index.js",
46
+ "types": "./dist/key-proof/index.d.ts"
47
+ },
44
48
  "./fold": {
45
49
  "import": "./dist/fold/index.js",
46
50
  "types": "./dist/fold/index.d.ts"