Skip to content

Commit

Permalink
feat!(pollux): add jwt and anoncreds verification
Browse files Browse the repository at this point in the history
BREAKING CHANGE: ProofTypes was replaced by ClaimFilter

- This commit also has a fix with PRISM DID resolution, now it correctly resolves all public keys
- It adds a new utility method to castor to retrieve all public keys from a DID

Fixes ATL-6627
  • Loading branch information
goncalo-frade-iohk committed May 3, 2024
1 parent 931c843 commit 8e9e959
Show file tree
Hide file tree
Showing 77 changed files with 3,961 additions and 335 deletions.
5 changes: 2 additions & 3 deletions AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,9 @@ extension ApolloImpl: Apollo {
switch curve {
case .secp256k1:
if
let keyData = parameters[KeyProperties.rawKey.rawValue].flatMap({ Data(base64Encoded: $0) }),
let derivationPathStr = parameters[KeyProperties.derivationPath.rawValue]
let keyData = parameters[KeyProperties.rawKey.rawValue].flatMap({ Data(base64Encoded: $0) })
{
let derivationPath = try DerivationPath(string: derivationPathStr)
let derivationPath = try parameters[KeyProperties.derivationPath.rawValue].map { try DerivationPath(string: $0) } ?? DerivationPath(index:0)
return Secp256k1PrivateKey(raw: keyData, derivationPath: derivationPath)
} else {
guard
Expand Down
6 changes: 4 additions & 2 deletions AtalaPrismSDK/Builders/Sources/PolluxBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import Pollux

public struct PolluxBuilder {
private let pluto: Pluto
private let castor: Castor

public init(pluto: Pluto) {
public init(pluto: Pluto, castor: Castor) {
self.pluto = pluto
self.castor = castor
}

public func build() -> Pollux {
PolluxImpl(pluto: pluto)
PolluxImpl(castor: castor, pluto: pluto)
}
}
46 changes: 46 additions & 0 deletions AtalaPrismSDK/Castor/Sources/CastorImpl+Public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,50 @@ extension CastorImpl: Castor {
}
return try await resolver.resolve(did: did)
}

public func getDIDPublicKeys(did: DID) async throws -> [PublicKey] {
let document = try await resolveDID(did: did)

return try await document.verificationMethods
.asyncMap { verificationMethod -> PublicKey in
try await verificationMethodToPublicKey(method: verificationMethod)
}
}

private func verificationMethodToPublicKey(method: DIDDocument.VerificationMethod) async throws -> PublicKey {
switch method.type {
case "EcdsaSecp256k1VerificationKey2019", "secp256k1":
guard let multibaseData = method.publicKeyMultibase else {
throw CastorError.cannotRetrievePublicKeyFromDocument
}
return try apollo.createPublicKey(
parameters: [
KeyProperties.type.rawValue: "EC",
KeyProperties.rawKey.rawValue: multibaseData,
KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue
])
case "Ed25519VerificationKey2018", "ed25519":
guard let multibaseData = method.publicKeyMultibase else {
throw CastorError.cannotRetrievePublicKeyFromDocument
}
return try apollo.createPublicKey(
parameters: [
KeyProperties.type.rawValue: "EC",
KeyProperties.rawKey.rawValue: multibaseData,
KeyProperties.curve.rawValue: KnownKeyCurves.ed25519.rawValue
])
case "X25519KeyAgreementKey2019", "x25519":
guard let multibaseData = method.publicKeyMultibase else {
throw CastorError.cannotRetrievePublicKeyFromDocument
}
return try apollo.createPublicKey(
parameters: [
KeyProperties.type.rawValue: "EC",
KeyProperties.rawKey.rawValue: multibaseData,
KeyProperties.curve.rawValue: KnownKeyCurves.x25519.rawValue
])
default:
throw UnknownError.somethingWentWrongError(customMessage: nil, underlyingErrors: nil)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ struct VerifyDIDSignatureOperation {
guard let multibaseData = method.publicKeyMultibase else {
throw CastorError.cannotRetrievePublicKeyFromDocument
}
return try await apollo.createPrivateKey(
return try apollo.createPublicKey(
parameters: [
KeyProperties.type.rawValue: "EC",
KeyProperties.rawKey.rawValue: multibaseData,
KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue
]).publicKey()
])
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,42 @@ import Domain
import Foundation

struct LongFormPrismDIDResolver: DIDResolverDomain {
enum KeyType {
case master
case issuing
case authentication
case agreement
case capabilityDelegation
case capabilityInvocation
case revocation
case unknown

init(usage: PrismDIDPublicKey.Usage) {
switch usage {
case .masterKey:
self = .master
case .issuingKey:
self = .issuing
case .keyAgreementKey:
self = .agreement
case .capabilityDelegationKey:
self = .capabilityDelegation
case .capabilityInvocationKey:
self = .capabilityInvocation
case .authenticationKey:
self = .authentication
case .revocationKey:
self = .revocation
case .unknownKey:
self = .unknown
}
}
}
struct PublicKeyDecoded {
let id: String
let keyType: KeyType
let method: DIDDocument.VerificationMethod
}
let apollo: Apollo
let logger: PrismLogger

Expand All @@ -25,16 +61,21 @@ struct LongFormPrismDIDResolver: DIDResolverDomain {
encodedData: data
)

let authenticate = verificationMethods.first.map {
DIDDocument.Authentication(urls: [$0.key], verificationMethods: [])
let authenticates = verificationMethods.filter { $0.keyType == .authentication }.map {
DIDDocument.Authentication(urls: [$0.id], verificationMethods: [])
}

let agreements = verificationMethods.filter { $0.keyType == .authentication }.map {
DIDDocument.KeyAgreement(urls: [$0.id], verificationMethods: [])
}

let servicesProperty = DIDDocument.Services(values: services)

let verificationMethodsProperty = DIDDocument.VerificationMethods(values: Array(verificationMethods.values))
let verificationMethodsProperty = DIDDocument.VerificationMethods(values: verificationMethods.map(\.method))

let properties = [
authenticate,
authenticates,
agreements,
servicesProperty,
verificationMethodsProperty
].compactMap { $0 as? DIDDocumentCoreProperty }
Expand All @@ -49,7 +90,7 @@ struct LongFormPrismDIDResolver: DIDResolverDomain {
did: DID,
stateHash: String,
encodedData: Data
) throws -> ([String: DIDDocument.VerificationMethod], [DIDDocument.Service]) {
) throws -> ([PublicKeyDecoded], [DIDDocument.Service]) {
let verifyEncodedState = encodedData.sha256()
guard stateHash == verifyEncodedState else { throw CastorError.initialStateOfDIDChanged }
let operation = try Io_Iohk_Atala_Prism_Protos_AtalaOperation(serializedData: encodedData)
Expand All @@ -63,30 +104,35 @@ struct LongFormPrismDIDResolver: DIDResolverDomain {
throw error
}
}

let services = operation.createDid.didData.services.map {
DIDDocument.Service(
id: $0.id,
type: [$0.type],
serviceEndpoint: $0.serviceEndpoint.map { .init(uri: $0) }
)
}
return (publicKeys.reduce(
[String: DIDDocument.VerificationMethod]())
{ partialResult, publicKey in

let decodedPublicKeys = publicKeys.map {
let didUrl = DIDUrl(
did: did,
fragment: publicKey.id
fragment: $0.id
)

let method = DIDDocument.VerificationMethod(
id: didUrl,
controller: did,
type: publicKey.keyData.getProperty(.curve) ?? "",
publicKeyMultibase: publicKey.keyData.raw.base64EncodedString()
type: $0.keyData.getProperty(.curve) ?? "",
publicKeyMultibase: $0.keyData.raw.base64EncodedString()
)

return PublicKeyDecoded(
id: didUrl.string,
keyType: .init(usage: $0.usage),
method: method
)
var result = partialResult
result[didUrl.string] = method
return result
}, services)
}

return (decodedPublicKeys, services)
}
}
4 changes: 4 additions & 0 deletions AtalaPrismSDK/Domain/Sources/BBs/Apollo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,9 @@ public protocol Apollo {
/// - Throws: An error if the private key could not be created. The specific error will depend on the underlying key creation process.
func createPublicKey(parameters: [String: String]) throws -> PublicKey

/// Creates a new link secret, which is used in anoncreds processes.
///
/// - Returns: A newly generated link secret as `Key`.
/// - Throws: An error if the key generation fails.
func createNewLinkSecret() throws -> Key
}
7 changes: 7 additions & 0 deletions AtalaPrismSDK/Domain/Sources/BBs/Castor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public protocol Castor {
challenge: Data,
signature: Data
) async throws -> Bool

/// Retrieves the public keys associated with a specific decentralized identifier (DID).
///
/// - Parameter did: The decentralized identifier (DID) whose public keys are to be retrieved.
/// - Returns: An array of `PublicKey` objects associated with the given DID.
/// - Throws: An error if the retrieval process fails.
func getDIDPublicKeys(did: DID) async throws -> [PublicKey]
}

extension Castor {
Expand Down
30 changes: 30 additions & 0 deletions AtalaPrismSDK/Domain/Sources/BBs/Pollux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,36 @@ public protocol Pollux {
offerMessage: Message,
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:
/// - type: The type of credential being requested (e.g., JWT, AnonCred).
/// - toDID: The decentralized identifier (DID) of the entity to which the presentation request is being sent.
/// - name: A descriptive name for the presentation request.
/// - version: The version of the presentation request format or protocol.
/// - claimFilters: A collection of filters specifying the claims required in the credential.
/// - Returns: The serialized presentation request as `Data`.
/// - Throws: An error if the request creation fails.
func createPresentationRequest(
type: CredentialType,
toDID: DID,
name: String,
version: String,
claimFilters: [ClaimFilter]
) throws -> Data

/// Verifies the validity of a presentation contained within a message, using specified options.
///
/// - Parameters:
/// - message: 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(
message: Message,
options: [CredentialOperationsOptions]
) async throws -> Bool
}

public extension Pollux {
Expand Down
6 changes: 6 additions & 0 deletions AtalaPrismSDK/Domain/Sources/Models/Common/Downloader.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Foundation

/// `Downloader` is a protocol that defines functionality for downloading data from a given endpoint.
public protocol Downloader {
/// Downloads data from the specified endpoint URL or DID.
///
/// - Parameter urlOrDID: The URL or decentralized identifier (DID) from which the data should be downloaded.
/// - Returns: The downloaded data as `Data`.
/// - Throws: An error if the download fails or the endpoint is unreachable.
func downloadFromEndpoint(urlOrDID: String) async throws -> Data
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import Foundation

/// `CredentialType` is an enumeration that defines the types of credentials supported in the system.
public enum CredentialType {
/// Represents a credential in JWT (JSON Web Token) format.
case jwt

/// Represents a credential in AnonCred (Anonymous Credentials) format.
case anoncred
}

/// `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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ public protocol ProvableCredential {
/// - Returns: The proof as a `String`.
/// - Throws: If there is an error creating the proof.
func presentation(request: Message, 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.
func isValidForPresentation(request: Message, options: [CredentialOperationsOptions]) throws -> Bool
}

public extension Credential {
Expand Down

0 comments on commit 8e9e959

Please sign in to comment.