Remove template matching and pseudo opcodes
The current code contains a rather complex script template matching engine, which is only used for 3 particular script types (P2PK, P2PKH, multisig). The first two of these are trivial to match for otherwise, and a specialized matcher for multisig is both more compact and more efficient than a generic one. The goal is being more flexible, so that for example larger standard multisigs inside SegWit outputs are more easy to implement. As a side-effect, it also gets rid of the pseudo opcodes hack.
This commit is contained in:
parent
6b824c090f
commit
c814e2e7e8
4 changed files with 69 additions and 106 deletions
|
@ -33,10 +33,10 @@ public:
|
||||||
/**
|
/**
|
||||||
* secp256k1:
|
* secp256k1:
|
||||||
*/
|
*/
|
||||||
static const unsigned int PUBLIC_KEY_SIZE = 65;
|
static constexpr unsigned int PUBLIC_KEY_SIZE = 65;
|
||||||
static const unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33;
|
static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33;
|
||||||
static const unsigned int SIGNATURE_SIZE = 72;
|
static constexpr unsigned int SIGNATURE_SIZE = 72;
|
||||||
static const unsigned int COMPACT_SIGNATURE_SIZE = 65;
|
static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65;
|
||||||
/**
|
/**
|
||||||
* see www.keylength.com
|
* see www.keylength.com
|
||||||
* script supports up to 75 for single byte push
|
* script supports up to 75 for single byte push
|
||||||
|
|
|
@ -141,11 +141,6 @@ const char* GetOpName(opcodetype opcode)
|
||||||
|
|
||||||
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
|
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
|
||||||
|
|
||||||
// Note:
|
|
||||||
// The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum
|
|
||||||
// as kind of implementation hack, they are *NOT* real opcodes. If found in real
|
|
||||||
// Script, just let the default: case deal with them.
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "OP_UNKNOWN";
|
return "OP_UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,13 +181,6 @@ enum opcodetype
|
||||||
OP_NOP9 = 0xb8,
|
OP_NOP9 = 0xb8,
|
||||||
OP_NOP10 = 0xb9,
|
OP_NOP10 = 0xb9,
|
||||||
|
|
||||||
|
|
||||||
// template matching params
|
|
||||||
OP_SMALLINTEGER = 0xfa,
|
|
||||||
OP_PUBKEYS = 0xfb,
|
|
||||||
OP_PUBKEYHASH = 0xfd,
|
|
||||||
OP_PUBKEY = 0xfe,
|
|
||||||
|
|
||||||
OP_INVALIDOPCODE = 0xff,
|
OP_INVALIDOPCODE = 0xff,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -35,22 +35,54 @@ const char* GetTxnOutputType(txnouttype t)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool MatchPayToPubkey(const CScript& script, valtype& pubkey)
|
||||||
|
{
|
||||||
|
if (script.size() == CPubKey::PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) {
|
||||||
|
pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::PUBLIC_KEY_SIZE + 1);
|
||||||
|
return CPubKey::ValidSize(pubkey);
|
||||||
|
}
|
||||||
|
if (script.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) {
|
||||||
|
pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1);
|
||||||
|
return CPubKey::ValidSize(pubkey);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool MatchPayToPubkeyHash(const CScript& script, valtype& pubkeyhash)
|
||||||
|
{
|
||||||
|
if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) {
|
||||||
|
pubkeyhash = valtype(script.begin () + 3, script.begin() + 23);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test for "small positive integer" script opcodes - OP_1 through OP_16. */
|
||||||
|
static constexpr bool IsSmallInteger(opcodetype opcode)
|
||||||
|
{
|
||||||
|
return opcode >= OP_1 && opcode <= OP_16;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys)
|
||||||
|
{
|
||||||
|
opcodetype opcode;
|
||||||
|
valtype data;
|
||||||
|
CScript::const_iterator it = script.begin();
|
||||||
|
if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false;
|
||||||
|
|
||||||
|
if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false;
|
||||||
|
required = CScript::DecodeOP_N(opcode);
|
||||||
|
while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) {
|
||||||
|
pubkeys.emplace_back(std::move(data));
|
||||||
|
}
|
||||||
|
if (!IsSmallInteger(opcode)) return false;
|
||||||
|
unsigned int keys = CScript::DecodeOP_N(opcode);
|
||||||
|
if (pubkeys.size() != keys || keys < required) return false;
|
||||||
|
return (it + 1 == script.end());
|
||||||
|
}
|
||||||
|
|
||||||
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet)
|
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet)
|
||||||
{
|
{
|
||||||
// Templates
|
|
||||||
static std::multimap<txnouttype, CScript> mTemplates;
|
|
||||||
if (mTemplates.empty())
|
|
||||||
{
|
|
||||||
// Standard tx, sender provides pubkey, receiver adds signature
|
|
||||||
mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));
|
|
||||||
|
|
||||||
// Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey
|
|
||||||
mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));
|
|
||||||
|
|
||||||
// Sender provides N pubkeys, receivers provides M signatures
|
|
||||||
mTemplates.insert(std::make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
vSolutionsRet.clear();
|
vSolutionsRet.clear();
|
||||||
|
|
||||||
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
|
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
|
||||||
|
@ -95,84 +127,27 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan templates
|
std::vector<unsigned char> data;
|
||||||
const CScript& script1 = scriptPubKey;
|
if (MatchPayToPubkey(scriptPubKey, data)) {
|
||||||
for (const std::pair<txnouttype, CScript>& tplate : mTemplates)
|
typeRet = TX_PUBKEY;
|
||||||
{
|
vSolutionsRet.push_back(std::move(data));
|
||||||
const CScript& script2 = tplate.second;
|
return true;
|
||||||
vSolutionsRet.clear();
|
}
|
||||||
|
|
||||||
opcodetype opcode1, opcode2;
|
if (MatchPayToPubkeyHash(scriptPubKey, data)) {
|
||||||
std::vector<unsigned char> vch1, vch2;
|
typeRet = TX_PUBKEYHASH;
|
||||||
|
vSolutionsRet.push_back(std::move(data));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Compare
|
unsigned int required;
|
||||||
CScript::const_iterator pc1 = script1.begin();
|
std::vector<std::vector<unsigned char>> keys;
|
||||||
CScript::const_iterator pc2 = script2.begin();
|
if (MatchMultisig(scriptPubKey, required, keys)) {
|
||||||
while (true)
|
typeRet = TX_MULTISIG;
|
||||||
{
|
vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16
|
||||||
if (pc1 == script1.end() && pc2 == script2.end())
|
vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end());
|
||||||
{
|
vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..16
|
||||||
// Found a match
|
return true;
|
||||||
typeRet = tplate.first;
|
|
||||||
if (typeRet == TX_MULTISIG)
|
|
||||||
{
|
|
||||||
// Additional checks for TX_MULTISIG:
|
|
||||||
unsigned char m = vSolutionsRet.front()[0];
|
|
||||||
unsigned char n = vSolutionsRet.back()[0];
|
|
||||||
if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!script1.GetOp(pc1, opcode1, vch1))
|
|
||||||
break;
|
|
||||||
if (!script2.GetOp(pc2, opcode2, vch2))
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Template matching opcodes:
|
|
||||||
if (opcode2 == OP_PUBKEYS)
|
|
||||||
{
|
|
||||||
while (CPubKey::ValidSize(vch1))
|
|
||||||
{
|
|
||||||
vSolutionsRet.push_back(vch1);
|
|
||||||
if (!script1.GetOp(pc1, opcode1, vch1))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!script2.GetOp(pc2, opcode2, vch2))
|
|
||||||
break;
|
|
||||||
// Normal situation is to fall through
|
|
||||||
// to other if/else statements
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opcode2 == OP_PUBKEY)
|
|
||||||
{
|
|
||||||
if (!CPubKey::ValidSize(vch1))
|
|
||||||
break;
|
|
||||||
vSolutionsRet.push_back(vch1);
|
|
||||||
}
|
|
||||||
else if (opcode2 == OP_PUBKEYHASH)
|
|
||||||
{
|
|
||||||
if (vch1.size() != sizeof(uint160))
|
|
||||||
break;
|
|
||||||
vSolutionsRet.push_back(vch1);
|
|
||||||
}
|
|
||||||
else if (opcode2 == OP_SMALLINTEGER)
|
|
||||||
{ // Single-byte small integer pushed onto vSolutions
|
|
||||||
if (opcode1 == OP_0 ||
|
|
||||||
(opcode1 >= OP_1 && opcode1 <= OP_16))
|
|
||||||
{
|
|
||||||
char n = (char)CScript::DecodeOP_N(opcode1);
|
|
||||||
vSolutionsRet.push_back(valtype(1, n));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (opcode1 != opcode2 || vch1 != vch2)
|
|
||||||
{
|
|
||||||
// Others must match exactly
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vSolutionsRet.clear();
|
vSolutionsRet.clear();
|
||||||
|
|
Loading…
Add table
Reference in a new issue