txscript: Optimize IsMultisigSigScript.

This converts the IsMultisigSigScript function to analyze the raw script
and make use of the new tokenizer instead of the far less efficient
parseScript thereby significantly optimizing the function.

In order to accomplish this, it first rejects scripts that can't
possibly fit the bill due to the final byte of what would be the redeem
script not being the appropriate opcode or the overall script not having
enough bytes.  Then, it uses a new function that is introduced named
finalOpcodeData that uses the tokenizer to return any data associated
with the final opcode in the signature script (which will be nil for
non-push opcodes or if the script fails to parse) and analyzes it as if
it were a redeem script when it is non nil.

It is also worth noting that this new implementation intentionally has
the same semantic difference from the existing implementation as the
updated IsMultisigScript function in regards to allowing zero pubkeys
whereas previously it incorrectly required at least one pubkey.

Finally, the comment is modified to explicitly call out the script
version semantics.

The following is a before and after comparison of analyzing a large
script that is not a multisig script and both a 1-of-2 multisig public
key script (which should be false) and a signature script comprised of a
pay-to-script-hash 1-of-2 multisig redeem script (which should be true):

benchmark                               old ns/op     new ns/op     delta
BenchmarkIsMultisigSigScriptLarge-8     69328         2.93          -100.00%
BenchmarkIsMultisigSigScript-8          2375          146           -93.85%

benchmark                               old allocs     new allocs     delta
BenchmarkIsMultisigSigScriptLarge-8     5              0              -100.00%
BenchmarkIsMultisigSigScript-8          3              0              -100.00%

benchmark                               old bytes     new bytes     delta
BenchmarkIsMultisigSigScriptLarge-8     330035        0             -100.00%
BenchmarkIsMultisigSigScript-8          9472          0             -100.00%
This commit is contained in:
Dave Collins 2019-03-13 01:11:18 -05:00 committed by Olaoluwa Osuntokun
parent 02dab1695f
commit 34ebf0f32f
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
2 changed files with 48 additions and 12 deletions

View file

@ -769,6 +769,25 @@ func GetSigOpCount(script []byte) int {
return getSigOpCount(pops, false)
}
// finalOpcodeData returns the data associated with the final opcode in the
// script. It will return nil if the script fails to parse.
func finalOpcodeData(scriptVersion uint16, script []byte) []byte {
// Avoid unnecessary work.
if len(script) == 0 {
return nil
}
var data []byte
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
data = tokenizer.Data()
}
if tokenizer.Err() != nil {
return nil
}
return data
}
// GetPreciseSigOpCount returns the number of signature operations in
// scriptPubKey. If bip16 is true then scriptSig may be searched for the
// Pay-To-Script-Hash script in order to find the precise number of signature

View file

@ -365,22 +365,39 @@ func IsMultisigScript(script []byte) (bool, error) {
return isMultisigScript(scriptVersion, script), nil
}
// IsMultisigSigScript takes a script, parses it, then returns whether or
// not it is a multisignature script.
// IsMultisigSigScript returns whether or not the passed script appears to be a
// signature script which consists of a pay-to-script-hash multi-signature
// redeem script. Determining if a signature script is actually a redemption of
// pay-to-script-hash requires the associated public key script which is often
// expensive to obtain. Therefore, this makes a fast best effort guess that has
// a high probability of being correct by checking if the signature script ends
// with a data push and treating that data push as if it were a p2sh redeem
// script
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func IsMultisigSigScript(script []byte) bool {
if len(script) == 0 || script == nil {
return false
}
pops, err := parseScript(script)
if err != nil {
return false
}
subPops, err := parseScript(pops[len(pops)-1].data)
if err != nil {
const scriptVersion = 0
// The script can't possibly be a multisig signature script if it doesn't
// end with OP_CHECKMULTISIG in the redeem script or have at least two small
// integers preceding it, and the redeem script itself must be preceded by
// at least a data push opcode. Fail fast to avoid more work below.
if len(script) < 4 || script[len(script)-1] != OP_CHECKMULTISIG {
return false
}
return isMultiSig(subPops)
// Parse through the script to find the last opcode and any data it might
// push and treat it as a p2sh redeem script even though it might not
// actually be one.
possibleRedeemScript := finalOpcodeData(scriptVersion, script)
if possibleRedeemScript == nil {
return false
}
// Finally, return if that possible redeem script is a multisig script.
return isMultisigScript(scriptVersion, possibleRedeemScript)
}
// isNullData returns true if the passed script is a null data transaction,