mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-25 06:42:19 +00:00
Ecdsa for scalajs (#37)
* refactor sign classes store in private key only S, in public key only Q point without wrappers * refactor, class ...[F[_]] to method ...[F[_]] * remove redundant * finally js crypto === java crypto * facade for hash, EcdsaJS implementation, rewrite crypto logic to hex for cross-platform compatibility * clean branch * delete Main, add headers * merge master * small fixes * todo * refactoring, comments * alphabet * merge master * fix tests * fix PR comments * fix PR comments
This commit is contained in:
parent
17a5930399
commit
d182a92e98
86
js/src/main/scala/fluence/crypto/algorithm/EcdsaJS.scala
Normal file
86
js/src/main/scala/fluence/crypto/algorithm/EcdsaJS.scala
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.algorithm
|
||||||
|
|
||||||
|
import cats.data.EitherT
|
||||||
|
import cats.{ Monad, MonadError }
|
||||||
|
import fluence.crypto.facade.EC
|
||||||
|
import fluence.crypto.hash.{ CryptoHasher, JsCryptoHasher }
|
||||||
|
import fluence.crypto.keypair.KeyPair
|
||||||
|
import fluence.crypto.signature.Signature
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
import scala.scalajs.js
|
||||||
|
import scala.scalajs.js.JSConverters._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return in all js methods hex, because in the other case we will receive javascript objects
|
||||||
|
* @param ec implementation of ecdsa logic for different curves
|
||||||
|
*/
|
||||||
|
class EcdsaJS(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) extends Algorithm with SignatureFunctions with KeyGenerator {
|
||||||
|
import CryptoErr._
|
||||||
|
|
||||||
|
override def generateKeyPair[F[_] : Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = {
|
||||||
|
nonFatalHandling {
|
||||||
|
val seedJs = seed.map(bs ⇒ js.Dynamic.literal(entropy = bs.toJSArray))
|
||||||
|
val key = ec.genKeyPair(seedJs)
|
||||||
|
val publicHex = key.getPublic(true, "hex")
|
||||||
|
val secretHex = key.getPrivate("hex")
|
||||||
|
val public = ByteVector.fromValidHex(publicHex)
|
||||||
|
val secret = ByteVector.fromValidHex(secretHex)
|
||||||
|
KeyPair.fromByteVectors(public, secret)
|
||||||
|
} ("Failed to generate key pair.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] = {
|
||||||
|
for {
|
||||||
|
secret ← nonFatalHandling{
|
||||||
|
ec.keyFromPrivate(keyPair.secretKey.value.toHex, "hex")
|
||||||
|
}("Cannot get private key from key pair.")
|
||||||
|
hash ← hash(message)
|
||||||
|
signHex ← nonFatalHandling(secret.sign(hash).toDER("hex"))("Cannot sign message")
|
||||||
|
} yield Signature(keyPair.publicKey, ByteVector.fromValidHex(signHex))
|
||||||
|
}
|
||||||
|
|
||||||
|
def hash[F[_] : Monad](message: ByteVector): EitherT[F, CryptoErr, js.Array[Byte]] = {
|
||||||
|
val arr = message.toArray
|
||||||
|
hasher.fold(EitherT.pure[F, CryptoErr](arr)) { h ⇒
|
||||||
|
nonFatalHandling {
|
||||||
|
h.hash(message.toArray)
|
||||||
|
}("Cannot hash message.")
|
||||||
|
}.map(_.toJSArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def verify[F[_] : Monad](signature: Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] = {
|
||||||
|
for {
|
||||||
|
public ← nonFatalHandling{
|
||||||
|
val hex = signature.publicKey.value.toHex
|
||||||
|
ec.keyFromPublic(hex, "hex")
|
||||||
|
}("Incorrect public key format.")
|
||||||
|
hash ← hash(message)
|
||||||
|
verify ← nonFatalHandling(public.verify(hash, signature.sign.toHex))("Cannot verify message.")
|
||||||
|
_ ← EitherT.cond[F](verify, (), CryptoErr("Signature is not verified"))
|
||||||
|
} yield ()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object EcdsaJS {
|
||||||
|
def ecdsa_secp256k1_sha256 = new EcdsaJS(new EC("secp256k1"), Some(JsCryptoHasher.Sha256))
|
||||||
|
}
|
52
js/src/main/scala/fluence/crypto/facade/Elliptic.scala
Normal file
52
js/src/main/scala/fluence/crypto/facade/Elliptic.scala
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.facade
|
||||||
|
|
||||||
|
import scala.scalajs.js
|
||||||
|
import scala.scalajs.js.annotation._
|
||||||
|
|
||||||
|
//TODO hide enc argument in methods, make it `hex` by default
|
||||||
|
/**
|
||||||
|
* https://github.com/indutny/elliptic - fast elliptic-curve cryptography in a plain javascript implementation
|
||||||
|
*/
|
||||||
|
@js.native
|
||||||
|
@JSImport("elliptic", "ec")
|
||||||
|
class EC(curve: String) extends js.Object {
|
||||||
|
def genKeyPair(options: Option[js.Dynamic] = None): KeyPair = js.native
|
||||||
|
def keyPair(options: js.Dynamic): KeyPair = js.native
|
||||||
|
def keyFromPublic(pub: String, enc: String): KeyPair = js.native
|
||||||
|
def keyFromPrivate(priv: String, enc: String): KeyPair = js.native
|
||||||
|
}
|
||||||
|
|
||||||
|
@js.native
|
||||||
|
@JSImport("elliptic", "ec")
|
||||||
|
class KeyPair(ec: EC, options: js.Dynamic) extends js.Object {
|
||||||
|
def verify(msg: js.Array[Byte], signature: String): Boolean = js.native
|
||||||
|
def sign(msg: js.Array[Byte]): Signature = js.native
|
||||||
|
|
||||||
|
def getPublic(compact: Boolean, enc: String): String = js.native
|
||||||
|
def getPrivate(enc: String): String = js.native
|
||||||
|
val priv: js.Any = js.native
|
||||||
|
val pub: js.Any = js.native
|
||||||
|
}
|
||||||
|
|
||||||
|
@js.native
|
||||||
|
@JSImport("elliptic", "ec")
|
||||||
|
class Signature(der: String, enc: String = "hex") extends js.Object {
|
||||||
|
def toDER(enc: String): String = js.native
|
||||||
|
}
|
@ -15,8 +15,18 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
object Main {
|
package fluence.crypto.facade
|
||||||
def main(args: Array[String]): Unit = {
|
|
||||||
println("Hello world!")
|
import scala.scalajs.js
|
||||||
}
|
import scala.scalajs.js.annotation.JSImport
|
||||||
|
|
||||||
|
//TODO hide enc argument in methods, make it `hex` by default
|
||||||
|
/**
|
||||||
|
* https://github.com/indutny/hash.js - part of elliptic library
|
||||||
|
*/
|
||||||
|
@js.native
|
||||||
|
@JSImport("hash.js", "sha256")
|
||||||
|
class SHA256() extends js.Object {
|
||||||
|
def update(msg: js.Array[Byte]): Unit = js.native
|
||||||
|
def digest(enc: String): String = js.native
|
||||||
}
|
}
|
40
js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala
Normal file
40
js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.hash
|
||||||
|
|
||||||
|
import fluence.crypto.facade.SHA256
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.scalajs.js.JSConverters._
|
||||||
|
|
||||||
|
object JsCryptoHasher {
|
||||||
|
|
||||||
|
lazy val Sha256: CryptoHasher[Array[Byte], Array[Byte]] = new CryptoHasher[Array[Byte], Array[Byte]] {
|
||||||
|
|
||||||
|
override def hash(msg1: Array[Byte]): Array[Byte] = {
|
||||||
|
val sha256 = new SHA256()
|
||||||
|
sha256.update(msg1.toJSArray)
|
||||||
|
ByteVector.fromValidHex(sha256.digest("hex")).toArray
|
||||||
|
}
|
||||||
|
|
||||||
|
override def hash(msg1: Array[Byte], msg2: Array[Byte]*): Array[Byte] = {
|
||||||
|
hash(msg1 ++ msg2.flatten)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
83
js/src/test/scala/fluence/crypto/EcdsaJSSpec.scala
Normal file
83
js/src/test/scala/fluence/crypto/EcdsaJSSpec.scala
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import cats.data.EitherT
|
||||||
|
import cats.instances.try_._
|
||||||
|
import fluence.crypto.algorithm.{ CryptoErr, EcdsaJS }
|
||||||
|
import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec }
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.util.{ Random, Try }
|
||||||
|
|
||||||
|
class EcdsaJSSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
||||||
|
|
||||||
|
def rndBytes(size: Int) = Random.nextString(10).getBytes
|
||||||
|
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
|
||||||
|
|
||||||
|
private implicit class TryEitherTExtractor[A, B](et: EitherT[Try, A, B]) {
|
||||||
|
def extract: B = et.value.get.right.get
|
||||||
|
|
||||||
|
def isOk: Boolean = et.value.fold(_ ⇒ false, _.isRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
"ecdsa algorithm" should {
|
||||||
|
"correct sign and verify data" in {
|
||||||
|
val algorithm = EcdsaJS.ecdsa_secp256k1_sha256
|
||||||
|
|
||||||
|
val keys = algorithm.generateKeyPair[Try]().extract
|
||||||
|
val data = rndByteVector(10)
|
||||||
|
val sign = algorithm.sign[Try](keys, data).extract
|
||||||
|
|
||||||
|
algorithm.verify[Try](sign, data).isOk shouldBe true
|
||||||
|
|
||||||
|
val randomData = rndByteVector(10)
|
||||||
|
val randomSign = algorithm.sign(keys, randomData).extract
|
||||||
|
|
||||||
|
algorithm.verify(sign.copy(sign = randomSign.sign), data).isOk shouldBe false
|
||||||
|
|
||||||
|
algorithm.verify(sign, randomData).isOk shouldBe false
|
||||||
|
}
|
||||||
|
|
||||||
|
"correctly work with signer and checker" in {
|
||||||
|
val algo = new SignAlgo(EcdsaJS.ecdsa_secp256k1_sha256)
|
||||||
|
val keys = algo.generateKeyPair().extract
|
||||||
|
val signer = algo.signer(keys)
|
||||||
|
|
||||||
|
val data = rndByteVector(10)
|
||||||
|
val sign = signer.sign(data).extract
|
||||||
|
|
||||||
|
algo.checker.check(sign, data).isOk shouldBe true
|
||||||
|
|
||||||
|
val randomSign = signer.sign(rndByteVector(10)).extract
|
||||||
|
algo.checker.check(randomSign, data).isOk shouldBe false
|
||||||
|
}
|
||||||
|
|
||||||
|
"throw an errors on invalid data" in {
|
||||||
|
val algo = new SignAlgo(EcdsaJS.ecdsa_secp256k1_sha256)
|
||||||
|
val keys = algo.generateKeyPair().extract
|
||||||
|
val signer = algo.signer(keys)
|
||||||
|
val data = rndByteVector(10)
|
||||||
|
|
||||||
|
val sign = signer.sign(data).extract
|
||||||
|
|
||||||
|
the[CryptoErr] thrownBy algo.checker.check(sign.copy(sign = rndByteVector(10)), data).value.flatMap(_.toTry).get
|
||||||
|
the[CryptoErr] thrownBy algo.checker.check(sign.copy(publicKey = sign.publicKey.copy(value = rndByteVector(10))), data).value.flatMap(_.toTry).get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,13 +26,9 @@ import cats.syntax.functor._
|
|||||||
import fluence.crypto.keypair.KeyPair
|
import fluence.crypto.keypair.KeyPair
|
||||||
import io.circe.parser.decode
|
import io.circe.parser.decode
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
import io.circe.{ Decoder, Encoder, HCursor, Json }
|
|
||||||
import scodec.bits.ByteVector
|
|
||||||
|
|
||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
|
|
||||||
case class KeyStore(keyPair: KeyPair)
|
|
||||||
|
|
||||||
class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
|
class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
|
||||||
import KeyStore._
|
import KeyStore._
|
||||||
def readKeyPair: F[KeyPair] = {
|
def readKeyPair: F[KeyPair] = {
|
||||||
@ -64,24 +60,3 @@ class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
|
|||||||
} yield newKeys
|
} yield newKeys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object KeyStore {
|
|
||||||
implicit val encodeKeyStorage: Encoder[KeyStore] = new Encoder[KeyStore] {
|
|
||||||
final def apply(ks: KeyStore): Json = Json.obj(("keystore", Json.obj(
|
|
||||||
("secret", Json.fromString(ks.keyPair.secretKey.value.toBase64)),
|
|
||||||
("public", Json.fromString(ks.keyPair.publicKey.value.toBase64)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit val decodeKeyStorage: Decoder[Option[KeyStore]] = new Decoder[Option[KeyStore]] {
|
|
||||||
final def apply(c: HCursor): Decoder.Result[Option[KeyStore]] =
|
|
||||||
for {
|
|
||||||
secret ← c.downField("keystore").downField("secret").as[String]
|
|
||||||
public ← c.downField("keystore").downField("public").as[String]
|
|
||||||
} yield {
|
|
||||||
for {
|
|
||||||
secret ← ByteVector.fromBase64(secret)
|
|
||||||
public ← ByteVector.fromBase64(public)
|
|
||||||
} yield KeyStore(KeyPair.fromByteVectors(public, secret))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,66 +22,73 @@ import java.security._
|
|||||||
import java.security.interfaces.ECPrivateKey
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.{ Applicative, Monad, MonadError }
|
import cats.Monad
|
||||||
import cats.syntax.flatMap._
|
import fluence.crypto.hash.{ CryptoHasher, JdkCryptoHasher }
|
||||||
import cats.syntax.functor._
|
|
||||||
import fluence.crypto.keypair.KeyPair
|
import fluence.crypto.keypair.KeyPair
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable
|
import org.bouncycastle.jce.ECNamedCurveTable
|
||||||
import org.bouncycastle.jce.interfaces.ECPublicKey
|
import org.bouncycastle.jce.interfaces.ECPublicKey
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.jce.spec.{ ECNamedCurveParameterSpec, ECParameterSpec, ECPrivateKeySpec, ECPublicKeySpec }
|
import org.bouncycastle.jce.spec.{ ECParameterSpec, ECPrivateKeySpec, ECPublicKeySpec }
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
import scala.util.control.NonFatal
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elliptic Curve Digital Signature Algorithm
|
* Elliptic Curve Digital Signature Algorithm
|
||||||
* @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
|
* @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
|
||||||
* @param scheme https://bouncycastle.org/specifications.html
|
* @param scheme https://bouncycastle.org/specifications.html
|
||||||
*/
|
*/
|
||||||
class Ecdsa(curveType: String, scheme: String) extends JavaAlgorithm
|
class Ecdsa(curveType: String, scheme: String, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) extends JavaAlgorithm
|
||||||
with SignatureFunctions with KeyGenerator {
|
with SignatureFunctions with KeyGenerator {
|
||||||
|
import CryptoErr._
|
||||||
import Ecdsa._
|
import Ecdsa._
|
||||||
|
|
||||||
private def nonFatalHandling[F[_] : Applicative, A](a: ⇒ A)(errorText: String): EitherT[F, CryptoErr, A] = {
|
val HEXradix = 16
|
||||||
try EitherT.pure(a)
|
|
||||||
catch {
|
|
||||||
case NonFatal(e) ⇒ EitherT.leftT(CryptoErr(errorText + " " + e.getLocalizedMessage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair] =
|
override def generateKeyPair[F[_] : Monad](seed: Option[Array[Byte]]): EitherT[F, CryptoErr, KeyPair] = {
|
||||||
for {
|
for {
|
||||||
ecSpec ← EitherT.fromOption[F](
|
ecSpec ← EitherT.fromOption(Option(ECNamedCurveTable.getParameterSpec(curveType)), CryptoErr("Parameter spec for the curve is not available."))
|
||||||
Option(ECNamedCurveTable.getParameterSpec(curveType)),
|
|
||||||
CryptoErr("Parameter spec for the curve is not available."))
|
|
||||||
g ← getKeyPairGenerator
|
g ← getKeyPairGenerator
|
||||||
_ ← nonFatalHandling(g.initialize(ecSpec, random))("Could not initialize KeyPairGenerator.")
|
_ ← nonFatalHandling(g.initialize(ecSpec, seed.map(new SecureRandom(_)).getOrElse(new SecureRandom())))("Could not initialize KeyPairGenerator.")
|
||||||
p ← EitherT.fromOption(
|
p ← EitherT.fromOption(
|
||||||
Option(g.generateKeyPair()),
|
Option(g.generateKeyPair()),
|
||||||
CryptoErr("Could not generate KeyPair. Unexpected."))
|
CryptoErr("Could not generate KeyPair. Unexpected."))
|
||||||
|
|
||||||
keyPair ← nonFatalHandling {
|
keyPair ← nonFatalHandling {
|
||||||
//store S number for private key and compressed Q point on curve for public key
|
//store S number for private key and compressed Q point on curve for public key
|
||||||
val pk = p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true)
|
val pk = ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true))
|
||||||
val sk = p.getPrivate.asInstanceOf[ECPrivateKey].getS.toByteArray
|
val bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
|
||||||
KeyPair.fromBytes(pk, sk)
|
val sk = ByteVector.fromValidHex(bg.toString(HEXradix))
|
||||||
}("Can't get public/private key from generated keypair")
|
KeyPair.fromByteVectors(pk, sk)
|
||||||
|
}("Could not generate KeyPair. Unexpected.")
|
||||||
} yield keyPair
|
} yield keyPair
|
||||||
|
}
|
||||||
|
|
||||||
override def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] =
|
override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, fluence.crypto.signature.Signature] = {
|
||||||
generateKeyPair(new SecureRandom())
|
signMessage(new BigInteger(keyPair.secretKey.value.toHex, HEXradix), message.toArray)
|
||||||
|
|
||||||
override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, fluence.crypto.signature.Signature] =
|
|
||||||
signMessage(keyPair.secretKey.value.toArray, message.toArray)
|
|
||||||
.map(bb ⇒ fluence.crypto.signature.Signature(keyPair.publicKey, ByteVector(bb)))
|
.map(bb ⇒ fluence.crypto.signature.Signature(keyPair.publicKey, ByteVector(bb)))
|
||||||
|
}
|
||||||
|
|
||||||
override def verify[F[_] : Monad](signature: fluence.crypto.signature.Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] = {
|
override def verify[F[_] : Monad](signature: fluence.crypto.signature.Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] = {
|
||||||
val publicKey = signature.publicKey.value.toArray
|
verifySign(signature.publicKey.value.toArray, message.toArray, signature.sign.toArray)
|
||||||
val messageBytes = message.toArray
|
}
|
||||||
val signatureBytes = signature.sign.toArray
|
|
||||||
|
|
||||||
|
private def signMessage[F[_] : Monad](privateKey: BigInteger, message: Array[Byte]): EitherT[F, CryptoErr, 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(h ⇒ h.hash(message)).getOrElse(message))
|
||||||
|
signProvider.sign()
|
||||||
|
}("Cannot sign message.")
|
||||||
|
}
|
||||||
|
} yield sign
|
||||||
|
}
|
||||||
|
|
||||||
|
private def verifySign[F[_] : Monad](publicKey: Array[Byte], message: Array[Byte], signature: Array[Byte]): EitherT[F, CryptoErr, Unit] = {
|
||||||
for {
|
for {
|
||||||
ec ← curveSpec
|
ec ← curveSpec
|
||||||
keySpec ← nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.")
|
keySpec ← nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.")
|
||||||
@ -90,30 +97,14 @@ class Ecdsa(curveType: String, scheme: String) extends JavaAlgorithm
|
|||||||
verify ← {
|
verify ← {
|
||||||
nonFatalHandling {
|
nonFatalHandling {
|
||||||
signProvider.initVerify(keyFactory.generatePublic(keySpec))
|
signProvider.initVerify(keyFactory.generatePublic(keySpec))
|
||||||
signProvider.update(messageBytes)
|
signProvider.update(hasher.map(h ⇒ h.hash(message)).getOrElse(message))
|
||||||
signProvider.verify(signatureBytes)
|
signProvider.verify(signature)
|
||||||
}("Cannot verify message.")
|
}("Cannot verify message.")
|
||||||
}
|
}
|
||||||
_ ← EitherT.cond[F](verify, (), CryptoErr("Signature is not verified"))
|
_ ← EitherT.cond[F](verify, (), CryptoErr("Signature is not verified"))
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def signMessage[F[_] : Monad](privateKey: Array[Byte], message: Array[Byte]): EitherT[F, CryptoErr, Array[Byte]] = {
|
|
||||||
for {
|
|
||||||
ec ← curveSpec
|
|
||||||
keySpec ← nonFatalHandling(new ECPrivateKeySpec(new BigInteger(privateKey), ec))("Cannot read private key.")
|
|
||||||
keyFactory ← getKeyFactory
|
|
||||||
signProvider ← getSignatureProvider
|
|
||||||
sign ← {
|
|
||||||
nonFatalHandling {
|
|
||||||
signProvider.initSign(keyFactory.generatePrivate(keySpec))
|
|
||||||
signProvider.update(message)
|
|
||||||
signProvider.sign()
|
|
||||||
}("Cannot sign message.")
|
|
||||||
}
|
|
||||||
} yield sign
|
|
||||||
}
|
|
||||||
|
|
||||||
private def curveSpec[F[_] : Monad] =
|
private def curveSpec[F[_] : Monad] =
|
||||||
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])("Cannot get curve parameters.")
|
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])("Cannot get curve parameters.")
|
||||||
|
|
||||||
@ -134,7 +125,8 @@ object Ecdsa {
|
|||||||
/**
|
/**
|
||||||
* size of key is 256 bit
|
* size of key is 256 bit
|
||||||
* `secp256k1` refers to the parameters of the ECDSA curve
|
* `secp256k1` refers to the parameters of the ECDSA curve
|
||||||
* `SHA256withECDSA` Preferably the size of the key is greater than or equal to the digest algorithm
|
* `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
|
||||||
*/
|
*/
|
||||||
lazy val ecdsa_secp256k1_sha256 = new Ecdsa("secp256k1", "SHA256withECDSA")
|
def ecdsa_secp256k1_sha256 = new Ecdsa("secp256k1", "NONEwithECDSA", Some(JdkCryptoHasher.Sha256))
|
||||||
}
|
}
|
||||||
|
57
src/main/scala/fluence/crypto/KeyStore.scala
Normal file
57
src/main/scala/fluence/crypto/KeyStore.scala
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import fluence.crypto.keypair.KeyPair
|
||||||
|
import io.circe.{ Decoder, Encoder, HCursor, Json }
|
||||||
|
import scodec.bits.{ Bases, ByteVector }
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
|
||||||
|
case class KeyStore(keyPair: KeyPair)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json example:
|
||||||
|
* {
|
||||||
|
* "keystore" : {
|
||||||
|
* "secret" : "SFcDtZClfcxx75w9xJpQgBm09d6h9tVmVUEgHYxlews=",
|
||||||
|
* "public" : "AlTBivFrIYe++9Me4gr4R11BtRzjZ2WXZGDNWD/bEPka"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
object KeyStore {
|
||||||
|
private val alphabet = Bases.Alphabets.Base64Url
|
||||||
|
implicit val encodeKeyStorage: Encoder[KeyStore] = new Encoder[KeyStore] {
|
||||||
|
final def apply(ks: KeyStore): Json = Json.obj(("keystore", Json.obj(
|
||||||
|
("secret", Json.fromString(ks.keyPair.secretKey.value.toBase64(alphabet))),
|
||||||
|
("public", Json.fromString(ks.keyPair.publicKey.value.toBase64(alphabet))))))
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val decodeKeyStorage: Decoder[Option[KeyStore]] = new Decoder[Option[KeyStore]] {
|
||||||
|
final def apply(c: HCursor): Decoder.Result[Option[KeyStore]] =
|
||||||
|
for {
|
||||||
|
secret ← c.downField("keystore").downField("secret").as[String]
|
||||||
|
public ← c.downField("keystore").downField("public").as[String]
|
||||||
|
} yield {
|
||||||
|
for {
|
||||||
|
secret ← ByteVector.fromBase64(secret, alphabet)
|
||||||
|
public ← ByteVector.fromBase64(public, alphabet)
|
||||||
|
} yield KeyStore(KeyPair.fromByteVectors(public, secret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,8 @@
|
|||||||
|
|
||||||
package fluence.crypto
|
package fluence.crypto
|
||||||
|
|
||||||
import java.security.SecureRandom
|
import cats.Monad
|
||||||
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.{ Monad, MonadError }
|
|
||||||
import fluence.crypto.algorithm.{ CryptoErr, DumbSign, KeyGenerator, SignatureFunctions }
|
import fluence.crypto.algorithm.{ CryptoErr, DumbSign, KeyGenerator, SignatureFunctions }
|
||||||
import fluence.crypto.keypair.KeyPair
|
import fluence.crypto.keypair.KeyPair
|
||||||
import fluence.crypto.signature.{ Signature, SignatureChecker, Signer }
|
import fluence.crypto.signature.{ Signature, SignatureChecker, Signer }
|
||||||
@ -34,12 +32,12 @@ import scala.language.higherKinds
|
|||||||
*/
|
*/
|
||||||
class SignAlgo(algo: KeyGenerator with SignatureFunctions) {
|
class SignAlgo(algo: KeyGenerator with SignatureFunctions) {
|
||||||
|
|
||||||
def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] = algo.generateKeyPair()
|
def generateKeyPair[F[_] : Monad](seed: Option[ByteVector] = None): EitherT[F, CryptoErr, KeyPair] =
|
||||||
def generateKeyPair[F[_] : Monad](seed: ByteVector): EitherT[F, CryptoErr, KeyPair] = algo.generateKeyPair(new SecureRandom(seed.toArray))
|
algo.generateKeyPair(seed.map(_.toArray))
|
||||||
|
|
||||||
def signer(kp: KeyPair): Signer = new Signer {
|
def signer(kp: KeyPair): Signer = new Signer {
|
||||||
override def publicKey: KeyPair.Public = kp.publicKey
|
|
||||||
override def sign[F[_] : Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] = algo.sign(kp, plain)
|
override def sign[F[_] : Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] = algo.sign(kp, plain)
|
||||||
|
override def publicKey: KeyPair.Public = kp.publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
def checker: SignatureChecker = new SignatureChecker {
|
def checker: SignatureChecker = new SignatureChecker {
|
||||||
|
@ -17,6 +17,19 @@
|
|||||||
|
|
||||||
package fluence.crypto.algorithm
|
package fluence.crypto.algorithm
|
||||||
|
|
||||||
import scala.util.control.NoStackTrace
|
import cats.Applicative
|
||||||
|
import cats.data.EitherT
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
import scala.util.control.{ NoStackTrace, NonFatal }
|
||||||
|
|
||||||
case class CryptoErr(errorMessage: String) extends Throwable(errorMessage) with NoStackTrace
|
case class CryptoErr(errorMessage: String) extends Throwable(errorMessage) with NoStackTrace
|
||||||
|
|
||||||
|
object CryptoErr {
|
||||||
|
def nonFatalHandling[F[_] : Applicative, A](a: ⇒ A)(errorText: String): EitherT[F, CryptoErr, A] = {
|
||||||
|
try EitherT.pure(a)
|
||||||
|
catch {
|
||||||
|
case NonFatal(e) ⇒ EitherT.leftT(CryptoErr(errorText + " " + e.getLocalizedMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package fluence.crypto.algorithm
|
package fluence.crypto.algorithm
|
||||||
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -28,11 +27,11 @@ import scodec.bits.ByteVector
|
|||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
|
|
||||||
class DumbSign extends KeyGenerator with SignatureFunctions {
|
class DumbSign extends KeyGenerator with SignatureFunctions {
|
||||||
override def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair] =
|
|
||||||
EitherT.pure(KeyPair.fromBytes(random.generateSeed(10), random.generateSeed(10)))
|
|
||||||
|
|
||||||
override def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] =
|
override def generateKeyPair[F[_] : Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = {
|
||||||
generateKeyPair(new SecureRandom())
|
val s = seed.getOrElse(new SecureRandom().generateSeed(10))
|
||||||
|
EitherT.pure(KeyPair.fromBytes(s, s))
|
||||||
|
}
|
||||||
|
|
||||||
override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] =
|
override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] =
|
||||||
EitherT.pure(Signature(keyPair.publicKey, message.reverse))
|
EitherT.pure(Signature(keyPair.publicKey, message.reverse))
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
|
|
||||||
package fluence.crypto.algorithm
|
package fluence.crypto.algorithm
|
||||||
|
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
import cats.Monad
|
import cats.Monad
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import fluence.crypto.keypair.KeyPair
|
import fluence.crypto.keypair.KeyPair
|
||||||
@ -26,7 +24,5 @@ import fluence.crypto.keypair.KeyPair
|
|||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
|
|
||||||
trait KeyGenerator {
|
trait KeyGenerator {
|
||||||
def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair]
|
def generateKeyPair[F[_] : Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair]
|
||||||
|
|
||||||
def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair]
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user