mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-25 14:52:19 +00:00
ed25519 for js (#8)
This commit is contained in:
parent
38608df192
commit
16f5a93562
@ -14,7 +14,7 @@ val scalaV = scalaVersion := "2.12.8"
|
|||||||
|
|
||||||
val commons = Seq(
|
val commons = Seq(
|
||||||
scalaV,
|
scalaV,
|
||||||
version := "0.0.6",
|
version := "0.0.7",
|
||||||
fork in Test := true,
|
fork in Test := true,
|
||||||
parallelExecution in Test := false,
|
parallelExecution in Test := false,
|
||||||
organization := "one.fluence",
|
organization := "one.fluence",
|
||||||
@ -26,7 +26,7 @@ val commons = Seq(
|
|||||||
bintrayOrganization := Some("fluencelabs"),
|
bintrayOrganization := Some("fluencelabs"),
|
||||||
publishMavenStyle := true,
|
publishMavenStyle := true,
|
||||||
bintrayRepository := "releases",
|
bintrayRepository := "releases",
|
||||||
resolvers += Resolver.bintrayRepo("fluencelabs", "releases")
|
resolvers ++= Seq(Resolver.bintrayRepo("fluencelabs", "releases"), Resolver.sonatypeRepo("releases"))
|
||||||
)
|
)
|
||||||
|
|
||||||
commons
|
commons
|
||||||
@ -103,8 +103,10 @@ lazy val `crypto-hashsign` = crossProject(JVMPlatform, JSPlatform)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.jsSettings(
|
.jsSettings(
|
||||||
|
libraryDependencies += "io.scalajs" %%% "nodejs" % "0.4.2",
|
||||||
npmDependencies in Compile ++= Seq(
|
npmDependencies in Compile ++= Seq(
|
||||||
"elliptic" -> "6.4.1"
|
"elliptic" -> "6.4.1",
|
||||||
|
"supercop.js" -> "2.0.1"
|
||||||
),
|
),
|
||||||
scalaJSModuleKind in Test := ModuleKind.CommonJSModule,
|
scalaJSModuleKind in Test := ModuleKind.CommonJSModule,
|
||||||
//all JavaScript dependencies will be concatenated to a single file *-jsdeps.js
|
//all JavaScript dependencies will be concatenated to a single file *-jsdeps.js
|
||||||
|
@ -86,7 +86,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
|
|||||||
//IV also needs to be transformed in byte array
|
//IV also needs to be transformed in byte array
|
||||||
val byteIv = iv.map(i ⇒ ByteVector.fromValidHex(i.toString))
|
val byteIv = iv.map(i ⇒ ByteVector.fromValidHex(i.toString))
|
||||||
byteIv.map(_.toArray ++ crypted.toArray).getOrElse(crypted.toArray)
|
byteIv.map(_.toArray ++ crypted.toArray).getOrElse(crypted.toArray)
|
||||||
}("Cannot encrypt data.")
|
}("Cannot encrypt data")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def decryptData[F[_]: Monad](key: Key, base64Data: String, iv: Option[String]) = {
|
private def decryptData[F[_]: Monad](key: Key, base64Data: String, iv: Option[String]) = {
|
||||||
@ -95,7 +95,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
|
|||||||
val cryptOptions = CryptOptions(iv = iv.map(i ⇒ CryptoJS.enc.Hex.parse(i)), padding = pad, mode = mode)
|
val cryptOptions = CryptOptions(iv = iv.map(i ⇒ CryptoJS.enc.Hex.parse(i)), padding = pad, mode = mode)
|
||||||
val dec = aes.decrypt(base64Data, key, cryptOptions)
|
val dec = aes.decrypt(base64Data, key, cryptOptions)
|
||||||
ByteVector.fromValidHex(dec.toString)
|
ByteVector.fromValidHex(dec.toString)
|
||||||
}("Cannot decrypt data.")
|
}("Cannot decrypt data")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,7 +112,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
|
|||||||
val (ivOp, data) = dataWithParams
|
val (ivOp, data) = dataWithParams
|
||||||
val base64 = ByteVector(data).toBase64
|
val base64 = ByteVector(data).toBase64
|
||||||
(ivOp, base64)
|
(ivOp, base64)
|
||||||
}("Cannot detach data and IV.")
|
}("Cannot detach data and IV")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,7 +123,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
|
|||||||
// get raw key from password and salt
|
// get raw key from password and salt
|
||||||
val keyOption = KeyOptions(BITS, iterations = iterationCount, hasher = CryptoJS.algo.SHA256)
|
val keyOption = KeyOptions(BITS, iterations = iterationCount, hasher = CryptoJS.algo.SHA256)
|
||||||
CryptoJS.PBKDF2(new String(password), salt, keyOption)
|
CryptoJS.PBKDF2(new String(password), salt, keyOption)
|
||||||
}("Cannot init secret key.")
|
}("Cannot init secret key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
|
|||||||
): EitherT[F, CryptoError, Array[Byte]] =
|
): EitherT[F, CryptoError, Array[Byte]] =
|
||||||
nonFatalHandling {
|
nonFatalHandling {
|
||||||
PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password)
|
PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password)
|
||||||
}("Cannot init secret key.")
|
}("Cannot init secret key")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup AES CBC cipher
|
* Setup AES CBC cipher
|
||||||
@ -132,7 +132,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
|
|||||||
cipher.init(encrypt, params)
|
cipher.init(encrypt, params)
|
||||||
|
|
||||||
cipher
|
cipher
|
||||||
}("Cannot setup aes cipher.")
|
}("Cannot setup aes cipher")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def cipherBytes[F[_]: Monad](
|
private def cipherBytes[F[_]: Monad](
|
||||||
@ -146,7 +146,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
|
|||||||
val lastBlockLength = cipher.doFinal(buf, outputLength)
|
val lastBlockLength = cipher.doFinal(buf, outputLength)
|
||||||
//remove padding
|
//remove padding
|
||||||
buf.slice(0, outputLength + lastBlockLength)
|
buf.slice(0, outputLength + lastBlockLength)
|
||||||
}("Error in cipher processing.")
|
}("Error in cipher processing")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +176,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
|
|||||||
val ivData = data.slice(0, ivSize)
|
val ivData = data.slice(0, ivSize)
|
||||||
val encData = data.slice(ivSize, data.length)
|
val encData = data.slice(ivSize, data.length)
|
||||||
DetachedData(ivData, encData)
|
DetachedData(ivData, encData)
|
||||||
}("Cannot detach data and IV.")
|
}("Cannot detach data and IV")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def paramsWithIV[F[_]: Monad](
|
private def paramsWithIV[F[_]: Monad](
|
||||||
|
@ -20,6 +20,7 @@ package fluence.crypto
|
|||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
import scala.util.control.{NoStackTrace, NonFatal}
|
import scala.util.control.{NoStackTrace, NonFatal}
|
||||||
|
|
||||||
case class CryptoError(message: String, causedBy: Option[Throwable] = None) extends NoStackTrace {
|
case class CryptoError(message: String, causedBy: Option[Throwable] = None) extends NoStackTrace {
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 io.scalajs.nodejs.buffer.Buffer
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
|
||||||
|
object CryptoJsHelpers {
|
||||||
|
implicit class ByteVectorOp(bv: ByteVector) {
|
||||||
|
def toJsBuffer: Buffer = Buffer.from(bv.toHex, "hex")
|
||||||
|
}
|
||||||
|
}
|
@ -37,38 +37,46 @@ import scala.scalajs.js.typedarray.Uint8Array
|
|||||||
class Ecdsa(ec: EC, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
|
class Ecdsa(ec: EC, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
|
||||||
import CryptoError.nonFatalHandling
|
import CryptoError.nonFatalHandling
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores key pair by secret key.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
def restoreKeyPair[F[_]](secretKey: KeyPair.Secret)(implicit F: Monad[F]): EitherT[F, CryptoError, KeyPair] = {
|
||||||
|
for {
|
||||||
|
secret ← nonFatalHandling {
|
||||||
|
val key = ec.keyFromPrivate(secretKey.value.toHex, "hex")
|
||||||
|
val publicHex = key.getPublic(compact = true, "hex")
|
||||||
|
val secretHex = key.getPrivate("hex")
|
||||||
|
val public = ByteVector.fromValidHex(publicHex)
|
||||||
|
val secret = ByteVector.fromValidHex(secretHex)
|
||||||
|
KeyPair.fromByteVectors(public, secret)
|
||||||
|
}("Incorrect secret key format")
|
||||||
|
} yield secret
|
||||||
|
}
|
||||||
|
|
||||||
val generateKeyPair: Crypto.KeyPairGenerator =
|
val generateKeyPair: Crypto.KeyPairGenerator =
|
||||||
new Crypto.Func[Option[Array[Byte]], KeyPair] {
|
new Crypto.Func[Option[Array[Byte]], KeyPair] {
|
||||||
override def apply[F[_]](input: Option[Array[Byte]])(implicit F: Monad[F]): EitherT[F, CryptoError, KeyPair] =
|
override def apply[F[_]](input: Option[Array[Byte]])(implicit F: Monad[F]): EitherT[F, CryptoError, KeyPair] =
|
||||||
nonFatalHandling {
|
nonFatalHandling {
|
||||||
val seedJs = input.map(bs ⇒ js.Dynamic.literal(entropy = bs.toJSArray))
|
val seedJs = input.map(bs ⇒ js.Dynamic.literal(entropy = bs.toJSArray))
|
||||||
val key = ec.genKeyPair(seedJs)
|
val key = ec.genKeyPair(seedJs)
|
||||||
val publicHex = key.getPublic(true, "hex")
|
val publicHex = key.getPublic(compact = true, "hex")
|
||||||
val secretHex = key.getPrivate("hex")
|
val secretHex = key.getPrivate("hex")
|
||||||
val public = ByteVector.fromValidHex(publicHex)
|
val public = ByteVector.fromValidHex(publicHex)
|
||||||
val secret = ByteVector.fromValidHex(secretHex)
|
val secret = ByteVector.fromValidHex(secretHex)
|
||||||
KeyPair.fromByteVectors(public, secret)
|
KeyPair.fromByteVectors(public, secret)
|
||||||
}("Failed to generate key pair.")
|
}("Failed to generate key pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoError, Signature] =
|
def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoError, Signature] =
|
||||||
for {
|
for {
|
||||||
secret ← nonFatalHandling {
|
secret ← nonFatalHandling {
|
||||||
ec.keyFromPrivate(keyPair.secretKey.value.toHex, "hex")
|
ec.keyFromPrivate(keyPair.secretKey.value.toHex, "hex")
|
||||||
}("Cannot get private key from key pair.")
|
}("Cannot get private key from key pair")
|
||||||
hash ← hash(message)
|
hash ← JsCryptoHasher.hashJs(message, hasher)
|
||||||
signHex ← nonFatalHandling(secret.sign(new Uint8Array(hash)).toDER("hex"))("Cannot sign message")
|
signHex ← nonFatalHandling(secret.sign(new Uint8Array(hash)).toDER("hex"))("Cannot sign message")
|
||||||
} yield Signature(ByteVector.fromValidHex(signHex))
|
} yield Signature(ByteVector.fromValidHex(signHex))
|
||||||
|
|
||||||
def hash[F[_]: Monad](message: ByteVector): EitherT[F, CryptoError, js.Array[Byte]] = {
|
|
||||||
val arr = message.toArray
|
|
||||||
hasher
|
|
||||||
.fold(EitherT.pure[F, CryptoError](arr)) { h ⇒
|
|
||||||
h[F](arr)
|
|
||||||
}
|
|
||||||
.map(_.toJSArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
def verify[F[_]: Monad](
|
def verify[F[_]: Monad](
|
||||||
pubKey: KeyPair.Public,
|
pubKey: KeyPair.Public,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
@ -79,8 +87,8 @@ class Ecdsa(ec: EC, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
|
|||||||
val hex = pubKey.value.toHex
|
val hex = pubKey.value.toHex
|
||||||
ec.keyFromPublic(hex, "hex")
|
ec.keyFromPublic(hex, "hex")
|
||||||
}("Incorrect public key format.")
|
}("Incorrect public key format.")
|
||||||
hash ← hash(message)
|
hash ← JsCryptoHasher.hashJs(message, hasher)
|
||||||
verify ← nonFatalHandling(public.verify(new Uint8Array(hash), signature.sign.toHex))("Cannot verify message.")
|
verify ← nonFatalHandling(public.verify(new Uint8Array(hash), signature.sign.toHex))("Cannot verify message")
|
||||||
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
110
hashsign/js/src/main/scala/fluence/crypto/ed25519/Ed25519.scala
Normal file
110
hashsign/js/src/main/scala/fluence/crypto/ed25519/Ed25519.scala
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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.ed25519
|
||||||
|
|
||||||
|
import cats.Monad
|
||||||
|
import cats.data.EitherT
|
||||||
|
import fluence.crypto.CryptoError.nonFatalHandling
|
||||||
|
import fluence.crypto.facade.ed25519.Supercop
|
||||||
|
import fluence.crypto.hash.JsCryptoHasher
|
||||||
|
import fluence.crypto.{Crypto, CryptoError, KeyPair, CryptoJsHelpers}
|
||||||
|
import fluence.crypto.signature.{SignAlgo, Signature, SignatureChecker, Signer}
|
||||||
|
import io.scalajs.nodejs.buffer.Buffer
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
|
||||||
|
class Ed25519(hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
|
||||||
|
|
||||||
|
import CryptoJsHelpers._
|
||||||
|
|
||||||
|
def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoError, Signature] =
|
||||||
|
for {
|
||||||
|
hash ← JsCryptoHasher.hash(message, hasher)
|
||||||
|
sign ← nonFatalHandling {
|
||||||
|
Supercop.sign(
|
||||||
|
Buffer.from(ByteVector(hash).toHex, "hex"),
|
||||||
|
keyPair.publicKey.value.toJsBuffer,
|
||||||
|
keyPair.secretKey.value.toJsBuffer
|
||||||
|
)
|
||||||
|
}("Error on signing message by js/ed25519 signature")
|
||||||
|
} yield Signature(ByteVector.fromValidHex(sign.toString("hex")))
|
||||||
|
|
||||||
|
def verify[F[_]: Monad](
|
||||||
|
pubKey: KeyPair.Public,
|
||||||
|
signature: Signature,
|
||||||
|
message: ByteVector
|
||||||
|
): EitherT[F, CryptoError, Unit] =
|
||||||
|
for {
|
||||||
|
hash ← JsCryptoHasher.hash(message, hasher)
|
||||||
|
verify ← nonFatalHandling(
|
||||||
|
Supercop.verify(signature.sign.toJsBuffer, ByteVector(hash).toJsBuffer, pubKey.value.toJsBuffer)
|
||||||
|
)("Cannot verify message")
|
||||||
|
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
||||||
|
} yield ()
|
||||||
|
|
||||||
|
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, KeyPair] = {
|
||||||
|
for {
|
||||||
|
seed ← nonFatalHandling(input.map(ByteVector(_).toJsBuffer).getOrElse(Supercop.createSeed()))(
|
||||||
|
"Error on seed creation"
|
||||||
|
)
|
||||||
|
jsKeyPair ← nonFatalHandling(Supercop.createKeyPair(seed))("Error on key pair generation.")
|
||||||
|
keyPair ← nonFatalHandling(
|
||||||
|
KeyPair.fromByteVectors(
|
||||||
|
ByteVector.fromValidHex(jsKeyPair.publicKey.toString("hex")),
|
||||||
|
ByteVector.fromValidHex(jsKeyPair.secretKey.toString("hex"))
|
||||||
|
)
|
||||||
|
)("Error on decoding public and secret keys")
|
||||||
|
} yield keyPair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Ed25519 {
|
||||||
|
|
||||||
|
val ed25519: Ed25519 = new Ed25519(Some(JsCryptoHasher.Sha256))
|
||||||
|
|
||||||
|
val signAlgo: SignAlgo = {
|
||||||
|
SignAlgo(
|
||||||
|
name = "ed25519",
|
||||||
|
generateKeyPair = ed25519.generateKeyPair,
|
||||||
|
signer = kp ⇒
|
||||||
|
Signer(
|
||||||
|
kp.publicKey,
|
||||||
|
new Crypto.Func[ByteVector, Signature] {
|
||||||
|
override def apply[F[_]](
|
||||||
|
input: ByteVector
|
||||||
|
)(implicit F: Monad[F]): EitherT[F, CryptoError, Signature] =
|
||||||
|
ed25519.sign(kp, input)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
checker = pk ⇒
|
||||||
|
new SignatureChecker {
|
||||||
|
override def check[F[_]: Monad](
|
||||||
|
signature: fluence.crypto.signature.Signature,
|
||||||
|
plain: ByteVector
|
||||||
|
): EitherT[F, CryptoError, Unit] =
|
||||||
|
ed25519.verify(pk, signature, plain)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.ed25519
|
||||||
|
|
||||||
|
import io.scalajs.nodejs.buffer.Buffer
|
||||||
|
|
||||||
|
import scala.scalajs.js
|
||||||
|
import scala.scalajs.js.annotation.JSImport
|
||||||
|
|
||||||
|
@js.native
|
||||||
|
trait KeyPair extends js.Object {
|
||||||
|
val publicKey: Buffer
|
||||||
|
val secretKey: Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
@js.native
|
||||||
|
@JSImport("supercop.js", JSImport.Namespace)
|
||||||
|
object Supercop extends js.Object {
|
||||||
|
def verify(sig: Buffer, msg: Buffer, pubKey: Buffer): Boolean = js.native
|
||||||
|
def sign(msg: Buffer, pubKey: Buffer, privKey: Buffer): Buffer = js.native
|
||||||
|
def createKeyPair(seed: Buffer): KeyPair = js.native
|
||||||
|
def createSeed(): Buffer = js.native
|
||||||
|
}
|
@ -17,10 +17,14 @@
|
|||||||
|
|
||||||
package fluence.crypto.hash
|
package fluence.crypto.hash
|
||||||
|
|
||||||
|
import cats.Monad
|
||||||
|
import cats.data.EitherT
|
||||||
import fluence.crypto.{Crypto, CryptoError}
|
import fluence.crypto.{Crypto, CryptoError}
|
||||||
import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
|
import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.language.higherKinds
|
||||||
|
import scala.scalajs.js
|
||||||
import scala.scalajs.js.JSConverters._
|
import scala.scalajs.js.JSConverters._
|
||||||
import scala.scalajs.js.typedarray.Uint8Array
|
import scala.scalajs.js.typedarray.Uint8Array
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
@ -44,4 +48,27 @@ object JsCryptoHasher {
|
|||||||
ByteVector.fromValidHex(sha1.digest("hex")).toArray
|
ByteVector.fromValidHex(sha1.digest("hex")).toArray
|
||||||
}.toEither.left.map(err ⇒ CryptoError("Cannot calculate Sha256 hash", Some(err)))
|
}.toEither.left.map(err ⇒ CryptoError("Cannot calculate Sha256 hash", Some(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates hash of message.
|
||||||
|
*
|
||||||
|
* @return hash in JS array
|
||||||
|
*/
|
||||||
|
def hashJs[F[_]: Monad](message: ByteVector, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]): EitherT[F, CryptoError, js.Array[Byte]] = {
|
||||||
|
hash(message, hasher)
|
||||||
|
.map(_.toJSArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates hash of message.
|
||||||
|
*
|
||||||
|
* @return hash in Scala array
|
||||||
|
*/
|
||||||
|
def hash[F[_]: Monad](message: ByteVector, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]): EitherT[F, CryptoError, Array[Byte]] = {
|
||||||
|
val arr = message.toArray
|
||||||
|
hasher
|
||||||
|
.fold(EitherT.pure[F, CryptoError](arr)) { h ⇒
|
||||||
|
h[F](arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
97
hashsign/js/src/test/scala/fluence/crypto/Ed25519Spec.scala
Normal file
97
hashsign/js/src/test/scala/fluence/crypto/Ed25519Spec.scala
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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 fluence.crypto.signature.Signature
|
||||||
|
import org.scalatest.{Matchers, WordSpec}
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
import cats.instances.try_._
|
||||||
|
import fluence.crypto.ed25519.Ed25519
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,9 @@ import scodec.bits.ByteVector
|
|||||||
|
|
||||||
import scala.util.{Random, Try}
|
import scala.util.{Random, Try}
|
||||||
|
|
||||||
class EcdsaSpec extends WordSpec with Matchers {
|
class EllipticSpec extends WordSpec with Matchers {
|
||||||
|
|
||||||
def rndBytes(size: Int) = Random.nextString(10).getBytes
|
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes
|
||||||
|
|
||||||
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
|
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
|
||||||
|
|
@ -142,36 +142,36 @@ class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Arra
|
|||||||
): EitherT[F, CryptoError, Unit] =
|
): EitherT[F, CryptoError, 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")
|
||||||
keyFactory ← getKeyFactory
|
keyFactory ← getKeyFactory
|
||||||
signProvider ← getSignatureProvider
|
signProvider ← getSignatureProvider
|
||||||
verify ← nonFatalHandling {
|
verify ← nonFatalHandling {
|
||||||
signProvider.initVerify(keyFactory.generatePublic(keySpec))
|
signProvider.initVerify(keyFactory.generatePublic(keySpec))
|
||||||
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
|
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
|
||||||
signProvider.verify(signature)
|
signProvider.verify(signature)
|
||||||
}("Cannot verify message.")
|
}("Cannot verify message")
|
||||||
|
|
||||||
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
_ ← EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
private def curveSpec[F[_]: Monad] =
|
private def curveSpec[F[_]: Monad] =
|
||||||
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])(
|
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])(
|
||||||
"Cannot get curve parameters."
|
"Cannot get curve parameters"
|
||||||
)
|
)
|
||||||
|
|
||||||
private def getKeyPairGenerator[F[_]: Monad] =
|
private def getKeyPairGenerator[F[_]: Monad] =
|
||||||
nonFatalHandling(KeyPairGenerator.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
nonFatalHandling(KeyPairGenerator.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
||||||
"Cannot get key pair generator."
|
"Cannot get key pair generator"
|
||||||
)
|
)
|
||||||
|
|
||||||
private def getKeyFactory[F[_]: Monad] =
|
private def getKeyFactory[F[_]: Monad] =
|
||||||
nonFatalHandling(KeyFactory.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
nonFatalHandling(KeyFactory.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))(
|
||||||
"Cannot get key factory instance."
|
"Cannot get key factory instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
private def getSignatureProvider[F[_]: Monad] =
|
private def getSignatureProvider[F[_]: Monad] =
|
||||||
nonFatalHandling(Signature.getInstance(scheme, BouncyCastleProvider.PROVIDER_NAME))(
|
nonFatalHandling(Signature.getInstance(scheme, BouncyCastleProvider.PROVIDER_NAME))(
|
||||||
"Cannot get signature instance."
|
"Cannot get signature instance"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,13 +137,21 @@ object Ed25519 {
|
|||||||
/**
|
/**
|
||||||
* Keys in tendermint are generating with a random seed of 32 bytes
|
* Keys in tendermint are generating with a random seed of 32 bytes
|
||||||
*/
|
*/
|
||||||
val tendermintEd25519 = new Ed25519(256)
|
val ed25519 = new Ed25519(256)
|
||||||
val tendermintAlgo: SignAlgo = signAlgo(256)
|
val signAlgo: SignAlgo = signAlgoInit(256)
|
||||||
|
|
||||||
def ed25519(strength: Int) = new Ed25519(strength)
|
/**
|
||||||
|
*
|
||||||
|
* @param strength the size, in bits, of the keys we want to produce
|
||||||
|
*/
|
||||||
|
def ed25519Init(strength: Int) = new Ed25519(strength)
|
||||||
|
|
||||||
def signAlgo(strength: Int): SignAlgo = {
|
/**
|
||||||
val algo = ed25519(strength)
|
*
|
||||||
|
* @param strength the size, in bits, of the keys we want to produce
|
||||||
|
*/
|
||||||
|
def signAlgoInit(strength: Int): SignAlgo = {
|
||||||
|
val algo = ed25519Init(strength)
|
||||||
SignAlgo(
|
SignAlgo(
|
||||||
name = "ed25519",
|
name = "ed25519",
|
||||||
generateKeyPair = algo.generateKeyPair,
|
generateKeyPair = algo.generateKeyPair,
|
||||||
|
@ -48,7 +48,7 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
|
|
||||||
"ed25519 algorithm" should {
|
"ed25519 algorithm" should {
|
||||||
"correct sign and verify data" in {
|
"correct sign and verify data" in {
|
||||||
val algorithm = Ed25519.ed25519(32)
|
val algorithm = Ed25519.ed25519Init(32)
|
||||||
|
|
||||||
val keys = algorithm.generateKeyPair.unsafe(None)
|
val keys = algorithm.generateKeyPair.unsafe(None)
|
||||||
val pubKey = keys.publicKey
|
val pubKey = keys.publicKey
|
||||||
@ -66,7 +66,7 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"correctly work with signer and checker" in {
|
"correctly work with signer and checker" in {
|
||||||
val algo = Ed25519.signAlgo(32)
|
val algo = Ed25519.signAlgo
|
||||||
val keys = algo.generateKeyPair.unsafe(None)
|
val keys = algo.generateKeyPair.unsafe(None)
|
||||||
val signer = algo.signer(keys)
|
val signer = algo.signer(keys)
|
||||||
val checker = algo.checker(keys.publicKey)
|
val checker = algo.checker(keys.publicKey)
|
||||||
@ -81,7 +81,7 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"throw an errors on invalid data" in {
|
"throw an errors on invalid data" in {
|
||||||
val algo = Ed25519.signAlgo(32)
|
val algo = Ed25519.signAlgo
|
||||||
val keys = algo.generateKeyPair.unsafe(None)
|
val keys = algo.generateKeyPair.unsafe(None)
|
||||||
val signer = algo.signer(keys)
|
val signer = algo.signer(keys)
|
||||||
val checker = algo.checker(keys.publicKey)
|
val checker = algo.checker(keys.publicKey)
|
||||||
@ -103,7 +103,7 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"store and read key from file" in {
|
"store and read key from file" in {
|
||||||
val algo = Ed25519.signAlgo(32)
|
val algo = Ed25519.signAlgo
|
||||||
val keys = algo.generateKeyPair.unsafe(None)
|
val keys = algo.generateKeyPair.unsafe(None)
|
||||||
|
|
||||||
val keyFile = File.createTempFile("test", "")
|
val keyFile = File.createTempFile("test", "")
|
||||||
@ -127,10 +127,10 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"restore key pair from secret key" in {
|
"restore key pair from secret key" in {
|
||||||
val algo = Ed25519.signAlgo(32)
|
val algo = Ed25519.signAlgo
|
||||||
val testKeys = algo.generateKeyPair.unsafe(None)
|
val testKeys = algo.generateKeyPair.unsafe(None)
|
||||||
|
|
||||||
val ed25519 = Ed25519.ed25519(32)
|
val ed25519 = Ed25519.ed25519
|
||||||
|
|
||||||
val newKeys = ed25519.restorePairFromSecret(testKeys.secretKey).extract
|
val newKeys = ed25519.restorePairFromSecret(testKeys.secretKey).extract
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ class Ed25519Spec extends WordSpec with Matchers {
|
|||||||
val privKey = ByteVector.fromBase64Descriptive(privKeyBase64).right.get
|
val privKey = ByteVector.fromBase64Descriptive(privKeyBase64).right.get
|
||||||
val pubKey = ByteVector.fromBase64Descriptive(pubKeyBase64).right.get
|
val pubKey = ByteVector.fromBase64Descriptive(pubKeyBase64).right.get
|
||||||
|
|
||||||
val restored = Ed25519.tendermintEd25519
|
val restored = Ed25519.ed25519
|
||||||
.restorePairFromSecret[Try](KeyPair.Secret(privKey.dropRight(32)))
|
.restorePairFromSecret[Try](KeyPair.Secret(privKey.dropRight(32)))
|
||||||
.value
|
.value
|
||||||
.get
|
.get
|
||||||
|
@ -26,4 +26,3 @@ object FluenceCrossType extends sbtcrossproject.CrossType {
|
|||||||
override def sharedSrcDir(projectBase: File, conf: String) =
|
override def sharedSrcDir(projectBase: File, conf: String) =
|
||||||
Some(shared(projectBase, conf))
|
Some(shared(projectBase, conf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.20")
|
|||||||
|
|
||||||
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0")
|
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0")
|
||||||
|
|
||||||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27")
|
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28")
|
||||||
addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.6.0")
|
addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.6.0")
|
||||||
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
|
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user