diff --git a/build.sbt b/build.sbt index 655eac4939..0cfb605dac 100644 --- a/build.sbt +++ b/build.sbt @@ -67,6 +67,8 @@ lazy val V = new { val typesafeConfig = "1.4.3" val protobuf = "3.1.9" + val grpcOkHttp = "1.63.0" + val testContainersScala = "0.41.3" val testContainersJavaKeycloak = "3.2.0" // scala-steward:off @@ -77,7 +79,6 @@ lazy val V = new { val logback = "1.4.14" val slf4j = "2.0.13" - val prismSdk = "1.4.1" // scala-steward:off val scalaUri = "4.0.3" val jwtCirceVersion = "9.4.6" @@ -138,6 +139,7 @@ lazy val D = new { val scalaPbRuntime: ModuleID = "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf" val scalaPbGrpc: ModuleID = "com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion + val grpcOkHttp: ModuleID = "io.grpc" % "grpc-okhttp" % V.grpcOkHttp val testcontainersPostgres: ModuleID = "com.dimafeng" %% "testcontainers-scala-postgresql" % V.testContainersScala % Test @@ -165,16 +167,6 @@ lazy val D = new { val monocleMacro: ModuleID = "dev.optics" %% "monocle-macro" % V.monocle % Test val apollo = "io.iohk.atala.prism.apollo" % "apollo-jvm" % V.apollo - // We have to exclude bouncycastle since for some reason bitcoinj depends on bouncycastle jdk15to18 - // (i.e. JDK 1.5 to 1.8), but we are using JDK 11 - val prismCrypto = "io.iohk.atala" % "prism-crypto-jvm" % V.prismSdk excludeAll - ExclusionRule( - organization = "org.bouncycastle" - ) - // Added here to make prism-crypto works. - // Once migrated to apollo, re-evaluate if this should be removed. - val bouncyBcpkix = "org.bouncycastle" % "bcpkix-jdk18on" % V.bouncyCastle - val bouncyBcprov = "org.bouncycastle" % "bcprov-jdk18on" % V.bouncyCastle // LIST of Dependencies val doobieDependencies: Seq[ModuleID] = @@ -204,9 +196,6 @@ lazy val D_SharedCrypto = new { Seq( D.zioJson, D.apollo, - D.bouncyBcpkix, - D.bouncyBcprov, - D.prismCrypto, // TODO: remove after migrated all primitives to apollo D.nimbusJwt, D.zioTest, D.zioTestSbt, @@ -290,7 +279,6 @@ lazy val D_Pollux = new { D.zioMock, D.munit, D.munitZio, - D.prismCrypto, // shared, logback, slf4jApi, @@ -399,7 +387,6 @@ lazy val D_CloudAgent = new { D.micrometer, D.micrometerPrometheusRegistry ) - val bouncyDependencies: Seq[ModuleID] = Seq(D.bouncyBcpkix, D.bouncyBcprov) val tapirDependencies: Seq[ModuleID] = Seq( tapirSwaggerUiBundle, @@ -417,7 +404,7 @@ lazy val D_CloudAgent = new { // Project Dependencies lazy val keyManagementDependencies: Seq[ModuleID] = - baseDependencies ++ bouncyDependencies ++ D.doobieDependencies ++ Seq(D.zioCatsInterop, D.zioMock, vaultDriver) + baseDependencies ++ D.doobieDependencies ++ Seq(D.zioCatsInterop, D.zioMock, vaultDriver) lazy val iamDependencies: Seq[ModuleID] = Seq(keycloakAuthz, D.jwtCirce) @@ -697,7 +684,7 @@ val prismNodeClient = project .in(file("prism-node/client/scala-client")) .settings( name := "prism-node-client", - libraryDependencies ++= Seq(D.scalaPbGrpc, D.scalaPbRuntime), + libraryDependencies ++= Seq(D.scalaPbGrpc, D.scalaPbRuntime, D.grpcOkHttp), coverageEnabled := false, // gRPC settings Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "scalapb"), diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDID.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDID.scala index a929f65bc0..b204c38281 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDID.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDID.scala @@ -1,7 +1,7 @@ package org.hyperledger.identus.castor.core.model.did import org.hyperledger.identus.castor.core.model.ProtoModelHelper -import io.iohk.atala.prism.crypto.{Sha256, Sha256Digest} +import org.hyperledger.identus.shared.crypto.Sha256Hash import io.iohk.atala.prism.protos.node_models import io.iohk.atala.prism.protos.node_models.AtalaOperation.Operation import org.hyperledger.identus.shared.models.Base64UrlString @@ -30,7 +30,7 @@ object PrismDID extends ProtoModelHelper { val LONG_FORM_SUFFIX_REGEX: Regex = "^([0-9a-f]{64}):([A-Za-z0-9_-]+$)".r def buildCanonical(stateHash: Array[Byte]): Either[String, CanonicalPrismDID] = - Try(Sha256Digest.fromBytes(stateHash)).toEither.left + Try(Sha256Hash.fromBytes(stateHash)).toEither.left .map(_.getMessage) .map(_ => CanonicalPrismDID(HexString.fromByteArray(stateHash))) @@ -109,7 +109,7 @@ final case class LongFormPrismDID private[did] (atalaOperation: node_models.Atal override val stateHash: HexString = { val encodedState = atalaOperation.toByteArray - HexString.fromByteArray(Sha256.compute(encodedState).getValue) + HexString.fromByteArray(Sha256Hash.compute(encodedState).bytes.toArray) } override val suffix: DIDMethodSpecificId = { diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDOperation.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDOperation.scala index 42cb8ec1e3..656abf4688 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDOperation.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDOperation.scala @@ -1,7 +1,7 @@ package org.hyperledger.identus.castor.core.model.did import org.hyperledger.identus.castor.core.model.ProtoModelHelper -import io.iohk.atala.prism.crypto.Sha256 +import org.hyperledger.identus.shared.crypto.Sha256Hash import scala.collection.compat.immutable.ArraySeq import io.iohk.atala.prism.protos.node_models @@ -9,7 +9,7 @@ import io.iohk.atala.prism.protos.node_models sealed trait PrismDIDOperation { def did: CanonicalPrismDID def toAtalaOperation: node_models.AtalaOperation - def toAtalaOperationHash: Array[Byte] = Sha256.compute(toAtalaOperation.toByteArray).getValue + def toAtalaOperationHash: Array[Byte] = Sha256Hash.compute(toAtalaOperation.toByteArray).bytes.toArray } object PrismDIDOperation extends ProtoModelHelper { @@ -38,7 +38,7 @@ final case class SignedPrismDIDOperation( import ProtoModelHelper.* this.toProto } - def toAtalaOperationId: Array[Byte] = Sha256.compute(toSignedAtalaOperation.toByteArray).getValue + def toAtalaOperationId: Array[Byte] = Sha256Hash.compute(toSignedAtalaOperation.toByteArray).bytes.toArray } final case class ScheduleDIDOperationOutcome( diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDSpec.scala index 54a18c23ed..ac4cade98a 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/PrismDIDSpec.scala @@ -1,7 +1,7 @@ package org.hyperledger.identus.castor.core.model.did import com.google.protobuf.ByteString -import io.iohk.atala.prism.crypto.{Sha256, Sha256Digest} +import org.hyperledger.identus.shared.crypto.Sha256Hash import io.iohk.atala.prism.protos.node_models import org.hyperledger.identus.shared.models.Base64UrlString import zio.* @@ -12,11 +12,11 @@ import org.hyperledger.identus.castor.core.model.did.PrismDID object PrismDIDSpec extends ZIOSpecDefault { private val canonicalSuffixHex = "9b5118411248d9663b6ab15128fba8106511230ff654e7514cdcc4ce919bde9b" - private val canonicalSuffix = Sha256Digest.fromHex(canonicalSuffixHex) + private val canonicalSuffix = Sha256Hash.fromHex(canonicalSuffixHex) private val encodedStateUsedBase64 = "Cj8KPRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDHpf-yhIns-LP3tLvA8icC5FJ1ZlBwbllPtIdNZ3q0jU" - private val short = PrismDID.buildCanonical(canonicalSuffix.getValue).toOption.get + private val short = PrismDID.buildCanonical(canonicalSuffix.bytes.toArray).toOption.get private val long = PrismDID .buildLongFormFromAtalaOperation( node_models.AtalaOperation.parseFrom(Base64UrlString.fromStringUnsafe(encodedStateUsedBase64).toByteArray) @@ -27,7 +27,7 @@ object PrismDIDSpec extends ZIOSpecDefault { private val didParserSpec = suite("PrismDID.fromString")( test("success for valid DID") { - val stateHash = Sha256.compute(Array()).getValue + val stateHash = Sha256Hash.compute(Array()).bytes.toArray val validDID = PrismDID.buildCanonical(stateHash).toOption.get val unsafeDID = PrismDID.fromString(validDID.toString) assert(unsafeDID)(isRight(equalTo(validDID))) @@ -58,7 +58,7 @@ object PrismDIDSpec extends ZIOSpecDefault { ) val encodedState = mockAtalaOperation.toByteArray val encodedStateBase64 = Base64UrlString.fromByteArray(encodedState).toStringNoPadding - val stateHash = Sha256.compute(encodedState).getHexValue + val stateHash = Sha256Hash.compute(encodedState).hexEncoded val didString = s"did:prism:$stateHash:$encodedStateBase64" val unsafeDID = PrismDID.fromString(didString) assert(unsafeDID)(isLeft(containsString("CreateDid Atala operation expected"))) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 364b0b1468..16d7b14c2c 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -5,7 +5,7 @@ import org.hyperledger.identus.agent.walletapi.model.Wallet import org.hyperledger.identus.agent.walletapi.service.{EntityService, WalletManagementService} import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.authentication.AuthenticationError.* -import io.iohk.atala.prism.crypto.Sha256 +import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.models.WalletAdministrationContext import org.hyperledger.identus.shared.models.WalletId import zio.{IO, URLayer, ZIO, ZLayer} @@ -73,7 +73,7 @@ case class ApiKeyAuthenticatorImpl( for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO - .fromTry(Try(Sha256.compute(saltAndApiKey.getBytes).getHexValue)) + .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) .logError("Failed to compute SHA256 hash") .mapError(cause => AuthenticationRepositoryError.UnexpectedError(cause)) entityId <- repository @@ -88,7 +88,7 @@ case class ApiKeyAuthenticatorImpl( for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO - .fromTry(Try(Sha256.compute(saltAndApiKey.getBytes).getHexValue)) + .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) .logError("Failed to compute SHA256 hash") .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) _ <- repository @@ -102,7 +102,7 @@ case class ApiKeyAuthenticatorImpl( for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO - .fromTry(Try(Sha256.compute(saltAndApiKey.getBytes).getHexValue)) + .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) .logError("Failed to compute SHA256 hash") .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) _ <- repository diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala index baa03202cb..9cc9cc260f 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala @@ -3,7 +3,7 @@ package org.hyperledger.identus.agent.walletapi.model import org.hyperledger.identus.castor.core.model.did.EllipticCurve import org.hyperledger.identus.castor.core.model.did.InternalKeyPurpose import org.hyperledger.identus.castor.core.model.did.VerificationRelationship -import io.iohk.atala.prism.crypto.Sha256 +import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.crypto.DerivationPath import org.hyperledger.identus.shared.crypto.Ed25519KeyPair import org.hyperledger.identus.shared.crypto.X25519KeyPair @@ -17,7 +17,7 @@ object WalletSeed { extension (s: WalletSeed) { final def toString(): String = "" def toByteArray: Array[Byte] = s.toArray - def sha256Digest: Array[Byte] = Sha256.compute(toByteArray).getValue() + def sha256Digest: Array[Byte] = Sha256Hash.compute(toByteArray).bytes.toArray } def fromByteArray(bytes: Array[Byte]): Either[String, WalletSeed] = { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala index a334f0ac34..4b14248393 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala @@ -4,7 +4,7 @@ import com.nimbusds.jose.jwk.OctetKeyPair import org.hyperledger.identus.agent.walletapi.storage.DIDSecretStorage import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.castor.core.model.did.PrismDID -import io.iohk.atala.prism.crypto.Sha256 +import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.crypto.jwk.FromJWK import org.hyperledger.identus.shared.crypto.jwk.JWK import org.hyperledger.identus.shared.models.HexString @@ -74,7 +74,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { - val relativePathHash = Sha256.compute(relativePath.getBytes(StandardCharsets.UTF_8)).getHexValue() + val relativePathHash = Sha256Hash.compute(relativePath.getBytes(StandardCharsets.UTF_8)).hexEncoded s"$basePath/$relativePathHash" -> Map(SEMANTIC_PATH_METADATA_KEY -> relativePath) } } @@ -88,7 +88,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { - val relativePathHash = Sha256.compute(relativePath.getBytes(StandardCharsets.UTF_8)).getHexValue() + val relativePathHash = Sha256Hash.compute(relativePath.getBytes(StandardCharsets.UTF_8)).hexEncoded s"$basePath/$relativePathHash" -> Map(SEMANTIC_PATH_METADATA_KEY -> relativePath) } } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultGenericSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultGenericSecretStorage.scala index 268c0eb4e9..d4a32f940b 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultGenericSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultGenericSecretStorage.scala @@ -2,7 +2,7 @@ package org.hyperledger.identus.agent.walletapi.vault import org.hyperledger.identus.agent.walletapi.storage.GenericSecret import org.hyperledger.identus.agent.walletapi.storage.GenericSecretStorage -import io.iohk.atala.prism.crypto.Sha256 +import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.models.WalletAccessContext import org.hyperledger.identus.shared.models.WalletId import zio.* @@ -43,7 +43,7 @@ class VaultGenericSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { - val relativePathHash = Sha256.compute(relativePath.getBytes(StandardCharsets.UTF_8)).getHexValue() + val relativePathHash = Sha256Hash.compute(relativePath.getBytes(StandardCharsets.UTF_8)).hexEncoded s"$basePath/$relativePathHash" -> Map(SEMANTIC_PATH_METADATA_KEY -> relativePath) } } diff --git a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Prism14Apollo.scala b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Prism14Apollo.scala deleted file mode 100644 index 6501618619..0000000000 --- a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Prism14Apollo.scala +++ /dev/null @@ -1,96 +0,0 @@ -package org.hyperledger.identus.shared.crypto - -import io.iohk.atala.prism.crypto.EC -import io.iohk.atala.prism.crypto.derivation.DerivationAxis -import io.iohk.atala.prism.crypto.derivation.KeyDerivation -import zio.* - -import scala.jdk.CollectionConverters.* -import scala.util.{Try, Success, Failure} - -final case class Prism14Secp256k1PublicKey(publicKey: io.iohk.atala.prism.crypto.keys.ECPublicKey) - extends Secp256k1PublicKey { - - override def getEncoded: Array[Byte] = getEncodedCompressed - - override def getEncodedUncompressed: Array[Byte] = publicKey.getEncoded() - - override def getEncodedCompressed: Array[Byte] = publicKey.getEncodedCompressed() - - override def getECPoint: ECPoint = { - val point = publicKey.getCurvePoint - ECPoint(point.getX().bytes(), point.getY().bytes()) - } - - override def verify(data: Array[Byte], signature: Array[Byte]): Try[Unit] = Try { - val sig = EC.INSTANCE.toSignatureFromBytes(signature) - EC.INSTANCE.verifyBytes(data, publicKey, sig) - }.flatMap(isValid => if (isValid) Success(()) else Failure(Exception("The signature verification does not match"))) - -} - -final case class Prism14Secp256k1PrivateKey(privateKey: io.iohk.atala.prism.crypto.keys.ECPrivateKey) - extends Secp256k1PrivateKey { - - override def toPublicKey: Secp256k1PublicKey = Prism14Secp256k1PublicKey( - EC.INSTANCE.toPublicKeyFromPrivateKey(privateKey) - ) - - override def getEncoded: Array[Byte] = privateKey.getEncoded() - - override def sign(data: Array[Byte]): Array[Byte] = EC.INSTANCE.signBytes(data, privateKey).getEncoded - -} - -object Prism14Secp256k1Ops extends Secp256k1KeyOps { - - override def generateKeyPair: Secp256k1KeyPair = { - val keyPair = EC.INSTANCE.generateKeyPair() - Secp256k1KeyPair( - Prism14Secp256k1PublicKey(keyPair.getPublicKey()), - Prism14Secp256k1PrivateKey(keyPair.getPrivateKey()), - ) - } - - override def privateKeyFromEncoded(bytes: Array[Byte]): Try[Secp256k1PrivateKey] = - Try(Prism14Secp256k1PrivateKey(EC.INSTANCE.toPrivateKeyFromBytes(bytes))) - - override def publicKeyFromEncoded(bytes: Array[Byte]): Try[Secp256k1PublicKey] = - Try(EC.INSTANCE.toPublicKeyFromBytes(bytes)) - .orElse(Try(EC.INSTANCE.toPublicKeyFromCompressed(bytes))) - .map(Prism14Secp256k1PublicKey.apply) - - override def publicKeyFromCoordinate(x: Array[Byte], y: Array[Byte]): Try[Secp256k1PublicKey] = - Try { - val pk = EC.INSTANCE.toPublicKeyFromByteCoordinates(x, y) - val point = pk.getCurvePoint() - val isOnCurve = EC.INSTANCE.isSecp256k1(point) - if (isOnCurve) Prism14Secp256k1PublicKey(pk) - else throw Exception("The point is not on the secp256k1 curve") - } - - override def deriveKeyPair(seed: Array[Byte])(path: DerivationPath*): UIO[Secp256k1KeyPair] = - ZIO.attempt { - val extendedKey = path - .foldLeft(KeyDerivation.INSTANCE.derivationRoot(seed)) { case (extendedKey, p) => - val axis = p match { - case DerivationPath.Hardened(i) => DerivationAxis.hardened(i) - case DerivationPath.Normal(i) => DerivationAxis.normal(i) - } - extendedKey.derive(axis) - } - val prism14KeyPair = extendedKey.keyPair() - Secp256k1KeyPair( - Prism14Secp256k1PublicKey(prism14KeyPair.getPublicKey()), - Prism14Secp256k1PrivateKey(prism14KeyPair.getPrivateKey()) - ) - }.orDie - - override def randomBip32Seed: UIO[(Array[Byte], Seq[String])] = - ZIO.attemptBlocking { - val mnemonic = KeyDerivation.INSTANCE.randomMnemonicCode() - val words = mnemonic.getWords().asScala.toList - KeyDerivation.INSTANCE.binarySeed(mnemonic, "") -> words - }.orDie - -} diff --git a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Sha256.scala b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Sha256.scala new file mode 100644 index 0000000000..00bf6e6e8d --- /dev/null +++ b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Sha256.scala @@ -0,0 +1,55 @@ +package org.hyperledger.identus.shared.crypto + +import java.security.MessageDigest + +// Reference: https://github.com/input-output-hk/atala-prism/blob/open-source-node/node/src/main/scala/io/iohk/atala/prism/node/crypto/CryptoUtils.scala +sealed trait Sha256Hash { + def bytes: Vector[Byte] + def hexEncoded: String = { + bytes.map(byte => f"${byte & 0xff}%02x").mkString + } + + override def equals(obj: Any): Boolean = obj match { + case other: Sha256Hash => bytes == other.bytes + case _ => false + } + + override def hashCode(): Int = bytes.hashCode() +} + +private[crypto] case class Sha256HashImpl(bytes: Vector[Byte]) extends Sha256Hash { + require(bytes.size == 32) +} + +object Sha256Hash { + + def fromBytes(arr: Array[Byte]): Sha256Hash = Sha256HashImpl(arr.toVector) + + def compute(bArray: Array[Byte]): Sha256Hash = { + Sha256HashImpl( + MessageDigest + .getInstance("SHA-256") + .digest(bArray) + .toVector + ) + } + + def fromHex(hexedBytes: String): Sha256Hash = { + val HEX_STRING_RE = "^[0-9a-fA-F]{64}$".r + if (HEX_STRING_RE.matches(hexedBytes)) Sha256HashImpl(hexToBytes(hexedBytes)) + else + throw new IllegalArgumentException( + "The given hex string doesn't correspond to a valid SHA-256 hash encoded as string" + ) + } + + private def hexToBytes(hex: String): Vector[Byte] = { + val HEX_ARRAY = "0123456789abcdef".toCharArray + for { + pair <- hex.grouped(2).toVector + firstIndex = HEX_ARRAY.indexOf(pair(0)) + secondIndex = HEX_ARRAY.indexOf(pair(1)) + octet = firstIndex << 4 | secondIndex + } yield octet.toByte + } +}