mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-24 14:22:18 +00:00
parent
e9b1e2d905
commit
1a8313b321
@ -14,7 +14,7 @@ val scalaV = scalaVersion := "2.12.8"
|
||||
|
||||
val commons = Seq(
|
||||
scalaV,
|
||||
version := "0.0.4",
|
||||
version := "0.0.5",
|
||||
fork in Test := true,
|
||||
parallelExecution in Test := false,
|
||||
organization := "one.fluence",
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package flyence.crypto
|
||||
package fluence.crypto
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.instances.try_._
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package flyence.crypto
|
||||
package fluence.crypto
|
||||
|
||||
import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
|
||||
import org.scalatest.{Matchers, WordSpec}
|
@ -62,14 +62,20 @@ class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Arra
|
||||
_ ← nonFatalHandling {
|
||||
g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
|
||||
}(s"Could not initialize KeyPairGenerator")
|
||||
p ← EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Could not generate KeyPair. Unexpected."))
|
||||
p ← EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Generated key pair is null"))
|
||||
keyPair ← nonFatalHandling {
|
||||
//store S number for private key and compressed Q point on curve for public key
|
||||
val pk = ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true))
|
||||
val bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
|
||||
val sk = ByteVector.fromValidHex(bg.toString(HEXradix))
|
||||
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. Unexpected.")
|
||||
}("Could not generate KeyPair")
|
||||
} yield keyPair
|
||||
}
|
||||
|
||||
|
170
hashsign/jvm/src/main/scala/fluence/crypto/ecdsa/Ed25519.scala
Normal file
170
hashsign/jvm/src/main/scala/fluence/crypto/ecdsa/Ed25519.scala
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.security._
|
||||
|
||||
import cats.Monad
|
||||
import cats.data.EitherT
|
||||
import fluence.crypto.KeyPair.Secret
|
||||
import fluence.crypto.signature.{SignAlgo, SignatureChecker, Signer}
|
||||
import fluence.crypto.{KeyPair, _}
|
||||
import org.bouncycastle.crypto.KeyGenerationParameters
|
||||
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator
|
||||
import org.bouncycastle.crypto.params.{Ed25519PrivateKeyParameters, Ed25519PublicKeyParameters}
|
||||
import org.bouncycastle.crypto.signers.Ed25519Signer
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.language.higherKinds
|
||||
|
||||
/**
|
||||
* Edwards-curve Digital Signature Algorithm (EdDSA)
|
||||
*/
|
||||
class Ed25519(strength: Int) extends JavaAlgorithm {
|
||||
|
||||
import CryptoError.nonFatalHandling
|
||||
|
||||
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 {
|
||||
g ← getKeyPairGenerator
|
||||
random = input.map(new SecureRandom(_)).getOrElse(new SecureRandom())
|
||||
keyParameters = new KeyGenerationParameters(random, strength)
|
||||
_ = g.init(keyParameters)
|
||||
p ← EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Generated key pair is null"))
|
||||
keyPair ← nonFatalHandling {
|
||||
val pk = p.getPublic match {
|
||||
case pk: Ed25519PublicKeyParameters => pk.getEncoded
|
||||
case p => throw new ClassCastException(s"Cannot cast public key (${p.getClass}) to Ed25519PublicKeyParameters")
|
||||
}
|
||||
val sk = p.getPrivate match {
|
||||
case sk: Ed25519PrivateKeyParameters => sk.getEncoded
|
||||
case s => throw new ClassCastException(s"Cannot cast private key (${p.getClass}) to Ed25519PrivateKeyParameters")
|
||||
}
|
||||
KeyPair.fromBytes(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 {
|
||||
keyPair ← nonFatalHandling {
|
||||
val secret = new Ed25519PrivateKeyParameters(sk.bytes, 0)
|
||||
KeyPair.fromBytes(secret.generatePublicKey().getEncoded, sk.bytes)
|
||||
}("Could not generate KeyPair from private key")
|
||||
} yield keyPair
|
||||
|
||||
def sign[F[_]: Monad](
|
||||
keyPair: KeyPair,
|
||||
message: ByteVector
|
||||
): EitherT[F, CryptoError, signature.Signature] =
|
||||
signMessage(keyPair.secretKey.bytes, 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: Array[Byte],
|
||||
message: Array[Byte]
|
||||
): EitherT[F, CryptoError, Array[Byte]] =
|
||||
for {
|
||||
sign ← nonFatalHandling {
|
||||
val privKey = new Ed25519PrivateKeyParameters(privateKey, 0)
|
||||
val signer = new Ed25519Signer
|
||||
signer.init(true, privKey)
|
||||
signer.update(message, 0, message.length)
|
||||
signer.generateSignature()
|
||||
}("Cannot sign message")
|
||||
|
||||
} yield sign
|
||||
|
||||
private def verifySign[F[_]: Monad](
|
||||
publicKey: Array[Byte],
|
||||
signature: Array[Byte],
|
||||
message: Array[Byte],
|
||||
): EitherT[F, CryptoError, Unit] =
|
||||
for {
|
||||
verify ← nonFatalHandling {
|
||||
val pubKey = new Ed25519PublicKeyParameters(publicKey, 0)
|
||||
val signer = new Ed25519Signer
|
||||
signer.init(false, pubKey)
|
||||
signer.update(message, 0, message.length)
|
||||
signer.verifySignature(signature)
|
||||
}("Cannot verify message")
|
||||
|
||||
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
||||
} yield ()
|
||||
|
||||
private def getKeyPairGenerator[F[_]: Monad] =
|
||||
nonFatalHandling {
|
||||
new Ed25519KeyPairGenerator()
|
||||
}(
|
||||
"Cannot get key pair generator"
|
||||
)
|
||||
}
|
||||
|
||||
object Ed25519 {
|
||||
|
||||
/**
|
||||
* Keys in tendermint are generating with a random seed of 32 bytes
|
||||
*/
|
||||
val tendermintEd25519 = new Ed25519(256)
|
||||
val tendermintAlgo: SignAlgo = signAlgo(256)
|
||||
|
||||
def ed25519(strength: Int) = new Ed25519(strength)
|
||||
|
||||
def signAlgo(strength: Int): SignAlgo = {
|
||||
val algo = ed25519(strength)
|
||||
SignAlgo(
|
||||
name = "ed25519",
|
||||
generateKeyPair = algo.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] =
|
||||
algo.sign(kp, input)
|
||||
}
|
||||
),
|
||||
checker = pk ⇒
|
||||
new SignatureChecker {
|
||||
override def check[F[_]: Monad](
|
||||
signature: fluence.crypto.signature.Signature,
|
||||
plain: ByteVector
|
||||
): EitherT[F, CryptoError, Unit] =
|
||||
algo.verify(pk, signature, plain)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
package fluence.crypto
|
||||
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.instances.try_._
|
||||
@ -30,7 +29,7 @@ import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{Random, Try}
|
||||
|
||||
class SignatureSpec extends WordSpec with Matchers {
|
||||
class EcdsaSpec extends WordSpec with Matchers {
|
||||
|
||||
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes
|
||||
|
174
hashsign/jvm/src/test/scala/fluence/crypto/Ed25519Spec.scala
Normal file
174
hashsign/jvm/src/test/scala/fluence/crypto/Ed25519Spec.scala
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 java.io.File
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.instances.try_._
|
||||
import fluence.crypto.ecdsa.Ed25519
|
||||
import fluence.crypto.keystore.FileKeyStorage
|
||||
import fluence.crypto.signature.Signature
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{Random, Try}
|
||||
|
||||
class Ed25519Spec extends WordSpec with Matchers {
|
||||
|
||||
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes
|
||||
|
||||
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
|
||||
|
||||
private implicit class TryEitherTExtractor[A <: Throwable, B](et: EitherT[Try, A, B]) {
|
||||
|
||||
def extract: B =
|
||||
et.value.map {
|
||||
case Left(e) ⇒ fail(e) // for making test fail message more describable
|
||||
case Right(v) ⇒ v
|
||||
}.get
|
||||
|
||||
def isOk: Boolean = et.value.fold(_ ⇒ false, _.isRight)
|
||||
}
|
||||
|
||||
"ed25519 algorithm" should {
|
||||
"correct sign and verify data" in {
|
||||
val algorithm = Ed25519.ed25519(32)
|
||||
|
||||
val keys = algorithm.generateKeyPair.unsafe(None)
|
||||
val pubKey = keys.publicKey
|
||||
val data = rndByteVector(10)
|
||||
val sign = algorithm.sign[Try](keys, data).extract
|
||||
|
||||
algorithm.verify[Try](pubKey, sign, data).isOk shouldBe true
|
||||
|
||||
val randomData = rndByteVector(10)
|
||||
val randomSign = algorithm.sign(keys, randomData).extract
|
||||
|
||||
algorithm.verify(pubKey, randomSign, data).isOk shouldBe false
|
||||
|
||||
algorithm.verify(pubKey, sign, randomData).isOk shouldBe false
|
||||
}
|
||||
|
||||
"correctly work with signer and checker" in {
|
||||
val algo = Ed25519.signAlgo(32)
|
||||
val keys = algo.generateKeyPair.unsafe(None)
|
||||
val signer = algo.signer(keys)
|
||||
val checker = algo.checker(keys.publicKey)
|
||||
|
||||
val data = rndByteVector(10)
|
||||
val sign = signer.sign(data).extract
|
||||
|
||||
checker.check(sign, data).isOk shouldBe true
|
||||
|
||||
val randomSign = signer.sign(rndByteVector(10)).extract
|
||||
checker.check(randomSign, data).isOk shouldBe false
|
||||
}
|
||||
|
||||
"throw an errors on invalid data" in {
|
||||
val algo = Ed25519.signAlgo(32)
|
||||
val keys = algo.generateKeyPair.unsafe(None)
|
||||
val signer = algo.signer(keys)
|
||||
val checker = algo.checker(keys.publicKey)
|
||||
val data = rndByteVector(10)
|
||||
|
||||
val sign = signer.sign(data).extract
|
||||
|
||||
the[CryptoError] thrownBy {
|
||||
checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get
|
||||
}
|
||||
val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey)
|
||||
the[CryptoError] thrownBy {
|
||||
invalidChecker
|
||||
.check(sign, data)
|
||||
.value
|
||||
.flatMap(_.toTry)
|
||||
.get
|
||||
}
|
||||
}
|
||||
|
||||
"store and read key from file" in {
|
||||
val algo = Ed25519.signAlgo(32)
|
||||
val keys = algo.generateKeyPair.unsafe(None)
|
||||
|
||||
val keyFile = File.createTempFile("test", "")
|
||||
if (keyFile.exists()) keyFile.delete()
|
||||
val storage = new FileKeyStorage(keyFile)
|
||||
|
||||
storage.storeKeyPair(keys).unsafeRunSync()
|
||||
|
||||
val keysReadE = storage.readKeyPair
|
||||
val keysRead = keysReadE.unsafeRunSync()
|
||||
|
||||
val signer = algo.signer(keys)
|
||||
val data = rndByteVector(10)
|
||||
val sign = signer.sign(data).extract
|
||||
|
||||
algo.checker(keys.publicKey).check(sign, data).isOk shouldBe true
|
||||
algo.checker(keysRead.publicKey).check(sign, data).isOk shouldBe true
|
||||
|
||||
//try to store key into previously created file
|
||||
storage.storeKeyPair(keys).attempt.unsafeRunSync().isLeft shouldBe true
|
||||
}
|
||||
|
||||
"restore key pair from secret key" in {
|
||||
val algo = Ed25519.signAlgo(32)
|
||||
val testKeys = algo.generateKeyPair.unsafe(None)
|
||||
|
||||
val ed25519 = Ed25519.ed25519(32)
|
||||
|
||||
val newKeys = ed25519.restorePairFromSecret(testKeys.secretKey).extract
|
||||
|
||||
testKeys shouldBe newKeys
|
||||
}
|
||||
|
||||
"work with tendermint keys" in {
|
||||
/*
|
||||
{
|
||||
"address": "C08269A8AACD53C3488F16F285821DAC77CF5DEF",
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeyEd25519",
|
||||
"value": "FWB5lXZ/TT2132+jXp/8aQzNwISwp9uuFz4z0TXDdxY="
|
||||
},
|
||||
"priv_key": {
|
||||
"type": "tendermint/PrivKeyEd25519",
|
||||
"value": "P6jw9q/Rytdxpv5Wxs1aYA8w82uS0x3CpmS9+GpaMGIVYHmVdn9NPbXfb6Nen/xpDM3AhLCn264XPjPRNcN3Fg=="
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
val privKeyBase64 = "P6jw9q/Rytdxpv5Wxs1aYA8w82uS0x3CpmS9+GpaMGIVYHmVdn9NPbXfb6Nen/xpDM3AhLCn264XPjPRNcN3Fg=="
|
||||
val pubKeyBase64 = "FWB5lXZ/TT2132+jXp/8aQzNwISwp9uuFz4z0TXDdxY="
|
||||
|
||||
val privKey = ByteVector.fromBase64Descriptive(privKeyBase64).right.get
|
||||
val pubKey = ByteVector.fromBase64Descriptive(pubKeyBase64).right.get
|
||||
|
||||
val restored = Ed25519.tendermintEd25519
|
||||
.restorePairFromSecret[Try](KeyPair.Secret(privKey.dropRight(32)))
|
||||
.value
|
||||
.get
|
||||
.right
|
||||
.get
|
||||
.publicKey
|
||||
.bytes
|
||||
|
||||
restored shouldBe pubKey.toArray
|
||||
restored shouldBe privKey.drop(32).toArray
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user