From 1aa4e14c4670d9ac1a18f86c2e6150f118f4a509 Mon Sep 17 00:00:00 2001 From: Bassam Date: Wed, 22 Nov 2023 11:41:54 -0500 Subject: [PATCH] feat: Create & Store Presentation (#794) Signed-off-by: Bassam Riman --- .../atala/pollux/anoncreds/AnoncredLib.scala | 6 +- .../iohk/atala/pollux/anoncreds/Models.scala | 44 ++--- .../atala/pollux/anoncreds/PoCNewLib.scala | 2 +- .../core/model/IssueCredentialRecord.scala | 9 + .../core/model/error/PresentationError.scala | 2 + .../repository/CredentialRepository.scala | 4 + .../CredentialRepositoryInMemory.scala | 28 +++ .../service/MockPresentationService.scala | 9 +- .../core/service/PresentationService.scala | 9 +- .../service/PresentationServiceImpl.scala | 176 +++++++++++++++--- .../service/PresentationServiceNotifier.scala | 12 +- .../anoncred-presentation-schema-example.json | 26 +++ .../service/PresentationServiceSpec.scala | 114 +++++++++++- .../PresentationServiceSpecHelper.scala | 68 ++++++- .../repository/JdbcCredentialRepository.scala | 31 +++ .../io/iohk/atala/agent/server/Main.scala | 2 +- .../server/jobs/PresentBackgroundJobs.scala | 107 +++++++---- .../controller/PresentProofController.scala | 7 + 18 files changed, 547 insertions(+), 109 deletions(-) create mode 100644 pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json diff --git a/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/AnoncredLib.scala b/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/AnoncredLib.scala index 9bd681714c..775c7987b1 100644 --- a/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/AnoncredLib.scala +++ b/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/AnoncredLib.scala @@ -141,12 +141,12 @@ object AnoncredLib { def createPresentation( presentationRequest: PresentationRequest, - credentialRequests: Seq[CredentialAndRequestedAttributesPredicates], + credentialRequests: Seq[CredentialRequests], selfAttested: Map[String, String], linkSecret: LinkSecret, schemas: Map[SchemaId, SchemaDef], credentialDefinitions: Map[CredentialDefinitionId, CredentialDefinition], - ): Either[uniffi.anoncreds_wrapper.AnoncredsException.CreatePresentationException, Presentation] = { + ): Either[uniffi.anoncreds_wrapper.AnoncredsException.CreatePresentationException, AnoncredPresentation] = { try { Right( uniffi.anoncreds_wrapper @@ -181,7 +181,7 @@ object AnoncredLib { // FIXME its always return false .... def verifyPresentation( - presentation: Presentation, + presentation: AnoncredPresentation, presentationRequest: PresentationRequest, schemas: Map[SchemaId, SchemaDef], credentialDefinitions: Map[CredentialDefinitionId, CredentialDefinition], diff --git a/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/Models.scala b/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/Models.scala index 0324c89601..531ffdb169 100644 --- a/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/Models.scala +++ b/pollux/lib/anoncreds/src/main/scala/io/iohk/atala/pollux/anoncreds/Models.scala @@ -1,20 +1,6 @@ package io.iohk.atala.pollux.anoncreds -import uniffi.anoncreds_wrapper.{ - Nonce, - Credential as UniffiCredential, - CredentialRequests as UniffiCredentialRequests, - CredentialDefinition as UniffiCredentialDefinition, - CredentialDefinitionPrivate as UniffiCredentialDefinitionPrivate, - CredentialKeyCorrectnessProof as UniffiCredentialKeyCorrectnessProof, - CredentialOffer as UniffiCredentialOffer, - CredentialRequest as UniffiCredentialRequest, - CredentialRequestMetadata as UniffiCredentialRequestMetadata, - LinkSecret as UniffiLinkSecret, - Schema as UniffiSchema, - Presentation as UniffiPresentation, - PresentationRequest as UniffiPresentationRequest, -} +import uniffi.anoncreds_wrapper.{Nonce, Credential as UniffiCredential, CredentialDefinition as UniffiCredentialDefinition, CredentialDefinitionPrivate as UniffiCredentialDefinitionPrivate, CredentialKeyCorrectnessProof as UniffiCredentialKeyCorrectnessProof, CredentialOffer as UniffiCredentialOffer, CredentialRequest as UniffiCredentialRequest, CredentialRequestMetadata as UniffiCredentialRequestMetadata, CredentialRequests as UniffiCredentialRequests, LinkSecret as UniffiLinkSecret, Presentation as UniffiPresentation, PresentationRequest as UniffiPresentationRequest, Schema as UniffiSchema} import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} import scala.jdk.CollectionConverters.* @@ -259,17 +245,17 @@ object Credential { } // **************************************************************************** -case class CredentialAndRequestedAttributesPredicates( +case class CredentialRequests( credential: Credential, requestedAttribute: Seq[String], requestedPredicate: Seq[String], ) -object CredentialAndRequestedAttributesPredicates { - given Conversion[CredentialAndRequestedAttributesPredicates, UniffiCredentialRequests] with { +object CredentialRequests { + given Conversion[CredentialRequests, UniffiCredentialRequests] with { import uniffi.anoncreds_wrapper.RequestedAttribute import uniffi.anoncreds_wrapper.RequestedPredicate - def apply(credentialRequests: CredentialAndRequestedAttributesPredicates): UniffiCredentialRequests = { + def apply(credentialRequests: CredentialRequests): UniffiCredentialRequests = { val credential = Credential.given_Conversion_Credential_UniffiCredential(credentialRequests.credential) val requestedAttributes = credentialRequests.requestedAttribute.map(a => RequestedAttribute(a, true)) val requestedPredicates = credentialRequests.requestedPredicate.map(p => RequestedPredicate(p)) @@ -277,9 +263,9 @@ object CredentialAndRequestedAttributesPredicates { } } - given Conversion[UniffiCredentialRequests, CredentialAndRequestedAttributesPredicates] with { - def apply(credentialRequests: UniffiCredentialRequests): CredentialAndRequestedAttributesPredicates = { - CredentialAndRequestedAttributesPredicates( + given Conversion[UniffiCredentialRequests, CredentialRequests] with { + def apply(credentialRequests: UniffiCredentialRequests): CredentialRequests = { + CredentialRequests( Credential.given_Conversion_UniffiCredential_Credential(credentialRequests.getCredential()), credentialRequests .getRequestedAttribute() @@ -316,17 +302,17 @@ object PresentationRequest { // **************************************************************************** -case class Presentation(data: String) -object Presentation { - given Conversion[Presentation, UniffiPresentation] with { - def apply(presentation: Presentation): UniffiPresentation = { +case class AnoncredPresentation(data: String) +object AnoncredPresentation { + given Conversion[AnoncredPresentation, UniffiPresentation] with { + def apply(presentation: AnoncredPresentation): UniffiPresentation = { UniffiPresentation(presentation.data) } } - given Conversion[UniffiPresentation, Presentation] with { - def apply(presentation: UniffiPresentation): Presentation = { - Presentation(presentation.getJson()) + given Conversion[UniffiPresentation, AnoncredPresentation] with { + def apply(presentation: UniffiPresentation): AnoncredPresentation = { + AnoncredPresentation(presentation.getJson()) } } } diff --git a/pollux/lib/anoncredsTest/src/test/scala/io/iohk/atala/pollux/anoncreds/PoCNewLib.scala b/pollux/lib/anoncredsTest/src/test/scala/io/iohk/atala/pollux/anoncreds/PoCNewLib.scala index 7172e28ea7..6e6a3c1d5a 100644 --- a/pollux/lib/anoncredsTest/src/test/scala/io/iohk/atala/pollux/anoncreds/PoCNewLib.scala +++ b/pollux/lib/anoncredsTest/src/test/scala/io/iohk/atala/pollux/anoncreds/PoCNewLib.scala @@ -109,7 +109,7 @@ class PoCNewLib extends AnyFlatSpec { val presentation = AnoncredLib.createPresentation( presentationRequest, // : PresentationRequest, Seq( - CredentialAndRequestedAttributesPredicates(processedCredential, Seq("sex"), Seq("age")) + CredentialRequests(processedCredential, Seq("sex"), Seq("age")) ), // credentials: Seq[Credential], Map(), // selfAttested: Map[String, String], linkSecret.secret, // linkSecret: LinkSecret, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala index 2cbae20941..7ec80dcde7 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala @@ -73,6 +73,15 @@ final case class ValidIssuedCredentialRecord( subjectId: Option[String] ) +final case class ValidFullIssuedCredentialRecord( + id: DidCommID, + issuedCredential: Option[IssueCredential], + credentialFormat: CredentialFormat, + schemaId: Option[String], + credentialDefinitionId: Option[UUID], + subjectId: Option[String] +) + object IssueCredentialRecord { enum Role: diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala index 194d706178..7009ad0021 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/error/PresentationError.scala @@ -20,4 +20,6 @@ object PresentationError { final case class UnsupportedCredentialFormat(vcFormat: String) extends PresentationError final case class InvalidAnoncredPresentationRequest(error: String) extends PresentationError final case class MissingAnoncredPresentationRequest(error: String) extends PresentationError + + final case class AnoncredPresentationCreationError(cause: Throwable) extends PresentationError } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala index 593f2e7725..186bc0276c 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala @@ -74,6 +74,10 @@ trait CredentialRepository { def getValidIssuedCredentials(recordId: Seq[DidCommID]): RIO[WalletAccessContext, Seq[ValidIssuedCredentialRecord]] + def getValidAnoncredIssuedCredentials( + recordIds: Seq[DidCommID] + ): RIO[WalletAccessContext, Seq[ValidFullIssuedCredentialRecord]] + def updateAfterFail( recordId: DidCommID, failReason: Option[String] diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala index 4624e0a156..5b2ba4dbea 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala @@ -137,6 +137,34 @@ class CredentialRepositoryInMemory( .toSeq } + override def getValidAnoncredIssuedCredentials( + recordId: Seq[DidCommID] + ): RIO[WalletAccessContext, Seq[ValidFullIssuedCredentialRecord]] = { + for { + storeRef <- walletStoreRef + store <- storeRef.get + } yield store.values + .filter(rec => + recordId.contains( + rec.id + ) && rec.issueCredentialData.isDefined + && rec.schemaId.isDefined + && rec.credentialDefinitionId.isDefined + && rec.credentialFormat == CredentialFormat.AnonCreds + ) + .map(rec => + ValidFullIssuedCredentialRecord( + rec.id, + rec.issueCredentialData, + rec.credentialFormat, + rec.schemaId, + rec.credentialDefinitionId, + rec.subjectId + ) + ) + .toSeq + } + override def deleteIssueCredentialRecord(recordId: DidCommID): RIO[WalletAccessContext, Int] = { for { storeRef <- walletStoreRef diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala index 639bcc4a4e..9be0cfb4c2 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala @@ -2,6 +2,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.mercury.model.DidId import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProofType, ProposePresentation, RequestPresentation} +import io.iohk.atala.pollux.anoncreds.AnoncredPresentation import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.presentation.Options import io.iohk.atala.pollux.core.model.{DidCommID, PresentationRecord} @@ -142,12 +143,18 @@ object MockPresentationService extends Mock[PresentationService] { ignoreWithZeroRetries: Boolean ): IO[PresentationError, Seq[PresentationRecord]] = ??? - override def createPresentationPayloadFromRecord( + override def createJwtPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, issuanceDate: Instant ): IO[PresentationError, PresentationPayload] = ??? + override def createAnoncredPresentationPayloadFromRecord( + record: DidCommID, + issuer: Issuer, + issuanceDate: Instant + ): IO[PresentationError, AnoncredPresentation] = ??? + override def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, limit: Int, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala index 911697351a..c191dba5d1 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala @@ -2,6 +2,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.mercury.model.* import io.iohk.atala.mercury.protocol.presentproof.* +import io.iohk.atala.pollux.anoncreds.AnoncredPresentation import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.presentation.* @@ -39,12 +40,18 @@ trait PresentationService { ignoreWithZeroRetries: Boolean ): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] - def createPresentationPayloadFromRecord( + def createJwtPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, PresentationPayload] + def createAnoncredPresentationPayloadFromRecord( + record: DidCommID, + issuer: Issuer, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] + def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, limit: Int, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala index 340e36ec3b..2090e38628 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala @@ -6,11 +6,15 @@ import io.circe.* import io.circe.parser.* import io.circe.syntax.* import io.iohk.atala.mercury.model.* +import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, IssueCredentialIssuedFormat} import io.iohk.atala.mercury.protocol.presentproof.* +import io.iohk.atala.pollux.anoncreds.* import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.error.PresentationError.* import io.iohk.atala.pollux.core.model.presentation.* +import io.iohk.atala.pollux.core.model.schema.CredentialSchema.parseCredentialSchema +import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import io.iohk.atala.pollux.core.repository.{CredentialRepository, PresentationRepository} import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 import io.iohk.atala.pollux.vc.jwt.* @@ -18,12 +22,16 @@ import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect import zio.* +import java.net.URI import java.rmi.UnexpectedException import java.time.Instant import java.util as ju import java.util.{UUID, Base64 as JBase64} private class PresentationServiceImpl( + credentialDefinitionService: CredentialDefinitionService, + uriDereferencer: URIDereferencer, + linkSecretService: LinkSecretService, presentationRepository: PresentationRepository, credentialRepository: CredentialRepository, maxRetries: Int = 5, // TODO move to config @@ -58,7 +66,7 @@ private class PresentationServiceImpl( } yield record } - override def createPresentationPayloadFromRecord( + override def createJwtPresentationPayloadFromRecord( recordId: DidCommID, prover: Issuer, issuanceDate: Instant @@ -91,15 +99,55 @@ private class PresentationServiceImpl( ) ) - presentationPayload <- createPresentationPayloadFromCredential( + presentationPayload <- createJwtPresentationPayloadFromCredential( issuedCredentials, - record.credentialFormat, requestPresentation, prover ) } yield presentationPayload } + override def createAnoncredPresentationPayloadFromRecord( + recordId: DidCommID, + prover: Issuer, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { + + for { + maybeRecord <- presentationRepository + .getPresentationRecord(recordId) + .mapError(RepositoryError.apply) + record <- ZIO + .fromOption(maybeRecord) + .mapError(_ => RecordIdNotFound(recordId)) + credentialsToUse <- ZIO + .fromOption(record.credentialsToUse) + .mapError(_ => InvalidFlowStateError(s"No request found for this record: $recordId")) + requestPresentation <- ZIO + .fromOption(record.requestPresentationData) + .mapError(_ => InvalidFlowStateError(s"RequestPresentation not found: $recordId")) + issuedValidCredentials <- credentialRepository + .getValidAnoncredIssuedCredentials(credentialsToUse.map(DidCommID(_))) + .mapError(RepositoryError.apply) + signedCredentials = issuedValidCredentials.flatMap(_.issuedCredential) + issuedCredentials <- ZIO.fromEither( + Either.cond( + signedCredentials.nonEmpty, + signedCredentials, + PresentationError.IssuedCredentialNotFoundError( + new Throwable("No matching issued credentials found in prover db") + ) + ) + ) + presentationPayload <- createAnoncredPresentationPayloadFromCredential( + issuedCredentials, + issuedValidCredentials.flatMap(_.schemaId), + issuedValidCredentials.flatMap(_.credentialDefinitionId), + requestPresentation, + ) + } yield presentationPayload + } + override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] = credential.maybeId.map(_.split("/").last).map(UUID.fromString) @@ -324,32 +372,22 @@ private class PresentationServiceImpl( } /** All credentials MUST be of the same format */ - private def createPresentationPayloadFromCredential( + private def createJwtPresentationPayloadFromCredential( issuedCredentials: Seq[String], - format: CredentialFormat, requestPresentation: RequestPresentation, prover: Issuer ): IO[PresentationError, PresentationPayload] = { val verifiableCredentials: Either[ PresentationError.PresentationDecodingError, - Seq[JwtVerifiableCredentialPayload | AnoncredVerifiableCredentialPayload] + Seq[JwtVerifiableCredentialPayload] ] = issuedCredentials.map { signedCredential => - format match { - case CredentialFormat.JWT => - decode[io.iohk.atala.mercury.model.Base64](signedCredential) - .flatMap(x => Right(new String(java.util.Base64.getDecoder().decode(x.base64)))) - .flatMap(x => Right(JwtVerifiableCredentialPayload(JWT(x)))) - .left - .map(err => PresentationDecodingError(new Throwable(s"JsonData decoding error: $err"))) - case CredentialFormat.AnonCreds => - decode[io.iohk.atala.mercury.model.Base64](signedCredential) - .flatMap(x => Right(new String(java.util.Base64.getDecoder().decode(x.base64)))) - .flatMap(x => Right(AnoncredVerifiableCredentialPayload(x))) - .left - .map(err => PresentationDecodingError(new Throwable(s"JsonData decoding error: $err"))) - } + decode[io.iohk.atala.mercury.model.Base64](signedCredential) + .flatMap(x => Right(new String(java.util.Base64.getDecoder.decode(x.base64)))) + .flatMap(x => Right(JwtVerifiableCredentialPayload(JWT(x)))) + .left + .map(err => PresentationDecodingError(new Throwable(s"JsonData decoding error: $err"))) }.sequence val maybePresentationOptions @@ -401,7 +439,98 @@ private class PresentationServiceImpl( } ) } yield presentationPayload + } + private def createAnoncredPresentationPayloadFromCredential( + issuedCredentials: Seq[IssueCredential], + schemaIds: Seq[String], + credentialDefinitionIds: Seq[UUID], + requestPresentation: RequestPresentation, + ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { + for { + schemaMap <- + ZIO + .collectAll(schemaIds.map { schemaId => + resolveSchema(schemaId) + }) + .map(_.toMap) + credentialDefinitionMap <- + ZIO + .collectAll(credentialDefinitionIds.map { credentialDefinitionId => + for { + credentialDefinition <- credentialDefinitionService + .getByGUID(credentialDefinitionId) + .mapError(e => UnexpectedError(e.toString)) + } yield (credentialDefinition.longId, CredentialDefinition(credentialDefinition.definition.toString)) + }) + .map(_.toMap) + + verifiableCredentials <- + ZIO.collectAll( + issuedCredentials + .flatMap(_.attachments) + .filter(attachment => attachment.format.contains(IssueCredentialIssuedFormat.Anoncred.name)) + .map(_.data) + .map { + case Base64(data) => Right(new String(JBase64.getUrlDecoder.decode(data))) + case _ => Left(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) + } + .map(ZIO.fromEither(_)) + ) + presentationRequestAttachment <- ZIO.fromEither( + requestPresentation.attachments.headOption.toRight(InvalidAnoncredPresentationRequest("Missing Presentation")) + ) + presentationRequestData <- + presentationRequestAttachment.data match + case Base64(data) => ZIO.succeed(new String(JBase64.getUrlDecoder.decode(data))) + case _ => ZIO.fail(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) + deserializedPresentationRequestData <- + AnoncredPresentationRequestV1.schemaSerDes + .deserialize(presentationRequestData) + .mapError(error => InvalidAnoncredPresentationRequest(error.error)) + linkSecret <- + linkSecretService + .fetchOrCreate() + .map(_.secret) + .mapError(t => AnoncredPresentationCreationError(t.cause)) + presentation <- + ZIO + .fromEither( + AnoncredLib.createPresentation( + PresentationRequest(presentationRequestData), + verifiableCredentials.map(verifiableCredential => + CredentialRequests( + Credential(verifiableCredential), + deserializedPresentationRequestData.requested_attributes.keys.toSeq, // TO FIX + deserializedPresentationRequestData.requested_predicates.keys.toSeq // TO FIX + ) + ), + Map.empty, // TO FIX + linkSecret, + schemaMap, + credentialDefinitionMap + ) + ) + .mapError((t: Throwable) => AnoncredPresentationCreationError(t)) + } yield presentation + } + + private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, SchemaDef)] = { + for { + uri <- ZIO.attempt(new URI(schemaId)).mapError(e => UnexpectedError(e.getMessage)) + content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) + vcSchema <- parseCredentialSchema(content).mapError(e => UnexpectedError(e.message)) + anoncredSchema <- AnoncredSchemaSerDesV1.schemaSerDes + .deserialize(vcSchema.schema) + .mapError(e => UnexpectedError(e.error)) + anoncredLibSchema = + SchemaDef( + schemaId, + anoncredSchema.version, + anoncredSchema.attrNames, + anoncredSchema.issuerId + ) + } yield (schemaId, anoncredLibSchema) } def acceptRequestPresentation( @@ -752,6 +881,9 @@ private class PresentationServiceImpl( } object PresentationServiceImpl { - val layer: URLayer[PresentationRepository & CredentialRepository, PresentationService] = - ZLayer.fromFunction(PresentationServiceImpl(_, _)) + val layer: URLayer[ + CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, + PresentationService + ] = + ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _, _)) } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala index 8b11864fa4..0b890e1e2b 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala @@ -3,6 +3,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.event.notification.{Event, EventNotificationService} import io.iohk.atala.mercury.model.DidId import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProofType, ProposePresentation, RequestPresentation} +import io.iohk.atala.pollux.anoncreds.AnoncredPresentation import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.presentation.Options import io.iohk.atala.pollux.core.model.{DidCommID, PresentationRecord} @@ -143,12 +144,19 @@ class PresentationServiceNotifier( ): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] = svc.getPresentationRecords(ignoreWithZeroRetries) - override def createPresentationPayloadFromRecord( + override def createJwtPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, PresentationPayload] = - svc.createPresentationPayloadFromRecord(record, issuer, issuanceDate) + svc.createJwtPresentationPayloadFromRecord(record, issuer, issuanceDate) + + override def createAnoncredPresentationPayloadFromRecord( + record: DidCommID, + issuer: Issuer, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = + svc.createAnoncredPresentationPayloadFromRecord(record, issuer, issuanceDate) override def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, diff --git a/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json b/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json new file mode 100644 index 0000000000..3d1f765fa9 --- /dev/null +++ b/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json @@ -0,0 +1,26 @@ +{ + "guid": "1631026d-5d55-3285-8ccd-bd70480cfbdc", + "id": "329da384-b2bb-497f-a605-4118dec75d31", + "longId": "did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff/329da384-b2bb-497f-a605-4118dec75d31?version=5.0.0", + "name": "DrivingLicense", + "version": "5.0.0", + "tags": [ + "string" + ], + "description": "Simple credential schema for the driving licence verifiable credential.", + "type": "AnoncredSchemaV1", + "schema": { + "name": "schema:uri2", + "version": "1.0", + "attrNames": [ + "name", + "sex", + "age" + ], + "issuerId": "did:prism:issuer" + }, + "author": "did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff", + "authored": "2023-04-06T08:48:01.654162Z", + "kind": "CredentialSchema", + "self": "/schema-registry/schemas/1631026d-5d55-3285-8ccd-bd70480cfbdc" +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala index a311a3a2b4..bb11014c79 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala @@ -3,23 +3,25 @@ package io.iohk.atala.pollux.core.service import io.circe.parser.decode import io.circe.syntax.* import io.iohk.atala.mercury.model.{AttachmentDescriptor, Base64, DidId} -import io.iohk.atala.mercury.protocol.issuecredential.IssueCredential +import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, IssueCredentialIssuedFormat} import io.iohk.atala.mercury.protocol.presentproof.* +import io.iohk.atala.pollux.anoncreds.AnoncredLib import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.* import io.iohk.atala.pollux.core.model.PresentationRecord.* import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.error.PresentationError.* import io.iohk.atala.pollux.core.model.presentation.Options +import io.iohk.atala.pollux.core.model.schema.CredentialDefinition.Input import io.iohk.atala.pollux.core.repository.{CredentialRepository, PresentationRepository} -import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 +import io.iohk.atala.pollux.core.service.serdes.{AnoncredPresentationRequestV1, AnoncredPresentationV1} import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* import zio.test.* import zio.test.Assertion.* -import java.time.Instant +import java.time.{Instant, OffsetDateTime} import java.util.Base64 as JBase64 object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSpecHelper { @@ -207,7 +209,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp record <- svc.getPresentationRecord(DidCommID()) } yield assertTrue(record.isEmpty) }, - test("createPresentationPayloadFromRecord returns jwt prsentation payload") { + test("createJwtPresentationPayloadFromRecord returns jwt presentation payload") { for { repo <- ZIO.service[CredentialRepository] aIssueCredentialRecord = issueCredentialRecord(CredentialFormat.JWT) @@ -229,11 +231,113 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp PresentationRecord.ProtocolState.RequestPending ) issuer = createIssuer(DID("did:prism:issuer")) - aPresentationPayload <- svc.createPresentationPayloadFromRecord(aRecord.id, issuer, Instant.now()) + aPresentationPayload <- svc.createJwtPresentationPayloadFromRecord(aRecord.id, issuer, Instant.now()) } yield { assertTrue(aPresentationPayload.toJwtPresentationPayload.iss == "did:prism:issuer") } }, + test("createAnoncredPresentationPayloadFromRecord returns Anoncred presentation payload") { + for { + svc <- ZIO.service[CredentialDefinitionService] + issuerId = "did:prism:issuer" + holderID = "did:prism:holder" + schemaId = "resource:///anoncred-presentation-schema-example.json" + credentialDefinitionDb <- svc.create( + Input( + name = "Credential Definition Name", + description = "Credential Definition Description", + version = "1.2", + signatureType = "CL", + tag = "tag", + author = issuerId, + authored = Some(OffsetDateTime.parse("2022-03-10T12:00:00Z")), + schemaId = schemaId, + supportRevocation = false + ) + ) + repo <- ZIO.service[CredentialRepository] + schema = AnoncredLib.createSchema( + schemaId, + "0.1.0", + Set("name", "sex", "age"), + issuerId + ) + linkSecretService <- ZIO.service[LinkSecretService] + linkSecret <- linkSecretService.fetchOrCreate() + credentialDefinition = AnoncredLib.createCredDefinition(issuerId, schema, "tag", supportRevocation = false) + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) + credential = + AnoncredLib + .createCredential( + credentialDefinition.cd, + credentialDefinition.cdPrivate, + credentialOffer, + credentialRequest.request, + Seq( + ("name", "Miguel"), + ("sex", "M"), + ("age", "31"), + ) + ) + .data + issueCredential = IssueCredential( + from = DidId(issuerId), + to = DidId(holderID), + body = IssueCredential.Body(), + attachments = Seq( + AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(IssueCredentialIssuedFormat.Anoncred.name), + payload = credential.getBytes() + ) + ) + ) + aIssueCredentialRecord = + IssueCredentialRecord( + id = DidCommID(), + createdAt = Instant.now, + updatedAt = None, + thid = DidCommID(), + schemaId = Some(schemaId), + credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialFormat = CredentialFormat.AnonCreds, + role = IssueCredentialRecord.Role.Issuer, + subjectId = None, + validityPeriod = None, + automaticIssuance = None, + protocolState = IssueCredentialRecord.ProtocolState.CredentialReceived, + offerCredentialData = None, + requestCredentialData = None, + anonCredsRequestMetadata = None, + issueCredentialData = Some(issueCredential), + issuedCredentialRaw = + Some(issueCredential.attachments.map(_.data.asJson.noSpaces).headOption.getOrElse("???")), + issuingDID = None, + metaRetries = 5, + metaNextRetry = Some(Instant.now()), + metaLastFailure = None, + ) + _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) + svc <- ZIO.service[PresentationService] + aRecord <- svc.createAnoncredRecord() + repo <- ZIO.service[PresentationRepository] + _ <- repo.updatePresentationWithCredentialsToUse( + aRecord.id, + Some(Seq(aIssueCredentialRecord.id.value)), + PresentationRecord.ProtocolState.RequestPending + ) + issuer = createIssuer(DID("did:prism:issuer")) + aPresentationPayload <- svc.createAnoncredPresentationPayloadFromRecord(aRecord.id, issuer, Instant.now()) + validation <- AnoncredPresentationV1.schemaSerDes.validate(aPresentationPayload.data) + presentation <- AnoncredPresentationV1.schemaSerDes.deserialize(aPresentationPayload.data) + } yield { + assertTrue(validation) + assert( + presentation.proof.proofs.headOption.flatMap(_.primary_proof.eq_proof.revealed_attrs.headOption.map(_._1)) + )(isSome(equalTo("sex"))) + } + }, test("markRequestPresentationSent returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala index e3456f5fca..7e20c41bd9 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala @@ -1,19 +1,16 @@ package io.iohk.atala.pollux.core.service import com.nimbusds.jose.jwk.* +import io.iohk.atala.agent.walletapi.memory.GenericSecretStorageInMemory import io.iohk.atala.mercury.model.{AttachmentDescriptor, DidId} import io.iohk.atala.mercury.protocol.presentproof.* import io.iohk.atala.mercury.{AgentPeerService, PeerDID} import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.error.PresentationError -import io.iohk.atala.pollux.core.repository.{ - CredentialRepository, - CredentialRepositoryInMemory, - PresentationRepository, - PresentationRepositoryInMemory -} +import io.iohk.atala.pollux.core.repository.* +import io.iohk.atala.pollux.core.service.serdes.* import io.iohk.atala.pollux.vc.jwt.* -import io.iohk.atala.shared.models.WalletAccessContext +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* import java.security.* @@ -22,14 +19,28 @@ import java.util.UUID trait PresentationServiceSpecHelper { + protected val defaultWalletLayer = ZLayer.succeed(WalletAccessContext(WalletId.default)) + val peerDidAgentLayer = AgentPeerService.makeLayer(PeerDID.makePeerDid(serviceEndpoint = Some("http://localhost:9099"))) - val presentationServiceLayer = ZLayer.make[PresentationService & PresentationRepository & CredentialRepository]( + val genericSecretStorageLayer = GenericSecretStorageInMemory.layer + val uriDereferencerLayer = ResourceURIDereferencerImpl.layer + val credentialDefLayer = + CredentialDefinitionRepositoryInMemory.layer ++ uriDereferencerLayer >>> CredentialDefinitionServiceImpl.layer + val linkSecretLayer = genericSecretStorageLayer >+> LinkSecretServiceImpl.layer + + val presentationServiceLayer = ZLayer.make[ + PresentationService & CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & + CredentialRepository + ]( PresentationServiceImpl.layer, + credentialDefLayer, + uriDereferencerLayer, + linkSecretLayer, PresentationRepositoryInMemory.layer, CredentialRepositoryInMemory.layer - ) + ) ++ defaultWalletLayer def createIssuer(did: DID) = { val keyGen = KeyPairGenerator.getInstance("EC") @@ -159,4 +170,43 @@ trait PresentationServiceSpecHelper { ) } + def createAnoncredRecord( + pairwiseVerifierDID: DidId = DidId("did:prism:issuer"), + pairwiseProverDID: DidId = DidId("did:prism:prover-pairwise"), + thid: DidCommID = DidCommID() + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + val anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + requested_attributes = Map( + "sex" -> AnoncredRequestedAttributeV1( + name = "sex", + restrictions = List( + AnoncredAttributeRestrictionV1( + schema_id = None, + cred_def_id = Some("$CRED_DEF_ID"), + non_revoked = None + ) + ) + ) + ), + requested_predicates = Map( + "age" -> AnoncredRequestedPredicateV1( + name = "age", + p_type = ">=", + p_value = 18, + restrictions = List.empty + ) + ), + name = "proof_req_1", + nonce = "1103253414365527824079144", + version = "0.1", + non_revoked = Some(AnoncredNonRevokedIntervalV1(from = Some(1), to = Some(4))) + ) + svc.createAnoncredPresentationRecord( + thid = thid, + pairwiseVerifierDID = pairwiseVerifierDID, + pairwiseProverDID = pairwiseProverDID, + connectionId = Some("connectionId"), + anoncredPresentationRequestV1 + ) + } } diff --git a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala index dd7b5d5319..c56e3c10ac 100644 --- a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala +++ b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala @@ -448,6 +448,37 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ } + override def getValidAnoncredIssuedCredentials( + recordIds: Seq[DidCommID] + ): RIO[WalletAccessContext, Seq[ValidFullIssuedCredentialRecord]] = { + val idAsStrings = recordIds.map(_.toString) + val nel = NonEmptyList.of(idAsStrings.head, idAsStrings.tail: _*) + val inClauseFragment = Fragments.in(fr"id", nel) + + val cxnIO = sql""" + | SELECT + | id, + | issue_credential_data, + | credential_format, + | schema_id, + | credential_definition_id, + | subject_id + | FROM public.issue_credential_records + | WHERE 1=1 + | AND issue_credential_data IS NOT NULL + | AND schema_id IS NOT NULL + | AND credential_definition_id IS NOT NULL + | AND credential_format = 'AnonCreds' + | AND $inClauseFragment + """.stripMargin + .query[ValidFullIssuedCredentialRecord] + .to[Seq] + + cxnIO + .transactWallet(xa) + + } + override def deleteIssueCredentialRecord(recordId: DidCommID): RIO[WalletAccessContext, Int] = { val cxnIO = sql""" | DELETE diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Main.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Main.scala index 2511bc6960..3fd14f0a7f 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Main.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Main.scala @@ -149,7 +149,7 @@ object MainApp extends ZIOAppDefault { DIDServiceImpl.layer, EntityServiceImpl.layer, ManagedDIDServiceWithEventNotificationImpl.layer, - PresentationServiceImpl.layer >>> PresentationServiceNotifier.layer, + LinkSecretServiceImpl.layer >>> PresentationServiceImpl.layer >>> PresentationServiceNotifier.layer, VerificationPolicyServiceImpl.layer, WalletManagementServiceImpl.layer, // authentication diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/PresentBackgroundJobs.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/PresentBackgroundJobs.scala index b5e48c21db..6a005c27a0 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/PresentBackgroundJobs.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/PresentBackgroundJobs.scala @@ -9,7 +9,11 @@ import io.iohk.atala.agent.server.jobs.BackgroundJobError.{ InvalidState, NotImplemented } +import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError +import io.iohk.atala.agent.walletapi.service.ManagedDIDService +import io.iohk.atala.agent.walletapi.storage.DIDNonSecretStorage import io.iohk.atala.castor.core.model.did.* +import io.iohk.atala.castor.core.service.DIDService import io.iohk.atala.mercury.* import io.iohk.atala.mercury.model.* import io.iohk.atala.mercury.protocol.presentproof.* @@ -19,18 +23,15 @@ import io.iohk.atala.pollux.core.model.error.PresentationError.* import io.iohk.atala.pollux.core.model.error.{CredentialServiceError, PresentationError} import io.iohk.atala.pollux.core.service.{CredentialService, PresentationService} import io.iohk.atala.pollux.vc.jwt.{JWT, JwtPresentation, DidResolver as JwtDidResolver} +import io.iohk.atala.resolvers.DIDResolver import io.iohk.atala.shared.utils.DurationOps.toMetricsSeconds import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect import zio.* import zio.metrics.* import zio.prelude.Validation import zio.prelude.ZValidation.* -import io.iohk.atala.agent.walletapi.storage.DIDNonSecretStorage -import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError -import io.iohk.atala.resolvers.DIDResolver + import java.time.{Clock, Instant, ZoneId} -import io.iohk.atala.castor.core.service.DIDService -import io.iohk.atala.agent.walletapi.service.ManagedDIDService object PresentBackgroundJobs extends BackgroundJobsHelper { val presentProofExchanges = { @@ -230,7 +231,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, PresentationPending, - _, + credentialFormat, oRequestPresentation, _, _, @@ -239,7 +240,6 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _ ) => // Prover - // signedJwtPresentation = JwtPresentation.toEncodedJwt(w3cPresentationPayload, prover) oRequestPresentation match case None => ZIO.fail(InvalidState("PresentationRecord 'RequestPending' with no Record")) case Some(requestPresentation) => // TODO create build method in mercury for Presentation @@ -249,35 +249,72 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { presentationService <- ZIO.service[PresentationService] prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - presentationPayload <- presentationService - .createPresentationPayloadFromRecord( - id, - prover, - Instant.now() - ) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - signedJwtPresentation = JwtPresentation.toEncodedJwt( - presentationPayload.toW3CPresentationPayload, - prover - ) - presentation <- ZIO.succeed( - Presentation( - body = Presentation.Body( - goal_code = requestPresentation.body.goal_code, - comment = requestPresentation.body.comment - ), - attachments = Seq( - AttachmentDescriptor - .buildBase64Attachment( - payload = signedJwtPresentation.value.getBytes(), - mediaType = Some("prism/jwt") + presentation <- + credentialFormat match { + case CredentialFormat.JWT => + for { + presentationPayload <- + presentationService + .createJwtPresentationPayloadFromRecord( + id, + prover, + Instant.now() + ) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + signedJwtPresentation = JwtPresentation.toEncodedJwt( + presentationPayload.toW3CPresentationPayload, + prover ) - ), - thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), - from = requestPresentation.to, - to = requestPresentation.from - ) - ) + presentation <- ZIO.succeed( + Presentation( + body = Presentation.Body( + goal_code = requestPresentation.body.goal_code, + comment = requestPresentation.body.comment + ), + attachments = Seq( + AttachmentDescriptor + .buildBase64Attachment( + payload = signedJwtPresentation.value.getBytes(), + mediaType = Some("prism/jwt") + ) + ), + thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), + from = requestPresentation.to, + to = requestPresentation.from + ) + ) + } yield presentation + case CredentialFormat.AnonCreds => + for { + presentationPayload <- + presentationService + .createAnoncredPresentationPayloadFromRecord( + id, + prover, + Instant.now() + ) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + presentation <- ZIO.succeed( + Presentation( + body = Presentation.Body( + goal_code = requestPresentation.body.goal_code, + comment = requestPresentation.body.comment + ), + attachments = Seq( + AttachmentDescriptor + .buildBase64Attachment( + payload = presentationPayload.data.getBytes(), + mediaType = Some(PresentCredentialFormat.Anoncred.name), + format = Some(PresentCredentialFormat.Anoncred.name), + ) + ), + thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), + from = requestPresentation.to, + to = requestPresentation.from + ) + ) + } yield presentation + } _ <- presentationService .markPresentationGenerated(id, presentation) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala index 6ab89d8016..5a6056b3f7 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofController.scala @@ -47,8 +47,15 @@ object PresentProofController { ErrorResponse.badRequest(title = "InvalidFlowState", detail = Some(msg)) case PresentationError.MissingAnoncredPresentationRequest(msg) => ErrorResponse.badRequest(title = "Missing Anoncred Presentation Request", detail = Some(msg)) + case PresentationError.AnoncredPresentationCreationError(cause) => + ErrorResponse.badRequest(title = "Error Creating Anoncred Presentation", detail = Some(cause.toString)) case PresentationError.InvalidAnoncredPresentationRequest(msg) => ErrorResponse.badRequest(title = "Invalid Anoncred Presentation Request", detail = Some(msg)) + case PresentationError.NotMatchingPresentationCredentialFormat(cause) => + ErrorResponse.badRequest( + title = "Presentation and Credential Format Not Matching", + detail = Some(cause.toString) + ) case PresentationError.UnexpectedError(msg) => ErrorResponse.internalServerError(detail = Some(msg)) case PresentationError.IssuedCredentialNotFoundError(_) =>