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(pollux): add support for sd-jwt #145

Merged
merged 1 commit into from
Jun 11, 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
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
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
Loading