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

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

24 Apr 2024 10:47AM UTC coverage: 31.599% (-0.3%) from 31.912%
8815523478

Pull #966

patlo-iog
chore: resolve conflict

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

110 of 386 new or added lines in 22 files covered. (28.5%)

471 existing lines in 130 files now uncovered.

4652 of 14722 relevant lines covered (31.6%)

0.32 hits per line

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

63.51
/pollux/lib/vc-jwt/src/main/scala/io/iohk/atala/pollux/vc/jwt/Proof.scala
1
package io.iohk.atala.pollux.vc.jwt
2

3
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
4
import io.circe.*
5
import io.circe.syntax.*
6
import cats.implicits.*
7
import java.time.{Instant, ZoneOffset}
8
import zio.*
9
import io.iohk.atala.shared.utils.Json as JsonUtils
10
import io.iohk.atala.shared.utils.Base64Utils
11
import scodec.bits.ByteVector
12
import scala.util.Try
13
import java.security.*
14
import java.security.spec.X509EncodedKeySpec
15

16
sealed trait Proof {
17
  val id: Option[String] = None
18
  val `type`: String
19
  val proofPurpose: String
20
  val verificationMethod: String
21
  val created: Option[Instant] = None
22
  val domain: Option[String] = None
23
  val challenge: Option[String] = None
24
  val proofValue: String
25
  val previousProof: Option[String] = None
26
  val nonce: Option[String] = None
27
}
28

29
object Proof {
30
  given decodeProof: Decoder[Proof] = new Decoder[Proof] {
1✔
31
    final def apply(c: HCursor): Decoder.Result[Proof] = {
1✔
32
      val decoders: List[Decoder[Proof]] = List(
1✔
33
        Decoder[EddsaJcs2022Proof].widen
34
          // Note: Add another proof types here when available
35
      )
36

37
      decoders.foldLeft(
×
38
        Left[DecodingFailure, Proof](DecodingFailure("Cannot decode as Proof", c.history)): Decoder.Result[Proof]
1✔
39
      ) { (acc, decoder) =>
1✔
40
        acc.orElse(decoder.tryDecode(c))
41
      }
42
    }
43
  }
44
}
45

46
object EddsaJcs2022ProofGenerator {
1✔
47
  private val provider = BouncyCastleProviderSingleton.getInstance
1✔
48
  def generateProof(payload: Json, sk: PrivateKey, pk: PublicKey): Task[EddsaJcs2022Proof] = {
1✔
49
    for {
×
UNCOV
50
      canonicalizedJsonString <- ZIO.fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2))
×
51
      canonicalizedJson <- ZIO.fromEither(parser.parse(canonicalizedJsonString))
×
52
      dataToSign = canonicalizedJson.noSpaces.getBytes
×
53
      signature = sign(sk, dataToSign)
54
      base58BtsEncodedSignature = MultiBaseString(
55
        header = MultiBaseString.Header.Base58Btc,
×
56
        data = ByteVector.view(signature).toBase58
×
57
      ).toMultiBaseString
×
58
      created = Instant.now()
×
59
      multiKey = MultiKey(publicKeyMultibase =
×
60
        Some(MultiBaseString(header = MultiBaseString.Header.Base64Url, data = Base64Utils.encodeURL(pk.getEncoded)))
61
      )
×
62
      verificationMethod = Base64Utils.createDataUrl(
×
63
        multiKey.asJson.dropNullValues.noSpaces.getBytes,
64
        "application/json"
65
      )
66
    } yield EddsaJcs2022Proof(
67
      proofValue = base58BtsEncodedSignature,
68
      maybeCreated = Some(created),
69
      verificationMethod = verificationMethod
70
    )
71
  }
72

1✔
73
  def verifyProof(payload: Json, proofValue: String, pk: MultiKey): Task[Boolean] = {
74

1✔
75
    val res = for {
×
76
      canonicalizedJsonString <- ZIO
×
77
        .fromEither(JsonUtils.canonicalizeToJcs(payload.spaces2))
×
78
        .mapError(_.getMessage)
×
79
      canonicalizedJson <- ZIO
×
80
        .fromEither(parser.parse(canonicalizedJsonString))
×
81
        .mapError(_.getMessage)
×
82
      dataToVerify = canonicalizedJson.noSpaces.getBytes
1✔
83
      signature <- ZIO.fromEither(MultiBaseString.fromString(proofValue).flatMap(_.getBytes))
1✔
84
      publicKeyBytes <- ZIO.fromEither(
×
85
        pk.publicKeyMultibase.toRight("No public key provided inside MultiKey").flatMap(_.getBytes)
86
      )
×
87
      javaPublicKey <- ZIO.fromEither(recoverPublicKey(publicKeyBytes))
×
88
      isValid = verify(javaPublicKey, signature, dataToVerify)
89

90
    } yield isValid
91

1✔
92
    res.mapError(e => Throwable(e))
93
  }
94

1✔
95
  private def sign(privateKey: PrivateKey, data: Array[Byte]): Array[Byte] = {
96

1✔
97
    val signer = Signature.getInstance("SHA256withECDSA", provider)
1✔
98
    signer.initSign(privateKey)
1✔
99
    signer.update(data)
1✔
100
    signer.sign()
101
  }
102

1✔
103
  private def recoverPublicKey(pkBytes: Array[Byte]): Either[String, PublicKey] = {
1✔
104
    val keyFactory = KeyFactory.getInstance("EC", provider)
1✔
105
    val x509KeySpec = X509EncodedKeySpec(pkBytes)
1✔
106
    Try(keyFactory.generatePublic(x509KeySpec)).toEither.left.map(_.getMessage)
107
  }
108

1✔
109
  private def verify(publicKey: PublicKey, signature: Array[Byte], data: Array[Byte]): Boolean = {
1✔
110
    val verifier = Signature.getInstance("SHA256withECDSA", provider)
1✔
111
    verifier.initVerify(publicKey)
1✔
112
    verifier.update(data)
1✔
113
    verifier.verify(signature)
114
  }
115
}
116
case class EddsaJcs2022Proof(proofValue: String, verificationMethod: String, maybeCreated: Option[Instant])
117
    extends Proof {
118
  override val created: Option[Instant] = maybeCreated
119
  override val `type`: String = "DataIntegrityProof"
120
  override val proofPurpose: String = "assertionMethod"
121
  val cryptoSuite: String = "eddsa-jcs-2022"
122
}
123

124
object EddsaJcs2022Proof {
125

126
  given proofEncoder: Encoder[EddsaJcs2022Proof] =
127
    (proof: EddsaJcs2022Proof) =>
128
      Json
1✔
129
        .obj(
1✔
130
          ("id", proof.id.asJson),
1✔
131
          ("type", proof.`type`.asJson),
1✔
132
          ("proofPurpose", proof.proofPurpose.asJson),
1✔
133
          ("verificationMethod", proof.verificationMethod.asJson),
1✔
134
          ("created", proof.created.map(_.atOffset(ZoneOffset.UTC)).asJson),
1✔
135
          ("domain", proof.domain.asJson),
1✔
136
          ("challenge", proof.challenge.asJson),
1✔
137
          ("proofValue", proof.proofValue.asJson),
1✔
138
          ("cryptoSuite", proof.cryptoSuite.asJson),
1✔
139
          ("previousProof", proof.previousProof.asJson),
1✔
140
          ("nonce", proof.nonce.asJson),
1✔
141
          ("cryptoSuite", proof.cryptoSuite.asJson),
142
        )
143

144
  given proofDecoder: Decoder[EddsaJcs2022Proof] =
145
    (c: HCursor) =>
1✔
146
      for {
×
147
        id <- c.downField("id").as[Option[String]]
1✔
148
        `type` <- c.downField("type").as[String]
×
149
        proofPurpose <- c.downField("proofPurpose").as[String]
×
150
        verificationMethod <- c.downField("verificationMethod").as[String]
×
151
        created <- c.downField("created").as[Option[Instant]]
1✔
152
        domain <- c.downField("domain").as[Option[String]]
×
153
        challenge <- c.downField("challenge").as[Option[String]]
1✔
154
        proofValue <- c.downField("proofValue").as[String]
1✔
155
        previousProof <- c.downField("previousProof").as[Option[String]]
1✔
156
        nonce <- c.downField("nonce").as[Option[String]]
1✔
157
        cryptoSuite <- c.downField("cryptoSuite").as[String]
158
      } yield {
159
        EddsaJcs2022Proof(
160
          proofValue = proofValue,
161
          verificationMethod = verificationMethod,
162
          maybeCreated = created
163
        )
164
      }
165
}
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