Skip to content

Commit 1f5993c

Browse files
committed
Fix - made public key from signature recover method safer
1 parent 2905cbd commit 1f5993c

File tree

2 files changed

+31
-19
lines changed

2 files changed

+31
-19
lines changed

src/main/scala/io/iohk/ethereum/crypto/ECDSASignature.scala

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters
99
import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
1010
import org.bouncycastle.math.ec.{ECCurve, ECPoint}
1111

12+
import scala.util.Try
13+
1214
object ECDSASignature {
1315

1416
val SLength = 32
@@ -107,26 +109,28 @@ object ECDSASignature {
107109
chainId: Option[Byte],
108110
messageHash: Array[Byte]
109111
): Option[Array[Byte]] = {
110-
val order = curve.getCurve.getOrder
111-
//ignore case when x = r + order because it is negligibly improbable
112-
//says: https://github.com/paritytech/rust-secp256k1/blob/f998f9a8c18227af200f0f7fdadf8a6560d391ff/depend/secp256k1/src/ecdsa_impl.h#L282
113-
val xCoordinate = r
114-
val curveFp = curve.getCurve.asInstanceOf[ECCurve.Fp]
115-
val prime = curveFp.getQ
116-
117-
getRecoveredPointSign(recId, chainId).flatMap { recovery =>
118-
if (xCoordinate.compareTo(prime) < 0) {
119-
val R = constructPoint(xCoordinate, recovery)
120-
if (R.multiply(order).isInfinity) {
121-
val e = BigInt(1, messageHash)
122-
val rInv = r.modInverse(order)
123-
//Q = r^(-1)(sR - eG)
124-
val q = R.multiply(s.bigInteger).subtract(curve.getG.multiply(e.bigInteger)).multiply(rInv.bigInteger)
125-
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
126-
Some(q.getEncoded(false).tail)
112+
Try {
113+
val order = curve.getCurve.getOrder
114+
//ignore case when x = r + order because it is negligibly improbable
115+
//says: https://github.com/paritytech/rust-secp256k1/blob/f998f9a8c18227af200f0f7fdadf8a6560d391ff/depend/secp256k1/src/ecdsa_impl.h#L282
116+
val xCoordinate = r
117+
val curveFp = curve.getCurve.asInstanceOf[ECCurve.Fp]
118+
val prime = curveFp.getQ
119+
120+
getRecoveredPointSign(recId, chainId).flatMap { recovery =>
121+
if (xCoordinate.compareTo(prime) < 0) {
122+
val R = constructPoint(xCoordinate, recovery)
123+
if (R.multiply(order).isInfinity) {
124+
val e = BigInt(1, messageHash)
125+
val rInv = r.modInverse(order)
126+
//Q = r^(-1)(sR - eG)
127+
val q = R.multiply(s.bigInteger).subtract(curve.getG.multiply(e.bigInteger)).multiply(rInv.bigInteger)
128+
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
129+
Some(q.getEncoded(false).tail)
130+
} else None
127131
} else None
128-
} else None
129-
}
132+
}
133+
}.toOption.flatten
130134
}
131135

132136
private def constructPoint(xCoordinate: BigInt, recId: Int): ECPoint = {

src/test/scala/io/iohk/ethereum/crypto/ECDSASignatureSpec.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.iohk.ethereum.crypto
22

33
import akka.util.ByteString
44
import io.iohk.ethereum.nodebuilder.SecureRandomBuilder
5+
import io.iohk.ethereum.utils.ByteStringUtils
56
import org.scalacheck.Arbitrary
67
import org.scalacheck.Arbitrary.arbitrary
78
import org.bouncycastle.crypto.params.ECPublicKeyParameters
@@ -35,6 +36,13 @@ class ECDSASignatureSpec extends AnyFlatSpec with Matchers with ScalaCheckProper
3536
sig.publicKey(bytesToSign).isEmpty shouldBe true
3637
}
3738

39+
it should "fail public key recover without throwing when signature is bad (Invalid point compression)" in {
40+
val sig = ECDSASignature(ByteStringUtils.string2hash("149a2046f51f5d043633664d76eef4f99cdba8e53851dcda57224dfe8770f98a"), ByteStringUtils.string2hash("a8898478e9aae9fadb71c7ab5451d47d2efa4199fc26ecc1da62ce8fb77e06f1"), 28.toByte)
41+
val messageHash = ByteStringUtils.string2hash("a1ede9cdf0b6fe37a384b265dce6b74a7464f11799dcee022f628450a19cf4eb")
42+
43+
sig.publicKey(messageHash).isEmpty shouldBe true
44+
}
45+
3846
it should "sign message and recover public key" in {
3947
forAll(arbitrary[Array[Byte]], Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) {
4048
(message, keys) =>

0 commit comments

Comments
 (0)