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(agent): agent separation of concerns #159

Merged
merged 1 commit into from
Sep 26, 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
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/credential@v1.0`)
/// - 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/credential-offer@v1.0`)
/// - 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/credential-presentation@v1.0`)
/// - 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/definitions@v1.0"
)
case .anoncred:
let data = try AttachmentBase64(base64: rqstStr.tryToData().base64URLEncoded())
attachment = AttachmentDescriptor(
mediaType: "application/json",
data: data,
format: "anoncreds/proof-request@v1.0"
)
}

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/credential-offer@v1.0":
format = "anoncreds/credential-request@v1.0"
default:
throw EdgeAgentError.invalidMessageType(
type: offerFormat,
shouldBe: [
"prism/jwt",
"anoncreds/credential-offer@v1.0"
]
)
}

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
Loading