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

Signed-off-by: goncalo-frade-iohk <[email protected]>
  • Loading branch information
goncalo-frade-iohk committed Jun 11, 2024
1 parent 8e68386 commit afca01b
Show file tree
Hide file tree
Showing 29 changed files with 527 additions and 258 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
98 changes: 81 additions & 17 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,23 +98,40 @@ extension DIDCore.DIDDocument {
}

let services = try self.services?.map {
guard
let endpoint = $0.serviceEndpoint.value as? [String: Any],
let uri = endpoint["uri"] as? String
else {
throw CastorError.notPossibleToResolveDID(did: $0.id, reason: "Invalid service")
let service = try DIDCore.DIDDocument.Service(from: $0)
switch service.serviceEndpoint.value {
case let endpoint as [String: Any]:
guard
let uri = endpoint["uri"] as? String
else {
throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service")
}
return Domain.DIDDocument.Service(
id: service.id,
type: [service.type],
serviceEndpoint: [
.init(
uri: uri,
accept: endpoint["accept"] as? [String] ?? [],
routingKeys: endpoint["routing_keys"] as? [String] ?? []
)
]
)
case let endpoint as String:
return Domain.DIDDocument.Service(
id: service.id,
type: [service.type],
serviceEndpoint: [
.init(
uri: endpoint,
accept: ($0.value as? [String: Any])?["accept"] as? [String] ?? [],
routingKeys: ($0.value as? [String: Any])?["routing_keys"] as? [String] ?? []
)
]
)
default:
throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service")
}
return Domain.DIDDocument.Service(
id: $0.id,
type: [$0.type],
serviceEndpoint: [
.init(
uri: uri,
accept: endpoint["accept"] as? [String] ?? [],
routingKeys: endpoint["routing_keys"] as? [String] ?? []
)
]
)
} ?? [Domain.DIDDocument.Service]()

return Domain.DIDDocument(
Expand Down Expand Up @@ -184,3 +201,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)
)
}
}
16 changes: 4 additions & 12 deletions EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class PeerDIDCreationTests: XCTestCase {
KeyProperties.rawKey.rawValue: Data(base64URLEncoded: "COd9Xhr-amD7fuswWId2706JBUY_tmjp9eiNEieJeEE")!.base64Encoded()
])

print(keyAgreementPrivateKey.raw.base64URLEncoded())


let authenticationPrivateKey = try apollo.createPrivateKey(parameters: [
KeyProperties.type.rawValue: "EC",
Expand All @@ -35,28 +37,18 @@ final class PeerDIDCreationTests: XCTestCase {
services: [service]
)

XCTAssertEqual(did.string, validPeerDID)
XCTAssertTrue(did.string.contains("did:peer:2.Ez6LSoHkfN1Y4nK9RCjx7vopWsLrMGNFNgTNZgoCNQrTzmb1n.Vz6MknRZmapV7uYZQuZez9n9N3tQotjRN18UGS68Vcfo6gR4h.SeyJ"))
}

func testResolvePeerDID() async throws {
let peerDIDString = "did:peer:2.Ez6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd.Vz6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv.SeyJ0IjoiZG0iLCJzIjoibG9jYWxob3N0OjgwODIiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19"

let peerDID = DID(
schema: "did",
method: "peer",
methodId: "2.Ez6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd.Vz6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv.SeyJ0IjoiZG0iLCJzIjoibG9jYWxob3N0OjgwODIiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19"
)

let mypeerDIDString = "did:peer:2.Ez6LSmx3k5X9xMos7VXdMDJx1CGNTd2tWfLTVyMtu3toJWqPo.Vz6Mkvcu3GqbvM3vr5W1sDVe41wmLeUL6a7b4wEcrGw6ULATR.SeyJ0IjoiZG0iLCJzIjoiazhzLWRldi5hdGFsYXByaXNtLmlvL3ByaXNtLWFnZW50L2RpZGNvbW0iLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19"

let mypeerDID = DID(
schema: "did",
method: "peer",
methodId: "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwczovL21lZGlhdG9yLnJvb3RzaWQuY2xvdWQiLCJhIjpbImRpZGNvbW0vdjIiXX0"
)

let apollo = ApolloImpl()
let castor = CastorImpl(apollo: apollo)
let document = try await castor.resolveDID(did: mypeerDID)
let document = try await castor.resolveDID(did: peerDID)
}
}
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,10 @@
import Domain
import Foundation

extension SDJWTCredential: ExportableCredential {
public var exporting: Data {
(try? sdjwtString.tryToData()) ?? Data()
}

public var restorationType: String { "sd-jwt" }
}
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
}
}
Loading

0 comments on commit afca01b

Please sign in to comment.