diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -3415,6 +3415,69 @@ "CLEANSTACK", "v0 P2SH-P2WPKH Segwit Recovery spending a non-P2SH output" ], +[ + "0x09 0x300602010702010701 0x21 0x03a7bcb86f12d0635c850b6f0c945e4b4fcb400092a74b8d7e83275eb562d9fbb6", + "CHECKSIG", + "STRICTENC", + "OK", + "recovered-pubkey CHECKSIG 7,7 (wrapped r)" +], +[ + "0x40 0x303d021d776879206d757374207765207375666665722077697468206563647361021c2121212121212121212121212121212121212121212121212121212101 0x21 0x02da78d331f65fd308ed7afbc80d48c908a83050276cb9bdc1fa414bfea4511570", + "CHECKSIG", + "STRICTENC", + "OK", + "recovered-pubkey CHECKSIG with 63-byte DER" +], +[ + "0x40 0x303d021d776879206d757374207765207375666665722077697468206563647361021c2121212121212121212121212121212121212121212121212121212101 0x21 0x02da78d331f65fd308ed7afbc80d48c908a83050276cb9bdc1fa414bfea4511570", + "CHECKSIG", + "SCHNORR,STRICTENC", + "OK", + "recovered-pubkey CHECKSIG with 63-byte DER; schnorrflag" +], +[ + "0x41 0x303e021d776879206d757374207765207375666665722077697468206563647361021d212121212121212121212121212121212121212121212121212121212101 0x21 0x03f5d556a48a11a677f1a8eb0771f6cd11b1bcf378478c586d54f18634521b833e", + "CHECKSIG", + "STRICTENC", + "OK", + "recovered-pubkey CHECKSIG with 64-byte DER" +], +[ + "0x41 0x303e021d776879206d757374207765207375666665722077697468206563647361021d212121212121212121212121212121212121212121212121212121212101 0x21 0x03f5d556a48a11a677f1a8eb0771f6cd11b1bcf378478c586d54f18634521b833e", + "CHECKSIG", + "SCHNORR,STRICTENC", + "EVAL_FALSE", + "recovered-pubkey CHECKSIG with 64-byte DER; schnorrflag" +], +[ + "0x42 0x303f021d776879206d757374207765207375666665722077697468206563647361021e21212121212121212121212121212121212121212121212121212121212101 0x21 0x02224d851056412fbe03d1e2e8ec9030f5ee99c7b403e9743f261625eb8dd22922", + "CHECKSIG", + "STRICTENC", + "OK", + "recovered-pubkey CHECKSIG with 65-byte DER" +], +[ + "0x42 0x303f021d776879206d757374207765207375666665722077697468206563647361021e21212121212121212121212121212121212121212121212121212121212101 0x21 0x02224d851056412fbe03d1e2e8ec9030f5ee99c7b403e9743f261625eb8dd22922", + "CHECKSIG", + "SCHNORR,STRICTENC", + "OK", + "recovered-pubkey CHECKSIG with 65-byte DER; schnorrflag" +], +[ + "0 0x41 0x303e021d776879206d757374207765207375666665722077697468206563647361021d212121212121212121212121212121212121212121212121212121212101 0x21 0x036cd1f91735dda2d984cfbc17ab0e9e7d754d7e4e1fceb691751cdd5c26b0aecc", + "1 SWAP 1 CHECKMULTISIG", + "STRICTENC", + "OK", + "recovered-pubkey CHECKMULTISIG with 64-byte DER" +], +[ + "0 0x41 0x303e021d776879206d757374207765207375666665722077697468206563647361021d212121212121212121212121212121212121212121212121212121212101 0x21 0x036cd1f91735dda2d984cfbc17ab0e9e7d754d7e4e1fceb691751cdd5c26b0aecc", + "1 SWAP 1 CHECKMULTISIG", + "SCHNORR,STRICTENC", + "SIG_BADLENGTH", + "recovered-pubkey CHECKMULTISIG with 64-byte DER; schnorrflag" +], ["CHECKSEQUENCEVERIFY tests"], ["", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "INVALID_STACK_OPERATION", "CSV automatically fails on an empty stack"], diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -390,6 +390,82 @@ return *this; } + TestBuilder &PushECDSARecoveredPubKey( + const std::vector &rdata, const std::vector &sdata, + SigHashType sigHashType = SigHashType(), Amount amount = Amount::zero(), + uint32_t sigFlags = SCRIPT_ENABLE_SIGHASH_FORKID) { + // This calculates a pubkey to verify with a given ECDSA transaction + // signature. + uint256 hash = SignatureHash(script, CTransaction(spendTx), 0, + sigHashType, amount, nullptr, sigFlags); + + assert(rdata.size() <= 32); + assert(sdata.size() <= 32); + + // Our strategy: make a 'key recovery' signature, and just try all the + // recovery IDs. If none of them work then this means the 'r' value + // doesn't have any corresponding point, and the caller should pick a + // different r. + std::vector vchSig(65, 0); + std::copy(rdata.begin(), rdata.end(), + vchSig.begin() + (33 - rdata.size())); + std::copy(sdata.begin(), sdata.end(), + vchSig.begin() + (65 - sdata.size())); + + CPubKey key; + for (uint8_t recid : {0, 1, 2, 3}) { + vchSig[0] = 31 + recid; + if (key.RecoverCompact(hash, vchSig)) { + // found a match + break; + } + } + if (!key.IsValid()) { + throw std::runtime_error( + std::string("Could not generate pubkey for ") + HexStr(rdata)); + } + std::vector vchKey(key.begin(), key.end()); + + DoPush(vchKey); + return *this; + } + + TestBuilder & + PushECDSASigFromParts(const std::vector &rdata, + const std::vector &sdata, + SigHashType sigHashType = SigHashType()) { + // Constructs a DER signature out of variable-length r and s arrays & + // adds hashtype byte. + assert(rdata.size() <= 32); + assert(sdata.size() <= 32); + assert(rdata.size() > 0); + assert(sdata.size() > 0); + assert(rdata[0] != 0); + assert(sdata[0] != 0); + std::vector vchSig{0x30, 0x00, 0x02}; + if (rdata[0] & 0x80) { + vchSig.push_back(rdata.size() + 1); + vchSig.push_back(0); + vchSig.insert(vchSig.end(), rdata.begin(), rdata.end()); + } else { + vchSig.push_back(rdata.size()); + vchSig.insert(vchSig.end(), rdata.begin(), rdata.end()); + } + vchSig.push_back(0x02); + if (sdata[0] & 0x80) { + vchSig.push_back(sdata.size() + 1); + vchSig.push_back(0); + vchSig.insert(vchSig.end(), sdata.begin(), sdata.end()); + } else { + vchSig.push_back(sdata.size()); + vchSig.insert(vchSig.end(), sdata.begin(), sdata.end()); + } + vchSig[1] = vchSig.size() - 2; + vchSig.push_back(static_cast(sigHashType.getRawSigHashType())); + DoPush(vchSig); + return *this; + } + TestBuilder &Push(const CPubKey &pubkey) { DoPush(std::vector(pubkey.begin(), pubkey.end())); return *this; @@ -2259,6 +2335,99 @@ .Push(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID())) .ScriptError(SCRIPT_ERR_CLEANSTACK)); + { + // There is a point with x = 7 + order but not x = 7. + // Since r = x mod order, this can have valid signatures, as + // demonstrated here. + std::vector rdata{7}; + std::vector sdata{7}; + tests.push_back(TestBuilder(CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG 7,7 (wrapped r)", + SCRIPT_VERIFY_STRICTENC) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + } + { + std::vector rdata = ParseHex( + "776879206d757374207765207375666665722077697468206563647361"); + std::vector sdata(58 - rdata.size() - 1, 33); + tests.push_back( + TestBuilder(CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 63-byte DER", + SCRIPT_VERIFY_STRICTENC) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + tests.push_back( + TestBuilder( + CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 63-byte DER; schnorrflag", + SCRIPT_VERIFY_STRICTENC | SCRIPT_ENABLE_SCHNORR) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + } + { + // 64-byte ECDSA sig works before schnorr flag activation, but + // not after. + std::vector rdata = ParseHex( + "776879206d757374207765207375666665722077697468206563647361"); + std::vector sdata(58 - rdata.size(), 33); + tests.push_back( + TestBuilder(CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 64-byte DER", + SCRIPT_VERIFY_STRICTENC) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + tests.push_back( + TestBuilder( + CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 64-byte DER; schnorrflag", + SCRIPT_VERIFY_STRICTENC | SCRIPT_ENABLE_SCHNORR) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata) + .ScriptError(SCRIPT_ERR_EVAL_FALSE)); + } + { + std::vector rdata = ParseHex( + "776879206d757374207765207375666665722077697468206563647361"); + std::vector sdata(58 - rdata.size() + 1, 33); + tests.push_back( + TestBuilder(CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 65-byte DER", + SCRIPT_VERIFY_STRICTENC) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + tests.push_back( + TestBuilder( + CScript() << OP_CHECKSIG, + "recovered-pubkey CHECKSIG with 65-byte DER; schnorrflag", + SCRIPT_VERIFY_STRICTENC | SCRIPT_ENABLE_SCHNORR) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + } + { + // Try 64-byte ECDSA sig again, in multisig. + std::vector rdata = ParseHex( + "776879206d757374207765207375666665722077697468206563647361"); + std::vector sdata(58 - rdata.size(), 33); + tests.push_back( + TestBuilder(CScript() + << OP_1 << OP_SWAP << OP_1 << OP_CHECKMULTISIG, + "recovered-pubkey CHECKMULTISIG with 64-byte DER", + SCRIPT_VERIFY_STRICTENC) + .Num(0) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata)); + tests.push_back( + TestBuilder( + CScript() << OP_1 << OP_SWAP << OP_1 << OP_CHECKMULTISIG, + "recovered-pubkey CHECKMULTISIG with 64-byte DER; schnorrflag", + SCRIPT_VERIFY_STRICTENC | SCRIPT_ENABLE_SCHNORR) + .Num(0) + .PushECDSASigFromParts(rdata, sdata) + .PushECDSARecoveredPubKey(rdata, sdata) + .ScriptError(SCRIPT_ERR_SIG_BADLENGTH)); + } + std::set tests_set; {