From 2c0802855d200f30f7856007ffd0a9094ef2b66b Mon Sep 17 00:00:00 2001 From: Goncalo Frade Date: Tue, 16 Apr 2024 10:35:03 +0100 Subject: [PATCH] feat!: now services are AnyCodable to conform to DID specifications misc added a helper did parameter to PeerDID model so now you can get DIDCore.DID directly from it BREAKING CHANGE: The API changed so services now is a AnyCodable --- Package.swift | 2 +- Sources/PeerDID/Models/PeerDID.swift | 5 + Sources/PeerDID/PeerDID/PeerDIDHelper.swift | 128 ++++++------------ .../CreatePeerDIDAlgo2Tests.swift | 22 ++- Tests/PeerDIDTests/EncodeServiceTests.swift | 110 +++++++++++---- 5 files changed, 143 insertions(+), 124 deletions(-) diff --git a/Package.swift b/Package.swift index b1a1609..ab0e99d 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/swift-libp2p/swift-multibase.git", .upToNextMajor(from: "0.0.1")), .package(url: "https://github.com/swift-libp2p/swift-bases.git", .upToNextMajor(from: "0.0.3")), - .package(url: "https://github.com/beatt83/didcore-swift.git", .upToNextMinor(from: "1.1.0")) + .package(url: "https://github.com/beatt83/didcore-swift.git", .upToNextMinor(from: "2.0.0")) ], targets: [ .target( diff --git a/Sources/PeerDID/Models/PeerDID.swift b/Sources/PeerDID/Models/PeerDID.swift index 0f6e07b..d2b2b6f 100644 --- a/Sources/PeerDID/Models/PeerDID.swift +++ b/Sources/PeerDID/Models/PeerDID.swift @@ -6,6 +6,7 @@ // import Foundation +import DIDCore public struct PeerDID { @@ -48,6 +49,10 @@ public struct PeerDID { self.algo = algo } + public var did: DID { + DID(schema: schema, method: method, methodId: methodId) + } + public var string: String { "\(schema):\(method):\(methodId)" } diff --git a/Sources/PeerDID/PeerDID/PeerDIDHelper.swift b/Sources/PeerDID/PeerDID/PeerDIDHelper.swift index 62d768f..a17f822 100644 --- a/Sources/PeerDID/PeerDID/PeerDIDHelper.swift +++ b/Sources/PeerDID/PeerDID/PeerDIDHelper.swift @@ -23,7 +23,7 @@ public struct PeerDIDHelper { public static func createAlgo2( authenticationKeys: [PeerDIDVerificationMaterial], agreementKeys: [PeerDIDVerificationMaterial], - services: [DIDDocument.Service] + services: [AnyCodable] ) throws -> PeerDID { let encodedAgreementsStrings = try agreementKeys @@ -128,86 +128,58 @@ public extension PeerDIDHelper { extension PeerDIDHelper { - struct PeerDIDService: Codable { + func parseServiceToPeerDID(_ service: AnyCodable) throws -> AnyCodable { + guard var value = service.value as? [String: Any] else { + throw PeerDIDError.invalidPeerDIDService + } - struct ServiceEndpoint: Codable { - let uri: String - let r: [String]? // Routing keys - let a: [String]? // Accept + if var serviceEndpoint = value["serviceEndpoint"] as? [String: Any] { + replaceServiceDictionaryKey(dic: &serviceEndpoint, from: "accept", to: "a") + replaceServiceDictionaryKey(dic: &serviceEndpoint, from: "routingKeys", to: "r") + value["serviceEndpoint"] = serviceEndpoint } - let t: String // Type - let s: ServiceEndpoint // Service Endpoint + replaceServiceDictionaryKey(dic: &value, from: "type", to: "t") + replaceServiceDictionaryKey(dic: &value, from: "serviceEndpoint", to: "s") + replaceServiceDictionaryKey(dic: &value, from: "accept", to: "a") + replaceServiceDictionaryKey(dic: &value, from: "routingKeys", to: "r") - init(from: DIDDocument.Service) throws { - self.t = from.type - guard - let dic = from.serviceEndpoint.value as? [String: Any], - let uri = dic["uri"] as? String - else { - throw PeerDIDError.invalidPeerDIDService - } - self.s = .init( - uri: uri, - r: dic["routing_keys"] as? [String], - a: dic["accept"] as? [String] - ) - } + value.removeValue(forKey: "id") - func toDIDDocumentService(did: String, index: Int) throws -> DIDDocument.Service { - return .init( - id: "\(did)#\(t.lowercased())-\(index+1)", - type: t, - serviceEndpoint: AnyCodable( - dictionaryLiteral: ("uri", s.uri), ("accept", s.a ?? []), ("routing_keys", s.r ?? []) - ) - ) - } + return AnyCodable(value) } - struct PeerDIDServiceLegacy: Codable { - - let t: String // Type - let s: String // Service Endpoint - let r: [String]? // Routing keys - let a: [String]? // Accept - - init(t: String, s: String, r: [String], a: [String]) { - self.t = t - self.s = s - self.r = r - self.a = a + func parsePeerDIDServiceToService(_ service: AnyCodable, did: String, index: Int) throws -> AnyCodable { + guard var value = service.value as? [String: Any], let type = value["t"] as? String else { + throw PeerDIDError.invalidPeerDIDService } - init(from: DIDDocument.Service) throws { - self.t = from.type - guard - let uri = from.serviceEndpoint.value as? String - else { - throw PeerDIDError.invalidPeerDIDService - } - self.s = uri - self.r = [] - self.a = [] + replaceServiceDictionaryKey(dic: &value, from: "t", to: "type") + replaceServiceDictionaryKey(dic: &value, from: "s", to: "serviceEndpoint") + replaceServiceDictionaryKey(dic: &value, from: "a", to: "accept") + replaceServiceDictionaryKey(dic: &value, from: "r", to: "routingKeys") + + if var serviceEndpoint = value["serviceEndpoint"] as? [String: Any] { + replaceServiceDictionaryKey(dic: &serviceEndpoint, from: "a", to: "accept") + replaceServiceDictionaryKey(dic: &serviceEndpoint, from: "r", to: "routingKeys") + value["serviceEndpoint"] = serviceEndpoint } - func toDIDDocumentService(did: String, index: Int) throws -> DIDDocument.Service { - var serviceEndpoint: [String: Any] = ["uri": s] - a.map { serviceEndpoint["accept"] = $0 } - r.map { serviceEndpoint["routing_keys"] = $0 } - - return .init( - id: "\(did)#\(t.lowercased())-\(index+1)", - type: t, - serviceEndpoint: AnyCodable(dictionaryLiteral: ("uri", s), ("accept", a ?? []), ("routing_keys", r ?? [])) - ) + value["id"] = "\(did)#\(type.lowercased())-\(index+1)" + return AnyCodable(value) + } + + private func replaceServiceDictionaryKey(dic: inout [String: Any], from: String, to: String) { + if let value = dic[from] { + dic[to] = value + dic.removeValue(forKey: from) } } - public func encodePeerDIDServices(service: DIDDocument.Service) throws -> String { + public func encodePeerDIDServices(service: AnyCodable) throws -> String { let encoder = JSONEncoder.peerDIDEncoder() - let peerDIDService = try PeerDIDService(from: service) + let peerDIDService = try parseServiceToPeerDID(service) guard let jsonStr = String(data: try encoder.encode(peerDIDService), encoding: .utf8) else { throw PeerDIDError.somethingWentWrong } @@ -224,7 +196,7 @@ extension PeerDIDHelper { return "S\(encodedService)" } - public func decodedPeerDIDService(did: String, serviceString: String, index: Int) throws -> DIDDocument.Service { + public func decodedPeerDIDService(did: String, serviceString: String, index: Int) throws -> AnyCodable { guard let serviceBase64Data = Data(base64URLEncoded: serviceString), let serviceStr = String(data: serviceBase64Data, encoding: .utf8) @@ -238,21 +210,8 @@ extension PeerDIDHelper { else { throw PeerDIDError.invalidPeerDIDService } - let decoder = JSONDecoder() - if - let service = try? decoder.decode( - PeerDIDServiceLegacy.self, - from: peerDIDServiceData - ) - { - return try service.toDIDDocumentService(did: did, index: index) - } else { - let service = try decoder.decode( - PeerDIDService.self, - from: peerDIDServiceData - ) - return try service.toDIDDocumentService(did: did, index: index) - } + let serviceObject = try JSONDecoder().decode(AnyCodable.self, from: peerDIDServiceData) + return try parsePeerDIDServiceToService(serviceObject, did: did, index: index) } } @@ -275,10 +234,3 @@ extension DIDDocument.VerificationMethod { ) } } -extension DIDDocument.Service { - - func getJsonString() throws -> String? { - let encoder = JSONEncoder.peerDIDEncoder() - return String(data: try encoder.encode(self), encoding: .utf8) - } -} diff --git a/Tests/PeerDIDTests/CreatePeerDIDAlgo2Tests.swift b/Tests/PeerDIDTests/CreatePeerDIDAlgo2Tests.swift index c05026e..c3aba76 100644 --- a/Tests/PeerDIDTests/CreatePeerDIDAlgo2Tests.swift +++ b/Tests/PeerDIDTests/CreatePeerDIDAlgo2Tests.swift @@ -64,14 +64,18 @@ final class CreatePeerDIDAlgo2Tests: XCTestCase { type: .agreement(.jsonWebKey2020) ) - let validService = DIDDocument.Service( - id: "test", - type: "DIDCommMessaging", - serviceEndpoint: AnyCodable( - dictionaryLiteral: ("uri","https://example.com/endpoint"), ("routing_keys", ["did:example:somemediator#somekey"])) - ) + let validServiceDic = [ + "id": "test", + "type": "DIDCommMessaging", + "serviceEndpoint": [ + "uri": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"] + ] + ] as [String : Any] func testCreatePeerDIDAlgo2Base58() throws { + let validService = AnyCodable(validServiceDic) + let peerDID = try PeerDIDHelper.createAlgo2( authenticationKeys: [valid_ed25519_key1_base58, valid_ed25519_key2_base58], agreementKeys: [valid_x25519_key_base58], @@ -87,6 +91,8 @@ final class CreatePeerDIDAlgo2Tests: XCTestCase { } func testCreatePeerDIDAlgo2Multibase() throws { + let validService = AnyCodable(validServiceDic) + let peerDID = try PeerDIDHelper.createAlgo2( authenticationKeys: [valid_ed25519_key1_multibase, valid_ed25519_key2_multibase], agreementKeys: [valid_x25519_key_multibase], @@ -102,6 +108,8 @@ final class CreatePeerDIDAlgo2Tests: XCTestCase { } func testCreatePeerDIDAlgo2JWK() throws { + let validService = AnyCodable(validServiceDic) + let peerDID = try PeerDIDHelper.createAlgo2( authenticationKeys: [valid_ed25519_key1_jwk, valid_ed25519_key2_jwk], agreementKeys: [valid_x25519_key_jwk], @@ -117,6 +125,8 @@ final class CreatePeerDIDAlgo2Tests: XCTestCase { } func testCreatePeerDIDAlgo2MultipleServices() throws { + let validService = AnyCodable(validServiceDic) + let peerDID = try PeerDIDHelper.createAlgo2( authenticationKeys: [valid_ed25519_key1_jwk], agreementKeys: [valid_x25519_key_jwk], diff --git a/Tests/PeerDIDTests/EncodeServiceTests.swift b/Tests/PeerDIDTests/EncodeServiceTests.swift index 1ed983c..c636ab2 100644 --- a/Tests/PeerDIDTests/EncodeServiceTests.swift +++ b/Tests/PeerDIDTests/EncodeServiceTests.swift @@ -12,45 +12,97 @@ import XCTest final class EncodeEcnumbasisTests: XCTestCase { func testEncodeService() throws { - let service = DIDDocument.Service( - id: "did:test:abc#didcommmessaging-1", - type: "DIDCommMessaging", - serviceEndpoint: AnyCodable( - dictionaryLiteral: ("uri","https://example.com/endpoint"), ("routing_keys", ["did:example:somemediator#somekey"]), ("accept", ["didcomm/v2", "didcomm/aip2;env=rfc587"])) - ) + let service = [ + "id": "did:test:abc#didcommmessaging-1", + "type": "DIDCommMessaging", + "serviceEndpoint": [ + "uri": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"], + "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] + ] + ] as [String: Any] let expect = "SeyJzIjp7ImEiOlsiZGlkY29tbS92MiIsImRpZGNvbW0vYWlwMjtlbnY9cmZjNTg3Il0sInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9LCJ0IjoiZG0ifQ" - XCTAssertEqual(expect, try PeerDIDHelper().encodePeerDIDServices(service: service)) + XCTAssertEqual(expect, try PeerDIDHelper().encodePeerDIDServices(service: AnyCodable(service))) } - func testDecodeService() throws { + func testEncodeServiceLegacy() throws { + let service = [ + "id": "did:test:abc#didcommmessaging-1", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"], + "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] + ] as [String: Any] + + let expect = "SeyJhIjpbImRpZGNvbW0vdjIiLCJkaWRjb21tL2FpcDI7ZW52PXJmYzU4NyJdLCJyIjpbImRpZDpleGFtcGxlOnNvbWVtZWRpYXRvciNzb21la2V5Il0sInMiOiJodHRwczovL2V4YW1wbGUuY29tL2VuZHBvaW50IiwidCI6ImRtIn0" + + XCTAssertEqual(expect, try PeerDIDHelper().encodePeerDIDServices(service: AnyCodable(service))) + } + + func testEncodeServiceExtensions() throws { + let service = [ + "id": "did:test:abc#didcommmessaging-1", + "type": "DIDCommMessaging", + "serviceEndpoint": [ + "uri": "https://example.com/endpoint", + "routingKeys": ["did:example:somemediator#somekey"], + "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] + ], + "extension1": "testExtension", + "extension2": ["testExtension"], + "extension3": ["testKey": "test"], + ] as [String: Any] + + let expect = "SeyJleHRlbnNpb24xIjoidGVzdEV4dGVuc2lvbiIsImV4dGVuc2lvbjIiOlsidGVzdEV4dGVuc2lvbiJdLCJleHRlbnNpb24zIjp7InRlc3RLZXkiOiJ0ZXN0In0sInMiOnsiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXSwiciI6WyJkaWQ6ZXhhbXBsZTpzb21lbWVkaWF0b3Ijc29tZWtleSJdLCJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2VuZHBvaW50In0sInQiOiJkbSJ9" + + XCTAssertEqual(expect, try PeerDIDHelper().encodePeerDIDServices(service: AnyCodable(service))) + } + + func testDecodeServiceLegacy() throws { let did = "did:test:abc" let service = "eyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0" - let expected = DIDDocument.Service( - id: "did:test:abc#didcommmessaging-1", - type: "DIDCommMessaging", - serviceEndpoint: AnyCodable( - dictionaryLiteral: ("uri","https://example.com/endpoint"), ("routing_keys", ["did:example:somemediator#somekey"]), ("accept", ["didcomm/v2", "didcomm/aip2;env=rfc587"])) - ) + let decoded = try PeerDIDHelper().decodedPeerDIDService(did: did, serviceString: service, index: 0).value as! [String: Any] - XCTAssertEqual(expected, try PeerDIDHelper().decodedPeerDIDService(did: did, serviceString: service, index: 0)) + XCTAssertEqual("DIDCommMessaging", decoded["type"] as? String) + XCTAssertEqual("did:test:abc#didcommmessaging-1", decoded["id"] as? String) + XCTAssertEqual("https://example.com/endpoint", decoded["serviceEndpoint"] as? String) + XCTAssertEqual(["did:example:somemediator#somekey"], decoded["routingKeys"] as? [String]) + XCTAssertEqual(["didcomm/v2", "didcomm/aip2;env=rfc587"], decoded["accept"] as? [String]) } -} + + func testDecodeService() throws { + let did = "did:test:abc" + let service = "eyJzIjp7ImEiOlsiZGlkY29tbS92MiIsImRpZGNvbW0vYWlwMjtlbnY9cmZjNTg3Il0sInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9LCJ0IjoiZG0ifQ" + + let decoded = try PeerDIDHelper().decodedPeerDIDService(did: did, serviceString: service, index: 0).value as! [String: Any] + + XCTAssertEqual("DIDCommMessaging", decoded["type"] as? String) + XCTAssertEqual("did:test:abc#didcommmessaging-1", decoded["id"] as? String) + + let serviceEndpoint = decoded["serviceEndpoint"] as! [String: Any] + + XCTAssertEqual(["did:example:somemediator#somekey"], serviceEndpoint["routingKeys"] as? [String]) + XCTAssertEqual(["didcomm/v2", "didcomm/aip2;env=rfc587"], serviceEndpoint["accept"] as? [String]) + } + + func testDecodeServiceWithExtensions() throws { + let did = "did:test:abc" + let service = "eyJleHRlbnNpb24xIjoidGVzdEV4dGVuc2lvbiIsImV4dGVuc2lvbjIiOlsidGVzdEV4dGVuc2lvbiJdLCJleHRlbnNpb24zIjp7InRlc3RLZXkiOiJ0ZXN0In0sInMiOnsiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXSwiciI6WyJkaWQ6ZXhhbXBsZTpzb21lbWVkaWF0b3Ijc29tZWtleSJdLCJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2VuZHBvaW50In0sInQiOiJkbSJ9" + + let decoded = try PeerDIDHelper().decodedPeerDIDService(did: did, serviceString: service, index: 0).value as! [String: Any] -extension DIDDocument.Service: Equatable { - public static func == (lhs: DIDDocument.Service, rhs: DIDDocument.Service) -> Bool { - guard - let lhsS = lhs.serviceEndpoint.value as? [String: Any], - let rhsS = rhs.serviceEndpoint.value as? [String: Any] - else { - return false - } - return lhs.id == rhs.id && - lhs.type == rhs.type && - (lhsS["uri"] as! String) == (rhsS["uri"] as! String) && - (lhsS["accept"] as? [String]) == (rhsS["accept"] as? [String]) && - (lhsS["routing_keys"] as? [String]) == (rhsS["routing_keys"] as? [String]) + XCTAssertEqual("testExtension", decoded["extension1"] as? String) + XCTAssertEqual(["testExtension"], decoded["extension2"] as? [String]) + XCTAssertEqual(["testKey": "test"], decoded["extension3"] as? [String: String]) + XCTAssertEqual("DIDCommMessaging", decoded["type"] as? String) + XCTAssertEqual("did:test:abc#didcommmessaging-1", decoded["id"] as? String) + + let serviceEndpoint = decoded["serviceEndpoint"] as! [String: Any] + + XCTAssertEqual(["did:example:somemediator#somekey"], serviceEndpoint["routingKeys"] as? [String]) + XCTAssertEqual(["didcomm/v2", "didcomm/aip2;env=rfc587"], serviceEndpoint["accept"] as? [String]) } }