Skip to content

Commit 48013d0

Browse files
feat(pollux): add anoncreds prooving implementation
1 parent ae14bb9 commit 48013d0

18 files changed

+548
-46
lines changed

AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import Domain
22
import Pollux
33

44
public struct PolluxBuilder {
5+
private let pluto: Pluto
56

6-
public init() {}
7+
public init(pluto: Pluto) {
8+
self.pluto = pluto
9+
}
710

811
public func build() -> Pollux {
9-
PolluxImpl()
12+
PolluxImpl(pluto: pluto)
1013
}
1114
}

AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public enum CredentialOperationsOptions {
1212
case entropy(String) // Entropy for any randomization operation.
1313
case signableKey(SignableKey) // A key that can be used for signing.
1414
case exportableKey(ExportableKey) // A key that can be exported.
15+
case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters
1516
case custom(key: String, data: Data) // Any custom data.
1617
}
1718

AtalaPrismSDK/Domain/Sources/Models/Errors.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -730,13 +730,19 @@ public enum PolluxError: KnownPrismError {
730730

731731
/// An error case when the offer doesnt present enough information like Domain or Challenge
732732
case offerDoesntProvideEnoughInformation
733-
733+
734734
/// An error case when the issued credential message doesnt present enough information or unsupported attachment
735735
case unsupportedIssuedMessage
736736

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

740+
/// An error case when the message doesnt present enough information
741+
case messageDoesntProvideEnoughInformation
742+
743+
/// An requirement is missing for `CredentialOperationsOptions`
744+
case missingAndIsRequiredForOperation(type: String)
745+
740746
/// The error code returned by the server.
741747
public var code: Int {
742748
switch self {
@@ -754,6 +760,10 @@ public enum PolluxError: KnownPrismError {
754760
return 56
755761
case .unsupportedIssuedMessage:
756762
return 57
763+
case .messageDoesntProvideEnoughInformation:
764+
return 58
765+
case .missingAndIsRequiredForOperation:
766+
return 59
757767
}
758768
}
759769

@@ -780,6 +790,10 @@ public enum PolluxError: KnownPrismError {
780790
return "Operation \(operation) requires an ExportableKey"
781791
case .unsupportedIssuedMessage:
782792
return "Issue message provided doesnt have a valid attachment"
793+
case .messageDoesntProvideEnoughInformation:
794+
return "Message provided doesnt have enough information (attachment, type)"
795+
case .missingAndIsRequiredForOperation(let type):
796+
return "Operation requires the following parameter \(type)"
783797
}
784798
}
785799
}

AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnonCredentialDefinition.swift

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AnoncredsSwift
12
import Foundation
23

34
struct AnonCredentialDefinition: Codable {
@@ -18,4 +19,10 @@ struct AnonCredentialDefinition: Codable {
1819
let type: String
1920
let tag: String
2021
let value: Value
22+
23+
func getAnoncred() throws -> AnoncredsSwift.CredentialDefinition {
24+
let json = try JSONEncoder().encode(self)
25+
let jsonString = try json.toString()
26+
return try .init(jsonString: jsonString)
27+
}
2128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
struct AnonCredentialSchema: Codable {
4+
let name: String
5+
let version: String
6+
let attrNames: [String]
7+
let issuerId: String
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Domain
2+
import Foundation
3+
4+
extension AnoncredsCredentialStack: ProvableCredential {
5+
func presentation(
6+
request: Message,
7+
options: [CredentialOperationsOptions]
8+
) throws -> String {
9+
let requestStr: String
10+
guard let attachment = request.attachments.first else {
11+
throw PolluxError.messageDoesntProvideEnoughInformation
12+
}
13+
switch attachment.data {
14+
case let attachmentData as AttachmentJsonData:
15+
requestStr = try attachmentData.data.toString()
16+
case let attachmentData as AttachmentBase64:
17+
guard let data = Data(fromBase64URL: attachmentData.base64) else {
18+
throw PolluxError.messageDoesntProvideEnoughInformation
19+
}
20+
requestStr = try data.toString()
21+
default:
22+
throw PolluxError.messageDoesntProvideEnoughInformation
23+
}
24+
25+
guard
26+
let linkSecretOption = options.first(where: {
27+
if case .linkSecret = $0 { return true }
28+
return false
29+
}),
30+
case let CredentialOperationsOptions.linkSecret(_, secret: linkSecret) = linkSecretOption
31+
else {
32+
throw PolluxError.missingAndIsRequiredForOperation(type: "LinkSecret")
33+
}
34+
35+
if
36+
let zkpParameters = options.first(where: {
37+
if case .zkpPresentationParams = $0 { return true }
38+
return false
39+
}),
40+
case let CredentialOperationsOptions.zkpPresentationParams(attributes, predicates) = zkpParameters
41+
{
42+
return try AnoncredsPresentation().createPresentation(
43+
stack: self,
44+
request: requestStr,
45+
linkSecret: linkSecret,
46+
attributes: attributes,
47+
predicates: predicates
48+
)
49+
} else {
50+
return try AnoncredsPresentation().createPresentation(
51+
stack: self,
52+
request: requestStr,
53+
linkSecret: linkSecret,
54+
attributes: try computeAttributes(requestJson: requestStr),
55+
predicates: try computePredicates(requestJson: requestStr)
56+
)
57+
}
58+
}
59+
}
60+
61+
private func computeAttributes(requestJson: String) throws -> [String: Bool] {
62+
guard
63+
let json = try JSONSerialization.jsonObject(with: try requestJson.tryData(using: .utf8)) as? [String: Any]
64+
else {
65+
throw PolluxError.messageDoesntProvideEnoughInformation
66+
}
67+
let requestedAttributes = json["requested_attributes"] as? [String: Any]
68+
return requestedAttributes?.reduce([:]) { partialResult, row in
69+
var dic = partialResult
70+
dic[row.key] = true
71+
return dic
72+
} ?? [:]
73+
}
74+
75+
private func computePredicates(requestJson: String) throws -> [String] {
76+
guard
77+
let json = try JSONSerialization.jsonObject(with: try requestJson.tryData(using: .utf8)) as? [String: Any]
78+
else {
79+
throw PolluxError.messageDoesntProvideEnoughInformation
80+
}
81+
let requestedPredicates = json["requested_predicates"] as? [String: Any]
82+
return requestedPredicates?.map { $0.key } ?? []
83+
}

AtalaPrismSDK/Pollux/Sources/Models/AnonCreds/AnoncredsCredentialStack.swift

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Domain
33
import Foundation
44

55
struct AnoncredsCredentialStack: Codable {
6+
let schema: AnonCredentialSchema
67
let definition: AnonCredentialDefinition
78
let credential: AnonCredential
89
}
@@ -39,6 +40,8 @@ extension AnoncredsCredentialStack: Domain.Credential {
3940
] as [String : Any]
4041

4142
(try? JSONEncoder.didComm().encode(definition)).map { properties["credentialDefinition"] = $0 }
43+
(try? JSONEncoder.didComm().encode(schema))
44+
.map { properties["schema"] = $0 }
4245
(try? JSONEncoder.didComm().encode(credential.signature)).map { properties["signature"] = $0 }
4346
(try? JSONEncoder.didComm().encode(credential.signatureCorrectnessProof)).map { properties["signatureCorrectnessProof"] = $0 }
4447
(try? JSONEncoder.didComm().encode(credential.witness)).map { properties["witness"] = $0 }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import AnoncredsSwift
2+
import Domain
3+
import Foundation
4+
5+
struct AnoncredsPresentation {
6+
func createPresentation(
7+
stack: AnoncredsCredentialStack,
8+
request: String,
9+
linkSecret: String,
10+
attributes: [String: Bool],
11+
predicates: [String]
12+
) throws -> String {
13+
let linkSecret = try LinkSecret.newFromValue(valueString: linkSecret)
14+
let request = try PresentationRequest(jsonString: request)
15+
let credentialRequest = CredentialRequests(
16+
credential: try stack.credential.getAnoncred(),
17+
requestedAttribute: attributes.map {
18+
.init(referent: $0.key, revealed: $0.value)
19+
},
20+
requestedPredicate: predicates.map { .init(referent: $0) }
21+
)
22+
23+
let credential = stack.credential
24+
let schema = Schema.init(
25+
name: stack.schema.name,
26+
version: stack.schema.version,
27+
attrNames: AttributeNames(stack.schema.attrNames),
28+
issuerId: stack.schema.issuerId
29+
)
30+
31+
let credentialDefinition = try stack.definition.getAnoncred()
32+
return try Prover().createPresentation(
33+
presentationRequest: request,
34+
credentials: [credentialRequest],
35+
selfAttested: [:],
36+
linkSecret: linkSecret,
37+
schemas: [credential.schemaId: schema],
38+
credentialDefinitions: [credential.credentialDefinitionId: credentialDefinition]
39+
).getJson()
40+
}
41+
}

AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/CreateAnoncredCredentialRequest.swift

+32-5
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,58 @@ private struct Schema: Codable {
1111
let issuerId: String
1212
}
1313

14+
struct StorableCredentialRequestMetadata: StorableCredential {
15+
let metadataJson: Data
16+
let storingId: String
17+
var recoveryId: String { "anoncreds+metadata"}
18+
var credentialData: Data { metadataJson }
19+
var queryIssuer: String? { nil }
20+
var querySubject: String? { nil }
21+
var queryCredentialCreated: Date? { nil }
22+
var queryCredentialUpdated: Date? { nil }
23+
var queryCredentialSchema: String? { nil }
24+
var queryValidUntil: Date? { nil }
25+
var queryRevoked: Bool? { nil }
26+
var queryAvailableClaims: [String] { [] }
27+
}
28+
1429
struct CreateAnoncredCredentialRequest {
1530
static func create(
1631
did: String,
1732
linkSecret: String,
1833
linkSecretId: String,
1934
offerData: Data,
20-
credentialDefinitionDownloader: Downloader
35+
credentialDefinitionDownloader: Downloader,
36+
thid: String,
37+
pluto: Pluto
2138
) async throws -> String {
2239
let linkSecretObj = try LinkSecret.newFromValue(valueString: linkSecret)
2340
let offer = try CredentialOffer(jsonString: String(data: offerData, encoding: .utf8)!)
2441
let credDefId = offer.getCredDefId()
2542

2643
let credentialDefinitionData = try await credentialDefinitionDownloader.downloadFromEndpoint(urlOrDID: credDefId)
2744
let credentialDefinitionJson = try credentialDefinitionData.toString()
28-
2945
let credentialDefinition = try CredentialDefinition(jsonString: credentialDefinitionJson)
3046

31-
let def = try Prover().createCredentialRequest(
47+
let requestData = try Prover().createCredentialRequest(
3248
entropy: did,
3349
proverDid: nil,
3450
credDef: credentialDefinition,
3551
linkSecret: linkSecretObj,
3652
linkSecretId: linkSecretId,
3753
credentialOffer: offer
38-
).request.getJson()
39-
return def
54+
)
55+
56+
guard
57+
let metadata = try requestData.metadata.getJson().data(using: .utf8)
58+
else {
59+
throw CommonError.invalidCoding(message: "Could not decode to data")
60+
}
61+
62+
let storableMetadata = StorableCredentialRequestMetadata(metadataJson: metadata, storingId: thid)
63+
64+
try await pluto.storeCredential(credential: storableMetadata).first().await()
65+
66+
return try requestData.request.getJson()
4067
}
4168
}

AtalaPrismSDK/Pollux/Sources/Operation/Anoncreds/ParseAnoncredsCredentialFromMessage.swift

+37-4
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,49 @@ struct ParseAnoncredsCredentialFromMessage {
66
static func parse(
77
issuerCredentialData: Data,
88
linkSecret: String,
9-
credentialDefinitionDownloader: Downloader
9+
credentialDefinitionDownloader: Downloader,
10+
schemaDownloader: Downloader,
11+
thid: String,
12+
pluto: Pluto
1013
) async throws -> AnoncredsCredentialStack {
1114
let domainCred = try JSONDecoder().decode(AnonCredential.self, from: issuerCredentialData)
12-
1315
let credentialDefinitionData = try await credentialDefinitionDownloader
1416
.downloadFromEndpoint(urlOrDID: domainCred.credentialDefinitionId)
15-
17+
let schemaData = try await schemaDownloader
18+
.downloadFromEndpoint(urlOrDID: domainCred.schemaId)
19+
20+
guard let metadata = try await pluto.getAllCredentials()
21+
.first()
22+
.await()
23+
.first(where: { $0.storingId == thid })
24+
else {
25+
throw PolluxError.messageDoesntProvideEnoughInformation
26+
}
27+
28+
let linkSecretObj = try LinkSecret.newFromValue(valueString: linkSecret)
29+
let credentialDefinitionJson = try credentialDefinitionData.toString()
30+
let credentialDefinition = try CredentialDefinition(jsonString: credentialDefinitionJson)
31+
32+
let credentialMetadataJson = try metadata.credentialData.toString()
33+
let credentialMetadataObj = try CredentialRequestMetadata(jsonString: credentialMetadataJson)
34+
35+
let credentialObj = try Credential(jsonString: issuerCredentialData.toString())
36+
37+
let processedCredential = try Prover().processCredential(
38+
credential: credentialObj,
39+
credRequestMetadata: credentialMetadataObj,
40+
linkSecret: linkSecretObj,
41+
credDef: credentialDefinition,
42+
revRegDef: nil
43+
)
44+
45+
let processedCredentialJson = try processedCredential.getJson().tryData(using: .utf8)
46+
let finalCredential = try JSONDecoder().decode(AnonCredential.self, from: processedCredentialJson)
47+
1648
return AnoncredsCredentialStack(
49+
schema: try JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: schemaData),
1750
definition: try JSONDecoder.didComm().decode(AnonCredentialDefinition.self, from: credentialDefinitionData),
18-
credential: domainCred
51+
credential: finalCredential
1952
)
2053
}
2154
}

0 commit comments

Comments
 (0)