Skip to content

Commit

Permalink
feat(pollux) : Added challenge and domain (#361)
Browse files Browse the repository at this point in the history
* feat(pollux): Support challenge and domain

* feat(pollux): Support challenge and domain

* fix pr comments and improvements
  • Loading branch information
mineme0110 authored Feb 8, 2023
1 parent 50019ae commit abc007f
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ object PresentationError {
final case class InvalidFlowStateError(msg: String) extends PresentationError
final case class UnexpectedError(msg: String) extends PresentationError
final case class IssuedCredentialNotFoundError(cause: Throwable) extends PresentationError
final case class PresentationDecodingError(cause: Throwable) extends PresentationError

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.iohk.atala.pollux.core.model.presentation

import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._

case class Field(
id: Option[String] = None,
path: Seq[String] = Seq.empty,
name: Option[String] = None,
purpose: Option[String] = None
)
object Field {
given Encoder[Field] = deriveEncoder[Field]
given Decoder[Field] = deriveDecoder[Field]
}

case class Jwt(alg: Seq[String], proof_type: Seq[String])
object Jwt {
given Encoder[Jwt] = deriveEncoder[Jwt]
given Decoder[Jwt] = deriveDecoder[Jwt]
}
case class ClaimFormat(jwt: Jwt)
object ClaimFormat {
given Encoder[ClaimFormat] = deriveEncoder[ClaimFormat]
given Decoder[ClaimFormat] = deriveDecoder[ClaimFormat]
}
case class Constraints(fields: Option[Seq[Field]])
object Constraints {
given Encoder[Constraints] = deriveEncoder[Constraints]
given Decoder[Constraints] = deriveDecoder[Constraints]
}

/** Refer to <a href="https://identity.foundation/presentation-exchange/#input-descriptor">Input Descriptors</a>
*/
case class InputDescriptor(
id: String = java.util.UUID.randomUUID.toString(),
name: Option[String] = None,
purpose: Option[String] = None,
format: Option[ClaimFormat] = None,
constraints: Constraints
)
object InputDescriptor {
given Encoder[InputDescriptor] = deriveEncoder[InputDescriptor]
given Decoder[InputDescriptor] = deriveDecoder[InputDescriptor]
}

/** Refer to <a href="https://identity.foundation/presentation-exchange/#presentation-definition">Presentation
* Definition</a>
*/
case class PresentationDefinition(
id: String = java.util.UUID.randomUUID.toString(), // UUID
input_descriptors: Seq[InputDescriptor] = Seq.empty,
name: Option[String] = None,
purpose: Option[String] = None,
format: Option[ClaimFormat] = None
)
object PresentationDefinition {
given Encoder[PresentationDefinition] = deriveEncoder[PresentationDefinition]
given Decoder[PresentationDefinition] = deriveDecoder[PresentationDefinition]
}

case class Options(challenge: String, domain: String)
object Options {
given Encoder[Options] = deriveEncoder[Options]
given Decoder[Options] = deriveDecoder[Options]
}

case class PresentationAttachment(options: Option[Options] = None, presentation_definition: PresentationDefinition)
object PresentationAttachment {
given Encoder[PresentationAttachment] = deriveEncoder[PresentationAttachment]
given Decoder[PresentationAttachment] = deriveDecoder[PresentationAttachment]

def build(options: Option[Options] = None): PresentationAttachment = {
val presentationDefinition =
PresentationDefinition(input_descriptors = Seq.empty)
PresentationAttachment(options, presentationDefinition)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.iohk.atala.pollux.core.model.error.PresentationError
import io.iohk.atala.pollux.core.model.error.PresentationError._
import io.iohk.atala.pollux.core.model.IssuedCredentialRaw
import io.iohk.atala.pollux.core.repository.PresentationRepository
import io.iohk.atala.pollux.core.model.presentation._
import io.iohk.atala.pollux.vc.jwt.*
import zio.*
import io.iohk.atala.mercury.model.AttachmentDescriptor
Expand All @@ -29,8 +30,9 @@ import java.security.PublicKey
import io.iohk.atala.mercury.protocol.issuecredential.IssueCredential
import io.iohk.atala.pollux.core.model.IssueCredentialRecord
import io.iohk.atala.pollux.core.repository.CredentialRepository
import org.didcommx.didcomm.message.Attachment.Data.Base64 //FIXME create interface so we do not have to use it directly
import java.{util => ju}
import cats.syntax.all._
import cats._, cats.data._, cats.implicits._

trait PresentationService {

Expand All @@ -40,7 +42,8 @@ trait PresentationService {
thid: UUID,
subjectDid: DidId,
connectionId: Option[String],
proofTypes: Seq[ProofType]
proofTypes: Seq[ProofType],
options: Option[io.iohk.atala.pollux.core.model.presentation.Options]
): IO[PresentationError, PresentationRecord]

def getPresentationRecords(): IO[PresentationError, Seq[PresentationRecord]]
Expand All @@ -49,7 +52,7 @@ trait PresentationService {
record: UUID,
issuer: Issuer,
issuanceDate: Instant
): IO[PresentationError, W3cPresentationPayload]
): IO[PresentationError, PresentationPayload]

def getPresentationRecordsByStates(
state: PresentationRecord.ProtocolState*
Expand Down Expand Up @@ -136,7 +139,7 @@ private class PresentationServiceImpl(
recordId: UUID,
prover: Issuer,
issuanceDate: Instant
): IO[PresentationError, W3cPresentationPayload] = {
): IO[PresentationError, PresentationPayload] = {

for {
maybeRecord <- presentationRepository
Expand All @@ -149,7 +152,9 @@ private class PresentationServiceImpl(
credentialsToUse <- ZIO
.fromOption(record.credentialsToUse)
.mapError(_ => InvalidFlowStateError(s"No request found for this record: $recordId"))

requestPresentation <- ZIO
.fromOption(record.requestPresentationData)
.mapError(_ => InvalidFlowStateError(s"RequestPresentation not found: $recordId"))
issuedValidCredentials <- credentialRepository
.getValidIssuedCredentials(credentialsToUse.map(UUID.fromString))
.mapError(RepositoryError.apply)
Expand All @@ -165,8 +170,8 @@ private class PresentationServiceImpl(
)
)

w3cPresentationPayload <- createPresentationPayloadFromCredential(issuedCredentials, prover)
} yield w3cPresentationPayload
presentationPayload <- createPresentationPayloadFromCredential(issuedCredentials, requestPresentation, prover)
} yield presentationPayload
}

override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] =
Expand Down Expand Up @@ -199,10 +204,11 @@ private class PresentationServiceImpl(
thid: UUID,
subjectId: DidId,
connectionId: Option[String],
proofTypes: Seq[ProofType]
proofTypes: Seq[ProofType],
options: Option[io.iohk.atala.pollux.core.model.presentation.Options]
): IO[PresentationError, PresentationRecord] = {
for {
request <- ZIO.succeed(createDidCommRequestPresentation(proofTypes, thid, subjectId))
request <- ZIO.succeed(createDidCommRequestPresentation(proofTypes, thid, subjectId, options))
record <- ZIO.succeed(
PresentationRecord(
id = UUID.randomUUID(),
Expand Down Expand Up @@ -274,28 +280,77 @@ private class PresentationServiceImpl(

private def createPresentationPayloadFromCredential(
issuedCredentials: Seq[IssuedCredentialRaw],
requestPresentation: RequestPresentation,
prover: Issuer
): IO[PresentationError, W3cPresentationPayload] = {

val verifiableCredentials = issuedCredentials.map { issuedCredential =>
decode[io.iohk.atala.mercury.model.Base64](issuedCredential.signedCredential)
.map(x => new String(java.util.Base64.getDecoder().decode(x.base64)))
.map(x => JwtVerifiableCredentialPayload(JWT(x)))
.getOrElse(???)
}.toVector

val w3cPresentationPayload =
W3cPresentationPayload(
`@context` = Vector("https://www.w3.org/2018/presentations/v1"),
maybeId = None,
`type` = Vector("VerifiablePresentation"),
verifiableCredential = verifiableCredentials,
holder = prover.did.value,
verifier = Vector("https://example.edu/issuers/565049"), // TODO Fix this
maybeIssuanceDate = None,
maybeExpirationDate = None
)
ZIO.succeed(w3cPresentationPayload)
): IO[PresentationError, PresentationPayload] = {

// val verifiableCredentials = issuedCredentials.map { issuedCredential =>
// decode[io.iohk.atala.mercury.model.Base64](issuedCredential.signedCredential)
// .map(x => new String(java.util.Base64.getDecoder().decode(x.base64)))
// .map(x => JwtVerifiableCredentialPayload(JWT(x)))
// .getOrElse(???)
// }.toVector

val verifiableCredentials =
issuedCredentials.map { issuedCredential =>
decode[io.iohk.atala.mercury.model.Base64](issuedCredential.signedCredential).right
.flatMap(x => Right(new String(java.util.Base64.getDecoder().decode(x.base64))))
.right
.flatMap(x => Right(JwtVerifiableCredentialPayload(JWT(x))))
.left
.map(err => PresentationDecodingError(new Throwable(s"JsonData decoding error: $err")))
}.sequence

val maybePresentationOptions
: Either[PresentationError, Option[io.iohk.atala.pollux.core.model.presentation.Options]] =
requestPresentation.attachments.headOption
.map(attachment =>
decode[io.iohk.atala.mercury.model.JsonData](attachment.data.asJson.noSpaces)
.flatMap(data =>
io.iohk.atala.pollux.core.model.presentation.PresentationAttachment.given_Decoder_PresentationAttachment
.decodeJson(data.json.asJson)
.map(_.options)
.leftMap(err =>
PresentationDecodingError(new Throwable(s"PresentationAttachment decoding error: $err"))
)
)
.leftMap(err => PresentationDecodingError(new Throwable(s"JsonData decoding error: $err")))
)
.getOrElse(Right(None))

for {
maybeOptions <- ZIO.fromEither(maybePresentationOptions)
vcs <- ZIO.fromEither(verifiableCredentials)
presentationPayload <-
ZIO.succeed(
maybeOptions
.map { options =>
W3cPresentationPayload(
`@context` = Vector("https://www.w3.org/2018/presentations/v1"),
maybeId = None,
`type` = Vector("VerifiablePresentation"),
verifiableCredential = vcs.toVector,
holder = prover.did.value,
verifier = Vector(options.domain),
maybeIssuanceDate = None,
maybeExpirationDate = None
).toJwtPresentationPayload.copy(maybeNonce = Some(options.challenge))
}
.getOrElse {
W3cPresentationPayload(
`@context` = Vector("https://www.w3.org/2018/presentations/v1"),
maybeId = None,
`type` = Vector("VerifiablePresentation"),
verifiableCredential = vcs.toVector,
holder = prover.did.value,
verifier = Vector("https://example.verifier"), // TODO Fix this
maybeIssuanceDate = None,
maybeExpirationDate = None
).toJwtPresentationPayload
}
)
} yield presentationPayload

}

override def acceptRequestPresentation(
Expand Down Expand Up @@ -483,14 +538,23 @@ private class PresentationServiceImpl(
private[this] def createDidCommRequestPresentation(
proofTypes: Seq[ProofType],
thid: UUID,
subjectDId: DidId
subjectDId: DidId,
maybeOptions: Option[io.iohk.atala.pollux.core.model.presentation.Options]
): RequestPresentation = {
RequestPresentation(
body = RequestPresentation.Body(
goal_code = Some("request"),
proof_types = proofTypes
),
attachments = Seq.empty,
attachments = maybeOptions
.map(options =>
Seq(
AttachmentDescriptor.buildJsonAttachment(payload =
io.iohk.atala.pollux.core.model.presentation.PresentationAttachment.build(Some(options))
)
)
)
.getOrElse(Seq.empty),
from = didAgent.id,
to = subjectDId,
thid = Some(thid.toString)
Expand Down
Loading

0 comments on commit abc007f

Please sign in to comment.