Skip to content

Commit

Permalink
feat(prism-agent): implement DID resolution endpoint (#184)
Browse files Browse the repository at this point in the history
* ci(prism-node): add ci workflow

* ci(prism-node): rename file to prism-node-client.yml

* feat(castor): update castor to use prism-node stub

* feat(castor): implement DID parser backed by prism-identity 1.4

* feat(castor): update return type of PrismDID.buildLongForm

* test(castor): add public key id validation

* chore(castor): pr diff cleanup

* feat(castor): add service to domain model and implement validation

* feat(castor): implement DID parser backed by prism-identity 1.4

* feat(prism-agent): update castor with new  protobuf model

* chore(prism-agent): remove unused commitment secret storage

* test(prism-agent): fix and add test for template validation

* feat(prism-agent): sign AtalaOperation with master-key for publishing

* test(prism-agent): fix tests for publishing created DID

* feat(prism-agent): use hex string for operation id presentation

* test(prism-agent): add DID template validation test

* chore(prism-agent): commented code cleanup

* feat(prism-agent): integrate castor operation validator into wallet-api

* chore(prism-agent): cleanup unused castor db layers

* feat(prism-agent): make wallet DID publication status trackable

* test(prism-agent): make test compile

* feat(prism-agent): sync DID state from DLT

* revert: "feat(castor): implement DID parser backed by prism-identity 1.4"

This reverts commit ae5dcd1.

* chore(prism-agent): pr diff cleanup

* fix(prism-agent): fix compilation conflict

* fix(castor): remove duplicated method

* Bump up dependency version

* feat(castor): partially implement resolveDID

* fix(castor): fix parsing bug from Node response

* feat(castor): convert domain model to w3c representation

* feat(castor): w3c public-key and service translation

* feat(castor): translate jwk publickey

* feat(castor): complete w3c DIDDocument translation

* feat(castor): implement unpublished DID resolution

* fix(castor): fix unpublished did initial state removal

* feat(castor): transform EC point x y with base64 no padding

* feat(castor): resolve DID return metadata

* pr diff cleanup

* pr diff clean up

* chore(castor): remove unused method

* feat(castor): add filtering of revoked keys

* feat(castor): implement deactivate flag metadata

* feat(castor): resolve DID return metadata

* feat(prism-agent): implement resolution endpoint

* feat(prism-agent): simplify DIDDocument model

* fix(prism-agent): fix test compilation

* chore(prism-agent): revert unwanted config chages

* fix(prism-agent): fix compilation error

* chore(castor): remove unwanted logging

* pr diff cleanup

* feat(prism-agent): upgrade castor 0.4.0
  • Loading branch information
patlo-iog authored Dec 2, 2022
1 parent 2c3f19c commit 7fba9b0
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 60 deletions.
52 changes: 21 additions & 31 deletions prism-agent/service/api/http/castor/schemas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ components:
type: object
required:
- did
- type
- deactivated
- metadata
properties:
did:
$ref: "#/components/schemas/DID"
deactivated:
type: boolean
metadata:
$ref: "#/components/schemas/DIDDocumentMetadata"

DIDOperationResponse:
type: object
Expand Down Expand Up @@ -38,55 +37,52 @@ components:
authentication:
type: array
items:
$ref: "#/components/schemas/VerificationMethodOrRef"
$ref: "#/components/schemas/VerificationMethod"
assertionMethod:
type: array
items:
$ref: "#/components/schemas/VerificationMethodOrRef"
$ref: "#/components/schemas/VerificationMethod"
keyAgreement:
type: array
items:
$ref: "#/components/schemas/VerificationMethodOrRef"
$ref: "#/components/schemas/VerificationMethod"
capabilityInvocation:
type: array
items:
$ref: "#/components/schemas/VerificationMethodOrRef"
$ref: "#/components/schemas/VerificationMethod"
service:
type: array
items:
$ref: "#/components/schemas/Service"

VerificationMethodOrRef:
description: |
A reference to VerificationMethod or VerificationMethod object.
Exactly one property must be present to be valid.
DIDDocumentMetadata:
type: object
required:
- deactivated
properties:
ref:
type: string
example: "did:example:123456789abcdefghi#keys-1"
verificationMethod:
$ref: "#/components/schemas/VerificationMethod"
deactivated:
type: boolean
description: If a DID has been deactivated, DID document metadata MUST include this property with the boolean value true. If a DID has not been deactivated, this property is OPTIONAL, but if included, MUST have the boolean value false.

VerificationMethod:
type: object
required:
- id
- type
- controller
- jsonWebKey2020
- publicKeyJwk
properties:
id:
type: string
example: "did:prism:123#key-1"
type:
type: string
example: "JsonWebKey2020"
example: "EcdsaSecp256k1VerificationKey2019"
controller:
type: string
example: "did:prism:456"
jsonWebKey2020:
$ref: "#/components/schemas/JsonWebKey2020"
publicKeyJwk:
$ref: "#/components/schemas/PublicKeyJwk"

CreateDIDRequest:
type: object
Expand Down Expand Up @@ -267,14 +263,14 @@ components:
- type
- controller
- purposes
- jsonWebKey2020
- publicKeyJwk
properties:
id:
type: string
example: "key-1"
type:
type: string
example: "JsonWebKey2020"
example: "EcdsaSecp256k1VerificationKey2019"
controller:
type: string
example: "did:prism:456"
Expand All @@ -291,19 +287,13 @@ components:
"capabilityDelegation",
]
example: [ "authentication", "assertionMethod" ]
jsonWebKey2020:
$ref: "#/components/schemas/JsonWebKey2020"

JsonWebKey2020:
type: object
required:
- publicKeyJwk
properties:
publicKeyJwk:
$ref: "#/components/schemas/PublicKeyJwk"

PublicKeyJwk:
type: object
required:
- kty
properties:
crv:
type: string
Expand Down
2 changes: 1 addition & 1 deletion prism-agent/service/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Dependencies {
val zioInteropCats = "3.3.0"
val akka = "2.6.20"
val akkaHttp = "10.2.9"
val castor = "0.3.0"
val castor = "0.4.0"
val pollux = "0.5.0"
val connect = "0.2.0"
val bouncyCastle = "1.70"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,21 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
given RootJsonFormat[Delta] = jsonFormat2(Delta.apply)
given RootJsonFormat[DeltaUpdate] = jsonFormat2(DeltaUpdate.apply)
given RootJsonFormat[DID] = jsonFormat8(DID.apply)
given RootJsonFormat[DIDDocumentMetadata] = jsonFormat1(DIDDocumentMetadata.apply)
given RootJsonFormat[DidOperation] = jsonFormat4(DidOperation.apply)
given RootJsonFormat[DIDOperationResponse] = jsonFormat1(DIDOperationResponse.apply)
given RootJsonFormat[DidOperationStatus] = jsonFormat0(DidOperationStatus.apply)
given RootJsonFormat[DidOperationSubmission] = jsonFormat2(DidOperationSubmission.apply)
given RootJsonFormat[DidOperationType] = jsonFormat0(DidOperationType.apply)
given RootJsonFormat[DIDResponse] = jsonFormat2(DIDResponse.apply)
given RootJsonFormat[ErrorResponse] = jsonFormat5(ErrorResponse.apply)
given RootJsonFormat[JsonWebKey2020] = jsonFormat1(JsonWebKey2020.apply)
given RootJsonFormat[PublicKey] = jsonFormat5(PublicKey.apply)
given RootJsonFormat[PublicKeyJwk] = jsonFormat5(PublicKeyJwk.apply)
given RootJsonFormat[RecoverDIDRequest] = jsonFormat5(RecoverDIDRequest.apply)
given RootJsonFormat[Service] = jsonFormat3(Service.apply)
given RootJsonFormat[UpdateDIDRequest] = jsonFormat4(UpdateDIDRequest.apply)
given RootJsonFormat[UpdatePatch] = jsonFormat2(UpdatePatch.apply)
given RootJsonFormat[VerificationMethod] = jsonFormat4(VerificationMethod.apply)
given RootJsonFormat[VerificationMethodOrRef] = jsonFormat2(VerificationMethodOrRef.apply)

// Issue Credential Protocol
implicit object UUIDFormat extends JsonFormat[UUID] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package io.iohk.atala.agent.server.http.model

import io.iohk.atala.agent.openapi.model.{
Connection,
ConnectionInvitation,
CreateDIDRequest,
CreateManagedDidRequestDocumentTemplate,
CreateManagedDidRequestDocumentTemplatePublicKeysInner,
DID,
DIDDocumentMetadata,
DIDOperationResponse,
DIDResponse,
DidOperation,
DidOperationSubmission,
JsonWebKey2020,
IssueCredentialRecord,
IssueCredentialRecordCollection,
PublicKey,
PublicKeyJwk,
Service
Service,
VerificationMethod
}
import io.iohk.atala.castor.core.model.did as castorDomain
import io.iohk.atala.agent.walletapi.model as walletDomain
Expand All @@ -22,16 +29,13 @@ import io.iohk.atala.shared.utils.Traverse.*

import java.net.URI
import scala.util.Try
import io.iohk.atala.agent.openapi.model.IssueCredentialRecord
import io.iohk.atala.agent.openapi.model.IssueCredentialRecordCollection
import java.time.OffsetDateTime
import java.time.ZoneOffset
import io.iohk.atala.mercury.model.AttachmentDescriptor
import io.iohk.atala.mercury.model.Base64
import io.iohk.atala.agent.openapi.model.Connection
import io.iohk.atala.agent.openapi.model.ConnectionInvitation
import zio.ZIO
import io.iohk.atala.agent.server.http.model.HttpServiceError.InvalidPayload

import java.util.UUID
import io.iohk.atala.connect.core.model.ConnectionRecord.Role

Expand Down Expand Up @@ -148,4 +152,54 @@ trait OASDomainModelHelper {
.mapError(e => HttpServiceError.InvalidPayload(s"Error parsing string as UUID: ${e.getMessage()}"))
}

extension (resolution: (castorDomain.w3c.DIDDocumentMetadataRepr, castorDomain.w3c.DIDDocumentRepr)) {
def toOAS: DIDResponse = {
val (metadata, didDoc) = resolution
DIDResponse(
did = DID(
id = didDoc.id,
controller = Some(didDoc.controller),
verificationMethod = Some(didDoc.verificationMethod.map(_.toOAS)),
authentication = Some(didDoc.authentication.map(_.toOAS)),
assertionMethod = Some(didDoc.assertionMethod.map(_.toOAS)),
keyAgreement = Some(didDoc.keyAgreement.map(_.toOAS)),
capabilityInvocation = Some(didDoc.capabilityInvocation.map(_.toOAS)),
service = Some(didDoc.service.map(_.toOAS))
),
metadata = DIDDocumentMetadata(deactivated = metadata.deactivated)
)
}
}

extension (publicKeyRepr: castorDomain.w3c.PublicKeyRepr) {
def toOAS: VerificationMethod = {
VerificationMethod(
id = publicKeyRepr.id,
`type` = publicKeyRepr.`type`,
controller = publicKeyRepr.controller,
publicKeyJwk = publicKeyRepr.publicKeyJwk.toOAS
)
}
}

extension (publicKeyJwk: castorDomain.w3c.PublicKeyJwk) {
def toOAS: PublicKeyJwk = {
PublicKeyJwk(
crv = Some(publicKeyJwk.crv),
x = Some(publicKeyJwk.x),
y = Some(publicKeyJwk.y),
kty = publicKeyJwk.kty,
kid = None
)
}
}

extension (service: castorDomain.w3c.ServiceRepr) {
def toOAS: Service = Service(
id = service.id,
`type` = service.`type`,
serviceEndpoint = service.serviceEndpoint
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package io.iohk.atala.agent.server.http.model
import akka.http.scaladsl.server.StandardRoute
import io.iohk.atala.agent.openapi.model.ErrorResponse
import io.iohk.atala.agent.walletapi.model.error.{CreateManagedDIDError, PublishManagedDIDError}
import io.iohk.atala.castor.core.model.error.DIDOperationError
import io.iohk.atala.castor.core.model.did.w3c.DIDResolutionErrorRepr
import io.iohk.atala.castor.core.model.error.{DIDOperationError, DIDResolutionError}

import java.util.UUID
import io.iohk.atala.pollux.core.model.error.IssueCredentialError
import io.iohk.atala.connect.core.model.error.ConnectionError
Expand Down Expand Up @@ -66,6 +68,29 @@ trait OASErrorModelHelper {
}
}

given ToErrorResponse[DIDResolutionErrorRepr] with {
override def toErrorResponse(e: DIDResolutionErrorRepr): ErrorResponse = {
import DIDResolutionErrorRepr.*
val status = e match {
case InvalidDID => 422
case InvalidDIDUrl => 422
case NotFound => 404
case RepresentationNotSupported => 422
case InternalError => 500
case InvalidPublicKeyLength => 422
case InvalidPublicKeyType => 422
case UnsupportedPublicKeyType => 422
}
ErrorResponse(
`type` = "error-type",
title = e.value,
status = status,
detail = Some(e.toString),
instance = "error-instance"
)
}
}

given ToErrorResponse[IssueCredentialError] with {
def toErrorResponse(error: IssueCredentialError): ErrorResponse = {
ErrorResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import akka.http.scaladsl.server.Directives.*
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.server.Route
import io.iohk.atala.castor.core.service.DIDService
import io.iohk.atala.castor.core.model.did.PrismDID
import io.iohk.atala.castor.core.model.did.w3c.W3CModelHelper.*
import io.iohk.atala.castor.core.model.did.w3c.makeW3CResolver
import io.iohk.atala.agent.openapi.api.DIDApiService
import io.iohk.atala.agent.openapi.model.*
import io.iohk.atala.agent.server.http.model.{HttpServiceError, OASDomainModelHelper, OASErrorModelHelper}
Expand All @@ -23,22 +26,16 @@ class DIDApiServiceImpl(service: DIDService)(using runtime: Runtime[Any])
verificationMethod = None,
authentication = Some(
Seq(
VerificationMethodOrRef(verificationMethod =
Some(
VerificationMethod(
id = "did:prism:1:mainnet:abcdef123456#key-1",
`type` = "JsonWebKey2020",
controller = "did:prism:1:mainnet:abcdef123456",
jsonWebKey2020 = JsonWebKey2020(
publicKeyJwk = PublicKeyJwk(
crv = Some("P-256"),
x = Some("38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8"),
y = Some("nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"),
kty = Some("EC"),
kid = Some("_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw")
)
)
)
VerificationMethod(
id = "did:prism:1:mainnet:abcdef123456#key-1",
`type` = "JsonWebKey2020",
controller = "did:prism:1:mainnet:abcdef123456",
publicKeyJwk = PublicKeyJwk(
crv = Some("P-256"),
x = Some("38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8"),
y = Some("nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"),
kty = "EC",
kid = Some("_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw")
)
)
)
Expand All @@ -51,7 +48,7 @@ class DIDApiServiceImpl(service: DIDService)(using runtime: Runtime[Any])

private val mockDIDResponse = DIDResponse(
did = mockDID,
deactivated = false
metadata = DIDDocumentMetadata(deactivated = false)
)

private val mockDIDOperationResponse = DIDOperationResponse(
Expand Down Expand Up @@ -79,7 +76,11 @@ class DIDApiServiceImpl(service: DIDService)(using runtime: Runtime[Any])
toEntityMarshallerDIDResponse: ToEntityMarshaller[DIDResponse],
toEntityMarshallerErrorResponse: ToEntityMarshaller[ErrorResponse]
): Route = {
onZioSuccess(ZIO.unit) { _ => getDid200(mockDIDResponse) }
val result = makeW3CResolver(service)(didRef).mapError(HttpServiceError.DomainError.apply)
onZioSuccess(result.mapBoth(_.toOAS, _.toOAS).either) {
case Left(error) => complete(error.status -> error)
case Right(response) => getDid200(response)
}
}

override def recoverDid(didRef: String, recoverDIDRequest: RecoverDIDRequest)(implicit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package io.iohk.atala.agent.walletapi.service
import io.iohk.atala.agent.walletapi.model.error.{CreateManagedDIDError, PublishManagedDIDError}
import io.iohk.atala.agent.walletapi.model.{DIDPublicKeyTemplate, ManagedDIDState, ManagedDIDTemplate}
import io.iohk.atala.castor.core.model.did.{
DIDData,
DIDMetadata,
PrismDID,
PrismDIDOperation,
ScheduleDIDOperationOutcome,
ScheduledDIDOperationStatus,
ScheduledDIDOperationDetail,
ScheduledDIDOperationStatus,
Service,
SignedPrismDIDOperation,
VerificationRelationship
Expand Down Expand Up @@ -44,6 +46,8 @@ object ManagedDIDServiceSpec extends ZIOSpecDefault {
)
)

override def resolveDID(did: PrismDID): IO[error.DIDResolutionError, Option[(DIDMetadata, DIDData)]] = ZIO.none

override def getScheduledDIDOperationDetail(
operationId: Array[Byte]
): IO[error.DIDOperationError, Option[ScheduledDIDOperationDetail]] =
Expand Down

0 comments on commit 7fba9b0

Please sign in to comment.