Skip to content

Commit

Permalink
[ATL-1926] feat(castor): implement createPublishedDID functionality (…
Browse files Browse the repository at this point in the history
…1) (#48)

* feat(castor): partially implement createPublishedDID

* feat(castor): add missing layer in prism-agent

* feat(castor): fix typo

* feat(castor): move common primitives to shared

* feat(castor): move common primitives to shared
  • Loading branch information
patlo-iog authored Oct 6, 2022
1 parent 7629dc7 commit 4e5260e
Show file tree
Hide file tree
Showing 32 changed files with 586 additions and 23 deletions.
Empty file removed castor/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions castor/lib/.sbtopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-J-Xmx4G
-J-XX:MaxMetaspaceSize=4G
-J-Xss8M
1 change: 1 addition & 0 deletions castor/lib/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ThisBuild / scalaVersion := "3.1.3"
ThisBuild / organization := "io.iohk.atala"

val commonSettings = Seq(
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")),
githubTokenSource := TokenSource.Environment("ATALA_GITHUB_TOKEN"),
resolvers += Resolver.githubPackages("input-output-hk", "atala-prism-sdk"),
// Needed for Kotlin coroutines that support new memory management mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ package io.iohk.atala.castor.core.model

// TODO: replace with actual implementation
final case class IrisNotification(foo: String)

final case class PublishedDIDOperation(foo: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package io.iohk.atala.castor.core.model

import com.google.protobuf.ByteString
import io.iohk.atala.shared.models.HexStrings.*
import io.iohk.atala.castor.core.model.did.{
DIDDocument,
EllipticCurve,
PublicKey,
PublicKeyJwk,
PublishedDIDOperation,
Service,
ServiceType,
VerificationRelationship
}
import io.iohk.atala.iris.proto as iris_proto

private[castor] trait ProtoModelHelper {

extension (bytes: Array[Byte]) {
def toProto: ByteString = ByteString.copyFrom(bytes)
}

extension (operation: PublishedDIDOperation.Create) {
def toProto: iris_proto.dlt.IrisOperation = {
iris_proto.dlt.IrisOperation(
operation = iris_proto.dlt.IrisOperation.Operation.CreateDid(
value = iris_proto.did_operations.CreateDid(
initialUpdateCommitment = operation.updateCommitment.toByteArray.toProto,
initialRecoveryCommitment = operation.recoveryCommitment.toByteArray.toProto,
ledger = operation.storage.ledgerName,
document = Some(operation.document.toProto)
)
)
)
}
}

extension (doc: DIDDocument) {
def toProto: iris_proto.did_operations.DocumentDefinition = {
iris_proto.did_operations.DocumentDefinition(
publicKeys = doc.publicKeys.map(_.toProto),
services = doc.services.map(_.toProto)
)
}
}

extension (service: Service) {
def toProto: iris_proto.did_operations.DocumentDefinition.Service = {
iris_proto.did_operations.DocumentDefinition.Service(
id = service.id,
`type` = service.`type`.toProto,
serviceEndpoint = service.serviceEndpoint.toString
)
}
}

extension (key: PublicKey) {
def toProto: iris_proto.did_operations.DocumentDefinition.PublicKey = {
key match {
case k: PublicKey.JsonWebKey2020 =>
iris_proto.did_operations.DocumentDefinition.PublicKey(
id = k.id,
jwk = Some(k.publicKeyJwk.toProto),
purposes = k.purposes.map(_.toProto)
)
}
}
}

extension (serviceType: ServiceType) {
def toProto: iris_proto.did_operations.DocumentDefinition.Service.Type = {
import iris_proto.did_operations.DocumentDefinition.Service.Type.*
serviceType match {
case ServiceType.MediatorService => MEDIATOR_SERVICE
}
}
}

extension (purpose: VerificationRelationship) {
def toProto: iris_proto.did_operations.DocumentDefinition.PublicKey.Purpose = {
import iris_proto.did_operations.DocumentDefinition.PublicKey.Purpose.*
purpose match {
case VerificationRelationship.Authentication => AUTHENTICATION
case VerificationRelationship.AssertionMethod => ASSERTION_METHOD
case VerificationRelationship.KeyAgreement => KEY_AGREEMENT
case VerificationRelationship.CapabilityInvocation => CAPABILITY_INVOCATION
}
}
}

extension (key: PublicKeyJwk) {
def toProto: iris_proto.did_operations.PublicKeyJwk = {
key match {
case k: PublicKeyJwk.ECPublicKeyData =>
iris_proto.did_operations.PublicKeyJwk(
key = iris_proto.did_operations.PublicKeyJwk.Key.EcKey(
iris_proto.did_operations.PublicKeyJwk.ECKeyData(
curve = k.crv.toProto,
x = k.x.toByteArray.toProto,
y = k.y.toByteArray.toProto
)
)
)
}
}
}

extension (curve: EllipticCurve) {
def toProto: iris_proto.did_operations.PublicKeyJwk.Curve = {
import iris_proto.did_operations.PublicKeyJwk.Curve.*
curve match {
case EllipticCurve.SECP256K1 => SECP256K1
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.iohk.atala.castor.core.model.did

final case class DID(
method: String,
methodSpecificId: String
) {
override def toString: String = s"did:$method:$methodSpecificId"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.iohk.atala.castor.core.model.did

final case class DIDDocument(
publicKeys: Seq[PublicKey],
services: Seq[Service]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.iohk.atala.castor.core.model.did

import java.net.URL

sealed abstract class DIDStorage

object DIDStorage {
final case class Cardano(ledgerName: String) extends DIDStorage
final case class SecondaryStorage(uri: URL) extends DIDStorage
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.iohk.atala.castor.core.model.did

final case class DIDUrl(
did: DID,
path: Seq[String],
parameters: Map[String, Seq[String]],
fragment: Option[String]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.iohk.atala.castor.core.model.did

sealed trait EllipticCurve

object EllipticCurve {
case object SECP256K1 extends EllipticCurve
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.iohk.atala.castor.core.model.did

sealed abstract class PublicKey {
val id: String
val purposes: Seq[VerificationRelationship]
}

object PublicKey {
final case class JsonWebKey2020(
id: String,
purposes: Seq[VerificationRelationship],
publicKeyJwk: PublicKeyJwk
) extends PublicKey
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.iohk.atala.castor.core.model.did

import io.iohk.atala.shared.models.Base64UrlStrings.*

sealed trait PublicKeyJwk

object PublicKeyJwk {
final case class ECPublicKeyData(
crv: EllipticCurve,
x: Base64UrlString,
y: Base64UrlString
) extends PublicKeyJwk
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.iohk.atala.castor.core.model.did

import io.iohk.atala.shared.models.HexStrings.*

sealed trait PublishedDIDOperation

object PublishedDIDOperation {
final case class Create(
updateCommitment: HexString,
recoveryCommitment: HexString,
storage: DIDStorage.Cardano,
document: DIDDocument
) extends PublishedDIDOperation
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.iohk.atala.castor.core.model.did

import java.net.URI

final case class Service(
id: String,
`type`: ServiceType,
serviceEndpoint: URI
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.iohk.atala.castor.core.model.did

sealed trait ServiceType

object ServiceType {
case object MediatorService extends ServiceType
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.iohk.atala.castor.core.model.did

sealed trait VerificationRelationship

object VerificationRelationship {
case object Authentication extends VerificationRelationship

case object AssertionMethod extends VerificationRelationship

case object KeyAgreement extends VerificationRelationship

case object CapabilityInvocation extends VerificationRelationship
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.iohk.atala.castor.core.model.did.repr

import io.iohk.atala.castor.core.model.did.{DID, DIDUrl}

/** A projection of DIDDocument data model to W3C compliant DID representation */
final case class DIDDocumentRepr(
id: String,
controller: String,
verificationMethod: Seq[PublicKeyRepr],
authentication: Seq[PublicKeyRepr | String],
assertionMethod: Seq[PublicKeyRepr | String],
keyAgreement: Seq[PublicKeyRepr | String],
capabilityInvocation: Seq[PublicKeyRepr | String],
service: Seq[ServiceRepr]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.iohk.atala.castor.core.model.did.repr

final case class PublicKeyRepr(
id: String,
`type`: String,
controller: String,
jsonWebKey2020: Option[JsonWebKey2020Repr]
)

final case class JsonWebKey2020Repr(
publicKeyJwk: PublicKeyJwkRepr
)

final case class PublicKeyJwkRepr(
crv: String,
x: String,
y: String,
kty: String,
kid: Option[String]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.iohk.atala.castor.core.model.did.repr

import java.net.URI
import io.iohk.atala.castor.core.model.did.DIDUrl

final case class ServiceRepr(
id: String,
`type`: String,
serviceEndpoint: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.iohk.atala.castor.core.model

package object error {

sealed trait DIDOperationError
object DIDOperationError {
final case class DLTProxyError(cause: Throwable) extends DIDOperationError
final case class InvalidArgument(msg: String) extends DIDOperationError
final case class TooManyDidPublicKeyAccess(limit: Int, access: Option[Int]) extends DIDOperationError
final case class TooManyDidServiceAccess(limit: Int, access: Option[Int]) extends DIDOperationError
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.iohk.atala.castor.core.repository

import io.iohk.atala.castor.core.model.PublishedDIDOperation
import io.iohk.atala.castor.core.model.IrisNotification
import zio.*

// TODO: replace with actual implementation
trait DIDOperationRepository[F[_]] {
def getPublishedOperations: F[Seq[PublishedDIDOperation]]
def getIrisNotification: F[Seq[IrisNotification]]
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
package io.iohk.atala.castor.core.service

import io.iohk.atala.castor.core.model.did.{DIDDocument, PublishedDIDOperation}
import zio.*
import io.iohk.atala.castor.core.model.ProtoModelHelper
import io.iohk.atala.castor.core.model.error.DIDOperationError
import io.iohk.atala.castor.core.util.DIDOperationValidator
import io.iohk.atala.iris.proto.service.IrisServiceGrpc.IrisServiceStub

// TODO: replace with actual implementation
trait DIDService {
def resolveDID(did: String): UIO[Unit]
def createPublishedDID(operation: PublishedDIDOperation.Create): IO[DIDOperationError, DIDDocument]
}

object MockDIDService {
val layer: ULayer[DIDService] = ZLayer.succeed {
new DIDService {
override def resolveDID(did: String): UIO[Unit] = ZIO.unit
def createPublishedDID(operation: PublishedDIDOperation.Create): IO[DIDOperationError, DIDDocument] =
ZIO.fail(DIDOperationError.InvalidArgument("mocked error"))
}
}
}

object DIDServiceImpl {
val layer: URLayer[IrisServiceStub & DIDOperationValidator, DIDService] = ZLayer.fromFunction(DIDServiceImpl(_, _))
}

private class DIDServiceImpl(irisClient: IrisServiceStub, operationValidator: DIDOperationValidator)
extends DIDService,
ProtoModelHelper {

// TODO:
// 1. generate DID identifier from operation
// 2. check if DID already exists
// 3. persist state
override def createPublishedDID(operation: PublishedDIDOperation.Create): IO[DIDOperationError, DIDDocument] = {
for {
_ <- ZIO.fromEither(operationValidator.validate(operation))
_ <- ZIO
.fromFuture(_ => irisClient.scheduleOperation(operation.toProto))
.mapError(DIDOperationError.DLTProxyError.apply)
} yield operation.document
}

}
Loading

0 comments on commit 4e5260e

Please sign in to comment.