• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / atala-prism-building-blocks / 8719402531

17 Apr 2024 09:15AM UTC coverage: 31.196% (-0.3%) from 31.477%
8719402531

Pull #966

patlo-iog
feat: key management for Ed25519 and X25519

fix: support jwk okp for ECCompressKeyData in DID doc

feat: store random key via jdbc

build: move json from shared to castor

fix: store actual jwk with RLS added

chore: remove redendant implicits

wip: get key metadata

wip: read key from jdbc secret storage

chore: remove in-memory impl

chore: use external lib for jwk

feat: get keypair of any type

fix: non secret sql

feat: support update-operation with new key type

chore: cleanup

fix: managed did curve usage validation

feat: store did secret vault impl

chore: cleanup
Signed-off-by: Pat Losoponkul <pat.losoponkul@iohk.io>
Pull Request #966: feat: key management for Ed25519 and X25519

171 of 471 new or added lines in 23 files covered. (36.31%)

670 existing lines in 115 files now uncovered.

4490 of 14393 relevant lines covered (31.2%)

0.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.27
/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/sql/package.scala
1
package io.iohk.atala.agent.walletapi
2

3
import com.nimbusds.jose.jwk.OctetKeyPair
4
import doobie.*
5
import doobie.postgres.implicits.*
6
import doobie.util.invariant.InvalidEnum
7
import io.circe.*
8
import io.circe.parser.*
9
import io.circe.syntax.*
10
import io.iohk.atala.agent.walletapi.model.Wallet
11
import io.iohk.atala.agent.walletapi.model.{ManagedDIDState, PublicationState, KeyManagementMode}
12
import io.iohk.atala.castor.core.model.ProtoModelHelper.*
13
import io.iohk.atala.castor.core.model.did.EllipticCurve
14
import io.iohk.atala.castor.core.model.did.InternalKeyPurpose
15
import io.iohk.atala.castor.core.model.did.VerificationRelationship
16
import io.iohk.atala.castor.core.model.did.{PrismDID, PrismDIDOperation, ScheduledDIDOperationStatus}
17
import io.iohk.atala.event.notification.EventNotificationConfig
18
import io.iohk.atala.prism.protos.node_models
19
import io.iohk.atala.shared.crypto.jwk.JWK
20
import io.iohk.atala.shared.models.WalletId
21
import zio.json.*
22
import zio.json.ast.Json
23
import zio.json.ast.Json.*
24

25
import java.net.URL
26
import java.time.Instant
27
import java.util.UUID
28
import scala.collection.immutable.ArraySeq
29
import scala.util.Try
30

31
package object sql {
32

33
  sealed trait PublicationStatusType
34
  object PublicationStatusType {
35
    case object CREATED extends PublicationStatusType
36
    case object PUBLICATION_PENDING extends PublicationStatusType
37
    case object PUBLISHED extends PublicationStatusType
38

1✔
39
    def from(status: PublicationState): PublicationStatusType = status match {
1✔
40
      case PublicationState.Created()             => CREATED
1✔
41
      case PublicationState.PublicationPending(_) => PUBLICATION_PENDING
1✔
42
      case PublicationState.Published(_)          => PUBLISHED
43
    }
44
  }
45

1✔
46
  given Meta[VerificationRelationship | InternalKeyPurpose] = pgEnumString(
47
    "PRISM_DID_KEY_USAGE",
48
    {
1✔
49
      case "MASTER"                => InternalKeyPurpose.Master
×
50
      case "ISSUING"               => VerificationRelationship.AssertionMethod
1✔
51
      case "KEY_AGREEMENT"         => VerificationRelationship.KeyAgreement
1✔
52
      case "AUTHENTICATION"        => VerificationRelationship.Authentication
×
53
      case "REVOCATION"            => InternalKeyPurpose.Revocation
×
54
      case "CAPABILITY_INVOCATION" => VerificationRelationship.CapabilityInvocation
×
55
      case "CAPABILITY_DELEGATION" => VerificationRelationship.CapabilityDelegation
×
56
      case s                       => throw InvalidEnum[VerificationRelationship | InternalKeyPurpose](s)
57
    },
58
    {
1✔
59
      case InternalKeyPurpose.Master                     => "MASTER"
1✔
60
      case VerificationRelationship.AssertionMethod      => "ISSUING"
1✔
61
      case VerificationRelationship.KeyAgreement         => "KEY_AGREEMENT"
1✔
62
      case VerificationRelationship.Authentication       => "AUTHENTICATION"
×
63
      case InternalKeyPurpose.Revocation                 => "REVOCATION"
×
64
      case VerificationRelationship.CapabilityInvocation => "CAPABILITY_INVOCATION"
×
65
      case VerificationRelationship.CapabilityDelegation => "CAPABILITY_DELEGATION"
66
    }
67
  )
68

1✔
69
  given Meta[KeyManagementMode] = pgEnumString(
70
    "PRISM_DID_KEY_MODE",
71
    {
1✔
NEW
72
      case "HD"     => KeyManagementMode.HD
×
NEW
73
      case "RANDOM" => KeyManagementMode.RANDOM
×
74
      case s        => throw InvalidEnum[KeyManagementMode](s)
75
    },
76
    {
1✔
NEW
77
      case KeyManagementMode.HD     => "HD"
×
78
      case KeyManagementMode.RANDOM => "RANDOM"
79
    }
80
  )
81

1✔
82
  given Meta[EllipticCurve] = pgEnumString(
83
    "CURVE_NAME",
1✔
84
    s => EllipticCurve.parseString(s).get,
85
    _.name
86
  )
87

1✔
88
  given Meta[PublicationStatusType] = pgEnumString(
89
    "PRISM_DID_WALLET_STATUS",
90
    {
1✔
91
      case "CREATED"             => PublicationStatusType.CREATED
1✔
92
      case "PUBLICATION_PENDING" => PublicationStatusType.PUBLICATION_PENDING
1✔
93
      case "PUBLISHED"           => PublicationStatusType.PUBLISHED
×
94
      case s                     => throw InvalidEnum[PublicationStatusType](s)
95
    },
96
    {
1✔
97
      case PublicationStatusType.CREATED             => "CREATED"
1✔
98
      case PublicationStatusType.PUBLICATION_PENDING => "PUBLICATION_PENDING"
1✔
99
      case PublicationStatusType.PUBLISHED           => "PUBLISHED"
100
    }
101
  )
102

1✔
103
  given Meta[ScheduledDIDOperationStatus] = pgEnumString(
104
    "PRISM_DID_OPERATION_STATUS",
105
    {
1✔
106
      case "PENDING_SUBMISSION"     => ScheduledDIDOperationStatus.Pending
×
107
      case "AWAIT_CONFIRMATION"     => ScheduledDIDOperationStatus.AwaitingConfirmation
1✔
108
      case "CONFIRMED_AND_APPLIED"  => ScheduledDIDOperationStatus.Confirmed
×
109
      case "CONFIRMED_AND_REJECTED" => ScheduledDIDOperationStatus.Rejected
×
110
      case s                        => throw InvalidEnum[ScheduledDIDOperationStatus](s)
111
    },
112
    {
1✔
113
      case ScheduledDIDOperationStatus.Pending              => "PENDING_SUBMISSION"
1✔
114
      case ScheduledDIDOperationStatus.AwaitingConfirmation => "AWAIT_CONFIRMATION"
1✔
115
      case ScheduledDIDOperationStatus.Confirmed            => "CONFIRMED_AND_APPLIED"
×
116
      case ScheduledDIDOperationStatus.Rejected             => "CONFIRMED_AND_REJECTED"
117
    }
118
  )
UNCOV
119

×
120
  given prismDIDGet: Get[PrismDID] = Get[String].map(PrismDID.fromString(_).left.map(Exception(_)).toTry.get)
1✔
121
  given prismDIDPut: Put[PrismDID] = Put[String].contramap(_.asCanonical.toString)
122

1✔
123
  given arraySeqByteGet: Get[ArraySeq[Byte]] = Get[Array[Byte]].map(ArraySeq.from)
1✔
124
  given arraySeqBytePut: Put[ArraySeq[Byte]] = Put[Array[Byte]].contramap(_.toArray)
125

1✔
126
  given urlGet: Get[URL] = Get[String].map(URL(_))
1✔
127
  given urlPut: Put[URL] = Put[String].contramap(_.toString())
UNCOV
128

×
129
  given octetKeyPairGet: Get[OctetKeyPair] = Get[String].map(OctetKeyPair.parse)
1✔
130
  given octetKeyPairPut: Put[OctetKeyPair] = Put[String].contramap(_.toJSONString)
UNCOV
131

×
NEW
132
  given jwkGet: Get[JWK] = Get[String].map(s => JWK.fromString(s).left.map(Exception(_)).toTry.get)
×
133
  given jwkPut: Put[JWK] = Put[String].contramap(_.toJsonString)
NEW
134

×
135
  given jsonGet: Get[Json] = Get[String].map(_.fromJson[Json] match {
1✔
136
    case Right(value) => value
×
137
    case Left(error)  => throw new RuntimeException(error)
138
  })
1✔
139
  given jsonPut: Put[Json] = Put[String].contramap(_.toString())
140

141
  final case class DIDStateRow(
142
      did: PrismDID,
143
      publicationStatus: PublicationStatusType,
144
      atalaOperationContent: Array[Byte],
145
      publishOperationId: Option[Array[Byte]],
146
      createdAt: Instant,
147
      updatedAt: Instant,
148
      didIndex: Int,
149
      walletId: WalletId
150
  ) {
1✔
151
    def toDomain: Try[ManagedDIDState] = {
152
      publicationStatus match {
1✔
153
        case PublicationStatusType.CREATED =>
1✔
154
          createDIDOperation.map(op => ManagedDIDState(op, didIndex, PublicationState.Created()))
1✔
155
        case PublicationStatusType.PUBLICATION_PENDING =>
1✔
156
          for {
×
UNCOV
157
            createDIDOperation <- createDIDOperation
×
158
            operationId <- publishOperationId
×
159
              .toRight(RuntimeException(s"DID publication operation id does not exists for PUBLICATION_PENDING status"))
160
              .toTry
161
          } yield ManagedDIDState(
162
            createDIDOperation,
163
            didIndex,
1✔
164
            PublicationState.PublicationPending(ArraySeq.from(operationId))
165
          )
1✔
166
        case PublicationStatusType.PUBLISHED =>
1✔
167
          for {
×
168
            createDIDOperation <- createDIDOperation
1✔
169
            operationId <- publishOperationId
×
170
              .toRight(RuntimeException(s"DID publication operation id does not exists for PUBLISHED status"))
171
              .toTry
1✔
172
          } yield ManagedDIDState(createDIDOperation, didIndex, PublicationState.Published(ArraySeq.from(operationId)))
173
      }
174
    }
175

1✔
176
    private def createDIDOperation: Try[PrismDIDOperation.Create] = {
×
177
      Try(node_models.AtalaOperation.parseFrom(atalaOperationContent))
1✔
UNCOV
178
        .flatMap { atalaOperation =>
×
179
          atalaOperation.operation.createDid
×
180
            .toRight(
×
181
              s"cannot extract CreateDIDOperation from AtalaOperation (${atalaOperation.operation.getClass.getSimpleName} found)"
182
            )
×
183
            .flatMap(_.toDomain)
×
184
            .left
×
185
            .map(RuntimeException(_))
186
            .toTry
187
        }
188
    }
189
  }
190

191
  object DIDStateRow {
1✔
192
    def from(did: PrismDID, state: ManagedDIDState, now: Instant, walletId: WalletId): DIDStateRow = {
193
      val createOperation = state.createOperation
1✔
194
      val status = PublicationStatusType.from(state.publicationState)
195
      val publishedOperationId = state.publicationState match {
1✔
196
        case PublicationState.Created()                       => None
1✔
197
        case PublicationState.PublicationPending(operationId) => Some(operationId.toArray)
1✔
198
        case PublicationState.Published(operationId)          => Some(operationId.toArray)
199
      }
200
      DIDStateRow(
201
        did = did,
202
        publicationStatus = status,
1✔
UNCOV
203
        atalaOperationContent = createOperation.toAtalaOperation.toByteArray,
×
204
        publishOperationId = publishedOperationId.map(_.toArray),
205
        createdAt = now,
206
        updatedAt = now,
207
        didIndex = state.didIndex,
208
        walletId = walletId
209
      )
210
    }
211
  }
212

213
  final case class WalletRow(
214
      id: WalletId,
215
      name: String,
216
      createdAt: Instant,
217
      updatedAt: Instant
218
  ) {
1✔
219
    def toDomain: Wallet = {
220
      Wallet(
221
        id: WalletId,
222
        name: String,
223
        createdAt: Instant,
224
        updatedAt: Instant
225
      )
226
    }
227
  }
228

229
  object WalletRow {
1✔
230
    def from(wallet: Wallet): WalletRow = {
231
      WalletRow(
232
        id = wallet.id,
233
        name = wallet.name,
234
        createdAt = wallet.createdAt,
235
        updatedAt = wallet.updatedAt
236
      )
237
    }
238
  }
239

240
  final case class WalletNofiticationRow(
241
      id: UUID,
242
      walletId: WalletId,
243
      url: URL,
244
      customHeaders: String,
245
      createdAt: Instant,
246
  ) {
1✔
247
    def toDomain: Try[EventNotificationConfig] = {
×
248
      decode[Map[String, String]](customHeaders).toTry
1✔
249
        .map { headers =>
250
          EventNotificationConfig(
251
            id = id,
252
            walletId = walletId,
253
            url = url,
254
            customHeaders = headers,
255
            createdAt = createdAt,
256
          )
257
        }
258
    }
259
  }
260

261
  object WalletNofiticationRow {
1✔
262
    def from(config: EventNotificationConfig): WalletNofiticationRow = {
263
      WalletNofiticationRow(
264
        id = config.id,
265
        walletId = config.walletId,
266
        url = config.url,
1✔
267
        customHeaders = config.customHeaders.asJson.noSpacesSortKeys,
268
        createdAt = config.createdAt,
269
      )
270
    }
271
  }
272

273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc