Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pollux): add credential abstraction #94

Merged
merged 1 commit into from
Aug 7, 2023
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
4 changes: 2 additions & 2 deletions AtalaPrismSDK/Domain/Sources/BBs/Pluto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public protocol Pluto {
/// - Parameter credential: The credential to store.
/// - Returns: A publisher that completes when the operation finishes.
func storeCredential(
credential: VerifiableCredential
credential: StorableCredential
) -> AnyPublisher<Void, Error>

/// Returns all stored PRISM DIDs, along with their associated key pair indices and aliases (if any).
Expand Down Expand Up @@ -213,5 +213,5 @@ public protocol Pluto {

/// Returns all stored verifiable credentials.
/// - Returns: A publisher that emits an array of stored verifiable credentials.
func getAllCredentials() -> AnyPublisher<[VerifiableCredential], Error>
func getAllCredentials() -> AnyPublisher<[StorableCredential], Error>
}
55 changes: 49 additions & 6 deletions AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,53 @@
import Foundation

/// The Pollux protocol defines the set of credential operations that are used in the Atala PRISM architecture.
/// 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 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.
case exportableKey(ExportableKey) // A key that can be exported.
case custom(key: String, data: Data) // Any custom data.
}

/// The Pollux protocol defines a set of operations that are used in the Atala PRISM architecture.
public protocol Pollux {
/// Parses a JWT-encoded verifiable credential and returns a `VerifiableCredential` object representing the credential.
/// - Parameter jwtString: The JWT-encoded credential to parse.
/// - Throws: An error if the JWT cannot be parsed or decoded, or if the resulting verifiable credential is invalid.
/// - Returns: A `VerifiableCredential` object representing the parsed credential.
func parseVerifiableCredential(jwtString: String) throws -> VerifiableCredential
/// Parses an encoded item and returns an object representing the parsed item.
/// - 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

/// Restores a previously stored item using the provided restoration identifier and data.
/// - Parameters:
/// - restorationIdentifier: The identifier to use when restoring the item.
/// - credentialData: The data representing the stored item.
/// - Throws: An error if the item cannot be restored.
/// - Returns: An object representing the restored item.
func restoreCredential(restorationIdentifier: String, credentialData: Data) throws -> Credential

/// Processes a request based on a provided offer message and options.
/// - Parameters:
/// - 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(
offerMessage: Message,
options: [CredentialOperationsOptions]
) throws -> String
}

public extension Pollux {
/// Restores a previously stored item using a `StorableCredential` instance.
/// - Parameter storedCredential: The `StorableCredential` instance representing the stored item.
/// - Throws: An error if the item cannot be restored.
/// - Returns: An object representing the restored item.
func restoreCredential(storedCredential: StorableCredential) throws -> Credential {
try restoreCredential(
restorationIdentifier: storedCredential.recoveryId,
credentialData: storedCredential.credentialData
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation

/// `Claim` represents a claim in a credential. Claims are the attributes associated with the subject of a credential.
public struct Claim {
/// `ClaimType` represents the type of value a `Claim` can hold. This can be a string, boolean, date, data, or number.
public enum ClaimType: Comparable {
case string(String)
case bool(Bool)
case date(Date)
case data(Data)
case number(Double)

/// Provides comparison between two `ClaimType` instances based on their inherent values.
/// - Note: This comparison is only valid for `string`, `date`, and `number` claim types. For other types, it will always return `false`.
public static func < (lhs: Claim.ClaimType, rhs: Claim.ClaimType) -> Bool {
switch (lhs, rhs) {
case let (.string(str1), .string(str2)):
return str1 < str2
case let (.date(date1), .date(date2)):
return date1 < date2
case let (.number(number1), .number(number2)):
return number1 < number2
default:
return false
}
}
}

/// The key of the claim.
public let key: String
/// The value of the claim, represented as a `ClaimType`.
public let value: ClaimType

/// Initializes a new `Claim` with the provided key and value.
/// - Parameters:
/// - key: The key of the claim.
/// - value: The value of the claim, represented as a `ClaimType`.
public init(key: String, value: ClaimType) {
self.key = key
self.value = value
}

/// Provides the `Claim` value as a string.
/// - Returns: A string representation of the claim's value.
public func getValueAsString() -> String {
switch value {
case .string(let string):
return string
case .bool(let bool):
return "\(bool)"
case .date(let date):
return date.formatted()
case .data(let data):
return data.base64EncodedString()
case .number(let double):
return "\(double)"
}
}
}

/// `Credential` is a protocol that defines the fundamental attributes of a credential.
public protocol Credential {
/// The identifier of the credential.
var id: String { get }
/// The issuer of the credential.
var issuer: String { get }
/// The subject of the credential.
var subject: String? { get }
/// The claims included in the credential.
var claims: [Claim] { get }
/// Additional properties associated with the credential.
var properties: [String: Any] { get }
}

public extension Credential {
/// A Boolean value indicating whether the credential is Codable.
var isCodable: Bool { self is Codable }

/// Returns the Codable representation of the credential.
var codable: Codable? { self as? Codable }
curtis-h marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

/// `ProofableCredential` is a protocol that adds provability functionality to a credential.
public protocol ProvableCredential {
/// 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(request: Message, options: [CredentialOperationsOptions]) throws -> String
}

public extension Credential {
/// A Boolean value indicating whether the credential is proofable.
var isProofable: Bool { self is ProvableCredential }

/// Returns the proofable representation of the credential.
var proof: ProvableCredential? { self as? ProvableCredential }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

/// `StorableCredential` is a protocol that provides storable properties for a credential.
/// These properties are typically used for indexing or querying the credential in a storage system.
public protocol StorableCredential {
/// The identifier to be used for storing this credential.
var storingId: String { get }
/// The identifier to be used for recovering this credential.
var recoveryId: String { get }
/// The data representation of this credential.
var credentialData: Data { get }
/// The issuer that can be used as a query parameter.
var queryIssuer: String? { get }
/// The subject that can be used as a query parameter.
var querySubject: String? { get }
/// The date the credential was created that can be used as a query parameter.
var queryCredentialCreated: Date? { get }
/// The date the credential was last updated that can be used as a query parameter.
var queryCredentialUpdated: Date? { get }
/// The schema of the credential that can be used as a query parameter.
var queryCredentialSchema: String? { get }
/// The date until which the credential is valid that can be used as a query parameter.
var queryValidUntil: Date? { get }
/// The revocation status of the credential that can be used as a query parameter.
var queryRevoked: Bool? { get }
/// The available claims in the credential that can be used as a query parameter.
var queryAvailableClaims: [String] { get }
}

public extension Credential {
/// A Boolean value indicating whether the credential is storable.
var isStorable: Bool { self is StorableCredential }

/// Returns the storable representation of the credential.
var storable: StorableCredential? { self as? StorableCredential }
}
19 changes: 16 additions & 3 deletions AtalaPrismSDK/Domain/Sources/Models/DIDUrl.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
/// Represents a DIDUrl with "did", "path", "parameters", "fragment"
/// As specified in [w3 standards](`https://www.w3.org/TR/did-core/#dfn-did-urls`)
public struct DIDUrl {
/// The associated DID.
/// The associated Decentralized Identifier (DID).
public let did: DID

/// The path component of the DIDUrl.
/// An array of string fragments representing the path segments.
public let path: [String]

/// The parameters component of the DIDUrl.
/// A dictionary mapping parameter keys to their associated values.
public let parameters: [String: String]

/// The fragment component of the DIDUrl.
/// An optional string representing the fragment.
public let fragment: String?

/// Initializes a new `DIDUrl` with the given components.
/// - Parameters:
/// - did: The associated Decentralized Identifier (DID).
/// - path: An array of string fragments representing the path segments.
/// - parameters: A dictionary mapping parameter keys to their associated values.
/// - fragment: An optional string representing the fragment.
public init(
did: DID,
path: [String] = [],
Expand All @@ -25,18 +34,22 @@ public struct DIDUrl {
self.fragment = fragment
}

/// A string representation of this `DIDUrl`.
public var string: String {
did.string + fragmentString
did.string + pathString + queryString + fragmentString
}

/// A string representation of the path component of this `DIDUrl`.
private var pathString: String {
"/" + path.joined(separator: "/")
}

/// A string representation of the parameters component of this `DIDUrl`.
private var queryString: String {
"?" + parameters.map { $0 + "=" + $1 }.joined(separator: "&")
parameters.isEmpty ? "" : "?" + parameters.map { $0.key + "=" + $0.value }.joined(separator: "&")
}

/// A string representation of the fragment component of this `DIDUrl`.
private var fragmentString: String {
fragment.map { "#" + $0 } ?? ""
}
Expand Down
14 changes: 14 additions & 0 deletions AtalaPrismSDK/Domain/Sources/Models/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,12 @@ public enum PolluxError: KnownPrismError {

/// An error case representing an invalid JWT credential. This error is thrown when attempting to create a JWT presentation without a valid JWTCredential.
case invalidJWTCredential

/// An error case when the offer doesnt present enough information like Domain or Challenge
case offerDoesntProvideEnoughInformation

/// An error case there is missing an `ExportableKey`
case requiresExportableKeyForOperation(operation: String)

/// The error code returned by the server.
public var code: Int {
Expand All @@ -656,6 +662,10 @@ public enum PolluxError: KnownPrismError {
return 53
case .invalidJWTCredential:
return 54
case .offerDoesntProvideEnoughInformation:
return 55
case .requiresExportableKeyForOperation:
return 56
}
}

Expand All @@ -676,6 +686,10 @@ public enum PolluxError: KnownPrismError {
return "To create a JWT presentation a Prism DID is required"
case .invalidJWTCredential:
return "To create a JWT presentation please provide a valid JWTCredential"
case .offerDoesntProvideEnoughInformation:
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"
}
}
}
Expand Down
43 changes: 0 additions & 43 deletions AtalaPrismSDK/Domain/Sources/Models/KeyPair.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
@testable import Domain
import Foundation

import Domain
import Foundation

extension Message: Codable {
Expand All @@ -25,7 +21,7 @@ extension Message: Codable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(piuri, forKey: .piuri)
try container.encode(body.base64UrlEncodedString(), forKey: .body)
try container.encode(body, forKey: .body)
try container.encode(extraHeaders, forKey: .extraHeaders)
try container.encode(createdTime, forKey: .createdTime)
try container.encode(expiresTimePlus, forKey: .expiresTimePlus)
Expand All @@ -42,7 +38,7 @@ extension Message: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let piuri = try container.decode(String.self, forKey: .piuri)
let body = try container.decode(String.self, forKey: .body)
let body = try container.decode(Data.self, forKey: .body)
let extraHeaders = try container.decode([String: String].self, forKey: .extraHeaders)
let createdTime = try container.decode(Date.self, forKey: .createdTime)
let expiresTimePlus = try container.decode(Date.self, forKey: .expiresTimePlus)
Expand All @@ -60,7 +56,7 @@ extension Message: Codable {
from: try from.map { try DID(string: $0) },
to: try to.map { try DID(string: $0) },
fromPrior: fromPrior,
body: Data(fromBase64URL: body)!,
body: body,
extraHeaders: extraHeaders,
createdTime: createdTime,
expiresTimePlus: expiresTimePlus,
Expand All @@ -71,10 +67,3 @@ extension Message: Codable {
)
}
}


extension Message: Equatable {
public static func == (lhs: Domain.Message, rhs: Domain.Message) -> Bool {
lhs.id == rhs.id && lhs.piuri == rhs.piuri
}
}
Loading