Skip to content

Commit

Permalink
feat: interoperable credential definition api (#852)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <[email protected]>
  • Loading branch information
CryptoKnightIOG committed Jan 24, 2024
1 parent 06d8e86 commit f050918
Show file tree
Hide file tree
Showing 19 changed files with 148 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ final case class IssueCredentialRecord(
createdAt: Instant,
updatedAt: Option[Instant],
thid: DidCommID,
schemaId: Option[String],
schemaUri: Option[String],
credentialDefinitionId: Option[UUID],
credentialDefinitionUri: Option[String],
credentialFormat: CredentialFormat,
role: Role,
subjectId: Option[String],
Expand Down Expand Up @@ -77,8 +78,8 @@ final case class ValidFullIssuedCredentialRecord(
id: DidCommID,
issuedCredential: Option[IssueCredential],
credentialFormat: CredentialFormat,
schemaId: Option[String],
credentialDefinitionId: Option[UUID],
schemaUri: Option[String],
credentialDefinitionUri: Option[String],
subjectId: Option[String]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import io.iohk.atala.pollux.core.service.URIDereferencer
import zio.*
import zio.json.*
import zio.json.ast.Json
import zio.prelude.Validation

import java.net.URI
import java.time.{OffsetDateTime, ZoneOffset}
Expand Down Expand Up @@ -150,10 +149,11 @@ object CredentialSchema {
for {
uri <- ZIO.attempt(new URI(schemaId)).mapError(t => URISyntaxError(t.getMessage))
content <- uriDereferencer.dereference(uri).mapError(err => UnexpectedError(err.toString))
validAttrNames <- ZIO
.fromEither(content.fromJson[AnoncredSchemaSerDesV1])
.mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error"))
.map(_.attrNames)
validAttrNames <-
AnoncredSchemaSerDesV1.schemaSerDes
.deserialize(content)
.mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error"))
.map(_.attrNames)
jsonClaims <- ZIO.fromEither(claims.fromJson[Json]).mapError(err => UnexpectedError(err))
_ <- jsonClaims match
case Json.Obj(fields) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class CredentialRepositoryInMemory(
recordId.contains(
rec.id
) && rec.issueCredentialData.isDefined
&& rec.schemaId.isDefined
&& rec.schemaUri.isDefined
&& rec.credentialDefinitionId.isDefined
&& rec.credentialFormat == CredentialFormat.AnonCreds
)
Expand All @@ -157,8 +157,8 @@ class CredentialRepositoryInMemory(
rec.id,
rec.issueCredentialData,
rec.credentialFormat,
rec.schemaId,
rec.credentialDefinitionId,
rec.schemaUri,
rec.credentialDefinitionUri,
rec.subjectId
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package io.iohk.atala.pollux.core.service
import io.iohk.atala.agent.walletapi.storage
import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage
import io.iohk.atala.pollux.anoncreds.{AnoncredLib, AnoncredSchemaDef}
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.URISyntaxError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.{SchemaError, URISyntaxError}
import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import io.iohk.atala.pollux.core.model.schema.CredentialDefinition.{Filter, FilteredEntries}
import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError
import io.iohk.atala.pollux.core.model.schema.{CredentialDefinition, CredentialSchema}
import io.iohk.atala.pollux.core.model.secret.CredentialDefinitionSecret
import io.iohk.atala.pollux.core.repository.CredentialDefinitionRepository
import io.iohk.atala.pollux.core.repository.Repository.SearchQuery
Expand Down Expand Up @@ -84,12 +83,10 @@ class CredentialDefinitionServiceImpl(
)
} yield createdCredentialDefinition
}.mapError {
case e: CredentialDefinitionCreationError => e
case j: JsonSchemaError => UnexpectedError(j.error)
case s: URISyntaxError => UnexpectedError(s.message)
case u: URIDereferencerError => UnexpectedError(u.error)
case e: CredentialSchemaError => CredentialDefinitionValidationError(e)
case u: URIDereferencerError => CredentialDefinitionValidationError(URISyntaxError(u.error))
case j: JsonSchemaError => CredentialDefinitionValidationError(SchemaError(j))
case t: Throwable => RepositoryError(t)
case e: CredentialDefinitionCreationError => e
}

override def delete(guid: UUID): Result[CredentialDefinition] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ trait CredentialService {
pairwiseHolderDID: DidId,
thid: DidCommID,
credentialDefinitionGUID: UUID,
credentialDefinitionId: String,
claims: io.circe.Json,
validityPeriod: Option[Double] = None,
automaticIssuance: Option[Boolean],
credentialDefinitionId: String
automaticIssuance: Option[Boolean]
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord]

/** Return a list of records as well as a count of all filtered items */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ private class CredentialServiceImpl(
createdAt = Instant.now,
updatedAt = None,
thid = thid,
schemaId = maybeSchemaId,
schemaUri = maybeSchemaId,
credentialDefinitionId = None,
credentialDefinitionUri = None,
credentialFormat = CredentialFormat.JWT,
role = IssueCredentialRecord.Role.Issuer,
subjectId = None,
Expand Down Expand Up @@ -165,10 +166,10 @@ private class CredentialServiceImpl(
pairwiseHolderDID: DidId,
thid: DidCommID,
credentialDefinitionGUID: UUID,
credentialDefinitionId: String,
claims: Json,
validityPeriod: Option[Double],
automaticIssuance: Option[Boolean],
credentialDefinitionId: String
automaticIssuance: Option[Boolean]
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = {
for {
credentialDefinition <- credentialDefinitionService
Expand All @@ -181,20 +182,21 @@ private class CredentialServiceImpl(
offer <- createAnonCredsDidCommOfferCredential(
pairwiseIssuerDID = pairwiseIssuerDID,
pairwiseHolderDID = pairwiseHolderDID,
schemaId = credentialDefinition.schemaId,
schemaUri = credentialDefinition.schemaId,
credentialDefinitionGUID = credentialDefinitionGUID,
credentialDefinitionId = credentialDefinitionId,
claims = attributes,
thid = thid,
credentialDefinitionId
)
record <- ZIO.succeed(
IssueCredentialRecord(
id = DidCommID(),
createdAt = Instant.now,
updatedAt = None,
thid = thid,
schemaId = Some(credentialDefinition.schemaId),
schemaUri = Some(credentialDefinition.schemaId),
credentialDefinitionId = Some(credentialDefinitionGUID),
credentialDefinitionUri = Some(credentialDefinitionId),
credentialFormat = CredentialFormat.AnonCreds,
role = IssueCredentialRecord.Role.Issuer,
subjectId = None,
Expand Down Expand Up @@ -270,8 +272,9 @@ private class CredentialServiceImpl(
createdAt = Instant.now,
updatedAt = None,
thid = DidCommID(offer.thid.getOrElse(offer.id)),
schemaId = None,
schemaUri = None,
credentialDefinitionId = None,
credentialDefinitionUri = None,
credentialFormat = credentialFormat,
role = Role.Holder,
subjectId = None,
Expand Down Expand Up @@ -843,14 +846,14 @@ private class CredentialServiceImpl(
private[this] def createAnonCredsDidCommOfferCredential(
pairwiseIssuerDID: DidId,
pairwiseHolderDID: DidId,
schemaId: String,
schemaUri: String,
credentialDefinitionGUID: UUID,
credentialDefinitionId: String,
claims: Seq[Attribute],
thid: DidCommID,
credentialDefinitionId: String
thid: DidCommID
) = {
for {
credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaId), attributes = claims))
credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaUri), attributes = claims))
body = OfferCredential.Body(
goal_code = Some("Offer Credential"),
credential_preview = credentialPreview,
Expand Down Expand Up @@ -1036,7 +1039,7 @@ private class CredentialServiceImpl(
issuanceDate = issuanceDate,
maybeExpirationDate = record.validityPeriod.map(sec => issuanceDate.plusSeconds(sec.toLong)),
maybeCredentialSchema =
record.schemaId.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)),
record.schemaUri.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)),
credentialSubject = claims.add("id", jwtPresentation.iss.asJson).asJson,
maybeCredentialStatus = None,
maybeRefreshService = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@ class CredentialServiceNotifier(
pairwiseHolderDID: DidId,
thid: DidCommID,
credentialDefinitionGUID: UUID,
credentialDefinitionId: _root_.java.lang.String,
claims: Json,
validityPeriod: Option[Double],
automaticIssuance: Option[Boolean],
credentialDefinitionId: _root_.java.lang.String
automaticIssuance: Option[Boolean]
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] =
notifyOnSuccess(
svc.createAnonCredsIssueCredentialRecord(
pairwiseIssuerDID,
pairwiseHolderDID,
thid,
credentialDefinitionGUID,
credentialDefinitionId,
claims,
validityPeriod,
automaticIssuance,
credentialDefinitionId
automaticIssuance
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ object MockCredentialService extends Mock[CredentialService] {
pairwiseHolderDID: DidId,
thid: DidCommID,
credentialDefinitionGUID: UUID,
credentialDefinitionId: String,
claims: Json,
validityPeriod: Option[Double],
automaticIssuance: Option[Boolean],
credentialDefinitionId: String
automaticIssuance: Option[Boolean]
): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] =
proxy(
CreateAnonCredsIssueCredentialRecord,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,16 @@ import io.iohk.atala.mercury.protocol.issuecredential.IssueCredentialIssuedForma
import io.iohk.atala.mercury.protocol.presentproof.*
import io.iohk.atala.pollux.anoncreds.*
import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.CredentialSchemaParsingError
import io.iohk.atala.pollux.core.model.error.PresentationError
import io.iohk.atala.pollux.core.model.error.PresentationError.*
import io.iohk.atala.pollux.core.model.presentation.*
import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1
import io.iohk.atala.pollux.core.repository.{CredentialRepository, PresentationRepository}
import io.iohk.atala.pollux.core.service.serdes.{
AnoncredCredentialProofV1,
AnoncredCredentialProofsV1,
AnoncredPresentationRequestV1,
AnoncredPresentationV1
}
import io.iohk.atala.pollux.core.service.serdes.*
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.models.WalletAccessContext
import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect
import zio.{ZIO, *}
import zio.json.*
import zio.*

import java.net.URI
import java.rmi.UnexpectedException
Expand All @@ -37,7 +29,6 @@ import java.util.{UUID, Base64 as JBase64}
import scala.util.Try

private class PresentationServiceImpl(
credentialDefinitionService: CredentialDefinitionService,
uriDereferencer: URIDereferencer,
linkSecretService: LinkSecretService,
presentationRepository: PresentationRepository,
Expand Down Expand Up @@ -149,8 +140,8 @@ private class PresentationServiceImpl(
)
presentationPayload <- createAnoncredPresentationPayloadFromCredential(
issuedCredentials,
issuedValidCredentials.flatMap(_.schemaId),
issuedValidCredentials.flatMap(_.credentialDefinitionId),
issuedValidCredentials.flatMap(_.schemaUri),
issuedValidCredentials.flatMap(_.credentialDefinitionUri),
requestPresentation,
anoncredCredentialProof.credentialProofs
)
Expand Down Expand Up @@ -502,28 +493,21 @@ private class PresentationServiceImpl(
private def createAnoncredPresentationPayloadFromCredential(
issuedCredentialRecords: Seq[ValidFullIssuedCredentialRecord],
schemaIds: Seq[String],
credentialDefinitionIds: Seq[UUID],
credentialDefinitionIds: Seq[String],
requestPresentation: RequestPresentation,
credentialProofs: List[AnoncredCredentialProofV1],
): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = {
for {
schemaMap <-
ZIO
.collectAll(schemaIds.map { schemaId =>
resolveSchema(schemaId)
.collectAll(schemaIds.map { schemaUri =>
resolveSchema(schemaUri)
})
.map(_.toMap)
credentialDefinitionMap <-
ZIO
.collectAll(credentialDefinitionIds.map { credentialDefinitionId =>
for {
credentialDefinition <- credentialDefinitionService
.getByGUID(credentialDefinitionId)
.mapError(e => UnexpectedError(e.toString))
} yield (
credentialDefinition.longId,
AnoncredCredentialDefinition(credentialDefinition.definition.toString)
)
.collectAll(credentialDefinitionIds.map { credentialDefinitionUri =>
resolveCredentialDefinition(credentialDefinitionUri)
})
.map(_.toMap)
credentialProofsMap = credentialProofs.map(credentialProof => (credentialProof.credential, credentialProof)).toMap
Expand Down Expand Up @@ -593,21 +577,36 @@ private class PresentationServiceImpl(
} yield presentation
}

private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = {
private def resolveSchema(schemaUri: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = {
for {
uri <- ZIO.attempt(new URI(schemaId)).mapError(e => UnexpectedError(e.getMessage))
uri <- ZIO.attempt(new URI(schemaUri)).mapError(e => UnexpectedError(e.getMessage))
content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error))
anoncredSchema <- ZIO
.fromEither(content.fromJson[AnoncredSchemaSerDesV1])
.mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error"))
anoncredSchema <-
AnoncredSchemaSerDesV1.schemaSerDes
.deserialize(content)
.mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error"))
anoncredLibSchema =
AnoncredSchemaDef(
schemaId,
schemaUri,
anoncredSchema.version,
anoncredSchema.attrNames,
anoncredSchema.issuerId
)
} yield (schemaId, anoncredLibSchema)
} yield (schemaUri, anoncredLibSchema)
}

private def resolveCredentialDefinition(
credentialDefinitionUri: String
): IO[UnexpectedError, (String, AnoncredCredentialDefinition)] = {
for {
uri <- ZIO.attempt(new URI(credentialDefinitionUri)).mapError(e => UnexpectedError(e.getMessage))
content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error))
_ <-
PublicCredentialDefinitionSerDesV1.schemaSerDes
.validate(content)
.mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error"))
anoncredCredentialDefinition = AnoncredCredentialDefinition(content)
} yield (credentialDefinitionUri, anoncredCredentialDefinition)
}

def acceptRequestPresentation(
Expand Down Expand Up @@ -952,21 +951,7 @@ private class PresentationServiceImpl(
credentialDefinitionMap <-
ZIO
.collectAll(credentialDefinitionIds.map { credentialDefinitionId =>
for {
guid <-
ZIO
.fromOption(CredentialDefinition.extractGUID(credentialDefinitionId))
.mapError(_ =>
InvalidAnoncredPresentation(s"CredentialDefinitionId format invalid ${credentialDefinitionId}")
)
credentialDefinition <-
credentialDefinitionService
.getByGUID(guid)
.mapError(e => UnexpectedError(e.toString))
} yield (
credentialDefinition.longId,
AnoncredCredentialDefinition(credentialDefinition.definition.toString)
)
resolveCredentialDefinition(credentialDefinitionId)
})
.map(_.toMap)
serializedPresentationRequest <- requestPresentation.attachments.head.data match {
Expand Down Expand Up @@ -1105,8 +1090,8 @@ private class PresentationServiceImpl(

object PresentationServiceImpl {
val layer: URLayer[
CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository,
URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository,
PresentationService
] =
ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _, _))
ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _))
}
Loading

0 comments on commit f050918

Please sign in to comment.