Skip to content

Commit a81cd96

Browse files
sipaPieter Wuille
authored andcommitted
Only create signatures with even S, and verification mode to check.
To fix a minor malleability found by Sergio Lerner (reported here: https://bitcointalk.org/index.php?topic=8392.msg1245898#msg1245898) The problem is that if (R,S) is a valid ECDSA signature for a given message and public key, (R,-S) is also valid. Modulo N (the order of the secp256k1 curve), this means that both (R,S) and (R,N-S) are valid. Given that N is odd, S and N-S have a different lowest bit. We solve the problem by forcing signatures to have an even S value, excluding one of the alternatives. This commit just changes the signing code to always produce even S values, and adds a verification mode to check it. This code is not enabled anywhere yet. Existing tests in key_tests.cpp verify that the produced signatures are still valid.
1 parent 4323bfe commit a81cd96

File tree

4 files changed

+43
-17
lines changed

4 files changed

+43
-17
lines changed

src/key.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,26 @@ class CECKey {
194194
}
195195

196196
bool Sign(const uint256 &hash, std::vector<unsigned char>& vchSig) {
197+
vchSig.clear();
198+
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
199+
if (sig == NULL)
200+
return false;
201+
if (BN_is_odd(sig->s)) {
202+
// enforce even S values, by negating the value (modulo the order) if odd
203+
BN_CTX *ctx = BN_CTX_new();
204+
BN_CTX_start(ctx);
205+
const EC_GROUP *group = EC_KEY_get0_group(pkey);
206+
BIGNUM *order = BN_CTX_get(ctx);
207+
EC_GROUP_get_order(group, order, ctx);
208+
BN_sub(sig->s, order, sig->s);
209+
BN_CTX_end(ctx);
210+
BN_CTX_free(ctx);
211+
}
197212
unsigned int nSize = ECDSA_size(pkey);
198213
vchSig.resize(nSize); // Make sure it is big enough
199-
assert(ECDSA_sign(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], &nSize, pkey));
214+
unsigned char *pos = &vchSig[0];
215+
nSize = i2d_ECDSA_SIG(sig, &pos);
216+
ECDSA_SIG_free(sig);
200217
vchSig.resize(nSize); // Shrink to fit actual size
201218
return true;
202219
}

src/script.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,10 @@ const char* GetOpName(opcodetype opcode)
227227
}
228228
}
229229

230-
bool IsCanonicalPubKey(const valtype &vchPubKey) {
230+
bool IsCanonicalPubKey(const valtype &vchPubKey, unsigned int flags) {
231+
if (!(flags & SCRIPT_VERIFY_STRICTENC))
232+
return true;
233+
231234
if (vchPubKey.size() < 33)
232235
return error("Non-canonical public key: too short");
233236
if (vchPubKey[0] == 0x04) {
@@ -242,7 +245,10 @@ bool IsCanonicalPubKey(const valtype &vchPubKey) {
242245
return true;
243246
}
244247

245-
bool IsCanonicalSignature(const valtype &vchSig) {
248+
bool IsCanonicalSignature(const valtype &vchSig, unsigned int flags) {
249+
if (!(flags & SCRIPT_VERIFY_STRICTENC))
250+
return true;
251+
246252
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
247253
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
248254
// Where R and S are not negative (their first byte has its highest bit not set), and not
@@ -286,6 +292,11 @@ bool IsCanonicalSignature(const valtype &vchSig) {
286292
if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
287293
return error("Non-canonical signature: S value excessively padded");
288294

295+
if (flags & SCRIPT_VERIFY_EVEN_S) {
296+
if (S[nLenS-1] & 1)
297+
return error("Non-canonical signature: S value odd");
298+
}
299+
289300
return true;
290301
}
291302

@@ -302,7 +313,6 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
302313
if (script.size() > 10000)
303314
return false;
304315
int nOpCount = 0;
305-
bool fStrictEncodings = flags & SCRIPT_VERIFY_STRICTENC;
306316

307317
try
308318
{
@@ -841,9 +851,8 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
841851
// Drop the signature, since there's no way for a signature to sign itself
842852
scriptCode.FindAndDelete(CScript(vchSig));
843853

844-
bool fSuccess = (!fStrictEncodings || (IsCanonicalSignature(vchSig) && IsCanonicalPubKey(vchPubKey)));
845-
if (fSuccess)
846-
fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType, flags);
854+
bool fSuccess = IsCanonicalSignature(vchSig, flags) && IsCanonicalPubKey(vchPubKey, flags) &&
855+
CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType, flags);
847856

848857
popstack(stack);
849858
popstack(stack);
@@ -903,9 +912,8 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
903912
valtype& vchPubKey = stacktop(-ikey);
904913

905914
// Check signature
906-
bool fOk = (!fStrictEncodings || (IsCanonicalSignature(vchSig) && IsCanonicalPubKey(vchPubKey)));
907-
if (fOk)
908-
fOk = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType, flags);
915+
bool fOk = IsCanonicalSignature(vchSig, flags) && IsCanonicalPubKey(vchPubKey, flags) &&
916+
CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType, flags);
909917

910918
if (fOk) {
911919
isig++;

src/script.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ enum
3232
enum
3333
{
3434
SCRIPT_VERIFY_NONE = 0,
35-
SCRIPT_VERIFY_P2SH = (1U << 0),
36-
SCRIPT_VERIFY_STRICTENC = (1U << 1),
37-
SCRIPT_VERIFY_NOCACHE = (1U << 2),
35+
SCRIPT_VERIFY_P2SH = (1U << 0), // evaluate P2SH (BIP16) subscripts
36+
SCRIPT_VERIFY_STRICTENC = (1U << 1), // enforce strict conformance to DER and SEC2 for signatures and pubkeys
37+
SCRIPT_VERIFY_EVEN_S = (1U << 2), // enforce even S values in signatures (depends on STRICTENC)
38+
SCRIPT_VERIFY_NOCACHE = (1U << 3), // do not store results in signature cache (but do query it)
3839
};
3940

4041
enum txnouttype
@@ -665,8 +666,8 @@ class CScriptCompressor
665666
}
666667
};
667668

668-
bool IsCanonicalPubKey(const std::vector<unsigned char> &vchPubKey);
669-
bool IsCanonicalSignature(const std::vector<unsigned char> &vchSig);
669+
bool IsCanonicalPubKey(const std::vector<unsigned char> &vchPubKey, unsigned int flags);
670+
bool IsCanonicalSignature(const std::vector<unsigned char> &vchSig, unsigned int flags);
670671

671672
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, unsigned int flags, int nHashType);
672673
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet);

src/test/canonical_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(script_canon)
6464
string test = tv.get_str();
6565
if (IsHex(test)) {
6666
std::vector<unsigned char> sig = ParseHex(test);
67-
BOOST_CHECK_MESSAGE(IsCanonicalSignature(sig), test);
67+
BOOST_CHECK_MESSAGE(IsCanonicalSignature(sig, SCRIPT_VERIFY_STRICTENC), test);
6868
BOOST_CHECK_MESSAGE(IsCanonicalSignature_OpenSSL(sig), test);
6969
}
7070
}
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(script_noncanon)
7878
string test = tv.get_str();
7979
if (IsHex(test)) {
8080
std::vector<unsigned char> sig = ParseHex(test);
81-
BOOST_CHECK_MESSAGE(!IsCanonicalSignature(sig), test);
81+
BOOST_CHECK_MESSAGE(!IsCanonicalSignature(sig, SCRIPT_VERIFY_STRICTENC), test);
8282
BOOST_CHECK_MESSAGE(!IsCanonicalSignature_OpenSSL(sig), test);
8383
}
8484
}

0 commit comments

Comments
 (0)