From 8882142442aefa4c4fab9e2d11f5bc97ba6f8265 Mon Sep 17 00:00:00 2001 From: Goncalo-FradeIOHK Date: Thu, 24 Aug 2023 10:59:12 +0100 Subject: [PATCH] feat(pollux): add anoncreds issuance As didcomm swift repo has changed this also updates the breaking changes --- .../Apollo/Sources/ApolloImpl+Public.swift | 4 + .../CreateLinkSecretOperation.swift | 8 + .../Builders/Sources/PolluxBuilder.swift | 15 +- AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift | 2 + AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift | 4 + AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift | 12 +- .../Domain/Sources/Models/Errors.swift | 7 + .../Sources/Models/MessageAttachment.swift | 7 + .../DIDCommDIDResolverWrapper.swift | 18 +- .../DIDCommSecretsResolverWrapper.swift | 2 +- .../Mercury/Sources/MercuryImpl.swift | 3 - .../Domain/Providers/LinkSecretProvider.swift | 6 + .../Domain/Stores/LinkSecretStore.swift | 6 + .../CDLinkSecretDAO+LinkSecretProvider.swift | 16 ++ .../DAO/CDLinkSecretDAO+LinkSecretStore.swift | 13 ++ .../DAO/CDLinkSecretDAO.swift | 9 + .../Models/CDLinkSecret+CoreDataClass.swift | 5 + .../CDLinkSecret+CoreDataProperties.swift | 14 ++ .../Pluto/Sources/PlutoImpl+Public.swift | 8 + AtalaPrismSDK/Pluto/Sources/PlutoImpl.swift | 5 + .../PrismPluto.xcdatamodel/contents | 3 + .../AnonCredential+ProvableCredential.swift | 2 + .../AnonCredential+StorableCredential.swift | 49 ++++++ .../Models/AnonCreds/AnonCredential.swift | 158 ++++++++++++++++++ .../CreateAnoncredCredentialRequest.swift | 45 +++++ .../ParseAnoncredsCredentialFromMessage.swift | 8 + .../JWT/CreateJWTCredentialRequest.swift | 79 +++++++++ .../JWT/ParseJWTCredentialFromMessage.swift | 7 + .../Operation/VerifyJWTCredential.swift | 88 ---------- .../PolluxImpl+CredentialRequest.swift | 115 +++++++++++++ .../Sources/PolluxImpl+ParseCredential.swift | 33 ++++ .../Pollux/Sources/PolluxImpl+Public.swift | 113 ------------- AtalaPrismSDK/Pollux/Sources/PolluxImpl.swift | 9 +- .../Pollux/Tests/AnoncredsTests.swift | 111 ++++++++++++ AtalaPrismSDK/Pollux/Tests/JWTTests.swift | 30 ++-- .../Pollux/Tests/Mocks/AnoncredsMocks.swift | 8 + .../Sources/PrismAgent+Credentials.swift | 32 ++-- .../PrismAgent/Sources/PrismAgent.swift | 28 +--- Package.swift | 38 +++-- 39 files changed, 817 insertions(+), 303 deletions(-) create mode 100644 AtalaPrismSDK/Apollo/Sources/Operations/CreateLinkSecretOperation.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/Domain/Providers/LinkSecretProvider.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/Domain/Stores/LinkSecretStore.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretProvider.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretStore.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataClass.swift create mode 100644 AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataProperties.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+ProvableCredential.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+StorableCredential.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/CreateAnoncredCredentialRequest.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/ParseAnoncredsCredentialFromMessage.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/Operation/JWT/ParseJWTCredentialFromMessage.swift delete mode 100644 AtalaPrismSDK/Pollux/Sources/Operation/VerifyJWTCredential.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift create mode 100644 AtalaPrismSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift create mode 100644 AtalaPrismSDK/Pollux/Tests/AnoncredsTests.swift create mode 100644 AtalaPrismSDK/Pollux/Tests/Mocks/AnoncredsMocks.swift diff --git a/AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift b/AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift index 255f740b..01663ed9 100644 --- a/AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift +++ b/AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift @@ -142,4 +142,8 @@ returns random mnemonics nerver returns invalid mnemonics throw ApolloError.invalidKeyType(invalid: keyType, valid: ValidCryptographicTypes.allCases.map(\.rawValue)) } } + + public func createNewLinkSecret() -> String { + CreateLinkSecretOperation().create() + } } diff --git a/AtalaPrismSDK/Apollo/Sources/Operations/CreateLinkSecretOperation.swift b/AtalaPrismSDK/Apollo/Sources/Operations/CreateLinkSecretOperation.swift new file mode 100644 index 00000000..a2effd12 --- /dev/null +++ b/AtalaPrismSDK/Apollo/Sources/Operations/CreateLinkSecretOperation.swift @@ -0,0 +1,8 @@ +import AnoncredsSwift +import Foundation + +struct CreateLinkSecretOperation { + func create() -> String { + Prover().createLinkSecret().getBigNumber() + } +} diff --git a/AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift b/AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift index 452a5e18..961d7124 100644 --- a/AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift +++ b/AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift @@ -2,21 +2,10 @@ import Domain import Pollux public struct PolluxBuilder { - let apollo: Apollo - let castor: Castor - public init( - apollo: Apollo, - castor: Castor - ) { - self.apollo = apollo - self.castor = castor - } + public init() {} public func build() -> Pollux { - PolluxImpl( - apollo: apollo, - castor: castor - ) + PolluxImpl() } } diff --git a/AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift b/AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift index 10102021..55dfe385 100644 --- a/AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift +++ b/AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift @@ -54,4 +54,6 @@ public protocol Apollo { /// - Parameter compressedData: The compressed public key data /// - Returns: The decompressed public key func uncompressedPublicKey(compressedData: Data) -> PublicKey + + func createNewLinkSecret() -> String } diff --git a/AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift b/AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift index 482e1085..4c6a0140 100644 --- a/AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift +++ b/AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift @@ -80,6 +80,8 @@ public protocol Pluto { func storeCredential( credential: StorableCredential ) -> AnyPublisher + + func storeLinkSecret(secret: String) -> AnyPublisher /// Returns all stored PRISM DIDs, along with their associated key pair indices and aliases (if any). /// - Returns: A publisher that emits an array of tuples representing the stored PRISM DIDs, along with their associated key pair indices and aliases (if any). @@ -214,4 +216,6 @@ public protocol Pluto { /// Returns all stored verifiable credentials. /// - Returns: A publisher that emits an array of stored verifiable credentials. func getAllCredentials() -> AnyPublisher<[StorableCredential], Error> + + func getLinkSecret() -> AnyPublisher<[String], Error> } diff --git a/AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift b/AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift index 07240492..63573849 100644 --- a/AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift +++ b/AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift @@ -1,9 +1,13 @@ +import Combine import Foundation /// Options that can be passed into various operations. public enum CredentialOperationsOptions { - case schema(json: Data) // The JSON schema. - case link_secret(id: String, secret: String) // A secret link. + case schema(id: String, json: String) // The JSON schema. + case schemasStream(stream: AnyPublisher<[(id: String, json: String)], Error>) // Stream of schemas, only the first batch is considered + case credentialDefinition(id: String, json: String) // The JSON Credential Definition + case credentialDefinitionsStream(stream: AnyPublisher<[(id: String, json: String)], Error>) // Stream of credential definitions, only the first batch is considered + case linkSecret(id: String, secret: String) // A secret link. case subjectDID(DID) // The decentralized identifier of the subject. case entropy(String) // Entropy for any randomization operation. case signableKey(SignableKey) // A key that can be used for signing. @@ -17,7 +21,7 @@ public protocol Pollux { /// - Parameter data: The encoded item to parse. /// - Throws: An error if the item cannot be parsed or decoded. /// - Returns: An object representing the parsed item. - func parseCredential(data: Data) throws -> Credential + func parseCredential(issuedCredential: Message) throws -> Credential /// Restores a previously stored item using the provided restoration identifier and data. /// - Parameters: @@ -36,7 +40,7 @@ public protocol Pollux { func processCredentialRequest( offerMessage: Message, options: [CredentialOperationsOptions] - ) throws -> String + ) async throws -> String } public extension Pollux { diff --git a/AtalaPrismSDK/Domain/Sources/Models/Errors.swift b/AtalaPrismSDK/Domain/Sources/Models/Errors.swift index 1cceae21..9b1a70b8 100644 --- a/AtalaPrismSDK/Domain/Sources/Models/Errors.swift +++ b/AtalaPrismSDK/Domain/Sources/Models/Errors.swift @@ -648,6 +648,9 @@ public enum PolluxError: KnownPrismError { /// An error case when the offer doesnt present enough information like Domain or Challenge case offerDoesntProvideEnoughInformation + /// An error case when the issued credential message doesnt present enough information or unsupported attachment + case unsupportedIssuedMessage + /// An error case there is missing an `ExportableKey` case requiresExportableKeyForOperation(operation: String) @@ -666,6 +669,8 @@ public enum PolluxError: KnownPrismError { return 55 case .requiresExportableKeyForOperation: return 56 + case .unsupportedIssuedMessage: + return 57 } } @@ -690,6 +695,8 @@ public enum PolluxError: KnownPrismError { return "Offer provided doesnt have challenge or domain in the attachments, or there is no Json Attachment" case .requiresExportableKeyForOperation(let operation): return "Operation \(operation) requires an ExportableKey" + case .unsupportedIssuedMessage: + return "Issue message provided doesnt have a valid attachment" } } } diff --git a/AtalaPrismSDK/Domain/Sources/Models/MessageAttachment.swift b/AtalaPrismSDK/Domain/Sources/Models/MessageAttachment.swift index 9207669a..af4483b9 100644 --- a/AtalaPrismSDK/Domain/Sources/Models/MessageAttachment.swift +++ b/AtalaPrismSDK/Domain/Sources/Models/MessageAttachment.swift @@ -66,6 +66,13 @@ public struct AttachmentBase64: AttachmentData { public init(base64: String) { self.base64 = base64 } + + public func decoded() throws -> Data { + guard let decode = Data(base64Encoded: base64) else { + throw CommonError.invalidCoding(message: "Could not decode base64 message attchment") + } + return decode + } } /// The `AttachmentLinkData` struct represents a DIDComm attachment containing a link to external data. diff --git a/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift b/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift index 2bbd4b96..3a35445f 100644 --- a/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift +++ b/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift @@ -84,7 +84,7 @@ extension DidDoc { id: $0.id.string, type: .jsonWebKey2020, controller: $0.controller.string, - verificationMaterial: .jwk(value: jsonKeys) + verificationMaterial: .jwk(publicKeyJwk: jsonKeys) ) } @@ -93,9 +93,9 @@ extension DidDoc { return service.serviceEndpoint.first.map { Service( id: service.id, - kind: .didCommMessaging( + serviceEndpoint: .didCommMessaging( value: .init( - serviceEndpoint: $0.uri, + uri: $0.uri, accept: $0.accept, routingKeys: $0.routingKeys ) @@ -106,17 +106,17 @@ extension DidDoc { return service.serviceEndpoint.first.map { Service( id: service.id, - kind: .other(value: $0.uri) + serviceEndpoint: .other(value: $0.uri) ) } } } self.init( - did: did, - keyAgreements: keyAgreements, - authentications: authentications, - verificationMethods: verificationMethods, - services: services + id: did, + keyAgreement: keyAgreements, + authentication: authentications, + verificationMethod: verificationMethods, + service: services ) } } diff --git a/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommSecretsResolverWrapper.swift b/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommSecretsResolverWrapper.swift index 41e50b85..6efa7e66 100644 --- a/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommSecretsResolverWrapper.swift +++ b/AtalaPrismSDK/Mercury/Sources/DIDCommWrappers/DIDCommSecretsResolverWrapper.swift @@ -172,7 +172,7 @@ extension DIDCommxSwift.Secret { switch from.secretMaterial { case let .jwk(value): - material = .jwk(value: value) + material = .jwk(privateKeyJwk: value) } self.init( id: from.id, diff --git a/AtalaPrismSDK/Mercury/Sources/MercuryImpl.swift b/AtalaPrismSDK/Mercury/Sources/MercuryImpl.swift index b46f5e2e..2143793b 100644 --- a/AtalaPrismSDK/Mercury/Sources/MercuryImpl.swift +++ b/AtalaPrismSDK/Mercury/Sources/MercuryImpl.swift @@ -39,6 +39,3 @@ public struct MercuryImpl { ) } } - -extension ExampleDidResolver: DidResolver {} -extension ExampleSecretsResolver: SecretsResolver {} diff --git a/AtalaPrismSDK/Pluto/Sources/Domain/Providers/LinkSecretProvider.swift b/AtalaPrismSDK/Pluto/Sources/Domain/Providers/LinkSecretProvider.swift new file mode 100644 index 00000000..89d8fd88 --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/Domain/Providers/LinkSecretProvider.swift @@ -0,0 +1,6 @@ +import Combine +import Foundation + +protocol LinkSecretProvider { + func getAll() -> AnyPublisher<[String], Error> +} diff --git a/AtalaPrismSDK/Pluto/Sources/Domain/Stores/LinkSecretStore.swift b/AtalaPrismSDK/Pluto/Sources/Domain/Stores/LinkSecretStore.swift new file mode 100644 index 00000000..3c20020f --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/Domain/Stores/LinkSecretStore.swift @@ -0,0 +1,6 @@ +import Combine +import Foundation + +protocol LinkSecretStore { + func addLinkSecret(_ linkSecret: String) -> AnyPublisher +} diff --git a/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretProvider.swift b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretProvider.swift new file mode 100644 index 00000000..b25fc455 --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretProvider.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Goncalo Frade IOHK on 22/08/2023. +// +import Combine +import Foundation + +extension CDLinkSecretDAO: LinkSecretProvider { + func getAll() -> AnyPublisher<[String], Error> { + fetchController(context: readContext) + .map { $0.map(\.secret) } + .eraseToAnyPublisher() + } +} diff --git a/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretStore.swift b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretStore.swift new file mode 100644 index 00000000..025d30b5 --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO+LinkSecretStore.swift @@ -0,0 +1,13 @@ +import Combine +import CoreData +import Domain + +extension CDLinkSecretDAO: LinkSecretStore { + func addLinkSecret(_ linkSecret: String) -> AnyPublisher { + updateOrCreate(linkSecret, context: writeContext) { cdobj, context in + cdobj.secret = linkSecret + } + .map { _ in } + .eraseToAnyPublisher() + } +} diff --git a/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO.swift b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO.swift new file mode 100644 index 00000000..1671e128 --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/DAO/CDLinkSecretDAO.swift @@ -0,0 +1,9 @@ +import Combine +import CoreData + +struct CDLinkSecretDAO: CoreDataDAO { + typealias CoreDataObject = CDLinkSecret + let readContext: NSManagedObjectContext + let writeContext: NSManagedObjectContext + let identifierKey: String? = "secret" +} diff --git a/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataClass.swift b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataClass.swift new file mode 100644 index 00000000..2e2a1d8d --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataClass.swift @@ -0,0 +1,5 @@ +import CoreData +import Foundation + +@objc(CDLinkSecret) +class CDLinkSecret: NSManagedObject {} diff --git a/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataProperties.swift b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataProperties.swift new file mode 100644 index 00000000..613f0248 --- /dev/null +++ b/AtalaPrismSDK/Pluto/Sources/PersistentStorage/Models/CDLinkSecret+CoreDataProperties.swift @@ -0,0 +1,14 @@ +import CoreData +import Foundation + +extension CDLinkSecret { + @nonobjc class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDLinkSecret") + } + + @NSManaged var secret: String +} + +extension CDLinkSecret: Identifiable { + var id: String { secret } +} diff --git a/AtalaPrismSDK/Pluto/Sources/PlutoImpl+Public.swift b/AtalaPrismSDK/Pluto/Sources/PlutoImpl+Public.swift index c934e3f1..0a8012c6 100644 --- a/AtalaPrismSDK/Pluto/Sources/PlutoImpl+Public.swift +++ b/AtalaPrismSDK/Pluto/Sources/PlutoImpl+Public.swift @@ -166,4 +166,12 @@ extension PlutoImpl: Pluto { public func getAllCredentials() -> AnyPublisher<[StorableCredential], Error> { credentialsDAO.getAll() } + + public func storeLinkSecret(secret: String) -> AnyPublisher { + linkSecretDao.addLinkSecret(secret) + } + + public func getLinkSecret() -> AnyPublisher<[String], Error> { + linkSecretDao.getAll() + } } diff --git a/AtalaPrismSDK/Pluto/Sources/PlutoImpl.swift b/AtalaPrismSDK/Pluto/Sources/PlutoImpl.swift index 76026f1e..584d52a9 100644 --- a/AtalaPrismSDK/Pluto/Sources/PlutoImpl.swift +++ b/AtalaPrismSDK/Pluto/Sources/PlutoImpl.swift @@ -21,6 +21,7 @@ public struct PlutoImpl { let messageDao: CDMessageDAO let mediatorDAO: CDMediatorDIDDAO let credentialsDAO: CDCredentialDAO + let linkSecretDao: CDLinkSecretDAO private let coreDataManager: CoreDataManager private let keyRestoration: KeyRestoration @@ -64,5 +65,9 @@ public struct PlutoImpl { readContext: manager.mainContext, writeContext: manager.editContext ) + self.linkSecretDao = CDLinkSecretDAO( + readContext: manager.mainContext, + writeContext: manager.editContext + ) } } diff --git a/AtalaPrismSDK/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/PrismPluto.xcdatamodel/contents b/AtalaPrismSDK/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/PrismPluto.xcdatamodel/contents index a21fea9b..91953c1a 100644 --- a/AtalaPrismSDK/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/PrismPluto.xcdatamodel/contents +++ b/AtalaPrismSDK/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/PrismPluto.xcdatamodel/contents @@ -36,6 +36,9 @@ + + + diff --git a/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+ProvableCredential.swift b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+ProvableCredential.swift new file mode 100644 index 00000000..e95b43e9 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+ProvableCredential.swift @@ -0,0 +1,2 @@ +import Domain +import Foundation diff --git a/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+StorableCredential.swift b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+StorableCredential.swift new file mode 100644 index 00000000..a26f6a18 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential+StorableCredential.swift @@ -0,0 +1,49 @@ +import Domain +import Foundation + +extension AnonCredential: StorableCredential { + var storingId: String { + id + } + + var recoveryId: String { + "anon+credential" + } + + var credentialData: Data { + (try? JSONEncoder().encode(self)) ?? Data() + } + + var queryIssuer: String? { + issuer + } + + var querySubject: String? { + subject + } + + var queryCredentialCreated: Date? { + nil + } + + var queryCredentialUpdated: Date? { + nil + } + + var queryCredentialSchema: String? { + schemaId + } + + var queryValidUntil: Date? { + nil + } + + var queryRevoked: Bool? { + nil +// revocationRegistryId != nil + } + + var queryAvailableClaims: [String] { + claims.map(\.key) + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential.swift b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential.swift new file mode 100644 index 00000000..1260faef --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredential.swift @@ -0,0 +1,158 @@ +import AnoncredsSwift +import Domain +import Foundation + +struct AnonCredential { + struct Attribute { + let raw: String + let encoded: String + } + + struct Signature { + struct PrimaryCredential { + let m2: String + let a: String + let e: String + let v: String + } + + struct RevocationCredential { + struct WitnessSignature { + let sigmaI: String + let uI: String + let gI: String + } + let sigma: String + let c: String + let vrPrimePrime: String + let witnessSignature: WitnessSignature + let gI: String + let i: Int + let m2: String + } + + let primaryCredential: PrimaryCredential + let revocationCredential: RevocationCredential? + } + + struct SignatureCorrectnessProof { + let se: String + let c: String + } + + struct RevocationRegistry { + let accum: String + } + + struct Witness { + let omega: String + } + + let schemaId: String + let credentialDefinitionId: String + let values: [String: Attribute] + let signature: Signature + let signatureCorrectnessProof: SignatureCorrectnessProof + let revocationRegistryId: String? + let revocationRegistry: RevocationRegistry? + let witness: Witness? + + func getAnoncred() throws -> AnoncredsSwift.Credential { + let json = try JSONEncoder.didComm().encode(self) + guard let jsonString = String(data: json, encoding: .utf8) else { + throw UnknownError.somethingWentWrongError() + } + return try .init(jsonString: jsonString) + } +} + +extension AnonCredential.Attribute: Codable {} +extension AnonCredential.Signature.PrimaryCredential: Codable { + enum CodingKeys: String, CodingKey { + case m2 = "m_2" + case a + case e + case v + } +} +extension AnonCredential.Signature.RevocationCredential: Codable { + enum CodingKeys: String, CodingKey { + case sigma + case c + case vrPrimePrime = "vr_prime_prime" + case witnessSignature = "witness_signature" + case gI = "g_i" + case i + case m2 = "m2" + } +} +extension AnonCredential.Signature.RevocationCredential.WitnessSignature: Codable { + enum CodingKeys: String, CodingKey { + case sigmaI = "sigma_i" + case uI = "u_i" + case gI = "g_i" + } +} +extension AnonCredential.Signature: Codable { + enum CodingKeys: String, CodingKey { + case primaryCredential = "p_credential" + case revocationCredential = "r_credential" + } +} +extension AnonCredential.RevocationRegistry: Codable {} +extension AnonCredential.SignatureCorrectnessProof: Codable {} +extension AnonCredential.Witness: Codable {} +extension AnonCredential: Codable { + enum CodingKeys: String, CodingKey { + case schemaId = "schema_id" + case credentialDefinitionId = "cred_def_id" + case revocationRegistryId = "rev_reg_id" + case values + case signature + case signatureCorrectnessProof = "signature_correctness_proof" + case revocationRegistry = "rev_reg" + case witness + } +} + +extension AnonCredential: Domain.Credential { + var id: String { + guard + let jsonData = try? JSONEncoder().encode(self), + let identifier = String(data: jsonData.sha256, encoding: .utf8) + else { + assert(true, "This should never happen") + return "" + } + return identifier + } + + var issuer: String { + "" + } + + var subject: String? { + nil + } + + var claims: [Domain.Claim] { + values.map { + .init(key: $0, value: .string($1.raw)) + } + } + + var properties: [String : Any] { + let properties = [ + "schemaId" : schemaId, + "credentialDefinitionId" : credentialDefinitionId, +// "signatureJson" : signatureJson, +// "signatureCorrectnessProofJson" : signatureCorrectnessProofJson, +// "witnessJson" : witnessJson + ] as [String : Any] + +// revocationRegistryId.map { properties["revocationRegistryId"] = $0 } +// revocationRegistryJson.map { properties["revocationRegistryJson"] = $0 } + + return properties + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/CreateAnoncredCredentialRequest.swift b/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/CreateAnoncredCredentialRequest.swift new file mode 100644 index 00000000..32b4a070 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/CreateAnoncredCredentialRequest.swift @@ -0,0 +1,45 @@ +import AnoncredsSwift +import Combine +import Domain +import Foundation + +private struct Schema: Codable { + let name: String + let version: String + let attrNames: [String] + let issuerId: String +} + +struct CreateAnoncredCredentialRequest { + static func create( + did: String, + linkSecret: String, + linkSecretId: String, + offerData: Data, + credentialDefinitions: AnyPublisher<[(id: String, json: String)], Error> + ) async throws -> String { + let linkSecret = try LinkSecret.newFromJson(jsonString: linkSecret) + let offer = try CredentialOffer(jsonString: String(data: offerData, encoding: .utf8)!) + let credDefId = offer.getCredDefId() + + let definition = try await credentialDefinitions + .tryMap { + try $0 + .first { $0.id == credDefId.value } + .map { try CredentialDefinition(jsonString: $0.json) } + } + .first() + .await() + + guard let definition else { throw UnknownError.somethingWentWrongError() } + + return try Prover().createCredentialRequest( + entropy: nil, + proverDid: did, + credDef: definition, + linkSecret: linkSecret, + linkSecretId: linkSecretId, + credentialOffer: offer + ).request.getJson() + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/ParseAnoncredsCredentialFromMessage.swift b/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/ParseAnoncredsCredentialFromMessage.swift new file mode 100644 index 00000000..49ea7b2f --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/ParseAnoncredsCredentialFromMessage.swift @@ -0,0 +1,8 @@ +import AnoncredsSwift +import Foundation + +struct ParseAnoncredsCredentialFromMessage { + static func parse(issuerCredentialData: Data) throws -> AnonCredential { + try JSONDecoder().decode(AnonCredential.self, from: issuerCredentialData) + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift b/AtalaPrismSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift new file mode 100644 index 00000000..9e8ef4cb --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift @@ -0,0 +1,79 @@ +// +// File.swift +// +// +// Created by Goncalo Frade IOHK on 17/08/2023. +// +import Combine +import Domain +import Foundation +import SwiftJWT + +private struct Schema: Codable { + let name: String + let version: String + let attrNames: [String] + let issuerId: String +} + +struct CreateJWTCredentialRequest { + static func create(didStr: String, pem: Data, offerData: Data) throws -> String { + let jsonObject = try JSONSerialization.jsonObject(with: offerData) + guard + let domain = findValue(forKey: "domain", in: jsonObject), + let challenge = findValue(forKey: "challenge", in: jsonObject) + else { throw PolluxError.offerDoesntProvideEnoughInformation } + + let jwt = JWT(claims: ClaimsRequestSignatureJWT( + iss: didStr, + aud: domain, + nonce: challenge, + vp: .init(context: .init([ + "https://www.w3.org/2018/presentations/v1" + ]), type: .init([ + "VerifiablePresentation" + ])) + )) + + return try JWTEncoder(jwtSigner: .es256k(privateKey: pem)).encodeToString(jwt) + } +} + +private struct ClaimsRequestSignatureJWT: Claims { + struct VerifiablePresentation: Codable { + enum CodingKeys: String, CodingKey { + case context = "@context" + case type = "type" + } + + let context: Set + let type: Set + } + + let iss: String + let aud: String + let nonce: String + let vp: VerifiablePresentation +} + + +// TODO: This function is not the most appropriate but will do the job now to change later. +func findValue(forKey key: String, in json: Any) -> String? { + if let dict = json as? [String: Any] { + if let value = dict[key] { + return value as? String + } + for (_, subJson) in dict { + if let foundValue = findValue(forKey: key, in: subJson) { + return foundValue + } + } + } else if let array = json as? [Any] { + for subJson in array { + if let foundValue = findValue(forKey: key, in: subJson) { + return foundValue + } + } + } + return nil +} diff --git a/AtalaPrismSDK/Pollux/Sources/Operation/JWT/ParseJWTCredentialFromMessage.swift b/AtalaPrismSDK/Pollux/Sources/Operation/JWT/ParseJWTCredentialFromMessage.swift new file mode 100644 index 00000000..2a0319ea --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/Operation/JWT/ParseJWTCredentialFromMessage.swift @@ -0,0 +1,7 @@ +import Foundation + +struct ParseJWTCredentialFromMessage { + static func parse(issuerCredentialData: Data) throws -> JWTCredential { + try JWTCredential(data: issuerCredentialData) + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/Operation/VerifyJWTCredential.swift b/AtalaPrismSDK/Pollux/Sources/Operation/VerifyJWTCredential.swift deleted file mode 100644 index d63fc0fa..00000000 --- a/AtalaPrismSDK/Pollux/Sources/Operation/VerifyJWTCredential.swift +++ /dev/null @@ -1,88 +0,0 @@ -import CryptoKit -import Domain -import Foundation -import SwiftJWT - -//struct VerifyJWTCredential { -// let apollo: Apollo -// let castor: Castor -// let jwtString: String -// private let separatedJWTComponents: [String] -// private var headersComponent: String { separatedJWTComponents[0] } -// private var credentialComponent: String { separatedJWTComponents[1] } -// private var signatureComponent: String { separatedJWTComponents[2] } -// -// init(apollo: Apollo, castor: Castor, jwtString: String) throws { -// self.apollo = apollo -// self.castor = castor -// self.jwtString = jwtString -// self.separatedJWTComponents = jwtString.components(separatedBy: ".") -// guard -// self.separatedJWTComponents.count == 3 -// else { throw PolluxError.invalidJWTString } -// } -// -// func compute() async throws -> Bool { -// let document = try await getDIDDocument() -// let pemKeys = try getAuthenticationPublicKeyPem(document: document) -// var result = false -// try pemKeys.forEach { -// guard -// !result, -// let pemData = $0.data(using: .utf8) -// else { return } -// let verifier = JWTVerifier.es256k(publicKey: pemData) -// let decoder = JWTDecoder(jwtVerifier: verifier) -// do { -// _ = try decoder.decode(JWT.self, fromString: jwtString) -// result = true -// } catch let error as JWTError { -// switch error { -// case .invalidUTF8Data, .invalidJWTString, .invalidPrivateKey, .missingPEMHeaders: -// throw error -// default: -// break -// } -// } -// } -// return result -// } -// -// private func getDIDDocument() async throws -> DIDDocument { -// let did = try getJWTCredential().makeVerifiableCredential().issuer -// let document = try await castor.resolveDID(did: did) -// return document -// } -// -// private func getJWTCredential() throws -> JWTCredential { -// guard -// let base64Data = Data(fromBase64URL: credentialComponent), -// let jsonString = String(data: base64Data, encoding: .utf8) -// else { throw PolluxError.invalidJWTString } -// -// guard -// let dataValue = jsonString.data(using: .utf8) -// else { throw PolluxError.invalidCredentialError } -// return try JWTCredential( -// id: jwtString, -// fromJson: dataValue, -// decoder: JSONDecoder() -// ) -// } -// -// private func getAuthenticationPublicKeyPem(document: DIDDocument) throws -> [String] { -// return document.authenticate -// .map { $0.publicKey.flatMap { keyDataToPEMString($0) } } -// .compactMap { $0 } -// } -//} - -//public func keyDataToPEMString(_ keyData: PublicKey) -> String? { -// let keyBase64 = keyData.value.base64EncodedString() -// let pemString = """ -// -----BEGIN PUBLIC KEY----- -// \(keyBase64) -// -----END PUBLIC KEY----- -// """ -// return pemString -//} diff --git a/AtalaPrismSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift new file mode 100644 index 00000000..d693ef13 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift @@ -0,0 +1,115 @@ +// +// PolluxImpl+CredentialRequest.swift +// +// +// Created by Goncalo Frade IOHK on 16/08/2023. +// + +import Domain +import Foundation +import SwiftJWT + +extension PolluxImpl { + + private enum SupportedCredential: String { + case jwt + case anoncred + } + + public func processCredentialRequest( + offerMessage: Message, + options: [CredentialOperationsOptions] + ) async throws -> String { + guard let offerAttachment = offerMessage.attachments.first else { + throw PolluxError.offerDoesntProvideEnoughInformation + } + + switch offerAttachment.mediaType { + case "jwt", "", .none: + switch offerAttachment.data { + case let json as AttachmentJsonData: + return try processJWTCredentialRequest(offerData: json.data, options: options) + default: + throw PolluxError.offerDoesntProvideEnoughInformation + } + case "anoncreds": + switch offerAttachment.data { + case let json as AttachmentJsonData: + return try await processAnoncredsCredentialRequest(offerData: json.data, options: options) + default: + throw PolluxError.offerDoesntProvideEnoughInformation + } + default: + break + } + throw PolluxError.invalidCredentialError + } + + private func processJWTCredentialRequest(offerData: Data, options: [CredentialOperationsOptions]) throws -> String { + guard + let subjectDIDOption = options.first(where: { + if case .subjectDID = $0 { return true } + return false + }), + case let CredentialOperationsOptions.subjectDID(did) = subjectDIDOption + else { + throw PolluxError.invalidPrismDID + } + + guard + let exportableKeyOption = options.first(where: { + if case .exportableKey = $0 { return true } + return false + }), + case let CredentialOperationsOptions.exportableKey(exportableKey) = exportableKeyOption, + let pemData = exportableKey.pem.data(using: .utf8) + else { + throw PolluxError.requiresExportableKeyForOperation(operation: "Create Credential Request") + } + + return try CreateJWTCredentialRequest.create(didStr: did.string, pem: pemData, offerData: offerData) + } + + private func processAnoncredsCredentialRequest( + offerData: Data, + options: [CredentialOperationsOptions] + ) async throws -> String { + guard + let subjectDIDOption = options.first(where: { + if case .subjectDID = $0 { return true } + return false + }), + case let CredentialOperationsOptions.subjectDID(did) = subjectDIDOption + else { + throw PolluxError.invalidPrismDID + } + + guard + let linkSecretOption = options.first(where: { + if case .linkSecret = $0 { return true } + return false + }), + case let CredentialOperationsOptions.linkSecret(linkSecretId, secret: linkSecret) = linkSecretOption + else { + throw PolluxError.invalidPrismDID + } + + guard + let credentialDefinitonsStreamOption = options.first(where: { + if case .credentialDefinitionsStream = $0 { return true } + return false + }), + case let CredentialOperationsOptions.credentialDefinitionsStream(stream) = credentialDefinitonsStreamOption + else { + throw PolluxError.invalidPrismDID + } + + return try await CreateAnoncredCredentialRequest.create( + did: did.string, + linkSecret: linkSecret, + linkSecretId: linkSecretId, + offerData: offerData, + credentialDefinitions: stream + ) + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift new file mode 100644 index 00000000..cff68826 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift @@ -0,0 +1,33 @@ +import Domain +import Foundation + +extension PolluxImpl { + public func parseCredential(issuedCredential: Message) throws -> Credential { + guard let issuedAttachment = issuedCredential.attachments.first else { + throw PolluxError.unsupportedIssuedMessage + } + + switch issuedAttachment.mediaType { + case "jwt", "", "prism/jwt", .none: + switch issuedAttachment.data { + case let json as AttachmentJsonData: + return try ParseJWTCredentialFromMessage.parse(issuerCredentialData: json.data) + case let base64 as AttachmentBase64: + return try ParseJWTCredentialFromMessage.parse(issuerCredentialData: try base64.decoded()) + default: + throw PolluxError.unsupportedIssuedMessage + } + case "anoncreds", "prism/anoncreds": + switch issuedAttachment.data { + case let json as AttachmentJsonData: + return try ParseAnoncredsCredentialFromMessage.parse(issuerCredentialData: json.data) + case let base64 as AttachmentBase64: + return try ParseAnoncredsCredentialFromMessage.parse(issuerCredentialData: try base64.decoded()) + default: + throw PolluxError.unsupportedIssuedMessage + } + default: + throw PolluxError.invalidCredentialError + } + } +} diff --git a/AtalaPrismSDK/Pollux/Sources/PolluxImpl+Public.swift b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+Public.swift index ff287c24..ff496d4c 100644 --- a/AtalaPrismSDK/Pollux/Sources/PolluxImpl+Public.swift +++ b/AtalaPrismSDK/Pollux/Sources/PolluxImpl+Public.swift @@ -1,21 +1,8 @@ import Core import Domain import Foundation -import SwiftJWT extension PolluxImpl: Pollux { - public func parseCredential(data: Data) throws -> Credential { - if - let jwtCredential = try? JWTCredential(data: data) - { - return jwtCredential - } else if let w3cCredential = try? JSONDecoder().decode(W3CVerifiableCredential.self, from: data) { - return w3cCredential - } - - throw PolluxError.invalidCredentialError - } - public func restoreCredential(restorationIdentifier: String, credentialData: Data) throws -> Credential { switch restorationIdentifier { case "jwt+credential": @@ -26,104 +13,4 @@ extension PolluxImpl: Pollux { throw PolluxError.invalidCredentialError } } - - public func processCredentialRequest( - offerMessage: Message, - options: [CredentialOperationsOptions] - ) throws -> String { - guard - let subjectDIDOption = options.first(where: { - if case .subjectDID = $0 { return true } - return false - }), - case let CredentialOperationsOptions.subjectDID(did) = subjectDIDOption - else { - throw PolluxError.invalidPrismDID - } - - guard - let exportableKeyOption = options.first(where: { - if case .exportableKey = $0 { return true } - return false - }), - case let CredentialOperationsOptions.exportableKey(exportableKey) = exportableKeyOption, - let pemData = exportableKey.pem.data(using: .utf8) - else { - throw PolluxError.requiresExportableKeyForOperation(operation: "Create Credential Request") - } - - guard let offerData = offerMessage - .attachments - .first - .flatMap({ - switch $0.data { - case let json as AttachmentJsonData: - return json.data - default: - return nil - } - }) - else { throw PolluxError.offerDoesntProvideEnoughInformation } - - let jsonObject = try JSONSerialization.jsonObject(with: offerData) - guard - let domain = findValue(forKey: "domain", in: jsonObject), - let challenge = findValue(forKey: "challenge", in: jsonObject) - else { throw PolluxError.offerDoesntProvideEnoughInformation } - - let jwt = JWT(claims: ClaimsRequestSignatureJWT( - iss: did.string, - aud: domain, - nonce: challenge, - vp: .init(context: .init([ - "https://www.w3.org/2018/presentations/v1" - ]), type: .init([ - "VerifiablePresentation" - ])) - )) - - let jwtString = try JWTEncoder(jwtSigner: .es256k(privateKey: pemData)).encodeToString(jwt) - - return jwtString - } -} - -// TODO: This function is not the most appropriate but will do the job now to change later. -func findValue(forKey key: String, in json: Any) -> String? { - if let dict = json as? [String: Any] { - if let value = dict[key] { - return value as? String - } - for (_, subJson) in dict { - if let foundValue = findValue(forKey: key, in: subJson) { - return foundValue - } - } - } else if let array = json as? [Any] { - for subJson in array { - if let foundValue = findValue(forKey: key, in: subJson) { - return foundValue - } - } - } - return nil } - -private struct ClaimsRequestSignatureJWT: Claims { - struct VerifiablePresentation: Codable { - enum CodingKeys: String, CodingKey { - case context = "@context" - case type = "type" - } - - let context: Set - let type: Set - } - - let iss: String - let aud: String - let nonce: String - let vp: VerifiablePresentation -} - - diff --git a/AtalaPrismSDK/Pollux/Sources/PolluxImpl.swift b/AtalaPrismSDK/Pollux/Sources/PolluxImpl.swift index 100d8b5d..88b877cf 100644 --- a/AtalaPrismSDK/Pollux/Sources/PolluxImpl.swift +++ b/AtalaPrismSDK/Pollux/Sources/PolluxImpl.swift @@ -1,11 +1,6 @@ +import Combine import Domain public struct PolluxImpl { - let apollo: Apollo - let castor: Castor - - public init(apollo: Apollo, castor: Castor) { - self.apollo = apollo - self.castor = castor - } + public init() {} } diff --git a/AtalaPrismSDK/Pollux/Tests/AnoncredsTests.swift b/AtalaPrismSDK/Pollux/Tests/AnoncredsTests.swift new file mode 100644 index 00000000..69dad591 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Tests/AnoncredsTests.swift @@ -0,0 +1,111 @@ +// +// AnoncredsTests.swift +// +// +// Created by Goncalo Frade IOHK on 22/08/2023. +// + +@testable import Pollux +import XCTest + +final class AnoncredsTests: XCTestCase { + + let anoncredJson = """ +{ +"schema_id": "test schema id", +"cred_def_id": "test cred definition id", +"rev_reg_id": null, +"values": { + "first_name": { + "raw": "Alice", + "encoded": "113...335" + }, + "last_name": { + "raw": "Garcia", + "encoded": "532...452" + }, + "birthdate_dateint": { + "raw": "19981119", + "encoded": "19981119" + } +}, +"signature": { + "p_credential": { + "m_2": "992...312", + "a": "548...252", + "e": "259...199", + "v": "977...597" + }, + "r_credential": null +}, +"signature_correctness_proof": { + "se": "898...935", + "c": "935...598" +}, +"rev_reg": null, +"witness": null +} +""".data(using: .utf8)! + + let anoncredWithRevocationJson = """ +{ +"schema_id": "test schema id", +"cred_def_id": "test cred definition id", +"rev_reg_id": "revocation registry id", +"values": { + "first_name": { + "raw": "Alice", + "encoded": "113...335" + }, + "last_name": { + "raw": "Garcia", + "encoded": "532...452" + }, + "birthdate_dateint": { + "raw": "19981119", + "encoded": "19981119" + } +}, +"signature": { + "p_credential": { + "m_2": "992...312", + "a": "548...252", + "e": "259...199", + "v": "977...597" + }, + "r_credential": { + "sigma": "1 14C...8A8", + "c": "12A...BB6", + "vr_prime_prime": "0F3...FC4", + "witness_signature": { + "sigma_i": "1 1D72...000", + "u_i": "1 0B3...000", + "g_i": "1 10D...8A8" + }, + "g_i": "1 10D7...8A8", + "i": 1, + "m2": "FDC...283" + } +}, +"signature_correctness_proof": { + "se": "898...935", + "c": "935...598" +}, +"rev_reg": { + "accum": "21 118...1FB" +}, +"witness": { + "omega": "21 124...AC8" +} +} +""".data(using: .utf8)! + + func testDecodeAnoncred() throws { + let decoder = JSONDecoder() + XCTAssertNoThrow(try decoder.decode(AnonCredential.self, from: anoncredJson)) + } + + func testDecodeAnoncredWithRevocation() throws { + XCTAssertNoThrow(try JSONDecoder().decode(AnonCredential.self, from: anoncredWithRevocationJson)) + } +} diff --git a/AtalaPrismSDK/Pollux/Tests/JWTTests.swift b/AtalaPrismSDK/Pollux/Tests/JWTTests.swift index 7b876b67..4d959c72 100644 --- a/AtalaPrismSDK/Pollux/Tests/JWTTests.swift +++ b/AtalaPrismSDK/Pollux/Tests/JWTTests.swift @@ -21,7 +21,7 @@ final class JWTTests: XCTestCase { XCTAssertEqual(credential.id, validJWTString) } - func testJWTCreateCredentialRequest() throws { + func testJWTCreateCredentialRequest() async throws { let offerCredentialMessage = OfferCredential( id: "test1", body: .init( @@ -43,7 +43,7 @@ final class JWTTests: XCTestCase { to: DID.init(method: "test", methodId: "123") ) - let pollux = PolluxImpl(apollo: apollo, castor: castor) + let pollux = PolluxImpl() let privKey = try apollo.createPrivateKey(parameters: [ KeyProperties.type.rawValue: "EC", KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue, @@ -53,7 +53,7 @@ final class JWTTests: XCTestCase { let pubKey = privKey.publicKey() let subjectPrismDID = try castor.createPrismDID(masterPublicKey: pubKey, services: []) - let requestString = try pollux.processCredentialRequest( + let requestString = try await pollux.processCredentialRequest( offerMessage: offerCredentialMessage.makeMessage(), options: [ .subjectDID(subjectPrismDID), @@ -66,7 +66,7 @@ final class JWTTests: XCTestCase { XCTAssertEqual(validJWTString, requestString) } - func testJWTCreateCredentialRequestErrorMissingDomain() throws { + func testJWTCreateCredentialRequestErrorMissingDomain() async throws { let offerCredentialMessage = OfferCredential( id: "test1", body: .init( @@ -88,7 +88,7 @@ final class JWTTests: XCTestCase { to: DID.init(method: "test", methodId: "123") ) - let pollux = PolluxImpl(apollo: apollo, castor: castor) + let pollux = PolluxImpl() let privKey = try apollo.createPrivateKey(parameters: [ KeyProperties.type.rawValue: "EC", KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue, @@ -97,14 +97,18 @@ final class JWTTests: XCTestCase { let pubKey = privKey.publicKey() let subjectPrismDID = try castor.createPrismDID(masterPublicKey: pubKey, services: []) - - XCTAssertThrowsError(try pollux.processCredentialRequest( - offerMessage: offerCredentialMessage.makeMessage(), - options: [ - .subjectDID(subjectPrismDID), - .exportableKey(privKey) - ] - )) + do { + try await pollux.processCredentialRequest( + offerMessage: offerCredentialMessage.makeMessage(), + options: [ + .subjectDID(subjectPrismDID), + .exportableKey(privKey) + ] + ) + XCTFail("Should throw an error") + } catch { + XCTAssertThrowsError(error) + } } func testJWTPresentationSignature() async throws { diff --git a/AtalaPrismSDK/Pollux/Tests/Mocks/AnoncredsMocks.swift b/AtalaPrismSDK/Pollux/Tests/Mocks/AnoncredsMocks.swift new file mode 100644 index 00000000..c86748b2 --- /dev/null +++ b/AtalaPrismSDK/Pollux/Tests/Mocks/AnoncredsMocks.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Goncalo Frade IOHK on 22/08/2023. +// + +import Foundation diff --git a/AtalaPrismSDK/PrismAgent/Sources/PrismAgent+Credentials.swift b/AtalaPrismSDK/PrismAgent/Sources/PrismAgent+Credentials.swift index 87218ddf..c935b79a 100644 --- a/AtalaPrismSDK/PrismAgent/Sources/PrismAgent+Credentials.swift +++ b/AtalaPrismSDK/PrismAgent/Sources/PrismAgent+Credentials.swift @@ -30,17 +30,7 @@ public extension PrismAgent { /// - Returns: The parsed verifiable credential. /// - Throws: PrismAgentError, if there is a problem parsing the credential. func processIssuedCredentialMessage(message: IssueCredential) async throws -> Credential { - guard - let attachment = message.attachments.first?.data as? AttachmentBase64, - let data = Data(fromBase64URL: attachment.base64) - else { - throw UnknownError.somethingWentWrongError( - customMessage: "Cannot find attachment base64 in message", - underlyingErrors: nil - ) - } - - let credential = try pollux.parseCredential(data: data) + let credential = try pollux.parseCredential(issuedCredential: message.makeMessage()) guard let storableCredential = credential.storable else { return credential @@ -69,14 +59,18 @@ public extension PrismAgent { guard let privateKey = didInfo?.privateKeys.first else { throw PrismAgentError.cannotFindDIDKeyPairIndex } guard - let exporting = privateKey.exporting + let exporting = privateKey.exporting, + let linkSecret = try await pluto.getLinkSecret().first().await().first else { throw PrismAgentError.cannotFindDIDKeyPairIndex } - let requestString = try pollux.processCredentialRequest( + let requestString = try await pollux.processCredentialRequest( offerMessage: try offer.makeMessage(), options: [ .exportableKey(exporting), - .subjectDID(did) + .subjectDID(did), + .linkSecret(id: did.string, secret: linkSecret), + .credentialDefinitionsStream(stream: credentialDefinitions), + .schemasStream(stream: schemas) ] ) @@ -100,3 +94,13 @@ public extension PrismAgent { return requestCredential } } + +// TODO: Just while we dont have API for this +extension PrismAgent { + var credentialDefinitions: AnyPublisher<[(id: String, json: String)], Error> { + Just([(id: String, json: String)]()).tryMap { $0 }.eraseToAnyPublisher() + } + var schemas: AnyPublisher<[(id: String, json: String)], Error> { + Just([(id: String, json: String)]()).tryMap { $0 }.eraseToAnyPublisher() + } +} diff --git a/AtalaPrismSDK/PrismAgent/Sources/PrismAgent.swift b/AtalaPrismSDK/PrismAgent/Sources/PrismAgent.swift index 68329140..010bba53 100644 --- a/AtalaPrismSDK/PrismAgent/Sources/PrismAgent.swift +++ b/AtalaPrismSDK/PrismAgent/Sources/PrismAgent.swift @@ -99,7 +99,7 @@ public class PrismAgent { let apollo = ApolloBuilder().build() let castor = CastorBuilder(apollo: apollo).build() let pluto = PlutoBuilder(keyRestoration: apollo).build() - let pollux = PolluxBuilder(apollo: apollo, castor: castor).build() + let pollux = PolluxBuilder().build() let mercury = MercuryBuilder( apollo: apollo, castor: castor, @@ -161,6 +161,7 @@ public class PrismAgent { let hostDID = try await createNewPeerDID(updateMediator: false) try await connectionManager.registerMediator(hostDID: hostDID) } + try await firstLinkSecretSetup() state = .running logger.info(message: "Mediation Achieved", metadata: [ .publicMetadata(key: "Routing DID", value: mediatorRoutingDID?.string ?? "") @@ -185,25 +186,12 @@ public class PrismAgent { state = .stoped logger.info(message: "Agent not running") } -// -// // TODO: This is to be deleted in the future. For now it helps with issue credentials logic -// public func issueCredentialProtocol() { -// startFetchingMessages() -// Task { -// do { -// for try await offer in handleReceivedMessagesEvents() -// .drop(while: { (try? OfferCredential(fromMessage: $0)) != nil }) -// .values -// { -// if let issueProtocol = try? IssueCredentialProtocol(offer, connector: connectionManager) { -// try? await issueProtocol.nextStage() -// } -// } -// } catch { -// print(error.localizedDescription) -// } -// } -// } + + private func firstLinkSecretSetup() async throws { + if try await pluto.getLinkSecret().first().await().first == nil { + try await pluto.storeLinkSecret(secret: apollo.createNewLinkSecret()).first().await() + } + } } extension DID { diff --git a/Package.swift b/Package.swift index 94f440aa..efac6f0f 100644 --- a/Package.swift +++ b/Package.swift @@ -67,7 +67,8 @@ let package = Package( .package(url: "git@github.com:input-output-hk/atala-prism-didcomm-swift.git", from: "0.3.6"), .package(url: "git@github.com:swift-libp2p/swift-multibase.git", from: "0.0.1"), .package(url: "git@github.com:GigaBitcoin/secp256k1.swift.git", exact: "0.10.0"), - .package(url: "git@github.com:goncalo-frade-iohk/Swift-JWT.git", from: "4.1.3") + .package(url: "git@github.com:goncalo-frade-iohk/Swift-JWT.git", from: "4.1.3"), + .package(url: "git@github.com:input-output-hk/anoncreds-rs.git", from: "0.1.0") ], targets: [ .target( @@ -97,7 +98,8 @@ let package = Package( dependencies: [ "Domain", "Core", - .product(name: "secp256k1", package: "secp256k1.swift") + .product(name: "secp256k1", package: "secp256k1.swift"), + .product(name: "AnoncredsSwift", package: "anoncreds-rs") ], path: "AtalaPrismSDK/Apollo/Sources" ), @@ -127,7 +129,8 @@ let package = Package( dependencies: [ "Domain", "Core", - .product(name: "SwiftJWT", package: "Swift-JWT") + .product(name: "SwiftJWT", package: "Swift-JWT"), + .product(name: "AnoncredsSwift", package: "anoncreds-rs") ], path: "AtalaPrismSDK/Pollux/Sources" ), @@ -152,7 +155,10 @@ let package = Package( ), .target( name: "Pluto", - dependencies: ["Domain", "Core"], + dependencies: [ + "Domain", + "Core" + ], path: "AtalaPrismSDK/Pluto/Sources", resources: [.process("Resources/PrismPluto.xcdatamodeld")] ), @@ -176,21 +182,21 @@ let package = Package( ], path: "AtalaPrismSDK/PrismAgent/Sources" ), - .testTarget( - name: "PrismAgentTests", - dependencies: ["PrismAgent", "Core"], - path: "AtalaPrismSDK/PrismAgent/Tests" - ), +// .testTarget( +// name: "PrismAgentTests", +// dependencies: ["PrismAgent", "Core"], +// path: "AtalaPrismSDK/PrismAgent/Tests" +// ), .target( name: "Authenticate", dependencies: ["Domain", "Builders", "Core"], path: "AtalaPrismSDK/Authenticate/Sources" ), - .testTarget( - name: "AuthenticateTests", - dependencies: ["Authenticate"], - path: "AtalaPrismSDK/Authenticate/Tests" - ), +// .testTarget( +// name: "AuthenticateTests", +// dependencies: ["Authenticate"], +// path: "AtalaPrismSDK/Authenticate/Tests" +// ), // Internal core components (ex: logging) not public distributed .target( name: "Core", @@ -199,10 +205,6 @@ let package = Package( .product(name: "Logging", package: "swift-log") ], path: "Core/Sources" -// Unfortunately this doesnt seem to work properly right now. -// plugins: [ -// .plugin(name: "SwiftLintPlugin", package: "SwiftLint") -// ] ) ] )