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:
parent
3bdeaa46bf
commit
6be04a8e43
2 changed files with 48 additions and 12 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue