diff --git a/pollux/lib/anoncreds/anoncreds-jvm-1.0-SNAPSHOT.jar b/pollux/lib/anoncreds/anoncreds-jvm-1.0-SNAPSHOT.jar index 99cde9911b..77851ec6e3 100644 Binary files a/pollux/lib/anoncreds/anoncreds-jvm-1.0-SNAPSHOT.jar and b/pollux/lib/anoncreds/anoncreds-jvm-1.0-SNAPSHOT.jar differ diff --git a/pollux/lib/anoncreds/native-lib/NATIVE/darwin-aarch64/libuniffi_anoncreds.dylib b/pollux/lib/anoncreds/native-lib/NATIVE/darwin-aarch64/libuniffi_anoncreds.dylib index 5c2899c872..bc94bcf375 100755 Binary files a/pollux/lib/anoncreds/native-lib/NATIVE/darwin-aarch64/libuniffi_anoncreds.dylib and b/pollux/lib/anoncreds/native-lib/NATIVE/darwin-aarch64/libuniffi_anoncreds.dylib differ diff --git a/pollux/lib/anoncreds/native-lib/NATIVE/darwin-x86-64/libuniffi_anoncreds.dylib b/pollux/lib/anoncreds/native-lib/NATIVE/darwin-x86-64/libuniffi_anoncreds.dylib index b5efdd6895..08206a099e 100755 Binary files a/pollux/lib/anoncreds/native-lib/NATIVE/darwin-x86-64/libuniffi_anoncreds.dylib and b/pollux/lib/anoncreds/native-lib/NATIVE/darwin-x86-64/libuniffi_anoncreds.dylib differ diff --git a/pollux/lib/anoncreds/native-lib/NATIVE/linux-aarch64/libuniffi_anoncreds.so b/pollux/lib/anoncreds/native-lib/NATIVE/linux-aarch64/libuniffi_anoncreds.so index 51495c275f..5ccdac39dd 100755 Binary files a/pollux/lib/anoncreds/native-lib/NATIVE/linux-aarch64/libuniffi_anoncreds.so and b/pollux/lib/anoncreds/native-lib/NATIVE/linux-aarch64/libuniffi_anoncreds.so differ diff --git a/pollux/lib/anoncreds/native-lib/NATIVE/linux-x86-64/libuniffi_anoncreds.so b/pollux/lib/anoncreds/native-lib/NATIVE/linux-x86-64/libuniffi_anoncreds.so index aea4d32d5e..5431d6e903 100755 Binary files a/pollux/lib/anoncreds/native-lib/NATIVE/linux-x86-64/libuniffi_anoncreds.so and b/pollux/lib/anoncreds/native-lib/NATIVE/linux-x86-64/libuniffi_anoncreds.so differ diff --git a/pollux/lib/anoncreds/native-lib/helper_script_to_update.sh b/pollux/lib/anoncreds/native-lib/helper_script_to_update.sh index 7f8f235dca..2242db0f53 100644 --- a/pollux/lib/anoncreds/native-lib/helper_script_to_update.sh +++ b/pollux/lib/anoncreds/native-lib/helper_script_to_update.sh @@ -2,21 +2,25 @@ RELEASES=https://github.com/input-output-hk/anoncreds-rs/releases/download/ VERSION=expose_presentation_methods_in_uniffi -SHA="16a7178ca979f7643788f6ebf8189131ab922a53" +SHA="ea59e64c4e6be3885c3c4907d02d63254e2025c4" -mkdir NATIVE_new -mkdir NATIVE_new/darwin-aarch64 -mkdir NATIVE_new/darwin-x86-64 -mkdir NATIVE_new/linux-aarch64 -mkdir NATIVE_new/linux-x86-64 +rm -rf NATIVE/darwin-aarch64 +rm -rf NATIVE/darwin-x86-64 +rm -rf NATIVE/linux-aarch64 +rm -rf NATIVE/linux-x86-64 -wget -c $RELEASES/$VERSION/library-darwin-aarch64-$SHA.tar.gz -O - | tar -xz -C NATIVE_new/darwin-aarch64 -wget -c $RELEASES/$VERSION/library-darwin-x86_64-$SHA.tar.gz -O - | tar -xz -C NATIVE_new/darwin-x86-64 -wget -c $RELEASES/$VERSION/library-linux-aarch64-$SHA.tar.gz -O - | tar -xz -C NATIVE_new/linux-aarch64 -wget -c $RELEASES/$VERSION/library-linux-x86_64-$SHA.tar.gz -O - | tar -xz -C NATIVE_new/linux-x86-64 +mkdir NATIVE/darwin-aarch64 +mkdir NATIVE/darwin-x86-64 +mkdir NATIVE/linux-aarch64 +mkdir NATIVE/linux-x86-64 -rename 'libanoncreds_uniffi.so' 'libuniffi_anoncreds.so' NATIVE_new/**/*.so -rename 'libanoncreds_uniffi.dylib' 'libuniffi_anoncreds.dylib' NATIVE_new/**/*.dylib +wget -c $RELEASES/$VERSION/library-darwin-aarch64-$SHA.tar.gz -O - | tar -xz -C NATIVE/darwin-aarch64 +wget -c $RELEASES/$VERSION/library-darwin-x86_64-$SHA.tar.gz -O - | tar -xz -C NATIVE/darwin-x86-64 +wget -c $RELEASES/$VERSION/library-linux-aarch64-$SHA.tar.gz -O - | tar -xz -C NATIVE/linux-aarch64 +wget -c $RELEASES/$VERSION/library-linux-x86_64-$SHA.tar.gz -O - | tar -xz -C NATIVE/linux-x86-64 + +rename 'libanoncreds_uniffi.so' 'libuniffi_anoncreds.so' NATIVE/**/*.so +rename 'libanoncreds_uniffi.dylib' 'libuniffi_anoncreds.dylib' NATIVE/**/*.dylib ## TODO missing anoncreds-jvm-1.0-SNAPSHOT.jar 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 d4987bd8de..9a932eec48 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 @@ -138,19 +138,23 @@ object AnoncredLib { type SchemaId = String type CredentialDefinitionId = String + // TODO FIX + // [info] uniffi.anoncreds.AnoncredsException$CreatePresentationException: Create Presentation: Error: Error: Invalid structure + // [info] Caused by: Predicate is not satisfied + def createPresentation( presentationRequest: PresentationRequest, - credentials: Seq[Credential], + credentialRequests: Seq[CredentialRequests], selfAttested: Map[String, String], linkSecret: LinkSecret, schemas: Map[SchemaId, SchemaDef], credentialDefinitions: Map[CredentialDefinitionId, CredentialDefinition], - ): Presentation = { + ): AnoncredPresentation = { anoncreds .Prover() .createPresentation( presentationRequest, - credentials.map(e => e: uniffi.anoncreds.Credential).asJava, // sequence credentials, + credentialRequests.map(i => i: uniffi.anoncreds.CredentialRequests).asJava, // sequence credentials, selfAttested.asJava, // record? self_attested, linkSecret, // LinkSecret link_secret, schemas.view.mapValues(i => i: uniffi.anoncreds.Schema).toMap.asJava, // record schemas, @@ -159,8 +163,17 @@ object AnoncredLib { ) } + // TODO FIX + // uniffi.anoncreds.AnoncredsException$ProcessCredentialException: Verify Presentation: Error: + // Requested restriction validation failed for "{"sex": Some("M")}" attributes [$and operator validation failed. + // [$eq operator validation failed for tag: "attr::sex::value", value: "F" [Proof rejected: "attr::sex::value" values are different: expected: "F", actual: "M"]]] + + // TODO FIX + // uniffi.anoncreds.AnoncredsException$ProcessCredentialException: Verify Presentation: Error: Requested restriction validation failed for "{"sex": Some("M")}" attributes [$and operator validation failed. [$eq operator validation failed for tag: "cred_def_id", value: "CRED_DEF_ID" [Proof rejected: "cred_def_id" values are different: expected: "CRED_DEF_ID", actual: "mock:uri3"]]] + + // 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 ca8670c18d..c3075f922a 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 @@ -9,10 +9,11 @@ import uniffi.anoncreds.{ CredentialOffer as UniffiCredentialOffer, CredentialRequest as UniffiCredentialRequest, CredentialRequestMetadata as UniffiCredentialRequestMetadata, + CredentialRequests as UniffiCredentialRequests, LinkSecret as UniffiLinkSecret, - Schema as UniffiSchema, Presentation as UniffiPresentation, PresentationRequest as UniffiPresentationRequest, + Schema as UniffiSchema } import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} @@ -257,6 +258,46 @@ object Credential { } } +// **************************************************************************** +case class CredentialRequests( + credential: Credential, + requestedAttribute: Seq[String], + requestedPredicate: Seq[String], +) + +object CredentialRequests { + given Conversion[CredentialRequests, UniffiCredentialRequests] with { + import uniffi.anoncreds.{RequestedAttribute, RequestedPredicate} + 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)) + UniffiCredentialRequests(credential, requestedAttributes.asJava, requestedPredicates.asJava) + } + } + + given Conversion[UniffiCredentialRequests, CredentialRequests] with { + def apply(credentialRequests: UniffiCredentialRequests): CredentialRequests = { + CredentialRequests( + Credential.given_Conversion_UniffiCredential_Credential(credentialRequests.getCredential()), + credentialRequests + .getRequestedAttribute() + .asScala + .toSeq + .filter(e => e.getRevealed()) + .map(e => e.getReferent()), + credentialRequests + .getRequestedPredicate() + .asScala + .toSeq + .map(e => e.getReferent()) + ) + } + } +} + +//UniffiCredentialRequests + // **************************************************************************** case class PresentationRequest(data: String) @@ -274,15 +315,15 @@ 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 405f1592c6..ade9cadb7a 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 @@ -58,7 +58,8 @@ class PoCNewLib extends AnyFlatSpec { // ############## println("*** holder " + ("*" * 100)) - val linkSecret = LinkSecretWithId("ID_of_some_secret_1") + val ls1 = LinkSecret("65965334953670062552662719679603258895632947953618378932199361160021795698890") + val linkSecret = LinkSecretWithId("ID_of_some_secret_1", ls1) val credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) println("*" * 100) @@ -88,7 +89,10 @@ class PoCNewLib extends AnyFlatSpec { "name":"proof_req_1", "version":"0.1", "requested_attributes": { - "sex":{"name":"sex"} + "sex":{"name":"sex", "restrictions":{"attr::sex::value":"M","cred_def_id":"$CRED_DEF_ID"}} + }, + "requested_predicates":{ + "age":{"name":"age", "p_type":">=", "p_value":18} } }""".stripMargin @@ -97,8 +101,10 @@ class PoCNewLib extends AnyFlatSpec { val presentation = AnoncredLib.createPresentation( presentationRequest, // : PresentationRequest, - Seq(credential), // credentials: Seq[Credential], - Map("sex" -> "M"), // selfAttested: Map[String, String], + Seq( + CredentialRequests(credential, Seq("sex"), Seq("age")) + ), // credentials: Seq[Credential], + Map(), // selfAttested: Map[String, String], linkSecret.secret, // linkSecret: LinkSecret, Map(credentialOffer.schemaId -> schema), // schemas: Map[SchemaId, SchemaDef], Map( 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..8f14afadde 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,17 @@ 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} +import scala.util.Try private class PresentationServiceImpl( + credentialDefinitionService: CredentialDefinitionService, + uriDereferencer: URIDereferencer, + linkSecretService: LinkSecretService, presentationRepository: PresentationRepository, credentialRepository: CredentialRepository, maxRetries: Int = 5, // TODO move to config @@ -58,7 +67,7 @@ private class PresentationServiceImpl( } yield record } - override def createPresentationPayloadFromRecord( + override def createJwtPresentationPayloadFromRecord( recordId: DidCommID, prover: Issuer, issuanceDate: Instant @@ -91,15 +100,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 +373,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 +440,100 @@ 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( + Try( + 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 + ) + ).toEither + ) + .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 +884,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 aebb6e4456..1e575fd9b8 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 @@ -151,7 +151,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(_) =>