From de2af2f913cd161d7f9c1ad7e08888a965beea77 Mon Sep 17 00:00:00 2001 From: Bassam <84336050+CryptoKnightIOG@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:25:45 -0400 Subject: [PATCH] ATL-1932: Create JWT Verifiable Presentation (#69) * [ATL-1932] feat(pollux): replaced cats by zio * [ATL-1932] feat(pollux): use opeque tupe alias * [ATL-1932] feat(pollux): add VerifiablePayload Abstraction * [ATL-1932] feat(pollux): added VerifiablePresentation --- pollux/vc-jwt/project/Dependencies.scala | 7 +- .../io/iohk/atala/pollux/vc/jwt/DidJWT.scala | 60 ++- .../iohk/atala/pollux/vc/jwt/Verifiable.scala | 31 ++ .../pollux/vc/jwt/VerifiableCredential.scala | 394 ---------------- .../vc/jwt/VerifiableCredentialPayload.scala | 439 ++++++++++++++++++ .../vc/jwt/VerifiablePresentation.scala | 13 - .../jwt/VerifiablePresentationPayload.scala | 288 ++++++++++++ .../pollux/vc/jwt/demos/CredentialDemo.scala | 27 +- .../atala/pollux/vc/jwt/demos/JWTDemo.scala | 14 +- .../demos/JwtCredentialEncondingDemo.scala | 16 +- 10 files changed, 822 insertions(+), 467 deletions(-) create mode 100644 pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/Verifiable.scala delete mode 100644 pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredential.scala create mode 100644 pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala delete mode 100644 pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentation.scala create mode 100644 pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentationPayload.scala diff --git a/pollux/vc-jwt/project/Dependencies.scala b/pollux/vc-jwt/project/Dependencies.scala index 0dde333133..b6fb4ad493 100644 --- a/pollux/vc-jwt/project/Dependencies.scala +++ b/pollux/vc-jwt/project/Dependencies.scala @@ -4,6 +4,7 @@ object Dependencies { object Versions { val circeVersion = "0.14.3" val jwtCirceVersion = "9.1.1" + val zioPreludeVersion = "1.0.0-RC15" } private lazy val coreJwtCirce = ("io.circe" %% "circe-core" % Versions.circeVersion).cross(CrossVersion.for3Use2_13) @@ -18,11 +19,15 @@ object Dependencies { private lazy val jwtCirce = ("com.github.jwt-scala" %% "jwt-circe" % Versions.jwtCirceVersion).cross(CrossVersion.for3Use2_13) + private lazy val zioPrelude = "dev.zio" %% "zio-prelude" % Versions.zioPreludeVersion + private lazy val test = "org.scalameta" %% "munit" % "0.7.29" % Test // Dependency Modules + private lazy val zioDependencies: Seq[ModuleID] = Seq(zioPrelude) private lazy val circeDependencies: Seq[ModuleID] = Seq(coreJwtCirce, genericJwtCirce, parserJwtCirce) - private lazy val baseDependencies: Seq[ModuleID] = circeDependencies :+ jwtCirce :+ circeJsonSchema :+ test + private lazy val baseDependencies: Seq[ModuleID] = + circeDependencies ++ zioDependencies :+ jwtCirce :+ circeJsonSchema :+ test // Project Dependencies lazy val polluxVcJwtDependencies: Seq[ModuleID] = baseDependencies diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/DidJWT.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/DidJWT.scala index 542de0caf9..1bfee9bcdd 100644 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/DidJWT.scala +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/DidJWT.scala @@ -1,32 +1,45 @@ package io.iohk.atala.pollux.vc.jwt -import java.security.PrivateKey -import java.security.spec.ECGenParameterSpec -import java.sql.Timestamp -import java.time.{Instant, ZonedDateTime} +import io.circe import io.circe.* -import pdi.jwt.JwtClaim -import pdi.jwt.{JwtAlgorithm, JwtCirce} +import io.circe.generic.auto.* +import io.circe.parser.decode +import io.circe.syntax.* +import net.reactivecore.cjs.resolver.Downloader +import net.reactivecore.cjs.{DocumentValidator, Loader, Result} +import pdi.jwt.algorithms.JwtECDSAAlgorithm +import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} import java.security.* import java.security.spec.* -import java.time.Instant -import net.reactivecore.cjs.Loader -import net.reactivecore.cjs.{DocumentValidator, Loader, Result} -import net.reactivecore.cjs.resolver.Downloader -import cats.implicits.* -import io.circe.Json -import pdi.jwt.algorithms.JwtECDSAAlgorithm +import java.sql.Timestamp +import java.time.{Instant, ZonedDateTime} + +opaque type JWT = String + +object JWT { + def apply(value: String): JWT = value + + extension (jwt: JWT) { + def value: String = jwt + } + + object Implicits { + implicit val jwtEncoder: Encoder[JWT] = + (jwt: JWT) => jwt.value.asJson + } +} + -case class EncodedJWT(jwt: String) case class JWTHeader(typ: String = "JWT", alg: Option[String]) + case class JWTPayload( - iss: Option[String], - sub: Option[String], - aud: Vector[String], - iat: Option[Instant], - nbf: Option[Instant], - exp: Option[Instant], + iss: Option[String], + sub: Option[String], + aud: Vector[String], + iat: Option[Instant], + nbf: Option[Instant], + exp: Option[Instant], rexp: Option[Instant] ) trait JWTVerified( @@ -47,12 +60,13 @@ case class JWTVerifyPolicies( ) trait Signer { - def encode(claim: Json): String + def encode(claim: Json): JWT } class ES256Signer(privateKey: PrivateKey) extends Signer { val algorithm: JwtECDSAAlgorithm = JwtAlgorithm.ES256 - override def encode(claim: Json): String = { - return JwtCirce.encode(claim, privateKey, algorithm) + + override def encode(claim: Json): JWT = { + return JWT(JwtCirce.encode(claim, privateKey, algorithm)) } } diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/Verifiable.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/Verifiable.scala new file mode 100644 index 0000000000..309d8f63e7 --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/Verifiable.scala @@ -0,0 +1,31 @@ +package io.iohk.atala.pollux.vc.jwt + +import io.circe +import io.circe.* +import io.circe.generic.auto.* +import io.circe.parser.decode +import io.circe.syntax.* + +trait Verifiable(proof: Proof) + +case class Proof(`type`: String, other: Json) + +object Proof { + + object Implicits { + implicit val proofEncoder: Encoder[Proof] = + (proof: Proof) => proof.other.deepMerge(Map("type" -> proof.`type`).asJson) + implicit val proofDecoder: Decoder[Proof] = + (c: HCursor) => + for { + `type` <- c.downField("type").as[String] + other <- c.downField("type").delete.up.as[Json] + } yield { + Proof( + `type` = `type`, + other = other + ) + } + + } +} diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredential.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredential.scala deleted file mode 100644 index 20aa11de9b..0000000000 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredential.scala +++ /dev/null @@ -1,394 +0,0 @@ -package io.iohk.atala.pollux.vc.jwt - -import cats.Applicative -import cats.data.{Validated, ValidatedNel} -import cats.implicits.* -import io.circe -import io.circe.generic.auto.* -import io.circe.parser.decode -import io.circe.syntax.* -import io.circe.{Decoder, Encoder, HCursor, Json} -import pdi.jwt.{Jwt, JwtCirce} - -import java.security.{KeyPairGenerator, PublicKey} -import java.time.{Instant, ZonedDateTime} -import scala.util.{Failure, Success, Try} - -case class Proof(`type`: String) - -trait Verifiable(proof: Proof) - -case class IssuerDID(id: String) -case class Issuer(did: IssuerDID, signer: Signer, publicKey: PublicKey) - -trait W3CCredential( - `@context`: Vector[String], - `type`: Vector[String], - issuer: IssuerDID, - issuanceDate: ZonedDateTime, - expirationDate: ZonedDateTime -) - -trait VerifiableCredential - -trait W3CVerifiableCredential extends W3CCredential, Verifiable - -case class JwtVerifiableCredential(jwt: EncodedJWT) extends VerifiableCredential - -trait VerifiedCredential extends JWTVerified, W3CVerifiableCredential - -case class CredentialStatus( - id: String, - `type`: String -) - -case class RefreshService( - id: String, - `type`: String -) - -case class CredentialSchema( - id: String, - `type`: String -) - -sealed trait CredentialPayload( - maybeSub: Option[String], - `@context`: Vector[String], - `type`: Vector[String], - maybeJti: Option[String], - maybeNbf: Option[Instant], - aud: Vector[String], - maybeExp: Option[Instant], - maybeIss: Option[String], - maybeConnectionStatus: Option[CredentialStatus], - maybeRefreshService: Option[RefreshService], - maybeEvidence: Option[Json], - maybeTermsOfUse: Option[Json], - maybeCredentialSchema: Option[CredentialSchema], - credentialSubject: Json -) { - def toJwtCredentialPayload: JwtCredentialPayload = - JwtCredentialPayload( - maybeIss = maybeIss, - maybeSub = maybeSub, - vc = JwtVc( - `@context` = `@context`, - `type` = `type`, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeConnectionStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse - ), - maybeNbf = maybeNbf, - aud = aud, - maybeExp = maybeExp, - maybeJti = maybeJti - ) - - def toW3CCredentialPayload: ValidatedNel[String, W3CCredentialPayload] = - ( - Validated - .cond(maybeIss.isDefined, maybeIss.get, "Iss must be defined") - .toValidatedNel, - Validated - .cond(maybeNbf.isDefined, maybeNbf.get, "Nbf must be defined") - .toValidatedNel - ).mapN((iss, nbf) => - W3CCredentialPayload( - `@context` = `@context`.distinct, - maybeId = maybeJti, - `type` = `type`.distinct, - issuer = IssuerDID(id = iss), - issuanceDate = nbf, - maybeExpirationDate = maybeExp, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeConnectionStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse, - aud = aud - ) - ) -} - -case class JwtVc( - `@context`: Vector[String], - `type`: Vector[String], - maybeCredentialSchema: Option[CredentialSchema], - credentialSubject: Json, - maybeCredentialStatus: Option[CredentialStatus], - maybeRefreshService: Option[RefreshService], - maybeEvidence: Option[Json], - maybeTermsOfUse: Option[Json] -) -case class JwtCredentialPayload( - maybeIss: Option[String], - maybeSub: Option[String], - vc: JwtVc, - maybeNbf: Option[Instant], - aud: Vector[String], - maybeExp: Option[Instant], - maybeJti: Option[String] -) extends CredentialPayload( - maybeSub = maybeSub.orElse(vc.credentialSubject.hcursor.downField("id").as[String].toOption), - `@context` = vc.`@context`.distinct, - `type` = vc.`type`.distinct, - maybeJti = maybeJti, - maybeNbf = maybeNbf, - aud = aud, - maybeExp = maybeExp, - maybeIss = maybeIss, - maybeConnectionStatus = vc.maybeCredentialStatus, - maybeRefreshService = vc.maybeRefreshService, - maybeEvidence = vc.maybeEvidence, - maybeTermsOfUse = vc.maybeTermsOfUse, - maybeCredentialSchema = vc.maybeCredentialSchema, - credentialSubject = vc.credentialSubject - ) - -case class W3CCredentialPayload( - `@context`: Vector[String], - maybeId: Option[String], - `type`: Vector[String], - issuer: IssuerDID, - issuanceDate: Instant, - maybeExpirationDate: Option[Instant], - maybeCredentialSchema: Option[CredentialSchema], - credentialSubject: Json, - maybeCredentialStatus: Option[CredentialStatus], - maybeRefreshService: Option[RefreshService], - maybeEvidence: Option[Json], - maybeTermsOfUse: Option[Json], - - /** Not part of W3C Credential but included to preserve in case of conversion from JWT. */ - aud: Vector[String] = Vector.empty -) extends CredentialPayload( - maybeSub = credentialSubject.hcursor.downField("id").as[String].toOption, - `@context` = `@context`.distinct, - `type` = `type`.distinct, - maybeJti = maybeId, - maybeNbf = Some(issuanceDate), - aud = aud, - maybeExp = maybeExpirationDate, - maybeIss = Some(issuer.id), - maybeConnectionStatus = maybeCredentialStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject - ) - -object VerifiedCredentialJson { - object Encoders { - object 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 credentialStatusEncoder: Encoder[CredentialStatus] = - (credentialStatus: CredentialStatus) => - Json - .obj( - ("id", credentialStatus.id.asJson), - ("type", credentialStatus.`type`.asJson) - ) - - implicit val w3cCredentialPayloadEncoder: Encoder[W3CCredentialPayload] = - (w3cCredentialPayload: W3CCredentialPayload) => - Json - .obj( - ("@context", w3cCredentialPayload.`@context`.asJson), - ("id", w3cCredentialPayload.maybeId.asJson), - ("type", w3cCredentialPayload.`type`.asJson), - ("issuer", w3cCredentialPayload.issuer.id.asJson), - ("issuanceDate", w3cCredentialPayload.issuanceDate.asJson), - ("expirationDate", w3cCredentialPayload.maybeExpirationDate.asJson), - ("credentialSchema", w3cCredentialPayload.maybeCredentialSchema.asJson), - ("credentialSubject", w3cCredentialPayload.credentialSubject), - ("credentialStatus", w3cCredentialPayload.maybeCredentialStatus.asJson), - ("refreshService", w3cCredentialPayload.maybeRefreshService.asJson), - ("evidence", w3cCredentialPayload.maybeEvidence.asJson), - ("termsOfUse", w3cCredentialPayload.maybeTermsOfUse.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) - ) - .deepDropNullValues - .dropEmptyValues - - implicit val jwtCredentialPayloadEncoder: Encoder[JwtCredentialPayload] = - (jwtCredentialPayload: JwtCredentialPayload) => - Json - .obj( - ("iss", jwtCredentialPayload.maybeIss.asJson), - ("sub", jwtCredentialPayload.maybeSub.asJson), - ("vc", jwtCredentialPayload.vc.asJson), - ("nbf", jwtCredentialPayload.maybeNbf.asJson), - ("aud", jwtCredentialPayload.aud.asJson), - ("exp", jwtCredentialPayload.maybeExp.asJson), - ("jti", jwtCredentialPayload.maybeJti.asJson) - ) - .deepDropNullValues - .dropEmptyValues - } - } - - object Decoders { - object Implicits { - 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 credentialStatusEncoder: Decoder[CredentialStatus] = - (c: HCursor) => - for { - id <- c.downField("id").as[String] - `type` <- c.downField("type").as[String] - } yield { - CredentialStatus(id = id, `type` = `type`) - } - - implicit val w3cCredentialPayloadEncoder: Decoder[W3CCredentialPayload] = - (c: HCursor) => - for { - `@context` <- c.downField("@context").as[Vector[String]] - maybeId <- c.downField("id").as[Option[String]] - `type` <- c.downField("type").as[Vector[String]] - issuer <- c.downField("issuer").as[String] - issuanceDate <- c.downField("issuanceDate").as[Instant] - maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]] - maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]] - credentialSubject <- c.downField("credentialSubject").as[Json] - maybeCredentialStatus <- c.downField("credentialStatus").as[Option[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`.distinct, - maybeId = maybeId, - `type` = `type`.distinct, - issuer = IssuerDID(id = issuer), - issuanceDate = issuanceDate, - maybeExpirationDate = maybeExpirationDate, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeCredentialStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse, - aud = Vector.empty - ) - } - - implicit val jwtVcDecoder: Decoder[JwtVc] = - (c: HCursor) => - for { - `@context` <- c.downField("@context").as[Vector[String]] - `type` <- c.downField("type").as[Vector[String]] - maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]] - credentialSubject <- c.downField("credentialSubject").as[Json] - maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus]] - maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]] - maybeEvidence <- c.downField("evidence").as[Option[Json]] - maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]] - } yield { - JwtVc( - `@context` = `@context`.distinct, - `type` = `type`.distinct, - maybeCredentialSchema = maybeCredentialSchema, - credentialSubject = credentialSubject, - maybeCredentialStatus = maybeCredentialStatus, - maybeRefreshService = maybeRefreshService, - maybeEvidence = maybeEvidence, - maybeTermsOfUse = maybeTermsOfUse - ) - } - - implicit val jwtCredentialPayloadDecoder: Decoder[JwtCredentialPayload] = - (c: HCursor) => - for { - maybeIss <- c.downField("iss").as[Option[String]] - maybeSub <- c.downField("sub").as[Option[String]] - vc <- c.downField("vc").as[JwtVc] - maybeNbf <- c.downField("nbf").as[Option[Instant]] - maybeAud <- c.downField("aud").as[Option[Vector[String]]] - maybeExp <- c.downField("exp").as[Option[Instant]] - maybeJti <- c.downField("jti").as[Option[String]] - } yield { - JwtCredentialPayload( - maybeIss = maybeIss, - maybeSub = maybeSub, - vc = vc, - maybeNbf = maybeNbf, - aud = maybeAud.orEmpty, - maybeExp = maybeExp, - maybeJti = maybeJti - ) - } - } - } -} - -object JwtVerifiableCredential { - - import VerifiedCredentialJson.Decoders.Implicits.* - import VerifiedCredentialJson.Encoders.Implicits.* - - def encodeJwt(payload: JwtCredentialPayload, issuer: Issuer): EncodedJWT = - EncodedJWT(jwt = issuer.signer.encode(payload.asJson)) - - def toEncodedJwt(payload: W3CCredentialPayload, issuer: Issuer): EncodedJWT = - encodeJwt(payload.toJwtCredentialPayload, issuer) - - def decodeJwt(encodedJWT: EncodedJWT, publicKey: PublicKey): Try[JwtCredentialPayload] = { - JwtCirce.decodeRaw(encodedJWT.jwt, publicKey).flatMap(decode[JwtCredentialPayload](_).toTry) - } - - def validateEncodedJwt(encodedJWT: EncodedJWT, publicKey: PublicKey): Boolean = - JwtCirce.isValid(encodedJWT.jwt, publicKey) -} diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala new file mode 100644 index 0000000000..9827b2cef8 --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiableCredentialPayload.scala @@ -0,0 +1,439 @@ +package io.iohk.atala.pollux.vc.jwt + +import io.circe +import io.circe.generic.auto.* +import io.circe.parser.decode +import io.circe.syntax.* +import io.circe.{Decoder, Encoder, HCursor, Json} +import pdi.jwt.{Jwt, JwtCirce} +import zio.prelude.* + +import java.security.{KeyPairGenerator, PublicKey} +import java.time.{Instant, ZonedDateTime} +import scala.util.{Failure, Success, Try} + +opaque type DID = String + +object DID { + def apply(value: String): DID = value + + extension (did: DID) { + def value: String = did + } +} + +case class Issuer(did: DID, signer: Signer, publicKey: PublicKey) + +sealed trait VerifiableCredentialPayload + +case class W3cVerifiableCredentialPayload(payload: W3cCredentialPayload, proof: Proof) + extends Verifiable(proof), + VerifiableCredentialPayload + +case class JwtVerifiableCredentialPayload(jwt: JWT) extends VerifiableCredentialPayload + +case class CredentialStatus( + id: String, + `type`: String + ) + +case class RefreshService( + id: String, + `type`: String + ) + +case class CredentialSchema( + id: String, + `type`: String +) + +sealed trait CredentialPayload( + maybeSub: Option[String], + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + maybeJti: Option[String], + maybeNbf: Option[Instant], + aud: IndexedSeq[String], + maybeExp: Option[Instant], + maybeIss: Option[String], + maybeConnectionStatus: Option[CredentialStatus], + maybeRefreshService: Option[RefreshService], + maybeEvidence: Option[Json], + maybeTermsOfUse: Option[Json], + maybeCredentialSchema: Option[CredentialSchema], + credentialSubject: Json +) { + def toJwtCredentialPayload: JwtCredentialPayload = + JwtCredentialPayload( + maybeIss = maybeIss, + maybeSub = maybeSub, + vc = JwtVc( + `@context` = `@context`, + `type` = `type`, + maybeCredentialSchema = maybeCredentialSchema, + credentialSubject = credentialSubject, + maybeCredentialStatus = maybeConnectionStatus, + maybeRefreshService = maybeRefreshService, + maybeEvidence = maybeEvidence, + maybeTermsOfUse = maybeTermsOfUse + ), + maybeNbf = maybeNbf, + aud = aud, + maybeExp = maybeExp, + maybeJti = maybeJti + ) + + def toW3CCredentialPayload: Validation[String, W3cCredentialPayload] = + Validation.validateWith( + Validation.fromOptionWith("Iss must be defined")(maybeIss), + Validation.fromOptionWith("Nbf must be defined")(maybeNbf) + ) { (iss, nbf) => + W3cCredentialPayload( + `@context` = `@context`.distinct, + maybeId = maybeJti, + `type` = `type`.distinct, + issuer = DID(iss), + issuanceDate = nbf, + maybeExpirationDate = maybeExp, + maybeCredentialSchema = maybeCredentialSchema, + credentialSubject = credentialSubject, + maybeCredentialStatus = maybeConnectionStatus, + maybeRefreshService = maybeRefreshService, + maybeEvidence = maybeEvidence, + maybeTermsOfUse = maybeTermsOfUse, + aud = aud + ) + } +} + +case class JwtVc( + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + maybeCredentialSchema: Option[CredentialSchema], + credentialSubject: Json, + maybeCredentialStatus: Option[CredentialStatus], + maybeRefreshService: Option[RefreshService], + maybeEvidence: Option[Json], + maybeTermsOfUse: Option[Json] +) +case class JwtCredentialPayload( + maybeIss: Option[String], + maybeSub: Option[String], + vc: JwtVc, + maybeNbf: Option[Instant], + aud: IndexedSeq[String], + maybeExp: Option[Instant], + maybeJti: Option[String] +) extends CredentialPayload( + maybeSub = maybeSub.orElse(vc.credentialSubject.hcursor.downField("id").as[String].toOption), + `@context` = vc.`@context`.distinct, + `type` = vc.`type`.distinct, + maybeJti = maybeJti, + maybeNbf = maybeNbf, + aud = aud, + maybeExp = maybeExp, + maybeIss = maybeIss, + maybeConnectionStatus = vc.maybeCredentialStatus, + maybeRefreshService = vc.maybeRefreshService, + maybeEvidence = vc.maybeEvidence, + maybeTermsOfUse = vc.maybeTermsOfUse, + maybeCredentialSchema = vc.maybeCredentialSchema, + credentialSubject = vc.credentialSubject + ) + +case class W3cCredentialPayload( + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + maybeId: Option[String], + issuer: DID, + issuanceDate: Instant, + maybeExpirationDate: Option[Instant], + maybeCredentialSchema: Option[CredentialSchema], + credentialSubject: Json, + maybeCredentialStatus: Option[CredentialStatus], + maybeRefreshService: Option[RefreshService], + maybeEvidence: Option[Json], + maybeTermsOfUse: Option[Json], + + /** Not part of W3C Credential but included to preserve in case of conversion from JWT. */ + aud: IndexedSeq[String] = IndexedSeq.empty + ) extends CredentialPayload( + maybeSub = credentialSubject.hcursor.downField("id").as[String].toOption, + `@context` = `@context`.distinct, + `type` = `type`.distinct, + maybeJti = maybeId, + maybeNbf = Some(issuanceDate), + aud = aud, + maybeExp = maybeExpirationDate, + maybeIss = Some(issuer.value), + maybeConnectionStatus = maybeCredentialStatus, + maybeRefreshService = maybeRefreshService, + maybeEvidence = maybeEvidence, + maybeTermsOfUse = maybeTermsOfUse, + maybeCredentialSchema = maybeCredentialSchema, + credentialSubject = credentialSubject +) + +object CredentialPayload { + object Implicits { + + import JWT.Implicits.* + import Proof.Implicits.* + + implicit val didEncoder: Encoder[DID] = + (did: DID) => did.value.asJson + + 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 credentialStatusEncoder: Encoder[CredentialStatus] = + (credentialStatus: CredentialStatus) => + Json + .obj( + ("id", credentialStatus.id.asJson), + ("type", credentialStatus.`type`.asJson) + ) + + 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), + ("credentialSchema", w3cCredentialPayload.maybeCredentialSchema.asJson), + ("credentialSubject", w3cCredentialPayload.credentialSubject), + ("credentialStatus", w3cCredentialPayload.maybeCredentialStatus.asJson), + ("refreshService", w3cCredentialPayload.maybeRefreshService.asJson), + ("evidence", w3cCredentialPayload.maybeEvidence.asJson), + ("termsOfUse", w3cCredentialPayload.maybeTermsOfUse.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) + ) + .deepDropNullValues + .dropEmptyValues + + implicit val jwtCredentialPayloadEncoder: Encoder[JwtCredentialPayload] = + (jwtCredentialPayload: JwtCredentialPayload) => + Json + .obj( + ("iss", jwtCredentialPayload.maybeIss.asJson), + ("sub", jwtCredentialPayload.maybeSub.asJson), + ("vc", jwtCredentialPayload.vc.asJson), + ("nbf", jwtCredentialPayload.maybeNbf.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.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 credentialStatusDecoder: Decoder[CredentialStatus] = + (c: HCursor) => + for { + id <- c.downField("id").as[String] + `type` <- c.downField("type").as[String] + } yield { + CredentialStatus(id = id, `type` = `type`) + } + + implicit val w3cCredentialPayloadDecoder: Decoder[W3cCredentialPayload] = + (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(_))) + maybeId <- c.downField("id").as[Option[String]] + issuer <- c.downField("issuer").as[String] + issuanceDate <- c.downField("issuanceDate").as[Instant] + maybeExpirationDate <- c.downField("expirationDate").as[Option[Instant]] + maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]] + credentialSubject <- c.downField("credentialSubject").as[Json] + maybeCredentialStatus <- c.downField("credentialStatus").as[Option[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`.distinct, + `type` = `type`.distinct, + maybeId = maybeId, + issuer = DID(issuer), + issuanceDate = issuanceDate, + maybeExpirationDate = maybeExpirationDate, + maybeCredentialSchema = maybeCredentialSchema, + credentialSubject = credentialSubject, + maybeCredentialStatus = maybeCredentialStatus, + maybeRefreshService = maybeRefreshService, + maybeEvidence = maybeEvidence, + maybeTermsOfUse = maybeTermsOfUse, + aud = IndexedSeq.empty + ) + } + + implicit val jwtVcDecoder: Decoder[JwtVc] = + (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(_))) + maybeCredentialSchema <- c.downField("credentialSchema").as[Option[CredentialSchema]] + credentialSubject <- c.downField("credentialSubject").as[Json] + maybeCredentialStatus <- c.downField("credentialStatus").as[Option[CredentialStatus]] + maybeRefreshService <- c.downField("refreshService").as[Option[RefreshService]] + maybeEvidence <- c.downField("evidence").as[Option[Json]] + maybeTermsOfUse <- c.downField("termsOfUse").as[Option[Json]] + } yield { + JwtVc( + `@context` = `@context`.distinct, + `type` = `type`.distinct, + maybeCredentialSchema = maybeCredentialSchema, + credentialSubject = credentialSubject, + maybeCredentialStatus = maybeCredentialStatus, + maybeRefreshService = maybeRefreshService, + maybeEvidence = maybeEvidence, + maybeTermsOfUse = maybeTermsOfUse + ) + } + + implicit val jwtCredentialPayloadDecoder: Decoder[JwtCredentialPayload] = + (c: HCursor) => + for { + maybeIss <- c.downField("iss").as[Option[String]] + maybeSub <- c.downField("sub").as[Option[String]] + vc <- c.downField("vc").as[JwtVc] + 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]] + } yield { + JwtCredentialPayload( + maybeIss = maybeIss, + maybeSub = maybeSub, + vc = vc, + maybeNbf = maybeNbf, + aud = aud.distinct, + maybeExp = maybeExp, + maybeJti = maybeJti + ) + } + + implicit val w3cVerifiableCredentialPayloadDecoder: Decoder[W3cVerifiableCredentialPayload] = + (c: HCursor) => + for { + payload <- c.as[W3cCredentialPayload] + proof <- c.downField("proof").as[Proof] + } yield { + W3cVerifiableCredentialPayload( + payload = payload, + proof = proof + ) + } + + implicit val jwtVerifiableCredentialPayloadDecoder: Decoder[JwtVerifiableCredentialPayload] = + (c: HCursor) => + for { + jwt <- c.as[String] + } yield { + JwtVerifiableCredentialPayload( + jwt = JWT(jwt) + ) + } + + implicit val verifiableCredentialPayloadDecoder: Decoder[VerifiableCredentialPayload] = + jwtVerifiableCredentialPayloadDecoder.or( + w3cVerifiableCredentialPayloadDecoder.asInstanceOf[Decoder[VerifiableCredentialPayload]] + ) + } +} + +object JwtCredential { + import CredentialPayload.Implicits.* + + def encodeJwt(payload: JwtCredentialPayload, issuer: Issuer): JWT = issuer.signer.encode(payload.asJson) + + def toEncodedJwt(payload: W3cCredentialPayload, issuer: Issuer): JWT = + encodeJwt(payload.toJwtCredentialPayload, issuer) + + def decodeJwt(jwt: JWT, publicKey: PublicKey): Try[JwtCredentialPayload] = { + JwtCirce.decodeRaw(jwt.value, publicKey).flatMap(decode[JwtCredentialPayload](_).toTry) + } + + def validateEncodedJwt(jwt: JWT, publicKey: PublicKey): Boolean = + JwtCirce.isValid(jwt.value, publicKey) +} diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentation.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentation.scala deleted file mode 100644 index 54c990a42f..0000000000 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentation.scala +++ /dev/null @@ -1,13 +0,0 @@ -package io.iohk.atala.pollux.vc.jwt - -trait W3CPresentation( - `@context`: Vector[String], - `type`: Vector[String], - verifier: Vector[String], - verifiableCredential: Vector[W3CCredential] -) - -trait VerifiablePresentation -trait W3CVerifiablePresentation extends W3CPresentation, Verifiable -trait JWTVerifiablePresentation(jwt: EncodedJWT) extends VerifiablePresentation -trait VerifiedPresentation extends JWTVerified, W3CVerifiablePresentation diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentationPayload.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentationPayload.scala new file mode 100644 index 0000000000..31d98cc82a --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/VerifiablePresentationPayload.scala @@ -0,0 +1,288 @@ +package io.iohk.atala.pollux.vc.jwt + +import io.circe +import io.circe.* +import io.circe.generic.auto.* +import io.circe.parser.decode +import io.circe.syntax.* +import pdi.jwt.{Jwt, JwtCirce} +import zio.prelude.* + +import java.security.{KeyPairGenerator, PublicKey} +import java.time.{Instant, ZonedDateTime} +import scala.util.{Failure, Success, Try} + +sealed trait VerifiablePresentationPayload + +case class W3cVerifiablePresentationPayload(payload: W3cPresentationPayload, proof: Proof) + extends Verifiable(proof), + VerifiablePresentationPayload + +case class JwtVerifiablePresentationPayload(jwt: JWT) extends VerifiablePresentationPayload + +sealed trait PresentationPayload( + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + verifiableCredential: IndexedSeq[VerifiableCredentialPayload], + maybeIss: Option[String], + maybeNbf: Option[Instant], + aud: IndexedSeq[String], + maybeExp: Option[Instant], + maybeJti: Option[String], + maybeNonce: Option[String] + ) { + def toJwtPresentationPayload: JwtPresentationPayload = + JwtPresentationPayload( + maybeIss = maybeIss, + vp = JwtVp( + `@context` = `@context`, + `type` = `type`, + verifiableCredential = verifiableCredential + ), + maybeNbf = maybeNbf, + aud = aud, + maybeExp = maybeExp, + maybeJti = maybeJti, + maybeNonce = maybeNonce + ) + + def toW3CPresentationPayload: Validation[String, W3cPresentationPayload] = + Validation.validateWith( + Validation.fromOptionWith("Iss must be defined")(maybeIss), + Validation.fromOptionWith("Nbf must be defined")(maybeNbf) + ) { (iss, nbf) => + W3cPresentationPayload( + `@context` = `@context`.distinct, + maybeId = maybeJti, + `type` = `type`.distinct, + verifiableCredential = verifiableCredential, + holder = iss, + verifier = aud, + issuanceDate = nbf, + maybeExpirationDate = maybeExp, + maybeNonce = maybeNonce + ) + } +} + +case class W3cPresentationPayload( + `@context`: IndexedSeq[String], + maybeId: Option[String], + `type`: IndexedSeq[String], + verifiableCredential: IndexedSeq[VerifiableCredentialPayload], + holder: String, + verifier: IndexedSeq[String], + issuanceDate: Instant, + maybeExpirationDate: Option[Instant], + + /** Not part of W3C Presentation but included to preserve in case of conversion from JWT. */ + maybeNonce: Option[String] = Option.empty + ) extends PresentationPayload( + `@context` = `@context`.distinct, + `type` = `type`.distinct, + maybeJti = maybeId, + verifiableCredential = verifiableCredential, + aud = verifier, + maybeIss = Some(holder), + maybeNbf = Some(issuanceDate), + maybeExp = maybeExpirationDate, + maybeNonce = maybeNonce +) + +case class JwtVp( + `@context`: IndexedSeq[String], + `type`: IndexedSeq[String], + verifiableCredential: IndexedSeq[VerifiableCredentialPayload] + ) + +case class JwtPresentationPayload( + maybeIss: Option[String], + vp: JwtVp, + maybeNbf: Option[Instant], + aud: IndexedSeq[String], + maybeExp: Option[Instant], + maybeJti: Option[String], + maybeNonce: Option[String] + ) extends PresentationPayload( + maybeIss = maybeIss, + `@context` = vp.`@context`, + `type` = vp.`type`, + verifiableCredential = vp.verifiableCredential, + maybeNbf = maybeNbf, + aud = aud, + maybeExp = maybeExp, + maybeJti = maybeJti, + maybeNonce = maybeNonce +) + +object PresentationPayload { + + object Implicits { + + import CredentialPayload.Implicits.* + import JWT.Implicits.* + import Proof.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.issuanceDate.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.maybeIss.asJson), + ("vc", 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 w3cPresentationPayload: 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("issuer").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)) + issuanceDate <- c.downField("issuanceDate").as[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, + issuanceDate = issuanceDate, + 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 { + maybeIss <- c.downField("iss").as[Option[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( + maybeIss = maybeIss, + 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[Proof] + } 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]] + ) + } +} + diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/CredentialDemo.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/CredentialDemo.scala index 4fff36bac4..13a69082c6 100644 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/CredentialDemo.scala +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/CredentialDemo.scala @@ -1,34 +1,23 @@ package io.iohk.atala.pollux.vc.jwt.demos -import io.iohk.atala.pollux.vc.jwt.{ - CredentialSchema, - CredentialStatus, - IssuerDID, - JwtCredentialPayload, - RefreshService, - W3CCredentialPayload -} -import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Encoders.Implicits.* -import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Decoders.Implicits.* -import cats.implicits.* + import io.circe.* -import net.reactivecore.cjs.resolver.Downloader -import net.reactivecore.cjs.{DocumentValidator, Loader, Result} -import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} import io.circe.generic.auto.* -import io.circe.syntax.* -import io.circe.{Decoder, Encoder, HCursor, Json} import io.circe.parser.decode +import io.circe.syntax.* +import io.iohk.atala.pollux.vc.jwt.* +import io.iohk.atala.pollux.vc.jwt.CredentialPayload.Implicits.* +import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} import java.security.* import java.security.spec.* import java.time.{Instant, ZonedDateTime} @main def CredentialDemo(): Unit = - val w3cCredentialPayload = W3CCredentialPayload( + val w3cCredentialPayload = W3cCredentialPayload( `@context` = Vector("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Vector("VerifiableCredential", "UniversityDegreeCredential"), - issuer = IssuerDID(id = "https://example.edu/issuers/565049"), + issuer = DID("https://example.edu/issuers/565049"), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeCredentialSchema = Some( @@ -69,7 +58,7 @@ import java.time.{Instant, ZonedDateTime} println("==================") println("W3C Json => W3C") println("==================") - val decodedW3CJson = decode[W3CCredentialPayload](w3cJson).toOption.get + val decodedW3CJson = decode[W3cCredentialPayload](w3cJson).toOption.get println(decodedW3CJson) println("") diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JWTDemo.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JWTDemo.scala index 600897c597..f9cb48a85a 100644 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JWTDemo.scala +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JWTDemo.scala @@ -1,18 +1,14 @@ package io.iohk.atala.pollux.vc.jwt.demos +import cats.implicits.* import io.circe.* -import pdi.jwt.JwtClaim -import pdi.jwt.{JwtAlgorithm, JwtCirce} +import net.reactivecore.cjs.resolver.Downloader +import net.reactivecore.cjs.{DocumentValidator, Loader, Result} +import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} import java.security.* import java.security.spec.* import java.time.Instant -import net.reactivecore.cjs.Loader - -import net.reactivecore.cjs.{DocumentValidator, Loader, Result} -import net.reactivecore.cjs.resolver.Downloader -import cats.implicits._ -import io.circe.Json @main def jwtDemo(): Unit = val keyGen = KeyPairGenerator.getInstance("EC") @@ -22,7 +18,7 @@ import io.circe.Json val privateKey = keyPair.getPrivate val publicKey = keyPair.getPublic - val Right(claimJson) = jawn.parse(s"""{"expires":${Instant.now.getEpochSecond}}""") + val Right(claimJson) : Right[io.circe.ParsingFailure, io.circe.Json] @unchecked = jawn.parse(s"""{"expires":${Instant.now.getEpochSecond}}""") val jwt = JwtCirce.encode(claimJson, privateKey, JwtAlgorithm.ES256) diff --git a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JwtCredentialEncondingDemo.scala b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JwtCredentialEncondingDemo.scala index 43f981e5d6..b906676bc2 100644 --- a/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JwtCredentialEncondingDemo.scala +++ b/pollux/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/demos/JwtCredentialEncondingDemo.scala @@ -6,7 +6,7 @@ import io.circe.generic.auto.* import io.circe.parser.decode import io.circe.syntax.* import io.iohk.atala.pollux.vc.jwt.* -import io.iohk.atala.pollux.vc.jwt.VerifiedCredentialJson.Encoders.Implicits.* +import io.iohk.atala.pollux.vc.jwt.CredentialPayload.Implicits.* import net.reactivecore.cjs.resolver.Downloader import net.reactivecore.cjs.{DocumentValidator, Loader, Result} import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} @@ -29,7 +29,7 @@ import java.time.{Instant, ZonedDateTime} val publicKey = keyPair.getPublic val issuer = Issuer( - did = IssuerDID("did:issuer:MDP8AsFhHzhwUvGNuYkX7T"), + did = DID("did:issuer:MDP8AsFhHzhwUvGNuYkX7T"), signer = ES256Signer(privateKey), publicKey = publicKey ) @@ -39,11 +39,11 @@ import java.time.{Instant, ZonedDateTime} println("==================") println("Create W3C") println("==================") - val w3cCredentialPayload = W3CCredentialPayload( + val w3cCredentialPayload = W3cCredentialPayload( `@context` = Vector("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), maybeId = Some("http://example.edu/credentials/3732"), `type` = Vector("VerifiableCredential", "UniversityDegreeCredential"), - issuer = IssuerDID(id = "https://example.edu/issuers/565049"), + issuer = DID("https://example.edu/issuers/565049"), issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), maybeCredentialSchema = Some( @@ -78,21 +78,21 @@ import java.time.{Instant, ZonedDateTime} println("==================") println("W3C => Encoded JWT") println("==================") - val encodedJWT = JwtVerifiableCredential.toEncodedJwt(w3cCredentialPayload, issuer) - println(encodedJWT.jwt) + val encodedJWT = JwtCredential.toEncodedJwt(w3cCredentialPayload, issuer) + println(encodedJWT) println("") println("==================") println("Validate Encoded JWT") println("==================") - val valid = JwtVerifiableCredential.validateEncodedJwt(encodedJWT, issuer.publicKey) + val valid = JwtCredential.validateEncodedJwt(encodedJWT, issuer.publicKey) println(s"Is Valid? $valid") println("") println("==================") println("Encoded JWT => Decoded JWT Json") println("==================") - val decodedJwtCredential = JwtVerifiableCredential.decodeJwt(encodedJWT, issuer.publicKey) + val decodedJwtCredential = JwtCredential.decodeJwt(encodedJWT, issuer.publicKey) val decodedJwtCredentialAsJson = decodedJwtCredential.toOption.get.asJson.toString() println(decodedJwtCredentialAsJson)