@coordination-network/credential 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -45
- package/dist/credential-message.d.ts +5 -1
- package/dist/credential-message.js +17 -1
- package/dist/crypto.d.ts +3 -11
- package/dist/crypto.js +1 -31
- package/dist/errors.d.ts +1 -1
- package/dist/holder-ownership.d.ts +12 -0
- package/dist/holder-ownership.js +26 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1 -5
- package/dist/issue-credential.d.ts +5 -1
- package/dist/issue-credential.js +24 -2
- package/dist/revocation.d.ts +6 -5
- package/dist/revocation.js +9 -12
- package/dist/signer.d.ts +2 -13
- package/dist/types.d.ts +2 -38
- package/dist/zk-proof.d.ts +10 -0
- package/dist/zk-proof.js +14 -3
- package/package.json +6 -9
- package/src/credential-message.ts +22 -2
- package/src/crypto.ts +3 -34
- package/src/errors.ts +6 -1
- package/src/holder-ownership.ts +30 -0
- package/src/index.ts +1 -5
- package/src/issue-credential.ts +43 -6
- package/src/revocation.ts +10 -14
- package/src/signer.ts +2 -14
- package/src/types.ts +2 -36
- package/src/zk-proof.ts +19 -3
- package/dist/decode.d.ts +0 -8
- package/dist/decode.js +0 -27
- package/dist/issue.d.ts +0 -9
- package/dist/issue.js +0 -65
- package/dist/present.d.ts +0 -32
- package/dist/present.js +0 -73
- package/dist/profile.d.ts +0 -32
- package/dist/profile.js +0 -122
- package/dist/verify.d.ts +0 -29
- package/dist/verify.js +0 -102
- package/src/decode.ts +0 -25
- package/src/issue.ts +0 -83
- package/src/present.ts +0 -101
- package/src/profile.ts +0 -133
- package/src/verify.ts +0 -118
package/src/verify.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"
|
|
2
|
-
import { base64urlToBytes, createSdJwtVerifier, sdJwtHasher } from "./crypto"
|
|
3
|
-
import { urnToNetworkId, validateClaims } from "./profile"
|
|
4
|
-
import { Resolver } from "./resolver"
|
|
5
|
-
import { ApprovalCredentialClaims, Hex32, Jwk, VerifyErrorCode, VerifyResult } from "./types"
|
|
6
|
-
|
|
7
|
-
export interface VerifyOptions {
|
|
8
|
-
/** Verification time, unix seconds. Defaults to now. */
|
|
9
|
-
currentDate?: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const fail = (code: VerifyErrorCode, message: string): VerifyResult => ({ ok: false, code, message })
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The shared verification pipeline (spec 07, verification flow), check order
|
|
16
|
-
* first-failure-wins: decode/structure -> issuer approved -> key fetch ->
|
|
17
|
-
* signature and disclosure digests -> claims shape -> validity window ->
|
|
18
|
-
* revocation. A disclosure whose digest matches nothing is silently dropped
|
|
19
|
-
* by the library (phase 1 spike), so it surfaces in the claims-shape step as
|
|
20
|
-
* a missing approvalScope member and fails MALFORMED.
|
|
21
|
-
*
|
|
22
|
-
* The library's own nbf/exp check runs against a clamped timestamp so the
|
|
23
|
-
* window verdict (EXPIRED vs NOT_YET_VALID) always comes from our check,
|
|
24
|
-
* after the signature has been verified.
|
|
25
|
-
*/
|
|
26
|
-
async function runPipeline(
|
|
27
|
-
token: string,
|
|
28
|
-
resolver: Resolver,
|
|
29
|
-
now: number,
|
|
30
|
-
verifyWithLibrary: (issuerJwk: Jwk, clampedNow: number) => Promise<Record<string, unknown>>
|
|
31
|
-
): Promise<{ result: VerifyResult; issuerId?: Hex32 }> {
|
|
32
|
-
let payload: Record<string, unknown>
|
|
33
|
-
try {
|
|
34
|
-
const jwt = token.split("~")[0]
|
|
35
|
-
const segments = jwt.split(".")
|
|
36
|
-
if (segments.length !== 3) {
|
|
37
|
-
return { result: fail("MALFORMED", "not a compact SD-JWT") }
|
|
38
|
-
}
|
|
39
|
-
payload = JSON.parse(new TextDecoder().decode(base64urlToBytes(segments[1])))
|
|
40
|
-
if (typeof payload !== "object" || payload === null) {
|
|
41
|
-
return { result: fail("MALFORMED", "JWS payload is not an object") }
|
|
42
|
-
}
|
|
43
|
-
} catch {
|
|
44
|
-
return { result: fail("MALFORMED", "token could not be decoded") }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const issuerId = typeof payload.iss === "string" ? urnToNetworkId(payload.iss) : null
|
|
48
|
-
if (issuerId === null) {
|
|
49
|
-
return { result: fail("MALFORMED", "iss missing or not a urn:cn:network URN") }
|
|
50
|
-
}
|
|
51
|
-
if (!(await resolver.isApprovedNetworkMember(issuerId))) {
|
|
52
|
-
return {
|
|
53
|
-
result: fail("ISSUER_NOT_APPROVED", `issuer ${payload.iss} is not an approved network member`),
|
|
54
|
-
issuerId
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
const issuerJwk = await resolver.getCredentialPublicKey(issuerId)
|
|
58
|
-
if (issuerJwk === null) {
|
|
59
|
-
return { result: fail("KEY_NOT_FOUND", `no credential public key published for ${payload.iss}`), issuerId }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Clamp the library's clock inside the token's window; our own check below decides the verdict.
|
|
63
|
-
let clampedNow = now
|
|
64
|
-
if (typeof payload.exp === "number" && clampedNow > payload.exp) clampedNow = payload.exp
|
|
65
|
-
if (typeof payload.nbf === "number" && clampedNow < payload.nbf) clampedNow = payload.nbf
|
|
66
|
-
|
|
67
|
-
let reconstructed: Record<string, unknown>
|
|
68
|
-
try {
|
|
69
|
-
reconstructed = await verifyWithLibrary(issuerJwk, clampedNow)
|
|
70
|
-
} catch (error) {
|
|
71
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
72
|
-
if (message.includes("not yet valid")) return { result: fail("NOT_YET_VALID", message), issuerId }
|
|
73
|
-
if (message.includes("expired")) return { result: fail("EXPIRED", message), issuerId }
|
|
74
|
-
return { result: fail("PROOF_INVALID", message), issuerId }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const checked = validateClaims(reconstructed)
|
|
78
|
-
if (!checked.ok) {
|
|
79
|
-
return { result: fail("MALFORMED", checked.message), issuerId }
|
|
80
|
-
}
|
|
81
|
-
const claims: ApprovalCredentialClaims = checked.claims
|
|
82
|
-
|
|
83
|
-
if (now < claims.nbf) {
|
|
84
|
-
return { result: fail("NOT_YET_VALID", "credential is not yet within its validity window"), issuerId }
|
|
85
|
-
}
|
|
86
|
-
if (now > claims.exp) {
|
|
87
|
-
return { result: fail("EXPIRED", "credential validity window has passed"), issuerId }
|
|
88
|
-
}
|
|
89
|
-
if (await resolver.isRevoked(BigInt(claims.status.cn_zk_revocation.revocationCommitment))) {
|
|
90
|
-
return { result: fail("REVOKED", "credential commitment is in the revocation registry"), issuerId }
|
|
91
|
-
}
|
|
92
|
-
return { result: { ok: true, claims }, issuerId }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Verifies a compact SD-JWT credential with all its disclosures — the
|
|
97
|
-
* holder's verify-on-receipt and any check that does not involve a live
|
|
98
|
-
* presentation. Key binding is not checked here; that is verifyPresentation.
|
|
99
|
-
*/
|
|
100
|
-
export async function verifySdJwtCredential(
|
|
101
|
-
token: string,
|
|
102
|
-
resolver: Resolver,
|
|
103
|
-
options?: VerifyOptions
|
|
104
|
-
): Promise<VerifyResult> {
|
|
105
|
-
const now = options?.currentDate ?? Math.floor(Date.now() / 1000)
|
|
106
|
-
const { result } = await runPipeline(token, resolver, now, async (issuerJwk, clampedNow) => {
|
|
107
|
-
const sdjwt = new SDJwtVcInstance({
|
|
108
|
-
verifier: createSdJwtVerifier(issuerJwk),
|
|
109
|
-
hasher: sdJwtHasher,
|
|
110
|
-
hashAlg: "sha-256"
|
|
111
|
-
})
|
|
112
|
-
const { payload } = await sdjwt.verify(token, { currentDate: clampedNow })
|
|
113
|
-
return payload
|
|
114
|
-
})
|
|
115
|
-
return result
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export { runPipeline }
|