diff --git a/build.sbt b/build.sbt index 9307ab6f20..5efc89ffd8 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ inThisBuild( // scalacOptions += "-Ysafe-init", // scalacOptions += "-Werror", // <=> "-Xfatal-warnings" scalacOptions += "-Dquill.macro.log=false", // disable quill macro logs // TODO https://github.com/zio/zio-protoquill/issues/470, - scalacOptions ++= Seq("-Xmax-inlines", "50") // manually increase max-inlines above 32 (https://github.com/circe/circe/issues/2162) + scalacOptions ++= Seq("-Xmax-inlines", "50") ) ) @@ -58,9 +58,6 @@ lazy val V = new { val mockito = "3.2.18.0" val monocle = "3.2.0" - // https://mvnrepository.com/artifact/io.circe/circe-core - val circe = "0.14.7" - val tapir = "1.11.7" // scala-steward:off // TODO "1.10.5" val http4sBlaze = "0.23.15" // scala-steward:off // TODO "0.23.16" @@ -80,7 +77,7 @@ lazy val V = new { val scalaUri = "4.0.3" - val jwtCirceVersion = "9.4.6" + val jwtZioVersion = "9.4.6" val zioPreludeVersion = "1.0.0-RC31" val apollo = "1.3.5" @@ -123,12 +120,8 @@ lazy val D = new { val zioConfigMagnolia: ModuleID = "dev.zio" %% "zio-config-magnolia" % V.zioConfig val zioConfigTypesafe: ModuleID = "dev.zio" %% "zio-config-typesafe" % V.zioConfig - val circeCore: ModuleID = "io.circe" %% "circe-core" % V.circe - val circeGeneric: ModuleID = "io.circe" %% "circe-generic" % V.circe - val circeParser: ModuleID = "io.circe" %% "circe-parser" % V.circe - val networkntJsonSchemaValidator = "com.networknt" % "json-schema-validator" % V.jsonSchemaValidator - val jwtCirce = "com.github.jwt-scala" %% "jwt-circe" % V.jwtCirceVersion + val jwtZio = "com.github.jwt-scala" %% "jwt-zio-json" % V.jwtZioVersion val jsonCanonicalization: ModuleID = "io.github.erdtman" % "java-json-canonicalization" % "1.1" val titaniumJsonLd: ModuleID = "com.apicatalog" % "titanium-json-ld" % "1.4.0" val jakartaJson: ModuleID = "org.glassfish" % "jakarta.json" % "2.0.1" @@ -156,7 +149,6 @@ 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 @@ -185,7 +177,7 @@ lazy val D = new { // LIST of Dependencies val doobieDependencies: Seq[ModuleID] = - Seq(doobiePostgres, doobiePostgresCirce, doobieHikari, flyway) + Seq(doobiePostgres, doobieHikari, flyway) } lazy val D_Shared = new { @@ -210,9 +202,6 @@ lazy val D_SharedJson = new { Seq( D.zio, D.zioJson, - D.circeCore, - D.circeGeneric, - D.circeParser, D.jsonCanonicalization, D.titaniumJsonLd, D.jakartaJson, @@ -274,9 +263,6 @@ lazy val D_Castor = new { D.zioMock, D.zioTestSbt, D.zioTestMagnolia, - D.circeCore, - D.circeGeneric, - D.circeParser ) // Project Dependencies @@ -343,7 +329,7 @@ lazy val D_Pollux_VC_JWT = new { // Dependency Modules val zioDependencies: Seq[ModuleID] = Seq(zio, zioPrelude, zioTest, zioTestSbt, zioTestMagnolia) val baseDependencies: Seq[ModuleID] = - zioDependencies :+ D.jwtCirce :+ D.networkntJsonSchemaValidator :+ D.nimbusJwt :+ D.scalaTest + zioDependencies :+ D.jwtZio :+ D.networkntJsonSchemaValidator :+ D.nimbusJwt :+ D.scalaTest // Project Dependencies lazy val polluxVcJwtDependencies: Seq[ModuleID] = baseDependencies @@ -426,7 +412,7 @@ lazy val D_CloudAgent = new { lazy val keyManagementDependencies: Seq[ModuleID] = baseDependencies ++ D.doobieDependencies ++ Seq(D.zioCatsInterop, D.zioMock, vaultDriver) - lazy val iamDependencies: Seq[ModuleID] = Seq(keycloakAuthz, D.jwtCirce) + lazy val iamDependencies: Seq[ModuleID] = Seq(keycloakAuthz, D.jwtZio) lazy val serverDependencies: Seq[ModuleID] = baseDependencies ++ tapirDependencies ++ postgresDependencies ++ Seq( @@ -533,13 +519,7 @@ lazy val models = project .configure(commonConfigure) .settings(name := "mercury-data-models") .settings( - libraryDependencies ++= Seq(D.zio), - libraryDependencies ++= Seq( - D.circeCore, - D.circeGeneric, - D.circeParser - ), // TODO try to remove this from this module - // libraryDependencies += D.didScala + libraryDependencies ++= Seq(D.zio) ) .settings(libraryDependencies += D.nimbusJwt) // FIXME just for the DidAgent .dependsOn(shared) @@ -561,7 +541,6 @@ lazy val protocolConnection = project .configure(commonConfigure) .settings(name := "mercury-protocol-connection") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models, protocolInvitation) @@ -570,7 +549,6 @@ lazy val protocolCoordinateMediation = project .configure(commonConfigure) .settings(name := "mercury-protocol-coordinate-mediation") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models) @@ -579,7 +557,6 @@ lazy val protocolDidExchange = project .configure(commonConfigure) .settings(name := "mercury-protocol-did-exchange") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .dependsOn(models, protocolInvitation) lazy val protocolInvitation = project @@ -589,9 +566,6 @@ lazy val protocolInvitation = project .settings(libraryDependencies += D.zio) .settings( libraryDependencies ++= Seq( - D.circeCore, - D.circeGeneric, - D.circeParser, D.munit, D.munitZio ) @@ -611,7 +585,6 @@ lazy val protocolLogin = project .settings(name := "mercury-protocol-outofband-login") .settings(libraryDependencies += D.zio) .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models) @@ -634,7 +607,6 @@ lazy val protocolIssueCredential = project .configure(commonConfigure) .settings(name := "mercury-protocol-issue-credential") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models, protocolInvitation) @@ -643,7 +615,6 @@ lazy val protocolRevocationNotification = project .configure(commonConfigure) .settings(name := "mercury-protocol-revocation-notification") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models) @@ -652,7 +623,6 @@ lazy val protocolPresentProof = project .configure(commonConfigure) .settings(name := "mercury-protocol-present-proof") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models, protocolInvitation) @@ -667,7 +637,6 @@ lazy val protocolTrustPing = project .configure(commonConfigure) .settings(name := "mercury-protocol-trust-ping") .settings(libraryDependencies += D.zio) - .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) .dependsOn(models) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala index 3eb78b2d0b..8ca2c9e555 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala @@ -10,14 +10,13 @@ import org.hyperledger.identus.pollux.core.model.{CredInStatusList, CredentialSt import org.hyperledger.identus.pollux.core.service.{CredentialService, CredentialStatusListService} import org.hyperledger.identus.pollux.vc.jwt.revocation.{BitString, VCStatusList2021, VCStatusList2021Error} import org.hyperledger.identus.resolvers.DIDResolver -import org.hyperledger.identus.shared.json.JsonInterop import org.hyperledger.identus.shared.messaging import org.hyperledger.identus.shared.messaging.{Message, Producer, WalletIdAndRecordId} import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* +import zio.json.{DecoderOps, EncoderOps} import zio.json.ast.Json -import zio.json.DecoderOps import zio.metrics.Metric import java.util.UUID @@ -78,7 +77,7 @@ object StatusListJobs extends BackgroundJobsHelper { vcStatusListCredJson <- ZIO.fromEither(vcStatusListCredString.fromJson[Json]) issuer <- createJwtVcIssuer(statusListWithCreds.issuer, VerificationRelationship.AssertionMethod, None) vcStatusListCred <- VCStatusList2021 - .decodeFromJson(JsonInterop.toCirceJsonAst(vcStatusListCredJson), issuer) + .decodeFromJson(vcStatusListCredJson, issuer) .mapError(x => new Throwable(x.msg)) bitString <- vcStatusListCred.getBitString.mapError(x => new Throwable(x.msg)) _ <- ZIO.collectAll( @@ -99,7 +98,7 @@ object StatusListJobs extends BackgroundJobsHelper { case VCStatusList2021Error.EncodingError(msg: String) => new Throwable(msg) case VCStatusList2021Error.DecodingError(msg: String) => new Throwable(msg) } - vcStatusListCredJsonString <- updatedVcStatusListCred.toJsonWithEmbeddedProof.map(_.spaces2) + vcStatusListCredJsonString <- updatedVcStatusListCred.toJsonWithEmbeddedProof.map(_.toJson) _ <- credentialStatusListService.updateStatusListCredential( statusListWithCreds.id, vcStatusListCredJsonString diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticator.scala index 146253739a..29c92ba154 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticator.scala @@ -7,7 +7,7 @@ import org.hyperledger.identus.iam.authentication.AuthenticationError.{ InvalidCredentials } import org.hyperledger.identus.shared.utils.Traverse.* -import pdi.jwt.{JwtCirce, JwtClaim, JwtOptions} +import pdi.jwt.{JwtClaim, JwtOptions, JwtZIOJson} import zio.* import zio.json.ast.Json @@ -61,7 +61,7 @@ final class AccessToken private (token: String, claims: JwtClaim, rolesClaimPath object AccessToken { def fromString(token: String, rolesClaimPath: Seq[String] = Nil): Either[String, AccessToken] = - JwtCirce + JwtZIOJson .decode(token, JwtOptions(false, false, false)) .map(claims => AccessToken(token, claims, rolesClaimPath)) .toEither diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala index b72954d33a..c9ac3373b6 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala @@ -5,7 +5,6 @@ import org.hyperledger.identus.castor.core.model.did.{DID, DIDUrl, PrismDID, Ver import org.hyperledger.identus.oid4vci.domain.{IssuanceSession, Openid4VCIProofJwtOps} import org.hyperledger.identus.oid4vci.http.* import org.hyperledger.identus.oid4vci.storage.IssuanceSessionStorage -import org.hyperledger.identus.pollux.core.model.primitives.UriString import org.hyperledger.identus.pollux.core.model.primitives.UriString.toUriString import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.core.service.{ @@ -23,7 +22,6 @@ import org.hyperledger.identus.pollux.vc.jwt.{ * } import org.hyperledger.identus.shared.http.UriResolver -import org.hyperledger.identus.shared.json.JsonInterop import org.hyperledger.identus.shared.models.* import zio.* import zio.json.ast.Json @@ -206,7 +204,7 @@ case class OIDCCredentialIssuerServiceImpl( issuanceDate = Instant.now(), maybeExpirationDate = None, // TODO: Add expiration date maybeCredentialSchema = None, // TODO: Add schema from schema registry - credentialSubject = JsonInterop.toCirceJsonAst(buildCredentialSubject(subjectDid, claims)), + credentialSubject = buildCredentialSubject(subjectDid, claims), maybeCredentialStatus = None, // TODO: Add credential status maybeRefreshService = None, // TODO: Add refresh service maybeEvidence = None, // TODO: Add evidence diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala index 7d37bbb9c3..98874131a7 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala @@ -7,8 +7,6 @@ import org.hyperledger.identus.api.http.ErrorResponse import org.hyperledger.identus.castor.core.model.did.{DIDData, DIDMetadata, PrismDIDOperation, VerificationRelationship} import org.hyperledger.identus.castor.core.service.MockDIDService import org.hyperledger.identus.connect.core.model.ConnectionRecord -import org.hyperledger.identus.connect.core.model.ConnectionRecord.ProtocolState -import org.hyperledger.identus.connect.core.service import org.hyperledger.identus.connect.core.service.MockConnectionService import org.hyperledger.identus.container.util.MigrationAspects.migrate import org.hyperledger.identus.iam.authentication.AuthenticatorWithAuthZ @@ -21,16 +19,16 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.protocol.connection.ConnectionResponse import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, IssueCredentialRecord} -import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import org.hyperledger.identus.pollux.core.repository.CredentialDefinitionRepositoryInMemory import org.hyperledger.identus.pollux.core.service.{CredentialDefinitionServiceImpl, MockCredentialService} import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.shared.models.{KeyId, WalletId} -import sttp.client3.{basicRequest, DeserializationException, UriContext} +import sttp.client3.{basicRequest, UriContext} import sttp.client3.ziojson.* import sttp.model.StatusCode import zio.* -import zio.json.EncoderOps +import zio.json.{DecoderOps, EncoderOps} +import zio.json.ast.Json import zio.mock.Expectation import zio.test.* import zio.test.Assertion.* @@ -54,7 +52,7 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo schemaId = Some("mySchemaId"), credentialDefinitionId = Some(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")), credentialFormat = Some("JWT"), - claims = json.toJsonAST.toOption.get, + claims = json.fromJson[Json].toOption.get, automaticIssuance = Some(true), issuingDID = "did:prism:332518729a7b7805f73a788e0944802527911901d9b7c16152281be9bc62d944:CosBCogBEkkKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESi4KCXNlY3AyNTZrMRIhAuYoRIefsLhkvYwHz8gDtkG2b0kaZTDOLj_SExWX1fOXEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLOzab8f0ibt1P0zdMfoWDQTSlPc8_tkV9Jk5BBsXB8fA", diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala index 5847996fa5..1472480216 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerImplSpec.scala @@ -1,12 +1,10 @@ package org.hyperledger.identus.verification.controller import org.hyperledger.identus.agent.walletapi.model.BaseEntity -import org.hyperledger.identus.agent.walletapi.service.{ManagedDIDService, MockManagedDIDService} +import org.hyperledger.identus.agent.walletapi.service.MockManagedDIDService import org.hyperledger.identus.castor.core.service.MockDIDService import org.hyperledger.identus.iam.authentication.AuthenticatorWithAuthZ import org.hyperledger.identus.pollux.vc.jwt.* -import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* -import org.hyperledger.identus.shared.json.JsonInterop import org.hyperledger.identus.verification.controller.http.* import sttp.client3.{basicRequest, DeserializationException, Response, UriContext} import sttp.client3.ziojson.* @@ -47,12 +45,10 @@ object VcVerificationControllerImplSpec extends ZIOSpecDefault with VcVerificati `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -73,7 +69,7 @@ object VcVerificationControllerImplSpec extends ZIOSpecDefault with VcVerificati maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(io.circe.syntax.EncoderOps(jwtCredentialPayload).asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) authenticator <- ZIO.service[AuthenticatorWithAuthZ[BaseEntity]] backend = httpBackend(vcVerificationController, authenticator) request = List( diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala index 6d8cdbb50d..40c81dd49a 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala @@ -11,6 +11,7 @@ import org.hyperledger.identus.pollux.vc.jwt.revocation.BitStringError.{ import org.hyperledger.identus.pollux.vc.jwt.Issuer import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* +import zio.json.EncoderOps import java.util.UUID @@ -36,7 +37,7 @@ trait CredentialStatusListRepository { .mapError(x => new Throwable(x.msg)) credentialWithEmbeddedProof <- emptyStatusListCredential.toJsonWithEmbeddedProof - } yield credentialWithEmbeddedProof.spaces2 + } yield credentialWithEmbeddedProof.toJson } def getCredentialStatusListIds: UIO[Seq[(WalletId, UUID)]] diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala index 6f9e0fb47f..c21c1eb2fe 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala @@ -22,10 +22,8 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.prex.{ClaimFormat, Jwt, PresentationDefinition} import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} -import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.UriResolver -import org.hyperledger.identus.shared.json.JsonInterop import org.hyperledger.identus.shared.messaging.{Producer, WalletIdAndRecordId} import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.models.Failure.orDieAsUnmanagedFailure @@ -1219,7 +1217,7 @@ class CredentialServiceImpl( ids.map(id => org.hyperledger.identus.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)) ), maybeCredentialStatus = Some(credentialStatus), - credentialSubject = JsonInterop.toCirceJsonAst(claims.add("id", Json.Str(jwtPresentation.iss))), + credentialSubject = claims.add("id", Json.Str(jwtPresentation.iss)), maybeRefreshService = None, maybeEvidence = None, maybeTermsOfUse = None, @@ -1455,7 +1453,7 @@ class CredentialServiceImpl( .fromOption(offer.attachments.headOption) .orElse(ZIO.dieMessage(s"Attachments not found in record: ${record.id}")) json <- attachmentDescriptor.data match - case JsonData(json) => ZIO.succeed(json.toJsonAST.toOption.get) + case JsonData(json) => ZIO.succeed(json) case _ => ZIO.dieMessage(s"Attachment doesn't contain JsonData: ${record.id}") maybeOptions <- ZIO .fromEither(json.as[PresentationAttachment].map(_.options)) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala index f944750108..53258b6ef5 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala @@ -3,7 +3,6 @@ package org.hyperledger.identus.pollux.core.service.verification import org.hyperledger.identus.pollux.core.model.primitives.UriString import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.vc.jwt.{ - CredentialPayload, CredentialSchema as JwtCredentialSchema, DidResolver, JWT, @@ -12,6 +11,7 @@ import org.hyperledger.identus.pollux.vc.jwt.{ } import org.hyperledger.identus.shared.http.UriResolver import zio.* +import zio.json.EncoderOps import java.time.OffsetDateTime @@ -123,7 +123,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriResolver: UriResolv CredentialSchema .validateJWTCredentialSubject( schemaUri, - CredentialPayload.Implicits.jwtVcEncoder(decodedJwt.vc).noSpaces, + decodedJwt.vc.toJson, uriResolver ) .mapError(error => diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolverSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolverSpec.scala index 3527948e64..4d2523bccc 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolverSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolverSpec.scala @@ -3,7 +3,7 @@ package org.hyperledger.identus.pollux.core.service.uriResolvers import io.lemonlabs.uri.Url import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.crypto.Sha256Hash -import org.hyperledger.identus.shared.json.{Json as JsonUtils, JsonInterop} +import org.hyperledger.identus.shared.json.Json as JsonUtils import org.hyperledger.identus.shared.models.PrismEnvelopeData import org.hyperledger.identus.shared.utils.Base64Utils import zio.* @@ -155,7 +155,7 @@ object DidUrlResolverSpec extends ZIOSpecDefault { Service( id = "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a#agent-base-url", `type` = "LinkedResourceV1", - serviceEndpoint = JsonInterop.toCirceJsonAst(Json.Str("https://agent-url.com")) + serviceEndpoint = Json.Str("https://agent-url.com") ) ) ), diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala index 8f42b8aa32..d0b2e2d66c 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala @@ -1,15 +1,13 @@ package org.hyperledger.identus.pollux.core.service.verification -import io.circe.syntax.EncoderOps import org.hyperledger.identus.agent.walletapi.service.MockManagedDIDService import org.hyperledger.identus.castor.core.service.MockDIDService import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.vc.jwt.* -import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* -import org.hyperledger.identus.shared.json.JsonInterop import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* import zio.json.ast.Json +import zio.json.EncoderOps import zio.test.* import zio.Config.OffsetDateTime @@ -39,12 +37,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -65,7 +61,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -106,12 +102,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -132,7 +126,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -173,12 +167,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -199,7 +191,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -240,12 +232,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -266,7 +256,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -314,12 +304,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -340,7 +328,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -385,12 +373,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Alice"), - "age" -> Json.Num(42), - "email" -> Json.Str("alice@wonderland.com") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Alice"), + "age" -> Json.Num(42), + "email" -> Json.Str("alice@wonderland.com") ), maybeCredentialStatus = Some( CredentialStatus( @@ -411,7 +397,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -462,15 +448,13 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS ) ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Alice"), - "age" -> Json.Num(42), - "email" -> Json.Str("alice@wonderland.com"), - "dateOfIssuance" -> Json.Str("2000-01-01T10:00:00Z"), - "drivingLicenseID" -> Json.Num(12345), - "drivingClass" -> Json.Str("5") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Alice"), + "age" -> Json.Num(42), + "email" -> Json.Str("alice@wonderland.com"), + "dateOfIssuance" -> Json.Str("2000-01-01T10:00:00Z"), + "drivingLicenseID" -> Json.Num(12345), + "drivingClass" -> Json.Str("5") ), maybeCredentialStatus = Some( CredentialStatus( @@ -491,7 +475,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -542,15 +526,13 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS ) ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Alice"), - "age" -> Json.Num(42), - "email" -> Json.Str("alice@wonderland.com"), - "dateOfIssuance" -> Json.Str("2000-01-01T10:00:00Z"), - "drivingLicenseID" -> Json.Num(12345), - "drivingClass" -> Json.Num(5) - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Alice"), + "age" -> Json.Num(42), + "email" -> Json.Str("alice@wonderland.com"), + "dateOfIssuance" -> Json.Str("2000-01-01T10:00:00Z"), + "drivingLicenseID" -> Json.Num(12345), + "drivingClass" -> Json.Num(5) ), maybeCredentialStatus = Some( CredentialStatus( @@ -571,7 +553,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -617,12 +599,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -643,7 +623,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -685,12 +665,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -711,7 +689,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -753,12 +731,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -779,7 +755,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( @@ -821,12 +797,10 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS `type` = "JsonSchemaValidator2018" ) ), - credentialSubject = JsonInterop.toCirceJsonAst( - Json.Obj( - "userName" -> Json.Str("Bob"), - "age" -> Json.Num(42), - "email" -> Json.Str("email") - ) + credentialSubject = Json.Obj( + "userName" -> Json.Str("Bob"), + "age" -> Json.Num(42), + "email" -> Json.Str("email") ), maybeCredentialStatus = Some( CredentialStatus( @@ -847,7 +821,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS maybeTermsOfUse = Option.empty, aud = Set(verifier) ).toJwtCredentialPayload - signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) result <- svc.verify( List( diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala index e6bb34938c..fe801b3eca 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala @@ -4,7 +4,7 @@ import com.networknt.schema.{JsonSchema, SpecVersion} import org.hyperledger.identus.shared.json.{JsonPath, JsonPathError, JsonSchemaError, JsonSchemaUtils} import zio.* import zio.json.{JsonDecoder, JsonEncoder} -import zio.json.ast.Json as ZioJson +import zio.json.ast.Json opaque type JsonPathValue = String @@ -20,16 +20,16 @@ object JsonPathValue { } } -opaque type FieldFilter = ZioJson +opaque type FieldFilter = Json object FieldFilter { - given Conversion[ZioJson, FieldFilter] = identity + given Conversion[Json, FieldFilter] = identity - given JsonEncoder[FieldFilter] = ZioJson.encoder - given JsonDecoder[FieldFilter] = ZioJson.decoder + given JsonEncoder[FieldFilter] = Json.encoder + given JsonDecoder[FieldFilter] = Json.decoder extension (f: FieldFilter) - def asJsonZio: ZioJson = f + def asJsonZio: Json = f // Json schema draft 7 must be used // https://identity.foundation/presentation-exchange/spec/v2.1.1/#json-schema diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala index 067d83506a..63eafb90c7 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala @@ -2,12 +2,11 @@ package org.hyperledger.identus.pollux.prex import org.hyperledger.identus.pollux.prex.PresentationSubmissionError.* import org.hyperledger.identus.pollux.vc.jwt.{JWT, JwtCredential, JwtPresentation, JwtPresentationPayload} -import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* -import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* -import org.hyperledger.identus.shared.json.{JsonInterop, JsonPathError, JsonSchemaValidatorImpl} +import org.hyperledger.identus.shared.json.{JsonPathError, JsonSchemaValidatorImpl} import org.hyperledger.identus.shared.models.{Failure, StatusCode} import zio.* -import zio.json.ast.Json as ZioJson +import zio.json.ast.Json +import zio.json.EncoderOps sealed trait PresentationSubmissionError extends Failure { override def namespace: String = "PresentationSubmissionError" @@ -82,7 +81,7 @@ object PresentationSubmissionVerification { def verify( pd: PresentationDefinition, ps: PresentationSubmission, - rootTraversalObject: ZioJson, + rootTraversalObject: Json, )(formatVerification: ClaimFormatVerification): IO[PresentationSubmissionError, Unit] = { for { _ <- verifySubmissionId(pd, ps) @@ -119,7 +118,7 @@ object PresentationSubmissionVerification { private def verifyInputConstraints( pd: PresentationDefinition, - entries: Seq[(String, ZioJson)] + entries: Seq[(String, Json)] ): IO[PresentationSubmissionError, Unit] = { val descriptorLookup = pd.input_descriptors.map(d => d.id -> d).toMap val descriptorWithEntry = entries.flatMap { case (id, entry) => descriptorLookup.get(id).map(_ -> entry) } @@ -132,7 +131,7 @@ object PresentationSubmissionVerification { private def verifyInputConstraint( descriptor: InputDescriptor, - entry: ZioJson + entry: Json ): IO[PresentationSubmissionError, Unit] = { val mandatoryFields = descriptor.constraints.fields .getOrElse(Nil) @@ -157,9 +156,9 @@ object PresentationSubmissionVerification { } private def extractSubmissionEntry( - traversalObject: ZioJson, + traversalObject: Json, descriptor: InputDescriptorMapping - )(formatVerification: ClaimFormatVerification): IO[PresentationSubmissionError, ZioJson] = { + )(formatVerification: ClaimFormatVerification): IO[PresentationSubmissionError, Json] = { for { path <- ZIO .fromEither(descriptor.path.toJsonPath) @@ -180,9 +179,9 @@ object PresentationSubmissionVerification { } private def verifyJwtVc( - json: ZioJson, + json: Json, path: JsonPathValue - )(formatVerification: JWT => IO[String, Unit]): IO[PresentationSubmissionError, ZioJson] = { + )(formatVerification: JWT => IO[String, Unit]): IO[PresentationSubmissionError, Json] = { val format = ClaimFormatValue.jwt_vc for { jwt <- ZIO @@ -194,13 +193,13 @@ object PresentationSubmissionVerification { .mapError(e => ClaimDecodeFailure(format, path, e)) _ <- formatVerification(jwt) .mapError(errors => ClaimFormatVerificationFailure(format, path, errors.mkString)) - } yield JsonInterop.toZioJsonAst(io.circe.syntax.EncoderOps(payload).asJson) + } yield payload.toJsonAST.toOption.get } private def verifyJwtVp( - json: ZioJson, + json: Json, path: JsonPathValue - )(formatVerification: JWT => IO[String, Unit]): IO[PresentationSubmissionError, ZioJson] = { + )(formatVerification: JWT => IO[String, Unit]): IO[PresentationSubmissionError, Json] = { val format = ClaimFormatValue.jwt_vp for { jwt <- ZIO @@ -212,6 +211,6 @@ object PresentationSubmissionVerification { .mapError(e => ClaimDecodeFailure(format, path, e.getMessage())) _ <- formatVerification(jwt) .mapError(errors => ClaimFormatVerificationFailure(format, path, errors.mkString)) - } yield JsonInterop.toZioJsonAst(io.circe.syntax.EncoderOps(payload).asJson) + } yield payload.toJsonAST.toOption.get } } diff --git a/pollux/prex/src/test/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerificationSpec.scala b/pollux/prex/src/test/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerificationSpec.scala index 75ff38eec9..1125395447 100644 --- a/pollux/prex/src/test/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerificationSpec.scala +++ b/pollux/prex/src/test/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerificationSpec.scala @@ -4,7 +4,6 @@ import org.hyperledger.identus.castor.core.model.did.DID import org.hyperledger.identus.pollux.prex.PresentationSubmissionError.* import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.crypto.Apollo -import org.hyperledger.identus.shared.json.JsonInterop import zio.* import zio.json.{DecoderOps, JsonDecoder} import zio.json.ast.Json @@ -49,7 +48,7 @@ object PresentationSubmissionVerificationSpec extends ZIOSpecDefault { `@context` = Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), maybeCredentialSchema = None, - credentialSubject = JsonInterop.toCirceJsonAst(subject), + credentialSubject = subject, maybeCredentialStatus = None, maybeRefreshService = None, maybeEvidence = None, diff --git a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala index 42ce9bae4f..4fb19e0dbb 100644 --- a/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala +++ b/pollux/sd-jwt/src/main/scala/org/hyperledger/identus/pollux/sdjwt/SDJWT.scala @@ -3,6 +3,7 @@ package org.hyperledger.identus.pollux.sdjwt import sdjwtwrapper.* import zio.json.* import zio.json.ast.Json +import zio.json.internal.Write import scala.util.{Failure, Success, Try} @@ -50,12 +51,10 @@ object SDJWT { claimsMap: Map[String, String], ): CredentialCompact = { - given encoder: JsonEncoder[String | Int] = new JsonEncoder[String | Int] { - override def unsafeEncode(b: String | Int, indent: Option[Int], out: zio.json.internal.Write): Unit = { - b match { - case obj: String => JsonEncoder.string.unsafeEncode(obj, indent, out) - case obj: Int => JsonEncoder.int.unsafeEncode(obj, indent, out) - } + given encoder: JsonEncoder[String | Int] = (b: String | Int, indent: Option[Int], out: Write) => { + b match { + case obj: String => JsonEncoder.string.unsafeEncode(obj, indent, out) + case obj: Int => JsonEncoder.int.unsafeEncode(obj, indent, out) } } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidJWT.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidJWT.scala index d2590bb512..1d21906ab5 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidJWT.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidJWT.scala @@ -5,10 +5,11 @@ import com.nimbusds.jose.crypto.{ECDSASigner, Ed25519Signer} import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.jwk.{Curve, ECKey} import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT} -import io.circe.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1PrivateKey} import org.hyperledger.identus.shared.models.KeyId import zio.* +import zio.json.{EncoderOps, JsonDecoder, JsonEncoder} +import zio.json.ast.Json import java.security.* import java.security.interfaces.ECPublicKey @@ -21,6 +22,9 @@ object JWT { extension (jwt: JWT) { def value: String = jwt } + + given JsonEncoder[JWT] = JsonEncoder.string.contramap(jwt => jwt.value) + given JsonDecoder[JWT] = JsonDecoder.string.map(JWT(_)) } object JwtSignerImplicits { @@ -61,7 +65,7 @@ class ES256KSigner(privateKey: PrivateKey, keyId: Option[KeyId] = None) extends } override def encode(claim: Json): JWT = { - val claimSet = JWTClaimsSet.parse(claim.noSpaces) + val claimSet = JWTClaimsSet.parse(claim.toJson) val signedJwt = SignedJWT( keyId .map(kid => new JWSHeader.Builder(JWSAlgorithm.ES256K).`type`(JOSEObjectType.JWT).keyID(kid.value)) @@ -85,7 +89,7 @@ class EdSigner(ed25519KeyPair: Ed25519KeyPair, keyId: Option[KeyId] = None) exte } override def encode(claim: Json): JWT = { - val claimSet = JWTClaimsSet.parse(claim.noSpaces) + val claimSet = JWTClaimsSet.parse(claim.toJson) val signedJwt = SignedJWT( keyId diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidResolver.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidResolver.scala index 49a3230f3e..cdc7dc4945 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidResolver.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/DidResolver.scala @@ -1,18 +1,9 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe.Json -import org.hyperledger.identus.castor.core.model.did.w3c.{ - makeW3CResolver, - DIDDocumentRepr, - DIDResolutionErrorRepr, - PublicKeyJwk, - PublicKeyRepr, - PublicKeyReprOrRef, - ServiceRepr -} +import org.hyperledger.identus.castor.core.model.did.w3c.* import org.hyperledger.identus.castor.core.service.DIDService -import org.hyperledger.identus.shared.json.JsonInterop import zio.* +import zio.json.ast.Json import java.time.Instant import scala.annotation.unused @@ -135,7 +126,7 @@ class PrismDidResolver(didService: DIDService) extends DidResolver { Service( id = service.id, `type` = service.`type`, - serviceEndpoint = JsonInterop.toCirceJsonAst(service.serviceEndpoint) + serviceEndpoint = service.serviceEndpoint ) } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/EcdsaSecp256k1VerificationKey2019.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/EcdsaSecp256k1VerificationKey2019.scala index 883087c1ff..c0ad48ac59 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/EcdsaSecp256k1VerificationKey2019.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/EcdsaSecp256k1VerificationKey2019.scala @@ -1,9 +1,8 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe.* -import io.circe.syntax.* +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} -import java.time.{Instant, ZoneOffset} +import java.time.{Instant, OffsetDateTime, ZoneOffset} case class EcdsaSecp256k1VerificationKey2019( publicKeyJwk: JsonWebKey, @@ -12,38 +11,30 @@ case class EcdsaSecp256k1VerificationKey2019( expires: Option[Instant] = None ) { val `type`: String = "EcdsaSecp256k1VerificationKey2019" - val `@context`: Set[String] = - Set("https://w3id.org/security/v1") + val `@context`: Set[String] = Set("https://w3id.org/security/v1") } object EcdsaSecp256k1VerificationKey2019 { - given ecdsaSecp256k1VerificationKey2019Encoder: Encoder[EcdsaSecp256k1VerificationKey2019] = - (key: EcdsaSecp256k1VerificationKey2019) => - Json - .obj( - ("@context", key.`@context`.asJson), - ("type", key.`type`.asJson), - ("id", key.id.asJson), - ("controller", key.controller.asJson), - ("publicKeyJwk", key.publicKeyJwk.asJson.dropNullValues), - ("expires", key.expires.map(_.atOffset(ZoneOffset.UTC)).asJson) - ) - - given ecdsaSecp256k1VerificationKey2019Decoder: Decoder[EcdsaSecp256k1VerificationKey2019] = - (c: HCursor) => - for { - id <- c.downField("id").as[Option[String]] - `type` <- c.downField("type").as[String] - controller <- c.downField("controller").as[Option[String]] - publicKeyJwk <- c.downField("publicKeyJwk").as[JsonWebKey] - expires <- c.downField("expires").as[Option[Instant]] - } yield { - EcdsaSecp256k1VerificationKey2019( - id = id, - publicKeyJwk = publicKeyJwk, - controller = controller, - expires = expires - ) - } + private case class Json_EcdsaSecp256k1VerificationKey2019( + `@context`: Set[String], + `type`: String, + id: Option[String], + controller: Option[String], + publicKeyJwk: JsonWebKey, + expires: Option[OffsetDateTime] + ) + private given JsonEncoder[Json_EcdsaSecp256k1VerificationKey2019] = DeriveJsonEncoder.gen + given JsonEncoder[EcdsaSecp256k1VerificationKey2019] = JsonEncoder[Json_EcdsaSecp256k1VerificationKey2019].contramap { + key => + Json_EcdsaSecp256k1VerificationKey2019( + key.`@context`, + key.`type`, + key.id, + key.controller, + key.publicKeyJwk, + key.expires.map(_.atOffset(ZoneOffset.UTC)) + ) + } + given JsonDecoder[EcdsaSecp256k1VerificationKey2019] = DeriveJsonDecoder.gen } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/InstantDecoderEncoder.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/InstantDecoderEncoder.scala deleted file mode 100644 index 03c63b74bd..0000000000 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/InstantDecoderEncoder.scala +++ /dev/null @@ -1,13 +0,0 @@ -package org.hyperledger.identus.pollux.vc.jwt - -import io.circe.{Decoder, Encoder, HCursor} - -import java.time.Instant - -object InstantDecoderEncoder { - implicit val instantToEpochSecondsEncoder: Encoder[Instant] = - (instant: Instant) => Encoder.encodeLong(instant.getEpochSecond) - - implicit val epochSecondsToInstantDecoder: Decoder[Instant] = - (c: HCursor) => Decoder.decodeLong.map(s => Instant.ofEpochSecond(s)).apply(c) -} diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerification.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerification.scala index 4d614a2436..3899575ac1 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerification.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerification.scala @@ -6,12 +6,12 @@ import com.nimbusds.jose.jwk.* import com.nimbusds.jose.util.Base64URL import com.nimbusds.jose.JWSVerifier import com.nimbusds.jwt.SignedJWT -import io.circe -import io.circe.generic.auto.* import org.hyperledger.identus.castor.core.model.did.VerificationRelationship import org.hyperledger.identus.shared.crypto.Ed25519PublicKey import pdi.jwt.* import zio.* +import zio.json.{DecoderOps, EncoderOps} +import zio.json.ast.Json import zio.prelude.* import java.security.interfaces.{ECPublicKey, EdECPublicKey} @@ -30,13 +30,13 @@ object JWTVerification { def validateAlgorithm(jwt: JWT): Validation[String, Unit] = { val decodedJWT = Validation - .fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRawAll(jwt.value, JwtOptions(false, false, false))) .mapError(_.getMessage) for { decodedJwtTask <- decodedJWT (header, _, _) = decodedJwtTask algorithm <- Validation - .fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm) + .fromOptionWith("An algorithm must be specified in the header")(JwtZIOJson.parseHeader(header).algorithm) result <- Validation .fromPredicateWith("Algorithm Not Supported")( @@ -52,7 +52,7 @@ object JWTVerification { )(issuerDidExtractor: T => String): IO[String, Validation[String, DIDDocument]] = { val decodedJWT = Validation - .fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRawAll(jwt.value, JwtOptions(false, false, false))) .mapError(_.getMessage) val claim: Validation[String, String] = @@ -100,7 +100,7 @@ object JWTVerification { didResolver: DidResolver )(decoder: String => Validation[String, T])(issuerDidExtractor: T => String): IO[String, Validation[String, Unit]] = { val decodedJWT = Validation - .fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRawAll(jwt.value, JwtOptions(false, false, false))) .mapError(_.getMessage) val extractAlgorithm: Validation[String, JwtAlgorithm] = @@ -108,7 +108,7 @@ object JWTVerification { decodedJwtTask <- decodedJWT (header, _, _) = decodedJwtTask algorithm <- Validation - .fromOptionWith("An algorithm must be specified in the header")(JwtCirce.parseHeader(header).algorithm) + .fromOptionWith("An algorithm must be specified in the header")(JwtZIOJson.parseHeader(header).algorithm) } yield algorithm val claim: Validation[String, String] = @@ -242,22 +242,19 @@ object JWTVerification { } def extractJwtHeader(jwt: JWT): Validation[String, JwtHeader] = { - import io.circe.parser._ - import io.circe.Json - def parseHeaderUnsafe(json: Json): Either[String, JwtHeader] = - Try(JwtCirce.parseHeader(json.noSpaces)).toEither.left.map(_.getMessage) + Try(JwtZIOJson.parseHeader(json.toJson)).toEither.left.map(_.getMessage) def decodeJwtHeader(header: String): Validation[String, JwtHeader] = { val eitherResult = for { - json <- parse(header).left.map(_.getMessage) + json <- header.fromJson[Json] jwtHeader <- parseHeaderUnsafe(json) } yield jwtHeader Validation.fromEither(eitherResult) } for { decodedJwt <- Validation - .fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRawAll(jwt.value, JwtOptions(false, false, false))) .mapError(_.getMessage) (header, _, _) = decodedJwt jwtHeader <- decodeJwtHeader(header) diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonEncoders.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonEncoders.scala new file mode 100644 index 0000000000..93d1d9e3f0 --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonEncoders.scala @@ -0,0 +1,50 @@ +package org.hyperledger.identus.pollux.vc.jwt + +import zio.json.{JsonDecoder, JsonEncoder} +import zio.json.ast.Json +import zio.json.internal.Write + +import java.time.Instant +import scala.reflect.ClassTag + +object JsonEncoders { + given JsonEncoder[Instant] = JsonEncoder.long.contramap(_.getEpochSecond) + given JsonDecoder[Instant] = JsonDecoder.long.map(Instant.ofEpochSecond) + + def aOrBEncoder[A: JsonEncoder: ClassTag, B: JsonEncoder: ClassTag]: JsonEncoder[A | B] = + (a: A | B, indent: Option[Int], out: Write) => + a match + case a: A => JsonEncoder[A].unsafeEncode(a, indent, out) + case b: B => JsonEncoder[B].unsafeEncode(b, indent, out) + + def aOrBDecoder[A: JsonDecoder: ClassTag, B: JsonDecoder: ClassTag]: JsonDecoder[A | B] = + JsonDecoder[Json].mapOrFail { json => + json.as[A].orElse(json.as[B]) + } + + given stringOrStringSetEncoder: JsonEncoder[String | Set[String]] = + aOrBEncoder[String, Set[String]] + given stringOrStringSetDecoder: JsonDecoder[String | Set[String]] = + aOrBDecoder[String, Set[String]] + + given stringOrStringIndexedSeqEncoder: JsonEncoder[String | IndexedSeq[String]] = + aOrBEncoder[String, IndexedSeq[String]] + given stringOrStringIndexedSeqDecoder: JsonDecoder[String | IndexedSeq[String]] = + aOrBDecoder[String, IndexedSeq[String]] + + given credStatusOrCredStatusListEncoder: JsonEncoder[CredentialStatus | List[CredentialStatus]] = + aOrBEncoder[CredentialStatus, List[CredentialStatus]] + given credStatusOrCredStatusListDecoder: JsonDecoder[CredentialStatus | List[CredentialStatus]] = + aOrBDecoder[CredentialStatus, List[CredentialStatus]] + + given stringOrCredIssuerEncoder: JsonEncoder[String | CredentialIssuer] = + aOrBEncoder[String, CredentialIssuer] + given stringOrCredIssuerDecoder: JsonDecoder[String | CredentialIssuer] = + aOrBDecoder[String, CredentialIssuer] + + given credSchemaOrCredSchemaListEncoder: JsonEncoder[CredentialSchema | List[CredentialSchema]] = + aOrBEncoder[CredentialSchema, List[CredentialSchema]] + given credSchemaOrCredSchemaListDecoder: JsonDecoder[CredentialSchema | List[CredentialSchema]] = + aOrBDecoder[CredentialSchema, List[CredentialSchema]] + +} diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonWebKey.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonWebKey.scala index 34cac8c490..6f839e29bc 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonWebKey.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/JsonWebKey.scala @@ -1,7 +1,5 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe.* -import io.circe.generic.semiauto.* import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} case class JsonWebKey( @@ -20,12 +18,6 @@ case class JsonWebKey( ) object JsonWebKey { - given jsonWebKeyEncoderCirce: Encoder[JsonWebKey] = deriveEncoder[JsonWebKey] - - given jsonWebKeyDecoderCirce: Decoder[JsonWebKey] = deriveDecoder[JsonWebKey] - - given jsonWebKeyEncoderCirceZioJson: JsonEncoder[JsonWebKey] = DeriveJsonEncoder.gen[JsonWebKey] - - given jsonWebKeyDecoderCirceZioJson: JsonDecoder[JsonWebKey] = DeriveJsonDecoder.gen[JsonWebKey] - + given JsonEncoder[JsonWebKey] = DeriveJsonEncoder.gen + given JsonDecoder[JsonWebKey] = DeriveJsonDecoder.gen } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiBaseString.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiBaseString.scala index 510c771469..4958c05889 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiBaseString.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiBaseString.scala @@ -1,8 +1,8 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe.* import org.hyperledger.identus.shared.utils.Base64Utils import scodec.bits.ByteVector +import zio.json.{JsonDecoder, JsonEncoder} case class MultiBaseString(header: MultiBaseString.Header, data: String) { def toMultiBaseString: String = s"${header.value}$data" @@ -36,15 +36,13 @@ object MultiBaseString { } } - given multiBaseStringEncoder: Encoder[MultiBaseString] = (multiBaseString: MultiBaseString) => - Json.fromString(multiBaseString.toMultiBaseString) + given JsonEncoder[MultiBaseString] = JsonEncoder.string.contramap(_.toMultiBaseString) - given multiBaseStringDecoder: Decoder[MultiBaseString] = (c: HCursor) => - Decoder.decodeString(c).flatMap { str => - val header = MultiBaseString.Header.fromValue(str.head) - header match { - case Some(value) => Right(MultiBaseString(value, str.tail)) - case None => Left(DecodingFailure(s"no enum value matched for $str", List(CursorOp.Field(str)))) - } + given JsonDecoder[MultiBaseString] = JsonDecoder[String].mapOrFail { str => + val header = MultiBaseString.Header.fromValue(str.head) + header match { + case Some(value) => Right(MultiBaseString(value, str.tail)) + case None => Left(s"no enum value matched for $str") } + } } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiKey.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiKey.scala index f1ed33a4ce..4f65afa7d3 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiKey.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/MultiKey.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe.* -import io.circe.syntax.* +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} case class MultiKey( publicKeyMultibase: Option[MultiBaseString] = None, @@ -11,26 +10,20 @@ case class MultiKey( val `@context`: Set[String] = Set("https://w3id.org/security/multikey/v1") } object MultiKey { - given multiKeyEncoder: Encoder[MultiKey] = - (multiKey: MultiKey) => - Json - .obj( - ("@context", multiKey.`@context`.asJson), - ("type", multiKey.`type`.asJson), - ("publicKeyMultibase", multiKey.publicKeyMultibase.asJson), - ("secretKeyMultibase", multiKey.secretKeyMultibase.asJson), - ) - - given multiKeyDecoder: Decoder[MultiKey] = - (c: HCursor) => - for { - publicKeyMultibase <- c.downField("publicKeyMultibase").as[Option[MultiBaseString]] - secretKeyMultibase <- c.downField("secretKeyMultibase").as[Option[MultiBaseString]] - } yield { - MultiKey( - publicKeyMultibase = publicKeyMultibase, - secretKeyMultibase = secretKeyMultibase, - ) - } - + private case class Json_MultiKey( + `@context`: Set[String], + `type`: String, + publicKeyMultibase: Option[MultiBaseString], + secretKeyMultibase: Option[MultiBaseString], + ) + private given JsonEncoder[Json_MultiKey] = DeriveJsonEncoder.gen + given JsonEncoder[MultiKey] = JsonEncoder[Json_MultiKey].contramap({ key => + Json_MultiKey( + key.`@context`, + key.`type`, + key.publicKeyMultibase, + key.secretKeyMultibase + ) + }) + given JsonDecoder[MultiKey] = DeriveJsonDecoder.gen } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala index ca27945182..28fe848d17 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala @@ -1,20 +1,19 @@ package org.hyperledger.identus.pollux.vc.jwt -import cats.implicits.* import com.nimbusds.jose.{JWSAlgorithm, JWSHeader, JWSObject, Payload} import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jwt.SignedJWT -import io.circe.* -import io.circe.syntax.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, KmpEd25519KeyOps} import org.hyperledger.identus.shared.json.Json as JsonUtils import org.hyperledger.identus.shared.utils.Base64Utils import scodec.bits.ByteVector import zio.* +import zio.json.{DecoderOps, DeriveJsonDecoder, DeriveJsonEncoder, EncoderOps, JsonDecoder, JsonEncoder} +import zio.json.ast.{Json, JsonCursor} -import java.security.* +import java.io.IOException import java.security.interfaces.ECPublicKey -import java.time.{Instant, ZoneOffset} +import java.time.{Instant, OffsetDateTime, ZoneOffset} import scala.jdk.CollectionConverters.* sealed trait Proof { @@ -29,24 +28,15 @@ sealed trait Proof { val nonce: Option[String] = None } -trait DataIntegrityProof extends Proof { +sealed trait DataIntegrityProof extends Proof { val proofValue: String } object Proof { - given decodeProof: Decoder[Proof] = new Decoder[Proof] { - final def apply(c: HCursor): Decoder.Result[Proof] = { - val decoders: List[Decoder[Proof]] = List( - Decoder[EddsaJcs2022Proof].widen, - Decoder[EcdsaSecp256k1Signature2019Proof].widen, - // Note: Add another proof types here when available - ) - decoders.foldLeft( - Left[DecodingFailure, Proof](DecodingFailure("Cannot decode as Proof", c.history)): Decoder.Result[Proof] - ) { (acc, decoder) => - acc.orElse(decoder.tryDecode(c)) - } - } + given JsonDecoder[Proof] = JsonDecoder[Json].mapOrFail { json => + json + .as[EddsaJcs2022Proof] + .orElse(json.as[EcdsaSecp256k1Signature2019Proof]) } } @@ -56,7 +46,7 @@ object EcdsaSecp256k1Signature2019ProofGenerator { } def generateProof(payload: Json, signer: ECDSASigner, pk: ECPublicKey): Task[EcdsaSecp256k1Signature2019Proof] = { for { - dataToSign <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.spaces2)) + dataToSign <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.toJson)) created = Instant.now() header = new JWSHeader.Builder(JWSAlgorithm.ES256K) .base64URLEncodePayload(false) @@ -79,7 +69,7 @@ object EcdsaSecp256k1Signature2019ProofGenerator { publicKeyJwk = jwk ) verificationMethodUrl = Base64Utils.createDataUrl( - ecdaSecp256k1VerificationKey2019.asJson.dropNullValues.noSpaces.getBytes, + ecdaSecp256k1VerificationKey2019.toJson.getBytes, "application/json" ) } yield EcdsaSecp256k1Signature2019Proof( @@ -91,7 +81,7 @@ object EcdsaSecp256k1Signature2019ProofGenerator { def verifyProof(payload: Json, jws: String, pk: ECPublicKey): Task[Boolean] = { for { - dataToVerify <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.spaces2)) + dataToVerify <- ZIO.fromEither(JsonUtils.canonicalizeJsonLDoRdf(payload.toJson)) verifier = JWTVerification.toECDSAVerifier(pk) signedJws = SignedJWT.parse(jws) header = signedJws.getHeader @@ -141,9 +131,9 @@ object EddsaJcs2022ProofGenerator { def generateProof(payload: Json, ed25519KeyPair: Ed25519KeyPair): Task[EddsaJcs2022Proof] = { for { - canonicalizedJsonString <- ZIO.fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2)) - canonicalizedJson <- ZIO.fromEither(parser.parse(canonicalizedJsonString)) - dataToSign = canonicalizedJson.noSpaces.getBytes + canonicalizedJsonString <- ZIO.fromEither(JsonUtils.canonicalizeToJcs(payload.toJson)) + canonicalizedJson <- ZIO.fromEither(canonicalizedJsonString.fromJson[Json].left.map(e => IOException(e))) + dataToSign = canonicalizedJson.toJson.getBytes signature = ed25519KeyPair.privateKey.sign(dataToSign) base58BtsEncodedSignature = MultiBaseString( header = MultiBaseString.Header.Base58Btc, @@ -152,7 +142,7 @@ object EddsaJcs2022ProofGenerator { created = Instant.now() multiKey = pkToMultiKey(ed25519KeyPair.publicKey) verificationMethod = Base64Utils.createDataUrl( - multiKey.asJson.dropNullValues.noSpaces.getBytes, + multiKey.toJson.getBytes, "application/json" ) } yield EddsaJcs2022Proof( @@ -162,25 +152,16 @@ object EddsaJcs2022ProofGenerator { ) } - def verifyProof(payload: Json, proofValue: String, pk: MultiKey): IO[ParsingFailure, Boolean] = for { - canonicalizedJsonString <- ZIO - .fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2)) - .mapError(ioError => ParsingFailure("Error Parsing canonicalized", ioError)) - canonicalizedJson <- ZIO - .fromEither(parser.parse(canonicalizedJsonString)) - dataToVerify = canonicalizedJson.noSpaces.getBytes + def verifyProof(payload: Json, proofValue: String, pk: MultiKey): IO[IOException, Boolean] = for { + canonicalizedJsonString <- ZIO.fromEither(JsonUtils.canonicalizeToJcs(payload.toJson)) + canonicalizedJson <- ZIO.fromEither(canonicalizedJsonString.fromJson[Json].left.map(e => IOException(e))) + dataToVerify = canonicalizedJson.toJson.getBytes signature <- ZIO .fromEither(MultiBaseString.fromString(proofValue).flatMap(_.getBytes)) - .mapError(error => - // TODO fix RuntimeException - ParsingFailure(error, new RuntimeException(error)) - ) + .mapError(error => IOException(error)) kmmPk <- ZIO .fromEither(multiKeytoPk(pk)) - .mapError(error => - // TODO fix RuntimeException - ParsingFailure("Error Parsing MultiBaseString", new RuntimeException("Error Parsing MultiBaseString")) - ) + .mapError(error => IOException(error)) isValid = verify(kmmPk, signature, dataToVerify) } yield isValid @@ -200,10 +181,8 @@ case class EddsaJcs2022Proof(proofValue: String, verificationMethod: String, may } object EddsaJcs2022Proof { - given proofEncoder: Encoder[EddsaJcs2022Proof] = - DataIntegrityProofCodecs.proofEncoder[EddsaJcs2022Proof]("eddsa-jcs-2022") - - given proofDecoder: Decoder[EddsaJcs2022Proof] = DataIntegrityProofCodecs.proofDecoder[EddsaJcs2022Proof]( + given JsonEncoder[EddsaJcs2022Proof] = DataIntegrityProofCodecs.proofEncoder("eddsa-jcs-2022") + given JsonDecoder[EddsaJcs2022Proof] = DataIntegrityProofCodecs.proofDecoder( (proofValue, verificationMethod, created) => EddsaJcs2022Proof(proofValue, verificationMethod, created), "eddsa-jcs-2022" ) @@ -222,78 +201,89 @@ case class EcdsaSecp256k1Signature2019Proof( } object EcdsaSecp256k1Signature2019Proof { + private case class Json_EcdsaSecp256k1Signature2019Proof( + id: Option[String], + `type`: String = "EcdsaSecp256k1Signature2019", + proofPurpose: String = "assertionMethod", + verificationMethod: String, + created: Option[Instant], + domain: Option[String], + challenge: Option[String], + jws: String, + nonce: Option[String] + ) + private object Json_EcdsaSecp256k1Signature2019Proof { + given JsonEncoder[Json_EcdsaSecp256k1Signature2019Proof] = DeriveJsonEncoder.gen + given JsonDecoder[Json_EcdsaSecp256k1Signature2019Proof] = DeriveJsonDecoder.gen + } + given JsonEncoder[EcdsaSecp256k1Signature2019Proof] = JsonEncoder[Json_EcdsaSecp256k1Signature2019Proof].contramap { + proof => + Json_EcdsaSecp256k1Signature2019Proof( + id = proof.id, + `type` = proof.`type`, + proofPurpose = proof.proofPurpose, + verificationMethod = proof.verificationMethod, + created = proof.created, + domain = proof.domain, + challenge = proof.challenge, + jws = proof.jws, + nonce = proof.nonce + ) + } + given JsonDecoder[EcdsaSecp256k1Signature2019Proof] = JsonDecoder[Json_EcdsaSecp256k1Signature2019Proof].map { + jsonProof => + EcdsaSecp256k1Signature2019Proof( + jws = jsonProof.jws, + verificationMethod = jsonProof.verificationMethod, + created = jsonProof.created, + challenge = jsonProof.challenge, + domain = jsonProof.domain, + nonce = jsonProof.nonce + ) + } - given proofEncoder: Encoder[EcdsaSecp256k1Signature2019Proof] = - (proof: EcdsaSecp256k1Signature2019Proof) => - Json - .obj( - ("id", proof.id.asJson), - ("type", proof.`type`.asJson), - ("proofPurpose", proof.proofPurpose.asJson), - ("verificationMethod", proof.verificationMethod.asJson), - ("created", proof.created.map(_.atOffset(ZoneOffset.UTC)).asJson), - ("domain", proof.domain.asJson), - ("challenge", proof.challenge.asJson), - ("jws", proof.jws.asJson), - ("nonce", proof.nonce.asJson), - ) - - given proofDecoder: Decoder[EcdsaSecp256k1Signature2019Proof] = - (c: HCursor) => - for { - id <- c.downField("id").as[Option[String]] - `type` <- c.downField("type").as[String] - proofPurpose <- c.downField("proofPurpose").as[String] - verificationMethod <- c.downField("verificationMethod").as[String] - created <- c.downField("created").as[Option[Instant]] - domain <- c.downField("domain").as[Option[String]] - challenge <- c.downField("challenge").as[Option[String]] - jws <- c.downField("jws").as[String] - nonce <- c.downField("nonce").as[Option[String]] - } yield { - EcdsaSecp256k1Signature2019Proof( - jws = jws, - verificationMethod = verificationMethod, - created = created, - challenge = challenge, - domain = domain, - nonce = nonce - ) - } } object DataIntegrityProofCodecs { - def proofEncoder[T <: DataIntegrityProof](cryptoSuiteValue: String): Encoder[T] = (proof: T) => - Json.obj( - ("id", proof.id.asJson), - ("type", proof.`type`.asJson), - ("proofPurpose", proof.proofPurpose.asJson), - ("verificationMethod", proof.verificationMethod.asJson), - ("created", proof.created.map(_.atOffset(ZoneOffset.UTC)).asJson), - ("domain", proof.domain.asJson), - ("challenge", proof.challenge.asJson), - ("proofValue", proof.proofValue.asJson), - ("cryptoSuite", Json.fromString(cryptoSuiteValue)), - ("previousProof", proof.previousProof.asJson), - ("nonce", proof.nonce.asJson) - ) + private case class Json_DataIntegrityProof( + id: Option[String] = None, + `type`: String, + proofPurpose: String, + verificationMethod: String, + created: Option[OffsetDateTime] = None, + domain: Option[String] = None, + challenge: Option[String] = None, + proofValue: String, + cryptoSuite: String, + previousProof: Option[String] = None, + nonce: Option[String] = None + ) + private given JsonEncoder[Json_DataIntegrityProof] = DeriveJsonEncoder.gen + def proofEncoder[T <: DataIntegrityProof](cryptoSuiteValue: String): JsonEncoder[T] = + JsonEncoder[Json_DataIntegrityProof].contramap { proof => + Json_DataIntegrityProof( + proof.id, + proof.`type`, + proof.proofPurpose, + proof.verificationMethod, + proof.created.map(_.atOffset(ZoneOffset.UTC)), + proof.domain, + proof.challenge, + proof.proofValue, + cryptoSuiteValue, + proof.previousProof, + proof.nonce + ) + } def proofDecoder[T <: DataIntegrityProof]( createProof: (String, String, Option[Instant]) => T, cryptoSuiteValue: String - ): Decoder[T] = - (c: HCursor) => - for { - id <- c.downField("id").as[Option[String]] - `type` <- c.downField("type").as[String] - proofPurpose <- c.downField("proofPurpose").as[String] - verificationMethod <- c.downField("verificationMethod").as[String] - created <- c.downField("created").as[Option[Instant]] - domain <- c.downField("domain").as[Option[String]] - challenge <- c.downField("challenge").as[Option[String]] - proofValue <- c.downField("proofValue").as[String] - previousProof <- c.downField("previousProof").as[Option[String]] - nonce <- c.downField("nonce").as[Option[String]] - cryptoSuite <- c.downField("cryptoSuite").as[String] - } yield createProof(proofValue, verificationMethod, created) + ): JsonDecoder[T] = JsonDecoder[Json].mapOrFail { json => + for { + proofValue <- json.get(JsonCursor.field("proofValue").isString).map(_.value) + verificationMethod <- json.get(JsonCursor.field("verificationMethod").isString).map(_.value) + maybeCreated <- json.get(JsonCursor.field("created")).map(_.as[Instant]) + } yield createProof(proofValue, verificationMethod, maybeCreated.toOption) + } } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Verifiable.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Verifiable.scala index 3f768d906f..0b95d6af1d 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Verifiable.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Verifiable.scala @@ -1,9 +1,6 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe -import io.circe.* -import io.circe.generic.auto.* -import io.circe.syntax.* +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} import scala.annotation.unused @@ -13,26 +10,6 @@ trait Verifiable(@unused proof: JwtProof) case class JwtProof(`type`: String = "JwtProof2020", jwt: JWT) object JwtProof { - - object Implicits { - implicit val proofEncoder: Encoder[JwtProof] = - (proof: JwtProof) => - Json - .obj( - ("type", proof.`type`.asJson), - ("jwt", proof.jwt.value.asJson) - ) - implicit val proofDecoder: Decoder[JwtProof] = - (c: HCursor) => - for { - `type` <- c.downField("type").as[String] - jwt <- c.downField("jwt").as[String] - } yield { - JwtProof( - `type` = `type`, - jwt = JWT(jwt) - ) - } - - } + given JsonEncoder[JwtProof] = DeriveJsonEncoder.gen + given JsonDecoder[JwtProof] = DeriveJsonDecoder.gen } diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala index e243dfee54..b8bd641944 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala @@ -1,18 +1,17 @@ package org.hyperledger.identus.pollux.vc.jwt import com.nimbusds.jwt.SignedJWT -import io.circe -import io.circe.* -import io.circe.generic.auto.* -import io.circe.parser.decode -import io.circe.syntax.* import org.hyperledger.identus.castor.core.model.did.{DID, VerificationRelationship} import org.hyperledger.identus.pollux.vc.jwt.revocation.BitString import org.hyperledger.identus.shared.crypto.KmpSecp256k1KeyOps import org.hyperledger.identus.shared.http.UriResolver +import org.hyperledger.identus.shared.json.JsonOps.* import org.hyperledger.identus.shared.utils.Base64Utils import pdi.jwt.* import zio.* +import zio.json.{DecoderOps, DeriveJsonDecoder, DeriveJsonEncoder, EncoderOps, JsonDecoder, JsonEncoder} +import zio.json.ast.{Json, JsonCursor} +import zio.json.internal.Write import zio.prelude.* import java.security.PublicKey @@ -24,17 +23,59 @@ case class Issuer(did: DID, signer: Signer, publicKey: PublicKey) sealed trait VerifiableCredentialPayload +object VerifiableCredentialPayload { + given JsonEncoder[VerifiableCredentialPayload] = + (a: VerifiableCredentialPayload, indent: Option[Int], out: Write) => + a match + case p: W3cVerifiableCredentialPayload => + JsonEncoder[W3cVerifiableCredentialPayload].unsafeEncode(p, indent, out) + case p: JwtVerifiableCredentialPayload => + JsonEncoder[JwtVerifiableCredentialPayload].unsafeEncode(p, indent, out) + + given JsonDecoder[VerifiableCredentialPayload] = JsonDecoder[Json].mapOrFail { json => + json + .as[JwtVerifiableCredentialPayload] + .orElse(json.as[W3cVerifiableCredentialPayload]) + } +} + case class W3cVerifiableCredentialPayload(payload: W3cCredentialPayload, proof: JwtProof) extends Verifiable(proof), VerifiableCredentialPayload +object W3cVerifiableCredentialPayload { + given JsonEncoder[W3cVerifiableCredentialPayload] = JsonEncoder[Json].contramap { payload => + (for { + jsonObject <- payload.toJsonAST.flatMap(_.asObject.toRight("Payload's json representation is not an object")) + payload <- payload.proof.toJsonAST.map(p => jsonObject.add("proof", p)) + } yield payload).getOrElse(UnexpectedCodeExecutionPath) + } + given JsonDecoder[W3cVerifiableCredentialPayload] = JsonDecoder[Json].mapOrFail { json => + for { + payload <- json.as[W3cCredentialPayload] + proof <- json.get(JsonCursor.field("proof")).flatMap(_.as[JwtProof]) + } yield W3cVerifiableCredentialPayload(payload, proof) + } +} + case class JwtVerifiableCredentialPayload(jwt: JWT) extends VerifiableCredentialPayload +object JwtVerifiableCredentialPayload { + given JsonEncoder[JwtVerifiableCredentialPayload] = JsonEncoder.string.contramap(_.jwt.value) + given JsonDecoder[JwtVerifiableCredentialPayload] = + JsonDecoder[String].map(s => JwtVerifiableCredentialPayload(JWT(s))) +} + enum StatusPurpose { case Revocation case Suspension } +object StatusPurpose { + given JsonEncoder[StatusPurpose] = DeriveJsonEncoder.gen + given JsonDecoder[StatusPurpose] = DeriveJsonDecoder.gen +} + case class CredentialStatus( id: String, `type`: String, @@ -43,22 +84,42 @@ case class CredentialStatus( statusListCredential: String ) +object CredentialStatus { + given JsonEncoder[CredentialStatus] = DeriveJsonEncoder.gen + given JsonDecoder[CredentialStatus] = DeriveJsonDecoder.gen +} + case class RefreshService( id: String, `type`: String ) +object RefreshService { + given JsonEncoder[RefreshService] = DeriveJsonEncoder.gen + given JsonDecoder[RefreshService] = DeriveJsonDecoder.gen +} + //TODO: refactor to use the new CredentialSchemaRef case class CredentialSchema( id: String, `type`: String ) +object CredentialSchema { + given JsonEncoder[CredentialSchema] = DeriveJsonEncoder.gen + given JsonDecoder[CredentialSchema] = DeriveJsonDecoder.gen +} + case class CredentialIssuer( id: String, `type`: String ) +object CredentialIssuer { + given JsonEncoder[CredentialIssuer] = DeriveJsonEncoder.gen + given JsonDecoder[CredentialIssuer] = DeriveJsonDecoder.gen +} + sealed trait CredentialPayload { def maybeSub: Option[String] @@ -152,6 +213,65 @@ case class JwtVc( maybeTermsOfUse: Option[Json] ) +object JwtVc { + import JsonEncoders.given + + private case class Json_JwtVc( + `@context`: String | Set[String], + `type`: String | Set[String], + credentialSchema: Option[CredentialSchema | List[CredentialSchema]], + credentialSubject: Json, + credentialStatus: Option[CredentialStatus | List[CredentialStatus]], + refreshService: Option[RefreshService], + evidence: Option[Json], + termsOfUse: Option[Json], + validFrom: Option[Instant], + validUntil: Option[Instant], + issuer: Option[String | CredentialIssuer] + ) + + private given JsonEncoder[Json_JwtVc] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_JwtVc] = DeriveJsonDecoder.gen + + given JsonEncoder[JwtVc] = JsonEncoder[Json_JwtVc].contramap { vc => + Json_JwtVc( + vc.`@context`, + vc.`type`, + vc.maybeCredentialSchema, + vc.credentialSubject, + vc.maybeCredentialStatus, + vc.maybeRefreshService, + vc.maybeEvidence, + vc.maybeTermsOfUse, + vc.maybeValidFrom, + vc.maybeValidUntil, + vc.maybeIssuer + ) + } + + given JsonDecoder[JwtVc] = JsonDecoder[Json_JwtVc].map { payload => + JwtVc( + payload.`@context` match + case str: String => Set(str) + case set: Set[String] => set + , + payload.`type` match + case str: String => Set(str) + case set: Set[String] => set + , + payload.credentialSchema, + payload.credentialSubject, + payload.validFrom, + payload.validUntil, + payload.issuer, + payload.credentialStatus, + payload.refreshService, + payload.evidence, + payload.termsOfUse + ) + } +} + case class JwtCredentialPayload( iss: String, override val maybeSub: Option[String], @@ -174,6 +294,50 @@ case class JwtCredentialPayload( override val issuer = vc.maybeIssuer.getOrElse(iss) } +object JwtCredentialPayload { + import JsonEncoders.given + + private case class Json_JwtCredentialPayload( + iss: String, + sub: Option[String], + vc: JwtVc, + nbf: Instant, + aud: String | Set[String] = Set.empty, + exp: Option[Instant], + jti: Option[String] + ) + + private given JsonEncoder[Json_JwtCredentialPayload] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_JwtCredentialPayload] = DeriveJsonDecoder.gen + + given JsonEncoder[JwtCredentialPayload] = JsonEncoder[Json_JwtCredentialPayload].contramap { payload => + Json_JwtCredentialPayload( + payload.iss, + payload.maybeSub, + payload.vc, + payload.nbf, + payload.aud, + payload.maybeExp, + payload.maybeJti + ) + } + + given JsonDecoder[JwtCredentialPayload] = JsonDecoder[Json_JwtCredentialPayload].map { payload => + JwtCredentialPayload( + payload.iss, + payload.sub, + payload.vc, + payload.nbf, + payload.aud match + case str: String => Set(str) + case set: Set[String] => set + , + payload.exp, + payload.jti + ) + } +} + case class W3cCredentialPayload( override val `@context`: Set[String], override val `type`: Set[String], @@ -191,351 +355,81 @@ case class W3cCredentialPayload( override val maybeValidFrom: Option[Instant], override val maybeValidUntil: Option[Instant] ) extends CredentialPayload { - override val maybeSub = credentialSubject.hcursor.downField("id").as[String].toOption + override val maybeSub = credentialSubject.get(JsonCursor.field("id").isString).map(_.value).toOption override val maybeJti = maybeId override val nbf = issuanceDate override val maybeExp = maybeExpirationDate } -object CredentialPayload { - object Implicits { - - import InstantDecoderEncoder.* - import JwtProof.Implicits.* - - implicit val refreshServiceEncoder: Encoder[RefreshService] = - (refreshService: RefreshService) => - Json - .obj( - ("id", refreshService.id.asJson), - ("type", refreshService.`type`.asJson) - ) - - implicit val credentialSchemaEncoder: Encoder[CredentialSchema] = - (credentialSchema: CredentialSchema) => - Json - .obj( - ("id", credentialSchema.id.asJson), - ("type", credentialSchema.`type`.asJson) - ) - - implicit val credentialIssuerEncoder: Encoder[CredentialIssuer] = - (credentialIssuer: CredentialIssuer) => - Json - .obj( - ("id", credentialIssuer.id.asJson), - ("type", credentialIssuer.`type`.asJson) - ) - - implicit val credentialStatusPurposeEncoder: Encoder[StatusPurpose] = (a: StatusPurpose) => a.toString.asJson - - implicit val credentialStatusEncoder: Encoder[CredentialStatus] = - (credentialStatus: CredentialStatus) => - Json - .obj( - ("id", credentialStatus.id.asJson), - ("type", credentialStatus.`type`.asJson), - ("statusPurpose", credentialStatus.statusPurpose.asJson), - ("statusListIndex", credentialStatus.statusListIndex.asJson), - ("statusListCredential", credentialStatus.statusListCredential.asJson) - ) - - implicit val credentialStatusOrListEncoder: Encoder[CredentialStatus | List[CredentialStatus]] = Encoder.instance { - case status: CredentialStatus => Encoder[CredentialStatus].apply(status) - case statusList: List[CredentialStatus] => Encoder[List[CredentialStatus]].apply(statusList) - } - - implicit val stringOrCredentialIssuerEncoder: Encoder[String | CredentialIssuer] = Encoder.instance { - case string: String => Encoder[String].apply(string) - case credentialIssuer: CredentialIssuer => Encoder[CredentialIssuer].apply(credentialIssuer) - } - - implicit val credentialSchemaOrListEncoder: Encoder[CredentialSchema | List[CredentialSchema]] = Encoder.instance { - case schema: CredentialSchema => Encoder[CredentialSchema].apply(schema) - case schemaList: List[CredentialSchema] => Encoder[List[CredentialSchema]].apply(schemaList) - } - - implicit val w3cCredentialPayloadEncoder: Encoder[W3cCredentialPayload] = - (w3cCredentialPayload: W3cCredentialPayload) => - Json - .obj( - ("@context", w3cCredentialPayload.`@context`.asJson), - ("type", w3cCredentialPayload.`type`.asJson), - ("id", w3cCredentialPayload.maybeId.asJson), - ("issuer", w3cCredentialPayload.issuer.asJson), - ("issuanceDate", w3cCredentialPayload.issuanceDate.asJson), - ("expirationDate", w3cCredentialPayload.maybeExpirationDate.asJson), - ("validFrom", w3cCredentialPayload.maybeValidFrom.asJson), - ("validUntil", w3cCredentialPayload.maybeValidUntil.asJson), - ("credentialSchema", w3cCredentialPayload.maybeCredentialSchema.asJson), - ("credentialSubject", w3cCredentialPayload.credentialSubject), - ("credentialStatus", w3cCredentialPayload.maybeCredentialStatus.asJson), - ("refreshService", w3cCredentialPayload.maybeRefreshService.asJson), - ("evidence", w3cCredentialPayload.maybeEvidence.asJson), - ("termsOfUse", w3cCredentialPayload.maybeTermsOfUse.asJson), - ("validFrom", w3cCredentialPayload.maybeValidFrom.asJson), - ("validUntil", w3cCredentialPayload.maybeValidUntil.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val jwtVcEncoder: Encoder[JwtVc] = - (jwtVc: JwtVc) => - Json - .obj( - ("@context", jwtVc.`@context`.asJson), - ("type", jwtVc.`type`.asJson), - ("credentialSchema", jwtVc.maybeCredentialSchema.asJson), - ("credentialSubject", jwtVc.credentialSubject), - ("credentialStatus", jwtVc.maybeCredentialStatus.asJson), - ("refreshService", jwtVc.maybeRefreshService.asJson), - ("evidence", jwtVc.maybeEvidence.asJson), - ("termsOfUse", jwtVc.maybeTermsOfUse.asJson), - ("validFrom", jwtVc.maybeValidFrom.asJson), - ("validUntil", jwtVc.maybeValidUntil.asJson), - ("issuer", jwtVc.maybeIssuer.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val jwtCredentialPayloadEncoder: Encoder[JwtCredentialPayload] = - (jwtCredentialPayload: JwtCredentialPayload) => - Json - .obj( - ("iss", jwtCredentialPayload.iss.asJson), - ("sub", jwtCredentialPayload.maybeSub.asJson), - ("vc", jwtCredentialPayload.vc.asJson), - ("nbf", jwtCredentialPayload.nbf.asJson), - ("aud", jwtCredentialPayload.aud.asJson), - ("exp", jwtCredentialPayload.maybeExp.asJson), - ("jti", jwtCredentialPayload.maybeJti.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val w3CVerifiableCredentialPayloadEncoder: Encoder[W3cVerifiableCredentialPayload] = - (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) => - w3cVerifiableCredentialPayload.payload.asJson - .deepMerge(Map("proof" -> w3cVerifiableCredentialPayload.proof).asJson) - - implicit val jwtVerifiableCredentialPayloadEncoder: Encoder[JwtVerifiableCredentialPayload] = - (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => - jwtVerifiableCredentialPayload.jwt.value.asJson - - implicit val verifiableCredentialPayloadEncoder: Encoder[VerifiableCredentialPayload] = { - case (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) => w3cVerifiableCredentialPayload.asJson - case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => jwtVerifiableCredentialPayload.asJson - } - - implicit val refreshServiceDecoder: Decoder[RefreshService] = - (c: HCursor) => - for { - id <- c.downField("id").as[String] - `type` <- c.downField("type").as[String] - } yield { - RefreshService(id = id, `type` = `type`) - } - - implicit val credentialSchemaDecoder: Decoder[CredentialSchema] = - (c: HCursor) => - for { - id <- c.downField("id").as[String] - `type` <- c.downField("type").as[String] - } yield { - CredentialSchema(id = id, `type` = `type`) - } - - implicit val credentialIssuerDecoder: Decoder[CredentialIssuer] = - (c: HCursor) => - for { - id <- c.downField("id").as[String] - `type` <- c.downField("type").as[String] - } yield { - CredentialIssuer(id = id, `type` = `type`) - } - - implicit val credentialStatusPurposeDecoder: Decoder[StatusPurpose] = (c: HCursor) => - Decoder.decodeString(c).flatMap { str => - Try(StatusPurpose.valueOf(str)).toEither.leftMap { _ => - DecodingFailure(s"no enum value matched for $str", List(CursorOp.Field(str))) - } - } - - implicit val credentialStatusDecoder: Decoder[CredentialStatus] = - (c: HCursor) => - for { - id <- c.downField("id").as[String] - `type` <- c.downField("type").as[String] - statusPurpose <- c.downField("statusPurpose").as[StatusPurpose] - statusListIndex <- c.downField("statusListIndex").as[Int] - statusListCredential <- c.downField("statusListCredential").as[String] - } yield { - CredentialStatus( - id = id, - `type` = `type`, - statusPurpose = statusPurpose, - statusListIndex = statusListIndex, - statusListCredential = statusListCredential - ) - } - - implicit val stringOrCredentialIssuerDecoder: Decoder[String | CredentialIssuer] = - Decoder[String] - .map(schema => schema: String | CredentialIssuer) - .or(Decoder[CredentialIssuer].map(schema => schema: String | CredentialIssuer)) - - implicit val credentialSchemaOrListDecoder: Decoder[CredentialSchema | List[CredentialSchema]] = - Decoder[CredentialSchema] - .map(schema => schema: CredentialSchema | List[CredentialSchema]) - .or(Decoder[List[CredentialSchema]].map(schema => schema: CredentialSchema | List[CredentialSchema])) - - implicit val credentialStatusOrListDecoder: Decoder[CredentialStatus | List[CredentialStatus]] = - Decoder[CredentialStatus] - .map(status => status: CredentialStatus | List[CredentialStatus]) - .or(Decoder[List[CredentialStatus]].map(status => status: CredentialStatus | List[CredentialStatus])) - - implicit val w3cCredentialPayloadDecoder: Decoder[W3cCredentialPayload] = - (c: HCursor) => - for { - `@context` <- c - .downField("@context") - .as[Set[String]] - .orElse(c.downField("@context").as[String].map(Set(_))) - `type` <- c - .downField("type") - .as[Set[String]] - .orElse(c.downField("type").as[String].map(Set(_))) - maybeId <- c.downField("id").as[Option[String]] - issuer <- c.downField("issuer").as[String | CredentialIssuer] - issuanceDate <- c.downField("issuanceDate").as[Instant] - maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]] - maybeValidFrom <- c.downField("validFrom").as[Option[Instant]] - maybeValidUntil <- c.downField("validUntil").as[Option[Instant]] - maybeCredentialSchema <- c - .downField("credentialSchema") - .as[Option[CredentialSchema | List[CredentialSchema]]] - credentialSubject <- c.downField("credentialSubject").as[Json] - maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus | List[CredentialStatus]]] - maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]] - maybeEvidence <- c.downField("evidence").as[Option[Json]] - maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]] - } yield { - W3cCredentialPayload( - `@context` = `@context`, - `type` = `type`, - maybeId = maybeId, - issuer = issuer, - issuanceDate = issuanceDate, - maybeExpirationDate = maybeExpirationDate, - maybeValidFrom = maybeValidFrom, - maybeValidUntil = maybeValidUntil, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeCredentialStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse, - aud = Set.empty - ) - } - - implicit val jwtVcDecoder: Decoder[JwtVc] = - (c: HCursor) => - for { - `@context` <- c - .downField("@context") - .as[Set[String]] - .orElse(c.downField("@context").as[String].map(Set(_))) - `type` <- c - .downField("type") - .as[Set[String]] - .orElse(c.downField("type").as[String].map(Set(_))) - maybeCredentialSchema <- c - .downField("credentialSchema") - .as[Option[CredentialSchema | List[CredentialSchema]]] - credentialSubject <- c.downField("credentialSubject").as[Json] - maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus | List[CredentialStatus]]] - maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]] - maybeEvidence <- c.downField("evidence").as[Option[Json]] - maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]] - maybeValidFrom <- c.downField("validFrom").as[Option[Instant]] - maybeValidUntil <- c.downField("validUntil").as[Option[Instant]] - maybeIssuer <- c.downField("issuer").as[Option[String | CredentialIssuer]] - } yield { - JwtVc( - `@context` = `@context`, - `type` = `type`, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeCredentialStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse, - maybeValidFrom = maybeValidFrom, - maybeValidUntil = maybeValidUntil, - maybeIssuer = maybeIssuer - ) - } - - implicit val jwtCredentialPayloadDecoder: Decoder[JwtCredentialPayload] = - (c: HCursor) => - for { - iss <- c.downField("iss").as[String] - maybeSub <- c.downField("sub").as[Option[String]] - vc <- c.downField("vc").as[JwtVc] - nbf <- c.downField("nbf").as[Instant] - aud <- c - .downField("aud") - .as[Option[String]] - .map(_.iterator.toSet) - .orElse(c.downField("aud").as[Option[Set[String]]].map(_.iterator.toSet.flatten)) - maybeExp <- c.downField("exp").as[Option[Instant]] - maybeJti <- c.downField("jti").as[Option[String]] - } yield { - JwtCredentialPayload( - iss = iss, - maybeSub = maybeSub, - vc = vc, - nbf = nbf, - aud = aud, - maybeExp = maybeExp, - maybeJti = maybeJti - ) - } - - implicit val w3cVerifiableCredentialPayloadDecoder: Decoder[W3cVerifiableCredentialPayload] = - (c: HCursor) => - for { - payload <- c.as[W3cCredentialPayload] - proof <- c.downField("proof").as[JwtProof] - } yield { - W3cVerifiableCredentialPayload( - payload = payload, - proof = proof - ) - } - - implicit val jwtVerifiableCredentialPayloadDecoder: Decoder[JwtVerifiableCredentialPayload] = - (c: HCursor) => - for { - jwt <- c.as[String] - } yield { - JwtVerifiableCredentialPayload( - jwt = JWT(jwt) - ) - } +object W3cCredentialPayload { + import JsonEncoders.given + private case class Json_W3cCredentialPayload( + `@context`: String | Set[String], + `type`: String | Set[String], + id: Option[String], + issuer: String | CredentialIssuer, + issuanceDate: Instant, + expirationDate: Option[Instant], + validFrom: Option[Instant], + validUntil: Option[Instant], + credentialSchema: Option[CredentialSchema | List[CredentialSchema]], + credentialSubject: Json, + credentialStatus: Option[CredentialStatus | List[CredentialStatus]], + refreshService: Option[RefreshService], + evidence: Option[Json], + termsOfUse: Option[Json] + ) - implicit val verifiableCredentialPayloadDecoder: Decoder[VerifiableCredentialPayload] = - jwtVerifiableCredentialPayloadDecoder.or( - w3cVerifiableCredentialPayloadDecoder.asInstanceOf[Decoder[VerifiableCredentialPayload]] - ) + private given JsonEncoder[Json_W3cCredentialPayload] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_W3cCredentialPayload] = DeriveJsonDecoder.gen + + given JsonEncoder[W3cCredentialPayload] = JsonEncoder[Json_W3cCredentialPayload].contramap { payload => + Json_W3cCredentialPayload( + payload.`@context`, + payload.`type`, + payload.maybeId, + payload.issuer, + payload.issuanceDate, + payload.maybeExpirationDate, + payload.maybeValidFrom, + payload.maybeValidUntil, + payload.maybeCredentialSchema, + payload.credentialSubject, + payload.maybeCredentialStatus, + payload.maybeRefreshService, + payload.maybeEvidence, + payload.maybeTermsOfUse + ) + } + given JsonDecoder[W3cCredentialPayload] = JsonDecoder[Json_W3cCredentialPayload].map { payload => + W3cCredentialPayload( + payload.`@context` match + case str: String => Set(str) + case set: Set[String] => set + , + payload.`type` match + case str: String => Set(str) + case set: Set[String] => set + , + payload.id, + payload.issuer, + payload.issuanceDate, + payload.expirationDate, + payload.credentialSchema, + payload.credentialSubject, + payload.credentialStatus, + payload.refreshService, + payload.evidence, + payload.termsOfUse, + Set.empty, + payload.validFrom, + payload.validUntil, + ) } } object CredentialVerification { - import CredentialPayload.Implicits.* - def validateValidFromNotAfterValidUntil( maybeValidFrom: Option[Instant], maybeValidUntil: Option[Instant], @@ -647,18 +541,17 @@ object CredentialVerification { def verifyCredentialStatus( credentialStatus: CredentialStatus )(uriResolver: UriResolver): IO[String, Validation[String, Unit]] = { - val res = for { statusListString <- uriResolver .resolve(credentialStatus.statusListCredential) .mapError(err => s"Could not resolve status list credential: $err") _ <- ZIO.logInfo("Credential status: " + credentialStatus) vcStatusListCredJson <- ZIO - .fromEither(io.circe.parser.parse(statusListString)) + .fromEither(statusListString.fromJson[Json]) .mapError(err => s"Could not parse status list credential as Json string: $err") - statusListCredJsonWithoutProof = vcStatusListCredJson.hcursor.downField("proof").delete.top.get + statusListCredJsonWithoutProof = vcStatusListCredJson.removeField("proof") proof <- ZIO - .fromEither(vcStatusListCredJson.hcursor.downField("proof").as[Proof]) + .fromEither(vcStatusListCredJson.get(JsonCursor.field("proof")).flatMap(_.as[Proof])) .mapError(err => s"Could not extract proof from status list credential: $err") // Verify proof @@ -666,11 +559,10 @@ object CredentialVerification { case EddsaJcs2022Proof(proofValue, verificationMethod, maybeCreated) => val publicKeyMultiBaseEffect = uriResolver .resolve(verificationMethod) - .mapError(_.toThrowable) + .mapError(_.toThrowable.getMessage) .flatMap { jsonResponse => - ZIO.fromEither(io.circe.parser.decode[MultiKey](jsonResponse)).mapError(_.fillInStackTrace) + ZIO.fromEither(jsonResponse.fromJson[MultiKey]) } - .mapError(_.getMessage) for { publicKeyMultiBase <- publicKeyMultiBaseEffect @@ -682,14 +574,10 @@ object CredentialVerification { case EcdsaSecp256k1Signature2019Proof(jws, verificationMethod, _, _, _, _) => val jwkEffect = uriResolver .resolve(verificationMethod) - .mapError(_.toThrowable) + .mapError(_.toThrowable.getMessage) .flatMap { jsonResponse => - ZIO - .fromEither(io.circe.parser.decode[EcdsaSecp256k1VerificationKey2019](jsonResponse)) - .map(_.publicKeyJwk) - .mapError(_.fillInStackTrace) + ZIO.fromEither(jsonResponse.fromJson[EcdsaSecp256k1VerificationKey2019].map(_.publicKeyJwk)) } - .mapError(_.getMessage) for { jwk <- jwkEffect @@ -709,19 +597,15 @@ object CredentialVerification { .verifyProof(statusListCredJsonWithoutProof, jws, ecPublicKey) .mapError(_.getMessage) } yield verified - // Note: add other proof types here when available - - case _ => ZIO.fail(s"Unsupported proof type - ${proof.`type`}") - proofVerificationValidation = if (verified) Validation.unit else Validation.fail("Could not verify status list credential proof") // Check revocation status in the list by index - encodedBitStringEither = vcStatusListCredJson.hcursor - .downField("credentialSubject") - .as[Json] - .flatMap(_.hcursor.downField("encodedList").as[String]) - encodedBitString <- ZIO.fromEither(encodedBitStringEither).mapError(_.getMessage) + encodedBitString <- ZIO.fromEither( + vcStatusListCredJson + .get(JsonCursor.field("credentialSubject").isObject.field("encodedList").isString) + .map(_.value) + ) bitString <- BitString.valueOf(encodedBitString).mapError(_.message) isRevoked <- bitString.isRevoked(credentialStatus.statusListIndex).mapError(_.message) revocationValidation = if (isRevoked) Validation.fail("Credential is revoked") else Validation.unit @@ -734,9 +618,8 @@ object CredentialVerification { object JwtCredential { - import CredentialPayload.Implicits.* - - def encodeJwt(payload: JwtCredentialPayload, issuer: Issuer): JWT = issuer.signer.encode(payload.asJson) + def encodeJwt(payload: JwtCredentialPayload, issuer: Issuer): JWT = + issuer.signer.encode(payload.toJsonAST.getOrElse(UnexpectedCodeExecutionPath)) def decodeJwt(jwt: JWT, publicKey: PublicKey): Try[JwtCredentialPayload] = { val signedJWT = SignedJWT.parse(jwt.value) @@ -746,19 +629,19 @@ object JwtCredential { if isSignatureValid then val claimsSet = signedJWT.getJWTClaimsSet.toString - decode[JwtCredentialPayload](claimsSet).toTry + claimsSet.fromJson[JwtCredentialPayload].left.map(s => new RuntimeException(s)).toTry else Failure(Exception(s"Invalid JWT signature for: ${JWT.value}")) } def decodeJwt(jwt: JWT): IO[String, JwtCredentialPayload] = { val decodeJWT = - ZIO.fromTry(JwtCirce.decodeRawAll(jwt.value, JwtOptions(false, false, false))).mapError(_.getMessage) + ZIO.fromTry(JwtZIOJson.decodeRawAll(jwt.value, JwtOptions(false, false, false))).mapError(_.getMessage) val validatedDecodedClaim: IO[String, JwtCredentialPayload] = for { decodedJwtTask <- decodeJWT (_, claim, _) = decodedJwtTask - decodedClaim <- ZIO.fromEither(decode[JwtCredentialPayload](claim).left.map(_.toString)) + decodedClaim <- ZIO.fromEither(claim.fromJson[JwtCredentialPayload]) } yield decodedClaim validatedDecodedClaim @@ -774,7 +657,7 @@ object JwtCredential { proofPurpose: Option[VerificationRelationship] = None )(didResolver: DidResolver): IO[String, Validation[String, Unit]] = { JWTVerification.validateEncodedJwt(jwt, proofPurpose)(didResolver: DidResolver)(claim => - Validation.fromEither(decode[JwtCredentialPayload](claim).left.map(_.toString)) + Validation.fromEither(claim.fromJson[JwtCredentialPayload]) )(_.iss) } @@ -782,14 +665,14 @@ object JwtCredential { jwt: JWT, )(didResolver: DidResolver): IO[String, Validation[String, DIDDocument]] = { JWTVerification.validateIssuer(jwt)(didResolver: DidResolver)(claim => - Validation.fromEither(decode[JwtCredentialPayload](claim).left.map(_.toString)) + Validation.fromEither(claim.fromJson[JwtCredentialPayload]) )(_.iss) } def validateExpiration(jwt: JWT, dateTime: OffsetDateTime): Validation[String, Unit] = { Validation .fromTry( - JwtCirce(Clock.fixed(dateTime.toInstant, ZoneId.of(dateTime.getOffset.getId))) + JwtZIOJson(Clock.fixed(dateTime.toInstant, ZoneId.of(dateTime.getOffset.getId))) .decodeRawAll(jwt.value, JwtOptions(false, true, false)) ) .flatMap(_ => Validation.unit) @@ -799,7 +682,7 @@ object JwtCredential { def validateNotBefore(jwt: JWT, dateTime: OffsetDateTime): Validation[String, Unit] = { Validation .fromTry( - JwtCirce(Clock.fixed(dateTime.toInstant, ZoneId.of(dateTime.getOffset.getId))) + JwtZIOJson(Clock.fixed(dateTime.toInstant, ZoneId.of(dateTime.getOffset.getId))) .decodeRawAll(jwt.value, JwtOptions(false, false, true)) ) .flatMap(_ => Validation.unit) @@ -809,12 +692,12 @@ object JwtCredential { def verifyDates(jwt: JWT, leeway: TemporalAmount)(implicit clock: Clock): Validation[String, Unit] = { val decodeJWT = Validation - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) .mapError(_.getMessage) for { decodedJWT <- decodeJWT - jwtCredentialPayload <- Validation.fromEither(decode[JwtCredentialPayload](decodedJWT)).mapError(_.getMessage) + jwtCredentialPayload <- Validation.fromEither(decodedJWT.fromJson[JwtCredentialPayload]) nbf = jwtCredentialPayload.nbf maybeExp = jwtCredentialPayload.maybeExp maybeValidFrom = jwtCredentialPayload.vc.maybeValidFrom @@ -851,12 +734,12 @@ object JwtCredential { def verifyRevocationStatusJwt(jwt: JWT)(uriResolver: UriResolver): IO[String, Validation[String, Unit]] = { val decodeJWT = ZIO - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) + .fromTry(JwtZIOJson.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) .mapError(_.getMessage) val res = for { decodedJWT <- decodeJWT - jwtCredentialPayload <- ZIO.fromEither(decode[JwtCredentialPayload](decodedJWT)).mapError(_.getMessage) + jwtCredentialPayload <- ZIO.fromEither(decodedJWT.fromJson[JwtCredentialPayload]) credentialStatus = jwtCredentialPayload.vc.maybeCredentialStatus .map { { @@ -876,14 +759,12 @@ object JwtCredential { object W3CCredential { - import CredentialPayload.Implicits.* - def encodeW3C(payload: W3cCredentialPayload, issuer: Issuer): W3cVerifiableCredentialPayload = { W3cVerifiableCredentialPayload( payload = payload, proof = JwtProof( `type` = "JwtProof2020", - jwt = issuer.signer.encode(payload.asJson) + jwt = issuer.signer.encode(payload.toJsonAST.getOrElse(UnexpectedCodeExecutionPath)) ) ) } @@ -892,15 +773,14 @@ object W3CCredential { JwtCredential.encodeJwt(payload.toJwtCredentialPayload, issuer) def toJsonWithEmbeddedProof(payload: W3cCredentialPayload, issuer: Issuer): Task[Json] = { - val jsonCred = payload.asJson + val jsonCred = payload.toJsonAST.toOption.flatMap(_.asObject).getOrElse(UnexpectedCodeExecutionPath) for { proof <- issuer.signer.generateProofForJson(jsonCred, issuer.publicKey) jsonProof <- proof match - case b: EcdsaSecp256k1Signature2019Proof => ZIO.succeed(b.asJson.dropNullValues) - case c: EddsaJcs2022Proof => ZIO.succeed(c.asJson.dropNullValues) - case _: DataIntegrityProof => UnexpectedCodeExecutionPath - verifiableCredentialWithProof = jsonCred.deepMerge(Map("proof" -> jsonProof).asJson) + case b: EcdsaSecp256k1Signature2019Proof => ZIO.succeed(b.toJsonAST.getOrElse(UnexpectedCodeExecutionPath)) + case c: EddsaJcs2022Proof => ZIO.succeed(c.toJsonAST.getOrElse(UnexpectedCodeExecutionPath)) + verifiableCredentialWithProof = jsonCred.add("proof", jsonProof) } yield verifiableCredentialWithProof } @@ -910,7 +790,7 @@ object W3CCredential { proofPurpose: Option[VerificationRelationship] = None )(didResolver: DidResolver): IO[String, Validation[String, Unit]] = { JWTVerification.validateEncodedJwt(payload.proof.jwt, proofPurpose)(didResolver: DidResolver)(claim => - Validation.fromEither(decode[W3cCredentialPayload](claim).left.map(_.toString)) + Validation.fromEither(claim.fromJson[W3cCredentialPayload]) )(vc => vc.issuer match { case string: String => string diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala index ffe96ca470..f08496efd2 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala @@ -1,14 +1,11 @@ package org.hyperledger.identus.pollux.vc.jwt -import io.circe -import io.circe.* -import io.circe.generic.auto.* -import io.circe.parser.decode -import io.circe.syntax.* import org.hyperledger.identus.castor.core.model.did.VerificationRelationship import org.hyperledger.identus.shared.http.UriResolver -import pdi.jwt.{JwtCirce, JwtOptions} +import pdi.jwt.{JwtOptions, JwtZIOJson} import zio.* +import zio.json.{DecoderOps, DeriveJsonDecoder, DeriveJsonEncoder, EncoderOps, JsonDecoder, JsonEncoder} +import zio.json.ast.{Json, JsonCursor} import zio.prelude.* import java.security.PublicKey @@ -18,14 +15,34 @@ import scala.util.Try sealed trait VerifiablePresentationPayload -case class Prover(did: String, signer: Signer, publicKey: PublicKey) +object VerifiablePresentationPayload { + given JsonDecoder[VerifiablePresentationPayload] = JsonDecoder[Json].mapOrFail { json => + json + .as[JwtVerifiablePresentationPayload] + .orElse(json.as[W3cVerifiablePresentationPayload]) + } +} case class W3cVerifiablePresentationPayload(payload: W3cPresentationPayload, proof: JwtProof) extends Verifiable(proof), VerifiablePresentationPayload +object W3cVerifiablePresentationPayload { + given JsonDecoder[W3cVerifiablePresentationPayload] = JsonDecoder[Json].mapOrFail { json => + for { + payload <- json.as[W3cPresentationPayload] + proof <- json.get(JsonCursor.field("proof")).flatMap(_.as[JwtProof]) + } yield W3cVerifiablePresentationPayload(payload, proof) + } +} + case class JwtVerifiablePresentationPayload(jwt: JWT) extends VerifiablePresentationPayload +object JwtVerifiablePresentationPayload { + given JsonDecoder[JwtVerifiablePresentationPayload] = + JsonDecoder.string.map(s => JwtVerifiablePresentationPayload(JWT(s))) +} + sealed trait PresentationPayload( `@context`: IndexedSeq[String], `type`: IndexedSeq[String], @@ -90,12 +107,106 @@ case class W3cPresentationPayload( maybeNonce = maybeNonce ) +object W3cPresentationPayload { + import JsonEncoders.given + private case class Json_W3cPresentationPayload( + `@context`: String | IndexedSeq[String], + `type`: String | IndexedSeq[String], + id: Option[String], + verifiableCredential: IndexedSeq[VerifiableCredentialPayload], + holder: String, + verifier: String | IndexedSeq[String], + issuanceDate: Option[Instant], + expirationDate: Option[Instant] + ) + + private given JsonEncoder[Json_W3cPresentationPayload] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_W3cPresentationPayload] = DeriveJsonDecoder.gen + + given JsonEncoder[W3cPresentationPayload] = JsonEncoder[Json_W3cPresentationPayload].contramap { payload => + Json_W3cPresentationPayload( + payload.`@context`, + payload.`type`, + payload.maybeId, + payload.verifiableCredential, + payload.holder, + payload.verifier, + payload.maybeIssuanceDate, + payload.maybeExpirationDate + ) + } + given JsonDecoder[W3cPresentationPayload] = JsonDecoder[Json_W3cPresentationPayload].map { payload => + W3cPresentationPayload( + payload.`@context` match + case str: String => IndexedSeq(str) + case set: IndexedSeq[String] => set + , + payload.id, + payload.`type` match + case str: String => IndexedSeq(str) + case set: IndexedSeq[String] => set + , + payload.verifiableCredential match + case str: VerifiableCredentialPayload => IndexedSeq(str) + case set: IndexedSeq[VerifiableCredentialPayload] => set + , + payload.holder, + payload.verifier match + case str: String => IndexedSeq(str) + case set: IndexedSeq[String] => set + , + payload.issuanceDate, + payload.expirationDate, + None + ) + } +} + case class JwtVp( `@context`: IndexedSeq[String], `type`: IndexedSeq[String], verifiableCredential: IndexedSeq[VerifiableCredentialPayload] ) +object JwtVp { + private case class Json_JwtVp( + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + verifiableCredential: IndexedSeq[VerifiableCredentialPayload] + ) + + private given JsonEncoder[Json_JwtVp] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_JwtVp] = JsonDecoder[Json].mapOrFail { json => + for { + context <- json + .get(JsonCursor.field("@context")) + .flatMap(ctx => ctx.as[String].map(IndexedSeq(_)).orElse(ctx.as[IndexedSeq[String]])) + typ <- json + .get(JsonCursor.field("type")) + .flatMap(ctx => ctx.as[String].map(IndexedSeq(_)).orElse(ctx.as[IndexedSeq[String]])) + vcp <- json + .get(JsonCursor.field("verifiableCredential")) + .flatMap(ctx => + ctx + .as[VerifiableCredentialPayload] + .map(IndexedSeq(_)) + .orElse(ctx.as[IndexedSeq[VerifiableCredentialPayload]]) + ) + } yield Json_JwtVp(context, typ, vcp) + } + + given JsonEncoder[JwtVp] = JsonEncoder[Json_JwtVp].contramap { payload => + Json_JwtVp( + payload.`@context`, + payload.`type`, + payload.verifiableCredential + ) + } + given JsonDecoder[JwtVp] = JsonDecoder[Json_JwtVp].map { payload => + JwtVp(payload.`@context`, payload.`type`, payload.verifiableCredential) + } +} + case class JwtPresentationPayload( iss: String, vp: JwtVp, @@ -116,6 +227,48 @@ case class JwtPresentationPayload( maybeNonce = maybeNonce ) +object JwtPresentationPayload { + import JsonEncoders.given + private case class Json_JwtPresentationPayload( + iss: String, + vp: JwtVp, + nbf: Option[Instant], + aud: String | IndexedSeq[String] = IndexedSeq.empty, + exp: Option[Instant], + jti: Option[String], + nonce: Option[String] + ) + + private given JsonEncoder[Json_JwtPresentationPayload] = DeriveJsonEncoder.gen + private given JsonDecoder[Json_JwtPresentationPayload] = DeriveJsonDecoder.gen + + given JsonEncoder[JwtPresentationPayload] = JsonEncoder[Json_JwtPresentationPayload].contramap { payload => + Json_JwtPresentationPayload( + payload.iss, + payload.vp, + payload.maybeNbf, + payload.aud, + payload.maybeExp, + payload.maybeJti, + payload.maybeNonce + ) + } + given JsonDecoder[JwtPresentationPayload] = JsonDecoder[Json_JwtPresentationPayload].map { payload => + JwtPresentationPayload( + payload.iss, + payload.vp, + payload.nbf, + payload.aud match + case str: String => IndexedSeq(str) + case set: IndexedSeq[String] => set.distinct + , + payload.exp, + payload.jti, + payload.nonce + ) + } +} + //FIXME THIS WILL NOT WORK like that case class AnomcredVp( `@context`: IndexedSeq[String], @@ -142,188 +295,17 @@ case class AnomcredPresentationPayload( maybeNonce = maybeNonce ) -object PresentationPayload { - - object Implicits { - - import CredentialPayload.Implicits.* - import InstantDecoderEncoder.* - import JwtProof.Implicits.* - - implicit val w3cPresentationPayloadEncoder: Encoder[W3cPresentationPayload] = - (w3cPresentationPayload: W3cPresentationPayload) => - Json - .obj( - ("@context", w3cPresentationPayload.`@context`.asJson), - ("id", w3cPresentationPayload.maybeId.asJson), - ("type", w3cPresentationPayload.`type`.asJson), - ("verifiableCredential", w3cPresentationPayload.verifiableCredential.asJson), - ("holder", w3cPresentationPayload.holder.asJson), - ("verifier", w3cPresentationPayload.verifier.asJson), - ("issuanceDate", w3cPresentationPayload.maybeIssuanceDate.asJson), - ("expirationDate", w3cPresentationPayload.maybeExpirationDate.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val jwtVpEncoder: Encoder[JwtVp] = - (jwtVp: JwtVp) => - Json - .obj( - ("@context", jwtVp.`@context`.asJson), - ("type", jwtVp.`type`.asJson), - ("verifiableCredential", jwtVp.verifiableCredential.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val jwtPresentationPayloadEncoder: Encoder[JwtPresentationPayload] = - (jwtPresentationPayload: JwtPresentationPayload) => - Json - .obj( - ("iss", jwtPresentationPayload.iss.asJson), - ("vp", jwtPresentationPayload.vp.asJson), - ("nbf", jwtPresentationPayload.maybeNbf.asJson), - ("aud", jwtPresentationPayload.aud.asJson), - ("exp", jwtPresentationPayload.maybeExp.asJson), - ("jti", jwtPresentationPayload.maybeJti.asJson), - ("nonce", jwtPresentationPayload.maybeNonce.asJson) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val w3cPresentationPayloadDecoder: Decoder[W3cPresentationPayload] = - (c: HCursor) => - for { - `@context` <- c - .downField("@context") - .as[IndexedSeq[String]] - .orElse(c.downField("@context").as[String].map(IndexedSeq(_))) - maybeId <- c.downField("id").as[Option[String]] - `type` <- c - .downField("type") - .as[IndexedSeq[String]] - .orElse(c.downField("type").as[String].map(IndexedSeq(_))) - holder <- c.downField("holder").as[String] - verifiableCredential <- c - .downField("verifiableCredential") - .as[Option[VerifiableCredentialPayload]] - .map(_.iterator.toIndexedSeq) - .orElse( - c.downField("verifiableCredential") - .as[Option[IndexedSeq[VerifiableCredentialPayload]]] - .map(_.iterator.toIndexedSeq.flatten) - ) - verifier <- c - .downField("verifier") - .as[Option[String]] - .map(_.iterator.toIndexedSeq) - .orElse(c.downField("verifier").as[Option[IndexedSeq[String]]].map(_.iterator.toIndexedSeq.flatten)) - maybeIssuanceDate <- c.downField("issuanceDate").as[Option[Instant]] - maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]] - } yield { - W3cPresentationPayload( - `@context` = `@context`.distinct, - maybeId = maybeId, - `type` = `type`.distinct, - verifiableCredential = verifiableCredential.distinct, - holder = holder, - verifier = verifier.distinct, - maybeIssuanceDate = maybeIssuanceDate, - maybeExpirationDate = maybeExpirationDate, - maybeNonce = Option.empty - ) - } - - implicit val jwtVpDecoder: Decoder[JwtVp] = - (c: HCursor) => - for { - `@context` <- c - .downField("@context") - .as[IndexedSeq[String]] - .orElse(c.downField("@context").as[String].map(IndexedSeq(_))) - `type` <- c - .downField("type") - .as[IndexedSeq[String]] - .orElse(c.downField("type").as[String].map(IndexedSeq(_))) - maybeVerifiableCredential <- c - .downField("verifiableCredential") - .as[Option[IndexedSeq[VerifiableCredentialPayload]]] - } yield { - JwtVp( - `@context` = `@context`.distinct, - `type` = `type`.distinct, - verifiableCredential = maybeVerifiableCredential.toIndexedSeq.flatten - ) - } - - implicit val JwtPresentationPayloadDecoder: Decoder[JwtPresentationPayload] = - (c: HCursor) => - for { - iss <- c.downField("iss").as[String] - vp <- c.downField("vp").as[JwtVp] - maybeNbf <- c.downField("nbf").as[Option[Instant]] - aud <- c - .downField("aud") - .as[Option[String]] - .map(_.iterator.toIndexedSeq) - .orElse(c.downField("aud").as[Option[IndexedSeq[String]]].map(_.iterator.toIndexedSeq.flatten)) - maybeExp <- c.downField("exp").as[Option[Instant]] - maybeJti <- c.downField("jti").as[Option[String]] - maybeNonce <- c.downField("nonce").as[Option[String]] - } yield { - JwtPresentationPayload( - iss = iss, - vp = vp, - maybeNbf = maybeNbf, - aud = aud.distinct, - maybeExp = maybeExp, - maybeJti = maybeJti, - maybeNonce = maybeNonce - ) - } - - implicit val w3cVerifiablePresentationPayloadDecoder: Decoder[W3cVerifiablePresentationPayload] = - (c: HCursor) => - for { - payload <- c.as[W3cPresentationPayload] - proof <- c.downField("proof").as[JwtProof] - } yield { - W3cVerifiablePresentationPayload( - payload = payload, - proof = proof - ) - } - - implicit val jwtVerifiablePresentationPayloadDecoder: Decoder[JwtVerifiablePresentationPayload] = - (c: HCursor) => - for { - jwt <- c.as[String] - } yield { - JwtVerifiablePresentationPayload( - jwt = JWT(jwt) - ) - } - - implicit val verifiablePresentationPayloadDecoder: Decoder[VerifiablePresentationPayload] = - jwtVerifiablePresentationPayloadDecoder.or( - w3cVerifiablePresentationPayloadDecoder.asInstanceOf[Decoder[VerifiablePresentationPayload]] - ) - } -} - object JwtPresentation { - import PresentationPayload.Implicits.* - - def encodeJwt(payload: JwtPresentationPayload, issuer: Issuer): JWT = issuer.signer.encode(payload.asJson) + def encodeJwt(payload: JwtPresentationPayload, issuer: Issuer): JWT = + issuer.signer.encode(payload.toJsonAST.toOption.get) def toEncodeW3C(payload: W3cPresentationPayload, issuer: Issuer): W3cVerifiablePresentationPayload = { W3cVerifiablePresentationPayload( payload = payload, proof = JwtProof( `type` = "JwtProof2020", - jwt = issuer.signer.encode(payload.asJson) + jwt = issuer.signer.encode(payload.toJsonAST.toOption.get) ) ) } @@ -331,22 +313,10 @@ object JwtPresentation { def toEncodedJwt(payload: W3cPresentationPayload, issuer: Issuer): JWT = encodeJwt(payload.toJwtPresentationPayload, issuer) - def decodeJwt(jwt: JWT): Try[JwtPresentationPayload] = { - JwtCirce - .decodeRaw(jwt.value, JwtOptions(signature = false, expiration = false, notBefore = false)) - .flatMap(decode[JwtPresentationPayload](_).toTry) - } - - def decodeJwt[A](jwt: JWT)(using decoder: io.circe.Decoder[A]): Try[A] = { - JwtCirce + def decodeJwt[A](jwt: JWT)(using decoder: JsonDecoder[A]): Try[A] = { + JwtZIOJson .decodeRaw(jwt.value, options = JwtOptions(signature = false, expiration = false, notBefore = false)) - .flatMap(decode[A](_).toTry) - } - - def decodeJwt(jwt: JWT, publicKey: PublicKey): Try[JwtPresentationPayload] = { - JwtCirce - .decodeRaw(jwt.value, publicKey, JwtOptions(expiration = false, notBefore = false)) - .flatMap(decode[JwtPresentationPayload](_).toTry) + .flatMap(a => a.fromJson[A].left.map(s => new RuntimeException(s)).toTry) } def validateEncodedJwt(jwt: JWT, publicKey: PublicKey): Validation[String, Unit] = @@ -357,7 +327,7 @@ object JwtPresentation { proofPurpose: Option[VerificationRelationship] )(didResolver: DidResolver): IO[String, Validation[String, Unit]] = { JWTVerification.validateEncodedJwt(jwt, proofPurpose)(didResolver: DidResolver)(claim => - Validation.fromEither(decode[JwtPresentationPayload](claim).left.map(_.toString)) + Validation.fromEither(claim.fromJson[JwtPresentationPayload]) )(_.iss) } @@ -366,7 +336,7 @@ object JwtPresentation { proofPurpose: Option[VerificationRelationship] )(didResolver: DidResolver): IO[String, Validation[String, Unit]] = { JWTVerification.validateEncodedJwt(jwt, proofPurpose)(didResolver: DidResolver)(claim => - Validation.fromEither(decode[W3cPresentationPayload](claim).left.map(_.toString)) + Validation.fromEither(claim.fromJson[W3cPresentationPayload]) )(_.holder) } @@ -440,7 +410,6 @@ object JwtPresentation { decodedJwtPresentation: JwtPresentationPayload, schemaIdAndTrustedIssuers: Seq[CredentialSchemaAndTrustedIssuersConstraint] ): Validation[String, Unit] = { - import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* val vcList = decodedJwtPresentation.vp.verifiableCredential val expectedSchemaIds = schemaIdAndTrustedIssuers.map(_.schemaId) @@ -520,7 +489,6 @@ object JwtPresentation { } def verifyHolderBinding(jwt: JWT): Validation[String, Unit] = { - import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* def validateCredentialSubjectId( vcList: IndexedSeq[VerifiableCredentialPayload], @@ -530,9 +498,9 @@ object JwtPresentation { .validateAll( vcList.map { case (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) => - val mayBeSubjectDid = w3cVerifiableCredentialPayload.payload.credentialSubject.hcursor - .downField("id") - .as[String] + val mayBeSubjectDid = w3cVerifiableCredentialPayload.payload.credentialSubject + .get(JsonCursor.field("id").isString) + .map(_.value) .toOption if (mayBeSubjectDid.contains(iss)) { Validation.unit diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala index e4ea76cb6b..a1143b5813 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021.scala @@ -1,10 +1,9 @@ package org.hyperledger.identus.pollux.vc.jwt.revocation -import io.circe.{Json, JsonObject} -import io.circe.syntax.* import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.pollux.vc.jwt.revocation.VCStatusList2021Error.{DecodingError, EncodingError} import zio.* +import zio.json.ast.{Json, JsonCursor} import java.time.Instant @@ -12,16 +11,16 @@ class VCStatusList2021 private (val vcPayload: W3cCredentialPayload, jwtIssuer: def encoded: UIO[JWT] = ZIO.succeed(W3CCredential.toEncodedJwt(vcPayload, jwtIssuer)) - def toJsonWithEmbeddedProof: Task[Json] = W3CCredential.toJsonWithEmbeddedProof(vcPayload, jwtIssuer) + def toJsonWithEmbeddedProof: Task[Json] = + W3CCredential.toJsonWithEmbeddedProof(vcPayload, jwtIssuer) def updateBitString(bitString: BitString): IO[VCStatusList2021Error, VCStatusList2021] = { - import CredentialPayload.Implicits.* val res = for { vcId <- ZIO.fromOption(vcPayload.maybeId).mapError(_ => DecodingError("VC id not found")) purpose <- ZIO - .fromEither(vcPayload.credentialSubject.hcursor.downField("statusPurpose").as[StatusPurpose]) - .mapError(x => DecodingError(x.message)) + .fromEither(vcPayload.credentialSubject.get(JsonCursor.field("statusPurpose")).flatMap(_.as[StatusPurpose])) + .mapError(x => DecodingError(x)) } yield VCStatusList2021.build(vcId, jwtIssuer, bitString, purpose) res.flatten @@ -31,7 +30,7 @@ class VCStatusList2021 private (val vcPayload: W3cCredentialPayload, jwtIssuer: for { encodedBitString <- ZIO .fromOption( - vcPayload.credentialSubject.hcursor.downField("encodedList").as[String].toOption + vcPayload.credentialSubject.get(JsonCursor.field("encodedList").isString).map(_.value).toOption ) .mapError(_ => DecodingError("'encodedList' attribute not found in credential subject")) bitString <- BitString.valueOf(encodedBitString).mapError(e => DecodingError(e.message)) @@ -50,10 +49,11 @@ object VCStatusList2021 { for { encodedBitString <- revocationData.encoded.mapError(e => EncodingError(e.message)) } yield { - val claims = JsonObject() - .add("type", "StatusList2021".asJson) - .add("statusPurpose", purpose.toString.asJson) - .add("encodedList", encodedBitString.asJson) + val claims = Json + .Obj() + .add("type", Json.Str("StatusList2021")) + .add("statusPurpose", Json.Str(purpose.toString)) + .add("encodedList", Json.Str(encodedBitString)) val w3Credential = W3cCredentialPayload( `@context` = Set( "https://www.w3.org/2018/credentials/v1", @@ -65,7 +65,7 @@ object VCStatusList2021 { issuanceDate = Instant.now, maybeExpirationDate = None, maybeCredentialSchema = None, - credentialSubject = claims.asJson, + credentialSubject = claims, maybeCredentialStatus = None, maybeRefreshService = None, maybeEvidence = None, @@ -78,11 +78,10 @@ object VCStatusList2021 { } def decodeFromJson(json: Json, issuer: Issuer): IO[DecodingError, VCStatusList2021] = { - import CredentialPayload.Implicits.* for { w3cCredentialPayload <- ZIO - .fromEither(io.circe.parser.decode[W3cCredentialPayload](json.noSpaces)) - .mapError(t => DecodingError(t.getMessage)) + .fromEither(json.as[W3cCredentialPayload]) + .mapError(t => DecodingError(t)) } yield VCStatusList2021(w3cCredentialPayload, issuer) } diff --git a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala index 8222a4fe64..074e1f7704 100644 --- a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala +++ b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JWTVerificationTest.scala @@ -3,14 +3,12 @@ package org.hyperledger.identus.pollux.vc.jwt import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.jwk.{Curve, ECKey} import com.nimbusds.jose.jwk.gen.ECKeyGenerator -import io.circe.* -import io.circe.syntax.* import org.hyperledger.identus.castor.core.model.did.{DID, VerificationRelationship} -import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* import org.hyperledger.identus.pollux.vc.jwt.StatusPurpose.Revocation import org.hyperledger.identus.shared.http.* import zio.* -import zio.prelude.Validation +import zio.json.ast.Json +import zio.json.EncoderOps import zio.test.* import zio.test.Assertion.* @@ -79,7 +77,7 @@ object JWTVerificationTest extends ZIOSpecDefault { `@context` = Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), maybeCredentialSchema = None, - credentialSubject = Json.obj("id" -> Json.fromString("1")), + credentialSubject = Json.Obj("id" -> Json.Str("1")), maybeCredentialStatus = credentialStatus, maybeRefreshService = None, maybeEvidence = None, @@ -96,7 +94,7 @@ object JWTVerificationTest extends ZIOSpecDefault { maybeExp = Some(jwtCredentialExp), // EXPIRATION DATE maybeJti = Some("http://example.edu/credentials/3732") // CREDENTIAL ID ) - issuer.issuer.signer.encode(jwtCredentialPayload.asJson) + issuer.issuer.signer.encode(jwtCredentialPayload.toJsonAST.toOption.get) } private def generateDidDocument( diff --git a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021Spec.scala b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021Spec.scala index 218184f64a..8e7d489642 100644 --- a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021Spec.scala +++ b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/VCStatusList2021Spec.scala @@ -4,7 +4,8 @@ import org.hyperledger.identus.castor.core.model.did.DID import org.hyperledger.identus.pollux.vc.jwt.{ES256KSigner, Issuer, JwtCredential} import org.hyperledger.identus.shared.crypto.KmpSecp256k1KeyOps import zio.{UIO, ZIO} -import zio.test.{assertTrue, Spec, ZIOSpecDefault} +import zio.json.ast.JsonCursor +import zio.test.{assertTrue, ZIOSpecDefault} object VCStatusList2021Spec extends ZIOSpecDefault { @@ -33,7 +34,7 @@ object VCStatusList2021Spec extends ZIOSpecDefault { statusList <- VCStatusList2021.build(VC_ID, issuer, bitString) json <- statusList.toJsonWithEmbeddedProof } yield { - assertTrue(json.hcursor.downField("proof").focus.isDefined) + assertTrue(json.get(JsonCursor.field("proof")).isRight) } }, test("Generate VC contains required fields in 'credentialSubject'") { @@ -43,7 +44,7 @@ object VCStatusList2021Spec extends ZIOSpecDefault { statusList <- VCStatusList2021.build(VC_ID, issuer, bitString) encodedJwtVC <- statusList.encoded jwtVCPayload <- ZIO.fromTry(JwtCredential.decodeJwt(encodedJwtVC, issuer.publicKey)) - credentialSubjectKeys <- ZIO.fromOption(jwtVCPayload.credentialSubject.hcursor.keys) + credentialSubjectKeys <- ZIO.fromOption(jwtVCPayload.credentialSubject.asObject.map(_.keys.toSet)) } yield { assertTrue(credentialSubjectKeys.toSet == Set("type", "statusPurpose", "encodedList")) } diff --git a/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonInterop.scala b/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonInterop.scala deleted file mode 100644 index bd6e41ab51..0000000000 --- a/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonInterop.scala +++ /dev/null @@ -1,25 +0,0 @@ -package org.hyperledger.identus.shared.json - -import io.circe.Json as CirceJson -import zio.json.* -import zio.json.ast.Json as ZioJson - -object JsonInterop { - def toZioJsonAst(circeJson: CirceJson): ZioJson = { - val encoded = circeJson.noSpaces - encoded.fromJson[ZioJson] match { - case Left(failure) => - throw Exception(s"Circe and Zio Json interop fail. Unable to convert from Circe to Zio AST. $failure") - case Right(value) => value - } - } - - def toCirceJsonAst(zioJson: ZioJson): CirceJson = { - val encoded = zioJson.toJson - io.circe.parser.parse(encoded).left.map(_.toString) match { - case Left(failure) => - throw Exception(s"Circe and Zio Json interop fail. Unable to convert from Zio to Circe AST. $failure") - case Right(value) => value - } - } -} diff --git a/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonOps.scala b/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonOps.scala new file mode 100644 index 0000000000..5e70affd0f --- /dev/null +++ b/shared/json/src/main/scala/org/hyperledger/identus/shared/json/JsonOps.scala @@ -0,0 +1,21 @@ +package org.hyperledger.identus.shared.json + +import zio.json.ast.Json + +object JsonOps { + extension (json: Json) { + def removeNullValues: Json = json match + case Json.Obj(fields) => + Json.Obj(fields.collect { case (key, value) if value != Json.Null => key -> value.removeNullValues }) + case Json.Arr(elements) => + Json.Arr(elements.map(_.removeNullValues)) + case other => other + + def removeField(name: String): Json = json match + case Json.Obj(fields) => + Json.Obj(fields.filterNot { case (key, value) => key == name }) + case Json.Arr(elements) => + Json.Arr(elements.map(_.removeField(name))) + case other => other + } +}