Skip to content

Commit ce99d7e

Browse files
feat(pollux): add support for sd-jwt
This commit adds support for sd-jwt. Receive issued credentials and present. Fixes ATL-7185
1 parent 8e68386 commit ce99d7e

27 files changed

+483
-234
lines changed

EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift

+8-9
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ struct CreatePeerDIDOperation {
1717
agreementKeys: [keyAgreementFromPublicKey(publicKey: agreementPublicKey)],
1818
services: services.flatMap { service in
1919
service.serviceEndpoint.map {
20-
DIDCore.DIDDocument.Service(
21-
id: service.id,
22-
type: service.type.first ?? "",
23-
serviceEndpoint: AnyCodable(
24-
dictionaryLiteral:
25-
("uri", $0.uri),
26-
("accept", $0.accept),
27-
("routing_keys", $0.routingKeys)
28-
)
20+
AnyCodable(dictionaryLiteral:
21+
("id", service.id),
22+
("type", service.type.first ?? ""),
23+
("serviceEndpoint", [
24+
"uri" : $0.uri,
25+
"accept" : $0.accept,
26+
"routing_keys" : $0.routingKeys
27+
])
2928
)
3029
}
3130
}

EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift

+54-6
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ extension DIDCore.DIDDocument {
6060
assertionMethod: nil,
6161
capabilityDelegation: nil,
6262
keyAgreement: keyAgreementIds.map { .stringValue($0) },
63-
services: services
63+
services: services.map { $0.toAnyCodable() }
6464
)
6565
}
6666

@@ -98,15 +98,16 @@ extension DIDCore.DIDDocument {
9898
}
9999

100100
let services = try self.services?.map {
101-
guard
102-
let endpoint = $0.serviceEndpoint.value as? [String: Any],
101+
let service = try DIDCore.DIDDocument.Service(from: $0)
102+
guard
103+
let endpoint = service.serviceEndpoint.value as? [String: Any],
103104
let uri = endpoint["uri"] as? String
104105
else {
105-
throw CastorError.notPossibleToResolveDID(did: $0.id, reason: "Invalid service")
106+
throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service")
106107
}
107108
return Domain.DIDDocument.Service(
108-
id: $0.id,
109-
type: [$0.type],
109+
id: service.id,
110+
type: [service.type],
110111
serviceEndpoint: [
111112
.init(
112113
uri: uri,
@@ -184,3 +185,50 @@ extension DIDCore.DIDDocument.VerificationMethod {
184185
}
185186
}
186187
}
188+
189+
extension DIDCore.DIDDocument.Service {
190+
init(from: AnyCodable) throws {
191+
guard
192+
let dic = from.value as? [String: Any],
193+
let id = dic["id"] as? String,
194+
let type = dic["type"] as? String,
195+
let serviceEndpoint = dic["serviceEndpoint"]
196+
else { throw CommonError.invalidCoding(message: "Could not decode service") }
197+
switch serviceEndpoint {
198+
case let value as AnyCodable:
199+
self = .init(
200+
id: id,
201+
type: type,
202+
serviceEndpoint: value
203+
)
204+
case let value as String:
205+
self = .init(
206+
id: id,
207+
type: type,
208+
serviceEndpoint: AnyCodable(value)
209+
)
210+
case let value as [String: Any]:
211+
self = .init(
212+
id: id,
213+
type: type,
214+
serviceEndpoint: AnyCodable(value)
215+
)
216+
case let value as [String]:
217+
self = .init(
218+
id: id,
219+
type: type,
220+
serviceEndpoint: AnyCodable(value)
221+
)
222+
default:
223+
throw CommonError.invalidCoding(message: "Could not decode service")
224+
}
225+
}
226+
227+
func toAnyCodable() -> AnyCodable {
228+
AnyCodable(dictionaryLiteral:
229+
("id", self.id),
230+
("type", self.type),
231+
("serviceEndpoint", self.serviceEndpoint.value)
232+
)
233+
}
234+
}

EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum CredentialOperationsOptions {
1313
case signableKey(SignableKey) // A key that can be used for signing.
1414
case exportableKey(ExportableKey) // A key that can be exported.
1515
case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters
16+
case disclosingClaims(claims: [String])
1617
case custom(key: String, data: Data) // Any custom data.
1718
}
1819

EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift

+2
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ public extension EdgeAgent {
198198
switch offerFormat {
199199
case "prism/jwt":
200200
format = "prism/jwt"
201+
case "vc+sd-jwt":
202+
format = "vc+sd-jwt"
201203
case "anoncreds/[email protected]":
202204
format = "anoncreds/[email protected]"
203205
default:

EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public extension EdgeAgent {
4444
.linkSecret(id: "", secret: linkSecretString)
4545
]
4646
)
47-
case "prism/jwt", "dif/presentation-exchange/[email protected]":
47+
case "prism/jwt", "vc+sd-jwt", "dif/presentation-exchange/[email protected]":
4848
guard
4949
let subjectDIDString = credential.subject
5050
else {

EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public class EdgeAgent {
3232
}
3333

3434
let logger = SDKLogger(category: .edgeAgent)
35-
let apollo: Apollo & KeyRestoration
36-
let castor: Castor
37-
let pluto: Pluto
38-
let pollux: Pollux & CredentialImporter
39-
let mercury: Mercury
35+
public let apollo: Apollo & KeyRestoration
36+
public let castor: Castor
37+
public let pluto: Pluto
38+
public let pollux: Pollux & CredentialImporter
39+
public let mercury: Mercury
4040
var mediationHandler: MediatorHandler?
4141

4242
var connectionManager: ConnectionsManagerImpl?

EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ final class PresentationExchangeFlowTests: XCTestCase {
123123
}
124124
}
125125

126-
private struct MockCredentialClaim: JWTRegisteredFieldsClaims {
126+
private struct MockCredentialClaim: JWTRegisteredFieldsClaims, Codable {
127127
struct VC: Codable {
128128
let credentialSubject: [String: String]
129129
}

EdgeAgentSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift

+48-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ extension DIDCore.DIDDocument {
8585
verificationMethods: verificationMethods,
8686
authentication: authentications.map { .stringValue($0) },
8787
keyAgreement: keyAgreements.map { .stringValue($0) },
88-
services: services
88+
services: services.map { $0.toAnyCodable() }
8989
)
9090
}
9191
}
@@ -95,3 +95,50 @@ extension Dictionary where Key == String, Value == String {
9595
try Core.convertToJsonString(dic: self)
9696
}
9797
}
98+
99+
extension DIDCore.DIDDocument.Service {
100+
init(from: DIDCore.AnyCodable) throws {
101+
guard
102+
let dic = from.value as? [String: Any],
103+
let id = dic["id"] as? String,
104+
let type = dic["type"] as? String,
105+
let serviceEndpoint = dic["serviceEndpoint"]
106+
else { throw CommonError.invalidCoding(message: "Could not decode service") }
107+
switch serviceEndpoint {
108+
case let value as DIDCore.AnyCodable:
109+
self = .init(
110+
id: id,
111+
type: type,
112+
serviceEndpoint: value
113+
)
114+
case let value as String:
115+
self = .init(
116+
id: id,
117+
type: type,
118+
serviceEndpoint: AnyCodable(value)
119+
)
120+
case let value as [String: Any]:
121+
self = .init(
122+
id: id,
123+
type: type,
124+
serviceEndpoint: AnyCodable(value)
125+
)
126+
case let value as [String]:
127+
self = .init(
128+
id: id,
129+
type: type,
130+
serviceEndpoint: AnyCodable(value)
131+
)
132+
default:
133+
throw CommonError.invalidCoding(message: "Could not decode service")
134+
}
135+
}
136+
137+
func toAnyCodable() -> DIDCore.AnyCodable {
138+
AnyCodable(dictionaryLiteral:
139+
("id", self.id),
140+
("type", self.type),
141+
("serviceEndpoint", self.serviceEndpoint.value)
142+
)
143+
}
144+
}

EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ struct SessionManager {
2121
headers: [String: String] = [:],
2222
parameters: [String: String] = [:]
2323
) async throws -> Data? {
24-
try await call(request: try makeRequest(
24+
let url = URL(string: url.absoluteString.replacingOccurrences(of: "host.docker.internal", with: "localhost"))!
25+
return try await call(request: try makeRequest(
2526
url: url,
2627
method: .post,
2728
body: body,

EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension JWTCredential: ProvableCredential {
3131
switch attachment.format {
3232
case "dif/presentation-exchange/[email protected]":
3333
let requestData = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: jsonData)
34-
let payload = try JWT<DefaultJWTClaimsImpl>.getPayload(jwtString: jwtString)
34+
let payload: Data = try JWT.getPayload(jwtString: jwtString)
3535
do {
3636
try VerifyPresentationSubmission.verifyPresentationSubmissionClaims(
3737
request: requestData.presentationDefinition, credentials: [payload]

EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Core
22
import Domain
33
import Foundation
44
import JSONWebAlgorithms
5+
import JSONWebKey
56
import JSONWebSignature
67
import JSONWebToken
78
import Sextant
@@ -198,7 +199,7 @@ struct JWTPresentation {
198199
let jwt = try JWT.signed(
199200
payload: payload,
200201
protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256K),
201-
key: .init(
202+
key: JSONWebKey.JWK(
202203
keyType: .init(rawValue: keyJWK.kty)!,
203204
keyID: keyJWK.kid,
204205
x: keyJWK.x.flatMap { Data(fromBase64URL: $0) },
@@ -213,7 +214,7 @@ struct JWTPresentation {
213214
}
214215
}
215216

216-
struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims {
217+
struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims, Codable {
217218
struct VerifiablePresentation: Codable {
218219
enum CodingKeys: String, CodingKey {
219220
case context = "@context"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
3+
extension SDJWTCredential: Codable {
4+
enum CodingKeys: String, CodingKey {
5+
case sdjwtString
6+
}
7+
8+
func encode(to encoder: any Encoder) throws {
9+
var container = encoder.container(keyedBy: CodingKeys.self)
10+
11+
try container.encode(sdjwtString, forKey: .sdjwtString)
12+
}
13+
14+
init(from decoder: any Decoder) throws {
15+
let container = try decoder.container(keyedBy: CodingKeys.self)
16+
17+
let sdjwtString = try container.decode(String.self, forKey: .sdjwtString)
18+
19+
try self.init(sdjwtString: sdjwtString)
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Domain
2+
import Foundation
3+
4+
extension SDJWTCredential: ProvableCredential {
5+
func presentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> String {
6+
try SDJWTPresentation().createPresentation(
7+
credential: self,
8+
request: request,
9+
options: options
10+
)
11+
}
12+
13+
func isValidForPresentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> Bool {
14+
request.attachments.first.map { $0.format == "vc+sd-jwt"} ?? true
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Domain
2+
import Foundation
3+
4+
extension SDJWTCredential: StorableCredential {
5+
var storingId: String {
6+
sdjwtString
7+
}
8+
9+
var recoveryId: String {
10+
"sd-jwt+credential"
11+
}
12+
13+
var credentialData: Data {
14+
(try? sdjwtString.tryToData()) ?? Data()
15+
}
16+
17+
var queryIssuer: String? {
18+
issuer
19+
}
20+
21+
var querySubject: String? {
22+
subject
23+
}
24+
25+
var queryCredentialCreated: Date? {
26+
nil
27+
}
28+
29+
var queryCredentialUpdated: Date? {
30+
nil
31+
}
32+
33+
var queryCredentialSchema: String? {
34+
nil
35+
}
36+
37+
var queryValidUntil: Date? {
38+
nil
39+
}
40+
41+
var queryRevoked: Bool? {
42+
nil
43+
}
44+
45+
var queryAvailableClaims: [String] {
46+
claims.map(\.key)
47+
}
48+
}

0 commit comments

Comments
 (0)