@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/LICENSE +201 -0
- package/dist/index.cjs +535 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +448 -0
- package/dist/index.d.ts +448 -0
- package/dist/index.js +514 -0
- package/dist/index.js.map +1 -0
- package/package.json +81 -0
- package/plugin.schema.json +183 -0
- package/src/__tests__/localAgent.test.ts +95 -0
- package/src/__tests__/shared/linkedVPManagerAgentLogic.ts +180 -0
- package/src/agent/LinkedVPManager.ts +240 -0
- package/src/index.ts +7 -0
- package/src/services/LinkedVPService.ts +150 -0
- package/src/types/ILinkedVPManager.ts +90 -0
- package/src/types/index.ts +1 -0
package/src/index.ts
ADDED
|
@@ -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'
|