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

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

04 Mar 2024 07:15PM UTC coverage: 31.187% (-0.1%) from 31.311%
8145761569

Pull #917

shotexa
Fix minor bugs

Signed-off-by: Shota Jolbordi <shota.jolbordi@iohk.io>
Pull Request #917: feat(pollux): check verification status on presentation verification

34 of 139 new or added lines in 10 files covered. (24.46%)

408 existing lines in 114 files now uncovered.

4228 of 13557 relevant lines covered (31.19%)

0.31 hits per line

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

59.46
/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 io.iohk.atala.shared.models.HexString
8

9
import java.time.{Instant, ZoneOffset}
10
import zio.*
11
import io.iohk.atala.shared.utils.Json as JsonUtils
12
import io.iohk.atala.shared.utils.Base64Utils
13
import scodec.bits.ByteVector
14
import scala.util.Try
15
import java.security.*
16
import java.security.spec.X509EncodedKeySpec
17

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

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

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

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

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

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

92
    } yield isValid
NEW
93

×
94
    res.mapError(e => Throwable(e))
95
  }
96

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

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

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

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

126
object EddsaJcs2022Proof {
127

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

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