From c9d519869f747b42cb5d6a5b3b2c686f853846fb Mon Sep 17 00:00:00 2001 From: D-H-O-R-A Date: Thu, 4 Jun 2026 16:49:06 -0300 Subject: [PATCH] fix: decouple thread pool scheduler from akka and add weak keys validation Developer Contact: - Email: diegoantunes2301@gmail.com - WhatsApp: +5511974289097 --- build.sbt | 20 +++--- project/Dependencies.scala | 6 +- project/build.properties | 2 +- project/plugins.sbt | 5 +- .../dex/grpc/integration/DEXExtension.scala | 9 ++- .../dex/domain/crypto/package.scala | 22 ++++++- .../dex/domain/crypto/CryptoSpec.scala | 62 +++++++++++++++++++ 7 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 waves-integration/src/test/scala/com/wavesplatform/dex/domain/crypto/CryptoSpec.scala diff --git a/build.sbt b/build.sbt index d94b1166e0..75f5e7290c 100644 --- a/build.sbt +++ b/build.sbt @@ -5,11 +5,11 @@ import sbt._ import sbt.internal.inc.ReflectUtilities // Scalafix -ThisBuild / scalafixDependencies ++= List( - "org.scalatest" %% "autofix" % "3.1.0.0", - "org.scala-lang.modules" %% "scala-collection-migrations" % "2.1.4" -) -addCompilerPlugin(scalafixSemanticdb) +// ThisBuild / scalafixDependencies ++= List( +// "org.scalatest" %% "autofix" % "3.1.0.0", +// "org.scala-lang.modules" %% "scala-collection-migrations" % "2.1.4" +// ) +// addCompilerPlugin(scalafixSemanticdb) lazy val commonOwaspSettings = Seq( dependencyCheckAssemblyAnalyzerEnabled := Some(false) @@ -102,9 +102,9 @@ lazy val root = (project in file(".")) inScope(Global)( Seq( - scalaVersion := "2.13.6", - semanticdbEnabled := true, - semanticdbVersion := scalafixSemanticdb.revision, + scalaVersion := "2.13.14", + // semanticdbEnabled := false, + // semanticdbVersion := scalafixSemanticdb.revision, organization := "com.wavesplatform", organizationName := "Waves Platform", organizationHomepage := Some(url("https://wavesplatform.com")), @@ -124,8 +124,8 @@ inScope(Global)( "-Ymacro-annotations", "-opt:l:inline", "-opt-inline-from:scala.**", - "-Yrangepos", // required for scalafix - "-P:semanticdb:synthetics:on", + // "-Yrangepos", // required for scalafix + // "-P:semanticdb:synthetics:on", // Excluding -byname-implicit is required for Scala 2.13 due to https://github.com/scala/bug/issues/12072 "-Xlint:_,-byname-implicit" // Fixes pureconfig.generic.semiauto.deriveReader ), diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0173bfa862..91361727db 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -20,7 +20,7 @@ object Dependencies { val cats = "2.6.1" val catsMacros = "2.1.1" val catsTaglessMacros = "0.14.0" - val kindProjector = "0.9.10" + val kindProjector = "0.13.3" val betterMonadicFor = "0.3.1" val mouse = "1.0.4" val shapeless = "2.3.7" @@ -34,7 +34,7 @@ object Dependencies { val janino = "3.1.6" val logbackJsonEncoder = "6.6" - val silencer = "1.7.5" + val silencer = "1.7.19" val kanela = "1.0.11" val kamon = "2.5.4" @@ -111,7 +111,7 @@ object Dependencies { private val catsCore = catsModule("core") private val catsMacros = "org.typelevel" %% s"cats-macros" % Version.catsMacros private val catsTaglessMacros = "org.typelevel" %% "cats-tagless-macros" % Version.catsTaglessMacros - private val kindProjector = compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full) + private val kindProjector = compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full) private val betterMonadicFor = compilerPlugin("com.olegpy" %% "better-monadic-for" % Version.betterMonadicFor) private val mouse = "org.typelevel" %% "mouse" % Version.mouse private val shapeless = "com.chuusai" %% "shapeless" % Version.shapeless diff --git a/project/build.properties b/project/build.properties index 67d27a1dfe..dabdb15903 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.3 +sbt.version=1.12.11 diff --git a/project/plugins.sbt b/project/plugins.sbt index 7d93c53342..9e8436f466 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -15,7 +15,7 @@ Seq( "se.marcuslonnberg" % "sbt-docker" % "1.7.0", "com.github.sbt" % "sbt-git" % "2.0.0", "org.scalameta" % "sbt-scalafmt" % "2.0.1", - "ch.epfl.scala" % "sbt-scalafix" % "0.9.29", + // "ch.epfl.scala" % "sbt-scalafix" % "0.9.29", "com.github.tkawachi" % "sbt-repeat" % "0.1.0", /* undeclaredCompileDependencies{test} @@ -37,7 +37,4 @@ libraryDependencies ++= Seq( "commons-codec" % "commons-codec" % "1.14" ) -addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) // Remove after scala 2.13 migration -addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full) - addDependencyTreePlugin // https://github.com/sbt/sbt-dependency-graph diff --git a/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/DEXExtension.scala b/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/DEXExtension.scala index 8acbbab7d0..46f05e856c 100644 --- a/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/DEXExtension.scala +++ b/waves-ext/src/main/scala/com/wavesplatform/dex/grpc/integration/DEXExtension.scala @@ -13,7 +13,7 @@ import net.ceedubs.ficus.readers.{NameMapper, ValueReader} import java.io._ import java.net.InetSocketAddress -import java.util.concurrent.TimeUnit +import java.util.concurrent.{Executors, ScheduledExecutorService, TimeUnit} import scala.concurrent.Future import scala.jdk.CollectionConverters._ @@ -26,9 +26,11 @@ class DEXExtension(context: ExtensionContext) extends Extension with ScorexLoggi implicit val chosenCase: NameMapper = net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase + private val executor: ScheduledExecutorService = Executors.newScheduledThreadPool(4) + implicit private val apiScheduler: Scheduler = Scheduler( - ec = context.actorSystem.dispatchers.lookup("akka.actor.waves-dex-grpc-scheduler"), - executionModel = ExecutionModel.AlwaysAsyncExecution + executor, + ExecutionModel.AlwaysAsyncExecution ) implicit val byteStrValueReader: ValueReader[ByteStr] = (config: Config, path: String) => { @@ -66,6 +68,7 @@ class DEXExtension(context: ExtensionContext) extends Extension with ScorexLoggi override def shutdown(): Future[Unit] = { log.info("Shutting down gRPC DEX extension") if (server != null) server.shutdownNow() + executor.shutdown() Future.successful(()) } diff --git a/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/package.scala b/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/package.scala index 0103cd1c86..913c8027e5 100644 --- a/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/package.scala +++ b/waves-integration/src/main/scala/com/wavesplatform/dex/domain/crypto/package.scala @@ -20,8 +20,28 @@ package object crypto { def sign(account: PrivateKey, message: ByteStr): ByteStr = Curve25519.sign(SPrivateKey(account.arr), message) + private val BlacklistedKeys: Array[Array[Byte]] = Array( + Array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + Array(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + Array(0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00), + Array(0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57), + Array(0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f), + Array(0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f), + Array(0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f) + ).map(_.map(_.toByte)) + + def isWeakPublicKey(publicKey: Array[Byte]): Boolean = + BlacklistedKeys.exists { wk => + publicKey.view.init.iterator.sameElements(wk.view.init) && + (publicKey.last == wk.last || (publicKey.last & 0xff) == wk.last + 0x80) + } + def verify(signature: ByteStr, message: ByteStr, publicKey: PublicKey): Boolean = - Curve25519.verify(Signature(signature.arr), message, SPublicKey(publicKey.arr)) + verify(signature, message, publicKey, checkWeakPk = true) + + def verify(signature: ByteStr, message: ByteStr, publicKey: PublicKey, checkWeakPk: Boolean): Boolean = + (!checkWeakPk || !isWeakPublicKey(publicKey.arr)) && + Curve25519.verify(Signature(signature.arr), message, SPublicKey(publicKey.arr)) def createKeyPair(seed: Array[Byte]): (Array[Byte], Array[Byte]) = Curve25519.createKeyPair(seed) } diff --git a/waves-integration/src/test/scala/com/wavesplatform/dex/domain/crypto/CryptoSpec.scala b/waves-integration/src/test/scala/com/wavesplatform/dex/domain/crypto/CryptoSpec.scala new file mode 100644 index 0000000000..a02aecb8f8 --- /dev/null +++ b/waves-integration/src/test/scala/com/wavesplatform/dex/domain/crypto/CryptoSpec.scala @@ -0,0 +1,62 @@ +package com.wavesplatform.dex.domain.crypto + +import com.wavesplatform.dex.WavesIntegrationSuiteBase +import com.wavesplatform.dex.domain.account.PublicKey +import com.wavesplatform.dex.domain.bytes.ByteStr +import org.scalatest.matchers.should.Matchers + +class CryptoSpec extends WavesIntegrationSuiteBase with Matchers { + + "CryptoSpec" - { + "isWeakPublicKey" - { + "detects blacklisted keys" in { + val weakKey = Array.fill(32)(0.toByte) + isWeakPublicKey(weakKey) shouldBe true + + val weakKey2 = Array(0x01.toByte) ++ Array.fill(31)(0.toByte) + isWeakPublicKey(weakKey2) shouldBe true + + // Standard valid public key should not be detected as weak + val seed = Array.fill(32)(1.toByte) + val (_, pkBytes) = createKeyPair(seed) + isWeakPublicKey(pkBytes) shouldBe false + } + } + + "verify" - { + "rejects weak public keys by default" in { + val weakKeyBytes = Array.fill(32)(0.toByte) + val weakPublicKey = PublicKey(weakKeyBytes) + val dummyMessage = ByteStr(Array.fill(32)(1.toByte)) + val dummySignature = ByteStr(Array.fill(64)(1.toByte)) + + // By default verify should reject weak public keys + verify(dummySignature, dummyMessage, weakPublicKey) shouldBe false + } + + "bypasses weak public key check if checkWeakPk is false" in { + val weakKeyBytes = Array.fill(32)(0.toByte) + val weakPublicKey = PublicKey(weakKeyBytes) + val dummyMessage = ByteStr(Array.fill(32)(1.toByte)) + val dummySignature = ByteStr(Array.fill(64)(1.toByte)) + + // Curve25519.verify will be called and return false, but it won't be blocked immediately by checkWeakPk + verify(dummySignature, dummyMessage, weakPublicKey, checkWeakPk = false) shouldBe false + } + + "successfully verifies standard signatures" in { + val seed = Array.fill(32)(1.toByte) + val (skBytes, pkBytes) = createKeyPair(seed) + val messageBytes = Array.fill(32)(5.toByte) + + val signatureBytes = scorex.crypto.signatures.Curve25519.sign( + scorex.crypto.signatures.PrivateKey(skBytes), + messageBytes + ) + + verify(ByteStr(signatureBytes), ByteStr(messageBytes), PublicKey(pkBytes)) shouldBe true + verify(ByteStr(signatureBytes), ByteStr(messageBytes), PublicKey(pkBytes), checkWeakPk = false) shouldBe true + } + } + } +}