@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 +4 -0
- package/dist/chain/index.js +3 -2
- package/dist/{chunk-SGPSXM56.js → chunk-4YBXPYEU.js} +4 -2
- package/dist/chunk-73YLK7GK.js +191 -0
- package/dist/{chunk-NXQW6EBF.js → chunk-D4IZXFPM.js} +3 -43
- package/dist/chunk-IDVYITX7.js +45 -0
- package/dist/credentials/index.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +24 -6
- package/dist/key-proof/index.d.ts +143 -0
- package/dist/key-proof/index.js +20 -0
- package/package.json +5 -1
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
|
|
package/dist/chain/index.js
CHANGED
|
@@ -49,13 +49,14 @@ import {
|
|
|
49
49
|
verifyIdentityExtensionFromTrustedState,
|
|
50
50
|
verifyRevocation,
|
|
51
51
|
verifySignRequest
|
|
52
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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"
|