6 Commits
0.0.1 ... 0.0.6

Author SHA1 Message Date
38608df192 Slogging dependency is kept only in keystore (#7) 2019-06-13 15:28:05 +03:00
1a8313b321 ed25519 implementation (#6)
* ed25519 implementation
2019-06-06 13:47:24 +03:00
e9b1e2d905 Updated dependencies (#5) 2019-04-18 18:39:48 +03:00
fcf7e08813 Updated dependencies (#3) 2019-01-23 21:30:50 +03:00
3e0d21d197 Ecdsa recreate pair from secret (#2)
* add possibility to re-create public key from private key for ECDSA alghorithm

* change version

* change version

* add comments, change version of cats-effect

* add comments, change method name

* change comment

* typo
2018-08-22 11:26:10 +03:00
d938192ef6 Initial Readme (#1)
* Badges

* Bintray resolver

* readme WIP

* More readme
2018-06-04 01:09:43 -07:00
14 changed files with 485 additions and 38 deletions

View File

@ -1,2 +1,62 @@
# crypto
[![Build Status](https://travis-ci.org/fluencelabs/crypto.svg?branch=master)](https://travis-ci.org/fluencelabs/crypto)
# Сrypto
Cryptography for Scala and Scala.js, FP-flavoured.
APIs are mostly based on [codec](https://github.com/fluencelabs/codec) approach with partial bijections, using `CryptoError` case class for errors.
## Submodules
### crypto-core
Provides APIs and data types for various cryptographical operations, but without implementations.
This library should be added as dependency to both implementations of crypto algorithms and on the user side, if particular app doesn't require a particular algorithm.
### crypto-hashsign
Cross-platform hashing and signing algos.
Use a `fluence.crypto.hash.CryptoHashers` to access a hashing algorithm. Currently `Sha1` and `Sha256` are provided.
`fluence.crypto.ecdsa.Ecdsa.signAlgo` is an instance of `SignAlgo` with `ecdsa_secp256k1_sha256` under the hood.
### crypto-cipher
Encryption and decryption algorithms, coded as arrows and bijections. Currently contains `AES` ciphering.
### crypto-keystore
Provides a JSON format for serializing a keypair, using `codec-circe` for JSON processing.
For Scala on JVM, storing on the disc is implemented with use of `cats-effect`.
### crypto-jwt
Simplified JWT implementation, meaning a JSON-serialized header and claim with signature checking.
`codec-circe` is used for JSON encoding/decoding, and `codec-bits` for binary data manipulations.
## Installation
```scala
// Bintray repo is used so far. Migration to Maven Central is planned
resolvers += Resolver.bintrayRepo("fluencelabs", "releases")
val cryptoV = "0.0.3"
libraryDependencies ++= Seq(
"one.fluence" %%% "crypto-core" % cryptoV, // basic types and APIs
"one.fluence" %%% "crypto-hashsign" % cryptoV, // hashers and signatures
"one.fluence" %%% "crypto-cipher" % cryptoV, // encoding and decoding
"one.fluence" %%% "crypto-keystore" % cryptoV, // serialize and store a keypair
"one.fluence" %%% "crypto-jwt" % cryptoV // simple JWT implementation
)
```
## License
Fluence is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License v3 (AGPLv3) as published by the Free Software Foundation.
Fluence includes some [external modules](https://github.com/fluencelabs/crypto/blob/master/build.sbt) that carry their own licensing.

View File

@ -1,6 +1,6 @@
import de.heikoseeberger.sbtheader.License
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import sbtcrossproject.crossProject
import sbtcrossproject.CrossPlugin.autoImport.crossProject
name := "crypto"
@ -10,11 +10,11 @@ javaOptions in Test ++= Seq("-ea")
skip in publish := true // Skip root project
val scalaV = scalaVersion := "2.12.5"
val scalaV = scalaVersion := "2.12.8"
val commons = Seq(
scalaV,
version := "0.0.1",
version := "0.0.6",
fork in Test := true,
parallelExecution in Test := false,
organization := "one.fluence",
@ -25,20 +25,21 @@ val commons = Seq(
headerLicense := Some(License.AGPLv3("2017", organizationName.value)),
bintrayOrganization := Some("fluencelabs"),
publishMavenStyle := true,
bintrayRepository := "releases"
bintrayRepository := "releases",
resolvers += Resolver.bintrayRepo("fluencelabs", "releases")
)
commons
val CodecV = "0.0.3"
val CodecV = "0.0.5"
val CatsEffectV = "1.0.0-RC"
val CatsEffectV = "1.2.0"
val SloggingV = "0.6.1"
val ScalatestV = "3.0.+"
val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15on" % "1.59"
val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15on" % "1.61"
enablePlugins(AutomateHeaderPlugin)
@ -92,7 +93,6 @@ lazy val `crypto-hashsign` = crossProject(JVMPlatform, JSPlatform)
.settings(
commons,
libraryDependencies ++= Seq(
"biz.enef" %%% "slogging" % SloggingV,
"org.scalatest" %%% "scalatest" % ScalatestV % Test
)
)
@ -104,7 +104,7 @@ lazy val `crypto-hashsign` = crossProject(JVMPlatform, JSPlatform)
)
.jsSettings(
npmDependencies in Compile ++= Seq(
"elliptic" -> "6.4.0"
"elliptic" -> "6.4.1"
),
scalaJSModuleKind in Test := ModuleKind.CommonJSModule,
//all JavaScript dependencies will be concatenated to a single file *-jsdeps.js
@ -125,6 +125,7 @@ lazy val `crypto-cipher` = crossProject(JVMPlatform, JSPlatform)
.settings(
commons,
libraryDependencies ++= Seq(
"biz.enef" %%% "slogging" % SloggingV % Test,
"org.scalatest" %%% "scalatest" % ScalatestV % Test
)
)
@ -157,12 +158,12 @@ lazy val `crypto-jwt` = crossProject(JVMPlatform, JSPlatform)
.settings(
commons,
libraryDependencies ++= Seq(
"one.fluence" %%% "codec-circe" % CodecV,
"org.scalatest" %%% "scalatest" % ScalatestV % Test
"one.fluence" %%% "codec-circe" % CodecV,
"org.scalatest" %%% "scalatest" % ScalatestV % Test
)
)
.jsSettings(
fork in Test := false,
fork in Test := false,
scalaJSModuleKind := ModuleKind.CommonJSModule
)
.enablePlugins(AutomateHeaderPlugin)

View File

@ -127,7 +127,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
}
}
object AesCrypt extends slogging.LazyLogging {
object AesCrypt {
def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)

View File

@ -220,7 +220,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
}
}
object AesCrypt extends slogging.LazyLogging {
object AesCrypt {
def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)

View File

@ -15,13 +15,12 @@
* 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_._
import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.signature.Signature
import fluence.crypto.{CryptoError, KeyPair}
import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector

View File

@ -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}

View File

@ -23,6 +23,7 @@ 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}
@ -61,17 +62,48 @@ 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
}
/**
* 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

View 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.eddsa
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)
}
)
}
}

View File

@ -29,9 +29,9 @@ 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) = Random.nextString(10).getBytes
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
@ -125,5 +125,16 @@ class SignatureSpec extends WordSpec with Matchers {
//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 = Ecdsa.signAlgo
val testKeys = algo.generateKeyPair.unsafe(None)
val ecdsa = Ecdsa.ecdsa_secp256k1_sha256
val newKeys = ecdsa.restorePairFromSecret(testKeys.secretKey).extract
testKeys shouldBe newKeys
}
}
}

View 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.eddsa.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
}
}
}

View File

@ -88,7 +88,7 @@ object CryptoJwt {
private val alphabet = Bases.Alphabets.Base64Url
private val strVec = BitsCodecs.base64AlphabetToVector(alphabet).swap
private val strVec = BitsCodecs.Base64.alphabetToVector(alphabet).swap
val signatureCodec: PureCodec[Signature, String] =
PureCodec.liftB[Signature, ByteVector](_.sign, Signature(_)) andThen strVec

View File

@ -69,7 +69,7 @@ object KeyStore {
)
// ByteVector to/from String, with the chosen alphabet
private val vecToStr = BitsCodecs.base64AlphabetToVector(alphabet).swap
private val vecToStr = BitsCodecs.Base64.alphabetToVector(alphabet).swap
implicit val keyPairJsonCodec: PureCodec[KeyPair, Json] =
PureCodec.liftB[KeyPair, (ByteVector, ByteVector)](

View File

@ -1 +1 @@
sbt.version = 1.1.4
sbt.version = 1.2.8

View File

@ -1,15 +1,15 @@
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.4.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.20")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "4.1.0")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")
addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.3.1")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.3.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27")
addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.6.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.10.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.14.0")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4")
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4")