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

View File

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

View File

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