@sphereon/ssi-sdk.linked-vp 0.34.1-feature.SSISDK.82.and.SSISDK.70.345

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/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @public
3
+ */
4
+ import schema from '../plugin.schema.json'
5
+ export { schema }
6
+ export { LinkedVPManager, linkedVPManagerMethods } from './agent/LinkedVPManager'
7
+ export * from './types/ILinkedVPManager'
@@ -0,0 +1,150 @@
1
+ import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
2
+ import { calculateSdHash, defaultGenerateDigest, PartialSdJwtKbJwt } from '@sphereon/ssi-sdk.sd-jwt'
3
+
4
+ import {
5
+ CredentialMapper,
6
+ DocumentFormat,
7
+ Loggers,
8
+ OriginalVerifiableCredential,
9
+ SdJwtDecodedVerifiableCredential,
10
+ WrappedVerifiableCredential,
11
+ } from '@sphereon/ssi-types'
12
+ import { LinkedVPPresentation, LOGGER_NAMESPACE, RequiredContext } from '../types'
13
+
14
+ const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE)
15
+ const CLOCK_SKEW = 120 // TODO make adjustable?
16
+
17
+ /**
18
+ * Extracts the original credential from various wrapper types
19
+ */
20
+ function extractOriginalCredential(
21
+ credential: UniqueDigitalCredential | WrappedVerifiableCredential | OriginalVerifiableCredential,
22
+ ): OriginalVerifiableCredential {
23
+ if (typeof credential === 'string') {
24
+ return credential
25
+ }
26
+
27
+ if ('digitalCredential' in credential) {
28
+ const udc = credential as UniqueDigitalCredential
29
+ if (udc.originalVerifiableCredential) {
30
+ return udc.originalVerifiableCredential
31
+ }
32
+ return udc.uniformVerifiableCredential as OriginalVerifiableCredential
33
+ }
34
+
35
+ if ('original' in credential) {
36
+ return credential.original
37
+ }
38
+
39
+ return credential as OriginalVerifiableCredential
40
+ }
41
+
42
+ /**
43
+ * Creates a Verifiable Presentation for LinkedVP publishing
44
+ * Contains multiple credentials in a single JWT VP
45
+ * No nonce or audience since this is for publishing, not responding to verification
46
+ */
47
+ export async function createLinkedVPPresentation(
48
+ holderDid: string,
49
+ credential: UniqueDigitalCredential,
50
+ agent: RequiredContext['agent'],
51
+ ): Promise<LinkedVPPresentation> {
52
+ logger.debug(`Creating LinkedVP presentation for ${holderDid} of credential ${credential.id}`)
53
+
54
+ const identifier = await agent.identifierManagedGet({ identifier: holderDid })
55
+ const originalCredential = extractOriginalCredential(credential)
56
+ const documentFormat = CredentialMapper.detectDocumentType(originalCredential)
57
+ switch (documentFormat) {
58
+ case DocumentFormat.SD_JWT_VC: {
59
+ // SD-JWT with KB-JWT
60
+ const decodedSdJwt = await CredentialMapper.decodeSdJwtVcAsync(
61
+ typeof originalCredential === 'string' ? originalCredential : (originalCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc,
62
+ defaultGenerateDigest,
63
+ )
64
+
65
+ const hashAlg = decodedSdJwt.signedPayload._sd_alg ?? 'sha-256'
66
+ const sdHash = calculateSdHash(decodedSdJwt.compactSdJwtVc, hashAlg, defaultGenerateDigest)
67
+ const kbJwtPayload: PartialSdJwtKbJwt['payload'] = {
68
+ iat: Math.floor(Date.now() / 1000 - CLOCK_SKEW),
69
+ sd_hash: sdHash,
70
+ }
71
+
72
+ const presentationResult = await agent.createSdJwtPresentation({
73
+ presentation: decodedSdJwt.compactSdJwtVc,
74
+ kb: {
75
+ payload: kbJwtPayload as any, // FIXME?
76
+ },
77
+ })
78
+
79
+ return {
80
+ documentFormat,
81
+ presentationPayload: presentationResult.presentation,
82
+ }
83
+ }
84
+ case DocumentFormat.JSONLD: {
85
+ // JSON-LD VC - create JSON-LD VP with challenge and domain in proof
86
+ const vcObject = typeof originalCredential === 'string' ? JSON.parse(originalCredential) : originalCredential
87
+
88
+ const vpObject = {
89
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
90
+ type: ['VerifiablePresentation'],
91
+ verifiableCredential: [vcObject],
92
+ holder: holderDid,
93
+ }
94
+
95
+ // Create JSON-LD VP with proof
96
+ const verifiablePresentationSP = await agent.createVerifiablePresentation({
97
+ presentation: vpObject,
98
+ proofFormat: 'lds',
99
+ keyRef: identifier.kmsKeyRef || identifier.kid,
100
+ })
101
+ return {
102
+ documentFormat,
103
+ presentationPayload: verifiablePresentationSP,
104
+ }
105
+ }
106
+ case DocumentFormat.MSO_MDOC: {
107
+ // ISO mdoc - create mdoc VP token
108
+ // This is a placeholder implementation
109
+ // Full implementation would require:
110
+ // 1. Decode the mdoc using CredentialMapper or mdoc utilities
111
+ // 2. Build proper mdoc VP token with session transcript
112
+ // 3. Include nonce/audience in the session transcript
113
+ logger.warning('mso_mdoc format has basic support - production use requires proper mdoc VP token implementation')
114
+
115
+ return {
116
+ documentFormat,
117
+ presentationPayload: originalCredential,
118
+ }
119
+ }
120
+ default: {
121
+ // JWT VC - create JWT VP with nonce and aud in payload
122
+ const vcJwt = typeof originalCredential === 'string' ? originalCredential : JSON.stringify(originalCredential)
123
+
124
+ // Create VP JWT using agent method
125
+ const vpPayload = {
126
+ iss: holderDid,
127
+ vp: {
128
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
129
+ type: ['VerifiablePresentation'],
130
+ holder: holderDid,
131
+ verifiableCredential: [vcJwt],
132
+ },
133
+ iat: Math.floor(Date.now() / 1000 - CLOCK_SKEW),
134
+ exp: Math.floor(Date.now() / 1000 + 600 + CLOCK_SKEW), // 10 minutes
135
+ }
136
+
137
+ // Use the agent's JWT creation capability
138
+ const vpJwt = await agent.createVerifiablePresentation({
139
+ presentation: vpPayload.vp,
140
+ proofFormat: 'jwt',
141
+ keyRef: identifier.kmsKeyRef || identifier.kid,
142
+ })
143
+
144
+ return {
145
+ documentFormat,
146
+ presentationPayload: (vpJwt.proof && 'jwt' in vpJwt.proof && vpJwt.proof.jwt) || vpJwt,
147
+ }
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,90 @@
1
+ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
2
+ import { ICredentialStore } from '@sphereon/ssi-sdk.credential-store'
3
+ import { VcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
4
+ import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt'
5
+ import { DocumentFormat } from '@sphereon/ssi-types'
6
+ import { IAgentContext, IPluginMethodMap } from '@veramo/core'
7
+ import { IKeyManager } from '@veramo/core/src/types/IKeyManager'
8
+
9
+ export const LOGGER_NAMESPACE = 'sphereon:linked-vp'
10
+
11
+ export type LinkedVPPresentation = {
12
+ documentFormat: DocumentFormat
13
+ presentationPayload: string | Record<string, any>
14
+ }
15
+
16
+ export interface ILinkedVPManager extends IPluginMethodMap {
17
+ /**
18
+ * Publish a credential as a LinkedVP by adding it to the holder's DID Document
19
+ * @param args - Publication arguments including credential ID and scope configuration
20
+ * @param context - Agent context
21
+ */
22
+ lvpPublishCredential(args: PublishCredentialArgs, context: RequiredContext): Promise<LinkedVPEntry>
23
+
24
+ /**
25
+ * Unpublish a credential by removing its LinkedVP entry from the DID Document
26
+ * @param args - Unpublish arguments
27
+ * @param context - Agent context
28
+ */
29
+ lvpUnpublishCredential(args: UnpublishCredentialArgs, context: RequiredContext): Promise<boolean>
30
+
31
+ /**
32
+ * Check if a LinkedVP entry exists by linkedVpId
33
+ * @param args - Query arguments
34
+ * @param context - Agent context
35
+ */
36
+ lvpHasEntry(args: HasLinkedVPEntryArgs, context: RequiredContext): Promise<boolean>
37
+
38
+ /**
39
+ * Get LinkedVP service entries for a DID to be added to a DID Document
40
+ * This is useful when generating DID Documents with toDidDocument
41
+ * @param args - Query arguments for the DID
42
+ * @param context - Agent context
43
+ */
44
+ lvpGetServiceEntries(args: GetServiceEntriesArgs, context: RequiredContext): Promise<Array<LinkedVPServiceEntry>>
45
+
46
+ /**
47
+ * Generate and return a Verifiable Presentation for a published LinkedVP
48
+ * This is the main endpoint handler for GET /linked-vp/{linkedVpId}
49
+ * @param args - Generation arguments
50
+ * @param context - Agent context
51
+ */
52
+ lvpGeneratePresentation(args: GeneratePresentationArgs, context: RequiredContext): Promise<LinkedVPPresentation>
53
+ }
54
+
55
+ export type PublishCredentialArgs = {
56
+ digitalCredentialId: string
57
+ linkedVpId?: string // Optional: if not provided, will be auto-generated
58
+ }
59
+
60
+ export type UnpublishCredentialArgs = {
61
+ linkedVpId: string
62
+ }
63
+
64
+ export type HasLinkedVPEntryArgs = {
65
+ linkedVpId: string
66
+ }
67
+
68
+ export type GetServiceEntriesArgs = {
69
+ tenantId?: string
70
+ }
71
+
72
+ export type GeneratePresentationArgs = {
73
+ linkedVpId: string
74
+ }
75
+
76
+ export type LinkedVPEntry = {
77
+ id: string
78
+ linkedVpId: string
79
+ linkedVpFrom?: Date
80
+ tenantId?: string
81
+ createdAt: Date
82
+ }
83
+
84
+ export type LinkedVPServiceEntry = {
85
+ id: string
86
+ type: 'LinkedVerifiablePresentation'
87
+ serviceEndpoint: string
88
+ }
89
+
90
+ export type RequiredContext = IAgentContext<IIdentifierResolution & ICredentialStore & IKeyManager & VcdmCredentialPlugin & ISDJwtPlugin>
@@ -0,0 +1 @@
1
+ export * from './ILinkedVPManager'