Refactor WitnessVersion.rebuild() to be Either[ScriptError,ScriptPubKey] to make the taproot implemtation easier (#4382)

This commit is contained in:
Chris Stewart 2022-06-11 10:35:13 -05:00 committed by GitHub
parent ab215e26df
commit b021649ac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 33 deletions

View File

@ -2,11 +2,10 @@ package org.bitcoins.core.protocol.script
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.result._
import org.bitcoins.core.util.BytesUtil
import org.bitcoins.crypto.{CryptoUtil, Sha256Digest, Sha256Hash160Digest}
import scala.util.{Failure, Success, Try}
/** Created by chris on 11/10/16.
* The version of the [[org.bitcoins.core.protocol.script.WitnessScriptPubKey WitnessScriptPubKey]],
* this indicates how a [[org.bitcoins.core.protocol.script.ScriptWitness ScriptWitness]] is rebuilt.
@ -16,11 +15,12 @@ sealed trait WitnessVersion {
/** Rebuilds the full script from the given witness and [[org.bitcoins.core.protocol.script.ScriptPubKey ScriptPubKey]]
* Either returns the [[org.bitcoins.core.protocol.script.ScriptPubKey ScriptPubKey]]
* it needs to be executed against or the failure that was encountered while rebuilding the witness
* it needs to be executed against or the [[ScriptError]] that was encountered when
* building the rebuild the scriptpubkey from the witness
*/
def rebuild(
scriptWitness: ScriptWitness,
witnessProgram: Seq[ScriptToken]): Try[ScriptPubKey]
witnessProgram: Seq[ScriptToken]): Either[ScriptError, ScriptPubKey]
def version: ScriptNumberOperation
}
@ -30,38 +30,34 @@ case object WitnessVersion0 extends WitnessVersion {
/** Rebuilds a witness version 0 SPK program, see BIP141 */
override def rebuild(
scriptWitness: ScriptWitness,
witnessProgram: Seq[ScriptToken]): Try[ScriptPubKey] = {
witnessProgram: Seq[ScriptToken]): Either[ScriptError, ScriptPubKey] = {
val programBytes = BytesUtil.toByteVector(witnessProgram)
programBytes.size match {
case 20 =>
//p2wpkh
val hash = Sha256Hash160Digest(programBytes)
Success(P2PKHScriptPubKey(hash))
Right(P2PKHScriptPubKey(hash))
case 32 =>
//p2wsh
if (scriptWitness.stack.isEmpty)
Failure(
new IllegalArgumentException(
"P2WSH cannot be rebuilt without redeem script"))
else {
if (scriptWitness.stack.isEmpty) {
Left(ScriptErrorWitnessProgramWitnessEmpty)
} else {
//need to check if the hashes match
val stackTop = scriptWitness.stack.head
val stackHash = CryptoUtil.sha256(stackTop)
val witnessHash = Sha256Digest(witnessProgram.head.bytes)
if (stackHash != witnessHash) {
Failure(new IllegalArgumentException(
s"Witness hash $witnessHash did not match stack hash $stackHash"))
Left(ScriptErrorWitnessProgramMisMatch)
} else {
val compactSizeUInt =
CompactSizeUInt.calculateCompactSizeUInt(stackTop)
val scriptPubKey = ScriptPubKey(compactSizeUInt.bytes ++ stackTop)
Success(scriptPubKey)
Right(scriptPubKey)
}
}
case _ =>
//witness version 0 programs need to be 20 bytes or 32 bytes in size
Failure(new IllegalArgumentException(
s"Witness program had invalid length (${programBytes.length}) for version 0, must be 20 or 30: $witnessProgram"))
Left(ScriptErrorWitnessProgramWrongLength)
}
}
@ -72,7 +68,7 @@ case object WitnessVersion1 extends WitnessVersion {
override def rebuild(
scriptWitness: ScriptWitness,
witnessProgram: Seq[ScriptToken]): Try[ScriptPubKey] = {
witnessProgram: Seq[ScriptToken]): Either[ScriptError, ScriptPubKey] = {
throw new UnsupportedOperationException("Taproot is not yet supported")
}
@ -89,10 +85,8 @@ case class UnassignedWitness(version: ScriptNumberOperation)
override def rebuild(
scriptWitness: ScriptWitness,
witnessProgram: Seq[ScriptToken]): Try[ScriptPubKey] =
Failure(
new UnsupportedOperationException(
s"Rebuilding is not defined for version $version yet."))
witnessProgram: Seq[ScriptToken]): Either[ScriptError, ScriptPubKey] =
Left(ScriptErrorDiscourageUpgradeableWitnessProgram)
}
object WitnessVersion {

View File

@ -415,9 +415,10 @@ sealed abstract class ScriptInterpreter {
if (witness.stack.size != 2) {
Left(ScriptErrorWitnessProgramMisMatch)
} else {
Right(
(witness.stack.map(ScriptConstant(_)),
WitnessVersion0.rebuild(witness, program).get))
for {
rebuilt <- WitnessVersion0.rebuild(witness, program)
r <- Right((witness.stack.map(ScriptConstant(_)), rebuilt))
} yield r
}
case 32 =>
//p2wsh
@ -425,10 +426,9 @@ sealed abstract class ScriptInterpreter {
Left(ScriptErrorWitnessProgramWitnessEmpty)
else {
WitnessVersion0.rebuild(witness, program) match {
case Success(rebuilt) =>
case Right(rebuilt) =>
Right((witness.stack.tail.map(ScriptConstant(_)), rebuilt))
case Failure(_) =>
Left(ScriptErrorWitnessProgramMisMatch)
case Left(err) => Left(err)
}
}
case _ =>

View File

@ -440,9 +440,9 @@ trait BitcoinScriptUtil {
case w: WitnessScriptPubKey =>
txSignatureComponent match {
case wtxSigComponent: WitnessTxSigComponent =>
val scriptT = w.witnessVersion.rebuild(wtxSigComponent.witness,
val scriptE = w.witnessVersion.rebuild(wtxSigComponent.witness,
w.witnessProgram)
parseScriptTry(scriptT)
parseScriptEither(scriptE)
case rWTxSigComponent: WitnessTxSigComponentRebuilt =>
rWTxSigComponent.scriptPubKey.asm
case _: BaseTxSigComponent =>
@ -493,7 +493,7 @@ trait BitcoinScriptUtil {
val wtx = spendingTransaction.asInstanceOf[WitnessTransaction]
val scriptT =
w.witnessVersion.rebuild(wtx.witness.witnesses(idx), w.witnessProgram)
parseScriptTry(scriptT)
parseScriptEither(scriptT)
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
@ -553,10 +553,13 @@ trait BitcoinScriptUtil {
} else program.originalScript
}
private def parseScriptTry(scriptT: Try[ScriptPubKey]): Seq[ScriptToken] =
private def parseScriptEither(
scriptT: Either[ScriptError, ScriptPubKey]): Seq[ScriptToken] =
scriptT match {
case Success(scriptPubKey) => scriptPubKey.asm
case Failure(err) => throw err
case Right(scriptPubKey) => scriptPubKey.asm
case Left(err) =>
throw new IllegalArgumentException(
s"Could not parse witness script, got err=$err")
}
/** Casts the given script token to a boolean value