Skip to content

Commit

Permalink
feat!(agent): agent separation of concerns
Browse files Browse the repository at this point in the history
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
goncalo-frade-iohk committed Sep 25, 2024
1 parent d3597e1 commit 80571a6
Show file tree
Hide file tree
Showing 55 changed files with 1,769 additions and 622 deletions.
41 changes: 41 additions & 0 deletions EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand All @@ -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.
Expand All @@ -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:
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,36 @@ public protocol ProvableCredential {
/// - options: The options to use when creating the proof.
/// - Returns: The proof as a `String`.
/// - Throws: If there is an error creating the proof.
@available(*, deprecated, message: "Please use the new method for presentation(type: requestPayload: options:)")
func presentation(request: Message, options: [CredentialOperationsOptions]) throws -> String

/// Creates a presentation proof for a request message with the given options.
///
/// - Parameters:
/// - request: The request message for which the proof needs to be created.
/// - options: The options to use when creating the proof.
/// - Returns: The proof as a `String`.
/// - Throws: If there is an error creating the proof.
func presentation(type: String, requestPayload: Data, options: [CredentialOperationsOptions]) throws -> String

/// Validates if the credential can be used for the given presentation request, using the specified options.
///
/// - Parameters:
/// - request: The presentation request message to be validated against.
/// - options: Options that may influence the validation process.
/// - Returns: A Boolean indicating whether the credential is valid for the presentation (`true`) or not (`false`).
/// - Throws: If there is an error during the validation process.
@available(*, deprecated, message: "Please use the new method for isValidForPresentation(type: requestPayload: options:)")
func isValidForPresentation(request: Message, options: [CredentialOperationsOptions]) throws -> Bool

/// Validates if the credential can be used for the given presentation request, using the specified options.
///
/// - Parameters:
/// - request: The presentation request message to be validated against.
/// - options: Options that may influence the validation process.
/// - Returns: A Boolean indicating whether the credential is valid for the presentation (`true`) or not (`false`).
/// - Throws: If there is an error during the validation process.
func isValidForPresentation(type: String, requestPayload: Data, options: [CredentialOperationsOptions]) throws -> Bool
}

public extension Credential {
Expand Down
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
}
}
Loading

0 comments on commit 80571a6

Please sign in to comment.