-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!(agent): agent separation of concerns
BREAKING CHANGE: This is a refactor, from now on the EdgeAgent will not have any reference with DIDComm and a DIDCommAgent will replace this. EdgeAgent now will scope all the logic that is inherent to it removing any transport layer association, and new agents like DIDCommAgent will scope the EdgeAgent functionalities for a transport layer. With this Pollux also has some significant changes so it is not aggregated to the DIDComm Message. OIDCAgent will take part of OIDC transport layer communication. Signed-off-by: goncalo-frade-iohk <[email protected]>
- Loading branch information
1 parent
d3597e1
commit 65ff99d
Showing
55 changed files
with
1,769 additions
and
622 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,8 @@ public enum CredentialOperationsOptions { | |
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 thid(String) | ||
case presentationRequestId(String) | ||
case custom(key: String, data: Data) // Any custom data. | ||
} | ||
|
||
|
@@ -23,8 +25,18 @@ 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. | ||
@available(*, deprecated, message: "Please use the new method for parseCredential(type: String, credentialPayload: Data, options: [CredentialOperationsOptions])") | ||
func parseCredential(issuedCredential: Message, options: [CredentialOperationsOptions]) async throws -> Credential | ||
|
||
/// Parses an encoded item and returns an object representing the parsed item. | ||
/// - Parameters: | ||
/// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/[email protected]`) | ||
/// - credentialPayload: The encoded credential to parse. | ||
/// - options: Options required for some types of credentials. | ||
/// - Throws: An error if the item cannot be parsed or decoded. | ||
/// - Returns: An object representing the parsed item. | ||
func parseCredential(type: String, credentialPayload: Data, options: [CredentialOperationsOptions]) async throws -> Credential | ||
|
||
/// Restores a previously stored item using the provided restoration identifier and data. | ||
/// - Parameters: | ||
/// - restorationIdentifier: The identifier to use when restoring the item. | ||
|
@@ -39,11 +51,25 @@ public protocol Pollux { | |
/// - options: The options to use when processing the request. | ||
/// - Throws: An error if the request cannot be processed. | ||
/// - Returns: A string representing the result of the request process. | ||
@available(*, deprecated, message: "Please use the new method for processCredentialRequest(type: String, offerPayload: Data, options: [CredentialOperationsOptions])") | ||
func processCredentialRequest( | ||
offerMessage: Message, | ||
options: [CredentialOperationsOptions] | ||
) async throws -> String | ||
|
||
/// Processes a request based on a provided offer message and options. | ||
/// - Parameters: | ||
/// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/[email protected]`) | ||
/// - offerMessage: The offer message that contains the details of the request. | ||
/// - options: The options to use when processing the request. | ||
/// - Throws: An error if the request cannot be processed. | ||
/// - Returns: A string representing the result of the request process. | ||
func processCredentialRequest( | ||
type: String, | ||
offerPayload: Data, | ||
options: [CredentialOperationsOptions] | ||
) async throws -> String | ||
|
||
/// Creates a presentation request for credentials of a specified type, directed to a specific DID, with additional metadata and filtering options. | ||
/// | ||
/// - Parameters: | ||
|
@@ -69,10 +95,25 @@ public protocol Pollux { | |
/// - options: An array of options that influence how the presentation verification is conducted. | ||
/// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). | ||
/// - Throws: An error if there is a problem verifying the presentation. | ||
@available(*, deprecated, message: "Please use the new method for verifyPresentation(type: String, presentationPayload: Data, options: [CredentialOperationsOptions])") | ||
func verifyPresentation( | ||
message: Message, | ||
options: [CredentialOperationsOptions] | ||
) async throws -> Bool | ||
|
||
/// Verifies the validity of a presentation contained within a message, using specified options. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/[email protected]`) | ||
/// - presentationPayload: The message containing the presentation to be verified. | ||
/// - options: An array of options that influence how the presentation verification is conducted. | ||
/// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). | ||
/// - Throws: An error if there is a problem verifying the presentation. | ||
func verifyPresentation( | ||
type: String, | ||
presentationPayload: Data, | ||
options: [CredentialOperationsOptions] | ||
) async throws -> Bool | ||
} | ||
|
||
public extension Pollux { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
282 changes: 282 additions & 0 deletions
282
EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Credentials.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
import Core | ||
import Combine | ||
import Domain | ||
import Foundation | ||
import Logging | ||
import JSONWebToken | ||
|
||
public extension DIDCommAgent { | ||
|
||
/// This function initiates a presentation request for a specific type of credential, specifying the sender's and receiver's DIDs, and any claim filters applicable. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the credential for which the presentation is requested. | ||
/// - fromDID: The decentralized identifier (DID) of the entity initiating the request. | ||
/// - toDID: The decentralized identifier (DID) of the entity to which the request is being sent. | ||
/// - claimFilters: A collection of filters specifying the claims required in the credential. | ||
/// - Returns: The initiated request for presentation. | ||
/// - Throws: EdgeAgentError, if there is a problem initiating the presentation request. | ||
func initiatePresentationRequest( | ||
type: CredentialType, | ||
fromDID: DID, | ||
toDID: DID, | ||
claimFilters: [ClaimFilter] | ||
) async throws -> RequestPresentation { | ||
let rqstStr = try await edgeAgent.initiatePresentationRequest( | ||
type: type, | ||
fromDID: fromDID, | ||
toDID: toDID, | ||
claimFilters: claimFilters | ||
) | ||
let attachment: AttachmentDescriptor | ||
switch type { | ||
case .jwt: | ||
let data = try AttachmentBase64(base64: rqstStr.tryToData().base64URLEncoded()) | ||
attachment = AttachmentDescriptor( | ||
mediaType: "application/json", | ||
data: data, | ||
format: "dif/presentation-exchange/[email protected]" | ||
) | ||
case .anoncred: | ||
let data = try AttachmentBase64(base64: rqstStr.tryToData().base64URLEncoded()) | ||
attachment = AttachmentDescriptor( | ||
mediaType: "application/json", | ||
data: data, | ||
format: "anoncreds/[email protected]" | ||
) | ||
} | ||
|
||
return RequestPresentation( | ||
body: .init( | ||
proofTypes: [ProofTypes( | ||
schema: "", | ||
requiredFields: claimFilters.flatMap(\.paths), | ||
trustIssuers: nil | ||
)] | ||
), | ||
attachments: [attachment], | ||
thid: nil, | ||
from: fromDID, | ||
to: toDID | ||
) | ||
} | ||
|
||
/// This function verifies the presentation contained within a message. | ||
/// | ||
/// - Parameters: | ||
/// - message: The message containing the presentation to be verified. | ||
/// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). | ||
/// - Throws: EdgeAgentError, if there is a problem verifying the presentation. | ||
|
||
func verifyPresentation(message: Message) async throws -> Bool { | ||
do { | ||
let downloader = DownloadDataWithResolver(castor: castor) | ||
guard | ||
let attachment = message.attachments.first, | ||
let requestId = message.thid | ||
else { | ||
throw PolluxError.couldNotFindPresentationInAttachments | ||
} | ||
|
||
let jsonData: Data | ||
switch attachment.data { | ||
case let attchedData as AttachmentBase64: | ||
guard let decoded = Data(fromBase64URL: attchedData.base64) else { | ||
throw CommonError.invalidCoding(message: "Invalid base64 url attachment") | ||
} | ||
jsonData = decoded | ||
case let attchedData as AttachmentJsonData: | ||
jsonData = attchedData.data | ||
default: | ||
throw EdgeAgentError.invalidAttachmentFormat(nil) | ||
} | ||
|
||
guard let format = attachment.format else { | ||
throw EdgeAgentError.invalidAttachmentFormat(nil) | ||
} | ||
|
||
return try await pollux.verifyPresentation( | ||
type: format, | ||
presentationPayload: jsonData, | ||
options: [ | ||
.presentationRequestId(requestId), | ||
.credentialDefinitionDownloader(downloader: downloader), | ||
.schemaDownloader(downloader: downloader) | ||
]) | ||
} catch { | ||
logger.error(error: error) | ||
throw error | ||
} | ||
} | ||
|
||
/// This function parses an issued credential message, stores and returns the verifiable credential. | ||
/// | ||
/// - Parameters: | ||
/// - message: Issue credential Message. | ||
/// - Returns: The parsed verifiable credential. | ||
/// - Throws: EdgeAgentError, if there is a problem parsing the credential. | ||
func processIssuedCredentialMessage(message: IssueCredential3_0) async throws -> Credential { | ||
guard | ||
let linkSecret = try await pluto.getLinkSecret().first().await() | ||
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } | ||
|
||
let restored = try await self.apollo.restoreKey(linkSecret) | ||
guard | ||
let linkSecretString = String(data: restored.raw, encoding: .utf8) | ||
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } | ||
|
||
let downloader = DownloadDataWithResolver(castor: castor) | ||
guard | ||
let attachment = message.attachments.first, | ||
let format = attachment.format | ||
else { | ||
throw PolluxError.unsupportedIssuedMessage | ||
} | ||
|
||
let jsonData: Data | ||
switch attachment.data { | ||
case let attchedData as AttachmentBase64: | ||
guard let decoded = Data(fromBase64URL: attchedData.base64) else { | ||
throw CommonError.invalidCoding(message: "Invalid base64 url attachment") | ||
} | ||
jsonData = decoded | ||
case let attchedData as AttachmentJsonData: | ||
jsonData = attchedData.data | ||
default: | ||
throw EdgeAgentError.invalidAttachmentFormat(nil) | ||
} | ||
|
||
let credential = try await pollux.parseCredential( | ||
type: format, | ||
credentialPayload: jsonData, | ||
options: [ | ||
.linkSecret(id: "", secret: linkSecretString), | ||
.credentialDefinitionDownloader(downloader: downloader), | ||
.schemaDownloader(downloader: downloader) | ||
] | ||
) | ||
|
||
guard let storableCredential = credential.storable else { | ||
return credential | ||
} | ||
try await pluto | ||
.storeCredential(credential: storableCredential) | ||
.first() | ||
.await() | ||
return credential | ||
} | ||
|
||
/// This function prepares a request credential from an offer given the subject DID. | ||
/// | ||
/// - Parameters: | ||
/// - did: Subject DID. | ||
/// - did: Received offer credential. | ||
/// - Returns: Created request credential | ||
/// - Throws: EdgeAgentError, if there is a problem creating the request credential. | ||
func prepareRequestCredentialWithIssuer(did: DID, offer: OfferCredential3_0) async throws -> RequestCredential3_0? { | ||
guard did.method == "prism" else { throw PolluxError.invalidPrismDID } | ||
let didInfo = try await pluto | ||
.getDIDInfo(did: did) | ||
.first() | ||
.await() | ||
|
||
guard let storedPrivateKey = didInfo?.privateKeys.first else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } | ||
|
||
let privateKey = try await apollo.restorePrivateKey(storedPrivateKey) | ||
|
||
guard | ||
let exporting = privateKey.exporting, | ||
let linkSecret = try await pluto.getLinkSecret().first().await() | ||
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } | ||
|
||
let restored = try await self.apollo.restoreKey(linkSecret) | ||
guard | ||
let linkSecretString = String(data: restored.raw, encoding: .utf8) | ||
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } | ||
|
||
let downloader = DownloadDataWithResolver(castor: castor) | ||
guard | ||
let attachment = offer.attachments.first, | ||
let offerFormat = attachment.format | ||
else { | ||
throw PolluxError.unsupportedIssuedMessage | ||
} | ||
|
||
let jsonData: Data | ||
switch attachment.data { | ||
case let attchedData as AttachmentBase64: | ||
guard let decoded = Data(fromBase64URL: attchedData.base64) else { | ||
throw CommonError.invalidCoding(message: "Invalid base64 url attachment") | ||
} | ||
jsonData = decoded | ||
case let attchedData as AttachmentJsonData: | ||
jsonData = attchedData.data | ||
default: | ||
throw EdgeAgentError.invalidAttachmentFormat(nil) | ||
} | ||
let requestString = try await pollux.processCredentialRequest( | ||
type: offerFormat, | ||
offerPayload: jsonData, | ||
options: [ | ||
.exportableKey(exporting), | ||
.subjectDID(did), | ||
.linkSecret(id: did.string, secret: linkSecretString), | ||
.credentialDefinitionDownloader(downloader: downloader), | ||
.schemaDownloader(downloader: downloader) | ||
] | ||
) | ||
|
||
guard | ||
let base64String = requestString.data(using: .utf8)?.base64EncodedString() | ||
else { | ||
throw CommonError.invalidCoding(message: "Could not encode to base64") | ||
} | ||
guard | ||
let offerPiuri = ProtocolTypes(rawValue: offer.type) | ||
else { | ||
throw EdgeAgentError.invalidMessageType( | ||
type: offer.type, | ||
shouldBe: [ | ||
ProtocolTypes.didcommOfferCredential3_0.rawValue | ||
] | ||
) | ||
} | ||
let format: String | ||
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: | ||
throw EdgeAgentError.invalidMessageType( | ||
type: offerFormat, | ||
shouldBe: [ | ||
"prism/jwt", | ||
"anoncreds/[email protected]" | ||
] | ||
) | ||
} | ||
|
||
let type = offerPiuri == .didcommOfferCredential ? | ||
ProtocolTypes.didcommRequestCredential : | ||
ProtocolTypes.didcommRequestCredential3_0 | ||
|
||
let requestCredential = RequestCredential3_0( | ||
body: .init( | ||
goalCode: offer.body.goalCode, | ||
comment: offer.body.comment | ||
), | ||
type: type.rawValue, | ||
attachments: [.init( | ||
data: AttachmentBase64(base64: base64String), | ||
format: format | ||
)], | ||
thid: offer.thid, | ||
from: offer.to, | ||
to: offer.from | ||
) | ||
return requestCredential | ||
} | ||
} |
Oops, something went wrong.