From 0b9e9371f1a53fc94f878dd31324087554f5d52f Mon Sep 17 00:00:00 2001 From: Sergei <251985147+sergei-boiko-trustwallet@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:07:55 +0100 Subject: [PATCH 1/8] fix(public-key): enforce message length validation for ECDSA signatures --- src/PrivateKey.cpp | 3 +-- src/PublicKey.cpp | 24 +++++++++++++++++++++++- src/PublicKey.h | 5 ++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 5f3dfa79454..025e2319313 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -256,10 +256,9 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { } int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - if (digest_size < 32) { + if (digest_size != 32) { return -1; } - assert(digest_size >= 32); return ecdsa_sign_digest(curve, priv_key, digest, sig, pby, is_canonical); } diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index d6b3c715b01..1ecb1f733a6 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -20,7 +20,7 @@ namespace TW { -bool validateSignatureLength(TWPublicKeyType type, const Data& signature) { +static bool validateSignatureLength(TWPublicKeyType type, const Data& signature) { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: @@ -35,6 +35,25 @@ bool validateSignatureLength(TWPublicKeyType type, const Data& signature) { } } +static bool validateMessageLength(TWPublicKeyType type, const Data& message) { + switch (type) { + case TWPublicKeyTypeED25519: + case TWPublicKeyTypeCURVE25519: + case TWPublicKeyTypeED25519Blake2b: + case TWPublicKeyTypeED25519Cardano: + // Technically, we should allow any message size for ed25519. + return true; + case TWPublicKeyTypeSECP256k1: + case TWPublicKeyTypeNIST256p1: + case TWPublicKeyTypeSECP256k1Extended: + case TWPublicKeyTypeNIST256p1Extended: + case TWPublicKeyTypeStarkex: + return message.size() == PublicKey::ecdsaMessageSize; + default: + return false; + } +} + /// Determines if a collection of bytes makes a valid public key of the /// given type. bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { @@ -165,6 +184,9 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { if (!validateSignatureLength(type, signature)) { return false; } + if (!validateMessageLength(type, message)) { + return false; + } switch (type) { case TWPublicKeyTypeSECP256k1: diff --git a/src/PublicKey.h b/src/PublicKey.h index 6ea239c96b1..f87485417e7 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -37,9 +37,12 @@ class PublicKey { /// The number of bytes in a secp256k1 signature. static const size_t secp256k1SignatureSize = 65; - /// Magic number used in V compnent encoding + /// Magic number used in V component encoding static const byte SignatureVOffset = 27; + /// The exact number of bytes in a message that can be signed or verified. + static const size_t ecdsaMessageSize = 32; + /// The public key bytes. Data bytes; From c93a95fc84d38fcf31d1b390d3cffcc455c05c6c Mon Sep 17 00:00:00 2001 From: Sergei <251985147+sergei-boiko-trustwallet@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:00:42 +0100 Subject: [PATCH 2/8] fix(aes): Fix starkex signature verification * Fix `PrivateKey::signAsDER` --- src/PrivateKey.cpp | 4 ++-- src/PrivateKey.h | 2 ++ src/PublicKey.cpp | 11 ++++++++--- src/PublicKey.h | 2 +- tests/interface/TWPublicKeyTests.cpp | 26 ++++++++++++++++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 025e2319313..5da3178b71d 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -256,7 +256,7 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { } int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - if (digest_size != 32) { + if (digest_size != PrivateKey::ecdsaMessageSize) { return -1; } return ecdsa_sign_digest(curve, priv_key, digest, sig, pby, is_canonical); @@ -370,7 +370,7 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data sig(64); bool success = - ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; + ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; if (!success) { return {}; } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index 4e59e95207b..2afad32605a 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -20,6 +20,8 @@ class PrivateKey { static const size_t _size = 32; /// The number of bytes in a Cardano key (two extended ed25519 keys + chain code) static const size_t cardanoKeySize = 2 * 3 * 32; + /// The number of bytes in an ECDSA message digest (e.g., 32-byte hash) that can be signed. + static const size_t ecdsaMessageSize = 32; /// The private key bytes: /// - common case: 'size' bytes diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 1ecb1f733a6..87767df58fb 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -41,14 +41,15 @@ static bool validateMessageLength(TWPublicKeyType type, const Data& message) { case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: case TWPublicKeyTypeED25519Cardano: - // Technically, we should allow any message size for ed25519. + // Allow any message size for ed25519. return true; case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeNIST256p1: case TWPublicKeyTypeSECP256k1Extended: case TWPublicKeyTypeNIST256p1Extended: - case TWPublicKeyTypeStarkex: return message.size() == PublicKey::ecdsaMessageSize; + case TWPublicKeyTypeStarkex: + return message.size() >= PublicKey::ecdsaMessageSize; default: return false; } @@ -205,7 +206,7 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { } case TWPublicKeyTypeCURVE25519: { auto ed25519PublicKey = Data(); - ed25519PublicKey.resize(PublicKey::ed25519Size); + ed25519PublicKey.resize(ed25519Size); curve25519_pk_to_ed25519(ed25519PublicKey.data(), bytes.data()); ed25519PublicKey[31] &= 0x7F; @@ -225,6 +226,10 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { } bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { + if (message.size() != ecdsaMessageSize) { + return false; + } + switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: { diff --git a/src/PublicKey.h b/src/PublicKey.h index f87485417e7..20108ba2a67 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -40,7 +40,7 @@ class PublicKey { /// Magic number used in V component encoding static const byte SignatureVOffset = 27; - /// The exact number of bytes in a message that can be signed or verified. + /// The number of bytes in an ECDSA message digest (e.g., 32-byte hash) that can be verified. static const size_t ecdsaMessageSize = 32; /// The public key bytes. diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 7aa372ffc80..7f567ba3494 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -91,6 +91,19 @@ TEST(TWPublicKeyTests, VerifyInvalidLength) { ASSERT_FALSE(TWPublicKeyVerify(publicKey.get(), signature.get(), message.get())); } +TEST(TWPublicKeyTests, VerifyInvalidMessageLength) { + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); + + // Invalid message length - 31 bytes instead of 32 + const auto invalidMessage = DATA("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb114"); + // Valid 64-byte signature + const auto signature = DATA("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + ASSERT_FALSE(TWPublicKeyVerify(publicKey.get(), signature.get(), invalidMessage.get())); +} + TEST(TWPublicKeyTests, VerifyAsDER) { const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); @@ -108,6 +121,19 @@ TEST(TWPublicKeyTests, VerifyAsDER) { ASSERT_FALSE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); } +TEST(TWPublicKeyTests, VerifyAsDERInvalidMessageLength) { + const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); + + // Invalid message length - 31 bytes instead of 32 + const auto invalidMessage = DATA("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb114"); + // Valid 64-byte signature + const auto signature = DATA("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + ASSERT_FALSE(TWPublicKeyVerifyAsDER(publicKey.get(), signature.get(), invalidMessage.get())); +} + TEST(TWPublicKeyTests, VerifyEd25519) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); From 4f42acabaaabc5d29826a7d2a9670d20179cbc2b Mon Sep 17 00:00:00 2001 From: Sergei <251985147+sergei-boiko-trustwallet@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:11:14 +0100 Subject: [PATCH 3/8] fix(aes): Fix `PrivateKey::signAsDER` --- src/PrivateKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 5da3178b71d..c46069a1274 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -370,7 +370,7 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data sig(64); bool success = - ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; + ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr, nullptr) == 0; if (!success) { return {}; } From 88d8dc3b33871a1a88a124ce0415701a77c0326b Mon Sep 17 00:00:00 2001 From: Sergei <251985147+sergei-boiko-trustwallet@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:41:54 +0100 Subject: [PATCH 4/8] fix(aes): Fix `PrivateKey::signAsDER` --- src/PrivateKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index c46069a1274..55192171203 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -370,7 +370,7 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data sig(64); bool success = - ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr, nullptr) == 0; + ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), sig.data(), nullptr, nullptr) == 0; if (!success) { return {}; } From ae91e9f4ebf92df8271a462f1b9ac91dc5d19090 Mon Sep 17 00:00:00 2001 From: Sergei <251985147+sergei-boiko-trustwallet@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:48:22 +0100 Subject: [PATCH 5/8] fix(public-key): update starkex message size validation and add tests for long digest signing --- src/PublicKey.cpp | 3 ++- src/PublicKey.h | 3 +++ tests/common/PrivateKeyTests.cpp | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 87767df58fb..a8e97abbd24 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -49,7 +49,8 @@ static bool validateMessageLength(TWPublicKeyType type, const Data& message) { case TWPublicKeyTypeNIST256p1Extended: return message.size() == PublicKey::ecdsaMessageSize; case TWPublicKeyTypeStarkex: - return message.size() >= PublicKey::ecdsaMessageSize; + // Digest shorter than 32 bytes will be left-padded with zeros before verification. + return message.size() <= PublicKey::starkexMessageMaxSize; default: return false; } diff --git a/src/PublicKey.h b/src/PublicKey.h index 20108ba2a67..df5b9b13895 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -42,6 +42,9 @@ class PublicKey { /// The number of bytes in an ECDSA message digest (e.g., 32-byte hash) that can be verified. static const size_t ecdsaMessageSize = 32; + /// The maximum number of bytes in a message that can be verified with a starkex public key. + /// Digest shorter than 32 bytes will be left-padded with zeros before verification. + static const size_t starkexMessageMaxSize = 32; /// The public key bytes. Data bytes; diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index fccfebcff6f..eba15ad8b9f 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -323,6 +323,24 @@ TEST(PrivateKey, SignShortDigest) { } } +TEST(PrivateKey, SignLongDigest) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + Data shortDigest = TW::data("123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012"); + { + Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1); + EXPECT_EQ(actual.size(), 0ul); + } + { + Data actual = privateKey.sign(shortDigest, TWCurveNIST256p1); + EXPECT_EQ(actual.size(), 0ul); + } + { + Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1, isCanonical); + EXPECT_EQ(actual.size(), 0ul); + } +} + TEST(PrivateKey, SignWithDifferentCurveWorks) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); // Using the deprecated constructor without specifying a curve From 14e65c9efdf999bf74eddb9dc13387d7dcb9117d Mon Sep 17 00:00:00 2001 From: nikhil-gupta-tw <252030465+nikhil-gupta-tw@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:31:15 +0530 Subject: [PATCH 6/8] Fix buffer over-read --- src/PrivateKey.cpp | 2 +- src/PublicKey.cpp | 5 +++++ tests/common/PrivateKeyTests.cpp | 6 ++++++ tests/common/PublicKeyTests.cpp | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 5f3dfa79454..27214e61488 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -371,7 +371,7 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data sig(64); bool success = - ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; + ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), sig.data(), nullptr, nullptr) == 0; if (!success) { return {}; } diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index d6b3c715b01..26b0880e2fc 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -20,6 +20,8 @@ namespace TW { +static constexpr size_t kEcdsaMinDigestSize = 32; + bool validateSignatureLength(TWPublicKeyType type, const Data& signature) { switch (type) { case TWPublicKeyTypeSECP256k1: @@ -169,9 +171,11 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: + if (message.size() < kEcdsaMinDigestSize) { return false; } return ecdsa_verify_digest(&secp256k1, bytes.data(), signature.data(), message.data()) == 0; case TWPublicKeyTypeNIST256p1: case TWPublicKeyTypeNIST256p1Extended: + if (message.size() < kEcdsaMinDigestSize) { return false; } return ecdsa_verify_digest(&nist256p1, bytes.data(), signature.data(), message.data()) == 0; case TWPublicKeyTypeED25519: return ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; @@ -206,6 +210,7 @@ bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: { + if (message.size() < kEcdsaMinDigestSize) { return false; } Data sig(64); int ret = ecdsa_sig_from_der(signature.data(), signature.size(), sig.data()); if (ret) { diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index fccfebcff6f..5841feb9e65 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -372,4 +372,10 @@ TEST(PrivateKey, SignWithDifferentCurveThrows) { } } +TEST(PrivateKey, SignAsDERRejectsShortDigest) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto shortDigest = parse_hex("0102030405060708"); + EXPECT_TRUE(privateKey.signAsDER(shortDigest).empty()); +} + } // namespace TW::tests diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index d69e1bc7952..30c0c5a07e2 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -366,3 +366,19 @@ TEST(PublicKeyTests, isValidED25519) { EXPECT_FALSE(PublicKey::isValid(parse_hex("0101beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); EXPECT_FALSE(PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1).isValidED25519()); } + +TEST(PublicKeyTests, VerifyRejectsShortDigest) { + const auto publicKey = PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1); + const auto validSig = parse_hex("0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); + const auto shortDigest = parse_hex("0102030405060708"); + + EXPECT_FALSE(publicKey.verify(validSig, shortDigest)); +} + +TEST(PublicKeyTests, VerifyAsDERRejectsShortDigest) { + const auto publicKey = PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1); + const auto derSig = parse_hex("304402200f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c102202071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); + const auto shortDigest = parse_hex("0102030405060708"); + + EXPECT_FALSE(publicKey.verifyAsDER(derSig, shortDigest)); +} From 85ceb2d09428ba81c7156b1ea5d2d55104809e5c Mon Sep 17 00:00:00 2001 From: nikhil-gupta-tw <252030465+nikhil-gupta-tw@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:23:04 +0530 Subject: [PATCH 7/8] Addresses copilot comment --- tests/common/PublicKeyTests.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index 30c0c5a07e2..879ee737373 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -382,3 +382,12 @@ TEST(PublicKeyTests, VerifyAsDERRejectsShortDigest) { EXPECT_FALSE(publicKey.verifyAsDER(derSig, shortDigest)); } + +TEST(PublicKeyTests, VerifyNist256p1RejectsShortDigest) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveNIST256p1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + const auto validSig = parse_hex("0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); + const auto shortDigest = parse_hex("0102030405060708"); + + EXPECT_FALSE(publicKey.verify(validSig, shortDigest)); +} From 7f2580dc3e0d1bb16865f0ad2b791d8924c7d063 Mon Sep 17 00:00:00 2001 From: nikhil-gupta-tw <252030465+nikhil-gupta-tw@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:11:59 +0530 Subject: [PATCH 8/8] Add signature size verification --- src/PublicKey.cpp | 3 +++ src/PublicKey.h | 5 +++++ tests/common/PublicKeyTests.cpp | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index a8e97abbd24..9cee86379ed 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -227,6 +227,9 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { } bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { + if (signature.size() < derSignatureMinSize || signature.size() > derSignatureMaxSize) { + return false; + } if (message.size() != ecdsaMessageSize) { return false; } diff --git a/src/PublicKey.h b/src/PublicKey.h index df5b9b13895..d116be3ae0a 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -46,6 +46,11 @@ class PublicKey { /// Digest shorter than 32 bytes will be left-padded with zeros before verification. static const size_t starkexMessageMaxSize = 32; + /// The minimum number of bytes in a valid DER-encoded ECDSA signature. + static const size_t derSignatureMinSize = 2; + /// The maximum number of bytes in a valid DER-encoded ECDSA signature. + static const size_t derSignatureMaxSize = 72; + /// The public key bytes. Data bytes; diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index 879ee737373..c903f66a054 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -383,6 +383,15 @@ TEST(PublicKeyTests, VerifyAsDERRejectsShortDigest) { EXPECT_FALSE(publicKey.verifyAsDER(derSig, shortDigest)); } +TEST(PublicKeyTests, VerifyAsDERRejectsInvalidSignatureSize) { + const auto publicKey = PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1); + const auto digest = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + + EXPECT_FALSE(publicKey.verifyAsDER(Data(), digest)); + EXPECT_FALSE(publicKey.verifyAsDER(parse_hex("30"), digest)); + EXPECT_FALSE(publicKey.verifyAsDER(Data(73, 0x30), digest)); +} + TEST(PublicKeyTests, VerifyNist256p1RejectsShortDigest) { const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveNIST256p1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1);