Skip to content

Commit 0c42577

Browse files
feat(pollux): offline issuance (#222)
Signed-off-by: goncalo-frade-iohk <[email protected]>
1 parent e0bd2ff commit 0c42577

38 files changed

+2174
-39
lines changed

EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public enum CredentialOperationsOptions {
1111
case subjectDID(DID) // The decentralized identifier of the subject.
1212
case entropy(String) // Entropy for any randomization operation.
1313
case signableKey(SignableKey) // A key that can be used for signing.
14+
case privateKey(PrivateKey) // A private key that can be used for signing.
1415
case exportableKey(ExportableKey) // A key that can be exported.
1516
case exportableKeys([ExportableKey]) // A key that can be exported.
1617
case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters
@@ -115,6 +116,42 @@ public protocol Pollux {
115116
presentationPayload: Data,
116117
options: [CredentialOperationsOptions]
117118
) async throws -> Bool
119+
120+
/// Issues a new verifiable credential of the specified type using the provided claims and options.
121+
///
122+
/// - Parameters:
123+
/// - type: A string identifying the credential format to issue. Supported values typically include:
124+
/// - "jwt", "vc+jwt" for JSON Web Token-based credentials
125+
/// - "vc+sd-jwt" for selective disclosure JWT credentials
126+
/// Implementations may support additional types; consult the concrete `Pollux` implementation.
127+
/// - claims: A result-builder closure (`@CredentialClaimsBuilder`) that constructs the set of input claims
128+
/// to be embedded or referenced in the credential. The structure and required fields depend on the `type`.
129+
/// - options: A list of `CredentialOperationsOptions` providing contextual data and cryptographic material
130+
/// required for issuance. Common options include:
131+
/// - `.exportableKey(PrivateKey)` for signing
132+
/// - `.linkSecret(id:secret:)` for AnonCreds link secret
133+
/// - `.custom(key:data:)` for implementation-specific inputs
134+
///
135+
/// - Returns: A `Credential` representing the issued verifiable credential, including any metadata necessary
136+
/// for storage, presentation, or later verification.
137+
///
138+
/// - Throws: An error if issuance fails due to invalid inputs, unsupported `type`, missing or incompatible
139+
/// options (e.g., absent signing key, schema, or link secret), claim-building errors, or cryptographic failures.
140+
///
141+
/// - Important:
142+
/// - The exact combination of required `options` depends on the credential `type`.
143+
/// - For formats requiring signing, you must provide a compatible key via one of the key-related options.
144+
/// - When issuing AnonCreds credentials, schema and credential definition information, as well as a link secret,
145+
/// are typically required.
146+
/// - The `claims` closure should produce claims compatible with the selected `type`; mismatches may cause errors.
147+
///
148+
/// - See Also:
149+
/// - `parseCredential(type:credentialPayload:options:)`
150+
func issueCredential(
151+
type: String,
152+
@CredentialClaimsBuilder claims: () -> InputClaim,
153+
options: [CredentialOperationsOptions]
154+
) async throws -> Credential
118155
}
119156

120157
public extension Pollux {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import Foundation
2+
3+
/// A claim type that represents a JSON array within a larger claims structure.
4+
///
5+
/// `ArrayClaim` is used to model an array value for a given key in a claim-based
6+
/// payload. It leverages result builders to concisely construct arrays of elements,
7+
/// where each element can be a primitive (string, number, boolean), another array,
8+
/// or an object. The resulting array is wrapped as a `ClaimElement` and associated
9+
/// with the provided key.
10+
///
11+
/// This type offers two convenience initializers:
12+
/// - One that accepts a builder producing `[ArrayElementClaim]`, ideal when you want
13+
/// to explicitly construct array elements using the typed helpers like
14+
/// `ArrayElementClaim.string(_:)`, `.number(_:)`, `.bool(_:)`, `.array {}`, and
15+
/// `.object {}`.
16+
/// - One that accepts a builder producing `[InputClaim]`, which is useful for arrays
17+
/// directly composed of other claim types conforming to `InputClaim`.
18+
///
19+
/// The `disclosable` flag can be used to indicate whether the array (and its elements,
20+
/// depending on your surrounding logic) may be disclosed in contexts like logs or
21+
/// debug output.
22+
///
23+
/// Example usage with `ArrayElementClaim`:
24+
/// ```swift
25+
/// let claim = ArrayClaim(key: "items") {
26+
/// ArrayElementClaim.string("apple")
27+
/// ArrayElementClaim.number(42)
28+
/// ArrayElementClaim.bool(true)
29+
/// ArrayElementClaim.array {
30+
/// ArrayElementClaim.string("nested")
31+
/// }
32+
/// ArrayElementClaim.object {
33+
/// StringClaim(key: "name", value: "widget")
34+
/// NumberClaim(key: "count", value: 3)
35+
/// }
36+
/// }
37+
/// ```
38+
///
39+
/// Example usage with `[InputClaim]`:
40+
/// ```swift
41+
/// let claim = ArrayClaim(key: "objects") {
42+
/// ObjectClaim {
43+
/// StringClaim(key: "id", value: "123")
44+
/// }
45+
/// ObjectClaim {
46+
/// StringClaim(key: "id", value: "456")
47+
/// }
48+
/// }
49+
/// ```
50+
///
51+
/// - SeeAlso: `ArrayElementClaim`, `ObjectClaim`, `StringClaim`, `NumberClaim`, `BoolClaim`, `InputClaim`, `ClaimElement`
52+
public struct ArrayClaim: InputClaim {
53+
public var value: ClaimElement
54+
55+
/// Initializes an `ArrayClaim` with a key and a builder for the array elements.
56+
/// - Parameters:
57+
/// - key: The key for the claim.
58+
/// - claims: A closure that returns an array of `ArrayElementClaim` using the result builder.
59+
public init(key: String, disclosable: Bool = false, @ArrayClaimBuilder claims: () -> [InputClaim]) {
60+
self.value = .init(key: key, element: .array(claims().map(\.value)), disclosable: disclosable)
61+
}
62+
}
63+
64+
/// Represents an element within an array claim.
65+
public struct ArrayValueClaim {
66+
let value: InputClaim
67+
68+
public init<T: InputClaim>(_ value: T) {
69+
self.value = value
70+
}
71+
72+
/// Creates an `ArrayElementClaim` with a string value.
73+
/// - Parameter str: The string value for the claim.
74+
/// - Returns: An `ArrayElementClaim` containing the string value.
75+
public static func string(_ str: String, disclosable: Bool = false) -> ArrayValueClaim {
76+
.init(StringClaim(key: "", value: str, disclosable: disclosable))
77+
}
78+
79+
/// Creates an `ArrayElementClaim` with a numeric value.
80+
/// - Parameter number: The numeric value for the claim.
81+
/// - Returns: An `ArrayElementClaim` containing the numeric value.
82+
public static func number<N: Numeric & Codable>(_ number: N, disclosable: Bool = false) -> ArrayValueClaim {
83+
.init(NumberClaim(key: "", value: number, disclosable: disclosable))
84+
}
85+
86+
/// Creates an `ArrayElementClaim` with a boolean value.
87+
/// - Parameter boolean: The boolean value for the claim.
88+
/// - Returns: An `ArrayElementClaim` containing the boolean value.
89+
public static func bool(_ boolean: Bool, disclosable: Bool = false) -> ArrayValueClaim {
90+
.init(BoolClaim(key: "", value: boolean, disclosable: disclosable))
91+
}
92+
93+
/// Creates an `ArrayElementClaim` with an array of claims.
94+
/// - Parameter claims: A closure that returns an array of `ArrayElementClaim` using the result builder.
95+
/// - Returns: An `ArrayElementClaim` containing the array of claims.
96+
public static func array(@ArrayClaimBuilder claims: () -> [InputClaim], disclosable: Bool = false) -> ArrayValueClaim {
97+
.init(ArrayClaim(key: "", disclosable: disclosable, claims: claims))
98+
}
99+
100+
/// Creates an `ArrayElementClaim` with an object of claims.
101+
/// - Parameter claims: A closure that returns an array of `Claim` using the result builder.
102+
/// - Returns: An `ArrayElementClaim` containing the object of claims.
103+
public static func object(@ObjectClaimBuilder claims: () -> [InputClaim], disclosable: Bool = false) -> ArrayValueClaim {
104+
.init(ObjectClaim(key: "", disclosable: disclosable, claims: claims))
105+
}
106+
107+
/// Creates an `ArrayElementClaim` with a string value.
108+
/// - Parameter claim: The InputClaim value for the claim.
109+
/// - Returns: An `ArrayElementClaim` containing the string value.
110+
public static func claim<T: InputClaim>(_ claim: T) -> ArrayValueClaim {
111+
.init(claim)
112+
}
113+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/// A Swift result builder that simplifies the construction of heterogeneous collections of claims.
2+
///
3+
/// ArrayClaimBuilder supports building arrays of different claim types used in the claims system:
4+
/// - `any InputClaim` (erased protocol type for general claims)
5+
/// - `ArrayElementClaim` (claims that represent elements within an array)
6+
/// - `StringClaim` (claims that operate on string values)
7+
///
8+
/// This builder enables a declarative DSL-like syntax to compose claims with:
9+
/// - Multiple `buildBlock` overloads to handle different claim array types
10+
/// - Support for optional sections via `buildOptional`
11+
/// - Conditional branching via `buildEither(first:)` and `buildEither(second:)`
12+
/// - Flattening of nested arrays via `buildArray`
13+
/// - Expression lifting via `buildExpression` for each supported claim type
14+
///
15+
/// Typical usage:
16+
/// - Use in an API that takes a closure annotated with `@ArrayClaimBuilder`
17+
/// - Compose claims inline using standard control flow constructs (`if`, `if/else`, optionals)
18+
///
19+
/// Notes:
20+
/// - Overloaded `buildBlock` methods allow the same builder to be used in contexts that expect
21+
/// different element types, while keeping type safety.
22+
/// - Each `buildArray` variant flattens nested arrays to a single-level array.
23+
/// - Optional and conditional helpers return empty arrays when absent, which naturally composes
24+
/// into the final result without additional branching.
25+
///
26+
/// Dependencies:
27+
/// - `InputClaim` protocol/type-erased existential used via `any InputClaim`
28+
/// - `ArrayElementClaim` value type used for array element-level claims
29+
/// - `StringClaim` value type used for string-specific claims
30+
///
31+
/// Example (conceptual):
32+
/// @ArrayClaimBuilder
33+
/// func makeClaims() -> [any InputClaim] {
34+
/// StringClaim.equals("hello")
35+
/// if condition {
36+
/// ArrayElementClaim.index(0)
37+
/// }
38+
/// optionalClaim
39+
/// }
40+
///
41+
/// The builder will collect, flatten, and return the correct array type based on the function's return type.
42+
@resultBuilder
43+
public struct ArrayClaimBuilder {
44+
// public typealias ClaimPartialResult = [any InputClaim]
45+
public typealias ArrayClaimPartialResult = [ArrayValueClaim]
46+
// public typealias StringClaimPartialResult = [StringClaim]
47+
/// Builds an array of `ArrayElementClaim` from the provided components.
48+
/// - Parameter components: The array element claims to include in the array.
49+
/// - Returns: An array of `ArrayElementClaim`.
50+
public static func buildBlock(_ components: ArrayClaimPartialResult...) -> ArrayClaimPartialResult {
51+
components.flatMap { $0 }
52+
}
53+
54+
/// Builds an array of `ArrayElementClaim` from the provided components.
55+
/// - Parameter components: The array element claims to include in the array.
56+
/// - Returns: An array of `ArrayElementClaim`.
57+
// public static func buildBlock(_ components: ClaimPartialResult...) -> ClaimPartialResult {
58+
// components.flatMap { $0 }
59+
// }
60+
61+
/// Builds an array of `StringClaim` from the provided components.
62+
/// - Parameter components: The string claims to include in the array.
63+
/// - Returns: An array of `StringClaim`.
64+
// public static func buildBlock(_ components: StringClaimPartialResult...) -> StringClaimPartialResult {
65+
// components.flatMap { $0 }
66+
// }
67+
68+
// public static func buildPartialBlock(first: ClaimPartialResult) -> ClaimPartialResult {
69+
// first
70+
// }
71+
//
72+
// public static func buildPartialBlock(accumulated: ClaimPartialResult, next: ClaimPartialResult) -> ClaimPartialResult {
73+
// accumulated + next
74+
// }
75+
//
76+
// public static func buildExpression(_ expression: any InputClaim) -> ClaimPartialResult {
77+
// [expression]
78+
// }
79+
80+
public static func buildExpression(_ expression: ArrayValueClaim) -> ArrayClaimPartialResult {
81+
[expression]
82+
}
83+
84+
// public static func buildExpression(_ expression: StringClaim) -> StringClaimPartialResult {
85+
// [expression]
86+
// }
87+
88+
// public static func buildArray(_ components: [ClaimPartialResult]) -> ClaimPartialResult {
89+
// components.flatMap { $0 }
90+
// }
91+
92+
public static func buildArray(_ components: [ArrayClaimPartialResult]) -> ArrayClaimPartialResult {
93+
components.flatMap { $0 }
94+
}
95+
96+
// public static func buildArray(_ components: [StringClaimPartialResult]) -> StringClaimPartialResult {
97+
// components.flatMap { $0 }
98+
// }
99+
100+
/// Adds support for optionals
101+
// public static func buildOptional(_ component: ClaimPartialResult?) -> ClaimPartialResult {
102+
// guard let component else {
103+
// return []
104+
// }
105+
// return component
106+
// }
107+
108+
public static func buildOptional(_ component: ArrayClaimPartialResult?) -> ArrayClaimPartialResult {
109+
guard let component else {
110+
return []
111+
}
112+
return component
113+
}
114+
115+
/// Adds support for if statements in build block
116+
// public static func buildEither(first component: ClaimPartialResult) -> ClaimPartialResult {
117+
// component
118+
// }
119+
//
120+
// public static func buildEither(second component: ClaimPartialResult) -> ClaimPartialResult {
121+
// component
122+
// }
123+
124+
public static func buildEither(first component: ArrayClaimPartialResult) -> ArrayClaimPartialResult {
125+
component
126+
}
127+
128+
public static func buildEither(second component: ArrayClaimPartialResult) -> ArrayClaimPartialResult {
129+
component
130+
}
131+
132+
/// Adds support for optionals
133+
// public static func buildOptional(_ component: StringClaimPartialResult?) -> StringClaimPartialResult {
134+
// guard let component else {
135+
// return []
136+
// }
137+
// return component
138+
// }
139+
140+
141+
/// Adds support for if statements in build block
142+
// public static func buildEither(first component: StringClaimPartialResult) -> StringClaimPartialResult {
143+
// component
144+
// }
145+
//
146+
// public static func buildEither(second component: StringClaimPartialResult) -> StringClaimPartialResult {
147+
// component
148+
// }
149+
150+
// public static func buildFinalResult(_ component: ArrayClaimPartialResult) -> ClaimPartialResult {
151+
// component.map(\.value)
152+
// }
153+
//
154+
// public static func buildFinalResult(_ component: StringClaimPartialResult) -> ClaimPartialResult {
155+
// component
156+
// }
157+
158+
public static func buildFinalResult(_ component: ArrayClaimPartialResult) -> [InputClaim] {
159+
component.map(\.value)
160+
}
161+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
3+
/// A simple input claim wrapper for Boolean values.
4+
///
5+
/// `BoolClaim` conforms to `InputClaim` and encapsulates a single `Bool` value
6+
/// associated with a key, packaged as a `ClaimElement`. It is intended for use
7+
/// in systems that collect, disclose, or transmit typed claims as key/value
8+
/// pairs.
9+
///
10+
/// Usage:
11+
/// - Initialize with a key and a `Bool` to create a claim that can be fed into
12+
/// a broader claims pipeline.
13+
/// - Optionally mark the claim as `disclosable` to indicate whether the value
14+
/// can be shared externally.
15+
///
16+
/// Example:
17+
/// ```swift
18+
/// let acceptsTerms = BoolClaim(key: "accepts_terms", value: true, disclosable: true)
19+
/// ```
20+
///
21+
/// - SeeAlso: `InputClaim`, `ClaimElement`
22+
public struct BoolClaim: InputClaim {
23+
public var value: ClaimElement
24+
25+
/// Initializes a `BoolClaim` with a key and a boolean value.
26+
/// - Parameters:
27+
/// - key: The key for the claim.
28+
/// - value: The boolean value for the claim.
29+
public init(key: String, value: Bool, disclosable: Bool = false) {
30+
self.value = ClaimElement(key: key, element: .codable(value), disclosable: disclosable)
31+
}
32+
}

0 commit comments

Comments
 (0)