2021 02 21 cheap redeemscript check (#2707)

* Add more cheap checks to see if a given script token is a redeemScript

* Make cheap witness version check before deserializing to a bytevector
This commit is contained in:
Chris Stewart 2021-02-23 10:37:40 -06:00 committed by GitHub
parent c6bf0bb1a3
commit 0d7edb7a68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 32 deletions

View file

@ -1176,15 +1176,21 @@ object WitnessScriptPubKey extends ScriptFactory[WitnessScriptPubKey] {
// ScriptConstantImpl(ByteVector(37 bytes, 0x0021020afd6012af90835558c68365a370b7e6cd1c0d4664a8656c8c7847185cb5db6651ae)))
//we can also have a LockTimeScriptPubKey with a nested 0 public key multisig script, need to check that as well
val bytes = BytesUtil.toByteVector(asm)
//it turns out this method gets called a lot when we attempt
//to type check spks, so let's do a cheap check before deserializing
//everything to a byte vector which is expensive
val firstOp = asm.headOption
if (bytes.size < 4 || bytes.size > 42) false
else if (!validWitVersions.contains(firstOp.getOrElse(OP_1NEGATE))) false
else if (MultiSignatureScriptPubKey.isValidAsm(asm)) false
else if (LockTimeScriptPubKey.isValidAsm(asm)) false
else if (asm(1).toLong + 2 == bytes.size) true
else false
if (!validWitVersions.contains(firstOp.getOrElse(OP_1NEGATE))) {
false
} else {
val bytes = BytesUtil.toByteVector(asm)
if (bytes.length < 4 || bytes.length > 42) false
else if (MultiSignatureScriptPubKey.isValidAsm(asm)) false
else if (LockTimeScriptPubKey.isValidAsm(asm)) false
else if (asm(1).toLong + 2 == bytes.size) true
else false
}
}
}

View file

@ -254,33 +254,41 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
/** Detects if the given script token is a redeem script */
def isRedeemScript(token: ScriptToken): Boolean = {
val redeemScript: ScriptPubKey = parseRedeemScript(token)
def isStandardNonP2SH(
spk: ScriptPubKey,
isRecursiveCall: Boolean): Boolean = {
spk match {
case _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey =>
true
case EmptyScriptPubKey => isRecursiveCall // Fine if nested
case conditional: ConditionalScriptPubKey =>
isStandardNonP2SH(conditional.firstSPK,
isRecursiveCall = true) && isStandardNonP2SH(
conditional.secondSPK,
isRecursiveCall = true)
case locktime: LockTimeScriptPubKey =>
Try(locktime.locktime).isSuccess &&
isStandardNonP2SH(locktime.nestedScriptPubKey,
isRecursiveCall = true)
case _: NonStandardScriptPubKey | _: WitnessCommitment |
_: P2SHScriptPubKey =>
false
}
token match {
case _: ScriptNumberOperation | _: ScriptOperation =>
//more cheap checks, we can't have a redeemScript
//if the token is OP_0/OP_1/OP_CHECKSIG etc
false
case constant: ScriptConstant =>
val redeemScript: ScriptPubKey = parseRedeemScript(constant)
isStandardNonP2SH(redeemScript, isRecursiveCall = false)
}
}
isStandardNonP2SH(redeemScript, isRecursiveCall = false)
private def isStandardNonP2SH(
spk: ScriptPubKey,
isRecursiveCall: Boolean): Boolean = {
spk match {
case _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey =>
true
case EmptyScriptPubKey => isRecursiveCall // Fine if nested
case conditional: ConditionalScriptPubKey =>
isStandardNonP2SH(conditional.firstSPK,
isRecursiveCall = true) && isStandardNonP2SH(
conditional.secondSPK,
isRecursiveCall = true)
case locktime: LockTimeScriptPubKey =>
if (Try(locktime.locktime).isSuccess) {
isStandardNonP2SH(locktime.nestedScriptPubKey, isRecursiveCall = true)
} else {
false
}
case _: NonStandardScriptPubKey | _: WitnessCommitment |
_: P2SHScriptPubKey =>
false
}
}
/** Parses a redeem script from the given script token */