Skip to content

Commit

Permalink
feat: add anoncreds credential definition rest api (#624)
Browse files Browse the repository at this point in the history
Signed-off-by: Bassam Riman <bassam.riman@iohk.io>
Signed-off-by: Anton Baliasnikov <anton.baliasnikov@iohk.io>
Co-authored-by: Anton Baliasnikov <anton.baliasnikov@iohk.io>
  • Loading branch information
CryptoKnightIOG and Anton Baliasnikov committed Aug 30, 2023
1 parent c61999d commit 99e338a
Show file tree
Hide file tree
Showing 56 changed files with 3,250 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
name: "Build and unit tests for ${{ inputs.component-name }}"
runs-on: self-hosted
container:
image: ghcr.io/input-output-hk/atala-qa-automation
image: ghcr.io/input-output-hk/agent-ci-ubuntu-22-jdk-11:0.1.0
volumes:
- /nix:/nix
credentials:
Expand Down
5 changes: 1 addition & 4 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import scala.concurrent.duration.fromNow
import sbtbuildinfo.BuildInfoPlugin.autoImport.*
import org.scoverage.coveralls.Imports.CoverallsKeys._

Expand Down Expand Up @@ -687,9 +686,7 @@ lazy val polluxAnoncreds = project
name := "pollux-anoncreds",
Compile / unmanagedJars += baseDirectory.value / "anoncreds-java-1.0-SNAPSHOT.jar",
Compile / unmanagedResourceDirectories ++= Seq(
// export LD_LIBRARY_PATH=.../anoncreds-rs/uniffi/target/x86_64-unknown-linux-gnu/release:$LD_LIBRARY_PATH,
baseDirectory.value / "native-lib" / "NATIVE" / "darwin-aarch64",
baseDirectory.value / "native-lib" / "NATIVE" / "linux" / "amd64"
baseDirectory.value / "native-lib" / "NATIVE"
),
)

Expand Down
31 changes: 31 additions & 0 deletions infrastructure/charts/agent/templates/apisixroute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,37 @@ spec:

---

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: agent-credential-definition-registry-route
namespace: "{{ .Release.Namespace }}"
labels:
{{ template "labels.common" . }}
spec:
http:
- name: agent-credential-definition-registry-rule
match:
hosts:
{{- range .Values.ingress.applicationUrls }}
- {{ . }}
{{- end }}
paths:
- /prism-agent/credential-definition-registry/definitions/*
methods:
- GET
backends:
- serviceName: agent-server-tapir-service
servicePort: 8085
plugins:
- name: proxy-rewrite
enable: true
config:
regex_uri: ["^/prism-agent/credential-definition-registry/definitions/(.*)", "credential-definition-registry/definitions/$1"]
{{ template "cors" . }}

---

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion pollux/lib/anoncredsTest/src/test/scala/Uniffy.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import uniffi.anoncreds._
import uniffi.anoncreds.*
import uniffi.anoncreds.CredentialDefinition
object Uniffy extends App {
val prover = new Prover()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.iohk.atala.pollux.anoncreds

import org.scalatest.flatspec.AnyFlatSpec

import scala.jdk.CollectionConverters.*

/** polluxAnoncredsTest/Test/testOnly io.iohk.atala.pollux.anoncreds.PoCNewLib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package io.iohk.atala.pollux.core.model.error
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError

sealed trait CredentialSchemaError {
def userMessage: String
def message: String
}

object CredentialSchemaError {
case class SchemaError(schemaError: JsonSchemaError) extends CredentialSchemaError {
def userMessage: String = schemaError.error
def message: String = schemaError.error
}
case class URISyntaxError(userMessage: String) extends CredentialSchemaError
case class CredentialSchemaParsingError(userMessage: String) extends CredentialSchemaError
case class UnsupportedCredentialSchemaType(userMessage: String) extends CredentialSchemaError
case class UnexpectedError(userMessage: String) extends CredentialSchemaError
case class URISyntaxError(message: String) extends CredentialSchemaError
case class CredentialSchemaParsingError(message: String) extends CredentialSchemaError
case class UnsupportedCredentialSchemaType(message: String) extends CredentialSchemaError
case class UnexpectedError(message: String) extends CredentialSchemaError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package io.iohk.atala.pollux.core.model.schema

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.*
import zio.*
import zio.json.*

import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.UUID

type Definition = zio.json.ast.Json
type CorrectnessProof = zio.json.ast.Json

/** @param guid
* Globally unique identifier of the CredentialDefinition object. It's calculated as a UUID from a string that
* contains the following fields: author, id, and version.
* @param id
* Locally unique identifier of the CredentialDefinition. It is a UUID. When the version of the credential definition
* changes, this `id` keeps the same value.
* @param name
* Human-readable name of the CredentialDefinition.
* @param description
* Human-readable description of the CredentialDefinition.
* @param version
* Version of the CredentialDefinition.
* @param author
* DID of the CredentialDefinition's author.
* @param authored
* Datetime stamp of the schema creation.
* @param tags
* Tags of the CredentialDefinition, used for convenient lookup.
* @param schemaId
* Schema ID that identifies the schema associated with this definition.
* @param definition
* Definition object that represents the actual definition of the credential.
* @param keyCorrectnessProof
* A proof that validates the correctness of the key within the context of the credential definition.
* @param signatureType
* Signature type used in the CredentialDefinition.
* @param supportRevocation
* Boolean flag indicating whether revocation is supported for this CredentialDefinition.
*/
case class CredentialDefinition(
guid: UUID,
id: UUID,
name: String,
description: String,
version: String,
author: String,
authored: OffsetDateTime,
tag: String,
schemaId: String,
definitionJsonSchemaId: String,
definition: Definition,
keyCorrectnessProofJsonSchemaId: String,
keyCorrectnessProof: CorrectnessProof,
signatureType: String,
supportRevocation: Boolean
) {
def longId = CredentialDefinition.makeLongId(author, id, version)
}

object CredentialDefinition {

def makeLongId(author: String, id: UUID, version: String) =
s"$author/${id.toString}?version=${version}"

def makeGUID(author: String, id: UUID, version: String) =
UUID.nameUUIDFromBytes(makeLongId(author, id, version).getBytes)

def make(
in: Input,
definitionSchemaId: String,
definition: Definition,
proofSchemaId: String,
proof: CorrectnessProof
): ZIO[Any, Nothing, CredentialDefinition] = {
for {
id <- zio.Random.nextUUID
cs <- make(id, in, definitionSchemaId, definition, proofSchemaId, proof)
} yield cs
}

def make(
id: UUID,
in: Input,
definitionSchemaId: String,
definition: Definition,
keyCorrectnessProofSchemaId: String,
keyCorrectnessProof: CorrectnessProof
): ZIO[Any, Nothing, CredentialDefinition] = {
for {
ts <- zio.Clock.currentDateTime.map(
_.atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime
)
guid = makeGUID(in.author, id, in.version)
} yield CredentialDefinition(
guid = guid,
id = id,
name = in.name,
description = in.description,
version = in.version,
schemaId = in.schemaId,
author = in.author,
authored = in.authored.map(_.atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime).getOrElse(ts),
tag = in.tag,
definitionJsonSchemaId = definitionSchemaId,
definition = definition,
keyCorrectnessProofJsonSchemaId = keyCorrectnessProofSchemaId,
keyCorrectnessProof = keyCorrectnessProof,
signatureType = in.signatureType,
supportRevocation = in.supportRevocation
)
}

val defaultAgentDid = "did:prism:agent"

case class Input(
name: String,
description: String,
version: String,
authored: Option[OffsetDateTime],
tag: String,
author: String = defaultAgentDid,
schemaId: String,
signatureType: String,
supportRevocation: Boolean
)

case class Filter(
author: Option[String] = None,
name: Option[String] = None,
version: Option[String] = None,
tag: Option[String] = None
)

case class FilteredEntries(entries: Seq[CredentialDefinition], count: Long, totalCount: Long)

given JsonEncoder[CredentialDefinition] = DeriveJsonEncoder.gen[CredentialDefinition]

given JsonDecoder[CredentialDefinition] = DeriveJsonDecoder.gen[CredentialDefinition]
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ class SchemaSerDes[S](jsonSchemaSchemaStr: String) {
} yield json
}

def validate(jsonString: String): IO[JsonSchemaError, Unit] = {
def validate(jsonString: String): IO[JsonSchemaError, Boolean] = {
for {
jsonSchemaSchema <- JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr)
schemaValidator = JsonSchemaValidatorImpl(jsonSchemaSchema)
_ <- schemaValidator.validate(jsonString)
} yield {}
} yield true
}

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

import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import io.iohk.atala.pollux.core.repository.Repository.SearchCapability

import java.util.UUID

trait CredentialDefinitionRepository[F[_]]
extends Repository[F, CredentialDefinition]
with SearchCapability[F, CredentialDefinition.Filter, CredentialDefinition] {
def create(cs: CredentialDefinition): F[CredentialDefinition]

def getByGuid(guid: UUID): F[Option[CredentialDefinition]]

def update(cs: CredentialDefinition): F[Option[CredentialDefinition]]

def getAllVersions(id: UUID, author: String): F[Seq[String]]

def delete(guid: UUID): F[Option[CredentialDefinition]]

def deleteAll(): F[Long]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.*
import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import zio.*

import java.util.UUID

class CredentialDefinitionRepositoryInMemory(
storeRef: Ref[Map[UUID, CredentialDefinition]]
) extends CredentialDefinitionRepository[Task] {
override def create(record: CredentialDefinition): Task[CredentialDefinition] = {
for {
_ <- for {
store <- storeRef.get
maybeRecord = store.values.find(_.id == record.guid)
_ <- maybeRecord match
case None => ZIO.unit
case Some(value) => ZIO.fail(UniqueConstraintViolation("Unique Constraint Violation on 'id'"))
} yield ()
_ <- storeRef.update(r => r + (record.guid -> record))
} yield record
}

override def getByGuid(guid: UUID): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
record = store.get(guid)
} yield record
}

override def update(cs: CredentialDefinition): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
maybeExisting = store.get(cs.id)
_ <- maybeExisting match {
case Some(existing) =>
val updatedStore = store.updated(cs.id, cs)
storeRef.set(updatedStore)
case None => ZIO.unit
}
} yield maybeExisting
}

override def getAllVersions(id: UUID, author: String): Task[Seq[String]] = {
storeRef.get.map { store =>
store.values
.filter(credDef => credDef.id == id && credDef.author == author)
.map(_.version)
.toSeq
}
}

override def delete(guid: UUID): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
maybeRecord = store.get(guid)
_ <- maybeRecord match {
case Some(record) => storeRef.update(r => r - record.id)
case None => ZIO.unit
}
} yield maybeRecord
}

override def deleteAll(): Task[Long] = {
for {
store <- storeRef.get
deleted = store.size
_ <- storeRef.update(Map.empty)
} yield deleted.toLong
}

override def search(
query: Repository.SearchQuery[CredentialDefinition.Filter]
): Task[Repository.SearchResult[CredentialDefinition]] = {
storeRef.get.map { store =>
val filtered = store.values.filter { credDef =>
query.filter.author.forall(_ == credDef.author) &&
query.filter.name.forall(_ == credDef.name) &&
query.filter.version.forall(_ == credDef.version) &&
query.filter.tag.forall(tag => credDef.tag == tag)
}
val paginated = filtered.slice(query.skip, query.skip + query.limit)
Repository.SearchResult(paginated.toSeq, paginated.size, filtered.size)
}
}
}

object CredentialDefinitionRepositoryInMemory {
val layer: ULayer[CredentialDefinitionRepository[Task]] = ZLayer.fromZIO(
Ref
.make(Map.empty[UUID, CredentialDefinition])
.map(CredentialDefinitionRepositoryInMemory(_))
)
}
Loading

0 comments on commit 99e338a

Please sign in to comment.