Skip to content

Commit

Permalink
feat(pollux): add support for sd-jwt
Browse files Browse the repository at this point in the history
This commit adds support for sd-jwt. Receive issued credentials and present.

Fixes ATL-7185
  • Loading branch information
goncalo-frade-iohk committed Jun 11, 2024
1 parent 8e68386 commit ce99d7e
Show file tree
Hide file tree
Showing 27 changed files with 483 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ struct CreatePeerDIDOperation {
agreementKeys: [keyAgreementFromPublicKey(publicKey: agreementPublicKey)],
services: services.flatMap { service in
service.serviceEndpoint.map {
DIDCore.DIDDocument.Service(
id: service.id,
type: service.type.first ?? "",
serviceEndpoint: AnyCodable(
dictionaryLiteral:
("uri", $0.uri),
("accept", $0.accept),
("routing_keys", $0.routingKeys)
)
AnyCodable(dictionaryLiteral:
("id", service.id),
("type", service.type.first ?? ""),
("serviceEndpoint", [
"uri" : $0.uri,
"accept" : $0.accept,
"routing_keys" : $0.routingKeys
])
)
}
}
Expand Down
60 changes: 54 additions & 6 deletions EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extension DIDCore.DIDDocument {
assertionMethod: nil,
capabilityDelegation: nil,
keyAgreement: keyAgreementIds.map { .stringValue($0) },
services: services
services: services.map { $0.toAnyCodable() }
)
}

Expand Down Expand Up @@ -98,15 +98,16 @@ extension DIDCore.DIDDocument {
}

let services = try self.services?.map {
guard
let endpoint = $0.serviceEndpoint.value as? [String: Any],
let service = try DIDCore.DIDDocument.Service(from: $0)
guard
let endpoint = service.serviceEndpoint.value as? [String: Any],
let uri = endpoint["uri"] as? String
else {
throw CastorError.notPossibleToResolveDID(did: $0.id, reason: "Invalid service")
throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service")
}
return Domain.DIDDocument.Service(
id: $0.id,
type: [$0.type],
id: service.id,
type: [service.type],
serviceEndpoint: [
.init(
uri: uri,
Expand Down Expand Up @@ -184,3 +185,50 @@ extension DIDCore.DIDDocument.VerificationMethod {
}
}
}

extension DIDCore.DIDDocument.Service {
init(from: AnyCodable) throws {
guard
let dic = from.value as? [String: Any],
let id = dic["id"] as? String,
let type = dic["type"] as? String,
let serviceEndpoint = dic["serviceEndpoint"]
else { throw CommonError.invalidCoding(message: "Could not decode service") }
switch serviceEndpoint {
case let value as AnyCodable:
self = .init(
id: id,
type: type,
serviceEndpoint: value
)
case let value as String:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
case let value as [String: Any]:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
case let value as [String]:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
default:
throw CommonError.invalidCoding(message: "Could not decode service")
}
}

func toAnyCodable() -> AnyCodable {
AnyCodable(dictionaryLiteral:
("id", self.id),
("type", self.type),
("serviceEndpoint", self.serviceEndpoint.value)
)
}
}
1 change: 1 addition & 0 deletions EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum CredentialOperationsOptions {
case signableKey(SignableKey) // A key that can be used for signing.
case exportableKey(ExportableKey) // A key that can be exported.
case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters
case disclosingClaims(claims: [String])
case custom(key: String, data: Data) // Any custom data.
}

Expand Down
2 changes: 2 additions & 0 deletions EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ public extension EdgeAgent {
switch offerFormat {
case "prism/jwt":
format = "prism/jwt"
case "vc+sd-jwt":
format = "vc+sd-jwt"
case "anoncreds/[email protected]":
format = "anoncreds/[email protected]"
default:
Expand Down
2 changes: 1 addition & 1 deletion EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public extension EdgeAgent {
.linkSecret(id: "", secret: linkSecretString)
]
)
case "prism/jwt", "dif/presentation-exchange/[email protected]":
case "prism/jwt", "vc+sd-jwt", "dif/presentation-exchange/[email protected]":
guard
let subjectDIDString = credential.subject
else {
Expand Down
10 changes: 5 additions & 5 deletions EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public class EdgeAgent {
}

let logger = SDKLogger(category: .edgeAgent)
let apollo: Apollo & KeyRestoration
let castor: Castor
let pluto: Pluto
let pollux: Pollux & CredentialImporter
let mercury: Mercury
public let apollo: Apollo & KeyRestoration
public let castor: Castor
public let pluto: Pluto
public let pollux: Pollux & CredentialImporter
public let mercury: Mercury
var mediationHandler: MediatorHandler?

var connectionManager: ConnectionsManagerImpl?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ final class PresentationExchangeFlowTests: XCTestCase {
}
}

private struct MockCredentialClaim: JWTRegisteredFieldsClaims {
private struct MockCredentialClaim: JWTRegisteredFieldsClaims, Codable {
struct VC: Codable {
let credentialSubject: [String: String]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extension DIDCore.DIDDocument {
verificationMethods: verificationMethods,
authentication: authentications.map { .stringValue($0) },
keyAgreement: keyAgreements.map { .stringValue($0) },
services: services
services: services.map { $0.toAnyCodable() }
)
}
}
Expand All @@ -95,3 +95,50 @@ extension Dictionary where Key == String, Value == String {
try Core.convertToJsonString(dic: self)
}
}

extension DIDCore.DIDDocument.Service {
init(from: DIDCore.AnyCodable) throws {
guard
let dic = from.value as? [String: Any],
let id = dic["id"] as? String,
let type = dic["type"] as? String,
let serviceEndpoint = dic["serviceEndpoint"]
else { throw CommonError.invalidCoding(message: "Could not decode service") }
switch serviceEndpoint {
case let value as DIDCore.AnyCodable:
self = .init(
id: id,
type: type,
serviceEndpoint: value
)
case let value as String:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
case let value as [String: Any]:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
case let value as [String]:
self = .init(
id: id,
type: type,
serviceEndpoint: AnyCodable(value)
)
default:
throw CommonError.invalidCoding(message: "Could not decode service")
}
}

func toAnyCodable() -> DIDCore.AnyCodable {
AnyCodable(dictionaryLiteral:
("id", self.id),
("type", self.type),
("serviceEndpoint", self.serviceEndpoint.value)
)
}
}
3 changes: 2 additions & 1 deletion EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ struct SessionManager {
headers: [String: String] = [:],
parameters: [String: String] = [:]
) async throws -> Data? {
try await call(request: try makeRequest(
let url = URL(string: url.absoluteString.replacingOccurrences(of: "host.docker.internal", with: "localhost"))!
return try await call(request: try makeRequest(
url: url,
method: .post,
body: body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension JWTCredential: ProvableCredential {
switch attachment.format {
case "dif/presentation-exchange/[email protected]":
let requestData = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: jsonData)
let payload = try JWT<DefaultJWTClaimsImpl>.getPayload(jwtString: jwtString)
let payload: Data = try JWT.getPayload(jwtString: jwtString)
do {
try VerifyPresentationSubmission.verifyPresentationSubmissionClaims(
request: requestData.presentationDefinition, credentials: [payload]
Expand Down
5 changes: 3 additions & 2 deletions EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Core
import Domain
import Foundation
import JSONWebAlgorithms
import JSONWebKey
import JSONWebSignature
import JSONWebToken
import Sextant
Expand Down Expand Up @@ -198,7 +199,7 @@ struct JWTPresentation {
let jwt = try JWT.signed(
payload: payload,
protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256K),
key: .init(
key: JSONWebKey.JWK(
keyType: .init(rawValue: keyJWK.kty)!,
keyID: keyJWK.kid,
x: keyJWK.x.flatMap { Data(fromBase64URL: $0) },
Expand All @@ -213,7 +214,7 @@ struct JWTPresentation {
}
}

struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims {
struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims, Codable {
struct VerifiablePresentation: Codable {
enum CodingKeys: String, CodingKey {
case context = "@context"
Expand Down
21 changes: 21 additions & 0 deletions EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

extension SDJWTCredential: Codable {
enum CodingKeys: String, CodingKey {
case sdjwtString
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(sdjwtString, forKey: .sdjwtString)
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

let sdjwtString = try container.decode(String.self, forKey: .sdjwtString)

try self.init(sdjwtString: sdjwtString)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Domain
import Foundation

extension SDJWTCredential: ProvableCredential {
func presentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> String {
try SDJWTPresentation().createPresentation(
credential: self,
request: request,
options: options
)
}

func isValidForPresentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> Bool {
request.attachments.first.map { $0.format == "vc+sd-jwt"} ?? true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Domain
import Foundation

extension SDJWTCredential: StorableCredential {
var storingId: String {
sdjwtString
}

var recoveryId: String {
"sd-jwt+credential"
}

var credentialData: Data {
(try? sdjwtString.tryToData()) ?? Data()
}

var queryIssuer: String? {
issuer
}

var querySubject: String? {
subject
}

var queryCredentialCreated: Date? {
nil
}

var queryCredentialUpdated: Date? {
nil
}

var queryCredentialSchema: String? {
nil
}

var queryValidUntil: Date? {
nil
}

var queryRevoked: Bool? {
nil
}

var queryAvailableClaims: [String] {
claims.map(\.key)
}
}
Loading

0 comments on commit ce99d7e

Please sign in to comment.