mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-24 22:32:17 +00:00
213 lines
8.1 KiB
Scala
213 lines
8.1 KiB
Scala
/*
|
|
* Copyright (C) 2017 Fluence Labs Limited
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package fluence.crypto.ecdsa
|
|
|
|
import java.math.BigInteger
|
|
import java.security._
|
|
import java.security.interfaces.ECPrivateKey
|
|
|
|
import cats.Monad
|
|
import cats.data.EitherT
|
|
import fluence.crypto.KeyPair.Secret
|
|
import fluence.crypto.{KeyPair, _}
|
|
import fluence.crypto.hash.JdkCryptoHasher
|
|
import fluence.crypto.signature.{SignAlgo, SignatureChecker, Signer}
|
|
import org.bouncycastle.jce.ECNamedCurveTable
|
|
import org.bouncycastle.jce.interfaces.ECPublicKey
|
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
|
import org.bouncycastle.jce.spec.{ECParameterSpec, ECPrivateKeySpec, ECPublicKeySpec}
|
|
import scodec.bits.ByteVector
|
|
|
|
import scala.language.higherKinds
|
|
|
|
/**
|
|
* Elliptic Curve Digital Signature Algorithm
|
|
* @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
|
|
* @param scheme https://bouncycastle.org/specifications.html
|
|
*/
|
|
class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]])
|
|
extends JavaAlgorithm {
|
|
|
|
import CryptoError.nonFatalHandling
|
|
import Ecdsa._
|
|
|
|
val HEXradix = 16
|
|
|
|
val generateKeyPair: Crypto.KeyPairGenerator =
|
|
new Crypto.Func[Option[Array[Byte]], KeyPair] {
|
|
override def apply[F[_]](
|
|
input: Option[Array[Byte]]
|
|
)(implicit F: Monad[F]): EitherT[F, CryptoError, fluence.crypto.KeyPair] =
|
|
for {
|
|
ecSpec ← EitherT.fromOption(
|
|
Option(ECNamedCurveTable.getParameterSpec(curveType)),
|
|
CryptoError("Parameter spec for the curve is not available.")
|
|
)
|
|
g ← getKeyPairGenerator
|
|
_ ← nonFatalHandling {
|
|
g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
|
|
}(s"Could not initialize KeyPairGenerator")
|
|
p ← EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Generated key pair is null"))
|
|
keyPair ← nonFatalHandling {
|
|
val pk = p.getPublic match {
|
|
case pk: ECPublicKey => ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true))
|
|
case p => throw new ClassCastException(s"Cannot cast public key (${p.getClass}) to Ed25519PublicKeyParameters")
|
|
}
|
|
val sk = p.getPrivate match {
|
|
case sk: ECPrivateKey =>
|
|
val bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
|
|
ByteVector.fromValidHex(bg.toString(HEXradix))
|
|
case s => throw new ClassCastException(s"Cannot cast private key (${p.getClass}) to Ed25519PrivateKeyParameters")
|
|
}
|
|
KeyPair.fromByteVectors(pk, sk)
|
|
}("Could not generate KeyPair")
|
|
} yield keyPair
|
|
}
|
|
|
|
/**
|
|
* Restores pair of keys from the known secret key.
|
|
* The public key will be the same each method call with the same secret key.
|
|
* @param sk secret key
|
|
* @return key pair
|
|
*/
|
|
def restorePairFromSecret[F[_]: Monad](sk: Secret): EitherT[F, CryptoError, KeyPair] =
|
|
for {
|
|
ecSpec ← EitherT.fromOption(
|
|
Option(ECNamedCurveTable.getParameterSpec(curveType)),
|
|
CryptoError("Parameter spec for the curve is not available.")
|
|
)
|
|
keyPair ← nonFatalHandling {
|
|
val hex = sk.value.toHex
|
|
val d = new BigInteger(hex, HEXradix)
|
|
// to re-create public key from private we need to multiply known from curve point G with D (private key)
|
|
// result will be point Q (public key)
|
|
// https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
|
|
val g = ecSpec.getG
|
|
val q = g.multiply(d)
|
|
val pk = ByteVector(q.getEncoded(true))
|
|
KeyPair.fromByteVectors(pk, sk.value)
|
|
}("Could not generate KeyPair from private key. Unexpected.")
|
|
} yield keyPair
|
|
|
|
def sign[F[_]: Monad](
|
|
keyPair: KeyPair,
|
|
message: ByteVector
|
|
): EitherT[F, CryptoError, signature.Signature] =
|
|
signMessage(new BigInteger(keyPair.secretKey.value.toHex, HEXradix), message.toArray)
|
|
.map(bb ⇒ fluence.crypto.signature.Signature(ByteVector(bb)))
|
|
|
|
def verify[F[_]: Monad](
|
|
publicKey: KeyPair.Public,
|
|
signature: fluence.crypto.signature.Signature,
|
|
message: ByteVector
|
|
): EitherT[F, CryptoError, Unit] =
|
|
verifySign(publicKey.bytes, signature.bytes, message.toArray)
|
|
|
|
private def signMessage[F[_]: Monad](
|
|
privateKey: BigInteger,
|
|
message: Array[Byte]
|
|
): EitherT[F, CryptoError, Array[Byte]] =
|
|
for {
|
|
ec ← curveSpec
|
|
keySpec ← nonFatalHandling(new ECPrivateKeySpec(privateKey, ec))("Cannot read private key.")
|
|
keyFactory ← getKeyFactory
|
|
signProvider ← getSignatureProvider
|
|
sign ← nonFatalHandling {
|
|
signProvider.initSign(keyFactory.generatePrivate(keySpec))
|
|
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
|
|
signProvider.sign()
|
|
}("Cannot sign message.")
|
|
|
|
} yield sign
|
|
|
|
private def verifySign[F[_]: Monad](
|
|
publicKey: Array[Byte],
|
|
signature: Array[Byte],
|
|
message: Array[Byte],
|
|
): EitherT[F, CryptoError, Unit] =
|
|
for {
|
|
ec ← curveSpec
|
|
keySpec ← nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key")
|
|
keyFactory ← getKeyFactory
|
|
signProvider ← getSignatureProvider
|
|
verify ← nonFatalHandling {
|
|
signProvider.initVerify(keyFactory.generatePublic(keySpec))
|
|
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
|
|
signProvider.verify(signature)
|
|
}("Cannot verify message")
|
|
|
|
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
|
} yield ()
|
|
|
|
private def curveSpec[F[_]: Monad] =
|
|
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])(
|
|
"Cannot get curve parameters"
|
|
)
|
|
|
|
private def getKeyPairGenerator[F[_]: Monad] =
|
|
nonFatalHandling(KeyPairGenerator.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
|
"Cannot get key pair generator"
|
|
)
|
|
|
|
private def getKeyFactory[F[_]: Monad] =
|
|
nonFatalHandling(KeyFactory.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
|
"Cannot get key factory instance"
|
|
)
|
|
|
|
private def getSignatureProvider[F[_]: Monad] =
|
|
nonFatalHandling(Signature.getInstance(scheme, BouncyCastleProvider.PROVIDER_NAME))(
|
|
"Cannot get signature instance"
|
|
)
|
|
}
|
|
|
|
object Ecdsa {
|
|
//algorithm name in security provider
|
|
val ECDSA = "ECDSA"
|
|
|
|
/**
|
|
* size of key is 256 bit
|
|
* `secp256k1` refers to the parameters of the ECDSA curve
|
|
* `NONEwithECDSA with sha-256 hasher` Preferably the size of the key is greater than or equal to the digest algorithm
|
|
* don't use `SHA256WithECDSA` because of non-compatibility with javascript libraries
|
|
*/
|
|
val ecdsa_secp256k1_sha256 = new Ecdsa("secp256k1", "NONEwithECDSA", Some(JdkCryptoHasher.Sha256))
|
|
|
|
val signAlgo: SignAlgo = SignAlgo(
|
|
name = "ecdsa_secp256k1_sha256",
|
|
generateKeyPair = ecdsa_secp256k1_sha256.generateKeyPair,
|
|
signer = kp ⇒
|
|
Signer(
|
|
kp.publicKey,
|
|
new Crypto.Func[ByteVector, signature.Signature] {
|
|
override def apply[F[_]](
|
|
input: ByteVector
|
|
)(implicit F: Monad[F]): EitherT[F, CryptoError, signature.Signature] =
|
|
ecdsa_secp256k1_sha256.sign(kp, input)
|
|
}
|
|
),
|
|
checker = pk ⇒
|
|
new SignatureChecker {
|
|
override def check[F[_]: Monad](
|
|
signature: fluence.crypto.signature.Signature,
|
|
plain: ByteVector
|
|
): EitherT[F, CryptoError, Unit] =
|
|
ecdsa_secp256k1_sha256.verify(pk, signature, plain)
|
|
}
|
|
)
|
|
}
|