From 69aefa65e69e9b5052b443917ea5db0291e6c6d8 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:14 -0500 Subject: [PATCH] txscript: Optimize IsPayToScriptHash. This converts the IsPayToScriptHash function to analyze the raw script instead of using the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractScriptHash and works with the raw script bytes to simultaneously determine if the script is a p2sh script, and in the case it is, extract and return the hash. The second new function is named isScriptHashScript and is defined in terms of the former. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this also deprecates the isScriptHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToScriptHash 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 p2sh script: benchmark old ns/op new ns/op delta BenchmarkIsPayToScriptHash-8 62393 0.60 -100.00% benchmark old allocs new allocs delta BenchmarkIsPayToScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPayToScriptHash-8 311299 0 -100.00% --- txscript/script.go | 14 +++++++++----- txscript/standard.go | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index b154c110..a8cfd166 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -56,6 +56,8 @@ func isSmallInt(op byte) bool { // isScriptHash returns true if the script passed is a pay-to-script-hash // transaction, false otherwise. +// +// DEPRECATED. Use isScriptHashScript or extractScriptHash instead. func isScriptHash(pops []parsedOpcode) bool { return len(pops) == 3 && pops[0].opcode.value == OP_HASH160 && @@ -77,12 +79,14 @@ func IsPayToPubKeyHash(script []byte) bool { // IsPayToScriptHash returns true if the script is in the standard // pay-to-script-hash (P2SH) format, false otherwise. +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before determining if the script is a P2SH which means nodes +// on existing rules will analyze new version scripts as if they were version 0. func IsPayToScriptHash(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isScriptHash(pops) + return isScriptHashScript(script) } // isWitnessScriptHash returns true if the passed script is a diff --git a/txscript/standard.go b/txscript/standard.go index f7948f79..a02f8723 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -167,6 +167,32 @@ func isPubKeyHashScript(script []byte) bool { return extractPubKeyHash(script) != nil } +// extractScriptHash extracts the script hash from the passed script if it is a +// standard pay-to-script-hash script. It will return nil otherwise. +// +// NOTE: This function is only valid for version 0 opcodes. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func extractScriptHash(script []byte) []byte { + // A pay-to-script-hash script is of the form: + // OP_HASH160 <20-byte scripthash> OP_EQUAL + if len(script) == 23 && + script[0] == OP_HASH160 && + script[1] == OP_DATA_20 && + script[22] == OP_EQUAL { + + return script[2:22] + } + + return nil +} + +// isScriptHashScript returns whether or not the passed script is a standard +// pay-to-script-hash script. +func isScriptHashScript(script []byte) bool { + return extractScriptHash(script) != nil +} + // isPubkey returns true if the script passed is a pay-to-pubkey transaction, // false otherwise. func isPubkey(pops []parsedOpcode) bool {