From 3e08c31134d2dd4a8b1a10e123081fa5ba95cef4 Mon Sep 17 00:00:00 2001 From: Bassam Date: Mon, 30 Oct 2023 20:41:18 -0400 Subject: [PATCH 01/12] feat: add serdeser for anoncred presentation request (#768) Signed-off-by: Bassam Riman --- ...redPresentationRequestSchemaSerDesV1.scala | 158 ++++++++++++++++++ ...dPresentationRequestSchemaSerDesSpec.scala | 101 +++++++++++ ...redentialDefinitionSchemaSerDesSpec.scala} | 13 +- 3 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala create mode 100644 pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala rename pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/{helper/PublicCredentialDefinitionSerDesSpec.scala => serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala} (86%) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala new file mode 100644 index 0000000000..e788e52b7d --- /dev/null +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala @@ -0,0 +1,158 @@ +package io.iohk.atala.pollux.core.service.serdes + +import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes +import zio.* +import zio.json.* + +case class AnoncredPresentationRequestSchemaSerDesV1( + requested_attributes: Map[String, AnoncredRequestedAttribute], + requested_predicates: Map[String, AnoncredRequestedPredicate], + name: String, + nonce: String, + version: String, + non_revoked: Option[AnoncredNonRevokedInterval] +) + +case class AnoncredRequestedAttribute(name: String, restrictions: List[AnoncredAttributeRestriction]) + +case class AnoncredRequestedPredicate( + name: String, + p_type: String, + p_value: Int, + restrictions: List[AnoncredPredicateRestriction] +) + +case class AnoncredAttributeRestriction( + schema_id: Option[String], + cred_def_id: Option[String], + non_revoked: Option[AnoncredNonRevokedInterval] +) + +case class AnoncredPredicateRestriction( + schema_id: Option[String], + cred_def_id: Option[String], + non_revoked: Option[AnoncredNonRevokedInterval] +) + +case class AnoncredNonRevokedInterval(from: Option[Int], to: Option[Int]) + +object AnoncredPresentationRequestSchemaSerDesV1 { + val version: String = "PresentationRequestV1" + + private val schema: String = + """ + |{ + | "$schema": "http://json-schema.org/draft-07/schema#", + | "type": "object", + | "properties": { + | "requested_attributes": { + | "type": "object", + | "additionalProperties": { + | "type": "object", + | "properties": { + | "name": { "type": "string" }, + | "restrictions": { + | "type": "array", + | "items": { + | "type": "object", + | "properties": { + | "schema_id": { "type": "string" }, + | "cred_def_id": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | } + | } + | } + | }, + | "required": ["name", "restrictions"] + | } + | }, + | "requested_predicates": { + | "type": "object", + | "additionalProperties": { + | "type": "object", + | "properties": { + | "name": { "type": "string" }, + | "p_type": { "type": "string" }, + | "p_value": { "type": "integer" }, + | "restrictions": { + | "type": "array", + | "items": { + | "type": "object", + | "properties": { + | "schema_id": { "type": "string" }, + | "cred_def_id": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | } + | } + | } + | }, + | "required": ["name", "p_type", "p_value", "restrictions"] + | } + | }, + | "name": { "type": "string" }, + | "nonce": { "type": "string" }, + | "version": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | }, + | "required": ["requested_attributes", "requested_predicates", "name", "nonce", "version" ] + |} + | + |""".stripMargin + + val schemaSerDes: SchemaSerDes[AnoncredPresentationRequestSchemaSerDesV1] = SchemaSerDes(schema) + + given JsonDecoder[AnoncredRequestedAttribute] = + DeriveJsonDecoder.gen[AnoncredRequestedAttribute] + + given JsonEncoder[AnoncredRequestedAttribute] = + DeriveJsonEncoder.gen[AnoncredRequestedAttribute] + + given JsonDecoder[AnoncredRequestedPredicate] = + DeriveJsonDecoder.gen[AnoncredRequestedPredicate] + + given JsonEncoder[AnoncredRequestedPredicate] = + DeriveJsonEncoder.gen[AnoncredRequestedPredicate] + + given JsonDecoder[AnoncredAttributeRestriction] = + DeriveJsonDecoder.gen[AnoncredAttributeRestriction] + + given JsonEncoder[AnoncredNonRevokedInterval] = + DeriveJsonEncoder.gen[AnoncredNonRevokedInterval] + + given JsonDecoder[AnoncredNonRevokedInterval] = + DeriveJsonDecoder.gen[AnoncredNonRevokedInterval] + + given JsonEncoder[AnoncredAttributeRestriction] = + DeriveJsonEncoder.gen[AnoncredAttributeRestriction] + + given JsonDecoder[AnoncredPredicateRestriction] = + DeriveJsonDecoder.gen[AnoncredPredicateRestriction] + + given JsonEncoder[AnoncredPredicateRestriction] = + DeriveJsonEncoder.gen[AnoncredPredicateRestriction] + + given JsonDecoder[AnoncredPresentationRequestSchemaSerDesV1] = + DeriveJsonDecoder.gen[AnoncredPresentationRequestSchemaSerDesV1] + + given JsonEncoder[AnoncredPresentationRequestSchemaSerDesV1] = + DeriveJsonEncoder.gen[AnoncredPresentationRequestSchemaSerDesV1] + +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala new file mode 100644 index 0000000000..5545985f22 --- /dev/null +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala @@ -0,0 +1,101 @@ +package io.iohk.atala.pollux.core.service.serdes + +import zio.* +import zio.test.* +import zio.test.Assertion.* + +object AnoncredPresentationRequestSchemaSerDesSpec extends ZIOSpecDefault { + val json = + """ + |{ + | "requested_attributes": { + | "attribute1": { + | "name": "Attribute 1", + | "restrictions": [ + | { + | "cred_def_id": "credential_definition_id_of_attribute1", + | "non_revoked": { + | "from": 1635734400, + | "to": 1735734400 + | } + | } + | ] + | } + | }, + | "requested_predicates": { + | "predicate1": { + | "name": "Predicate 1", + | "p_type": ">=", + | "p_value": 18, + | "restrictions": [ + | { + | "schema_id": "schema_id_of_predicate1", + | "non_revoked": { + | "from": 1635734400 + | } + | } + | ] + | } + | }, + | "name": "Example Presentation Request", + | "nonce": "1234567890", + | "version": "1.0" + |} + |""".stripMargin + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("AnoncredPresentationRequestSerDes")( + test("should validate a correct schema") { + assertZIO(AnoncredPresentationRequestSchemaSerDesV1.schemaSerDes.validate(json))(isTrue) + }, + test("should deserialize correctly") { + val expectedPresentationRequest = + AnoncredPresentationRequestSchemaSerDesV1( + requested_attributes = Map( + "attribute1" -> AnoncredRequestedAttribute( + "Attribute 1", + List( + AnoncredAttributeRestriction( + None, + Some("credential_definition_id_of_attribute1"), + Some( + AnoncredNonRevokedInterval( + Some(1635734400), + Some(1735734400) + ) + ) + ) + ) + ) + ), + requested_predicates = Map( + "predicate1" -> + AnoncredRequestedPredicate( + "Predicate 1", + ">=", + 18, + List( + AnoncredPredicateRestriction( + Some("schema_id_of_predicate1"), + None, + Some( + AnoncredNonRevokedInterval( + Some(1635734400), + None + ) + ) + ) + ) + ) + ), + name = "Example Presentation Request", + nonce = "1234567890", + version = "1.0", + non_revoked = None + ) + + assertZIO(AnoncredPresentationRequestSchemaSerDesV1.schemaSerDes.deserialize(json))( + Assertion.equalTo(expectedPresentationRequest) + ) + } + ) +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/helper/PublicCredentialDefinitionSerDesSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala similarity index 86% rename from pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/helper/PublicCredentialDefinitionSerDesSpec.scala rename to pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala index 4a05b09fd5..aebb68bea8 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/helper/PublicCredentialDefinitionSerDesSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala @@ -1,16 +1,11 @@ -package io.iohk.atala.pollux.core.service.helper +package io.iohk.atala.pollux.core.service.serdes -import io.iohk.atala.pollux.core.service.serdes.PublicCredentialDefinitionSerDesV1 -import io.iohk.atala.pollux.core.service.serdes.PublicCredentialPrimaryPublicKeyV1 -import io.iohk.atala.pollux.core.service.serdes.PublicCredentialRevocationKeyV1 -import io.iohk.atala.pollux.core.service.serdes.PublicCredentialValueV1 import zio.* -import zio.test.* import zio.test.Assertion.* -import zio.test.assertZIO +import zio.test.* -object PublicCredentialDefinitionSerDesSpec extends ZIOSpecDefault { - val json = +object PublicCredentialDefinitionSchemaSerDesSpec extends ZIOSpecDefault { + val json: String = """ |{ | "schemaId": "resource:///anoncred-schema-example.json", From 94401d1739676e128d53622f27f07c85b7466bd2 Mon Sep 17 00:00:00 2001 From: Bassam Date: Tue, 31 Oct 2023 08:30:03 -0400 Subject: [PATCH 02/12] feat: Create/Store presentation proof request (#774) Signed-off-by: Bassam Riman --- .../core/model/error/PresentationError.scala | 2 + .../model/schema/validator/SchemaSerDes.scala | 8 +- .../service/MockPresentationService.scala | 33 +++- .../core/service/PresentationService.scala | 12 +- .../service/PresentationServiceImpl.scala | 78 +++++++-- .../service/PresentationServiceNotifier.scala | 29 +++- .../AnoncredPresentationRequestV1.scala | 158 ++++++++++++++++++ .../PresentationServiceNotifierSpec.scala | 7 +- .../service/PresentationServiceSpec.scala | 118 +++++++++---- .../PresentationServiceSpecHelper.scala | 17 +- .../controller/PresentProofController.scala | 2 + .../PresentProofControllerImpl.scala | 51 ++++-- .../http/RequestPresentationInput.scala | 20 ++- 13 files changed, 444 insertions(+), 91 deletions(-) create mode 100644 pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala 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 48896a7409..7ecf89525c 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 @@ -17,4 +17,6 @@ object PresentationError { object MissingCredential extends PresentationError object MissingCredentialFormat extends PresentationError final case class UnsupportedCredentialFormat(vcFormat: String) extends PresentationError + + final case class MissingAnoncredPresentationRequest(error: String) extends PresentationError } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala index bec8fc2e06..82fba4989f 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala @@ -2,18 +2,20 @@ package io.iohk.atala.pollux.core.model.schema.validator import com.networknt.schema.JsonSchema import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError.* -import zio.IO -import zio.ZIO import zio.json.* -import zio.json.JsonDecoder import zio.json.ast.Json import zio.json.ast.Json.* +import zio.{IO, ZIO} class SchemaSerDes[S](jsonSchemaSchemaStr: String) { def initialiseJsonSchema: IO[JsonSchemaError, JsonSchema] = JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr) + def serialize(instance: S)(using encoder: JsonEncoder[S]): String = { + instance.toJson + } + def deserialize( schema: zio.json.ast.Json )(using decoder: JsonDecoder[S]): IO[JsonSchemaError, S] = { 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 8376f80d90..639bcc4a4e 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 @@ -5,23 +5,31 @@ import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProofType, Pro 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} +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 import io.iohk.atala.pollux.vc.jwt.{Issuer, PresentationPayload, W3cCredentialPayload} +import io.iohk.atala.shared.models.WalletAccessContext import zio.mock.{Mock, Proxy} import zio.{IO, URLayer, ZIO, ZLayer, mock} import java.time.Instant import java.util.UUID -import io.iohk.atala.pollux.core.model.CredentialFormat object MockPresentationService extends Mock[PresentationService] { - object CreatePresentationRecord + object CreateJwtPresentationRecord extends Effect[ (DidId, DidId, DidCommID, Option[String], Seq[ProofType], Option[Options]), PresentationError, PresentationRecord ] + object CreateAnoncredPresentationRecord + extends Effect[ + (DidId, DidId, DidCommID, Option[String], AnoncredPresentationRequestV1), + PresentationError, + PresentationRecord + ] + object MarkRequestPresentationSent extends Effect[DidCommID, PresentationError, PresentationRecord] object ReceivePresentation extends Effect[Presentation, PresentationError, PresentationRecord] @@ -54,20 +62,32 @@ object MockPresentationService extends Mock[PresentationService] { proxy <- ZIO.service[Proxy] } yield new PresentationService { - override def createPresentationRecord( + override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, pairwiseProverDID: DidId, thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], - options: Option[Options], - format: CredentialFormat, + options: Option[Options] ): IO[PresentationError, PresentationRecord] = proxy( - CreatePresentationRecord, + CreateJwtPresentationRecord, (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, proofTypes, options) ) + override def createAnoncredPresentationRecord( + pairwiseVerifierDID: DidId, + pairwiseProverDID: DidId, + thid: DidCommID, + connectionId: Option[String], + presentationRequest: AnoncredPresentationRequestV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + proxy( + CreateAnoncredPresentationRecord, + (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, presentationRequest) + ) + } + override def acceptRequestPresentation( recordId: DidCommID, credentialsToUse: Seq[String] @@ -158,6 +178,7 @@ object MockPresentationService extends Mock[PresentationService] { recordId: DidCommID, failReason: Option[String] ): IO[PresentationError, Unit] = ??? + } } 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 5b314426bd..911697351a 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 @@ -5,6 +5,7 @@ import io.iohk.atala.mercury.protocol.presentproof.* import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.presentation.* +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import zio.* @@ -17,14 +18,21 @@ trait PresentationService { def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] - def createPresentationRecord( + def createJwtPresentationRecord( pairwiseVerifierDID: DidId, pairwiseProverDID: DidId, thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[io.iohk.atala.pollux.core.model.presentation.Options], - format: CredentialFormat, + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] + + def createAnoncredPresentationRecord( + pairwiseVerifierDID: DidId, + pairwiseProverDID: DidId, + thid: DidCommID, + connectionId: Option[String], + presentationRequest: AnoncredPresentationRequestV1 ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] def getPresentationRecords( 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 8022b26ea9..12678eb92d 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 @@ -12,6 +12,7 @@ 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.repository.{CredentialRepository, PresentationRepository} +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect @@ -141,14 +142,13 @@ private class PresentationServiceImpl( markPresentationRejected(recordId) } - override def createPresentationRecord( + override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, pairwiseProverDID: DidId, thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], - maybeOptions: Option[io.iohk.atala.pollux.core.model.presentation.Options], - format: CredentialFormat, + maybeOptions: Option[io.iohk.atala.pollux.core.model.presentation.Options] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { for { request <- ZIO.succeed( @@ -157,13 +157,7 @@ private class PresentationServiceImpl( thid, pairwiseVerifierDID, pairwiseProverDID, - format match { - case CredentialFormat.JWT => maybeOptions.map(options => Seq(toJWTAttachment(options))).getOrElse(Seq.empty) - case CredentialFormat.AnonCreds => - maybeOptions - .map(options => Seq(toAnoncredAttachment(options))) - .getOrElse(Seq.empty) // TODO ATL-5945 Create Actual Anoncred Request - } + maybeOptions.map(options => Seq(toJWTAttachment(options))).getOrElse(Seq.empty) ) ) record <- ZIO.succeed( @@ -177,7 +171,57 @@ private class PresentationServiceImpl( role = PresentationRecord.Role.Verifier, subjectId = pairwiseProverDID, protocolState = PresentationRecord.ProtocolState.RequestPending, - credentialFormat = format, + credentialFormat = CredentialFormat.JWT, + requestPresentationData = Some(request), + proposePresentationData = None, + presentationData = None, + credentialsToUse = None, + metaRetries = maxRetries, + metaNextRetry = Some(Instant.now()), + metaLastFailure = None, + ) + ) + count <- presentationRepository + .createPresentationRecord(record) + .flatMap { + case 1 => ZIO.succeed(()) + case n => ZIO.fail(UnexpectedException(s"Invalid row count result: $n")) + } + .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( + s"${record.id}_present_proof_flow_verifier_req_pending_to_sent_ms_gauge" + ) + } yield record + } + + override def createAnoncredPresentationRecord( + pairwiseVerifierDID: DidId, + pairwiseProverDID: DidId, + thid: DidCommID, + connectionId: Option[String], + presentationRequest: AnoncredPresentationRequestV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + for { + request <- ZIO.succeed( + createDidCommRequestPresentation( + Seq.empty, + thid, + pairwiseVerifierDID, + pairwiseProverDID, + Seq(toAnoncredAttachment(presentationRequest)) + ) + ) + record <- ZIO.succeed( + PresentationRecord( + id = DidCommID(), + createdAt = Instant.now, + updatedAt = None, + thid = thid, + connectionId = connectionId, + schemaId = None, // TODO REMOVE from DB + role = PresentationRecord.Role.Verifier, + subjectId = pairwiseProverDID, + protocolState = PresentationRecord.ProtocolState.RequestPending, + credentialFormat = CredentialFormat.AnonCreds, requestPresentationData = Some(request), proposePresentationData = None, presentationData = None, @@ -621,11 +665,13 @@ private class PresentationServiceImpl( ) } - // TODO ATL-5945 Create Actual Anoncred Request - private[this] def toAnoncredAttachment(options: Options): AttachmentDescriptor = { - AttachmentDescriptor.buildJsonAttachment( - payload = PresentationAttachment.build(Some(options)), - format = Some(PresentCredentialRequestFormat.Anoncred.name) + private[this] def toAnoncredAttachment( + presentationRequest: AnoncredPresentationRequestV1 + ): AttachmentDescriptor = { + AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(presentationRequest).getBytes() ) } 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 287b1b2ae5..8b11864fa4 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 @@ -6,13 +6,13 @@ import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProofType, Pro 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} +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 import io.iohk.atala.pollux.vc.jwt.{Issuer, PresentationPayload, W3cCredentialPayload} import io.iohk.atala.shared.models.WalletAccessContext -import zio.{URLayer, ZIO, ZLayer, IO} +import zio.{IO, URLayer, ZIO, ZLayer} import java.time.Instant import java.util.UUID -import io.iohk.atala.pollux.core.model.CredentialFormat class PresentationServiceNotifier( svc: PresentationService, @@ -21,24 +21,39 @@ class PresentationServiceNotifier( private val presentationUpdatedEvent = "PresentationUpdated" - override def createPresentationRecord( + override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, pairwiseProverDID: DidId, thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[Options], - format: CredentialFormat, ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess( - svc.createPresentationRecord( + svc.createJwtPresentationRecord( pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, proofTypes, - options, - format: CredentialFormat + options + ) + ) + + def createAnoncredPresentationRecord( + pairwiseVerifierDID: DidId, + pairwiseProverDID: DidId, + thid: DidCommID, + connectionId: Option[String], + presentationRequest: AnoncredPresentationRequestV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = + notifyOnSuccess( + svc.createAnoncredPresentationRecord( + pairwiseVerifierDID, + pairwiseProverDID, + thid, + connectionId, + presentationRequest ) ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala new file mode 100644 index 0000000000..0cc8ed1e86 --- /dev/null +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala @@ -0,0 +1,158 @@ +package io.iohk.atala.pollux.core.service.serdes + +import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes +import zio.* +import zio.json.* + +case class AnoncredPresentationRequestV1( + requested_attributes: Map[String, AnoncredRequestedAttributeV1], + requested_predicates: Map[String, AnoncredRequestedPredicateV1], + name: String, + nonce: String, + version: String, + non_revoked: Option[AnoncredNonRevokedIntervalV1] +) + +case class AnoncredRequestedAttributeV1(name: String, restrictions: List[AnoncredAttributeRestrictionV1]) + +case class AnoncredRequestedPredicateV1( + name: String, + p_type: String, + p_value: Int, + restrictions: List[AnoncredPredicateRestrictionV1] +) + +case class AnoncredAttributeRestrictionV1( + schema_id: Option[String], + cred_def_id: Option[String], + non_revoked: Option[AnoncredNonRevokedIntervalV1] +) + +case class AnoncredPredicateRestrictionV1( + schema_id: Option[String], + cred_def_id: Option[String], + non_revoked: Option[AnoncredNonRevokedIntervalV1] +) + +case class AnoncredNonRevokedIntervalV1(from: Option[Int], to: Option[Int]) + +object AnoncredPresentationRequestV1 { + val version: String = "PresentationRequestV1" + + private val schema: String = + """ + |{ + | "$schema": "http://json-schema.org/draft-07/schema#", + | "type": "object", + | "properties": { + | "requested_attributes": { + | "type": "object", + | "additionalProperties": { + | "type": "object", + | "properties": { + | "name": { "type": "string" }, + | "restrictions": { + | "type": "array", + | "items": { + | "type": "object", + | "properties": { + | "schema_id": { "type": "string" }, + | "cred_def_id": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | } + | } + | } + | }, + | "required": ["name", "restrictions"] + | } + | }, + | "requested_predicates": { + | "type": "object", + | "additionalProperties": { + | "type": "object", + | "properties": { + | "name": { "type": "string" }, + | "p_type": { "type": "string" }, + | "p_value": { "type": "integer" }, + | "restrictions": { + | "type": "array", + | "items": { + | "type": "object", + | "properties": { + | "schema_id": { "type": "string" }, + | "cred_def_id": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | } + | } + | } + | }, + | "required": ["name", "p_type", "p_value", "restrictions"] + | } + | }, + | "name": { "type": "string" }, + | "nonce": { "type": "string" }, + | "version": { "type": "string" }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } + | }, + | "required": ["requested_attributes", "requested_predicates", "name", "nonce", "version" ] + |} + | + |""".stripMargin + + val schemaSerDes: SchemaSerDes[AnoncredPresentationRequestV1] = SchemaSerDes(schema) + + given JsonDecoder[AnoncredRequestedAttributeV1] = + DeriveJsonDecoder.gen[AnoncredRequestedAttributeV1] + + given JsonEncoder[AnoncredRequestedAttributeV1] = + DeriveJsonEncoder.gen[AnoncredRequestedAttributeV1] + + given JsonDecoder[AnoncredRequestedPredicateV1] = + DeriveJsonDecoder.gen[AnoncredRequestedPredicateV1] + + given JsonEncoder[AnoncredRequestedPredicateV1] = + DeriveJsonEncoder.gen[AnoncredRequestedPredicateV1] + + given JsonDecoder[AnoncredAttributeRestrictionV1] = + DeriveJsonDecoder.gen[AnoncredAttributeRestrictionV1] + + given JsonEncoder[AnoncredNonRevokedIntervalV1] = + DeriveJsonEncoder.gen[AnoncredNonRevokedIntervalV1] + + given JsonDecoder[AnoncredNonRevokedIntervalV1] = + DeriveJsonDecoder.gen[AnoncredNonRevokedIntervalV1] + + given JsonEncoder[AnoncredAttributeRestrictionV1] = + DeriveJsonEncoder.gen[AnoncredAttributeRestrictionV1] + + given JsonDecoder[AnoncredPredicateRestrictionV1] = + DeriveJsonDecoder.gen[AnoncredPredicateRestrictionV1] + + given JsonEncoder[AnoncredPredicateRestrictionV1] = + DeriveJsonEncoder.gen[AnoncredPredicateRestrictionV1] + + given JsonDecoder[AnoncredPresentationRequestV1] = + DeriveJsonDecoder.gen[AnoncredPresentationRequestV1] + + given JsonEncoder[AnoncredPresentationRequestV1] = + DeriveJsonEncoder.gen[AnoncredPresentationRequestV1] + +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala index 06f5751e11..c65a1d512c 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala @@ -36,7 +36,7 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS ) private val verifierHappyFlowExpectations = - MockPresentationService.CreatePresentationRecord( + MockPresentationService.CreateJwtPresentationRecord( assertion = Assertion.anything, result = Expectation.value(record) ) ++ @@ -100,14 +100,13 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS svc <- ZIO.service[PresentationService] ens <- ZIO.service[EventNotificationService] - record <- svc.createPresentationRecord( + record <- svc.createJwtPresentationRecord( DidId(""), DidId(""), DidCommID(""), None, Seq.empty, - None, - format = CredentialFormat.JWT, + None ) _ <- svc.markRequestPresentationSent(record.id) _ <- svc.receivePresentation(presentation(record.thid.value)) 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 94135bc62d..96d2f1ea1d 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 @@ -2,7 +2,7 @@ package io.iohk.atala.pollux.core.service import io.circe.parser.decode import io.circe.syntax.* -import io.iohk.atala.mercury.model.{AttachmentDescriptor, DidId} +import io.iohk.atala.mercury.model.{AttachmentDescriptor, Base64, DidId} import io.iohk.atala.mercury.protocol.issuecredential.IssueCredential import io.iohk.atala.mercury.protocol.presentproof.* import io.iohk.atala.pollux.core.model.* @@ -12,14 +12,15 @@ 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.repository.{CredentialRepository, PresentationRepository} +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 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 io.iohk.atala.shared.models.WalletId -import io.iohk.atala.shared.models.WalletAccessContext +import java.util.Base64 as JBase64 object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSpecHelper { @@ -28,7 +29,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp private val singleWalletSpec = suite("singleWalletSpec")( - test("createPresentationRecord creates a valid PresentationRecord") { + test("createPresentationRecord creates a valid JWT PresentationRecord") { val didGen = for { suffix <- Gen.stringN(10)(Gen.alphaNumericChar) } yield DidId("did:peer:" + suffix) @@ -54,14 +55,13 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp svc <- ZIO.service[PresentationService] pairwiseVerifierDid = DidId("did:peer:Verifier") pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createPresentationRecord( + record <- svc.createJwtPresentationRecord( pairwiseVerifierDid, pairwiseProverDid, thid, connectionId, proofTypes, - options, - format = CredentialFormat.JWT, + options ) } yield { assertTrue(record.thid == thid) && @@ -100,12 +100,74 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } } }, + test("createPresentationRecord creates a valid Anoncred PresentationRecord") { + check( + Gen.uuid.map(e => DidCommID(e.toString())), + Gen.option(Gen.string), + Gen.string, + Gen.string, + Gen.string + ) { (thid, connectionId, name, nonce, version) => + for { + svc <- ZIO.service[PresentationService] + pairwiseVerifierDid = DidId("did:peer:Verifier") + pairwiseProverDid = DidId("did:peer:Prover") + anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + name, + nonce, + version, + None + ) + record <- + svc.createAnoncredPresentationRecord( + pairwiseVerifierDid, + pairwiseProverDid, + thid, + connectionId, + anoncredPresentationRequestV1 + ) + } yield { + assertTrue(record.thid == thid) && + assertTrue(record.updatedAt.isEmpty) && + assertTrue(record.connectionId == connectionId) && + assertTrue(record.role == PresentationRecord.Role.Verifier) && + assertTrue(record.protocolState == PresentationRecord.ProtocolState.RequestPending) && + assertTrue(record.requestPresentationData.isDefined) && + assertTrue(record.requestPresentationData.get.to == pairwiseProverDid) && + assertTrue(record.requestPresentationData.get.thid.contains(thid.toString)) && + assertTrue(record.requestPresentationData.get.body.goal_code.contains("Request Proof Presentation")) && + assertTrue( + record.requestPresentationData.get.attachments.map(_.media_type) == Seq(Some("application/json")) + ) && + assertTrue( + record.requestPresentationData.get.attachments.map(_.format) == Seq( + Some(PresentCredentialRequestFormat.Anoncred.name) + ) + ) && + assertTrue( + record.requestPresentationData.get.attachments.map(_.data) == + Seq( + Base64( + JBase64.getUrlEncoder.encodeToString( + AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + ) + ) + ) + ) && + assertTrue(record.proposePresentationData.isEmpty) && + assertTrue(record.presentationData.isEmpty) && + assertTrue(record.credentialsToUse.isEmpty) + } + } + }, test("getPresentationRecords returns created PresentationRecord") { for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record1 <- svc.createRecord() - record2 <- svc.createRecord() + record1 <- svc.createJwtRecord() + record2 <- svc.createJwtRecord() records <- svc.getPresentationRecords(false) } yield { assertTrue(records.size == 2) @@ -114,7 +176,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("getPresentationRecordsByStates returns the correct records") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() records <- svc.getPresentationRecordsByStates( ignoreWithZeroRetries = true, limit = 10, @@ -132,16 +194,16 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("getPresentationRecord returns the correct record") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() - bRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() + bRecord <- svc.createJwtRecord() record <- svc.getPresentationRecord(bRecord.id) } yield assertTrue(record.contains(bRecord)) }, test("getPresentationRecord returns nothing for an unknown 'recordId'") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() - bRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() + bRecord <- svc.createJwtRecord() record <- svc.getPresentationRecord(DidCommID()) } yield assertTrue(record.isEmpty) }, @@ -159,7 +221,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp IssueCredentialRecord.ProtocolState.CredentialReceived ) svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationWithCredentialsToUse( aRecord.id, @@ -176,7 +238,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createRecord() + record <- svc.createJwtRecord() record <- svc.markRequestPresentationSent(record.id) } yield { @@ -187,7 +249,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createRecord() + record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( record.id, @@ -331,7 +393,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createRecord() + record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( record.id, @@ -347,7 +409,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("receivePresentation updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = presentation(aRecord.thid.value) aRecordReceived <- svc.receivePresentation(p) @@ -359,7 +421,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("acceptPresentation updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = presentation(aRecord.thid.value) aRecordReceived <- svc.receivePresentation(p) repo <- ZIO.service[PresentationRepository] @@ -377,7 +439,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("markPresentationRejected updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = presentation(aRecord.thid.value) _ <- svc.receivePresentation(p) repo <- ZIO.service[PresentationRepository] @@ -396,7 +458,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("rejectPresentation updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = presentation(aRecord.thid.value) aRecordReceived <- svc.receivePresentation(p) repo <- ZIO.service[PresentationRepository] @@ -416,7 +478,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createRecord() + record <- svc.createJwtRecord() p = presentation(record.thid.value) repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( @@ -433,7 +495,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp for { svc <- ZIO.service[PresentationService] pairwiseProverDid = DidId("did:peer:Prover") - record <- svc.createRecord() + record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( record.id, @@ -448,7 +510,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("receiveProposePresentation updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = proposePresentation(aRecord.thid.value) aRecordReceived <- svc.receiveProposePresentation(p) } yield { @@ -459,7 +521,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("acceptProposePresentation updates the PresentatinRecord") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createRecord() + aRecord <- svc.createJwtRecord() p = proposePresentation(aRecord.thid.value) aRecordReceived <- svc.receiveProposePresentation(p) repo <- ZIO.service[PresentationRepository] @@ -485,8 +547,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp val wallet2 = ZLayer.succeed(WalletAccessContext(walletId2)) for { svc <- ZIO.service[PresentationService] - record1 <- svc.createRecord().provide(wallet1) - record2 <- svc.createRecord().provide(wallet2) + record1 <- svc.createJwtRecord().provide(wallet1) + record2 <- svc.createJwtRecord().provide(wallet2) ownRecord1 <- svc.getPresentationRecord(record1.id).provide(wallet1) ownRecord2 <- svc.getPresentationRecord(record2.id).provide(wallet2) crossRecord1 <- svc.getPresentationRecord(record1.id).provide(wallet2) 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 83b8da38ad..a6ce5b0ba7 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 @@ -5,13 +5,15 @@ 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.repository.PresentationRepository +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.vc.jwt.* +import io.iohk.atala.shared.models.WalletAccessContext import zio.* import java.security.* @@ -139,22 +141,21 @@ trait PresentationServiceSpecHelper { ) extension (svc: PresentationService) - def createRecord( + def createJwtRecord( pairwiseVerifierDID: DidId = DidId("did:prism:issuer"), pairwiseProverDID: DidId = DidId("did:prism:prover-pairwise"), thid: DidCommID = DidCommID(), - schemaId: String = "schemaId", - connectionId: Option[String] = None, - ) = { + schemaId: _root_.java.lang.String = "schemaId", + options: Option[io.iohk.atala.pollux.core.model.presentation.Options] = None + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { val proofType = ProofType(schemaId, None, None) - svc.createPresentationRecord( + svc.createJwtPresentationRecord( thid = thid, pairwiseVerifierDID = pairwiseVerifierDID, pairwiseProverDID = pairwiseProverDID, connectionId = Some("connectionId"), proofTypes = Seq(proofType), - options = None, - format = CredentialFormat.JWT, + options = options ) } 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 40af6c73d5..02dc1223d2 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 @@ -45,6 +45,8 @@ object PresentProofController { ErrorResponse.notFound(detail = Some(s"Thread Id not found: $thid")) case PresentationError.InvalidFlowStateError(msg) => ErrorResponse.badRequest(title = "InvalidFlowState", detail = Some(msg)) + case PresentationError.MissingAnoncredPresentationRequest(msg) => + ErrorResponse.badRequest(title = "Missing Anoncred Presentation Request", detail = Some(msg)) case PresentationError.UnexpectedError(msg) => ErrorResponse.internalServerError(detail = Some(msg)) case PresentationError.IssuedCredentialNotFoundError(_) => diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala index 77d478c648..581593677b 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala @@ -30,22 +30,41 @@ class PresentProofControllerImpl( val result: ZIO[WalletAccessContext, ConnectionServiceError | PresentationError, PresentationStatus] = for { didIdPair <- getPairwiseDIDs(request.connectionId).provideSomeLayer(ZLayer.succeed(connectionService)) credentialFormat = request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT) - record <- presentationService - .createPresentationRecord( - pairwiseVerifierDID = didIdPair.myDID, - pairwiseProverDID = didIdPair.theirDid, - thid = DidCommID(), - connectionId = Some(request.connectionId.toString), - proofTypes = request.proofs.map { e => - ProofType( - schema = e.schemaId, // TODO rename field to schemaId - requiredFields = None, - trustIssuers = Some(e.trustIssuers.map(DidId(_))) - ) - }, - options = request.options.map(x => Options(x.challenge, x.domain)), - format = credentialFormat, - ) + record <- + credentialFormat match { + case CredentialFormat.JWT => + presentationService + .createJwtPresentationRecord( + pairwiseVerifierDID = didIdPair.myDID, + pairwiseProverDID = didIdPair.theirDid, + thid = DidCommID(), + connectionId = Some(request.connectionId.toString), + proofTypes = request.proofs.map { e => + ProofType( + schema = e.schemaId, + requiredFields = None, + trustIssuers = Some(e.trustIssuers.map(DidId(_))) + ) + }, + options = request.options.map(x => Options(x.challenge, x.domain)) + ) + case CredentialFormat.AnonCreds => + request.anoncredPresentationRequest match { + case Some(presentationRequest) => + presentationService + .createAnoncredPresentationRecord( + pairwiseVerifierDID = didIdPair.myDID, + pairwiseProverDID = didIdPair.theirDid, + thid = DidCommID(), + connectionId = Some(request.connectionId.toString), + presentationRequest = presentationRequest + ) + case None => + ZIO.fail( + PresentationError.MissingAnoncredPresentationRequest("Anoncred presentation request is missing") + ) + } + } } yield PresentationStatus.fromDomain(record) result.mapError { diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala index fd146ac010..4f50e50f30 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala @@ -1,9 +1,10 @@ package io.iohk.atala.presentproof.controller.http import io.iohk.atala.api.http.Annotation +import io.iohk.atala.pollux.core.service.serdes.* import io.iohk.atala.presentproof.controller.http.RequestPresentationInput.annotations -import sttp.tapir.{Schema, Validator} import sttp.tapir.Schema.annotations.{description, encodedExample} +import sttp.tapir.{Schema, Validator} import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} import java.util.UUID @@ -18,6 +19,9 @@ final case class RequestPresentationInput( @description(annotations.proofs.description) @encodedExample(annotations.proofs.example) proofs: Seq[ProofRequestAux], + @description(annotations.proofs.description) // TODO + @encodedExample(annotations.proofs.example) // TODO + anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], @description(annotations.credentialFormat.description) @encodedExample(annotations.credentialFormat.example) credentialFormat: Option[String], @@ -61,5 +65,19 @@ object RequestPresentationInput { given decoder: JsonDecoder[RequestPresentationInput] = DeriveJsonDecoder.gen[RequestPresentationInput] + import AnoncredPresentationRequestV1.given + + given Schema[AnoncredPresentationRequestV1] = Schema.derived + + given Schema[AnoncredRequestedAttributeV1] = Schema.derived + + given Schema[AnoncredRequestedPredicateV1] = Schema.derived + + given Schema[AnoncredNonRevokedIntervalV1] = Schema.derived + + given Schema[AnoncredAttributeRestrictionV1] = Schema.derived + + given Schema[AnoncredPredicateRestrictionV1] = Schema.derived + given schema: Schema[RequestPresentationInput] = Schema.derived } From ede4d4c71f699da74882900043162ca97ad6a8a1 Mon Sep 17 00:00:00 2001 From: Bassam Date: Tue, 7 Nov 2023 06:45:44 -0500 Subject: [PATCH 03/12] feat: Receive/Store Presentation Request (#776) Signed-off-by: Bassam Riman --- .../core/model/error/PresentationError.scala | 2 +- .../service/PresentationServiceImpl.scala | 16 +++- .../service/PresentationServiceSpec.scala | 96 ++++++++++++++++++- .../controller/PresentProofController.scala | 2 + 4 files changed, 110 insertions(+), 6 deletions(-) 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 7ecf89525c..a7227cee7e 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 @@ -17,6 +17,6 @@ object PresentationError { object MissingCredential extends PresentationError object MissingCredentialFormat extends 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 } 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 12678eb92d..f81699abeb 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 @@ -21,7 +21,7 @@ import zio.* import java.rmi.UnexpectedException import java.time.Instant import java.util as ju -import java.util.UUID +import java.util.{UUID, Base64 as JBase64} private class PresentationServiceImpl( presentationRepository: PresentationRepository, @@ -278,9 +278,17 @@ private class PresentationServiceImpl( val jsonF = PresentCredentialRequestFormat.JWT.name // stable identifier val anoncredF = PresentCredentialRequestFormat.Anoncred.name // stable identifier head.format match - case None => ZIO.fail(PresentationError.MissingCredentialFormat) - case Some(`jsonF`) => ZIO.succeed(CredentialFormat.JWT) - case Some(`anoncredF`) => ZIO.succeed(CredentialFormat.AnonCreds) + case None => ZIO.fail(PresentationError.MissingCredentialFormat) + case Some(`jsonF`) => ZIO.succeed(CredentialFormat.JWT) + case Some(`anoncredF`) => + head.data match + case Base64(data) => + val decodedData = new String(JBase64.getUrlDecoder.decode(data)) + AnoncredPresentationRequestV1.schemaSerDes + .validate(decodedData) + .map(_ => CredentialFormat.AnonCreds) + .mapError(error => InvalidAnoncredPresentationRequest(error.error)) + case _ => ZIO.fail(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) case Some(unsupportedFormat) => ZIO.fail(PresentationError.UnsupportedCredentialFormat(unsupportedFormat)) case _ => ZIO.fail(PresentationError.UnexpectedError("Presentation with multi attachments")) } 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 96d2f1ea1d..332810668a 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 @@ -319,7 +319,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp fails(equalTo(UnsupportedCredentialFormat(vcFormat = "Some/UnsupportedCredentialFormat"))) ) }, - test("receiveRequestPresentation updates the RequestPresentation in PresentatinRecord") { + test("receiveRequestPresentation JWT updates the RequestPresentation in PresentationRecord") { for { svc <- ZIO.service[PresentationService] connectionId = Some("connectionId") @@ -350,6 +350,100 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) } }, + test("receiveRequestPresentation Anoncred updates the RequestPresentation in PresentationRecord") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + "name", + "nonce", + "version", + None + ) + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) + + } yield { + assertTrue(aRecord.connectionId == connectionId) && + assertTrue(aRecord.protocolState == PresentationRecord.ProtocolState.RequestReceived) && + assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) + } + }, + test("receiveRequestPresentation Anoncred should fail given invalid attachment") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + + attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( + payload = presentationAttachmentAsJson, + format = Some(PresentCredentialProposeFormat.Anoncred.name) + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit + + } yield assert(result)( + fails(equalTo(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data"))) + ) + }, + test("receiveRequestPresentation Anoncred should fail given invalid anoncred format") { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = presentationAttachmentAsJson.getBytes() + ) + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit + + } yield assert(result)( + fails(isSubtype[InvalidAnoncredPresentationRequest](anything)) + ) + }, test("acceptRequestPresentation updates the PresentatinRecord JWT") { for { repo <- ZIO.service[CredentialRepository] 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 02dc1223d2..6ab89d8016 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,6 +47,8 @@ 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.InvalidAnoncredPresentationRequest(msg) => + ErrorResponse.badRequest(title = "Invalid Anoncred Presentation Request", detail = Some(msg)) case PresentationError.UnexpectedError(msg) => ErrorResponse.internalServerError(detail = Some(msg)) case PresentationError.IssuedCredentialNotFoundError(_) => From 839ac780677cdd11704e22a3ba8a919b6c369909 Mon Sep 17 00:00:00 2001 From: Bassam Date: Sat, 11 Nov 2023 15:14:52 -0500 Subject: [PATCH 04/12] feat: Accept Presentation Request (#781) Signed-off-by: Bassam Riman --- .../core/model/IssueCredentialRecord.scala | 1 + .../core/model/error/PresentationError.scala | 1 + .../CredentialRepositoryInMemory.scala | 2 +- .../service/PresentationServiceImpl.scala | 27 ++-- .../CredentialRepositorySpecSuite.scala | 131 +++++++++++------- .../PresentationServiceNotifierSpec.scala | 4 +- .../service/PresentationServiceSpec.scala | 116 ++++++++++++++-- .../PresentationServiceSpecHelper.scala | 8 +- .../repository/JdbcCredentialRepository.scala | 1 + 9 files changed, 210 insertions(+), 81 deletions(-) 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 a232e39848..2cbae20941 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 @@ -69,6 +69,7 @@ final case class IssueCredentialRecord( final case class ValidIssuedCredentialRecord( id: DidCommID, issuedCredentialRaw: Option[String], + credentialFormat: CredentialFormat, subjectId: Option[String] ) 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 a7227cee7e..194d706178 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 @@ -11,6 +11,7 @@ object PresentationError { final case class InvalidFlowStateError(msg: String) extends PresentationError final case class UnexpectedError(msg: String) extends PresentationError final case class IssuedCredentialNotFoundError(cause: Throwable) extends PresentationError + final case class NotMatchingPresentationCredentialFormat(cause: Throwable) extends PresentationError final case class PresentationDecodingError(cause: Throwable) extends PresentationError final case class PresentationNotFoundError(cause: Throwable) extends PresentationError final case class HolderBindingError(msg: String) extends PresentationError 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 f69e3b94e4..4624e0a156 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 @@ -133,7 +133,7 @@ class CredentialRepositoryInMemory( store <- storeRef.get } yield store.values .filter(rec => recordId.contains(rec.id) && rec.issuedCredentialRaw.isDefined) - .map(rec => ValidIssuedCredentialRecord(rec.id, rec.issuedCredentialRaw, rec.subjectId)) + .map(rec => ValidIssuedCredentialRecord(rec.id, rec.issuedCredentialRaw, rec.credentialFormat, rec.subjectId)) .toSeq } 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 f81699abeb..340e36ec3b 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 @@ -411,23 +411,32 @@ private class PresentationServiceImpl( for { record <- getRecordWithState(recordId, ProtocolState.RequestReceived) - issuedValidCredentials <- credentialRepository + issuedCredentials <- credentialRepository .getValidIssuedCredentials(credentialsToUse.map(DidCommID(_))) .mapError(RepositoryError.apply) _ <- ZIO.cond( - (issuedValidCredentials.map(_.subjectId).toSet.size == 1), + (issuedCredentials.map(_.subjectId).toSet.size == 1), (), PresentationError.HolderBindingError( - s"Creating a Verifiable Presentation for credential with different subject DID is not supported, found : ${issuedValidCredentials + s"Creating a Verifiable Presentation for credential with different subject DID is not supported, found : ${issuedCredentials .map(_.subjectId)}" ) ) - signedCredentials = issuedValidCredentials.flatMap(_.issuedCredentialRaw) - // record.credentialFormat match { - // case PresentationRecord.CredentialFormat.JWT => issuedRawCredentials - // case CredentialFormat.AnonCreds => issuedRawCredentials - // } - issuedCredentials <- ZIO.fromEither( + validatedCredentials <- ZIO.fromEither( + Either.cond( + issuedCredentials.forall(issuedValidCredential => + issuedValidCredential.credentialFormat == record.credentialFormat + ), + issuedCredentials, + PresentationError.NotMatchingPresentationCredentialFormat( + new IllegalArgumentException( + s"No matching issued credentials format: expectedFormat=${record.credentialFormat}" + ) + ) + ) + ) + signedCredentials = validatedCredentials.flatMap(_.issuedCredentialRaw) + _ <- ZIO.fromEither( Either.cond( signedCredentials.nonEmpty, signedCredentials, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala index b363446d36..ce622e7a16 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala @@ -17,14 +17,14 @@ import java.util.UUID object CredentialRepositorySpecSuite { val maxRetries = 5 // TODO Move to config - private def issueCredentialRecord = IssueCredentialRecord( + private def issueCredentialRecord(credentialFormat: CredentialFormat) = IssueCredentialRecord( id = DidCommID(), createdAt = Instant.now, updatedAt = None, thid = DidCommID(), schemaId = None, credentialDefinitionId = None, - credentialFormat = CredentialFormat.JWT, + credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, validityPeriod = None, @@ -53,7 +53,7 @@ object CredentialRepositorySpecSuite { test("createIssueCredentialRecord creates a new record in DB") { for { repo <- ZIO.service[CredentialRepository] - record = issueCredentialRecord + record = issueCredentialRecord(CredentialFormat.JWT) count <- repo.createIssueCredentialRecord(record) } yield assertTrue(count == 1) }, @@ -61,8 +61,8 @@ object CredentialRepositorySpecSuite { for { repo <- ZIO.service[CredentialRepository] thid = DidCommID() - aRecord = issueCredentialRecord.copy(thid = thid) - bRecord = issueCredentialRecord.copy(thid = thid) + aRecord = issueCredentialRecord(CredentialFormat.JWT).copy(thid = thid) + bRecord = issueCredentialRecord(CredentialFormat.JWT).copy(thid = thid) aCount <- repo.createIssueCredentialRecord(aRecord) bCount <- repo.createIssueCredentialRecord(bRecord).exit } yield assertTrue(aCount == 1) && assert(bCount)(fails(isSubtype[UniqueConstraintViolation](anything))) @@ -71,7 +71,7 @@ object CredentialRepositorySpecSuite { for { repo <- ZIO.service[CredentialRepository] issuingDID <- ZIO.fromEither(PrismDID.buildCanonicalFromSuffix("0" * 64)) - record = issueCredentialRecord.copy(issuingDID = Some(issuingDID)) + record = issueCredentialRecord(CredentialFormat.JWT).copy(issuingDID = Some(issuingDID)) count <- repo.createIssueCredentialRecord(record) readRecord <- repo.getIssueCredentialRecord(record.id) } yield assertTrue(count == 1) && assert(readRecord)(isSome(equalTo(record))) @@ -79,8 +79,8 @@ object CredentialRepositorySpecSuite { test("getIssueCredentialRecord correctly returns an existing record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) record <- repo.getIssueCredentialRecord(bRecord.id) @@ -89,8 +89,8 @@ object CredentialRepositorySpecSuite { test("getIssuanceCredentialRecord returns None for an unknown record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) record <- repo.getIssueCredentialRecord(DidCommID()) @@ -99,8 +99,8 @@ object CredentialRepositorySpecSuite { test("getIssuanceCredentialRecord returns all records") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) records <- repo.getIssueCredentialRecords(false).map(_._1) @@ -113,8 +113,8 @@ object CredentialRepositorySpecSuite { test("getIssuanceCredentialRecord returns records with offset") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) records <- repo.getIssueCredentialRecords(false, offset = Some(1)).map(_._1) @@ -126,8 +126,8 @@ object CredentialRepositorySpecSuite { test("getIssuanceCredentialRecord returns records with limit") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) records <- repo.getIssueCredentialRecords(false, limit = Some(1)).map(_._1) @@ -139,9 +139,9 @@ object CredentialRepositorySpecSuite { test("getIssuanceCredentialRecord returns records with offset and limit") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord - cRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) + cRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) _ <- repo.createIssueCredentialRecord(cRecord) @@ -156,8 +156,8 @@ object CredentialRepositorySpecSuite { test("deleteIssueCredentialRecord deletes an exsiting record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) count <- repo.deleteIssueCredentialRecord(aRecord.id) @@ -171,8 +171,8 @@ object CredentialRepositorySpecSuite { test("deleteIssueCredentialRecord does nothing for an unknown record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) count <- repo.deleteIssueCredentialRecord(DidCommID()) @@ -188,8 +188,8 @@ object CredentialRepositorySpecSuite { for { repo <- ZIO.service[CredentialRepository] thid = DidCommID() - aRecord = issueCredentialRecord.copy(thid = thid) - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT).copy(thid = thid) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) record <- repo.getIssueCredentialRecordByThreadId(thid, false) @@ -198,8 +198,8 @@ object CredentialRepositorySpecSuite { test("getIssueCredentialRecordByThreadId returns nothing for an unknown thid") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) record <- repo.getIssueCredentialRecordByThreadId(DidCommID(), false) @@ -208,9 +208,9 @@ object CredentialRepositorySpecSuite { test("getIssueCredentialRecordsByStates returns valid records") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord - cRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) + cRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) _ <- repo.createIssueCredentialRecord(cRecord) @@ -242,9 +242,9 @@ object CredentialRepositorySpecSuite { test("getIssueCredentialRecordsByStates returns an empty list if 'states' parameter is empty") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord - cRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) + cRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) _ <- repo.createIssueCredentialRecord(cRecord) @@ -256,30 +256,55 @@ object CredentialRepositorySpecSuite { test("getValidIssuedCredentials returns valid records") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord - bRecord = issueCredentialRecord - cRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) + bRecord = issueCredentialRecord(CredentialFormat.JWT) + cRecord = issueCredentialRecord(CredentialFormat.JWT) + dRecord = issueCredentialRecord(CredentialFormat.AnonCreds) _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) _ <- repo.createIssueCredentialRecord(cRecord) + _ <- repo.createIssueCredentialRecord(dRecord) _ <- repo.updateWithIssuedRawCredential( aRecord.id, IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage), "RAW_CREDENTIAL_DATA", ProtocolState.CredentialReceived ) - records <- repo.getValidIssuedCredentials(Seq(aRecord.id, bRecord.id)) + _ <- repo.updateWithIssuedRawCredential( + dRecord.id, + IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage), + "RAW_CREDENTIAL_DATA", + ProtocolState.CredentialReceived + ) + records <- repo.getValidIssuedCredentials(Seq(aRecord.id, bRecord.id, dRecord.id)) } yield { - assertTrue(records.size == 1) && + assertTrue(records.size == 2) && assertTrue( - records.contains(ValidIssuedCredentialRecord(aRecord.id, Some("RAW_CREDENTIAL_DATA"), aRecord.subjectId)) + records.contains( + ValidIssuedCredentialRecord( + dRecord.id, + Some("RAW_CREDENTIAL_DATA"), + CredentialFormat.JWT, + aRecord.subjectId + ) + ) + ) + assertTrue( + records.contains( + ValidIssuedCredentialRecord( + dRecord.id, + Some("RAW_CREDENTIAL_DATA"), + CredentialFormat.AnonCreds, + aRecord.subjectId + ) + ) ) } }, test("updateCredentialRecordProtocolState updates the record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) record <- repo.getIssueCredentialRecord(aRecord.id) count <- repo.updateCredentialRecordProtocolState( @@ -297,7 +322,7 @@ object CredentialRepositorySpecSuite { test("updateCredentialRecordProtocolState doesn't update the record for invalid from state") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) record <- repo.getIssueCredentialRecord(aRecord.id) count <- repo.updateCredentialRecordProtocolState( @@ -315,7 +340,7 @@ object CredentialRepositorySpecSuite { test("updateWithRequestCredential updates record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) record <- repo.getIssueCredentialRecord(aRecord.id) request = requestCredential @@ -334,7 +359,7 @@ object CredentialRepositorySpecSuite { test("updateWithIssueCredential updates record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) record <- repo.getIssueCredentialRecord(aRecord.id) issueCredential = IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage) @@ -353,7 +378,7 @@ object CredentialRepositorySpecSuite { test("updateWithIssuedRawCredential updates record") { for { repo <- ZIO.service[CredentialRepository] - aRecord = issueCredentialRecord + aRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aRecord) record <- repo.getIssueCredentialRecord(aRecord.id) issueCredential = IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage) @@ -372,7 +397,7 @@ object CredentialRepositorySpecSuite { } }, test("updateFail (fail one retry) updates record") { - val aRecord = issueCredentialRecord + val aRecord = issueCredentialRecord(CredentialFormat.JWT) val failReason = Some("Just to test") for { @@ -402,7 +427,7 @@ object CredentialRepositorySpecSuite { } }, test("updateFail (fail all retry) updates record") { - val aRecord = issueCredentialRecord + val aRecord = issueCredentialRecord(CredentialFormat.JWT) for { repo <- ZIO.service[CredentialRepository] @@ -443,8 +468,8 @@ object CredentialRepositorySpecSuite { val wallet2 = ZLayer.succeed(WalletAccessContext(walletId2)) for { repo <- ZIO.service[CredentialRepository] - record1 = issueCredentialRecord - record2 = issueCredentialRecord + record1 = issueCredentialRecord(CredentialFormat.JWT) + record2 = issueCredentialRecord(CredentialFormat.JWT) count1 <- repo.createIssueCredentialRecord(record1).provide(wallet1) count2 <- repo.createIssueCredentialRecord(record2).provide(wallet2) ownWalletRecords1 <- repo.getIssueCredentialRecords(false).provide(wallet1) @@ -466,8 +491,8 @@ object CredentialRepositorySpecSuite { val newState = IssueCredentialRecord.ProtocolState.OfferReceived for { repo <- ZIO.service[CredentialRepository] - record1 = issueCredentialRecord - record2 = issueCredentialRecord + record1 = issueCredentialRecord(CredentialFormat.JWT) + record2 = issueCredentialRecord(CredentialFormat.JWT) count1 <- repo.createIssueCredentialRecord(record1).provide(wallet1) update1 <- repo.updateWithSubjectId(record2.id, "my-id", newState).provide(wallet2) update2 <- repo.updateAfterFail(record2.id, Some("fail reason")).provide(wallet2) @@ -486,7 +511,7 @@ object CredentialRepositorySpecSuite { val wallet2 = ZLayer.succeed(WalletAccessContext(walletId2)) for { repo <- ZIO.service[CredentialRepository] - record1 = issueCredentialRecord + record1 = issueCredentialRecord(CredentialFormat.JWT) count1 <- repo.createIssueCredentialRecord(record1).provide(wallet1) delete1 <- repo.deleteIssueCredentialRecord(record1.id).provide(wallet2) } yield assert(count1)(equalTo(1)) && assert(delete1)(isZero) @@ -498,8 +523,8 @@ object CredentialRepositorySpecSuite { val wallet2 = ZLayer.succeed(WalletAccessContext(walletId2)) for { repo <- ZIO.service[CredentialRepository] - record1 = issueCredentialRecord - record2 = issueCredentialRecord + record1 = issueCredentialRecord(CredentialFormat.JWT) + record2 = issueCredentialRecord(CredentialFormat.JWT) count1 <- repo.createIssueCredentialRecord(record1).provide(wallet1) count2 <- repo.createIssueCredentialRecord(record2).provide(wallet2) _ <- repo diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala index c65a1d512c..759f64326f 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala @@ -2,7 +2,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.event.notification.{EventNotificationService, EventNotificationServiceImpl} import io.iohk.atala.mercury.model.DidId -import io.iohk.atala.mercury.protocol.presentproof.{Presentation, RequestPresentation} +import io.iohk.atala.mercury.protocol.presentproof.{PresentCredentialRequestFormat, Presentation, RequestPresentation} import io.iohk.atala.pollux.core.model.PresentationRecord.ProtocolState import io.iohk.atala.pollux.core.model.{CredentialFormat, DidCommID, PresentationRecord} import zio.mock.Expectation @@ -169,7 +169,7 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS svc <- ZIO.service[PresentationService] ens <- ZIO.service[EventNotificationService] - _ <- svc.receiveRequestPresentation(None, requestPresentationJWT) + _ <- svc.receiveRequestPresentation(None, requestPresentation(PresentCredentialRequestFormat.JWT)) _ <- svc.acceptRequestPresentation(record.id, Seq.empty) _ <- svc.markPresentationGenerated(record.id, presentation(record.thid.value)) _ <- svc.markPresentationSent(record.id) 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 332810668a..a311a3a2b4 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 @@ -210,7 +210,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("createPresentationPayloadFromRecord returns jwt prsentation payload") { for { repo <- ZIO.service[CredentialRepository] - aIssueCredentialRecord = issueCredentialRecord + aIssueCredentialRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) rawCredentialData = """{"base64":"ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKcFlYUWlPakUyTnprek1qYzROaklzSW1GMVpDSTZJbVJ2YldGcGJpSXNJbTV2Ym1ObElqb2lZMlk1T1RJMk56Z3RPREV3TmkwME1EZzVMV0UxWXprdE5tTmhObU0wWkRBMU1HVTBJaXdpZG5BaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZjSEpsYzJWdWRHRjBhVzl1Y3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZRY21WelpXNTBZWFJwYjI0aVhYMHNJbWx6Y3lJNkltUnBaRHB3Y21semJUcGhaR0psT1RJNE9XUXdZelZtWWpVMlptWmhOVEF6T0Rka01UZ3dOR0ZpTkdFeE5UYzJOVEkzWXprME5tRTFNalV5T0RFM1ptRTRaVGhoTW1OalpXUXdPa056YzBKRGMyZENSVzFKUzBSWE1XaGpNMUpzWTJsb2NHSnRVbXhsUTJ0UlFWVktVRU5uYkZSYVYwNTNUV3BWTW1GNlJWTkpSUzFNYVVkTU0xRklaRlZ1VG10d1dXSkthSE5VYTIxWVVGaEpVM0ZXZWpjMll6RlZPWGhvVURseWNFZHBSSEZXTlRselJYcEtWbEpEYWxJMGEwMHdaMGg0YkhWUU5tVk5Ta2wwZHpJMk4yWllWbEpoTUhoRE5XaEthVU5uTVhSWldFNHdXbGhKYjJGWE5XdGFXR2R3UlVGU1ExUjNiMHBWTWxacVkwUkpNVTV0YzNoRmFVSlFhVFJvYVRrd1FqTldTbnBhUzFkSGVWbGlSVFZLYkhveGVVVnhiR010TFc1T1ZsQmpXVlJmWVRaU2IyYzJiR1ZtWWtKTmVWWlZVVzh3WlVwRVRrbENPRnBpYWkxdWFrTlRUR05PZFhVek1URlZWM1JOVVhWWkluMC5CcmFpbEVXa2VlSXhWbjY3dnpkVHZGTXpBMV9oNzFoaDZsODBHRFBpbkRaVVB4ajAxSC0tUC1QZDIxTk9wRDd3am51SDkxdUNBOFZMUW9fS2FnVjlnQQo="}""" @@ -444,10 +444,10 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp fails(isSubtype[InvalidAnoncredPresentationRequest](anything)) ) }, - test("acceptRequestPresentation updates the PresentatinRecord JWT") { + test("acceptRequestPresentation updates the PresentationRecord JWT") { for { repo <- ZIO.service[CredentialRepository] - aIssueCredentialRecord = issueCredentialRecord + aIssueCredentialRecord = issueCredentialRecord(CredentialFormat.JWT) _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) rawCredentialData = """{"base64":"ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKcFlYUWlPakUyTnprek1qYzROaklzSW1GMVpDSTZJbVJ2YldGcGJpSXNJbTV2Ym1ObElqb2lZMlk1T1RJMk56Z3RPREV3TmkwME1EZzVMV0UxWXprdE5tTmhObU0wWkRBMU1HVTBJaXdpZG5BaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZjSEpsYzJWdWRHRjBhVzl1Y3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZRY21WelpXNTBZWFJwYjI0aVhYMHNJbWx6Y3lJNkltUnBaRHB3Y21semJUcGhaR0psT1RJNE9XUXdZelZtWWpVMlptWmhOVEF6T0Rka01UZ3dOR0ZpTkdFeE5UYzJOVEkzWXprME5tRTFNalV5T0RFM1ptRTRaVGhoTW1OalpXUXdPa056YzBKRGMyZENSVzFKUzBSWE1XaGpNMUpzWTJsb2NHSnRVbXhsUTJ0UlFWVktVRU5uYkZSYVYwNTNUV3BWTW1GNlJWTkpSUzFNYVVkTU0xRklaRlZ1VG10d1dXSkthSE5VYTIxWVVGaEpVM0ZXZWpjMll6RlZPWGhvVURseWNFZHBSSEZXTlRselJYcEtWbEpEYWxJMGEwMHdaMGg0YkhWUU5tVk5Ta2wwZHpJMk4yWllWbEpoTUhoRE5XaEthVU5uTVhSWldFNHdXbGhKYjJGWE5XdGFXR2R3UlVGU1ExUjNiMHBWTWxacVkwUkpNVTV0YzNoRmFVSlFhVFJvYVRrd1FqTldTbnBhUzFkSGVWbGlSVFZLYkhveGVVVnhiR010TFc1T1ZsQmpXVlJmWVRaU2IyYzJiR1ZtWWtKTmVWWlZVVzh3WlVwRVRrbENPRnBpYWkxdWFrTlRUR05PZFhVek1URlZWM1JOVVhWWkluMC5CcmFpbEVXa2VlSXhWbjY3dnpkVHZGTXpBMV9oNzFoaDZsODBHRFBpbkRaVVB4ajAxSC0tUC1QZDIxTk9wRDd3am51SDkxdUNBOFZMUW9fS2FnVjlnQQo="}""" @@ -460,7 +460,54 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp svc <- ZIO.service[PresentationService] connectionId = Some("connectionId") - aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentationJWT) + aRecord <- svc.receiveRequestPresentation( + connectionId, + requestPresentation(PresentCredentialRequestFormat.JWT) + ) + credentialsToUse = Seq(aIssueCredentialRecord.id.value) + updateRecord <- svc.acceptRequestPresentation(aRecord.id, credentialsToUse) + + } yield { + assertTrue(updateRecord.connectionId == connectionId) && + // assertTrue(updateRecord.requestPresentationData == Some(requestPresentation)) && // FIXME: enabling them make the test fail. + assertTrue(updateRecord.credentialsToUse.contains(credentialsToUse)) + } + }, + test("acceptRequestPresentation updates the PresentationRecord AnonCreds") { + for { + repo <- ZIO.service[CredentialRepository] + aIssueCredentialRecord = issueCredentialRecord(CredentialFormat.AnonCreds) + _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) + rawCredentialData = + """{"base64":"ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKcFlYUWlPakUyTnprek1qYzROaklzSW1GMVpDSTZJbVJ2YldGcGJpSXNJbTV2Ym1ObElqb2lZMlk1T1RJMk56Z3RPREV3TmkwME1EZzVMV0UxWXprdE5tTmhObU0wWkRBMU1HVTBJaXdpZG5BaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZjSEpsYzJWdWRHRjBhVzl1Y3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZRY21WelpXNTBZWFJwYjI0aVhYMHNJbWx6Y3lJNkltUnBaRHB3Y21semJUcGhaR0psT1RJNE9XUXdZelZtWWpVMlptWmhOVEF6T0Rka01UZ3dOR0ZpTkdFeE5UYzJOVEkzWXprME5tRTFNalV5T0RFM1ptRTRaVGhoTW1OalpXUXdPa056YzBKRGMyZENSVzFKUzBSWE1XaGpNMUpzWTJsb2NHSnRVbXhsUTJ0UlFWVktVRU5uYkZSYVYwNTNUV3BWTW1GNlJWTkpSUzFNYVVkTU0xRklaRlZ1VG10d1dXSkthSE5VYTIxWVVGaEpVM0ZXZWpjMll6RlZPWGhvVURseWNFZHBSSEZXTlRselJYcEtWbEpEYWxJMGEwMHdaMGg0YkhWUU5tVk5Ta2wwZHpJMk4yWllWbEpoTUhoRE5XaEthVU5uTVhSWldFNHdXbGhKYjJGWE5XdGFXR2R3UlVGU1ExUjNiMHBWTWxacVkwUkpNVTV0YzNoRmFVSlFhVFJvYVRrd1FqTldTbnBhUzFkSGVWbGlSVFZLYkhveGVVVnhiR010TFc1T1ZsQmpXVlJmWVRaU2IyYzJiR1ZtWWtKTmVWWlZVVzh3WlVwRVRrbENPRnBpYWkxdWFrTlRUR05PZFhVek1URlZWM1JOVVhWWkluMC5CcmFpbEVXa2VlSXhWbjY3dnpkVHZGTXpBMV9oNzFoaDZsODBHRFBpbkRaVVB4ajAxSC0tUC1QZDIxTk9wRDd3am51SDkxdUNBOFZMUW9fS2FnVjlnQQo="}""" + _ <- repo.updateWithIssuedRawCredential( + aIssueCredentialRecord.id, + IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage), + rawCredentialData, + IssueCredentialRecord.ProtocolState.CredentialReceived + ) + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + "name", + "nonce", + "version", + None + ) + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + ) + requestPresentation = RequestPresentation( + body = RequestPresentation.Body(goal_code = Some("Presentation Request")), + attachments = Seq(attachmentDescriptor), + to = DidId("did:peer:Prover"), + from = DidId("did:peer:Verifier"), + ) + aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) credentialsToUse = Seq(aIssueCredentialRecord.id.value) updateRecord <- svc.acceptRequestPresentation(aRecord.id, credentialsToUse) @@ -470,11 +517,56 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(updateRecord.credentialsToUse.contains(credentialsToUse)) } }, - test("rejectRequestPresentation updates the PresentatinRecord") { + test("acceptRequestPresentation should fail given unmatching format") { + for { + repo <- ZIO.service[CredentialRepository] + aIssueCredentialRecord = issueCredentialRecord(CredentialFormat.JWT) + _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) + rawCredentialData = + """{"base64":"ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKcFlYUWlPakUyTnprek1qYzROaklzSW1GMVpDSTZJbVJ2YldGcGJpSXNJbTV2Ym1ObElqb2lZMlk1T1RJMk56Z3RPREV3TmkwME1EZzVMV0UxWXprdE5tTmhObU0wWkRBMU1HVTBJaXdpZG5BaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZjSEpsYzJWdWRHRjBhVzl1Y3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZRY21WelpXNTBZWFJwYjI0aVhYMHNJbWx6Y3lJNkltUnBaRHB3Y21semJUcGhaR0psT1RJNE9XUXdZelZtWWpVMlptWmhOVEF6T0Rka01UZ3dOR0ZpTkdFeE5UYzJOVEkzWXprME5tRTFNalV5T0RFM1ptRTRaVGhoTW1OalpXUXdPa056YzBKRGMyZENSVzFKUzBSWE1XaGpNMUpzWTJsb2NHSnRVbXhsUTJ0UlFWVktVRU5uYkZSYVYwNTNUV3BWTW1GNlJWTkpSUzFNYVVkTU0xRklaRlZ1VG10d1dXSkthSE5VYTIxWVVGaEpVM0ZXZWpjMll6RlZPWGhvVURseWNFZHBSSEZXTlRselJYcEtWbEpEYWxJMGEwMHdaMGg0YkhWUU5tVk5Ta2wwZHpJMk4yWllWbEpoTUhoRE5XaEthVU5uTVhSWldFNHdXbGhKYjJGWE5XdGFXR2R3UlVGU1ExUjNiMHBWTWxacVkwUkpNVTV0YzNoRmFVSlFhVFJvYVRrd1FqTldTbnBhUzFkSGVWbGlSVFZLYkhveGVVVnhiR010TFc1T1ZsQmpXVlJmWVRaU2IyYzJiR1ZtWWtKTmVWWlZVVzh3WlVwRVRrbENPRnBpYWkxdWFrTlRUR05PZFhVek1URlZWM1JOVVhWWkluMC5CcmFpbEVXa2VlSXhWbjY3dnpkVHZGTXpBMV9oNzFoaDZsODBHRFBpbkRaVVB4ajAxSC0tUC1QZDIxTk9wRDd3am51SDkxdUNBOFZMUW9fS2FnVjlnQQo="}""" + _ <- repo.updateWithIssuedRawCredential( + aIssueCredentialRecord.id, + IssueCredential.makeIssueCredentialFromRequestCredential(requestCredential.makeMessage), + rawCredentialData, + IssueCredentialRecord.ProtocolState.CredentialReceived + ) + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + "name", + "nonce", + "version", + None + ) + attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + ) + requestPresentation = RequestPresentation( + body = RequestPresentation.Body(goal_code = Some("Presentation Request")), + attachments = Seq(attachmentDescriptor), + to = DidId("did:peer:Prover"), + from = DidId("did:peer:Verifier"), + ) + aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) + credentialsToUse = Seq(aIssueCredentialRecord.id.value) + result <- svc.acceptRequestPresentation(aRecord.id, credentialsToUse).exit + + } yield assert(result)( + fails(isSubtype[NotMatchingPresentationCredentialFormat](anything)) + ) + }, + test("rejectRequestPresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] connectionId = Some("connectionId") - aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentationJWT) + aRecord <- svc.receiveRequestPresentation( + connectionId, + requestPresentation(PresentCredentialRequestFormat.JWT) + ) updateRecord <- svc.rejectRequestPresentation(aRecord.id) } yield { @@ -500,7 +592,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(record.protocolState == PresentationRecord.ProtocolState.PresentationSent) } }, - test("receivePresentation updates the PresentatinRecord") { + test("receivePresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() @@ -512,7 +604,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecordReceived.presentationData == Some(p)) } }, - test("acceptPresentation updates the PresentatinRecord") { + test("acceptPresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() @@ -530,7 +622,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecordReceived.presentationData == Some(p)) } }, - test("markPresentationRejected updates the PresentatinRecord") { + test("markPresentationRejected updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() @@ -549,7 +641,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecordReject.protocolState == PresentationRecord.ProtocolState.PresentationRejected) } }, - test("rejectPresentation updates the PresentatinRecord") { + test("rejectPresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() @@ -601,7 +693,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(record.protocolState == PresentationRecord.ProtocolState.ProposalSent) } }, - test("receiveProposePresentation updates the PresentatinRecord") { + test("receiveProposePresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() @@ -612,7 +704,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(aRecordReceived.proposePresentationData == Some(p)) } }, - test("acceptProposePresentation updates the PresentatinRecord") { + test("acceptProposePresentation updates the PresentationRecord") { for { svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() 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 a6ce5b0ba7..e3456f5fca 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 @@ -53,7 +53,7 @@ trait PresentationServiceSpecHelper { attachments = Nil ) - protected def requestPresentationJWT: RequestPresentation = { + protected def requestPresentation(credentialFormat: PresentCredentialRequestFormat): RequestPresentation = { val body = RequestPresentation.Body(goal_code = Some("Presentation Request")) val presentationAttachmentAsJson = """{ @@ -66,7 +66,7 @@ trait PresentationServiceSpecHelper { val attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( payload = presentationAttachmentAsJson, - format = Some(PresentCredentialRequestFormat.JWT.name) + format = Some(credentialFormat.name) ) RequestPresentation( body = body, @@ -116,14 +116,14 @@ trait PresentationServiceSpecHelper { ) } - protected def issueCredentialRecord = IssueCredentialRecord( + protected def issueCredentialRecord(credentialFormat: CredentialFormat) = IssueCredentialRecord( id = DidCommID(), createdAt = Instant.now, updatedAt = None, thid = DidCommID(), schemaId = None, credentialDefinitionId = None, - credentialFormat = CredentialFormat.JWT, + credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, validityPeriod = None, 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 24cbdb5113..dd7b5d5319 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 @@ -433,6 +433,7 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | SELECT | id, | issued_credential_raw, + | credential_format, | subject_id | FROM public.issue_credential_records | WHERE From d3d06b46d3b08117366be67811287c11d99fe7cb Mon Sep 17 00:00:00 2001 From: Bassam Date: Thu, 16 Nov 2023 08:29:50 -0500 Subject: [PATCH 05/12] feat: Create Store Presentation (#788) Signed-off-by: Bassam Riman --- ...redPresentationRequestSchemaSerDesV1.scala | 158 ------ .../AnoncredPresentationRequestV1.scala | 2 +- .../serdes/AnoncredPresentationV1.scala | 512 ++++++++++++++++++ ... => AnoncredPresentationRequestSpec.scala} | 22 +- .../serdes/AnoncredPresentationSpec.scala | 213 ++++++++ 5 files changed, 737 insertions(+), 170 deletions(-) delete mode 100644 pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala create mode 100644 pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationV1.scala rename pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/{AnoncredPresentationRequestSchemaSerDesSpec.scala => AnoncredPresentationRequestSpec.scala} (79%) create mode 100644 pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala deleted file mode 100644 index e788e52b7d..0000000000 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesV1.scala +++ /dev/null @@ -1,158 +0,0 @@ -package io.iohk.atala.pollux.core.service.serdes - -import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes -import zio.* -import zio.json.* - -case class AnoncredPresentationRequestSchemaSerDesV1( - requested_attributes: Map[String, AnoncredRequestedAttribute], - requested_predicates: Map[String, AnoncredRequestedPredicate], - name: String, - nonce: String, - version: String, - non_revoked: Option[AnoncredNonRevokedInterval] -) - -case class AnoncredRequestedAttribute(name: String, restrictions: List[AnoncredAttributeRestriction]) - -case class AnoncredRequestedPredicate( - name: String, - p_type: String, - p_value: Int, - restrictions: List[AnoncredPredicateRestriction] -) - -case class AnoncredAttributeRestriction( - schema_id: Option[String], - cred_def_id: Option[String], - non_revoked: Option[AnoncredNonRevokedInterval] -) - -case class AnoncredPredicateRestriction( - schema_id: Option[String], - cred_def_id: Option[String], - non_revoked: Option[AnoncredNonRevokedInterval] -) - -case class AnoncredNonRevokedInterval(from: Option[Int], to: Option[Int]) - -object AnoncredPresentationRequestSchemaSerDesV1 { - val version: String = "PresentationRequestV1" - - private val schema: String = - """ - |{ - | "$schema": "http://json-schema.org/draft-07/schema#", - | "type": "object", - | "properties": { - | "requested_attributes": { - | "type": "object", - | "additionalProperties": { - | "type": "object", - | "properties": { - | "name": { "type": "string" }, - | "restrictions": { - | "type": "array", - | "items": { - | "type": "object", - | "properties": { - | "schema_id": { "type": "string" }, - | "cred_def_id": { "type": "string" }, - | "non_revoked": { - | "type": "object", - | "properties": { - | "from": { "type": "integer" }, - | "to": { "type": "integer" } - | } - | } - | } - | } - | } - | }, - | "required": ["name", "restrictions"] - | } - | }, - | "requested_predicates": { - | "type": "object", - | "additionalProperties": { - | "type": "object", - | "properties": { - | "name": { "type": "string" }, - | "p_type": { "type": "string" }, - | "p_value": { "type": "integer" }, - | "restrictions": { - | "type": "array", - | "items": { - | "type": "object", - | "properties": { - | "schema_id": { "type": "string" }, - | "cred_def_id": { "type": "string" }, - | "non_revoked": { - | "type": "object", - | "properties": { - | "from": { "type": "integer" }, - | "to": { "type": "integer" } - | } - | } - | } - | } - | } - | }, - | "required": ["name", "p_type", "p_value", "restrictions"] - | } - | }, - | "name": { "type": "string" }, - | "nonce": { "type": "string" }, - | "version": { "type": "string" }, - | "non_revoked": { - | "type": "object", - | "properties": { - | "from": { "type": "integer" }, - | "to": { "type": "integer" } - | } - | } - | }, - | "required": ["requested_attributes", "requested_predicates", "name", "nonce", "version" ] - |} - | - |""".stripMargin - - val schemaSerDes: SchemaSerDes[AnoncredPresentationRequestSchemaSerDesV1] = SchemaSerDes(schema) - - given JsonDecoder[AnoncredRequestedAttribute] = - DeriveJsonDecoder.gen[AnoncredRequestedAttribute] - - given JsonEncoder[AnoncredRequestedAttribute] = - DeriveJsonEncoder.gen[AnoncredRequestedAttribute] - - given JsonDecoder[AnoncredRequestedPredicate] = - DeriveJsonDecoder.gen[AnoncredRequestedPredicate] - - given JsonEncoder[AnoncredRequestedPredicate] = - DeriveJsonEncoder.gen[AnoncredRequestedPredicate] - - given JsonDecoder[AnoncredAttributeRestriction] = - DeriveJsonDecoder.gen[AnoncredAttributeRestriction] - - given JsonEncoder[AnoncredNonRevokedInterval] = - DeriveJsonEncoder.gen[AnoncredNonRevokedInterval] - - given JsonDecoder[AnoncredNonRevokedInterval] = - DeriveJsonDecoder.gen[AnoncredNonRevokedInterval] - - given JsonEncoder[AnoncredAttributeRestriction] = - DeriveJsonEncoder.gen[AnoncredAttributeRestriction] - - given JsonDecoder[AnoncredPredicateRestriction] = - DeriveJsonDecoder.gen[AnoncredPredicateRestriction] - - given JsonEncoder[AnoncredPredicateRestriction] = - DeriveJsonEncoder.gen[AnoncredPredicateRestriction] - - given JsonDecoder[AnoncredPresentationRequestSchemaSerDesV1] = - DeriveJsonDecoder.gen[AnoncredPresentationRequestSchemaSerDesV1] - - given JsonEncoder[AnoncredPresentationRequestSchemaSerDesV1] = - DeriveJsonEncoder.gen[AnoncredPresentationRequestSchemaSerDesV1] - -} diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala index 0cc8ed1e86..c4063533eb 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala @@ -37,7 +37,7 @@ case class AnoncredPredicateRestrictionV1( case class AnoncredNonRevokedIntervalV1(from: Option[Int], to: Option[Int]) object AnoncredPresentationRequestV1 { - val version: String = "PresentationRequestV1" + val version: String = "AnoncredPresentationRequestV1" private val schema: String = """ diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationV1.scala new file mode 100644 index 0000000000..f773f8311a --- /dev/null +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationV1.scala @@ -0,0 +1,512 @@ +package io.iohk.atala.pollux.core.service.serdes + +import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes +import zio.* +import zio.json.* + +case class AnoncredAdditionalPropertiesTypeStringV1(value: String) + +case class AnoncredRevealedAttrV1(sub_proof_index: Int, raw: String, encoded: String) + +case class AnoncredRequestedProofV1( + revealed_attrs: Map[String, AnoncredRevealedAttrV1], + self_attested_attrs: Map[String, String], + unrevealed_attrs: Map[String, String], + predicates: Map[String, AnoncredSubProofIndexV1] +) + +case class AnoncredSubProofIndexV1(sub_proof_index: Int) + +case class AnoncredIdentifierV1( + schema_id: String, + cred_def_id: String, + rev_reg_id: Option[String], + timestamp: Option[Int] +) + +// Adjusted classes for proofs +case class AnoncredEqProofV1( + revealed_attrs: Map[String, String], + a_prime: String, + e: String, + v: String, + m: Map[String, String], + m2: String +) + +case class AnoncredPredicateV1(attr_name: String, p_type: String, value: Int) + +case class AnoncredGeProofV1( + u: Map[String, String], + r: Map[String, String], + mj: String, + alpha: String, + t: Map[String, String], + predicate: AnoncredPredicateV1 +) + +case class AnoncredPrimaryProofV1(eq_proof: AnoncredEqProofV1, ge_proofs: List[AnoncredGeProofV1]) + +case class AnoncredSubProofV1(primary_proof: AnoncredPrimaryProofV1, non_revoc_proof: Option[AnoncredNonRevocProofV1]) + +case class AnoncredProofV1(proofs: List[AnoncredSubProofV1], aggregated_proof: AnoncredAggregatedProofV1) + +case class AnoncredAggregatedProofV1(c_hash: String, c_list: List[List[Int]]) + +case class AnoncredNonRevocProofXListV1( + rho: String, + r: String, + r_prime: String, + r_prime_prime: String, + r_prime_prime_prime: String, + o: String, + o_prime: String, + m: String, + m_prime: String, + t: String, + t_prime: String, + m2: String, + s: String, + c: String +) + +case class AnoncredNonRevocProofCListV1(e: String, d: String, a: String, g: String, w: String, s: String, u: String) + +case class AnoncredNonRevocProofV1(x_list: AnoncredNonRevocProofXListV1, c_list: AnoncredNonRevocProofCListV1) + +case class AnoncredPresentationV1( + proof: AnoncredProofV1, + requested_proof: AnoncredRequestedProofV1, + identifiers: List[AnoncredIdentifierV1] +) + +object AnoncredPresentationV1 { + val version: String = "AnoncredPresentationV1" + + private val schema: String = + """ + |{ + | "$schema":"http://json-schema.org/draft-07/schema#", + | "type":"object", + | "properties":{ + | "proof":{ + | "type":"object", + | "properties":{ + | "proofs":{ + | "type":"array", + | "items":{ + | "type":"object", + | "properties":{ + | "primary_proof":{ + | "type":"object", + | "properties":{ + | "eq_proof":{ + | "type":"object", + | "properties":{ + | "revealed_attrs":{ + | "type":"object", + | "additionalProperties":{ + | "type":"string" + | } + | }, + | "a_prime":{ + | "type":"string" + | }, + | "e":{ + | "type":"string" + | }, + | "v":{ + | "type":"string" + | }, + | "m":{ + | "type":"object", + | "additionalProperties":{ + | "type":"string" + | } + | }, + | "m2":{ + | "type":"string" + | } + | }, + | "required":[ + | "revealed_attrs", + | "a_prime", + | "e", + | "v", + | "m", + | "m2" + | ] + | }, + | "ge_proofs":{ + | "type":"array", + | "items":{ + | "type":"object", + | "properties":{ + | "u":{ + | "type":"object", + | "additionalProperties":{ + | "type":"string" + | } + | }, + | "r":{ + | "type":"object", + | "additionalProperties":{ + | "type":"string" + | } + | }, + | "mj":{ + | "type":"string" + | }, + | "alpha":{ + | "type":"string" + | }, + | "t":{ + | "type":"object", + | "additionalProperties":{ + | "type":"string" + | } + | }, + | "predicate":{ + | "type":"object", + | "properties":{ + | "attr_name":{ + | "type":"string" + | }, + | "p_type":{ + | "type":"string" + | }, + | "value":{ + | "type":"integer" + | } + | }, + | "required":[ + | "attr_name", + | "p_type", + | "value" + | ] + | } + | }, + | "required":[ + | "u", + | "r", + | "mj", + | "alpha", + | "t", + | "predicate" + | ] + | } + | } + | }, + | "required":[ + | "eq_proof", + | "ge_proofs" + | ] + | }, + | "non_revoc_proof":{ + | "oneOf":[ + | { + | "type":"object", + | "properties":{ + | "x_list":{ + | "type":"object", + | "properties":{ + | "rho":{ + | "type":"string" + | }, + | "r":{ + | "type":"string" + | }, + | "r_prime":{ + | "type":"string" + | }, + | "r_prime_prime":{ + | "type":"string" + | }, + | "r_prime_prime_prime":{ + | "type":"string" + | }, + | "o":{ + | "type":"string" + | }, + | "o_prime":{ + | "type":"string" + | }, + | "m":{ + | "type":"string" + | }, + | "m_prime":{ + | "type":"string" + | }, + | "t":{ + | "type":"string" + | }, + | "t_prime":{ + | "type":"string" + | }, + | "m2":{ + | "type":"string" + | }, + | "s":{ + | "type":"string" + | }, + | "c":{ + | "type":"string" + | } + | }, + | "required":[ + | "rho", + | "r", + | "r_prime", + | "r_prime_prime", + | "r_prime_prime_prime", + | "o", + | "o_prime", + | "m", + | "m_prime", + | "t", + | "t_prime", + | "m2", + | "s", + | "c" + | ] + | }, + | "c_list":{ + | "type":"object", + | "properties":{ + | "e":{ + | "type":"string" + | }, + | "d":{ + | "type":"string" + | }, + | "a":{ + | "type":"string" + | }, + | "g":{ + | "type":"string" + | }, + | "w":{ + | "type":"string" + | }, + | "s":{ + | "type":"string" + | }, + | "u":{ + | "type":"string" + | } + | }, + | "required":[ + | "e", + | "d", + | "a", + | "g", + | "w", + | "s", + | "u" + | ] + | } + | }, + | "required":[ + | "x_list", + | "c_list" + | ] + | }, + | { + | "type":"null" + | } + | ] + | } + | }, + | "required":[ + | "primary_proof" + | ] + | } + | }, + | "aggregated_proof":{ + | "type":"object", + | "properties":{ + | "c_hash":{ + | "type":"string" + | }, + | "c_list":{ + | "type":"array", + | "items":{ + | "type":"array", + | "items":{ + | "type":"integer" + | } + | } + | } + | }, + | "required":[ + | "c_hash", + | "c_list" + | ] + | } + | }, + | "required":[ + | "proofs", + | "aggregated_proof" + | ] + | }, + | "requested_proof":{ + | "type":"object", + | "properties":{ + | "revealed_attrs":{ + | "type":"object", + | "additionalProperties":{ + | "type":"object", + | "properties":{ + | "sub_proof_index":{ + | "type":"integer" + | }, + | "raw":{ + | "type":"string" + | }, + | "encoded":{ + | "type":"string" + | } + | }, + | "required":[ + | "sub_proof_index", + | "raw", + | "encoded" + | ] + | } + | }, + | "self_attested_attrs":{ + | "type":"object" + | }, + | "unrevealed_attrs":{ + | "type":"object" + | }, + | "predicates":{ + | "type":"object", + | "additionalProperties":{ + | "type":"object", + | "properties":{ + | "sub_proof_index":{ + | "type":"integer" + | } + | }, + | "required":[ + | "sub_proof_index" + | ] + | } + | } + | }, + | "required":[ + | "revealed_attrs", + | "self_attested_attrs", + | "unrevealed_attrs", + | "predicates" + | ] + | }, + | "identifiers":{ + | "type":"array", + | "items":{ + | "type":"object", + | "properties":{ + | "schema_id":{ + | "type":"string" + | }, + | "cred_def_id":{ + | "type":"string" + | }, + | "rev_reg_id":{ + | "type":[ + | "string", + | "null" + | ] + | }, + | "timestamp":{ + | "type":[ + | "integer", + | "null" + | ] + | } + | }, + | "required":[ + | "schema_id", + | "cred_def_id" + | ] + | } + | } + | }, + | "required":[ + | "proof", + | "requested_proof", + | "identifiers" + | ] + |} + |""".stripMargin + + val schemaSerDes: SchemaSerDes[AnoncredPresentationV1] = SchemaSerDes(schema) + + given JsonDecoder[AnoncredAdditionalPropertiesTypeStringV1] = + DeriveJsonDecoder.gen[AnoncredAdditionalPropertiesTypeStringV1] + + given JsonEncoder[AnoncredAdditionalPropertiesTypeStringV1] = + DeriveJsonEncoder.gen[AnoncredAdditionalPropertiesTypeStringV1] + + given JsonDecoder[AnoncredEqProofV1] = DeriveJsonDecoder.gen[AnoncredEqProofV1] + + given JsonEncoder[AnoncredEqProofV1] = DeriveJsonEncoder.gen[AnoncredEqProofV1] + + given JsonDecoder[AnoncredPredicateV1] = DeriveJsonDecoder.gen[AnoncredPredicateV1] + + given JsonEncoder[AnoncredPredicateV1] = DeriveJsonEncoder.gen[AnoncredPredicateV1] + + given JsonDecoder[AnoncredGeProofV1] = DeriveJsonDecoder.gen[AnoncredGeProofV1] + + given JsonEncoder[AnoncredGeProofV1] = DeriveJsonEncoder.gen[AnoncredGeProofV1] + + given JsonDecoder[AnoncredNonRevocProofXListV1] = DeriveJsonDecoder.gen[AnoncredNonRevocProofXListV1] + + given JsonEncoder[AnoncredNonRevocProofXListV1] = DeriveJsonEncoder.gen[AnoncredNonRevocProofXListV1] + + given JsonDecoder[AnoncredNonRevocProofCListV1] = DeriveJsonDecoder.gen[AnoncredNonRevocProofCListV1] + + given JsonEncoder[AnoncredNonRevocProofCListV1] = DeriveJsonEncoder.gen[AnoncredNonRevocProofCListV1] + + given JsonDecoder[AnoncredNonRevocProofV1] = DeriveJsonDecoder.gen[AnoncredNonRevocProofV1] + + given JsonEncoder[AnoncredNonRevocProofV1] = DeriveJsonEncoder.gen[AnoncredNonRevocProofV1] + + given JsonDecoder[AnoncredPrimaryProofV1] = DeriveJsonDecoder.gen[AnoncredPrimaryProofV1] + + given JsonEncoder[AnoncredPrimaryProofV1] = DeriveJsonEncoder.gen[AnoncredPrimaryProofV1] + + given JsonDecoder[AnoncredAggregatedProofV1] = DeriveJsonDecoder.gen[AnoncredAggregatedProofV1] + + given JsonEncoder[AnoncredAggregatedProofV1] = DeriveJsonEncoder.gen[AnoncredAggregatedProofV1] + + given JsonDecoder[AnoncredSubProofV1] = DeriveJsonDecoder.gen[AnoncredSubProofV1] + + given JsonEncoder[AnoncredSubProofV1] = DeriveJsonEncoder.gen[AnoncredSubProofV1] + + given JsonDecoder[AnoncredProofV1] = DeriveJsonDecoder.gen[AnoncredProofV1] + + given JsonEncoder[AnoncredProofV1] = DeriveJsonEncoder.gen[AnoncredProofV1] + + given JsonDecoder[AnoncredRevealedAttrV1] = DeriveJsonDecoder.gen[AnoncredRevealedAttrV1] + + given JsonEncoder[AnoncredRevealedAttrV1] = DeriveJsonEncoder.gen[AnoncredRevealedAttrV1] + + given JsonDecoder[AnoncredSubProofIndexV1] = DeriveJsonDecoder.gen[AnoncredSubProofIndexV1] + + given JsonEncoder[AnoncredSubProofIndexV1] = DeriveJsonEncoder.gen[AnoncredSubProofIndexV1] + + given JsonDecoder[AnoncredRequestedProofV1] = DeriveJsonDecoder.gen[AnoncredRequestedProofV1] + + given JsonEncoder[AnoncredRequestedProofV1] = DeriveJsonEncoder.gen[AnoncredRequestedProofV1] + + given JsonDecoder[AnoncredIdentifierV1] = DeriveJsonDecoder.gen[AnoncredIdentifierV1] + + given JsonEncoder[AnoncredIdentifierV1] = DeriveJsonEncoder.gen[AnoncredIdentifierV1] + + given JsonDecoder[AnoncredPresentationV1] = DeriveJsonDecoder.gen[AnoncredPresentationV1] + + given JsonEncoder[AnoncredPresentationV1] = DeriveJsonEncoder.gen[AnoncredPresentationV1] + +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala similarity index 79% rename from pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala rename to pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala index 5545985f22..35bd8416c7 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSchemaSerDesSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala @@ -4,8 +4,8 @@ import zio.* import zio.test.* import zio.test.Assertion.* -object AnoncredPresentationRequestSchemaSerDesSpec extends ZIOSpecDefault { - val json = +object AnoncredPresentationRequestSpec extends ZIOSpecDefault { + val json: String = """ |{ | "requested_attributes": { @@ -45,20 +45,20 @@ object AnoncredPresentationRequestSchemaSerDesSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("AnoncredPresentationRequestSerDes")( test("should validate a correct schema") { - assertZIO(AnoncredPresentationRequestSchemaSerDesV1.schemaSerDes.validate(json))(isTrue) + assertZIO(AnoncredPresentationRequestV1.schemaSerDes.validate(json))(isTrue) }, test("should deserialize correctly") { val expectedPresentationRequest = - AnoncredPresentationRequestSchemaSerDesV1( + AnoncredPresentationRequestV1( requested_attributes = Map( - "attribute1" -> AnoncredRequestedAttribute( + "attribute1" -> AnoncredRequestedAttributeV1( "Attribute 1", List( - AnoncredAttributeRestriction( + AnoncredAttributeRestrictionV1( None, Some("credential_definition_id_of_attribute1"), Some( - AnoncredNonRevokedInterval( + AnoncredNonRevokedIntervalV1( Some(1635734400), Some(1735734400) ) @@ -69,16 +69,16 @@ object AnoncredPresentationRequestSchemaSerDesSpec extends ZIOSpecDefault { ), requested_predicates = Map( "predicate1" -> - AnoncredRequestedPredicate( + AnoncredRequestedPredicateV1( "Predicate 1", ">=", 18, List( - AnoncredPredicateRestriction( + AnoncredPredicateRestrictionV1( Some("schema_id_of_predicate1"), None, Some( - AnoncredNonRevokedInterval( + AnoncredNonRevokedIntervalV1( Some(1635734400), None ) @@ -93,7 +93,7 @@ object AnoncredPresentationRequestSchemaSerDesSpec extends ZIOSpecDefault { non_revoked = None ) - assertZIO(AnoncredPresentationRequestSchemaSerDesV1.schemaSerDes.deserialize(json))( + assertZIO(AnoncredPresentationRequestV1.schemaSerDes.deserialize(json))( Assertion.equalTo(expectedPresentationRequest) ) } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala new file mode 100644 index 0000000000..f7e22a8f3e --- /dev/null +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala @@ -0,0 +1,213 @@ +package io.iohk.atala.pollux.core.service.serdes + +import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationV1.* +import zio.* +import zio.test.* +import zio.test.Assertion.* + +object AnoncredPresentationSpec extends ZIOSpecDefault { + val json: String = + """ + |{ + | "proof": { + | "proofs": [ + | { + | "primary_proof": { + | "eq_proof": { + | "revealed_attrs": { + | "sex": "4046863..." + | }, + | "a_prime": "2329247...", + | "e": "1666946...", + | "v": "2137556...", + | "m": { + | "age": "1025474...", + | "master_secret": "8005118...", + | "name": "1031839..." + | }, + | "m2": "5852217..." + | }, + | "ge_proofs": [ + | { + | "u": { + | "1": "1135835...", + | "0": "5209733...", + | "3": "6777506...", + | "2": "8790500..." + | }, + | "r": { + | "DELTA": "1094759...", + | "3": "3908998...", + | "1": "3551550...", + | "2": "4594656...", + | "0": "7769940..." + | }, + | "mj": "1025474...", + | "alpha": "4089177...", + | "t": { + | "0": "1208121...", + | "DELTA": "9078219...", + | "1": "5769003...", + | "3": "4089901...", + | "2": "2261914..." + | }, + | "predicate": { + | "attr_name": "age", + | "p_type": "GE", + | "value": 18 + | } + | } + | ] + | }, + | "non_revoc_proof": null + | } + | ], + | "aggregated_proof": { + | "c_hash": "3880251...", + | "c_list": [ + | [184, 131, 15], + | [95, 179], + | [1, 200, 254], + | [179, 45, 156], + | [1, 67, 251], + | [2, 207, 34, 43] + | ] + | } + | }, + | "requested_proof": { + | "revealed_attrs": { + | "sex": { + | "sub_proof_index": 0, + | "raw": "M", + | "encoded": "4046863..." + | } + | }, + | "self_attested_attrs":{}, + | "unrevealed_attrs":{}, + | "predicates": { + | "age": { + | "sub_proof_index": 0 + | } + | } + | }, + | "identifiers": [ + | { + | "schema_id": "resource:///anoncred-presentation-schema-example.json", + | "cred_def_id": "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + | "rev_reg_id": null, + | "timestamp": null + | } + | ] + |} + | + |""".stripMargin + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("AnoncredPresentationRequestSerDes")( + test("should validate a correct schema") { + assertZIO(AnoncredPresentationV1.schemaSerDes.validate(json))(isTrue) + }, + test("should deserialize correctly") { + val predicate = AnoncredPredicateV1( + attr_name = "age", + p_type = "GE", + value = 18 + ) + + val geProof = AnoncredGeProofV1( + u = Map( + "1" -> "1135835...", + "0" -> "5209733...", + "3" -> "6777506...", + "2" -> "8790500..." + ), + r = Map( + "DELTA" -> "1094759...", + "3" -> "3908998...", + "1" -> "3551550...", + "2" -> "4594656...", + "0" -> "7769940..." + ), + mj = "1025474...", + alpha = "4089177...", + t = Map( + "0" -> "1208121...", + "DELTA" -> "9078219...", + "1" -> "5769003...", + "3" -> "4089901...", + "2" -> "2261914..." + ), + predicate = predicate + ) + + val eqProof = AnoncredEqProofV1( + revealed_attrs = Map("sex" -> "4046863..."), + a_prime = "2329247...", + e = "1666946...", + v = "2137556...", + m = Map( + "age" -> "1025474...", + "master_secret" -> "8005118...", + "name" -> "1031839..." + ), + m2 = "5852217..." + ) + + val primaryProof = AnoncredPrimaryProofV1( + eq_proof = eqProof, + ge_proofs = List(geProof) + ) + + val subProof = AnoncredSubProofV1( + primary_proof = primaryProof, + non_revoc_proof = None + ) + + val aggregatedProof = AnoncredAggregatedProofV1( + c_hash = "3880251...", + c_list = List( + List(184, 131, 15), + List(95, 179), + List(1, 200, 254), + List(179, 45, 156), + List(1, 67, 251), + List(2, 207, 34, 43) + ) + ) + + val revealedAttr = AnoncredRevealedAttrV1( + sub_proof_index = 0, + raw = "M", + encoded = "4046863..." + ) + + val requestedProof = AnoncredRequestedProofV1( + revealed_attrs = Map("sex" -> revealedAttr), + self_attested_attrs = Map.empty, + unrevealed_attrs = Map.empty, + predicates = Map("age" -> AnoncredSubProofIndexV1(0)) + ) + + val identifier = AnoncredIdentifierV1( + schema_id = "resource:///anoncred-presentation-schema-example.json", + cred_def_id = "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + rev_reg_id = None, + timestamp = None + ) + + val anoncredPresentationV1 = AnoncredPresentationV1( + proof = AnoncredProofV1( + proofs = List(subProof), + aggregated_proof = aggregatedProof + ), + requested_proof = requestedProof, + identifiers = List(identifier) + ) + + assert(AnoncredPresentationV1.schemaSerDes.serialize(anoncredPresentationV1))(Assertion.equalTo(json)) + + assertZIO(AnoncredPresentationV1.schemaSerDes.deserialize(json))( + Assertion.equalTo(anoncredPresentationV1) + ) + } + ) +} From 0e1576398d96053f152efc5f81167e2c819e5d03 Mon Sep 17 00:00:00 2001 From: Bassam Date: Wed, 22 Nov 2023 11:41:54 -0500 Subject: [PATCH 06/12] 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 a4166fc107..33eef9094d 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 @@ -161,7 +161,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(_) => From 06bc1c894991b17b2c5a37c4472b05352fde6c1b Mon Sep 17 00:00:00 2001 From: Bassam Date: Fri, 1 Dec 2023 09:37:45 -0500 Subject: [PATCH 07/12] feat: Add Predicate and Attribute through API (#802) Signed-off-by: Bassam Riman --- build.sbt | 3 +- .../iohk/atala/pollux/anoncreds/Models.scala | 16 +- .../core/model/PresentationRecord.scala | 8 +- .../model/schema/validator/SchemaSerDes.scala | 6 +- .../repository/PresentationRepository.scala | 7 + .../PresentationRepositoryInMemory.scala | 40 ++- .../service/MockPresentationService.scala | 16 +- .../core/service/PresentationService.scala | 9 +- .../service/PresentationServiceImpl.scala | 161 ++++++++-- .../service/PresentationServiceNotifier.scala | 12 +- .../serdes/AnoncredCredentialProofsV1.scala | 65 +++++ .../PresentationRepositorySpecSuite.scala | 64 +++- .../PresentationServiceNotifierSpec.scala | 2 + .../service/PresentationServiceSpec.scala | 65 ++++- .../serdes/AnoncredPresentationSpec.scala | 2 +- ...dd_anoncred_credentials_to_use_columns.sql | 4 + .../JdbcPresentationRepository.scala | 60 +++- .../server/jobs/PresentBackgroundJobs.scala | 275 +++++++++++++----- .../PresentProofControllerImpl.scala | 16 +- .../http/RequestPresentationAction.scala | 64 +++- 20 files changed, 747 insertions(+), 148 deletions(-) create mode 100644 pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredCredentialProofsV1.scala create mode 100644 pollux/lib/sql-doobie/src/main/resources/sql/pollux/V17__add_anoncred_credentials_to_use_columns.sql diff --git a/build.sbt b/build.sbt index c4baec8264..ed58186f11 100644 --- a/build.sbt +++ b/build.sbt @@ -139,6 +139,7 @@ lazy val D = new { "com.github.dasniko" % "testcontainers-keycloak" % V.testContainersJavaKeycloak % Test val doobiePostgres: ModuleID = "org.tpolecat" %% "doobie-postgres" % V.doobie + val doobiePostgresCirce: ModuleID = "org.tpolecat" %% "doobie-postgres-circe" % V.doobie val doobieHikari: ModuleID = "org.tpolecat" %% "doobie-hikari" % V.doobie val flyway: ModuleID = "org.flywaydb" % "flyway-core" % V.flyway @@ -157,7 +158,7 @@ lazy val D = new { // LIST of Dependencies val doobieDependencies: Seq[ModuleID] = - Seq(doobiePostgres, doobieHikari, flyway) + Seq(doobiePostgres, doobiePostgresCirce, doobieHikari, flyway) } lazy val D_Shared = new { 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 531ffdb169..ee3e24ced9 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,6 +1,20 @@ package io.iohk.atala.pollux.anoncreds -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 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.* diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/PresentationRecord.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/PresentationRecord.scala index 60f35f11ae..7d0c5f2ef9 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/PresentationRecord.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/PresentationRecord.scala @@ -1,13 +1,13 @@ package io.iohk.atala.pollux.core.model -import io.iohk.atala.mercury.protocol.presentproof.ProposePresentation -import io.iohk.atala.mercury.protocol.presentproof.RequestPresentation -import io.iohk.atala.mercury.protocol.presentproof.Presentation import io.iohk.atala.mercury.model.DidId +import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProposePresentation, RequestPresentation} import java.time.Instant import java.time.temporal.ChronoUnit +type AnoncredCredentialProofs = zio.json.ast.Json + final case class PresentationRecord( id: DidCommID, createdAt: Instant, @@ -23,6 +23,8 @@ final case class PresentationRecord( proposePresentationData: Option[ProposePresentation], presentationData: Option[Presentation], credentialsToUse: Option[List[String]], + anoncredCredentialsToUseJsonSchemaId: Option[String], + anoncredCredentialsToUse: Option[AnoncredCredentialProofs], metaRetries: Int, metaNextRetry: Option[Instant], metaLastFailure: Option[String], diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala index 82fba4989f..e3cfb4a239 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala @@ -12,10 +12,14 @@ class SchemaSerDes[S](jsonSchemaSchemaStr: String) { def initialiseJsonSchema: IO[JsonSchemaError, JsonSchema] = JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr) - def serialize(instance: S)(using encoder: JsonEncoder[S]): String = { + def serializeToJsonString(instance: S)(using encoder: JsonEncoder[S]): String = { instance.toJson } + def serialize(instance: S)(using encoder: JsonEncoder[S]): Either[String, Json] = { + instance.toJsonAST + } + def deserialize( schema: zio.json.ast.Json )(using decoder: JsonDecoder[S]): IO[JsonSchemaError, S] = { diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala index 506a5b6e66..5f361ac2fa 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala @@ -51,6 +51,13 @@ trait PresentationRepository { protocolState: ProtocolState ): RIO[WalletAccessContext, Int] + def updateAnoncredPresentationWithCredentialsToUse( + recordId: DidCommID, + anoncredCredentialsToUseJsonSchemaId: Option[String], + anoncredCredentialsToUse: Option[AnoncredCredentialProofs], + protocolState: ProtocolState + ): RIO[WalletAccessContext, Int] + def updateAfterFail( recordId: DidCommID, failReason: Option[String] diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala index d4a663d218..76cb0f0154 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala @@ -1,11 +1,10 @@ package io.iohk.atala.pollux.core.repository -import io.iohk.atala.mercury.protocol.presentproof._ +import io.iohk.atala.mercury.protocol.presentproof.* +import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.PresentationRecord.ProtocolState -import io.iohk.atala.pollux.core.model._ -import io.iohk.atala.pollux.core.model.error.PresentationError._ -import io.iohk.atala.shared.models.WalletAccessContext -import io.iohk.atala.shared.models.WalletId +import io.iohk.atala.pollux.core.model.error.PresentationError.* +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* import java.time.Instant @@ -114,6 +113,37 @@ class PresentationRepositoryInMemory( } yield count } + def updateAnoncredPresentationWithCredentialsToUse( + recordId: DidCommID, + anoncredCredentialsToUseJsonSchemaId: Option[String], + anoncredCredentialsToUse: Option[AnoncredCredentialProofs], + protocolState: ProtocolState + ): RIO[WalletAccessContext, Int] = { + for { + storeRef <- walletStoreRef + maybeRecord <- getPresentationRecord(recordId) + count <- maybeRecord + .map(record => + for { + _ <- storeRef.update(r => + r.updated( + recordId, + record.copy( + updatedAt = Some(Instant.now), + anoncredCredentialsToUseJsonSchemaId = anoncredCredentialsToUseJsonSchemaId, + anoncredCredentialsToUse = anoncredCredentialsToUse, + protocolState = protocolState, + metaRetries = maxRetries, + metaLastFailure = None, + ) + ) + ) + } yield 1 + ) + .getOrElse(ZIO.succeed(0)) + } yield count + } + override def updateWithPresentation( recordId: DidCommID, presentation: Presentation, 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 9be0cfb4c2..736f0ca451 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 @@ -6,7 +6,7 @@ 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} -import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 +import io.iohk.atala.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import io.iohk.atala.pollux.vc.jwt.{Issuer, PresentationPayload, W3cCredentialPayload} import io.iohk.atala.shared.models.WalletAccessContext import zio.mock.{Mock, Proxy} @@ -45,6 +45,13 @@ object MockPresentationService extends Mock[PresentationService] { object AcceptRequestPresentation extends Effect[(DidCommID, Seq[String]), PresentationError, PresentationRecord] + object AcceptAnoncredRequestPresentation + extends Effect[ + (DidCommID, AnoncredCredentialProofsV1), + PresentationError, + PresentationRecord + ] + object RejectRequestPresentation extends Effect[DidCommID, PresentationError, PresentationRecord] object MarkPresentationGenerated extends Effect[(DidCommID, Presentation), PresentationError, PresentationRecord] @@ -95,6 +102,12 @@ object MockPresentationService extends Mock[PresentationService] { ): IO[PresentationError, PresentationRecord] = proxy(AcceptRequestPresentation, (recordId, credentialsToUse)) + override def acceptAnoncredRequestPresentation( + recordId: DidCommID, + credentialsToUse: AnoncredCredentialProofsV1 + ): IO[PresentationError, PresentationRecord] = + proxy(AcceptAnoncredRequestPresentation, (recordId, credentialsToUse)) + override def rejectRequestPresentation(recordId: DidCommID): IO[PresentationError, PresentationRecord] = proxy(RejectRequestPresentation, recordId) @@ -152,6 +165,7 @@ object MockPresentationService extends Mock[PresentationService] { override def createAnoncredPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, issuanceDate: Instant ): IO[PresentationError, AnoncredPresentation] = ??? 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 c191dba5d1..88e5a0759a 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 @@ -6,7 +6,7 @@ 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.* -import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 +import io.iohk.atala.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import zio.* @@ -16,7 +16,6 @@ import java.util as ju import java.util.UUID trait PresentationService { - def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] def createJwtPresentationRecord( @@ -49,6 +48,7 @@ trait PresentationService { def createAnoncredPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] @@ -82,6 +82,11 @@ trait PresentationService { credentialsToUse: Seq[String] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] + def acceptAnoncredRequestPresentation( + recordId: DidCommID, + credentialsToUse: AnoncredCredentialProofsV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] + def rejectRequestPresentation(recordId: DidCommID): ZIO[WalletAccessContext, PresentationError, PresentationRecord] def receiveProposePresentation( 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 2090e38628..c7b866c45c 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,7 +6,7 @@ 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.issuecredential.IssueCredentialIssuedFormat import io.iohk.atala.mercury.protocol.presentproof.* import io.iohk.atala.pollux.anoncreds.* import io.iohk.atala.pollux.core.model.* @@ -16,11 +16,15 @@ 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.core.service.serdes.{ + AnoncredCredentialProofV1, + AnoncredCredentialProofsV1, + AnoncredPresentationRequestV1 +} import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect -import zio.* +import zio.{ZIO, *} import java.net.URI import java.rmi.UnexpectedException @@ -110,6 +114,7 @@ private class PresentationServiceImpl( override def createAnoncredPresentationPayloadFromRecord( recordId: DidCommID, prover: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { @@ -120,20 +125,19 @@ private class PresentationServiceImpl( 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) + issuedValidCredentials <- + credentialRepository + .getValidAnoncredIssuedCredentials( + anoncredCredentialProof.credentialProofs.map(credentialProof => DidCommID(credentialProof.credential)) + ) + .mapError(RepositoryError.apply) issuedCredentials <- ZIO.fromEither( Either.cond( - signedCredentials.nonEmpty, - signedCredentials, + issuedValidCredentials.nonEmpty, + issuedValidCredentials, PresentationError.IssuedCredentialNotFoundError( new Throwable("No matching issued credentials found in prover db") ) @@ -144,6 +148,7 @@ private class PresentationServiceImpl( issuedValidCredentials.flatMap(_.schemaId), issuedValidCredentials.flatMap(_.credentialDefinitionId), requestPresentation, + anoncredCredentialProof.credentialProofs ) } yield presentationPayload } @@ -224,6 +229,8 @@ private class PresentationServiceImpl( proposePresentationData = None, presentationData = None, credentialsToUse = None, + anoncredCredentialsToUseJsonSchemaId = None, + anoncredCredentialsToUse = None, metaRetries = maxRetries, metaNextRetry = Some(Instant.now()), metaLastFailure = None, @@ -274,6 +281,8 @@ private class PresentationServiceImpl( proposePresentationData = None, presentationData = None, credentialsToUse = None, + anoncredCredentialsToUseJsonSchemaId = None, + anoncredCredentialsToUse = None, metaRetries = maxRetries, metaNextRetry = Some(Instant.now()), metaLastFailure = None, @@ -356,6 +365,8 @@ private class PresentationServiceImpl( proposePresentationData = None, presentationData = None, credentialsToUse = None, + anoncredCredentialsToUseJsonSchemaId = None, + anoncredCredentialsToUse = None, metaRetries = maxRetries, metaNextRetry = Some(Instant.now()), metaLastFailure = None, @@ -441,11 +452,18 @@ private class PresentationServiceImpl( } yield presentationPayload } + case class AnoncredCredentialProof( + credential: String, + requestedAttribute: Seq[String], + requestedPredicate: Seq[String] + ) + private def createAnoncredPresentationPayloadFromCredential( - issuedCredentials: Seq[IssueCredential], + issuedCredentialRecords: Seq[ValidFullIssuedCredentialRecord], schemaIds: Seq[String], credentialDefinitionIds: Seq[UUID], requestPresentation: RequestPresentation, + credentialProofs: List[AnoncredCredentialProofV1], ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { for { schemaMap <- @@ -464,18 +482,32 @@ private class PresentationServiceImpl( } yield (credentialDefinition.longId, CredentialDefinition(credentialDefinition.definition.toString)) }) .map(_.toMap) - + credentialProofsMap = credentialProofs.map(credentialProof => (credentialProof.credential, credentialProof)).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(_)) + issuedCredentialRecords + .flatMap(issuedCredentialRecord => { + issuedCredentialRecord.issuedCredential + .map(issuedCredential => + issuedCredential.attachments + .filter(attachment => attachment.format.contains(IssueCredentialIssuedFormat.Anoncred.name)) + .map(_.data) + .map { + case Base64(data) => + Right( + AnoncredCredentialProof( + new String(JBase64.getUrlDecoder.decode(data)), + credentialProofsMap(issuedCredentialRecord.id.value).requestedAttribute, + credentialProofsMap(issuedCredentialRecord.id.value).requestedPredicate + ) + ) + case _ => Left(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) + } + .map(ZIO.fromEither(_)) + ) + .toSeq + .flatten + }) ) presentationRequestAttachment <- ZIO.fromEither( requestPresentation.attachments.headOption.toRight(InvalidAnoncredPresentationRequest("Missing Presentation")) @@ -500,9 +532,9 @@ private class PresentationServiceImpl( PresentationRequest(presentationRequestData), verifiableCredentials.map(verifiableCredential => CredentialRequests( - Credential(verifiableCredential), - deserializedPresentationRequestData.requested_attributes.keys.toSeq, // TO FIX - deserializedPresentationRequestData.requested_predicates.keys.toSeq // TO FIX + Credential(verifiableCredential.credential), + verifiableCredential.requestedAttribute, + verifiableCredential.requestedPredicate ) ), Map.empty, // TO FIX @@ -592,6 +624,81 @@ private class PresentationServiceImpl( } yield record } + override def acceptAnoncredRequestPresentation( + recordId: DidCommID, + credentialsToUse: AnoncredCredentialProofsV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + + for { + record <- getRecordWithState(recordId, ProtocolState.RequestReceived) + issuedCredentials <- credentialRepository + .getValidIssuedCredentials( + credentialsToUse.credentialProofs.map(credentialProof => DidCommID(credentialProof.credential)) + ) + .mapError(RepositoryError.apply) + _ <- ZIO.cond( + (issuedCredentials.map(_.subjectId).toSet.size == 1), + (), + PresentationError.HolderBindingError( + s"Creating a Verifiable Presentation for credential with different subject DID is not supported, found : ${issuedCredentials + .map(_.subjectId)}" + ) + ) + validatedCredentials <- ZIO.fromEither( + Either.cond( + issuedCredentials.forall(issuedValidCredential => + issuedValidCredential.credentialFormat == record.credentialFormat + ), + issuedCredentials, + PresentationError.NotMatchingPresentationCredentialFormat( + new IllegalArgumentException( + s"No matching issued credentials format: expectedFormat=${record.credentialFormat}" + ) + ) + ) + ) + signedCredentials = validatedCredentials.flatMap(_.issuedCredentialRaw) + _ <- ZIO.fromEither( + Either.cond( + signedCredentials.nonEmpty, + signedCredentials, + PresentationError.IssuedCredentialNotFoundError( + new Throwable(s"No matching issued credentials found in prover db from the given: $credentialsToUse") + ) + ) + ) + anoncredCredentialProofsV1AsJson <- ZIO + .fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + .mapError(error => + PresentationError.UnexpectedError( + s"Unable to serialize credentialsToUse. credentialsToUse:$credentialsToUse, error:$error" + ) + ) + count <- presentationRepository + .updateAnoncredPresentationWithCredentialsToUse( + recordId, + Option(AnoncredCredentialProofsV1.version), + Option(anoncredCredentialProofsV1AsJson), + ProtocolState.PresentationPending + ) + .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( + s"${record.id}_present_proof_flow_prover_presentation_pending_to_generated_ms_gauge" + ) + _ <- count match + case 1 => ZIO.succeed(()) + case n => ZIO.fail(RecordIdNotFound(recordId)) + record <- presentationRepository + .getPresentationRecord(recordId) + .mapError(RepositoryError.apply) + .flatMap { + case None => ZIO.fail(RecordIdNotFound(record.id)) + case Some(value) => ZIO.succeed(value) + } + } yield record + } + override def acceptPresentation( recordId: DidCommID ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { @@ -817,7 +924,7 @@ private class PresentationServiceImpl( AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(presentationRequest).getBytes() + payload = AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(presentationRequest).getBytes() ) } 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 0b890e1e2b..894e53858c 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 @@ -7,7 +7,7 @@ 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} -import io.iohk.atala.pollux.core.service.serdes.AnoncredPresentationRequestV1 +import io.iohk.atala.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import io.iohk.atala.pollux.vc.jwt.{Issuer, PresentationPayload, W3cCredentialPayload} import io.iohk.atala.shared.models.WalletAccessContext import zio.{IO, URLayer, ZIO, ZLayer} @@ -80,6 +80,13 @@ class PresentationServiceNotifier( ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess(svc.acceptRequestPresentation(recordId, credentialsToUse)) + override def acceptAnoncredRequestPresentation( + recordId: DidCommID, + credentialsToUse: AnoncredCredentialProofsV1 + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess( + svc.acceptAnoncredRequestPresentation(recordId, credentialsToUse) + ) + override def rejectRequestPresentation( recordId: DidCommID ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = @@ -154,9 +161,10 @@ class PresentationServiceNotifier( override def createAnoncredPresentationPayloadFromRecord( record: DidCommID, issuer: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = - svc.createAnoncredPresentationPayloadFromRecord(record, issuer, issuanceDate) + svc.createAnoncredPresentationPayloadFromRecord(record, issuer, anoncredCredentialProof, issuanceDate) override def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredCredentialProofsV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredCredentialProofsV1.scala new file mode 100644 index 0000000000..fc8dfdd3ef --- /dev/null +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredCredentialProofsV1.scala @@ -0,0 +1,65 @@ +package io.iohk.atala.pollux.core.service.serdes + +import io.iohk.atala.pollux.core.model.schema.validator.SchemaSerDes +import zio.* +import zio.json.* + +case class AnoncredCredentialProofV1( + credential: String, + requestedAttribute: Seq[String], + requestedPredicate: Seq[String] +) + +case class AnoncredCredentialProofsV1(credentialProofs: List[AnoncredCredentialProofV1]) + +object AnoncredCredentialProofsV1 { + val version: String = "AnoncredCredentialProofsV1" + private val schema: String = + """ + |{ + | "$schema": "http://json-schema.org/draft-07/schema#", + | "type": "object", + | "properties": { + | "credentialProofs": { + | "type": "array", + | "items": { + | "type": "object", + | "properties": { + | "credential": { + | "type": "string" + | }, + | "requestedAttribute": { + | "type": "array", + | "items": { + | "type": "string" + | } + | }, + | "requestedPredicate": { + | "type": "array", + | "items": { + | "type": "string" + | } + | } + | }, + | "required": ["credential", "requestedAttribute", "requestedPredicate"] + | } + | } + | }, + | "required": ["credentialProofs"] + |} + |""".stripMargin + + val schemaSerDes: SchemaSerDes[AnoncredCredentialProofsV1] = SchemaSerDes(schema) + + given JsonDecoder[AnoncredCredentialProofV1] = + DeriveJsonDecoder.gen[AnoncredCredentialProofV1] + + given JsonEncoder[AnoncredCredentialProofV1] = + DeriveJsonEncoder.gen[AnoncredCredentialProofV1] + + given JsonDecoder[AnoncredCredentialProofsV1] = + DeriveJsonDecoder.gen[AnoncredCredentialProofsV1] + + given JsonEncoder[AnoncredCredentialProofsV1] = + DeriveJsonEncoder.gen[AnoncredCredentialProofsV1] +} diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala index f1e0ed5b7d..6085681c06 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala @@ -4,8 +4,8 @@ import io.iohk.atala.mercury.model.DidId import io.iohk.atala.mercury.protocol.presentproof.{Presentation, ProposePresentation, RequestPresentation} import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.PresentationRecord.* -import io.iohk.atala.shared.models.WalletAccessContext -import io.iohk.atala.shared.models.WalletId +import io.iohk.atala.pollux.core.service.serdes.{AnoncredCredentialProofV1, AnoncredCredentialProofsV1} +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.test.* import zio.test.Assertion.* import zio.{ZIO, ZLayer} @@ -31,6 +31,8 @@ object PresentationRepositorySpecSuite { proposePresentationData = None, presentationData = None, credentialsToUse = None, + anoncredCredentialsToUseJsonSchemaId = None, + anoncredCredentialsToUse = None, metaRetries = maxRetries, metaNextRetry = Some(Instant.now()), metaLastFailure = None, @@ -195,6 +197,64 @@ object PresentationRepositorySpecSuite { assertTrue(records.exists(_.credentialsToUse.contains(Seq("credential1", "credential2")))) } }, + test("updatePresentationWithCredentialsToUse updates the record") { + for { + repo <- ZIO.service[PresentationRepository] + aRecord = presentationRecord + bRecord = presentationRecord + cRecord = presentationRecord + _ <- repo.createPresentationRecord(aRecord) + _ <- repo.createPresentationRecord(bRecord) + _ <- repo.createPresentationRecord(cRecord) + anoncredCredentialProofs = AnoncredCredentialProofsV1( + List(AnoncredCredentialProofV1("credential1", Seq("requestedAttribute"), Seq("requestedPredicate"))) + ) + anoncredCredentialProofsJson <- ZIO.fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(anoncredCredentialProofs) + ) + _ <- repo.updateAnoncredPresentationWithCredentialsToUse( + aRecord.id, + Some(AnoncredCredentialProofsV1.version), + Some(anoncredCredentialProofsJson), + ProtocolState.PresentationPending + ) + records <- repo.getPresentationRecord(aRecord.id) + } yield { + assertTrue(records.size == 1) && + assertTrue(records.exists(_.anoncredCredentialsToUse.contains(anoncredCredentialProofsJson))) && + assertTrue(records.exists(_.anoncredCredentialsToUseJsonSchemaId.contains(AnoncredCredentialProofsV1.version))) + } + }, + test("updatePresentationWithCredentialsToUse updates the record") { + for { + repo <- ZIO.service[PresentationRepository] + aRecord = presentationRecord + bRecord = presentationRecord + cRecord = presentationRecord + _ <- repo.createPresentationRecord(aRecord) + _ <- repo.createPresentationRecord(bRecord) + _ <- repo.createPresentationRecord(cRecord) + anoncredCredentialProofs = AnoncredCredentialProofsV1( + List(AnoncredCredentialProofV1("credential1", Seq("requestedAttribute"), Seq("requestedPredicate"))) + ) + anoncredCredentialProofsJson <- ZIO + .fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(anoncredCredentialProofs) + ) + + _ <- repo.updateAnoncredPresentationWithCredentialsToUse( + aRecord.id, + Some(AnoncredCredentialProofsV1.version), + Some(anoncredCredentialProofsJson), + ProtocolState.PresentationPending + ) + records <- repo.getPresentationRecord(aRecord.id) + } yield { + assertTrue(records.size == 1) && + assertTrue(records.exists(_.anoncredCredentialsToUse.contains(anoncredCredentialProofsJson))) && + assertTrue(records.exists(_.anoncredCredentialsToUseJsonSchemaId.contains(AnoncredCredentialProofsV1.version))) + } + }, test("updateCredentialRecordProtocolState updates the record") { for { repo <- ZIO.service[PresentationRepository] diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala index 759f64326f..f08aa46f16 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifierSpec.scala @@ -30,6 +30,8 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS None, None, None, + None, + None, 5, None, None 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 bb11014c79..09e89abae4 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 @@ -14,7 +14,12 @@ 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, AnoncredPresentationV1} +import io.iohk.atala.pollux.core.service.serdes.{ + AnoncredCredentialProofV1, + AnoncredCredentialProofsV1, + AnoncredPresentationRequestV1, + AnoncredPresentationV1 +} import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* @@ -153,7 +158,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp Seq( Base64( JBase64.getUrlEncoder.encodeToString( - AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + AnoncredPresentationRequestV1.schemaSerDes + .serializeToJsonString(anoncredPresentationRequestV1) + .getBytes() ) ) ) @@ -322,13 +329,32 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp svc <- ZIO.service[PresentationService] aRecord <- svc.createAnoncredRecord() repo <- ZIO.service[PresentationRepository] - _ <- repo.updatePresentationWithCredentialsToUse( + credentialsToUse = + AnoncredCredentialProofsV1( + List( + AnoncredCredentialProofV1( + aIssueCredentialRecord.id.value, + Seq("sex"), + Seq("age") + ) + ) + ) + credentialsToUseJson <- ZIO.fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + _ <- repo.updateAnoncredPresentationWithCredentialsToUse( aRecord.id, - Some(Seq(aIssueCredentialRecord.id.value)), + Some(AnoncredPresentationV1.version), + Some(credentialsToUseJson), PresentationRecord.ProtocolState.RequestPending ) issuer = createIssuer(DID("did:prism:issuer")) - aPresentationPayload <- svc.createAnoncredPresentationPayloadFromRecord(aRecord.id, issuer, Instant.now()) + aPresentationPayload <- svc.createAnoncredPresentationPayloadFromRecord( + aRecord.id, + issuer, + credentialsToUse, + Instant.now() + ) validation <- AnoncredPresentationV1.schemaSerDes.validate(aPresentationPayload.data) presentation <- AnoncredPresentationV1.schemaSerDes.deserialize(aPresentationPayload.data) } yield { @@ -472,7 +498,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + payload = + AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(anoncredPresentationRequestV1).getBytes() ) requestPresentation = RequestPresentation( body = body, @@ -603,7 +630,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + payload = + AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(anoncredPresentationRequestV1).getBytes() ) requestPresentation = RequestPresentation( body = RequestPresentation.Body(goal_code = Some("Presentation Request")), @@ -612,13 +640,25 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp from = DidId("did:peer:Verifier"), ) aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) - credentialsToUse = Seq(aIssueCredentialRecord.id.value) - updateRecord <- svc.acceptRequestPresentation(aRecord.id, credentialsToUse) + credentialsToUse = + AnoncredCredentialProofsV1( + List( + AnoncredCredentialProofV1( + aIssueCredentialRecord.id.value, + Seq("requestedAttribute"), + Seq("requestedPredicate") + ) + ) + ) + anoncredCredentialProofsJson <- ZIO.fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + updateRecord <- svc.acceptAnoncredRequestPresentation(aRecord.id, credentialsToUse) } yield { assertTrue(updateRecord.connectionId == connectionId) && - // assertTrue(updateRecord.requestPresentationData == Some(requestPresentation)) && // FIXME: enabling them make the test fail. - assertTrue(updateRecord.credentialsToUse.contains(credentialsToUse)) + assertTrue(updateRecord.anoncredCredentialsToUse.contains(anoncredCredentialProofsJson)) && + assertTrue(updateRecord.anoncredCredentialsToUseJsonSchemaId.contains(AnoncredCredentialProofsV1.version)) } }, test("acceptRequestPresentation should fail given unmatching format") { @@ -647,7 +687,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = AnoncredPresentationRequestV1.schemaSerDes.serialize(anoncredPresentationRequestV1).getBytes() + payload = + AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(anoncredPresentationRequestV1).getBytes() ) requestPresentation = RequestPresentation( body = RequestPresentation.Body(goal_code = Some("Presentation Request")), diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala index f7e22a8f3e..88e3fc8620 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala @@ -203,7 +203,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { identifiers = List(identifier) ) - assert(AnoncredPresentationV1.schemaSerDes.serialize(anoncredPresentationV1))(Assertion.equalTo(json)) + assert(AnoncredPresentationV1.schemaSerDes.serializeToJsonString(anoncredPresentationV1))(Assertion.equalTo(json)) assertZIO(AnoncredPresentationV1.schemaSerDes.deserialize(json))( Assertion.equalTo(anoncredPresentationV1) diff --git a/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V17__add_anoncred_credentials_to_use_columns.sql b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V17__add_anoncred_credentials_to_use_columns.sql new file mode 100644 index 0000000000..dde029abeb --- /dev/null +++ b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V17__add_anoncred_credentials_to_use_columns.sql @@ -0,0 +1,4 @@ +-- presentation_records +ALTER TABLE public.presentation_records + ADD COLUMN "anoncred_credentials_to_use_json_schema_id" VARCHAR(64), + ADD COLUMN "anoncred_credentials_to_use" JSON; \ No newline at end of file diff --git a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala index ac5cc921b9..19acb64d76 100644 --- a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala +++ b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala @@ -2,9 +2,12 @@ package io.iohk.atala.pollux.sql.repository import cats.data.NonEmptyList import doobie.* +import doobie.free.connection import doobie.implicits.* import doobie.postgres.* import doobie.postgres.implicits.* +import doobie.postgres.circe.json.implicits._ +import io.circe import io.circe.* import io.circe.parser.* import io.circe.syntax.* @@ -18,9 +21,11 @@ import io.iohk.atala.shared.db.Implicits.* import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.BytesOps import zio.* -import doobie.free.connection -import java.time.Instant import zio.interop.catz.* +import zio.json.* +import zio.json.ast.Json +import zio.json.ast.Json.* +import java.time.Instant // TODO: replace with actual implementation class JdbcPresentationRepository( xa: Transactor[ContextAwareTask], @@ -51,6 +56,31 @@ class JdbcPresentationRepository( .transactWallet(xa) } + def updateAnoncredPresentationWithCredentialsToUse( + recordId: DidCommID, + anoncredCredentialsToUseJsonSchemaId: Option[String], + anoncredCredentialsToUse: Option[AnoncredCredentialProofs], + protocolState: ProtocolState + ): RIO[WalletAccessContext, Int] = { + val cxnIO = + sql""" + | UPDATE public.presentation_records + | SET + | anoncred_credentials_to_use_json_schema_id = ${anoncredCredentialsToUseJsonSchemaId}, + | anoncred_credentials_to_use = ${anoncredCredentialsToUse}, + | protocol_state = $protocolState, + | updated_at = ${Instant.now}, + | meta_retries = $maxRetries, + | meta_next_retry = ${Instant.now}, + | meta_last_failure = null + | WHERE + | id = $recordId + """.stripMargin.update + + cxnIO.run + .transactWallet(xa) + } + // deserializes from the hex string private def deserializeInclusionProof(proof: String): MerkleInclusionProof = MerkleInclusionProof.decode( @@ -64,6 +94,20 @@ class JdbcPresentationRepository( import PresentationRecord.* + def zioJsonToCirceJson(zioJson: Json): circe.Json = { + parse(zioJson.toString).getOrElse(circe.Json.Null) + } + + def circeJsonToZioJson(circeJson: circe.Json): Json = { + circeJson.noSpaces.fromJson[Json].getOrElse(Json.Null) + } + + given jsonGet: Get[AnoncredCredentialProofs] = Get[circe.Json].map { jsonString => + circeJsonToZioJson(jsonString) + } + + given jsonPut: Put[AnoncredCredentialProofs] = Put[circe.Json].contramap(zioJsonToCirceJson(_)) + given didCommIDGet: Get[DidCommID] = Get[String].map(DidCommID(_)) given didCommIDPut: Put[DidCommID] = Put[String].contramap(_.value) @@ -109,6 +153,8 @@ class JdbcPresentationRepository( | credential_format, | request_presentation_data, | credentials_to_use, + | anoncred_credentials_to_use_json_schema_id, + | anoncred_credentials_to_use, | meta_retries, | meta_next_retry, | meta_last_failure, @@ -126,6 +172,8 @@ class JdbcPresentationRepository( | ${record.credentialFormat}, | ${record.requestPresentationData}, | ${record.credentialsToUse.map(_.toList)}, + | ${record.anoncredCredentialsToUseJsonSchemaId}, + | ${record.anoncredCredentialsToUse}, | ${record.metaRetries}, | ${record.metaNextRetry}, | ${record.metaLastFailure}, @@ -159,6 +207,8 @@ class JdbcPresentationRepository( | propose_presentation_data, | presentation_data, | credentials_to_use, + | anoncred_credentials_to_use_json_schema_id, + | anoncred_credentials_to_use, | meta_retries, | meta_next_retry, | meta_last_failure @@ -205,6 +255,8 @@ class JdbcPresentationRepository( | propose_presentation_data, | presentation_data, | credentials_to_use, + | anoncred_credentials_to_use_json_schema_id, + | anoncred_credentials_to_use, | meta_retries, | meta_next_retry, | meta_last_failure @@ -249,6 +301,8 @@ class JdbcPresentationRepository( | propose_presentation_data, | presentation_data, | credentials_to_use, + | anoncred_credentials_to_use_json_schema_id, + | anoncred_credentials_to_use, | meta_retries, | meta_next_retry, | meta_last_failure @@ -281,6 +335,8 @@ class JdbcPresentationRepository( | propose_presentation_data, | presentation_data, | credentials_to_use, + | anoncred_credentials_to_use_json_schema_id, + | anoncred_credentials_to_use, | meta_retries, | meta_next_retry, | meta_last_failure 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 6a005c27a0..988e235d11 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 @@ -21,12 +21,14 @@ import io.iohk.atala.mercury.protocol.reportproblem.v2.* import io.iohk.atala.pollux.core.model.* 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.serdes.AnoncredCredentialProofsV1 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.json.ast.Json import zio.metrics.* import zio.prelude.Validation import zio.prelude.ZValidation.* @@ -161,16 +163,36 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { // ########################## // ### PresentationRecord ### // ########################## - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalPending, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalPending, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalSent, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalSent, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalReceived, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalReceived, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalRejected, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalRejected, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, RequestPending, _, oRecord, _, _, _, _, _, _) => // Verifier + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + RequestPending, + _, + oRecord, + _, + _, + _, + _, + _, + _, + _, + _ + ) => // Verifier oRecord match case None => ZIO.fail(InvalidState("PresentationRecord 'RequestPending' with no Record")) case Some(record) => @@ -209,17 +231,17 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { .gauge("present_proof_flow_verifier_req_pending_to_sent_flow_ms_gauge") .trackDurationWith(_.toMetricsSeconds) - case PresentationRecord(id, _, _, _, _, _, _, _, RequestSent, _, _, _, _, _, _, _, _) => // Verifier + case PresentationRecord(id, _, _, _, _, _, _, _, RequestSent, _, _, _, _, _, _, _, _, _, _) => // Verifier ZIO.logDebug("PresentationRecord: RequestSent") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, RequestReceived, _, _, _, _, _, _, _, _) => // Prover + case PresentationRecord(id, _, _, _, _, _, _, _, RequestReceived, _, _, _, _, _, _, _, _, _, _) => // Prover ZIO.logDebug("PresentationRecord: RequestReceived") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, RequestRejected, _, _, _, _, _, _, _, _) => // Prover + case PresentationRecord(id, _, _, _, _, _, _, _, RequestRejected, _, _, _, _, _, _, _, _, _, _) => // Prover ZIO.logDebug("PresentationRecord: RequestRejected") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportPending, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportPending, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportSent, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportSent, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportReceived, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportReceived, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) case PresentationRecord( id, @@ -231,13 +253,15 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, PresentationPending, - credentialFormat, + CredentialFormat.JWT, oRequestPresentation, _, _, credentialsToUse, _, _, + _, + _, _ ) => // Prover oRequestPresentation match @@ -250,71 +274,140 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) presentation <- - credentialFormat match { - case CredentialFormat.JWT => - for { - presentationPayload <- - presentationService - .createJwtPresentationPayloadFromRecord( - id, - prover, - Instant.now() - ) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - signedJwtPresentation = JwtPresentation.toEncodedJwt( - presentationPayload.toW3CPresentationPayload, - prover + for { + presentationPayload <- + presentationService + .createJwtPresentationPayloadFromRecord( + id, + prover, + Instant.now() ) - 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)) + 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") ) - .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 - ) + ), + thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), + from = requestPresentation.to, + to = requestPresentation.from + ) + ) + } yield presentation + _ <- presentationService + .markPresentationGenerated(id, presentation) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield ()).mapError(e => (walletAccessContext, handlePresentationErrors(e))) + } yield result + + proverPresentationPendingToGeneratedFlow + @@ ProverPresentationPendingToGeneratedSuccess.trackSuccess + @@ ProverPresentationPendingToGeneratedFailed.trackError + @@ ProverPresentationPendingToGenerated + @@ Metric + .gauge("present_proof_flow_prover_presentation_pending_to_generated_flow_ms_gauge") + .trackDurationWith(_.toMetricsSeconds) + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + PresentationPending, + CredentialFormat.AnonCreds, + oRequestPresentation, + _, + _, + _, + _, + None, + _, + _, + _ + ) => // Prover + ZIO.fail(NotImplemented) + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + PresentationPending, + CredentialFormat.AnonCreds, + oRequestPresentation, + _, + _, + _, + _, + Some(credentialsToUseJson), + _, + _, + _ + ) => // Prover + oRequestPresentation match + case None => ZIO.fail(InvalidState("PresentationRecord 'RequestPending' with no Record")) + case Some(requestPresentation) => // TODO create build method in mercury for Presentation + val proverPresentationPendingToGeneratedFlow = for { + walletAccessContext <- buildWalletAccessContextLayer(requestPresentation.to) + result <- (for { + presentationService <- ZIO.service[PresentationService] + anoncredCredentialProofs <- + AnoncredCredentialProofsV1.schemaSerDes + .deserialize(credentialsToUseJson) + .mapError(error => PresentationError.UnexpectedError(error.error)) + prover <- createPrismDIDIssuerFromPresentationCredentials( + id, + anoncredCredentialProofs.credentialProofs.map(_.credential) + ).provideSomeLayer(ZLayer.succeed(walletAccessContext)) + presentation <- + for { + presentationPayload <- + presentationService + .createAnoncredPresentationPayloadFromRecord( + id, + prover, + anoncredCredentialProofs, + Instant.now() ) - } yield presentation - } + .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)) @@ -346,6 +439,8 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, + _, _ ) => // Prover ZIO.logDebug("PresentationRecord: PresentationGenerated") *> ZIO.unit @@ -388,7 +483,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { .gauge("present_proof_flow_prover_presentation_generated_to_sent_flow_ms_gauge") .trackDurationWith(_.toMetricsSeconds) - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationSent, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationSent, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationSent") *> ZIO.unit case PresentationRecord( id, @@ -407,6 +502,8 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, + _, _ ) => // Verifier ZIO.logDebug("PresentationRecord: PresentationReceived") *> ZIO.unit @@ -518,13 +615,33 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) .trackDurationWith(_.toMetricsSeconds) - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationVerificationFailed, _, _, _, _, _, _, _, _) => + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + PresentationVerificationFailed, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _ + ) => ZIO.logDebug("PresentationRecord: PresentationVerificationFailed") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationAccepted, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationAccepted, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationVerifiedAccepted") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationVerified, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationVerified, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationVerified") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationRejected, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationRejected, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationRejected") *> ZIO.unit } } yield () diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala index 581593677b..1e05838d68 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala @@ -11,7 +11,7 @@ import io.iohk.atala.mercury.protocol.presentproof.ProofType 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.{CredentialFormat, DidCommID, PresentationRecord} -import io.iohk.atala.pollux.core.service.PresentationService +import io.iohk.atala.pollux.core.service.{PresentationService} import io.iohk.atala.presentproof.controller.PresentProofController.toDidCommID import io.iohk.atala.presentproof.controller.http.* import io.iohk.atala.shared.models.WalletAccessContext @@ -111,10 +111,16 @@ class PresentProofControllerImpl( didCommId <- ZIO.succeed(DidCommID(id.toString)) record <- requestPresentationAction.action match { case "request-accept" => - presentationService.acceptRequestPresentation( - recordId = didCommId, - credentialsToUse = requestPresentationAction.proofId.getOrElse(Seq.empty) - ) + (requestPresentationAction.proofId, requestPresentationAction.anoncredPresentationRequest) match + case (Some(proofs), None) => + presentationService.acceptRequestPresentation(recordId = didCommId, credentialsToUse = proofs) + case (None, Some(proofs)) => + presentationService.acceptAnoncredRequestPresentation( + recordId = didCommId, + credentialsToUse = proofs + ) + case _ => presentationService.acceptRequestPresentation(recordId = didCommId, credentialsToUse = Seq()) + case "request-reject" => presentationService.rejectRequestPresentation(didCommId) case "presentation-accept" => presentationService.acceptPresentation(didCommId) case "presentation-reject" => presentationService.rejectPresentation(didCommId) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala index c88a97747e..08488ddca4 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala @@ -1,6 +1,7 @@ package io.iohk.atala.presentproof.controller.http import io.iohk.atala.api.http.Annotation +import io.iohk.atala.pollux.core.service.serdes.* import io.iohk.atala.presentproof.controller.http.RequestPresentationAction.annotations import sttp.tapir.Schema.annotations.{description, encodedExample, validate} import sttp.tapir.{Schema, Validator} @@ -13,7 +14,22 @@ final case class RequestPresentationAction( action: String, @description(annotations.proofId.description) @encodedExample(annotations.proofId.example) - proofId: Option[Seq[String]] = None + proofId: Option[Seq[String]] = None, + @description(annotations.anoncredProof.description) + @encodedExample(annotations.anoncredProof.example) + anoncredPresentationRequest: Option[AnoncredCredentialProofsV1], +) + +final case class AnoncredProof( + @description(annotations.credential.description) + @encodedExample(annotations.credential.example) + credential: String, + @description(annotations.requestedAttribute.description) + @encodedExample(annotations.requestedAttribute.example) + requestedAttribute: Seq[String], + @description(annotations.requestedPredicate.description) + @encodedExample(annotations.requestedPredicate.example) + requestedPredicate: Seq[String] ) object RequestPresentationAction { @@ -37,13 +53,53 @@ object RequestPresentationAction { "The unique identifier of the issue credential record - and hence VC - to use as the prover accepts the presentation request. Only applicable on the prover side when the action is `request-accept`.", example = None ) + + object anoncredProof + extends Annotation[Option[Seq[AnoncredProof]]]( + description = "A list of proofs from the Anoncred library, each corresponding to a credential.", + example = None + ) + + object credential + extends Annotation[String]( + description = + "The unique identifier of the issue credential record - and hence VC - to use as the prover accepts the presentation request. Only applicable on the prover side when the action is `request-accept`.", + example = "id" + ) + + object requestedAttribute + extends Annotation[Seq[String]]( + description = "The unique identifier of attribute that the credential is expected to provide.", + example = Seq("Attribute1", "Attribute2") + ) + + object requestedPredicate + extends Annotation[Seq[String]]( + description = "The unique identifier of Predicate that the credential is expected to answer for.", + example = Seq("Predicate1", "Predicate2") + ) } - given encoder: JsonEncoder[RequestPresentationAction] = + given RequestPresentationActionEncoder: JsonEncoder[RequestPresentationAction] = DeriveJsonEncoder.gen[RequestPresentationAction] - given decoder: JsonDecoder[RequestPresentationAction] = + given RequestPresentationActionDecoder: JsonDecoder[RequestPresentationAction] = DeriveJsonDecoder.gen[RequestPresentationAction] - given schema: Schema[RequestPresentationAction] = Schema.derived + given AnoncredProofEncoder: JsonEncoder[AnoncredProof] = + DeriveJsonEncoder.gen[AnoncredProof] + + given AnoncredProofDecoder: JsonDecoder[AnoncredProof] = + DeriveJsonDecoder.gen[AnoncredProof] + + given RequestPresentationActionSchema: Schema[RequestPresentationAction] = Schema.derived + + given AnoncredProofSchema: Schema[AnoncredProof] = Schema.derived + + import AnoncredCredentialProofsV1.given + + given Schema[AnoncredCredentialProofsV1] = Schema.derived + + given Schema[AnoncredCredentialProofV1] = Schema.derived + } From 0f82a2e1f5f3934074015ab12d39b31c40196d39 Mon Sep 17 00:00:00 2001 From: Bassam Date: Wed, 3 Jan 2024 14:09:14 -0500 Subject: [PATCH 08/12] feat: validate presentation (#815) Signed-off-by: Bassam Riman --- .../atala/pollux/anoncreds/AnoncredLib.scala | 58 ++--- .../iohk/atala/pollux/anoncreds/Models.scala | 208 +++++++++--------- .../atala/pollux/anoncreds/PoCNewLib.scala | 14 +- .../core/model/IssueCredentialRecord.scala | 4 +- .../core/model/error/PresentationError.scala | 3 +- .../model/schema/CredentialDefinition.scala | 14 +- .../model/schema/validator/SchemaSerDes.scala | 6 +- .../repository/CredentialRepository.scala | 4 +- .../CredentialRepositoryInMemory.scala | 4 +- .../CredentialDefinitionServiceImpl.scala | 4 +- .../core/service/CredentialServiceImpl.scala | 107 +++++---- .../core/service/LinkSecretService.scala | 4 +- .../core/service/LinkSecretServiceImpl.scala | 20 +- .../service/MockPresentationService.scala | 17 ++ .../core/service/PresentationService.scala | 14 ++ .../service/PresentationServiceImpl.scala | 140 +++++++++++- .../service/PresentationServiceNotifier.scala | 18 ++ .../AnoncredPresentationRequestV1.scala | 69 ++---- .../anoncred-presentation-schema-example.json | 2 +- .../service/CredentialServiceImplSpec.scala | 4 +- .../service/LinkSecretServiceImplSpec.scala | 4 +- .../service/PresentationServiceSpec.scala | 148 ++++++++++++- .../PresentationServiceSpecHelper.scala | 16 +- .../AnoncredPresentationRequestSpec.scala | 58 +++-- .../serdes/AnoncredPresentationSpec.scala | 2 +- ...CredentialDefinitionSchemaSerDesSpec.scala | 4 +- .../repository/JdbcCredentialRepository.scala | 10 +- .../vc/jwt/VerifiableCredentialPayload.scala | 2 - .../server/jobs/PresentBackgroundJobs.scala | 152 ++++++++++--- .../controller/PresentProofController.scala | 4 + .../http/RequestPresentationInput.scala | 4 - .../controller/IssueControllerTestTools.scala | 4 +- .../CredentialDefinitionBasicSpec.scala | 16 +- 33 files changed, 769 insertions(+), 369 deletions(-) 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 775c7987b1..5d6b3c7cf4 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 @@ -18,16 +18,16 @@ object AnoncredLib { version: String, // SCHEMA_Version attr_names: AttributeNames, issuer_id: IssuerId, // ISSUER_DID - ): SchemaDef = uniffi.anoncreds_wrapper.Schema.apply(name, version, attr_names.toSeq.asJava, issuer_id) + ): AnoncredSchemaDef = uniffi.anoncreds_wrapper.Schema.apply(name, version, attr_names.toSeq.asJava, issuer_id) // issuer def createCredDefinition( issuer_id: String, - schema: SchemaDef, + schema: AnoncredSchemaDef, tag: String, supportRevocation: Boolean, signature_type: uniffi.anoncreds_wrapper.SignatureType.CL.type = uniffi.anoncreds_wrapper.SignatureType.CL - ) = { + ): AnoncredCreateCredentialDefinition = { val credentialDefinition: uniffi.anoncreds_wrapper.IssuerCreateCredentialDefinitionReturn = uniffi.anoncreds_wrapper .Issuer() @@ -40,7 +40,7 @@ object AnoncredLib { uniffi.anoncreds_wrapper.CredentialDefinitionConfig(supportRevocation) ) - CreateCredentialDefinition( + AnoncredCreateCredentialDefinition( credentialDefinition.getCredentialDefinition(), credentialDefinition.getCredentialDefinitionPrivate(), credentialDefinition.getCredentialKeyCorrectnessProof() @@ -49,9 +49,9 @@ object AnoncredLib { // issuer def createOffer( - credentialDefinition: CreateCredentialDefinition, + credentialDefinition: AnoncredCreateCredentialDefinition, credentialDefinitionId: String - ): CredentialOffer = + ): AnoncredCredentialOffer = uniffi.anoncreds_wrapper .Issuer() .createCredentialOffer( @@ -62,15 +62,15 @@ object AnoncredLib { // holder def createCredentialRequest( - linkSecret: LinkSecretWithId, - credentialDefinition: CredentialDefinition, - credentialOffer: CredentialOffer, + linkSecret: AnoncredLinkSecretWithId, + credentialDefinition: AnoncredCredentialDefinition, + credentialOffer: AnoncredCredentialOffer, entropy: String = { val tmp = scala.util.Random() tmp.setSeed(java.security.SecureRandom.getInstanceStrong().nextLong()) tmp.nextString(80) } - ): CreateCrendentialRequest = { + ): AnoncredCreateCrendentialRequest = { val credentialRequest = uniffi.anoncreds_wrapper .Prover() @@ -83,16 +83,16 @@ object AnoncredLib { credentialOffer, // CredentialOffer credential_offer ) - CreateCrendentialRequest(credentialRequest.getRequest(), credentialRequest.getMetadata()) + AnoncredCreateCrendentialRequest(credentialRequest.getRequest(), credentialRequest.getMetadata()) } // holder def processCredential( - credential: Credential, - metadata: CredentialRequestMetadata, - linkSecret: LinkSecretWithId, - credentialDefinition: CredentialDefinition, - ): Credential = { + credential: AnoncredCredential, + metadata: AnoncredCredentialRequestMetadata, + linkSecret: AnoncredLinkSecretWithId, + credentialDefinition: AnoncredCredentialDefinition, + ): AnoncredCredential = { uniffi.anoncreds_wrapper .Prover() .processCredential( @@ -106,16 +106,16 @@ object AnoncredLib { // issuer def createCredential( - credentialDefinition: CredentialDefinition, - credentialDefinitionPrivate: CredentialDefinitionPrivate, - credentialOffer: CredentialOffer, - credentialRequest: CredentialRequest, + credentialDefinition: AnoncredCredentialDefinition, + credentialDefinitionPrivate: AnoncredCredentialDefinitionPrivate, + credentialOffer: AnoncredCredentialOffer, + credentialRequest: AnoncredCredentialRequest, attributeValues: Seq[(String, String)] // java.util.List[AttributeValues] : java.util.List[AttributeValues] // revocationRegistryId : String // revocationStatusList : RevocationStatusList // credentialRevocationConfig : CredentialRevocationConfig - ): Credential = { + ): AnoncredCredential = { uniffi.anoncreds_wrapper .Issuer() .createCredential( @@ -140,12 +140,12 @@ object AnoncredLib { // [info] Caused by: Predicate is not satisfied def createPresentation( - presentationRequest: PresentationRequest, - credentialRequests: Seq[CredentialRequests], + presentationRequest: AnoncredPresentationRequest, + credentialRequests: Seq[AnoncredCredentialRequests], selfAttested: Map[String, String], - linkSecret: LinkSecret, - schemas: Map[SchemaId, SchemaDef], - credentialDefinitions: Map[CredentialDefinitionId, CredentialDefinition], + linkSecret: AnoncredLinkSecret, + schemas: Map[SchemaId, AnoncredSchemaDef], + credentialDefinitions: Map[CredentialDefinitionId, AnoncredCredentialDefinition], ): Either[uniffi.anoncreds_wrapper.AnoncredsException.CreatePresentationException, AnoncredPresentation] = { try { Right( @@ -182,9 +182,9 @@ object AnoncredLib { // FIXME its always return false .... def verifyPresentation( presentation: AnoncredPresentation, - presentationRequest: PresentationRequest, - schemas: Map[SchemaId, SchemaDef], - credentialDefinitions: Map[CredentialDefinitionId, CredentialDefinition], + presentationRequest: AnoncredPresentationRequest, + schemas: Map[SchemaId, AnoncredSchemaDef], + credentialDefinitions: Map[CredentialDefinitionId, AnoncredCredentialDefinition], ): Boolean = { uniffi.anoncreds_wrapper .Verifier() 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 ee3e24ced9..38f9d4014e 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 @@ -21,41 +21,42 @@ import scala.jdk.CollectionConverters.* type AttributeNames = Set[String] type IssuerId = String -case class LinkSecretWithId(id: String, secret: LinkSecret) { def data = secret.data } -object LinkSecretWithId { - def apply(id: String): LinkSecretWithId = LinkSecretWithId(id, LinkSecret()) +case class AnoncredLinkSecretWithId(id: String, secret: AnoncredLinkSecret) { def data = secret.data } +object AnoncredLinkSecretWithId { + def apply(id: String): AnoncredLinkSecretWithId = AnoncredLinkSecretWithId(id, AnoncredLinkSecret()) } -case class LinkSecret(data: String) -object LinkSecret { +case class AnoncredLinkSecret(data: String) +object AnoncredLinkSecret { - def apply(): LinkSecret = LinkSecret.given_Conversion_UniffiLinkSecret_LinkSecret(UniffiLinkSecret()) + def apply(): AnoncredLinkSecret = + AnoncredLinkSecret.given_Conversion_UniffiLinkSecret_AnoncredLinkSecret(UniffiLinkSecret()) - given Conversion[LinkSecret, UniffiLinkSecret] with { - def apply(linkSecret: LinkSecret): UniffiLinkSecret = + given Conversion[AnoncredLinkSecret, UniffiLinkSecret] with { + def apply(linkSecret: AnoncredLinkSecret): UniffiLinkSecret = UniffiLinkSecret.Companion.newFromValue(linkSecret.data) } - given Conversion[UniffiLinkSecret, LinkSecret] with { - def apply(uniffiLinkSecret: UniffiLinkSecret): LinkSecret = - LinkSecret.apply(uniffiLinkSecret.getValue()) + given Conversion[UniffiLinkSecret, AnoncredLinkSecret] with { + def apply(uniffiLinkSecret: UniffiLinkSecret): AnoncredLinkSecret = + AnoncredLinkSecret.apply(uniffiLinkSecret.getValue()) } } //FIXME use same names as in https://hyperledger.github.io/anoncreds-spec/#term:schemas -case class SchemaDef( +case class AnoncredSchemaDef( name: String, // SCHEMA_ID version: String, // SCHEMA_Version attributes: AttributeNames, issuer_id: IssuerId, // ISSUER_DID ) -object SchemaDef { +object AnoncredSchemaDef { - given Conversion[SchemaDef, UniffiSchema] with { - def apply(schemaDef: SchemaDef): UniffiSchema = + given Conversion[AnoncredSchemaDef, UniffiSchema] with { + def apply(schemaDef: AnoncredSchemaDef): UniffiSchema = UniffiSchema.apply( schemaDef.name, schemaDef.version, @@ -65,9 +66,9 @@ object SchemaDef { } - given Conversion[UniffiSchema, SchemaDef] with { - def apply(schema: UniffiSchema): SchemaDef = - SchemaDef.apply( + given Conversion[UniffiSchema, AnoncredSchemaDef] with { + def apply(schema: UniffiSchema): AnoncredSchemaDef = + AnoncredSchemaDef.apply( name = schema.getName(), version = schema.getVersion(), attributes = schema.getAttrNames().asScala.toSet, @@ -106,20 +107,20 @@ object SchemaDef { // value: String, // issuerId: String, // ) -case class CredentialDefinition(data: String) { // TODO - def schemaId = CredentialDefinition - .given_Conversion_CredentialDefinition_UniffiCredentialDefinition(this) +case class AnoncredCredentialDefinition(data: String) { // TODO + def schemaId = AnoncredCredentialDefinition + .given_Conversion_AnoncredCredentialDefinition_UniffiCredentialDefinition(this) .getSchemaId() } -object CredentialDefinition { - given Conversion[CredentialDefinition, UniffiCredentialDefinition] with { - def apply(credentialDefinition: CredentialDefinition): UniffiCredentialDefinition = +object AnoncredCredentialDefinition { + given Conversion[AnoncredCredentialDefinition, UniffiCredentialDefinition] with { + def apply(credentialDefinition: AnoncredCredentialDefinition): UniffiCredentialDefinition = UniffiCredentialDefinition(credentialDefinition.data) } - given Conversion[UniffiCredentialDefinition, CredentialDefinition] with { - def apply(credentialDefinition: UniffiCredentialDefinition): CredentialDefinition = - CredentialDefinition(credentialDefinition.getJson()) + given Conversion[UniffiCredentialDefinition, AnoncredCredentialDefinition] with { + def apply(credentialDefinition: UniffiCredentialDefinition): AnoncredCredentialDefinition = + AnoncredCredentialDefinition(credentialDefinition.getJson()) } } @@ -134,90 +135,94 @@ object CredentialDefinition { // "r_key": null // } // } -case class CredentialDefinitionPrivate(data: String) -object CredentialDefinitionPrivate { - given Conversion[CredentialDefinitionPrivate, UniffiCredentialDefinitionPrivate] with { - def apply(credentialDefinitionPrivate: CredentialDefinitionPrivate): UniffiCredentialDefinitionPrivate = +case class AnoncredCredentialDefinitionPrivate(data: String) +object AnoncredCredentialDefinitionPrivate { + given Conversion[AnoncredCredentialDefinitionPrivate, UniffiCredentialDefinitionPrivate] with { + def apply(credentialDefinitionPrivate: AnoncredCredentialDefinitionPrivate): UniffiCredentialDefinitionPrivate = UniffiCredentialDefinitionPrivate(credentialDefinitionPrivate.data) } - given Conversion[UniffiCredentialDefinitionPrivate, CredentialDefinitionPrivate] with { - def apply(credentialDefinitionPrivate: UniffiCredentialDefinitionPrivate): CredentialDefinitionPrivate = - CredentialDefinitionPrivate(credentialDefinitionPrivate.getJson()) + given Conversion[UniffiCredentialDefinitionPrivate, AnoncredCredentialDefinitionPrivate] with { + def apply(credentialDefinitionPrivate: UniffiCredentialDefinitionPrivate): AnoncredCredentialDefinitionPrivate = + AnoncredCredentialDefinitionPrivate(credentialDefinitionPrivate.getJson()) } } // **************************************************************************** -case class CredentialKeyCorrectnessProof(data: String) -object CredentialKeyCorrectnessProof { - given Conversion[CredentialKeyCorrectnessProof, UniffiCredentialKeyCorrectnessProof] with { - def apply(credentialKeyCorrectnessProof: CredentialKeyCorrectnessProof): UniffiCredentialKeyCorrectnessProof = +case class AnoncredCredentialKeyCorrectnessProof(data: String) +object AnoncredCredentialKeyCorrectnessProof { + given Conversion[AnoncredCredentialKeyCorrectnessProof, UniffiCredentialKeyCorrectnessProof] with { + def apply( + credentialKeyCorrectnessProof: AnoncredCredentialKeyCorrectnessProof + ): UniffiCredentialKeyCorrectnessProof = UniffiCredentialKeyCorrectnessProof(credentialKeyCorrectnessProof.data) } - given Conversion[UniffiCredentialKeyCorrectnessProof, CredentialKeyCorrectnessProof] with { - def apply(credentialKeyCorrectnessProof: UniffiCredentialKeyCorrectnessProof): CredentialKeyCorrectnessProof = - CredentialKeyCorrectnessProof(credentialKeyCorrectnessProof.getJson()) + given Conversion[UniffiCredentialKeyCorrectnessProof, AnoncredCredentialKeyCorrectnessProof] with { + def apply( + credentialKeyCorrectnessProof: UniffiCredentialKeyCorrectnessProof + ): AnoncredCredentialKeyCorrectnessProof = + AnoncredCredentialKeyCorrectnessProof(credentialKeyCorrectnessProof.getJson()) } } -case class CreateCredentialDefinition( - cd: CredentialDefinition, - cdPrivate: CredentialDefinitionPrivate, - proofKey: CredentialKeyCorrectnessProof, +case class AnoncredCreateCredentialDefinition( + cd: AnoncredCredentialDefinition, + cdPrivate: AnoncredCredentialDefinitionPrivate, + proofKey: AnoncredCredentialKeyCorrectnessProof, ) // **************************************************************************** -case class CredentialOffer(data: String) { - lazy val schemaId = CredentialOffer - .given_Conversion_CredentialOffer_UniffiCredentialOffer(this) +case class AnoncredCredentialOffer(data: String) { + lazy val schemaId = AnoncredCredentialOffer + .given_Conversion_AnoncredCredentialOffer_UniffiCredentialOffer(this) .getSchemaId() - lazy val credDefId = CredentialOffer - .given_Conversion_CredentialOffer_UniffiCredentialOffer(this) + lazy val credDefId = AnoncredCredentialOffer + .given_Conversion_AnoncredCredentialOffer_UniffiCredentialOffer(this) .getCredDefId() } -object CredentialOffer { - given Conversion[CredentialOffer, UniffiCredentialOffer] with { - def apply(credentialOffer: CredentialOffer): UniffiCredentialOffer = +object AnoncredCredentialOffer { + given Conversion[AnoncredCredentialOffer, UniffiCredentialOffer] with { + def apply(credentialOffer: AnoncredCredentialOffer): UniffiCredentialOffer = UniffiCredentialOffer(credentialOffer.data) } - given Conversion[UniffiCredentialOffer, CredentialOffer] with { - def apply(credentialOffer: UniffiCredentialOffer): CredentialOffer = - CredentialOffer(credentialOffer.getJson()) + given Conversion[UniffiCredentialOffer, AnoncredCredentialOffer] with { + def apply(credentialOffer: UniffiCredentialOffer): AnoncredCredentialOffer = + AnoncredCredentialOffer(credentialOffer.getJson()) } } // **************************************************************************** -case class CreateCrendentialRequest( - request: CredentialRequest, - metadata: CredentialRequestMetadata +case class AnoncredCreateCrendentialRequest( + request: AnoncredCredentialRequest, + metadata: AnoncredCredentialRequestMetadata ) -case class CredentialRequest(data: String) -object CredentialRequest { +case class AnoncredCredentialRequest(data: String) +object AnoncredCredentialRequest { - given Conversion[CredentialRequest, UniffiCredentialRequest] with { - def apply(credentialRequest: CredentialRequest): UniffiCredentialRequest = + given Conversion[AnoncredCredentialRequest, UniffiCredentialRequest] with { + def apply(credentialRequest: AnoncredCredentialRequest): UniffiCredentialRequest = UniffiCredentialRequest(credentialRequest.data) } - given Conversion[UniffiCredentialRequest, CredentialRequest] with { - def apply(credentialRequest: UniffiCredentialRequest): CredentialRequest = - CredentialRequest(credentialRequest.getJson()) + given Conversion[UniffiCredentialRequest, AnoncredCredentialRequest] with { + def apply(credentialRequest: UniffiCredentialRequest): AnoncredCredentialRequest = + AnoncredCredentialRequest(credentialRequest.getJson()) } } -case class CredentialRequestMetadata( +case class AnoncredCredentialRequestMetadata( linkSecretBlinding: String, nonce: String, linkSecretName: String, ) -object CredentialRequestMetadata { - given Conversion[CredentialRequestMetadata, UniffiCredentialRequestMetadata] with { - def apply(credentialRequestMetadata: CredentialRequestMetadata): UniffiCredentialRequestMetadata = +object AnoncredCredentialRequestMetadata { + given Conversion[AnoncredCredentialRequestMetadata, UniffiCredentialRequestMetadata] with { + def apply(credentialRequestMetadata: AnoncredCredentialRequestMetadata): UniffiCredentialRequestMetadata = UniffiCredentialRequestMetadata( /*link_secret_blinding_data*/ credentialRequestMetadata.linkSecretBlinding, /*nonce*/ Nonce.Companion.newFromValue(credentialRequestMetadata.nonce), @@ -225,62 +230,63 @@ object CredentialRequestMetadata { ) } - given Conversion[UniffiCredentialRequestMetadata, CredentialRequestMetadata] with { - def apply(credentialRequestMetadata: UniffiCredentialRequestMetadata): CredentialRequestMetadata = - CredentialRequestMetadata( + given Conversion[UniffiCredentialRequestMetadata, AnoncredCredentialRequestMetadata] with { + def apply(credentialRequestMetadata: UniffiCredentialRequestMetadata): AnoncredCredentialRequestMetadata = + AnoncredCredentialRequestMetadata( linkSecretBlinding = credentialRequestMetadata.getLinkSecretBlindingData(), nonce = credentialRequestMetadata.getNonce().getValue(), linkSecretName = credentialRequestMetadata.getLinkSecretName(), ) } - given JsonDecoder[CredentialRequestMetadata] = DeriveJsonDecoder.gen[CredentialRequestMetadata] - given JsonEncoder[CredentialRequestMetadata] = DeriveJsonEncoder.gen[CredentialRequestMetadata] + given JsonDecoder[AnoncredCredentialRequestMetadata] = DeriveJsonDecoder.gen[AnoncredCredentialRequestMetadata] + given JsonEncoder[AnoncredCredentialRequestMetadata] = DeriveJsonEncoder.gen[AnoncredCredentialRequestMetadata] } // **************************************************************************** //Credential -case class Credential(data: String) { - lazy val credDefId: String = Credential - .given_Conversion_Credential_UniffiCredential(this) +case class AnoncredCredential(data: String) { + lazy val credDefId: String = AnoncredCredential + .given_Conversion_AnoncredCredential_UniffiCredential(this) .getCredDefId } -object Credential { - given Conversion[Credential, UniffiCredential] with { - def apply(credential: Credential): UniffiCredential = +object AnoncredCredential { + given Conversion[AnoncredCredential, UniffiCredential] with { + def apply(credential: AnoncredCredential): UniffiCredential = UniffiCredential(credential.data) } - given Conversion[UniffiCredential, Credential] with { - def apply(credential: UniffiCredential): Credential = - Credential(credential.getJson()) + given Conversion[UniffiCredential, AnoncredCredential] with { + def apply(credential: UniffiCredential): AnoncredCredential = + AnoncredCredential(credential.getJson()) } } // **************************************************************************** -case class CredentialRequests( - credential: Credential, +case class AnoncredCredentialRequests( + credential: AnoncredCredential, requestedAttribute: Seq[String], requestedPredicate: Seq[String], ) -object CredentialRequests { - given Conversion[CredentialRequests, UniffiCredentialRequests] with { +object AnoncredCredentialRequests { + given Conversion[AnoncredCredentialRequests, UniffiCredentialRequests] with { import uniffi.anoncreds_wrapper.RequestedAttribute import uniffi.anoncreds_wrapper.RequestedPredicate - def apply(credentialRequests: CredentialRequests): UniffiCredentialRequests = { - val credential = Credential.given_Conversion_Credential_UniffiCredential(credentialRequests.credential) + def apply(credentialRequests: AnoncredCredentialRequests): UniffiCredentialRequests = { + val credential = + AnoncredCredential.given_Conversion_AnoncredCredential_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()), + given Conversion[UniffiCredentialRequests, AnoncredCredentialRequests] with { + def apply(credentialRequests: UniffiCredentialRequests): AnoncredCredentialRequests = { + AnoncredCredentialRequests( + AnoncredCredential.given_Conversion_UniffiCredential_AnoncredCredential(credentialRequests.getCredential()), credentialRequests .getRequestedAttribute() .asScala @@ -301,16 +307,16 @@ object CredentialRequests { // **************************************************************************** -case class PresentationRequest(data: String) -object PresentationRequest { - given Conversion[PresentationRequest, UniffiPresentationRequest] with { - def apply(presentationRequest: PresentationRequest): UniffiPresentationRequest = +case class AnoncredPresentationRequest(data: String) +object AnoncredPresentationRequest { + given Conversion[AnoncredPresentationRequest, UniffiPresentationRequest] with { + def apply(presentationRequest: AnoncredPresentationRequest): UniffiPresentationRequest = UniffiPresentationRequest(presentationRequest.data) } - given Conversion[UniffiPresentationRequest, PresentationRequest] with { - def apply(presentationRequest: UniffiPresentationRequest): PresentationRequest = - PresentationRequest(presentationRequest.getJson()) + given Conversion[UniffiPresentationRequest, AnoncredPresentationRequest] with { + def apply(presentationRequest: UniffiPresentationRequest): AnoncredPresentationRequest = + AnoncredPresentationRequest(presentationRequest.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 6e6a3c1d5a..fd75b61a4b 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 @@ -15,13 +15,13 @@ class PoCNewLib extends AnyFlatSpec { "LinkSecret" should "be able to parse back to the anoncreds lib" in { import scala.language.implicitConversions - val ls1 = LinkSecret("65965334953670062552662719679603258895632947953618378932199361160021795698890") + val ls1 = AnoncredLinkSecret("65965334953670062552662719679603258895632947953618378932199361160021795698890") val ls1p = ls1: uniffi.anoncreds_wrapper.LinkSecret assert(ls1p.getValue() == "65965334953670062552662719679603258895632947953618378932199361160021795698890") - val ls0 = LinkSecret() + val ls0 = AnoncredLinkSecret() val ls0p = ls0: uniffi.anoncreds_wrapper.LinkSecret - val ls0_ = ls0p: LinkSecret + val ls0_ = ls0p: AnoncredLinkSecret assert(ls0.data == ls0_.data) } @@ -58,8 +58,8 @@ class PoCNewLib extends AnyFlatSpec { // ############## println("*** holder " + ("*" * 100)) - val ls1 = LinkSecret("65965334953670062552662719679603258895632947953618378932199361160021795698890") - val linkSecret = LinkSecretWithId("ID_of_some_secret_1", ls1) + val ls1 = AnoncredLinkSecret("65965334953670062552662719679603258895632947953618378932199361160021795698890") + val linkSecret = AnoncredLinkSecretWithId("ID_of_some_secret_1", ls1) val credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) println("*" * 100) @@ -89,7 +89,7 @@ class PoCNewLib extends AnyFlatSpec { // ############## // TODO READ about PresentationRequest https://hyperledger.github.io/anoncreds-spec/#create-presentation-request - val presentationRequest = PresentationRequest( + val presentationRequest = AnoncredPresentationRequest( s"""{ "nonce": "1103253414365527824079144", "name":"proof_req_1", @@ -109,7 +109,7 @@ class PoCNewLib extends AnyFlatSpec { val presentation = AnoncredLib.createPresentation( presentationRequest, // : PresentationRequest, Seq( - CredentialRequests(processedCredential, Seq("sex"), Seq("age")) + AnoncredCredentialRequests(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 7ec80dcde7..1551c85f53 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 @@ -9,7 +9,7 @@ import io.iohk.atala.mercury.protocol.issuecredential.{ OfferCredential, RequestCredential } -import io.iohk.atala.pollux.anoncreds.CredentialRequestMetadata +import io.iohk.atala.pollux.anoncreds.AnoncredCredentialRequestMetadata import io.iohk.atala.pollux.core.model.IssueCredentialRecord.* import java.time.Instant @@ -31,7 +31,7 @@ final case class IssueCredentialRecord( protocolState: ProtocolState, offerCredentialData: Option[OfferCredential], requestCredentialData: Option[RequestCredential], - anonCredsRequestMetadata: Option[CredentialRequestMetadata], + anonCredsRequestMetadata: Option[AnoncredCredentialRequestMetadata], issueCredentialData: Option[IssueCredential], issuedCredentialRaw: Option[String], issuingDID: Option[CanonicalPrismDID], 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 7009ad0021..257116b21e 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 @@ -19,7 +19,8 @@ object PresentationError { object MissingCredentialFormat extends PresentationError final case class UnsupportedCredentialFormat(vcFormat: String) extends PresentationError final case class InvalidAnoncredPresentationRequest(error: String) extends PresentationError + final case class InvalidAnoncredPresentation(error: String) extends PresentationError final case class MissingAnoncredPresentationRequest(error: String) extends PresentationError - final case class AnoncredPresentationCreationError(cause: Throwable) extends PresentationError + final case class AnoncredPresentationVerificationError(cause: Throwable) extends PresentationError } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialDefinition.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialDefinition.scala index 554d36aab0..98823ed3f5 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialDefinition.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialDefinition.scala @@ -8,6 +8,7 @@ import zio.json.* import java.time.OffsetDateTime import java.time.ZoneOffset import java.util.UUID +import scala.util.Try type Definition = zio.json.ast.Json type CorrectnessProof = zio.json.ast.Json @@ -58,7 +59,7 @@ case class CredentialDefinition( signatureType: String, supportRevocation: Boolean ) { - def longId = CredentialDefinition.makeLongId(author, id, version) + def longId = CredentialDefinition.makeLongId(author, guid, version) } object CredentialDefinition { @@ -69,6 +70,17 @@ object CredentialDefinition { def makeGUID(author: String, id: UUID, version: String) = UUID.nameUUIDFromBytes(makeLongId(author, id, version).getBytes) + def extractGUID(longId: String): Option[UUID] = { + longId.split("/") match { + case Array(_, idWithVersion) => + idWithVersion.split("\\?") match { + case Array(id, _) => Try(UUID.fromString(id)).toOption + case _ => None + } + case _ => None + } + } + def make( in: Input, definitionSchemaId: String, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala index e3cfb4a239..365f8a0251 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/validator/SchemaSerDes.scala @@ -48,12 +48,12 @@ class SchemaSerDes[S](jsonSchemaSchemaStr: String) { } yield json } - def validate(jsonString: String): IO[JsonSchemaError, Boolean] = { + def validate(jsonString: String): IO[JsonSchemaError, Unit] = { for { jsonSchemaSchema <- JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr) schemaValidator = JsonSchemaValidatorImpl(jsonSchemaSchema) - _ <- schemaValidator.validate(jsonString) - } yield true + result <- schemaValidator.validate(jsonString) + } yield result } } 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 186bc0276c..2f2b9615e5 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 @@ -1,7 +1,7 @@ package io.iohk.atala.pollux.core.repository import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, RequestCredential} -import io.iohk.atala.pollux.anoncreds.CredentialRequestMetadata +import io.iohk.atala.pollux.anoncreds.AnoncredCredentialRequestMetadata import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.ProtocolState import io.iohk.atala.shared.models.WalletAccessContext @@ -53,7 +53,7 @@ trait CredentialRepository { def updateWithAnonCredsRequestCredential( recordId: DidCommID, request: RequestCredential, - metadata: CredentialRequestMetadata, + metadata: AnoncredCredentialRequestMetadata, protocolState: ProtocolState ): RIO[WalletAccessContext, Int] 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 5b2ba4dbea..c329a2548a 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 @@ -1,7 +1,7 @@ package io.iohk.atala.pollux.core.repository import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, RequestCredential} -import io.iohk.atala.pollux.anoncreds.CredentialRequestMetadata +import io.iohk.atala.pollux.anoncreds.AnoncredCredentialRequestMetadata import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.ProtocolState import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.* @@ -315,7 +315,7 @@ class CredentialRepositoryInMemory( override def updateWithAnonCredsRequestCredential( recordId: DidCommID, request: RequestCredential, - metadata: CredentialRequestMetadata, + metadata: AnoncredCredentialRequestMetadata, protocolState: ProtocolState ): RIO[WalletAccessContext, RuntimeFlags] = { for { diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala index a7ec38bb81..1862f2690c 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala @@ -2,7 +2,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.agent.walletapi.storage import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage -import io.iohk.atala.pollux.anoncreds.{AnoncredLib, SchemaDef} +import io.iohk.atala.pollux.anoncreds.{AnoncredLib, AnoncredSchemaDef} import io.iohk.atala.pollux.core.model.error.CredentialSchemaError import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.URISyntaxError import io.iohk.atala.pollux.core.model.schema.CredentialDefinition @@ -37,7 +37,7 @@ class CredentialDefinitionServiceImpl( content <- uriDereferencer.dereference(uri) anoncredSchema <- AnoncredSchemaSerDesV1.schemaSerDes.deserialize(content) anoncredLibSchema = - SchemaDef( + AnoncredSchemaDef( in.schemaId, anoncredSchema.version, anoncredSchema.attrNames, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala index 2550e62047..ffddf3856d 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala @@ -10,7 +10,12 @@ import io.iohk.atala.castor.core.service.DIDService import io.iohk.atala.mercury.model.* import io.iohk.atala.mercury.protocol.issuecredential.* import io.iohk.atala.pollux.* -import io.iohk.atala.pollux.anoncreds.{AnoncredLib, CreateCredentialDefinition, CredentialOffer} +import io.iohk.atala.pollux.anoncreds.{ + AnoncredCreateCredentialDefinition, + AnoncredCredential, + AnoncredCredentialOffer, + AnoncredLib +} import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.CredentialFormat.AnonCreds import io.iohk.atala.pollux.core.model.IssueCredentialRecord.ProtocolState.OfferReceived @@ -318,7 +323,7 @@ private class CredentialServiceImpl( case Base64(value) => for { _ <- ZIO - .attempt(CredentialOffer(value)) + .attempt(AnoncredCredentialOffer(value)) .mapError(e => CredentialServiceError.UnexpectedError( s"Unexpected error parsing credential offer attachment: ${e.toString}" @@ -536,12 +541,12 @@ private class CredentialServiceImpl( } ) .mapError(_ => InvalidFlowStateError(s"No AnonCreds offer attachment found")) - credentialOffer = anoncreds.CredentialOffer(attachmentData) + credentialOffer = anoncreds.AnoncredCredentialOffer(attachmentData) _ <- ZIO.logInfo(s"Cred def ID => ${credentialOffer.getCredDefId}") credDefContent <- uriDereferencer .dereference(new URI(credentialOffer.getCredDefId)) .mapError(err => UnexpectedError(err.toString)) - credentialDefinition = anoncreds.CredentialDefinition(credDefContent) + credentialDefinition = anoncreds.AnoncredCredentialDefinition(credDefContent) linkSecret <- linkSecretService .fetchOrCreate() .mapError(e => CredentialServiceError.LinkSecretError.apply(e.cause)) @@ -600,34 +605,48 @@ private class CredentialServiceImpl( override def receiveCredentialIssue( issueCredential: IssueCredential ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = { - // TODO We can get rid of this 'raw' representation stored in DB, because it is not used. - val rawIssuedCredential = issueCredential.attachments.map(_.data.asJson.noSpaces).headOption.getOrElse("???") for { // TODO Move this type of generic/reusable code to a helper trait - attachmentFormatAndData <- ZIO.succeed { - import IssueCredentialIssuedFormat.{Anoncred, JWT} - issueCredential.attachments - .collectFirst { - case AttachmentDescriptor(_, _, Base64(v), Some(JWT.name), _, _, _, _) => (JWT, v) - case AttachmentDescriptor(_, _, Base64(v), Some(Anoncred.name), _, _, _, _) => (Anoncred, v) - } - .map { case (f, v) => (f, java.util.Base64.getUrlDecoder.decode(v)) } - } record <- getRecordFromThreadIdWithState( issueCredential.thid.map(DidCommID(_)), ignoreWithZeroRetries = true, ProtocolState.RequestPending, ProtocolState.RequestSent ) - _ <- attachmentFormatAndData match - case Some(IssueCredentialIssuedFormat.JWT, _) => ZIO.succeed(()) - case Some(IssueCredentialIssuedFormat.Anoncred, ba) => processAnonCredsCredential(record, ba) - case _ => ZIO.fail(UnexpectedError("No AnonCreds or JWT credential attachment found")) + processedAttachments <- { + import IssueCredentialIssuedFormat.Anoncred + ZIO.collectAll( + issueCredential.attachments + .map { + case AttachmentDescriptor( + id, + media_type, + Base64(v), + Some(Anoncred.name), + _, + _, + _, + _ + ) => + processAnonCredsCredential(record, java.util.Base64.getUrlDecoder.decode(v)) + .map(processedCredential => + AttachmentDescriptor.buildBase64Attachment( + id = id, + mediaType = media_type, + format = Some(IssueCredentialIssuedFormat.Anoncred.name), + payload = processedCredential + ) + ) + case attachment => ZIO.succeed(attachment) + } + ) + } + processedIssuedCredential = issueCredential.copy(attachments = processedAttachments) _ <- credentialRepository .updateWithIssuedRawCredential( record.id, - issueCredential, - rawIssuedCredential, + processedIssuedCredential, + processedIssuedCredential.attachments.map(_.data.asJson.noSpaces).headOption.getOrElse("???"), ProtocolState.CredentialReceived ) .flatMap { @@ -642,30 +661,33 @@ private class CredentialServiceImpl( } yield record } - private[this] def processAnonCredsCredential(record: IssueCredentialRecord, credentialBytes: Array[Byte]) = { + private[this] def processAnonCredsCredential( + record: IssueCredentialRecord, + credentialBytes: Array[Byte] + ): ZIO[WalletAccessContext, CredentialServiceError, Array[Byte]] = { for { - credential <- ZIO.succeed(anoncreds.Credential(new String(credentialBytes))) + credential <- ZIO.succeed(anoncreds.AnoncredCredential(new String(credentialBytes))) credDefContent <- uriDereferencer .dereference(new URI(credential.getCredDefId)) .mapError(err => UnexpectedError(err.toString)) - credentialDefinition = anoncreds.CredentialDefinition(credDefContent) + credentialDefinition = anoncreds.AnoncredCredentialDefinition(credDefContent) metadata <- ZIO .fromOption(record.anonCredsRequestMetadata) .mapError(_ => CredentialServiceError.UnexpectedError(s"No request metadata Id found un record: ${record.id}")) linkSecret <- linkSecretService .fetchOrCreate() .mapError(e => CredentialServiceError.LinkSecretError.apply(e.cause)) - _ <- ZIO + credential <- ZIO .attempt( AnoncredLib.processCredential( - anoncreds.Credential(new String(credentialBytes)), + anoncreds.AnoncredCredential(new String(credentialBytes)), metadata, linkSecret, credentialDefinition ) ) .mapError(error => UnexpectedError(s"AnonCreds credential processing error: ${error.getMessage}")) - } yield () + } yield credential.data.getBytes() } override def markOfferSent( @@ -856,16 +878,16 @@ private class CredentialServiceImpl( credentialDefinition <- credentialDefinitionService .getByGUID(credentialDefinitionGUID) .mapError(e => CredentialServiceError.UnexpectedError(e.toString)) - cd = anoncreds.CredentialDefinition(credentialDefinition.definition.toString) - kcp = anoncreds.CredentialKeyCorrectnessProof(credentialDefinition.keyCorrectnessProof.toString) + cd = anoncreds.AnoncredCredentialDefinition(credentialDefinition.definition.toString) + kcp = anoncreds.AnoncredCredentialKeyCorrectnessProof(credentialDefinition.keyCorrectnessProof.toString) maybeCredentialDefinitionSecret <- genericSecretStorage .get[UUID, CredentialDefinitionSecret](credentialDefinition.guid) .orDie credentialDefinitionSecret <- ZIO .fromOption(maybeCredentialDefinitionSecret) .mapError(_ => CredentialServiceError.CredentialDefinitionPrivatePartNotFound(credentialDefinition.guid)) - cdp = anoncreds.CredentialDefinitionPrivate(credentialDefinitionSecret.json.toString) - createCredentialDefinition = CreateCredentialDefinition(cd, cdp, kcp) + cdp = anoncreds.AnoncredCredentialDefinitionPrivate(credentialDefinitionSecret.json.toString) + createCredentialDefinition = AnoncredCreateCredentialDefinition(cd, cdp, kcp) offer = AnoncredLib.createOffer(createCredentialDefinition, credentialDefinitionId) } yield offer @@ -1069,7 +1091,7 @@ private class CredentialServiceImpl( credentialDefinition <- credentialDefinitionService .getByGUID(credentialDefinitionId) .mapError(e => CredentialServiceError.UnexpectedError(e.toString)) - cd = anoncreds.CredentialDefinition(credentialDefinition.definition.toString) + cd = anoncreds.AnoncredCredentialDefinition(credentialDefinition.definition.toString) offerCredential <- ZIO .fromOption(record.offerCredentialData) .mapError(_ => InvalidFlowStateError(s"No offer found for this record: ${record.id}")) @@ -1084,7 +1106,7 @@ private class CredentialServiceImpl( } ) .mapError(_ => InvalidFlowStateError(s"No AnonCreds offer attachment found")) - credentialOffer = anoncreds.CredentialOffer(offerCredentialAttachmentData) + credentialOffer = anoncreds.AnoncredCredentialOffer(offerCredentialAttachmentData) requestCredential <- ZIO .fromOption(record.requestCredentialData) .mapError(_ => InvalidFlowStateError(s"No request found for this record: ${record.id}")) @@ -1099,7 +1121,7 @@ private class CredentialServiceImpl( } ) .mapError(_ => InvalidFlowStateError(s"No AnonCreds request attachment found")) - credentialRequest = anoncreds.CredentialRequest(requestCredentialAttachmentData) + credentialRequest = anoncreds.AnoncredCredentialRequest(requestCredentialAttachmentData) attrValues = offerCredential.body.credential_preview.body.attributes.map { attr => (attr.name, attr.value) } @@ -1109,14 +1131,15 @@ private class CredentialServiceImpl( credentialDefinitionSecret <- ZIO .fromOption(maybeCredentialDefinitionSecret) .mapError(_ => CredentialServiceError.CredentialDefinitionPrivatePartNotFound(credentialDefinition.guid)) - cdp = anoncreds.CredentialDefinitionPrivate(credentialDefinitionSecret.json.toString) - credential = AnoncredLib.createCredential( - cd, - cdp, - credentialOffer, - credentialRequest, - attrValues - ) + cdp = anoncreds.AnoncredCredentialDefinitionPrivate(credentialDefinitionSecret.json.toString) + credential = + AnoncredLib.createCredential( + cd, + cdp, + credentialOffer, + credentialRequest, + attrValues + ) } yield credential } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretService.scala index c87c63e6ed..f0d6aa2448 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretService.scala @@ -1,6 +1,6 @@ package io.iohk.atala.pollux.core.service -import io.iohk.atala.pollux.anoncreds.LinkSecretWithId +import io.iohk.atala.pollux.anoncreds.AnoncredLinkSecretWithId import io.iohk.atala.pollux.core.model.error.LinkSecretError import io.iohk.atala.shared.models.WalletAccessContext import zio.ZIO @@ -8,5 +8,5 @@ import zio.ZIO trait LinkSecretService { type Result[T] = ZIO[WalletAccessContext, LinkSecretError, T] - def fetchOrCreate(): Result[LinkSecretWithId] + def fetchOrCreate(): Result[AnoncredLinkSecretWithId] } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImpl.scala index 4219d2bbd0..88e51199be 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImpl.scala @@ -1,7 +1,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.agent.walletapi.storage.{GenericSecret, GenericSecretStorage} -import io.iohk.atala.pollux.anoncreds.{LinkSecret, LinkSecretWithId} +import io.iohk.atala.pollux.anoncreds.{AnoncredLinkSecret, AnoncredLinkSecretWithId} import io.iohk.atala.pollux.core.model.error.LinkSecretError import io.iohk.atala.shared.models.WalletAccessContext import zio.* @@ -15,18 +15,18 @@ class LinkSecretServiceImpl(genericSecretStorage: GenericSecretStorage) extends type Result[T] = ZIO[WalletAccessContext, LinkSecretError, T] - override def fetchOrCreate(): Result[LinkSecretWithId] = { + override def fetchOrCreate(): Result[AnoncredLinkSecretWithId] = { genericSecretStorage - .get[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId) + .get[String, AnoncredLinkSecret](LinkSecretServiceImpl.defaultLinkSecretId) .flatMap { case Some(secret) => ZIO.succeed(secret) case None => - val linkSecret = LinkSecret() + val linkSecret = AnoncredLinkSecret() genericSecretStorage - .set[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId, linkSecret) + .set[String, AnoncredLinkSecret](LinkSecretServiceImpl.defaultLinkSecretId, linkSecret) .as(linkSecret) } - .map(linkSecret => LinkSecretWithId(LinkSecretServiceImpl.defaultLinkSecretId, linkSecret)) + .map(linkSecret => AnoncredLinkSecretWithId(LinkSecretServiceImpl.defaultLinkSecretId, linkSecret)) .mapError(LinkSecretError.apply) } } @@ -40,13 +40,13 @@ object LinkSecretServiceImpl { ] = ZLayer.fromFunction(LinkSecretServiceImpl(_)) - given GenericSecret[String, LinkSecret] = new { + given GenericSecret[String, AnoncredLinkSecret] = new { override def keyPath(id: String): String = s"link-secret/${id.toString}" - override def encodeValue(secret: LinkSecret): Json = Json.Str(secret.data) + override def encodeValue(secret: AnoncredLinkSecret): Json = Json.Str(secret.data) - override def decodeValue(json: Json): Try[LinkSecret] = json match { - case Json.Str(data) => Try(LinkSecret(data)) + override def decodeValue(json: Json): Try[AnoncredLinkSecret] = json match { + case Json.Str(data) => Try(AnoncredLinkSecret(data)) case _ => scala.util.Failure(new Exception("Invalid JSON format for LinkSecret")) } } 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 736f0ca451..6049790a99 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 @@ -43,6 +43,8 @@ object MockPresentationService extends Mock[PresentationService] { object MarkPresentationVerificationFailed extends Effect[DidCommID, PresentationError, PresentationRecord] + object VerifyAnoncredPresentation extends Effect[DidCommID, PresentationError, PresentationRecord] + object AcceptRequestPresentation extends Effect[(DidCommID, Seq[String]), PresentationError, PresentationRecord] object AcceptAnoncredRequestPresentation @@ -138,6 +140,13 @@ object MockPresentationService extends Mock[PresentationService] { override def markPresentationVerificationFailed(recordId: DidCommID): IO[PresentationError, PresentationRecord] = proxy(MarkPresentationVerificationFailed, recordId) + override def verifyAnoncredPresentation( + presentation: Presentation, + requestPresentation: RequestPresentation, + recordId: DidCommID + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = + proxy(VerifyAnoncredPresentation, recordId) + override def receiveRequestPresentation( connectionId: Option[String], request: RequestPresentation @@ -169,6 +178,14 @@ object MockPresentationService extends Mock[PresentationService] { issuanceDate: Instant ): IO[PresentationError, AnoncredPresentation] = ??? + override def createAnoncredPresentation( + requestPresentation: RequestPresentation, + recordId: DidCommID, + prover: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, Presentation] = ??? + 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 88e5a0759a..9727983870 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 @@ -52,6 +52,14 @@ trait PresentationService { issuanceDate: Instant ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] + def createAnoncredPresentation( + requestPresentation: RequestPresentation, + recordId: DidCommID, + prover: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, Presentation] + def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, limit: Int, @@ -126,6 +134,12 @@ trait PresentationService { recordId: DidCommID ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] + def verifyAnoncredPresentation( + presentation: Presentation, + requestPresentation: RequestPresentation, + recordId: DidCommID + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] + def reportProcessingFailure( recordId: DidCommID, failReason: Option[String] 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 c7b866c45c..6ac79b7b17 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 @@ -13,13 +13,15 @@ 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.CredentialDefinition 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.{ AnoncredCredentialProofV1, AnoncredCredentialProofsV1, - AnoncredPresentationRequestV1 + AnoncredPresentationRequestV1, + AnoncredPresentationV1 } import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext @@ -31,6 +33,7 @@ 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, @@ -153,6 +156,43 @@ private class PresentationServiceImpl( } yield presentationPayload } + def createAnoncredPresentation( + requestPresentation: RequestPresentation, + recordId: DidCommID, + prover: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, Presentation] = { + for { + presentationPayload <- + createAnoncredPresentationPayloadFromRecord( + recordId, + prover, + anoncredCredentialProof, + issuanceDate + ) + 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 + } + override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] = credential.maybeId.map(_.split("/").last).map(UUID.fromString) @@ -479,7 +519,10 @@ private class PresentationServiceImpl( credentialDefinition <- credentialDefinitionService .getByGUID(credentialDefinitionId) .mapError(e => UnexpectedError(e.toString)) - } yield (credentialDefinition.longId, CredentialDefinition(credentialDefinition.definition.toString)) + } yield ( + credentialDefinition.longId, + AnoncredCredentialDefinition(credentialDefinition.definition.toString) + ) }) .map(_.toMap) credentialProofsMap = credentialProofs.map(credentialProof => (credentialProof.credential, credentialProof)).toMap @@ -525,18 +568,20 @@ private class PresentationServiceImpl( .fetchOrCreate() .map(_.secret) .mapError(t => AnoncredPresentationCreationError(t.cause)) + credentialRequest = + verifiableCredentials.map(verifiableCredential => + AnoncredCredentialRequests( + AnoncredCredential(verifiableCredential.credential), + verifiableCredential.requestedAttribute, + verifiableCredential.requestedPredicate + ) + ) presentation <- ZIO .fromEither( AnoncredLib.createPresentation( - PresentationRequest(presentationRequestData), - verifiableCredentials.map(verifiableCredential => - CredentialRequests( - Credential(verifiableCredential.credential), - verifiableCredential.requestedAttribute, - verifiableCredential.requestedPredicate - ) - ), + AnoncredPresentationRequest(presentationRequestData), + credentialRequest, Map.empty, // TO FIX linkSecret, schemaMap, @@ -547,7 +592,7 @@ private class PresentationServiceImpl( } yield presentation } - private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, SchemaDef)] = { + private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = { for { uri <- ZIO.attempt(new URI(schemaId)).mapError(e => UnexpectedError(e.getMessage)) content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) @@ -556,7 +601,7 @@ private class PresentationServiceImpl( .deserialize(vcSchema.schema) .mapError(e => UnexpectedError(e.error)) anoncredLibSchema = - SchemaDef( + AnoncredSchemaDef( schemaId, anoncredSchema.version, anoncredSchema.attrNames, @@ -882,6 +927,77 @@ private class PresentationServiceImpl( PresentationRecord.ProtocolState.PresentationVerificationFailed ) + override def verifyAnoncredPresentation( + presentation: Presentation, + requestPresentation: RequestPresentation, + recordId: DidCommID + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + for { + serializedPresentation <- presentation.attachments.head.data match { + case Base64(data) => ZIO.succeed(AnoncredPresentation(new String(JBase64.getUrlDecoder.decode(data)))) + case _ => ZIO.fail(InvalidAnoncredPresentation("Expecting Base64-encoded data")) + } + deserializedPresentation <- + AnoncredPresentationV1.schemaSerDes + .deserialize(serializedPresentation.data) + .mapError(error => PresentationError.UnexpectedError(error.error)) + schemaIds = deserializedPresentation.identifiers.map(_.schema_id) + schemaMap <- + ZIO + .collectAll(schemaIds.map { schemaId => + resolveSchema(schemaId) + }) + .map(_.toMap) + credentialDefinitionIds = deserializedPresentation.identifiers.map(_.cred_def_id) + credentialDefinitionMap <- + ZIO + .collectAll(credentialDefinitionIds.map { credentialDefinitionId => + for { + guid <- + ZIO + .fromOption(CredentialDefinition.extractGUID(credentialDefinitionId)) + .mapError(_ => + InvalidAnoncredPresentation(s"CredentialDefinitionId format invalid ${credentialDefinitionId}") + ) + credentialDefinition <- + credentialDefinitionService + .getByGUID(guid) + .mapError(e => UnexpectedError(e.toString)) + } yield ( + credentialDefinition.longId, + AnoncredCredentialDefinition(credentialDefinition.definition.toString) + ) + }) + .map(_.toMap) + serializedPresentationRequest <- requestPresentation.attachments.head.data match { + case Base64(data) => ZIO.succeed(AnoncredPresentationRequest(new String(JBase64.getUrlDecoder.decode(data)))) + case _ => ZIO.fail(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data")) + } + isValid <- + ZIO + .fromTry( + Try( + AnoncredLib.verifyPresentation( + serializedPresentation, + serializedPresentationRequest, + schemaMap, + credentialDefinitionMap + ) + ) + ) + .mapError((t: Throwable) => AnoncredPresentationVerificationError(t)) + .flatMapError(e => + for { + _ <- markPresentationVerificationFailed(recordId) + } yield () + ZIO.succeed(e) + ) + result <- + if isValid then markPresentationVerified(recordId) + else markPresentationVerificationFailed(recordId) + } yield result + } + def reportProcessingFailure( recordId: DidCommID, failReason: Option[String] 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 894e53858c..59afcefd9c 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 @@ -118,6 +118,15 @@ class PresentationServiceNotifier( ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess(svc.markPresentationVerificationFailed(recordId)) + override def verifyAnoncredPresentation( + presentation: Presentation, + requestPresentation: RequestPresentation, + recordId: DidCommID + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = + notifyOnSuccess( + svc.verifyAnoncredPresentation(presentation, requestPresentation, recordId) + ) + override def acceptPresentation( recordId: DidCommID ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = @@ -166,6 +175,15 @@ class PresentationServiceNotifier( ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = svc.createAnoncredPresentationPayloadFromRecord(record, issuer, anoncredCredentialProof, issuanceDate) + override def createAnoncredPresentation( + requestPresentation: RequestPresentation, + recordId: DidCommID, + prover: Issuer, + anoncredCredentialProof: AnoncredCredentialProofsV1, + issuanceDate: Instant + ): ZIO[WalletAccessContext, PresentationError, Presentation] = + svc.createAnoncredPresentation(requestPresentation, recordId, prover, anoncredCredentialProof, issuanceDate) + override def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, limit: Int, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala index c4063533eb..e16fe79fff 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestV1.scala @@ -13,24 +13,17 @@ case class AnoncredPresentationRequestV1( non_revoked: Option[AnoncredNonRevokedIntervalV1] ) -case class AnoncredRequestedAttributeV1(name: String, restrictions: List[AnoncredAttributeRestrictionV1]) +case class AnoncredRequestedAttributeV1( + name: String, + restrictions: List[Map[String, String]], + non_revoked: Option[AnoncredNonRevokedIntervalV1] +) case class AnoncredRequestedPredicateV1( name: String, p_type: String, p_value: Int, - restrictions: List[AnoncredPredicateRestrictionV1] -) - -case class AnoncredAttributeRestrictionV1( - schema_id: Option[String], - cred_def_id: Option[String], - non_revoked: Option[AnoncredNonRevokedIntervalV1] -) - -case class AnoncredPredicateRestrictionV1( - schema_id: Option[String], - cred_def_id: Option[String], + restrictions: List[Map[String, String]], non_revoked: Option[AnoncredNonRevokedIntervalV1] ) @@ -54,20 +47,16 @@ object AnoncredPresentationRequestV1 { | "restrictions": { | "type": "array", | "items": { - | "type": "object", - | "properties": { - | "schema_id": { "type": "string" }, - | "cred_def_id": { "type": "string" }, - | "non_revoked": { - | "type": "object", - | "properties": { - | "from": { "type": "integer" }, - | "to": { "type": "integer" } - | } - | } - | } + | "type": "object" | } - | } + | }, + | "non_revoked": { + | "type": "object", + | "properties": { + | "from": { "type": "integer" }, + | "to": { "type": "integer" } + | } + | } | }, | "required": ["name", "restrictions"] | } @@ -83,19 +72,15 @@ object AnoncredPresentationRequestV1 { | "restrictions": { | "type": "array", | "items": { - | "type": "object", - | "properties": { - | "schema_id": { "type": "string" }, - | "cred_def_id": { "type": "string" }, - | "non_revoked": { - | "type": "object", - | "properties": { + | "type": "object" + | } + | }, + | "non_revoked": { + | "type": "object", + | "properties": { | "from": { "type": "integer" }, | "to": { "type": "integer" } - | } - | } | } - | } | } | }, | "required": ["name", "p_type", "p_value", "restrictions"] @@ -131,24 +116,12 @@ object AnoncredPresentationRequestV1 { given JsonEncoder[AnoncredRequestedPredicateV1] = DeriveJsonEncoder.gen[AnoncredRequestedPredicateV1] - given JsonDecoder[AnoncredAttributeRestrictionV1] = - DeriveJsonDecoder.gen[AnoncredAttributeRestrictionV1] - given JsonEncoder[AnoncredNonRevokedIntervalV1] = DeriveJsonEncoder.gen[AnoncredNonRevokedIntervalV1] given JsonDecoder[AnoncredNonRevokedIntervalV1] = DeriveJsonDecoder.gen[AnoncredNonRevokedIntervalV1] - given JsonEncoder[AnoncredAttributeRestrictionV1] = - DeriveJsonEncoder.gen[AnoncredAttributeRestrictionV1] - - given JsonDecoder[AnoncredPredicateRestrictionV1] = - DeriveJsonDecoder.gen[AnoncredPredicateRestrictionV1] - - given JsonEncoder[AnoncredPredicateRestrictionV1] = - DeriveJsonEncoder.gen[AnoncredPredicateRestrictionV1] - given JsonDecoder[AnoncredPresentationRequestV1] = DeriveJsonDecoder.gen[AnoncredPresentationRequestV1] 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 index 3d1f765fa9..14b1ffcfbe 100644 --- a/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json +++ b/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json @@ -10,7 +10,7 @@ "description": "Simple credential schema for the driving licence verifiable credential.", "type": "AnoncredSchemaV1", "schema": { - "name": "schema:uri2", + "name": "resource:///anoncred-presentation-schema-example.json", "version": "1.0", "attrNames": [ "name", diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala index 1be703bf0f..5b26f677d1 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala @@ -9,7 +9,7 @@ import io.iohk.atala.castor.core.service.MockDIDService import io.iohk.atala.mercury.model import io.iohk.atala.mercury.model.* import io.iohk.atala.mercury.protocol.issuecredential.* -import io.iohk.atala.pollux.anoncreds.Credential +import io.iohk.atala.pollux.anoncreds.AnoncredCredential import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import io.iohk.atala.pollux.core.model.error.CredentialServiceError @@ -602,7 +602,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS assertTrue(record.issueCredentialData.get.attachments.head.data match case model.Base64(value) => val ba = new String(Base64.getUrlDecoder.decode(value)) - Credential(ba).credDefId == credDefId + AnoncredCredential(ba).credDefId == credDefId case _ => false ) } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImplSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImplSpec.scala index c6ea4e6b49..c65219bc37 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImplSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/LinkSecretServiceImplSpec.scala @@ -2,7 +2,7 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.agent.walletapi.memory.GenericSecretStorageInMemory import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage -import io.iohk.atala.pollux.anoncreds.LinkSecret +import io.iohk.atala.pollux.anoncreds.AnoncredLinkSecret import io.iohk.atala.shared.models.WalletId.* import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* @@ -30,7 +30,7 @@ object LinkSecretServiceImplSpec extends ZIOSpecDefault { record1 <- svc.fetchOrCreate() storage <- ZIO.service[GenericSecretStorage] maybeDidSecret <- storage - .get[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId) + .get[String, AnoncredLinkSecret](LinkSecretServiceImpl.defaultLinkSecretId) } yield { assertTrue(record.id == LinkSecretServiceImpl.defaultLinkSecretId) assertTrue(record == record1) 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 09e89abae4..befbbdde40 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 @@ -2,10 +2,11 @@ package io.iohk.atala.pollux.core.service import io.circe.parser.decode import io.circe.syntax.* +import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage import io.iohk.atala.mercury.model.{AttachmentDescriptor, Base64, DidId} 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.anoncreds.* import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.* import io.iohk.atala.pollux.core.model.PresentationRecord.* @@ -13,6 +14,7 @@ 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.model.secret.CredentialDefinitionSecret import io.iohk.atala.pollux.core.repository.{CredentialRepository, PresentationRepository} import io.iohk.atala.pollux.core.service.serdes.{ AnoncredCredentialProofV1, @@ -27,12 +29,14 @@ import zio.test.* import zio.test.Assertion.* import java.time.{Instant, OffsetDateTime} -import java.util.Base64 as JBase64 +import java.util.{UUID, Base64 as JBase64} object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSpecHelper { override def spec = - suite("PresentationService")(singleWalletSpec, multiWalletSpec).provide(presentationServiceLayer) + suite("PresentationService")(singleWalletSpec, multiWalletSpec).provide( + presentationServiceLayer ++ genericSecretStorageLayer + ) private val singleWalletSpec = suite("singleWalletSpec")( @@ -358,12 +362,148 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp validation <- AnoncredPresentationV1.schemaSerDes.validate(aPresentationPayload.data) presentation <- AnoncredPresentationV1.schemaSerDes.deserialize(aPresentationPayload.data) } yield { - assertTrue(validation) + assert(validation)(isUnit) assert( presentation.proof.proofs.headOption.flatMap(_.primary_proof.eq_proof.revealed_attrs.headOption.map(_._1)) )(isSome(equalTo("sex"))) } }, + test("verify anoncred presentation") { + for { + credentialDefinitionService <- ZIO.service[CredentialDefinitionService] + issuerId = "did:prism:issuer" + holderID = "did:prism:holder" + schemaId = "resource:///anoncred-presentation-schema-example.json" + credentialDefinitionDb <- credentialDefinitionService.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 + ) + ) + credentialDefinitionDb <- credentialDefinitionService + .getByGUID(credentialDefinitionDb.guid) + repo <- ZIO.service[CredentialRepository] + linkSecretService <- ZIO.service[LinkSecretService] + linkSecret <- linkSecretService.fetchOrCreate() + cenericSecretStorage <- ZIO.service[GenericSecretStorage] + maybeCredentialDefintionPrivate <- + cenericSecretStorage + .get[UUID, CredentialDefinitionSecret](credentialDefinitionDb.guid) + credentialDefinition = AnoncredCreateCredentialDefinition( + AnoncredCredentialDefinition(credentialDefinitionDb.definition.toString()), + AnoncredCredentialDefinitionPrivate(maybeCredentialDefintionPrivate.get.json.toString()), + AnoncredCredentialKeyCorrectnessProof(credentialDefinitionDb.keyCorrectnessProof.toString()) + ) + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) + processedCredential = + AnoncredLib.processCredential( + AnoncredLib + .createCredential( + credentialDefinition.cd, + credentialDefinition.cdPrivate, + credentialOffer, + credentialRequest.request, + Seq( + ("name", "Miguel"), + ("sex", "M"), + ("age", "31"), + ) + ), + credentialRequest.metadata, + linkSecret, + credentialDefinition.cd + ) + 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 = processedCredential.data.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( + credentialDefinitionId = credentialDefinitionDb.longId + ) + repo <- ZIO.service[PresentationRepository] + credentialsToUse = + AnoncredCredentialProofsV1( + List( + AnoncredCredentialProofV1( + aIssueCredentialRecord.id.value, + Seq("sex"), + Seq("age") + ) + ) + ) + credentialsToUseJson <- ZIO.fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + _ <- + repo.updateAnoncredPresentationWithCredentialsToUse( + aRecord.id, + Some(AnoncredPresentationV1.version), + Some(credentialsToUseJson), + PresentationRecord.ProtocolState.RequestPending + ) + issuer = createIssuer(DID("did:prism:issuer")) + presentation <- svc.createAnoncredPresentation( + aRecord.requestPresentationData.get, + aRecord.id, + issuer, + credentialsToUse, + Instant.now() + ) + _ <- svc.receivePresentation(presentation) + validateRecord <- + svc.verifyAnoncredPresentation( + presentation, + aRecord.requestPresentationData.get, + aRecord.id + ) + } yield { + assert(validateRecord.protocolState)(equalTo(PresentationRecord.ProtocolState.PresentationVerified)) + } + }, 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 7e20c41bd9..d50f4376b5 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 @@ -171,6 +171,7 @@ trait PresentationServiceSpecHelper { } def createAnoncredRecord( + credentialDefinitionId: String = "$CRED_DEF_ID", pairwiseVerifierDID: DidId = DidId("did:prism:issuer"), pairwiseProverDID: DidId = DidId("did:prism:prover-pairwise"), thid: DidCommID = DidCommID() @@ -180,12 +181,12 @@ trait PresentationServiceSpecHelper { "sex" -> AnoncredRequestedAttributeV1( name = "sex", restrictions = List( - AnoncredAttributeRestrictionV1( - schema_id = None, - cred_def_id = Some("$CRED_DEF_ID"), - non_revoked = None + Map( + ("attr::sex::value" -> "M"), + ("cred_def_id" -> credentialDefinitionId) ) - ) + ), + non_revoked = None ) ), requested_predicates = Map( @@ -193,13 +194,14 @@ trait PresentationServiceSpecHelper { name = "age", p_type = ">=", p_value = 18, - restrictions = List.empty + restrictions = List.empty, + non_revoked = None ) ), name = "proof_req_1", nonce = "1103253414365527824079144", version = "0.1", - non_revoked = Some(AnoncredNonRevokedIntervalV1(from = Some(1), to = Some(4))) + non_revoked = None ) svc.createAnoncredPresentationRecord( thid = thid, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala index 35bd8416c7..85a18f3204 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationRequestSpec.scala @@ -13,13 +13,13 @@ object AnoncredPresentationRequestSpec extends ZIOSpecDefault { | "name": "Attribute 1", | "restrictions": [ | { - | "cred_def_id": "credential_definition_id_of_attribute1", - | "non_revoked": { - | "from": 1635734400, - | "to": 1735734400 - | } + | "cred_def_id": "credential_definition_id_of_attribute1" | } - | ] + | ], + | "non_revoked": { + | "from": 1635734400, + | "to": 1735734400 + | } | } | }, | "requested_predicates": { @@ -29,12 +29,12 @@ object AnoncredPresentationRequestSpec extends ZIOSpecDefault { | "p_value": 18, | "restrictions": [ | { - | "schema_id": "schema_id_of_predicate1", - | "non_revoked": { - | "from": 1635734400 - | } + | "schema_id": "schema_id_of_predicate1" | } - | ] + | ], + | "non_revoked": { + | "from": 1635734400 + | } | } | }, | "name": "Example Presentation Request", @@ -45,7 +45,7 @@ object AnoncredPresentationRequestSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("AnoncredPresentationRequestSerDes")( test("should validate a correct schema") { - assertZIO(AnoncredPresentationRequestV1.schemaSerDes.validate(json))(isTrue) + assertZIO(AnoncredPresentationRequestV1.schemaSerDes.validate(json))(isUnit) }, test("should deserialize correctly") { val expectedPresentationRequest = @@ -54,15 +54,14 @@ object AnoncredPresentationRequestSpec extends ZIOSpecDefault { "attribute1" -> AnoncredRequestedAttributeV1( "Attribute 1", List( - AnoncredAttributeRestrictionV1( - None, - Some("credential_definition_id_of_attribute1"), - Some( - AnoncredNonRevokedIntervalV1( - Some(1635734400), - Some(1735734400) - ) - ) + Map( + "cred_def_id" -> "credential_definition_id_of_attribute1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + Some(1735734400) ) ) ) @@ -74,15 +73,14 @@ object AnoncredPresentationRequestSpec extends ZIOSpecDefault { ">=", 18, List( - AnoncredPredicateRestrictionV1( - Some("schema_id_of_predicate1"), - None, - Some( - AnoncredNonRevokedIntervalV1( - Some(1635734400), - None - ) - ) + Map( + "schema_id" -> "schema_id_of_predicate1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + None ) ) ) diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala index 88e3fc8620..6517c6bd03 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala @@ -104,7 +104,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("AnoncredPresentationRequestSerDes")( test("should validate a correct schema") { - assertZIO(AnoncredPresentationV1.schemaSerDes.validate(json))(isTrue) + assertZIO(AnoncredPresentationV1.schemaSerDes.validate(json))(isUnit) }, test("should deserialize correctly") { val predicate = AnoncredPredicateV1( diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala index aebb68bea8..aa10648519 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/PublicCredentialDefinitionSchemaSerDesSpec.scala @@ -1,8 +1,8 @@ package io.iohk.atala.pollux.core.service.serdes import zio.* -import zio.test.Assertion.* import zio.test.* +import zio.test.Assertion.* object PublicCredentialDefinitionSchemaSerDesSpec extends ZIOSpecDefault { val json: String = @@ -46,7 +46,7 @@ object PublicCredentialDefinitionSchemaSerDesSpec extends ZIOSpecDefault { override def spec: Spec[TestEnvironment with Scope, Any] = suite("PublicCredentialDefinitionSerDes")( test("should validate a correct schema") { - assertZIO(PublicCredentialDefinitionSerDesV1.schemaSerDes.validate(json))(isTrue) + assertZIO(PublicCredentialDefinitionSerDesV1.schemaSerDes.validate(json))(isUnit) }, test("should deserialise") { val primary = PublicCredentialPrimaryPublicKeyV1( 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 c56e3c10ac..3ce67f30bd 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 @@ -9,7 +9,7 @@ import io.circe.parser.* import io.circe.syntax.* import io.iohk.atala.castor.core.model.did.* import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, OfferCredential, RequestCredential} -import io.iohk.atala.pollux.anoncreds.CredentialRequestMetadata +import io.iohk.atala.pollux.anoncreds.AnoncredCredentialRequestMetadata import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.* @@ -51,9 +51,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ given requestCredentialGet: Get[RequestCredential] = Get[String].map(decode[RequestCredential](_).getOrElse(???)) given requestCredentialPut: Put[RequestCredential] = Put[String].contramap(_.asJson.toString) - given acRequestMetadataGet: Get[CredentialRequestMetadata] = - Get[String].map(_.fromJson[CredentialRequestMetadata].getOrElse(???)) - given acRequestMetadataPut: Put[CredentialRequestMetadata] = Put[String].contramap(_.toJson) + given acRequestMetadataGet: Get[AnoncredCredentialRequestMetadata] = + Get[String].map(_.fromJson[AnoncredCredentialRequestMetadata].getOrElse(???)) + given acRequestMetadataPut: Put[AnoncredCredentialRequestMetadata] = Put[String].contramap(_.toJson) given issueCredentialGet: Get[IssueCredential] = Get[String].map(decode[IssueCredential](_).getOrElse(???)) given issueCredentialPut: Put[IssueCredential] = Put[String].contramap(_.asJson.toString) @@ -384,7 +384,7 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ override def updateWithAnonCredsRequestCredential( recordId: DidCommID, request: RequestCredential, - metadata: CredentialRequestMetadata, + metadata: AnoncredCredentialRequestMetadata, protocolState: ProtocolState ): RIO[WalletAccessContext, Int] = { val cxnIO = diff --git a/pollux/lib/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala b/pollux/lib/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala index b22493dd06..4002c87b57 100644 --- a/pollux/lib/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala +++ b/pollux/lib/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala @@ -34,8 +34,6 @@ case class W3cVerifiableCredentialPayload(payload: W3cCredentialPayload, proof: case class JwtVerifiableCredentialPayload(jwt: JWT) extends VerifiableCredentialPayload -case class AnoncredVerifiableCredentialPayload(json: String) extends VerifiableCredentialPayload //FIXME json type - case class CredentialStatus( id: String, `type`: String 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 988e235d11..836be0556d 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 @@ -197,7 +197,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { case None => ZIO.fail(InvalidState("PresentationRecord 'RequestPending' with no Record")) case Some(record) => val verifierReqPendingToSentFlow = for { - _ <- ZIO.log(s"PresentationRecord: RequestPending (Send Massage)") + _ <- ZIO.log(s"PresentationRecord: RequestPending (Send Message)") walletAccessContext <- buildWalletAccessContextLayer(record.from) result <- (for { didOps <- ZIO.service[DidOps] @@ -378,36 +378,15 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { anoncredCredentialProofs.credentialProofs.map(_.credential) ).provideSomeLayer(ZLayer.succeed(walletAccessContext)) presentation <- - for { - presentationPayload <- - presentationService - .createAnoncredPresentationPayloadFromRecord( - id, - prover, - anoncredCredentialProofs, - 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 - ) + presentationService + .createAnoncredPresentation( + requestPresentation, + id, + prover, + anoncredCredentialProofs, + Instant.now() ) - } yield presentation + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) _ <- presentationService .markPresentationGenerated(id, presentation) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ -495,7 +474,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, PresentationReceived, - _, + CredentialFormat.JWT, mayBeRequestPresentation, _, presentation, @@ -518,7 +497,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { didResolverService <- ZIO.service[JwtDidResolver] credentialsValidationResult <- p.attachments.head.data match { case Base64(data) => - val base64Decoded = new String(java.util.Base64.getDecoder().decode(data)) + val base64Decoded = new String(java.util.Base64.getDecoder.decode(data)) val maybePresentationOptions : Either[PresentationError, Option[io.iohk.atala.pollux.core.model.presentation.Options]] = mayBeRequestPresentation @@ -614,7 +593,114 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { "present_proof_flow_verifier_presentation_received_to_verification_success_or_failure_flow_ms_gauge" ) .trackDurationWith(_.toMetricsSeconds) - + case PresentationRecord( + _, + _, + _, + _, + _, + _, + _, + _, + PresentationReceived, + CredentialFormat.AnonCreds, + None, + _, + _, + _, + _, + _, + _, + _, + _ + ) => + ZIO.fail(InvalidState("PresentationRecord in 'PresentationReceived' with no Presentation Request")) + case PresentationRecord( + _, + _, + _, + _, + _, + _, + _, + _, + PresentationReceived, + CredentialFormat.AnonCreds, + _, + _, + None, + _, + _, + _, + _, + _, + _ + ) => + ZIO.fail(InvalidState("PresentationRecord in 'PresentationReceived' with no Presentation")) + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + PresentationReceived, + CredentialFormat.AnonCreds, + Some(requestPresentation), + _, + Some(presentation), + _, + _, + _, + _, + _, + _ + ) => // Verifier + ZIO.logDebug("PresentationRecord: PresentationReceived") *> ZIO.unit + val verifierPresentationReceivedToProcessed = + for { + walletAccessContext <- buildWalletAccessContextLayer(presentation.to) + presReceivedToProcessedAspect = CustomMetricsAspect.endRecordingTime( + s"${record.id}_present_proof_flow_verifier_presentation_received_to_verification_success_or_failure_ms_gauge", + "present_proof_flow_verifier_presentation_received_to_verification_success_or_failure_ms_gauge" + ) + result <- (for { + service <- ZIO.service[PresentationService] + _ <- (service + .verifyAnoncredPresentation(presentation, requestPresentation, id) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect) + .flatMapError(e => + for { + didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + ZLayer.succeed(walletAccessContext) + ) + reportproblem = ReportProblem.build( + fromDID = presentation.to, + toDID = presentation.from, + pthid = presentation.thid.getOrElse(presentation.id), + code = ProblemCode("e.p.presentation-verification-failed"), + comment = Some(e.toString) + ) + _ <- MessagingService + .send(reportproblem.toMessage) + .provideSomeLayer(didCommAgent) + _ <- ZIO.log(s"CredentialsValidationResult: ${e.toString}") + } yield () + ZIO.succeed(e) + ) + } yield ()).mapError(e => (walletAccessContext, handlePresentationErrors(e))) + } yield result + verifierPresentationReceivedToProcessed + @@ VerifierPresentationReceivedToProcessedSuccess.trackSuccess + @@ VerifierPresentationReceivedToProcessedFailed.trackError + @@ VerifierPresentationReceivedToProcessed + @@ Metric + .gauge( + "present_proof_flow_verifier_presentation_received_to_verification_success_or_failure_flow_ms_gauge" + ) + .trackDurationWith(_.toMetricsSeconds) case PresentationRecord( id, _, 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 5a6056b3f7..12b4c74c09 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 @@ -49,8 +49,12 @@ object PresentProofController { 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.AnoncredPresentationVerificationError(cause) => + ErrorResponse.badRequest(title = "Error Verifying Prensetation", detail = Some(cause.toString)) case PresentationError.InvalidAnoncredPresentationRequest(msg) => ErrorResponse.badRequest(title = "Invalid Anoncred Presentation Request", detail = Some(msg)) + case PresentationError.InvalidAnoncredPresentation(msg) => + ErrorResponse.badRequest(title = "Invalid Anoncred Presentation", detail = Some(msg)) case PresentationError.NotMatchingPresentationCredentialFormat(cause) => ErrorResponse.badRequest( title = "Presentation and Credential Format Not Matching", diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala index 4f50e50f30..dd460c4c25 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala @@ -75,9 +75,5 @@ object RequestPresentationInput { given Schema[AnoncredNonRevokedIntervalV1] = Schema.derived - given Schema[AnoncredAttributeRestrictionV1] = Schema.derived - - given Schema[AnoncredPredicateRestrictionV1] = Schema.derived - given schema: Schema[RequestPresentationInput] = Schema.derived } diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerTestTools.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerTestTools.scala index e8c9a52135..b5fd42f818 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerTestTools.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerTestTools.scala @@ -16,7 +16,7 @@ import io.iohk.atala.issue.controller.http.{ IssueCredentialRecord, IssueCredentialRecordPage } -import io.iohk.atala.pollux.anoncreds.LinkSecretWithId +import io.iohk.atala.pollux.anoncreds.AnoncredLinkSecretWithId import io.iohk.atala.pollux.core.model.CredentialFormat import io.iohk.atala.pollux.core.repository.{CredentialDefinitionRepositoryInMemory, CredentialRepositoryInMemory} import io.iohk.atala.pollux.core.service.* @@ -81,7 +81,7 @@ trait IssueControllerTestTools extends PostgresTestContainerSupport { didResolverLayer >+> ResourceURIDereferencerImpl.layer >+> CredentialRepositoryInMemory.layer >+> - ZLayer.succeed(LinkSecretWithId("Unused Linked Secret ID")) >+> + ZLayer.succeed(AnoncredLinkSecretWithId("Unused Linked Secret ID")) >+> MockDIDService.empty >+> MockManagedDIDService.empty >+> CredentialServiceImpl.layer >+> diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/pollux/credentialdefinition/CredentialDefinitionBasicSpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/pollux/credentialdefinition/CredentialDefinitionBasicSpec.scala index 4b53450a27..ffabec1c77 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/pollux/credentialdefinition/CredentialDefinitionBasicSpec.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/pollux/credentialdefinition/CredentialDefinitionBasicSpec.scala @@ -1,7 +1,6 @@ package io.iohk.atala.pollux.credentialdefinition -import io.iohk.atala.agent.walletapi.model.BaseEntity -import io.iohk.atala.agent.walletapi.model.Entity +import io.iohk.atala.agent.walletapi.model.{BaseEntity, Entity} import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage import io.iohk.atala.api.http.ErrorResponse import io.iohk.atala.container.util.MigrationAspects.* @@ -112,25 +111,22 @@ object CredentialDefinitionBasicSpec extends ZIOSpecDefault with CredentialDefin maybeValidPublicDefinition <- PublicCredentialDefinitionSerDesV1.schemaSerDes.validate( fetchedCredentialDefinition.definition.toString() ) - assertValidPublicDefinition = assert(maybeValidPublicDefinition)(Assertion.isTrue) + assertValidPublicDefinition = assert(maybeValidPublicDefinition)(Assertion.isUnit) maybeValidKeyCorrectnessProof <- ProofKeyCredentialDefinitionSchemaSerDesV1.schemaSerDes.validate( fetchedCredentialDefinition.keyCorrectnessProof.toString() ) - assertValidKeyCorrectnessProof = assert(maybeValidKeyCorrectnessProof)(Assertion.isTrue) + assertValidKeyCorrectnessProof = assert(maybeValidKeyCorrectnessProof)(Assertion.isUnit) storage <- ZIO.service[GenericSecretStorage] maybeDidSecret <- storage .get[UUID, CredentialDefinitionSecret](fetchedCredentialDefinition.guid) .provideSomeLayer(Entity.Default.wacLayer) maybeValidPrivateDefinitionZIO = maybeDidSecret match { case Some(didSecret) => - val validPrivateDefinition = - PrivateCredentialDefinitionSchemaSerDesV1.schemaSerDes.validate(didSecret.json.toString()) - validPrivateDefinition - case None => - ZIO.succeed(false) + PrivateCredentialDefinitionSchemaSerDesV1.schemaSerDes.validate(didSecret.json.toString()) + case None => ZIO.unit } maybeValidPrivateDefinition <- maybeValidPrivateDefinitionZIO - assertValidPrivateDefinition = assert(maybeValidPrivateDefinition)(Assertion.isTrue) + assertValidPrivateDefinition = assert(maybeValidPrivateDefinition)(Assertion.isUnit) } yield statusCodeIs201 && credentialDefinitionIsCreated && credentialDefinitionIsFetched && From 8439de494638e7394d0085fc5579e8294be62c2e Mon Sep 17 00:00:00 2001 From: bvoiturier Date: Mon, 15 Jan 2024 09:13:56 +0100 Subject: [PATCH 09/12] fix(prism-agent): fix credential schema parsing as spec (#841) Signed-off-by: Benjamin Voiturier Signed-off-by: Bassam Riman --- .../service/PresentationServiceImpl.scala | 10 +++--- .../anoncred-presentation-schema-example.json | 32 +++++-------------- 2 files changed, 13 insertions(+), 29 deletions(-) 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 6ac79b7b17..bd8d1e286f 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 @@ -10,11 +10,11 @@ import io.iohk.atala.mercury.protocol.issuecredential.IssueCredentialIssuedForma 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.CredentialSchemaError.CredentialSchemaParsingError 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.CredentialDefinition -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.{ @@ -27,6 +27,7 @@ import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect import zio.{ZIO, *} +import zio.json.* import java.net.URI import java.rmi.UnexpectedException @@ -596,10 +597,9 @@ private class PresentationServiceImpl( 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)) + anoncredSchema <- ZIO + .fromEither(content.fromJson[AnoncredSchemaSerDesV1]) + .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) anoncredLibSchema = AnoncredSchemaDef( schemaId, 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 index 14b1ffcfbe..febd78d65d 100644 --- a/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json +++ b/pollux/lib/core/src/test/resources/anoncred-presentation-schema-example.json @@ -1,26 +1,10 @@ { - "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" + "name": "resource:///anoncred-presentation-schema-example.json", + "version": "1.0", + "attrNames": [ + "name", + "sex", + "age" ], - "description": "Simple credential schema for the driving licence verifiable credential.", - "type": "AnoncredSchemaV1", - "schema": { - "name": "resource:///anoncred-presentation-schema-example.json", - "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" -} + "issuerId": "did:prism:issuer" +} \ No newline at end of file From e16f8d57f212604f49a6ebe5fc3f5f87a802a6f7 Mon Sep 17 00:00:00 2001 From: Bassam Date: Mon, 22 Jan 2024 09:50:28 -0500 Subject: [PATCH 10/12] feat: interoperable credential definition api (#852) Signed-off-by: Bassam Riman --- .../core/model/IssueCredentialRecord.scala | 7 +- .../core/model/schema/CredentialSchema.scala | 9 +- .../CredentialRepositoryInMemory.scala | 6 +- .../CredentialDefinitionServiceImpl.scala | 10 +-- .../core/service/CredentialService.scala | 4 +- .../core/service/CredentialServiceImpl.scala | 27 +++--- .../service/CredentialServiceNotifier.scala | 8 +- .../core/service/MockCredentialService.scala | 4 +- .../service/PresentationServiceImpl.scala | 83 ++++++++----------- .../CredentialRepositorySpecSuite.scala | 3 +- .../service/CredentialServiceImplSpec.scala | 6 +- .../CredentialServiceNotifierSpec.scala | 1 + .../service/PresentationServiceSpec.scala | 36 ++++++-- .../PresentationServiceSpecHelper.scala | 3 +- .../serdes/AnoncredPresentationSpec.scala | 4 +- ...V18__issue_credential_rename_schema_id.sql | 2 + .../repository/JdbcCredentialRepository.scala | 26 +++--- .../server/jobs/IssueBackgroundJobs.scala | 10 ++- .../controller/IssueControllerImpl.scala | 16 ++-- 19 files changed, 147 insertions(+), 118 deletions(-) create mode 100644 pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql 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 1551c85f53..c803f3421f 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 @@ -21,8 +21,9 @@ final case class IssueCredentialRecord( createdAt: Instant, updatedAt: Option[Instant], thid: DidCommID, - schemaId: Option[String], + schemaUri: Option[String], credentialDefinitionId: Option[UUID], + credentialDefinitionUri: Option[String], credentialFormat: CredentialFormat, role: Role, subjectId: Option[String], @@ -77,8 +78,8 @@ final case class ValidFullIssuedCredentialRecord( id: DidCommID, issuedCredential: Option[IssueCredential], credentialFormat: CredentialFormat, - schemaId: Option[String], - credentialDefinitionId: Option[UUID], + schemaUri: Option[String], + credentialDefinitionUri: Option[String], subjectId: Option[String] ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala index ea77c80792..6c05b941cb 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala @@ -149,10 +149,11 @@ object CredentialSchema { for { uri <- ZIO.attempt(new URI(schemaId)).mapError(t => URISyntaxError(t.getMessage)) content <- uriDereferencer.dereference(uri).mapError(err => UnexpectedError(err.toString)) - validAttrNames <- ZIO - .fromEither(content.fromJson[AnoncredSchemaSerDesV1]) - .mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error")) - .map(_.attrNames) + validAttrNames <- + AnoncredSchemaSerDesV1.schemaSerDes + .deserialize(content) + .mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error")) + .map(_.attrNames) jsonClaims <- ZIO.fromEither(claims.fromJson[Json]).mapError(err => UnexpectedError(err)) _ <- jsonClaims match case Json.Obj(fields) => 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 c329a2548a..2bc9bce4c5 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 @@ -148,7 +148,7 @@ class CredentialRepositoryInMemory( recordId.contains( rec.id ) && rec.issueCredentialData.isDefined - && rec.schemaId.isDefined + && rec.schemaUri.isDefined && rec.credentialDefinitionId.isDefined && rec.credentialFormat == CredentialFormat.AnonCreds ) @@ -157,8 +157,8 @@ class CredentialRepositoryInMemory( rec.id, rec.issueCredentialData, rec.credentialFormat, - rec.schemaId, - rec.credentialDefinitionId, + rec.schemaUri, + rec.credentialDefinitionUri, rec.subjectId ) ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala index 1862f2690c..01ea444476 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala @@ -4,7 +4,7 @@ import io.iohk.atala.agent.walletapi.storage import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage import io.iohk.atala.pollux.anoncreds.{AnoncredLib, AnoncredSchemaDef} import io.iohk.atala.pollux.core.model.error.CredentialSchemaError -import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.URISyntaxError +import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.{SchemaError, URISyntaxError} import io.iohk.atala.pollux.core.model.schema.CredentialDefinition import io.iohk.atala.pollux.core.model.schema.CredentialDefinition.{Filter, FilteredEntries} import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 @@ -84,12 +84,10 @@ class CredentialDefinitionServiceImpl( ) } yield createdCredentialDefinition }.mapError { - case e: CredentialDefinitionCreationError => e - case j: JsonSchemaError => UnexpectedError(j.error) - case s: URISyntaxError => UnexpectedError(s.message) - case u: URIDereferencerError => UnexpectedError(u.error) - case e: CredentialSchemaError => CredentialDefinitionValidationError(e) + case u: URIDereferencerError => CredentialDefinitionValidationError(URISyntaxError(u.error)) + case j: JsonSchemaError => CredentialDefinitionValidationError(SchemaError(j)) case t: Throwable => RepositoryError(t) + case e: CredentialDefinitionCreationError => e } override def delete(guid: UUID): Result[CredentialDefinition] = diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala index d6a81adcbf..47622c4335 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala @@ -32,10 +32,10 @@ trait CredentialService { pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: io.circe.Json, validityPeriod: Option[Double] = None, - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] /** Return a list of records as well as a count of all filtered items */ diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala index ffddf3856d..8b273ebdb5 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala @@ -130,8 +130,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = thid, - schemaId = maybeSchemaId, + schemaUri = maybeSchemaId, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = CredentialFormat.JWT, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -165,10 +166,10 @@ private class CredentialServiceImpl( pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = { for { credentialDefinition <- credentialDefinitionService @@ -181,11 +182,11 @@ private class CredentialServiceImpl( offer <- createAnonCredsDidCommOfferCredential( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, - schemaId = credentialDefinition.schemaId, + schemaUri = credentialDefinition.schemaId, credentialDefinitionGUID = credentialDefinitionGUID, + credentialDefinitionId = credentialDefinitionId, claims = attributes, thid = thid, - credentialDefinitionId ) record <- ZIO.succeed( IssueCredentialRecord( @@ -193,8 +194,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = thid, - schemaId = Some(credentialDefinition.schemaId), + schemaUri = Some(credentialDefinition.schemaId), credentialDefinitionId = Some(credentialDefinitionGUID), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -270,8 +272,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = DidCommID(offer.thid.getOrElse(offer.id)), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = Role.Holder, subjectId = None, @@ -843,14 +846,14 @@ private class CredentialServiceImpl( private[this] def createAnonCredsDidCommOfferCredential( pairwiseIssuerDID: DidId, pairwiseHolderDID: DidId, - schemaId: String, + schemaUri: String, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Seq[Attribute], - thid: DidCommID, - credentialDefinitionId: String + thid: DidCommID ) = { for { - credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaId), attributes = claims)) + credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaUri), attributes = claims)) body = OfferCredential.Body( goal_code = Some("Offer Credential"), credential_preview = credentialPreview, @@ -1036,7 +1039,7 @@ private class CredentialServiceImpl( issuanceDate = issuanceDate, maybeExpirationDate = record.validityPeriod.map(sec => issuanceDate.plusSeconds(sec.toLong)), maybeCredentialSchema = - record.schemaId.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)), + record.schemaUri.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)), credentialSubject = claims.add("id", jwtPresentation.iss.asJson).asJson, maybeCredentialStatus = None, maybeRefreshService = None, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala index 2474208493..177a9c7904 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala @@ -47,10 +47,10 @@ class CredentialServiceNotifier( pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: _root_.java.lang.String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: _root_.java.lang.String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = notifyOnSuccess( svc.createAnonCredsIssueCredentialRecord( @@ -58,10 +58,10 @@ class CredentialServiceNotifier( pairwiseHolderDID, thid, credentialDefinitionGUID, + credentialDefinitionId, claims, validityPeriod, - automaticIssuance, - credentialDefinitionId + automaticIssuance ) ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala index b3013f4745..f7a2a72f73 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala @@ -100,10 +100,10 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = proxy( CreateAnonCredsIssueCredentialRecord, 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 bd8d1e286f..08b25b001f 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 @@ -10,24 +10,16 @@ import io.iohk.atala.mercury.protocol.issuecredential.IssueCredentialIssuedForma 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.CredentialSchemaError.CredentialSchemaParsingError 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.CredentialDefinition 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.{ - AnoncredCredentialProofV1, - AnoncredCredentialProofsV1, - AnoncredPresentationRequestV1, - AnoncredPresentationV1 -} +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.utils.aspects.CustomMetricsAspect -import zio.{ZIO, *} -import zio.json.* +import zio.* import java.net.URI import java.rmi.UnexpectedException @@ -37,7 +29,6 @@ import java.util.{UUID, Base64 as JBase64} import scala.util.Try private class PresentationServiceImpl( - credentialDefinitionService: CredentialDefinitionService, uriDereferencer: URIDereferencer, linkSecretService: LinkSecretService, presentationRepository: PresentationRepository, @@ -149,8 +140,8 @@ private class PresentationServiceImpl( ) presentationPayload <- createAnoncredPresentationPayloadFromCredential( issuedCredentials, - issuedValidCredentials.flatMap(_.schemaId), - issuedValidCredentials.flatMap(_.credentialDefinitionId), + issuedValidCredentials.flatMap(_.schemaUri), + issuedValidCredentials.flatMap(_.credentialDefinitionUri), requestPresentation, anoncredCredentialProof.credentialProofs ) @@ -502,28 +493,21 @@ private class PresentationServiceImpl( private def createAnoncredPresentationPayloadFromCredential( issuedCredentialRecords: Seq[ValidFullIssuedCredentialRecord], schemaIds: Seq[String], - credentialDefinitionIds: Seq[UUID], + credentialDefinitionIds: Seq[String], requestPresentation: RequestPresentation, credentialProofs: List[AnoncredCredentialProofV1], ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { for { schemaMap <- ZIO - .collectAll(schemaIds.map { schemaId => - resolveSchema(schemaId) + .collectAll(schemaIds.map { schemaUri => + resolveSchema(schemaUri) }) .map(_.toMap) credentialDefinitionMap <- ZIO - .collectAll(credentialDefinitionIds.map { credentialDefinitionId => - for { - credentialDefinition <- credentialDefinitionService - .getByGUID(credentialDefinitionId) - .mapError(e => UnexpectedError(e.toString)) - } yield ( - credentialDefinition.longId, - AnoncredCredentialDefinition(credentialDefinition.definition.toString) - ) + .collectAll(credentialDefinitionIds.map { credentialDefinitionUri => + resolveCredentialDefinition(credentialDefinitionUri) }) .map(_.toMap) credentialProofsMap = credentialProofs.map(credentialProof => (credentialProof.credential, credentialProof)).toMap @@ -593,21 +577,36 @@ private class PresentationServiceImpl( } yield presentation } - private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = { + private def resolveSchema(schemaUri: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = { for { - uri <- ZIO.attempt(new URI(schemaId)).mapError(e => UnexpectedError(e.getMessage)) + uri <- ZIO.attempt(new URI(schemaUri)).mapError(e => UnexpectedError(e.getMessage)) content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) - anoncredSchema <- ZIO - .fromEither(content.fromJson[AnoncredSchemaSerDesV1]) - .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) + anoncredSchema <- + AnoncredSchemaSerDesV1.schemaSerDes + .deserialize(content) + .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) anoncredLibSchema = AnoncredSchemaDef( - schemaId, + schemaUri, anoncredSchema.version, anoncredSchema.attrNames, anoncredSchema.issuerId ) - } yield (schemaId, anoncredLibSchema) + } yield (schemaUri, anoncredLibSchema) + } + + private def resolveCredentialDefinition( + credentialDefinitionUri: String + ): IO[UnexpectedError, (String, AnoncredCredentialDefinition)] = { + for { + uri <- ZIO.attempt(new URI(credentialDefinitionUri)).mapError(e => UnexpectedError(e.getMessage)) + content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) + _ <- + PublicCredentialDefinitionSerDesV1.schemaSerDes + .validate(content) + .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) + anoncredCredentialDefinition = AnoncredCredentialDefinition(content) + } yield (credentialDefinitionUri, anoncredCredentialDefinition) } def acceptRequestPresentation( @@ -952,21 +951,7 @@ private class PresentationServiceImpl( credentialDefinitionMap <- ZIO .collectAll(credentialDefinitionIds.map { credentialDefinitionId => - for { - guid <- - ZIO - .fromOption(CredentialDefinition.extractGUID(credentialDefinitionId)) - .mapError(_ => - InvalidAnoncredPresentation(s"CredentialDefinitionId format invalid ${credentialDefinitionId}") - ) - credentialDefinition <- - credentialDefinitionService - .getByGUID(guid) - .mapError(e => UnexpectedError(e.toString)) - } yield ( - credentialDefinition.longId, - AnoncredCredentialDefinition(credentialDefinition.definition.toString) - ) + resolveCredentialDefinition(credentialDefinitionId) }) .map(_.toMap) serializedPresentationRequest <- requestPresentation.attachments.head.data match { @@ -1105,8 +1090,8 @@ private class PresentationServiceImpl( object PresentationServiceImpl { val layer: URLayer[ - CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, + URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, PresentationService ] = - ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _, _)) + ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _)) } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala index ce622e7a16..0d0a1eb1c0 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala @@ -22,8 +22,9 @@ object CredentialRepositorySpecSuite { createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala index 5b26f677d1..5b2c22ac49 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala @@ -82,7 +82,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS } yield { assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && - assertTrue(record.schemaId.isEmpty) && + assertTrue(record.schemaUri.isEmpty) && assertTrue(record.validityPeriod == validityPeriod) && assertTrue(record.automaticIssuance == automaticIssuance) && assertTrue(record.role == Role.Issuer) && @@ -158,7 +158,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && assertTrue( - record.schemaId.contains("resource:///vc-schema-example.json") + record.schemaUri.contains("resource:///vc-schema-example.json") ) && assertTrue(record.validityPeriod == validityPeriod) && assertTrue(record.automaticIssuance == automaticIssuance) && @@ -290,7 +290,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS } yield { assertTrue(holderRecord.thid.toString == offer.thid.get) && assertTrue(holderRecord.updatedAt.isEmpty) && - assertTrue(holderRecord.schemaId.isEmpty) && + assertTrue(holderRecord.schemaUri.isEmpty) && assertTrue(holderRecord.validityPeriod.isEmpty) && assertTrue(holderRecord.automaticIssuance.isEmpty) && assertTrue(holderRecord.role == Role.Holder) && diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala index 61c327985a..17e3ce7a3b 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala @@ -21,6 +21,7 @@ object CredentialServiceNotifierSpec extends MockSpecDefault with CredentialServ DidCommID(), None, None, + None, CredentialFormat.JWT, IssueCredentialRecord.Role.Issuer, None, 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 befbbdde40..f5f907d8a1 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 @@ -28,6 +28,8 @@ import zio.* import zio.test.* import zio.test.Assertion.* +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path, Paths} import java.time.{Instant, OffsetDateTime} import java.util.{UUID, Base64 as JBase64} @@ -276,7 +278,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp linkSecretService <- ZIO.service[LinkSecretService] linkSecret <- linkSecretService.fetchOrCreate() credentialDefinition = AnoncredLib.createCredDefinition(issuerId, schema, "tag", supportRevocation = false) - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") + credentialDefinitionId = "resource:///" + file.getFileName + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) credential = AnoncredLib @@ -310,8 +314,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = Some(schemaId), + schemaUri = Some(schemaId), credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -331,7 +336,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp ) _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) svc <- ZIO.service[PresentationService] - aRecord <- svc.createAnoncredRecord() + aRecord <- svc.createAnoncredRecord(credentialDefinitionId = credentialDefinitionId) repo <- ZIO.service[PresentationRepository] credentialsToUse = AnoncredCredentialProofsV1( @@ -392,16 +397,18 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp repo <- ZIO.service[CredentialRepository] linkSecretService <- ZIO.service[LinkSecretService] linkSecret <- linkSecretService.fetchOrCreate() - cenericSecretStorage <- ZIO.service[GenericSecretStorage] + genericSecretStorage <- ZIO.service[GenericSecretStorage] maybeCredentialDefintionPrivate <- - cenericSecretStorage + genericSecretStorage .get[UUID, CredentialDefinitionSecret](credentialDefinitionDb.guid) credentialDefinition = AnoncredCreateCredentialDefinition( AnoncredCredentialDefinition(credentialDefinitionDb.definition.toString()), AnoncredCredentialDefinitionPrivate(maybeCredentialDefintionPrivate.get.json.toString()), AnoncredCredentialKeyCorrectnessProof(credentialDefinitionDb.keyCorrectnessProof.toString()) ) - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") + credentialDefinitionId = "resource:///" + file.getFileName + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) processedCredential = AnoncredLib.processCredential( @@ -440,8 +447,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = Some(schemaId), + schemaUri = Some(schemaId), credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -462,7 +470,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) svc <- ZIO.service[PresentationService] aRecord <- svc.createAnoncredRecord( - credentialDefinitionId = credentialDefinitionDb.longId + credentialDefinitionId = credentialDefinitionId ) repo <- ZIO.service[PresentationRepository] credentialsToUse = @@ -1031,4 +1039,16 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } ) + def createTempJsonFile(jsonContent: String, fileName: String): Path = { + val resourceURI = this.getClass.getResource("/").toURI + val resourcePath = Paths.get(resourceURI) + + val filePath = resourcePath.resolve(fileName + ".json") + + Files.write(filePath, jsonContent.getBytes(StandardCharsets.UTF_8)) + + filePath.toFile.deleteOnExit() + filePath + } + } 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 d50f4376b5..bbb32df467 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 @@ -132,8 +132,9 @@ trait PresentationServiceSpecHelper { createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala index 6517c6bd03..275bc181be 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala @@ -93,7 +93,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { | "identifiers": [ | { | "schema_id": "resource:///anoncred-presentation-schema-example.json", - | "cred_def_id": "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + | "cred_def_id": "resource:///anoncred-presentation-credential-definition-example.json", | "rev_reg_id": null, | "timestamp": null | } @@ -189,7 +189,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { val identifier = AnoncredIdentifierV1( schema_id = "resource:///anoncred-presentation-schema-example.json", - cred_def_id = "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + cred_def_id = "resource:///anoncred-presentation-credential-definition-example.json", rev_reg_id = None, timestamp = None ) diff --git a/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql new file mode 100644 index 0000000000..6fc8f3c693 --- /dev/null +++ b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.issue_credential_records RENAME COLUMN schema_id TO schema_uri; +ALTER TABLE public.issue_credential_records ADD COLUMN credential_definition_uri VARCHAR(500); 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 3ce67f30bd..8d86a432df 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 @@ -69,8 +69,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -92,8 +93,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | ${record.createdAt}, | ${record.updatedAt}, | ${record.thid}, - | ${record.schemaId}, + | ${record.schemaUri}, | ${record.credentialDefinitionId}, + | ${record.credentialDefinitionUri}, | ${record.credentialFormat}, | ${record.role}, | ${record.subjectId}, @@ -136,8 +138,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -203,8 +206,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -253,8 +257,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -294,8 +299,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -460,14 +466,14 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | id, | issue_credential_data, | credential_format, - | schema_id, - | credential_definition_id, + | schema_uri, + | credential_definition_uri, | 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 schema_uri IS NOT NULL + | AND credential_definition_uri IS NOT NULL | AND credential_format = 'AnonCreds' | AND $inClauseFragment """.stripMargin diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala index 3f1f6977e5..ac3857a3d0 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala @@ -2,6 +2,7 @@ package io.iohk.atala.agent.server.jobs import io.iohk.atala.agent.server.config.AppConfig import io.iohk.atala.agent.server.jobs.BackgroundJobError.ErrorResponseReceivedFromPeerAgent +import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError import io.iohk.atala.castor.core.model.did.* import io.iohk.atala.mercury.* import io.iohk.atala.mercury.protocol.issuecredential.* @@ -12,7 +13,6 @@ import io.iohk.atala.shared.utils.DurationOps.toMetricsSeconds import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect import zio.* import zio.metrics.* -import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError object IssueBackgroundJobs extends BackgroundJobsHelper { @@ -148,6 +148,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, @@ -202,6 +203,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.JWT, Role.Holder, Some(subjectId), @@ -242,6 +244,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.AnonCreds, Role.Holder, None, @@ -284,6 +287,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Holder, _, _, @@ -339,6 +343,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, @@ -377,6 +382,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.JWT, Role.Issuer, _, @@ -418,6 +424,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.AnonCreds, Role.Issuer, _, @@ -460,6 +467,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala index a88521701b..bba5c66d68 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala @@ -78,21 +78,23 @@ class IssueControllerImpl( .mapError(_ => ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId")) ) + credentialDefinitionId = { + val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl + val urlSuffix = + s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition" + val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/" + s"$urlPrefix$urlSuffix" + } record <- credentialService .createAnonCredsIssueCredentialRecord( pairwiseIssuerDID = didIdPair.myDID, pairwiseHolderDID = didIdPair.theirDid, thid = DidCommID(), credentialDefinitionGUID = credentialDefinitionGUID, + credentialDefinitionId = credentialDefinitionId, claims = jsonClaims, validityPeriod = request.validityPeriod, - automaticIssuance = request.automaticIssuance.orElse(Some(true)), { - val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl - val urlSuffix = - s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition" - val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/" - s"$urlPrefix$urlSuffix" - } + automaticIssuance = request.automaticIssuance.orElse(Some(true)) ) } yield record } yield IssueCredentialRecord.fromDomain(outcome) From d416266995c07491de75ae56ce4d2ae89e8ca48a Mon Sep 17 00:00:00 2001 From: Bassam Date: Mon, 5 Feb 2024 13:54:36 -0500 Subject: [PATCH 11/12] feat: refactor duplcate code (#873) Signed-off-by: Bassam Riman --- .../service/PresentationServiceImpl.scala | 211 +++---- .../PresentationRepositorySpecSuite.scala | 30 - .../service/PresentationServiceSpec.scala | 562 +++++++----------- 3 files changed, 310 insertions(+), 493 deletions(-) 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 08b25b001f..d75e785f19 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 @@ -54,7 +54,7 @@ private class PresentationServiceImpl( ) _ <- count match case 1 => ZIO.succeed(()) - case n => ZIO.fail(RecordIdNotFound(recordId)) + case _ => ZIO.fail(RecordIdNotFound(recordId)) record <- presentationRepository .getPresentationRecord(recordId) .mapError(RepositoryError.apply) @@ -235,49 +235,15 @@ private class PresentationServiceImpl( proofTypes: Seq[ProofType], maybeOptions: Option[io.iohk.atala.pollux.core.model.presentation.Options] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { - for { - request <- ZIO.succeed( - createDidCommRequestPresentation( - proofTypes, - thid, - pairwiseVerifierDID, - pairwiseProverDID, - maybeOptions.map(options => Seq(toJWTAttachment(options))).getOrElse(Seq.empty) - ) - ) - record <- ZIO.succeed( - PresentationRecord( - id = DidCommID(), - createdAt = Instant.now, - updatedAt = None, - thid = thid, - connectionId = connectionId, - schemaId = None, // TODO REMOVE from DB - role = PresentationRecord.Role.Verifier, - subjectId = pairwiseProverDID, - protocolState = PresentationRecord.ProtocolState.RequestPending, - credentialFormat = CredentialFormat.JWT, - requestPresentationData = Some(request), - proposePresentationData = None, - presentationData = None, - credentialsToUse = None, - anoncredCredentialsToUseJsonSchemaId = None, - anoncredCredentialsToUse = None, - metaRetries = maxRetries, - metaNextRetry = Some(Instant.now()), - metaLastFailure = None, - ) - ) - count <- presentationRepository - .createPresentationRecord(record) - .flatMap { - case 1 => ZIO.succeed(()) - case n => ZIO.fail(UnexpectedException(s"Invalid row count result: $n")) - } - .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( - s"${record.id}_present_proof_flow_verifier_req_pending_to_sent_ms_gauge" - ) - } yield record + createPresentationRecord( + pairwiseVerifierDID, + pairwiseProverDID, + thid, + connectionId, + CredentialFormat.JWT, + proofTypes, + maybeOptions.map(options => Seq(toJWTAttachment(options))).getOrElse(Seq.empty) + ) } override def createAnoncredPresentationRecord( @@ -287,14 +253,34 @@ private class PresentationServiceImpl( connectionId: Option[String], presentationRequest: AnoncredPresentationRequestV1 ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + createPresentationRecord( + pairwiseVerifierDID, + pairwiseProverDID, + thid, + connectionId, + CredentialFormat.AnonCreds, + Seq.empty, + Seq(toAnoncredAttachment(presentationRequest)) + ) + } + + private def createPresentationRecord( + pairwiseVerifierDID: DidId, + pairwiseProverDID: DidId, + thid: DidCommID, + connectionId: Option[String], + format: CredentialFormat, + proofTypes: Seq[ProofType], + attachments: Seq[AttachmentDescriptor] + ) = { for { request <- ZIO.succeed( createDidCommRequestPresentation( - Seq.empty, + proofTypes, thid, pairwiseVerifierDID, pairwiseProverDID, - Seq(toAnoncredAttachment(presentationRequest)) + attachments ) ) record <- ZIO.succeed( @@ -308,7 +294,7 @@ private class PresentationServiceImpl( role = PresentationRecord.Role.Verifier, subjectId = pairwiseProverDID, protocolState = PresentationRecord.ProtocolState.RequestPending, - credentialFormat = CredentialFormat.AnonCreds, + credentialFormat = format, requestPresentationData = Some(request), proposePresentationData = None, presentationData = None, @@ -320,7 +306,7 @@ private class PresentationServiceImpl( metaLastFailure = None, ) ) - count <- presentationRepository + _ <- presentationRepository .createPresentationRecord(record) .flatMap { case 1 => ZIO.succeed(()) @@ -404,7 +390,7 @@ private class PresentationServiceImpl( metaLastFailure = None, ) ) - count <- presentationRepository + _ <- presentationRepository .createPresentationRecord(record) .flatMap { case 1 => ZIO.succeed(()) @@ -484,7 +470,7 @@ private class PresentationServiceImpl( } yield presentationPayload } - case class AnoncredCredentialProof( + private case class AnoncredCredentialProof( credential: String, requestedAttribute: Seq[String], requestedPredicate: Seq[String] @@ -544,7 +530,7 @@ private class PresentationServiceImpl( 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)) @@ -619,50 +605,30 @@ private class PresentationServiceImpl( issuedCredentials <- credentialRepository .getValidIssuedCredentials(credentialsToUse.map(DidCommID(_))) .mapError(RepositoryError.apply) - _ <- ZIO.cond( - (issuedCredentials.map(_.subjectId).toSet.size == 1), - (), - PresentationError.HolderBindingError( - s"Creating a Verifiable Presentation for credential with different subject DID is not supported, found : ${issuedCredentials - .map(_.subjectId)}" - ) - ) - validatedCredentials <- ZIO.fromEither( - Either.cond( - issuedCredentials.forall(issuedValidCredential => - issuedValidCredential.credentialFormat == record.credentialFormat - ), - issuedCredentials, - PresentationError.NotMatchingPresentationCredentialFormat( - new IllegalArgumentException( - s"No matching issued credentials format: expectedFormat=${record.credentialFormat}" - ) - ) - ) - ) - signedCredentials = validatedCredentials.flatMap(_.issuedCredentialRaw) - _ <- ZIO.fromEither( - Either.cond( - signedCredentials.nonEmpty, - signedCredentials, - PresentationError.IssuedCredentialNotFoundError( - new Throwable(s"No matching issued credentials found in prover db from the given: $credentialsToUse") - ) - ) + _ <- validateCredentials( + s"No matching issued credentials found in prover db from the given: $credentialsToUse", + record, + issuedCredentials ) count <- presentationRepository .updatePresentationWithCredentialsToUse(recordId, Option(credentialsToUse), ProtocolState.PresentationPending) .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( s"${record.id}_present_proof_flow_prover_presentation_pending_to_generated_ms_gauge" ) + record <- fetchPresentationRecord(recordId, count) + } yield record + } + + private def fetchPresentationRecord(recordId: DidCommID, count: RuntimeFlags) = { + for { _ <- count match case 1 => ZIO.succeed(()) - case n => ZIO.fail(RecordIdNotFound(recordId)) + case _ => ZIO.fail(RecordIdNotFound(recordId)) record <- presentationRepository .getPresentationRecord(recordId) .mapError(RepositoryError.apply) .flatMap { - case None => ZIO.fail(RecordIdNotFound(record.id)) + case None => ZIO.fail(RecordIdNotFound(recordId)) case Some(value) => ZIO.succeed(value) } } yield record @@ -680,8 +646,42 @@ private class PresentationServiceImpl( credentialsToUse.credentialProofs.map(credentialProof => DidCommID(credentialProof.credential)) ) .mapError(RepositoryError.apply) + _ <- validateCredentials( + s"No matching issued credentials found in prover db from the given: $credentialsToUse", + record, + issuedCredentials + ) + anoncredCredentialProofsV1AsJson <- ZIO + .fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + .mapError(error => + PresentationError.UnexpectedError( + s"Unable to serialize credentialsToUse. credentialsToUse:$credentialsToUse, error:$error" + ) + ) + count <- presentationRepository + .updateAnoncredPresentationWithCredentialsToUse( + recordId, + Option(AnoncredCredentialProofsV1.version), + Option(anoncredCredentialProofsV1AsJson), + ProtocolState.PresentationPending + ) + .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( + s"${record.id}_present_proof_flow_prover_presentation_pending_to_generated_ms_gauge" + ) + record <- fetchPresentationRecord(recordId, count) + } yield record + } + + private def validateCredentials( + errorMessage: String, + record: PresentationRecord, + issuedCredentials: Seq[ValidIssuedCredentialRecord] + ) = { + for { _ <- ZIO.cond( - (issuedCredentials.map(_.subjectId).toSet.size == 1), + issuedCredentials.map(_.subjectId).toSet.size == 1, (), PresentationError.HolderBindingError( s"Creating a Verifiable Presentation for credential with different subject DID is not supported, found : ${issuedCredentials @@ -707,40 +707,11 @@ private class PresentationServiceImpl( signedCredentials.nonEmpty, signedCredentials, PresentationError.IssuedCredentialNotFoundError( - new Throwable(s"No matching issued credentials found in prover db from the given: $credentialsToUse") + new Throwable(errorMessage) ) ) ) - anoncredCredentialProofsV1AsJson <- ZIO - .fromEither( - AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) - ) - .mapError(error => - PresentationError.UnexpectedError( - s"Unable to serialize credentialsToUse. credentialsToUse:$credentialsToUse, error:$error" - ) - ) - count <- presentationRepository - .updateAnoncredPresentationWithCredentialsToUse( - recordId, - Option(AnoncredCredentialProofsV1.version), - Option(anoncredCredentialProofsV1AsJson), - ProtocolState.PresentationPending - ) - .mapError(RepositoryError.apply) @@ CustomMetricsAspect.startRecordingTime( - s"${record.id}_present_proof_flow_prover_presentation_pending_to_generated_ms_gauge" - ) - _ <- count match - case 1 => ZIO.succeed(()) - case n => ZIO.fail(RecordIdNotFound(recordId)) - record <- presentationRepository - .getPresentationRecord(recordId) - .mapError(RepositoryError.apply) - .flatMap { - case None => ZIO.fail(RecordIdNotFound(record.id)) - case Some(value) => ZIO.succeed(value) - } - } yield record + } yield () } override def acceptPresentation( @@ -804,7 +775,7 @@ private class PresentationServiceImpl( .mapError(RepositoryError.apply) _ <- count match case 1 => ZIO.succeed(()) - case n => ZIO.fail(RecordIdNotFound(recordId)) + case _ => ZIO.fail(RecordIdNotFound(recordId)) record <- presentationRepository .getPresentationRecord(record.id) .mapError(RepositoryError.apply) @@ -1076,13 +1047,7 @@ private class PresentationServiceImpl( case n => ZIO.fail(UnexpectedException(s"Invalid row count result: $n")) } .mapError(RepositoryError.apply) - record <- presentationRepository - .getPresentationRecord(id) - .mapError(RepositoryError.apply) - .flatMap { - case None => ZIO.fail(RecordIdNotFound(id)) - case Some(value) => ZIO.succeed(value) - } + record <- fetchPresentationRecord(id, 1) } yield record } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala index 6085681c06..b396ee05f5 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala @@ -225,36 +225,6 @@ object PresentationRepositorySpecSuite { assertTrue(records.exists(_.anoncredCredentialsToUseJsonSchemaId.contains(AnoncredCredentialProofsV1.version))) } }, - test("updatePresentationWithCredentialsToUse updates the record") { - for { - repo <- ZIO.service[PresentationRepository] - aRecord = presentationRecord - bRecord = presentationRecord - cRecord = presentationRecord - _ <- repo.createPresentationRecord(aRecord) - _ <- repo.createPresentationRecord(bRecord) - _ <- repo.createPresentationRecord(cRecord) - anoncredCredentialProofs = AnoncredCredentialProofsV1( - List(AnoncredCredentialProofV1("credential1", Seq("requestedAttribute"), Seq("requestedPredicate"))) - ) - anoncredCredentialProofsJson <- ZIO - .fromEither( - AnoncredCredentialProofsV1.schemaSerDes.serialize(anoncredCredentialProofs) - ) - - _ <- repo.updateAnoncredPresentationWithCredentialsToUse( - aRecord.id, - Some(AnoncredCredentialProofsV1.version), - Some(anoncredCredentialProofsJson), - ProtocolState.PresentationPending - ) - records <- repo.getPresentationRecord(aRecord.id) - } yield { - assertTrue(records.size == 1) && - assertTrue(records.exists(_.anoncredCredentialsToUse.contains(anoncredCredentialProofsJson))) && - assertTrue(records.exists(_.anoncredCredentialsToUseJsonSchemaId.contains(AnoncredCredentialProofsV1.version))) - } - }, test("updateCredentialRecordProtocolState updates the record") { for { repo <- ZIO.service[PresentationRepository] 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 f5f907d8a1..1032557c38 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 @@ -35,7 +35,7 @@ import java.util.{UUID, Base64 as JBase64} object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSpecHelper { - override def spec = + override def spec: Spec[Any, Any] = suite("PresentationService")(singleWalletSpec, multiWalletSpec).provide( presentationServiceLayer ++ genericSecretStorageLayer ) @@ -59,7 +59,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } yield Options(challenge, domain) check( - Gen.uuid.map(e => DidCommID(e.toString())), + Gen.uuid.map(e => DidCommID(e.toString)), Gen.option(Gen.string), Gen.listOfBounded(1, 5)(proofTypeGen), Gen.option(optionsGen) @@ -88,7 +88,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp assertTrue(record.requestPresentationData.get.body.goal_code.contains("Request Proof Presentation")) && assertTrue(record.requestPresentationData.get.body.proof_types == proofTypes) && assertTrue( - if (record.requestPresentationData.get.attachments.length != 0) { + if (record.requestPresentationData.get.attachments.nonEmpty) { val maybePresentationOptions = record.requestPresentationData.get.attachments.headOption .map(attachment => @@ -115,7 +115,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp }, test("createPresentationRecord creates a valid Anoncred PresentationRecord") { check( - Gen.uuid.map(e => DidCommID(e.toString())), + Gen.uuid.map(e => DidCommID(e.toString)), Gen.option(Gen.string), Gen.string, Gen.string, @@ -180,9 +180,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("getPresentationRecords returns created PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") - record1 <- svc.createJwtRecord() - record2 <- svc.createJwtRecord() + _ <- svc.createJwtRecord() + _ <- svc.createJwtRecord() records <- svc.getPresentationRecords(false) } yield { assertTrue(records.size == 2) @@ -209,7 +208,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("getPresentationRecord returns the correct record") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createJwtRecord() + _ <- svc.createJwtRecord() bRecord <- svc.createJwtRecord() record <- svc.getPresentationRecord(bRecord.id) } yield assertTrue(record.contains(bRecord)) @@ -217,8 +216,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("getPresentationRecord returns nothing for an unknown 'recordId'") { for { svc <- ZIO.service[PresentationService] - aRecord <- svc.createJwtRecord() - bRecord <- svc.createJwtRecord() + _ <- svc.createJwtRecord() + _ <- svc.createJwtRecord() record <- svc.getPresentationRecord(DidCommID()) } yield assertTrue(record.isEmpty) }, @@ -251,121 +250,14 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp }, 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) - file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") - credentialDefinitionId = "resource:///" + file.getFileName - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) - 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(), - schemaUri = Some(schemaId), - credentialDefinitionId = Some(credentialDefinitionDb.guid), - credentialDefinitionUri = Some(credentialDefinitionId), - 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(credentialDefinitionId = credentialDefinitionId) - repo <- ZIO.service[PresentationRepository] - credentialsToUse = - AnoncredCredentialProofsV1( - List( - AnoncredCredentialProofV1( - aIssueCredentialRecord.id.value, - Seq("sex"), - Seq("age") - ) - ) - ) - credentialsToUseJson <- ZIO.fromEither( - AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) - ) - _ <- repo.updateAnoncredPresentationWithCredentialsToUse( - aRecord.id, - Some(AnoncredPresentationV1.version), - Some(credentialsToUseJson), - PresentationRecord.ProtocolState.RequestPending - ) - issuer = createIssuer(DID("did:prism:issuer")) - aPresentationPayload <- svc.createAnoncredPresentationPayloadFromRecord( - aRecord.id, - issuer, - credentialsToUse, - Instant.now() - ) - validation <- AnoncredPresentationV1.schemaSerDes.validate(aPresentationPayload.data) - presentation <- AnoncredPresentationV1.schemaSerDes.deserialize(aPresentationPayload.data) + presentationWithRecord <- createAnoncredPresentation + (presentation, _) = presentationWithRecord + serializedPresentation <- presentation.attachments.head.data match { + case Base64(data) => ZIO.succeed(AnoncredPresentation(new String(JBase64.getUrlDecoder.decode(data)))) + case _ => ZIO.fail(InvalidAnoncredPresentation("Expecting Base64-encoded data")) + } + validation <- AnoncredPresentationV1.schemaSerDes.validate(serializedPresentation.data) + presentation <- AnoncredPresentationV1.schemaSerDes.deserialize(serializedPresentation.data) } yield { assert(validation)(isUnit) assert( @@ -375,132 +267,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp }, test("verify anoncred presentation") { for { - credentialDefinitionService <- ZIO.service[CredentialDefinitionService] - issuerId = "did:prism:issuer" - holderID = "did:prism:holder" - schemaId = "resource:///anoncred-presentation-schema-example.json" - credentialDefinitionDb <- credentialDefinitionService.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 - ) - ) - credentialDefinitionDb <- credentialDefinitionService - .getByGUID(credentialDefinitionDb.guid) - repo <- ZIO.service[CredentialRepository] - linkSecretService <- ZIO.service[LinkSecretService] - linkSecret <- linkSecretService.fetchOrCreate() - genericSecretStorage <- ZIO.service[GenericSecretStorage] - maybeCredentialDefintionPrivate <- - genericSecretStorage - .get[UUID, CredentialDefinitionSecret](credentialDefinitionDb.guid) - credentialDefinition = AnoncredCreateCredentialDefinition( - AnoncredCredentialDefinition(credentialDefinitionDb.definition.toString()), - AnoncredCredentialDefinitionPrivate(maybeCredentialDefintionPrivate.get.json.toString()), - AnoncredCredentialKeyCorrectnessProof(credentialDefinitionDb.keyCorrectnessProof.toString()) - ) - file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") - credentialDefinitionId = "resource:///" + file.getFileName - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) - credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) - processedCredential = - AnoncredLib.processCredential( - AnoncredLib - .createCredential( - credentialDefinition.cd, - credentialDefinition.cdPrivate, - credentialOffer, - credentialRequest.request, - Seq( - ("name", "Miguel"), - ("sex", "M"), - ("age", "31"), - ) - ), - credentialRequest.metadata, - linkSecret, - credentialDefinition.cd - ) - 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 = processedCredential.data.getBytes() - ) - ) - ) - aIssueCredentialRecord = - IssueCredentialRecord( - id = DidCommID(), - createdAt = Instant.now, - updatedAt = None, - thid = DidCommID(), - schemaUri = Some(schemaId), - credentialDefinitionId = Some(credentialDefinitionDb.guid), - credentialDefinitionUri = Some(credentialDefinitionId), - 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) + presentationWithRecord <- createAnoncredPresentation + (presentation, aRecord) = presentationWithRecord svc <- ZIO.service[PresentationService] - aRecord <- svc.createAnoncredRecord( - credentialDefinitionId = credentialDefinitionId - ) - repo <- ZIO.service[PresentationRepository] - credentialsToUse = - AnoncredCredentialProofsV1( - List( - AnoncredCredentialProofV1( - aIssueCredentialRecord.id.value, - Seq("sex"), - Seq("age") - ) - ) - ) - credentialsToUseJson <- ZIO.fromEither( - AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) - ) - _ <- - repo.updateAnoncredPresentationWithCredentialsToUse( - aRecord.id, - Some(AnoncredPresentationV1.version), - Some(credentialsToUseJson), - PresentationRecord.ProtocolState.RequestPending - ) - issuer = createIssuer(DID("did:prism:issuer")) - presentation <- svc.createAnoncredPresentation( - aRecord.requestPresentationData.get, - aRecord.id, - issuer, - credentialsToUse, - Instant.now() - ) _ <- svc.receivePresentation(presentation) validateRecord <- svc.verifyAnoncredPresentation( @@ -515,7 +284,6 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("markRequestPresentationSent returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") record <- svc.createJwtRecord() record <- svc.markRequestPresentationSent(record.id) @@ -526,7 +294,6 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("markRequestPresentationRejected returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( @@ -625,101 +392,71 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } yield { assertTrue(aRecord.connectionId == connectionId) && assertTrue(aRecord.protocolState == PresentationRecord.ProtocolState.RequestReceived) && - assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) + assertTrue(aRecord.requestPresentationData.contains(requestPresentation)) } }, test("receiveRequestPresentation Anoncred updates the RequestPresentation in PresentationRecord") { + val anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( + Map.empty, + Map.empty, + "name", + "nonce", + "version", + None + ) + val attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = + AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(anoncredPresentationRequestV1).getBytes() + ) + val connectionId = Some("connectionId") for { svc <- ZIO.service[PresentationService] - connectionId = Some("connectionId") - body = RequestPresentation.Body(goal_code = Some("Presentation Request")) - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") - anoncredPresentationRequestV1 = AnoncredPresentationRequestV1( - Map.empty, - Map.empty, - "name", - "nonce", - "version", - None - ) - attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( - mediaType = Some("application/json"), - format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = - AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(anoncredPresentationRequestV1).getBytes() - ) - requestPresentation = RequestPresentation( - body = body, - attachments = Seq(attachmentDescriptor), - to = prover, - from = verifier, - ) + requestPresentationWithRecord <- receiveRequestPresentationTest(attachmentDescriptor) + (_, requestPresentation) = requestPresentationWithRecord aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) } yield { assertTrue(aRecord.connectionId == connectionId) && assertTrue(aRecord.protocolState == PresentationRecord.ProtocolState.RequestReceived) && - assertTrue(aRecord.requestPresentationData == Some(requestPresentation)) + assertTrue(aRecord.requestPresentationData.contains(requestPresentation)) } }, test("receiveRequestPresentation Anoncred should fail given invalid attachment") { - for { - svc <- ZIO.service[PresentationService] - connectionId = Some("connectionId") - body = RequestPresentation.Body(goal_code = Some("Presentation Request")) - presentationAttachmentAsJson = - """{ - "challenge": "1f44d55f-f161-4938-a659-f8026467f126", - "domain": "us.gov/DriverLicense", - "credential_manifest": {} - }""" - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") - attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( - payload = presentationAttachmentAsJson, - format = Some(PresentCredentialProposeFormat.Anoncred.name) - ) - requestPresentation = RequestPresentation( - body = body, - attachments = Seq(attachmentDescriptor), - to = prover, - from = verifier, - ) - result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit + val presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" - } yield assert(result)( - fails(equalTo(InvalidAnoncredPresentationRequest("Expecting Base64-encoded data"))) + val attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( + payload = presentationAttachmentAsJson, + format = Some(PresentCredentialProposeFormat.Anoncred.name) + ) + for { + requestPresentation <- receiveRequestPresentationTest(attachmentDescriptor).exit + } yield assert(requestPresentation)( + fails(isSubtype[InvalidAnoncredPresentationRequest](anything)) ) }, test("receiveRequestPresentation Anoncred should fail given invalid anoncred format") { + val presentationAttachmentAsJson = + """{ + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "us.gov/DriverLicense", + "credential_manifest": {} + }""" + val attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( + mediaType = Some("application/json"), + format = Some(PresentCredentialRequestFormat.Anoncred.name), + payload = presentationAttachmentAsJson.getBytes() + ) for { - svc <- ZIO.service[PresentationService] - connectionId = Some("connectionId") - body = RequestPresentation.Body(goal_code = Some("Presentation Request")) - presentationAttachmentAsJson = - """{ - "challenge": "1f44d55f-f161-4938-a659-f8026467f126", - "domain": "us.gov/DriverLicense", - "credential_manifest": {} - }""" - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") - attachmentDescriptor = AttachmentDescriptor.buildBase64Attachment( - mediaType = Some("application/json"), - format = Some(PresentCredentialRequestFormat.Anoncred.name), - payload = presentationAttachmentAsJson.getBytes() - ) - requestPresentation = RequestPresentation( - body = body, - attachments = Seq(attachmentDescriptor), - to = prover, - from = verifier, - ) - result <- svc.receiveRequestPresentation(connectionId, requestPresentation).exit - - } yield assert(result)( + requestPresentation <- receiveRequestPresentationTest(attachmentDescriptor).exit + } yield assert(requestPresentation)( fails(isSubtype[InvalidAnoncredPresentationRequest](anything)) ) }, @@ -871,7 +608,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("markPresentationSent returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") + _ = DidId("did:peer:Prover") record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( @@ -894,7 +631,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } yield { assertTrue(aRecordReceived.id == aRecord.id) && - assertTrue(aRecordReceived.presentationData == Some(p)) + assertTrue(aRecordReceived.presentationData.contains(p)) } }, test("acceptPresentation updates the PresentationRecord") { @@ -909,10 +646,10 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp PresentationRecord.ProtocolState.PresentationReceived, PresentationRecord.ProtocolState.PresentationVerified ) - aRecordAccept <- svc.acceptPresentation(aRecord.id) + _ <- svc.acceptPresentation(aRecord.id) } yield { assertTrue(aRecordReceived.id == aRecord.id) && - assertTrue(aRecordReceived.presentationData == Some(p)) + assertTrue(aRecordReceived.presentationData.contains(p)) } }, test("markPresentationRejected updates the PresentationRecord") { @@ -930,7 +667,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp aRecordReject <- svc.markPresentationRejected(aRecord.id) } yield { assertTrue(aRecordReject.id == aRecord.id) && - assertTrue(aRecordReject.presentationData == Some(p)) && + assertTrue(aRecordReject.presentationData.contains(p)) && assertTrue(aRecordReject.protocolState == PresentationRecord.ProtocolState.PresentationRejected) } }, @@ -939,7 +676,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp svc <- ZIO.service[PresentationService] aRecord <- svc.createJwtRecord() p = presentation(aRecord.thid.value) - aRecordReceived <- svc.receivePresentation(p) + _ <- svc.receivePresentation(p) repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( aRecord.id, @@ -949,14 +686,13 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp aRecordReject <- svc.rejectPresentation(aRecord.id) } yield { assertTrue(aRecordReject.id == aRecord.id) && - assertTrue(aRecordReject.presentationData == Some(p)) && + assertTrue(aRecordReject.presentationData.contains(p)) && assertTrue(aRecordReject.protocolState == PresentationRecord.ProtocolState.PresentationRejected) } }, test("markPresentationGenerated returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") record <- svc.createJwtRecord() p = presentation(record.thid.value) repo <- ZIO.service[PresentationRepository] @@ -973,7 +709,6 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("markProposePresentationSent returns updated PresentationRecord") { for { svc <- ZIO.service[PresentationService] - pairwiseProverDid = DidId("did:peer:Prover") record <- svc.createJwtRecord() repo <- ZIO.service[PresentationRepository] _ <- repo.updatePresentationRecordProtocolState( @@ -994,7 +729,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp aRecordReceived <- svc.receiveProposePresentation(p) } yield { assertTrue(aRecordReceived.id == aRecord.id) && - assertTrue(aRecordReceived.proposePresentationData == Some(p)) + assertTrue(aRecordReceived.proposePresentationData.contains(p)) } }, test("acceptProposePresentation updates the PresentationRecord") { @@ -1009,14 +744,161 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp PresentationRecord.ProtocolState.ProposalPending, PresentationRecord.ProtocolState.ProposalReceived ) - aRecordAccept <- svc.acceptProposePresentation(aRecord.id) + _ <- svc.acceptProposePresentation(aRecord.id) } yield { assertTrue(aRecordReceived.id == aRecord.id) && - assertTrue(aRecordReceived.proposePresentationData == Some(p)) + assertTrue(aRecordReceived.proposePresentationData.contains(p)) } }, ).provideSomeLayer(ZLayer.succeed(WalletAccessContext(WalletId.random))) + private def receiveRequestPresentationTest(attachment: AttachmentDescriptor) = { + for { + svc <- ZIO.service[PresentationService] + connectionId = Some("connectionId") + body = RequestPresentation.Body(goal_code = Some("Presentation Request")) + prover = DidId("did:peer:Prover") + verifier = DidId("did:peer:Verifier") + attachmentDescriptor = attachment + requestPresentation = RequestPresentation( + body = body, + attachments = Seq(attachmentDescriptor), + to = prover, + from = verifier, + ) + requestPresentationRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) + } yield (requestPresentationRecord, requestPresentation) + } + + private def createAnoncredPresentation = { + for { + credentialDefinitionService <- ZIO.service[CredentialDefinitionService] + issuerId = "did:prism:issuer" + holderID = "did:prism:holder" + schemaId = "resource:///anoncred-presentation-schema-example.json" + credentialDefinitionDb <- credentialDefinitionService.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] + linkSecretService <- ZIO.service[LinkSecretService] + linkSecret <- linkSecretService.fetchOrCreate() + genericSecretStorage <- ZIO.service[GenericSecretStorage] + maybeCredentialDefintionPrivate <- + genericSecretStorage + .get[UUID, CredentialDefinitionSecret](credentialDefinitionDb.guid) + credentialDefinition = AnoncredCreateCredentialDefinition( + AnoncredCredentialDefinition(credentialDefinitionDb.definition.toString()), + AnoncredCredentialDefinitionPrivate(maybeCredentialDefintionPrivate.get.json.toString()), + AnoncredCredentialKeyCorrectnessProof(credentialDefinitionDb.keyCorrectnessProof.toString()) + ) + file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") + credentialDefinitionId = "resource:///" + file.getFileName + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) + credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) + processedCredential = + AnoncredLib.processCredential( + AnoncredLib + .createCredential( + credentialDefinition.cd, + credentialDefinition.cdPrivate, + credentialOffer, + credentialRequest.request, + Seq( + ("name", "Miguel"), + ("sex", "M"), + ("age", "31"), + ) + ), + credentialRequest.metadata, + linkSecret, + credentialDefinition.cd + ) + 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 = processedCredential.data.getBytes() + ) + ) + ) + aIssueCredentialRecord = + IssueCredentialRecord( + id = DidCommID(), + createdAt = Instant.now, + updatedAt = None, + thid = DidCommID(), + schemaUri = Some(schemaId), + credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialDefinitionUri = Some(credentialDefinitionId), + 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( + credentialDefinitionId = credentialDefinitionId + ) + repo <- ZIO.service[PresentationRepository] + credentialsToUse = + AnoncredCredentialProofsV1( + List( + AnoncredCredentialProofV1( + aIssueCredentialRecord.id.value, + Seq("sex"), + Seq("age") + ) + ) + ) + credentialsToUseJson <- ZIO.fromEither( + AnoncredCredentialProofsV1.schemaSerDes.serialize(credentialsToUse) + ) + _ <- + repo.updateAnoncredPresentationWithCredentialsToUse( + aRecord.id, + Some(AnoncredPresentationV1.version), + Some(credentialsToUseJson), + PresentationRecord.ProtocolState.RequestPending + ) + issuer = createIssuer(DID("did:prism:issuer")) + presentation <- svc.createAnoncredPresentation( + aRecord.requestPresentationData.get, + aRecord.id, + issuer, + credentialsToUse, + Instant.now() + ) + } yield (presentation, aRecord) + } + private val multiWalletSpec = suite("multi-wallet spec")( test("createPresentation for different wallet and isolate records") { From 94b02773c050eaaecd1b3a121c995fcf8da7a9cd Mon Sep 17 00:00:00 2001 From: Bassam Date: Thu, 22 Feb 2024 11:12:21 -0500 Subject: [PATCH 12/12] feat: Update documentation (#898) Signed-off-by: Bassam Riman --- docs/docusaurus/credentials/present-proof.md | 110 +++++++++++++++++- .../http/RequestPresentationAction.scala | 34 +----- .../http/RequestPresentationInput.scala | 52 ++++++++- 3 files changed, 159 insertions(+), 37 deletions(-) diff --git a/docs/docusaurus/credentials/present-proof.md b/docs/docusaurus/credentials/present-proof.md index 4509eac59b..9469c57af1 100644 --- a/docs/docusaurus/credentials/present-proof.md +++ b/docs/docusaurus/credentials/present-proof.md @@ -1,3 +1,6 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # Present proof The [Present Proof Protocol](/docs/concepts/glossary#present-proof-protocol) allows: @@ -57,6 +60,9 @@ To do this, he makes a `POST` request to the [`/present-proof/presentations`](/a 1. `connectionId`: This field represents the unique identifier of an existing connection between the verifier and the Holder/prover. It is for exchanging messages related to the protocol flow execution. 2. `challenge` and `domain`: The Verifier provides the random seed challenge and operational domain, and the Holder/Prover must sign the generated proof to protect from replay attacks. + + + ```bash curl -X 'POST' 'http://localhost:8070/prism-agent/present-proof/presentations' \ -H 'accept: application/json' \ @@ -72,6 +78,56 @@ curl -X 'POST' 'http://localhost:8070/prism-agent/present-proof/presentations' \ }' ``` + + + +```bash +curl -X 'POST' 'http://localhost:8070/prism-agent/present-proof/presentations' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "connectionId": "872ddfa9-4115-46c2-8a1b-22c24c7431d7", + "anoncredPresentationRequest": { + "requested_attributes": { + "attribute1": { + "name": "Attribute 1", + "restrictions": [ + { + "cred_def_id": "credential_definition_id_of_attribute1" + } + ], + "non_revoked": { + "from": 1635734400, + "to": 1735734400 + } + } + }, + "requested_predicates": { + "predicate1": { + "name": "age", + "p_type": ">=", + "p_value": 18, + "restrictions": [ + { + "schema_id": "schema_id_of_predicate1" + } + ], + "non_revoked": { + "from": 1635734400 + } + } + }, + "name": "Example Presentation Request", + "nonce": "1234567890", + "version": "1.0" + }, + "credentialFormat": "AnonCreds" + }' +``` + + + Upon execution, a new presentation request record gets created with an initial state of `RequestPending`. The Verifier PRISM Agent will send the presentation request message to the PRISM Agent of the Holder/Prover through the specified DIDComm connection. The record state then is updated to `RequestSent`. The Verifier can retrieve the list of presentation records by making a `GET` request to the [`/present-proof/presentations`](/agent-api/#tag/Present-Proof/operation/getAllPresentation) endpoint: @@ -121,6 +177,9 @@ curl -X 'GET' 'http://localhost:8090/prism-agent/present-proof/presentations' \ The Holder/Prover can then accept a specific request, generate the proof, and send it to the Verifier PRISM Agent by making a `PATCH` request to the [`/present-proof/presentations/{id}`](/agent-api/#tag/Present-Proof/operation/updatePresentation) endpoint: + + + ```bash curl -X 'PATCH' 'http://localhost:8090/prism-agent/present-proof/presentations/{PRESENTATION_ID}' \ -H 'Content-Type: application/json' \ @@ -133,8 +192,39 @@ curl -X 'PATCH' 'http://localhost:8090/prism-agent/present-proof/presentations/{ The Holder/Prover will have to provide the following information: 1. `presentationId`: The unique identifier of the presentation record to accept. -2. `proofId`: The unique identifier of the verifiable credential record to use as proof. +2. `proofId`: The unique identifier of the verifiable credential record to use as proof. + + + + +```bash +curl -X 'PATCH' 'http://localhost:8090/prism-agent/present-proof/presentations/{PRESENTATION_ID}' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "action": "request-accept", + "anoncredPresentationRequest":{ + "credentialProofs":[ + { + "credential":"3e849b98-f0fd-4cb4-ae96-9ea527a76267", + "requestedAttribute":[ + "age" + ], + "requestedPredicate":[ + "age" + ] + } + ] + } + }' +``` + + +The Holder/Prover will have to provide the following information: +1. `presentationId`: The unique identifier of the presentation record to accept. +2. `anoncredPresentationRequest`: A list of credential unique identifier with the attribute and predicate the credential is answering for. + The record state is updated to `PresentationPending` and processed by the Holder/Prover PRISM Agent. The agent will automatically generate the proof presentation, change the state to `PresentationGenerated`, and will eventually send it to the Verifier Agent, and change the state to `PresentationSent`. ```mermaid @@ -152,4 +242,20 @@ stateDiagram-v2 The following diagram shows the end-to-end flow for a verifier to request and verify a proof presentation from a Holder/prover. -![](present-proof-flow.png) \ No newline at end of file +### JWT Present Proof Flow Diagram +![](present-proof-flow.png) +### Anoncreds Present Proof Flow Diagram +![](anoncreds-present-proof-flow.png) + + + + +![](present-proof-flow.png) + + + + +![](anoncreds-present-proof-flow.png) + + + \ No newline at end of file diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala index 08488ddca4..114b08893a 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationAction.scala @@ -20,18 +20,6 @@ final case class RequestPresentationAction( anoncredPresentationRequest: Option[AnoncredCredentialProofsV1], ) -final case class AnoncredProof( - @description(annotations.credential.description) - @encodedExample(annotations.credential.example) - credential: String, - @description(annotations.requestedAttribute.description) - @encodedExample(annotations.requestedAttribute.example) - requestedAttribute: Seq[String], - @description(annotations.requestedPredicate.description) - @encodedExample(annotations.requestedPredicate.example) - requestedPredicate: Seq[String] -) - object RequestPresentationAction { object annotations { object action @@ -55,7 +43,7 @@ object RequestPresentationAction { ) object anoncredProof - extends Annotation[Option[Seq[AnoncredProof]]]( + extends Annotation[Option[AnoncredCredentialProofsV1]]( description = "A list of proofs from the Anoncred library, each corresponding to a credential.", example = None ) @@ -66,18 +54,6 @@ object RequestPresentationAction { "The unique identifier of the issue credential record - and hence VC - to use as the prover accepts the presentation request. Only applicable on the prover side when the action is `request-accept`.", example = "id" ) - - object requestedAttribute - extends Annotation[Seq[String]]( - description = "The unique identifier of attribute that the credential is expected to provide.", - example = Seq("Attribute1", "Attribute2") - ) - - object requestedPredicate - extends Annotation[Seq[String]]( - description = "The unique identifier of Predicate that the credential is expected to answer for.", - example = Seq("Predicate1", "Predicate2") - ) } given RequestPresentationActionEncoder: JsonEncoder[RequestPresentationAction] = @@ -86,16 +62,8 @@ object RequestPresentationAction { given RequestPresentationActionDecoder: JsonDecoder[RequestPresentationAction] = DeriveJsonDecoder.gen[RequestPresentationAction] - given AnoncredProofEncoder: JsonEncoder[AnoncredProof] = - DeriveJsonEncoder.gen[AnoncredProof] - - given AnoncredProofDecoder: JsonDecoder[AnoncredProof] = - DeriveJsonDecoder.gen[AnoncredProof] - given RequestPresentationActionSchema: Schema[RequestPresentationAction] = Schema.derived - given AnoncredProofSchema: Schema[AnoncredProof] = Schema.derived - import AnoncredCredentialProofsV1.given given Schema[AnoncredCredentialProofsV1] = Schema.derived diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala index dd460c4c25..9f3c677fa8 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/RequestPresentationInput.scala @@ -19,8 +19,8 @@ final case class RequestPresentationInput( @description(annotations.proofs.description) @encodedExample(annotations.proofs.example) proofs: Seq[ProofRequestAux], - @description(annotations.proofs.description) // TODO - @encodedExample(annotations.proofs.example) // TODO + @description(annotations.anoncredPresentationRequest.description) + @encodedExample(annotations.anoncredPresentationRequest.example) anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], @description(annotations.credentialFormat.description) @encodedExample(annotations.credentialFormat.example) @@ -46,6 +46,54 @@ object RequestPresentationInput { example = Seq.empty ) + object anoncredPresentationRequest + extends Annotation[Option[AnoncredPresentationRequestV1]]( + description = "Anoncred Presentation Request", + example = Some( + AnoncredPresentationRequestV1( + requested_attributes = Map( + "attribute1" -> AnoncredRequestedAttributeV1( + "Attribute 1", + List( + Map( + "cred_def_id" -> "credential_definition_id_of_attribute1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + Some(1735734400) + ) + ) + ) + ), + requested_predicates = Map( + "predicate1" -> + AnoncredRequestedPredicateV1( + "Predicate 1", + ">=", + 18, + List( + Map( + "schema_id" -> "schema_id_of_predicate1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + None + ) + ) + ) + ), + name = "Example Presentation Request", + nonce = "1234567890", + version = "1.0", + non_revoked = None + ) + ) + ) + object credentialFormat extends Annotation[Option[String]]( description = "The credential format (default to 'JWT')",