Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: now services are AnyCodable to conform to DID specifications #8

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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])
}
}
Loading