Skip to content

Commit

Permalink
feat!: now services are AnyCodable to conform to DID specifications
Browse files Browse the repository at this point in the history
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
  • Loading branch information
beatt83 committed Apr 17, 2024
1 parent 36afa08 commit 4e82ff4
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions Sources/PeerDID/Models/PeerDID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import DIDCore

public struct PeerDID {

Expand Down Expand Up @@ -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)"
}
Expand Down
128 changes: 40 additions & 88 deletions Sources/PeerDID/PeerDID/PeerDIDHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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)
}
}

Expand All @@ -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)
}
}
22 changes: 16 additions & 6 deletions Tests/PeerDIDTests/CreatePeerDIDAlgo2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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],
Expand All @@ -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],
Expand All @@ -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],
Expand Down
110 changes: 81 additions & 29 deletions Tests/PeerDIDTests/EncodeServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}

0 comments on commit 4e82ff4

Please sign in to comment.