Skip to content

Commit

Permalink
feat(pollux): add credential abstraction
Browse files Browse the repository at this point in the history
ATL-4786

Besides adding the new credential abstraction this adds the following:
- Changes on Pollux to use the new abstraction
- Changes on PrismAgent to use new abstraction
- Changes on Sample App to for new abstraction
- Adds tests for new implementation
- Adds Codable to Messages
- Updates error codes and documentation
- Updates all documentation on new methods
  • Loading branch information
goncalo-frade-iohk committed Aug 7, 2023
1 parent a389894 commit ad02957
Show file tree
Hide file tree
Showing 67 changed files with 1,284 additions and 945 deletions.
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
)
}
}
81 changes: 81 additions & 0 deletions AtalaPrismSDK/Domain/Sources/Models/Credentials/Credential.swift
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 }
}
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 ProofableCredential {
/// 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 ProofableCredential }

/// Returns the proofable representation of the credential.
var proof: ProofableCredential? { self as? ProofableCredential }
}
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

0 comments on commit ad02957

Please sign in to comment.