core: Add ADT for LeafVersion

This commit is contained in:
Chris Stewart 2025-03-06 15:29:27 -06:00
parent aeca55f5ef
commit b407141146
10 changed files with 69 additions and 53 deletions

View file

@ -1,9 +1,9 @@
package org.bitcoins.core.protocol.transaction package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.protocol.Bech32mAddress import org.bitcoins.core.protocol.Bech32mAddress
import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.script.*
import org.bitcoins.crypto.{Sha256Digest, XOnlyPubKey} import org.bitcoins.crypto.{Sha256Digest, XOnlyPubKey}
import upickle.default._ import upickle.default.*
case class Given(internalPubkey: XOnlyPubKey, treeOpt: Option[TapscriptTree]) { case class Given(internalPubkey: XOnlyPubKey, treeOpt: Option[TapscriptTree]) {
@ -74,7 +74,7 @@ object TaprootWalletTestCase {
else if (`given`.objOpt.isDefined) { else if (`given`.objOpt.isDefined) {
val givenObj = `given`.obj val givenObj = `given`.obj
val script = ScriptPubKey.fromAsmHex(givenObj("script").str) val script = ScriptPubKey.fromAsmHex(givenObj("script").str)
val leafVersion = givenObj("leafVersion").num.toByte val leafVersion = LeafVersion.fromByte(givenObj("leafVersion").num.toByte)
val leaf = TapLeaf(leafVersion, script) val leaf = TapLeaf(leafVersion, script)
Some(leaf) Some(leaf)
} else { } else {

View file

@ -50,7 +50,7 @@ class TaprootWitnessTest extends BitcoinSUnitTest {
"7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac" "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac"
val spk = ScriptPubKey.fromAsmHex(asmHex) val spk = ScriptPubKey.fromAsmHex(asmHex)
assert(asmHex == spk.asmHex) assert(asmHex == spk.asmHex)
val leaf = TapLeaf(0xc0.toByte, spk) val leaf = TapLeaf(LeafVersion.Tapscript, spk)
val hash = TaprootScriptPath.computeTapleafHash(leaf) val hash = TaprootScriptPath.computeTapleafHash(leaf)
assert(hash.hex == expected) assert(hash.hex == expected)
} }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script.descriptor package org.bitcoins.core.protocol.script.descriptor
import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.script.*
import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey} import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey}
import org.bitcoins.testkitcore.util.BitcoinSUnitTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import org.scalatest.Assertion import org.scalatest.Assertion
@ -536,7 +536,7 @@ class DescriptorTest extends BitcoinSUnitTest {
case pub: ECPublicKey => pub.toXOnly case pub: ECPublicKey => pub.toXOnly
} }
val tree = val tree =
TapLeaf(TapLeaf.leafVersion, P2PKScriptPubKey(derivedKey.toXOnly)) TapLeaf(LeafVersion.Tapscript, P2PKScriptPubKey(derivedKey.toXOnly))
val (_, spk) = val (_, spk) =
TaprootScriptPubKey.fromInternalKeyTapscriptTree(internal, tree) TaprootScriptPubKey.fromInternalKeyTapscriptTree(internal, tree)
spk spk

View file

@ -22,11 +22,11 @@ sealed abstract class ControlBlock extends NetworkElement {
XOnlyPubKey.fromBytes(bytes.slice(1, 33)) XOnlyPubKey.fromBytes(bytes.slice(1, 33))
} }
val leafVersion: Byte = val leafVersion: LeafVersion =
(bytes.head & TaprootScriptPath.TAPROOT_LEAF_MASK).toByte LeafVersion.fromByte((bytes.head & LeafVersion.TAPROOT_LEAF_MASK).toByte)
val isTapLeafMask: Boolean = { val isTapLeafMask: Boolean = {
(bytes.head & TaprootScriptPath.TAPROOT_LEAF_MASK).toByte == TaprootScriptPath.TAPROOT_LEAF_TAPSCRIPT (bytes.head & LeafVersion.TAPROOT_LEAF_MASK).toByte == LeafVersion.Tapscript.toByte
} }
/** Leaf or branch hashes embedded in the control block */ /** Leaf or branch hashes embedded in the control block */
@ -71,7 +71,8 @@ object TapscriptControlBlock extends Factory[TapscriptControlBlock] {
if (bytes.isEmpty) { if (bytes.isEmpty) {
false false
} else { } else {
TapLeaf.knownLeafVersions.contains(bytes.head) && LeafVersion.knownLeafVersions.contains(
LeafVersion.fromMaskedByte(bytes.head)) &&
ControlBlock.isValid(bytes) && ControlBlock.isValid(bytes) &&
XOnlyPubKey.fromBytesT(bytes.slice(1, 33)).isSuccess XOnlyPubKey.fromBytesT(bytes.slice(1, 33)).isSuccess
} }
@ -79,7 +80,7 @@ object TapscriptControlBlock extends Factory[TapscriptControlBlock] {
/** Creates a control block with no scripts, just an internal key */ /** Creates a control block with no scripts, just an internal key */
def fromXOnlyPubKey(internalKey: XOnlyPubKey): TapscriptControlBlock = { def fromXOnlyPubKey(internalKey: XOnlyPubKey): TapscriptControlBlock = {
fromBytes(TapLeaf.leafVersion +: internalKey.bytes) fromBytes(LeafVersion.Tapscript.toByte +: internalKey.bytes)
} }
override def fromBytes(bytes: ByteVector): TapscriptControlBlock = { override def fromBytes(bytes: ByteVector): TapscriptControlBlock = {

View file

@ -0,0 +1,35 @@
package org.bitcoins.core.protocol.script
sealed abstract class LeafVersion {
def toByte: Byte
}
object LeafVersion {
/** BIP342 specifies validity rules that apply for leaf version 0xc0, but
* future proposals can introduce rules for other leaf versions.
*
* @see
* https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#rationale
*/
case object Tapscript extends LeafVersion {
override def toByte: Byte = 0xc0.toByte
}
case class UnknownLeafVersion(toByte: Byte) extends LeafVersion
val knownLeafVersions: Vector[LeafVersion] = Vector(
Tapscript /*, 0xc1.toByte*/ )
final val TAPROOT_LEAF_MASK: Byte = 0xfe.toByte
def fromByte(byte: Byte): LeafVersion = {
knownLeafVersions
.find(_.toByte == byte)
.getOrElse(UnknownLeafVersion(byte))
}
def fromMaskedByte(byte: Byte): LeafVersion = {
fromByte((TAPROOT_LEAF_MASK & byte).toByte)
}
}

View file

@ -7,7 +7,7 @@ import org.bitcoins.core.serializers.script.{
ScriptParser ScriptParser
} }
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
import org.bitcoins.crypto._ import org.bitcoins.crypto.*
import scodec.bits.ByteVector import scodec.bits.ByteVector
/** Created by chris on 11/10/16. The witness used to evaluate a /** Created by chris on 11/10/16. The witness used to evaluate a
@ -416,10 +416,6 @@ object TaprootScriptPath extends Factory[TaprootScriptPath] {
final val TAPROOT_CONTROL_MAX_SIZE: Int = { final val TAPROOT_CONTROL_MAX_SIZE: Int = {
TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT
} }
final val TAPROOT_LEAF_MASK: Byte = 0xfe.toByte
final val TAPROOT_LEAF_TAPSCRIPT: Byte = 0xc0.toByte
override def fromBytes(bytes: ByteVector): TaprootScriptPath = { override def fromBytes(bytes: ByteVector): TaprootScriptPath = {
RawScriptWitnessParser.read(bytes) match { RawScriptWitnessParser.read(bytes) match {
case t: TaprootScriptPath => t case t: TaprootScriptPath => t
@ -485,7 +481,8 @@ object TaprootScriptPath extends Factory[TaprootScriptPath] {
*/ */
def computeTapleafHash(leaf: TapLeaf): Sha256Digest = { def computeTapleafHash(leaf: TapLeaf): Sha256Digest = {
val bytes = val bytes =
ByteVector.fromInt(i = leaf.leafVersion, size = 1) ++ leaf.spk.bytes ByteVector.fromInt(i = leaf.leafVersion.toByte,
size = 1) ++ leaf.spk.bytes
CryptoUtil.tapLeafHash(bytes) CryptoUtil.tapLeafHash(bytes)
} }

View file

@ -29,10 +29,11 @@ case class TapBranch(tree1: TapscriptTree, tree2: TapscriptTree)
} }
} }
case class TapLeaf(leafVersion: Byte, spk: ScriptPubKey) extends TapscriptTree { case class TapLeaf(leafVersion: LeafVersion, spk: ScriptPubKey)
extends TapscriptTree {
override val bytes: ByteVector = override val bytes: ByteVector =
ByteVector.fromInt(leafVersion, 1) ++ spk.bytes ByteVector.fromInt(leafVersion.toByte, 1) ++ spk.bytes
val sha256: Sha256Digest = CryptoUtil.tapLeafHash(bytes) val sha256: Sha256Digest = CryptoUtil.tapLeafHash(bytes)
override val leafs: Vector[TapLeaf] = Vector(this) override val leafs: Vector[TapLeaf] = Vector(this)
@ -41,18 +42,6 @@ case class TapLeaf(leafVersion: Byte, spk: ScriptPubKey) extends TapscriptTree {
} }
} }
object TapLeaf {
val leafVersion: Byte = 0xc0.toByte
/** BIP342 specifies validity rules that apply for leaf version 0xc0, but
* future proposals can introduce rules for other leaf versions.
*
* @see
* https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#rationale
*/
val knownLeafVersions: Vector[Byte] = Vector(leafVersion, 0xc1.toByte)
}
object TapscriptTree { object TapscriptTree {
def buildTapscriptTree(leafs: Vector[TapLeaf]): TapscriptTree = { def buildTapscriptTree(leafs: Vector[TapLeaf]): TapscriptTree = {

View file

@ -386,7 +386,7 @@ case class TapscriptLeafExpression(source: RawSPKScriptExpression)
private def scriptPubKey: ScriptPubKey = source.scriptPubKey private def scriptPubKey: ScriptPubKey = source.scriptPubKey
override def tree: TapLeaf = override def tree: TapLeaf =
TapLeaf(TapLeaf.leafVersion, scriptPubKey) TapLeaf(LeafVersion.Tapscript, scriptPubKey)
} }
object SingleECPublicKeyExpression object SingleECPublicKeyExpression

View file

@ -4,7 +4,7 @@ import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.hd.BIP32Path import org.bitcoins.core.hd.BIP32Path
import org.bitcoins.core.number.{Int32, UInt32, UInt64} import org.bitcoins.core.number.{Int32, UInt32, UInt64}
import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.transaction.{ import org.bitcoins.core.protocol.transaction.{
BaseTransaction, BaseTransaction,
NonWitnessTransaction, NonWitnessTransaction,
@ -13,7 +13,7 @@ import org.bitcoins.core.protocol.transaction.{
} }
import org.bitcoins.core.serializers.script.RawScriptWitnessParser import org.bitcoins.core.serializers.script.RawScriptWitnessParser
import org.bitcoins.core.util.BytesUtil import org.bitcoins.core.util.BytesUtil
import org.bitcoins.crypto.{HashType, _} import org.bitcoins.crypto.*
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.annotation.tailrec import scala.annotation.tailrec
@ -59,7 +59,7 @@ sealed trait GlobalPSBTRecord extends PSBTRecord {
} }
object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] { object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] {
import org.bitcoins.core.psbt.PSBTGlobalKeyId._ import org.bitcoins.core.psbt.PSBTGlobalKeyId.*
case class UnsignedTransaction(transaction: NonWitnessTransaction) case class UnsignedTransaction(transaction: NonWitnessTransaction)
extends GlobalPSBTRecord { extends GlobalPSBTRecord {
@ -107,7 +107,7 @@ object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] {
} }
override def fromBytes(bytes: ByteVector): GlobalPSBTRecord = { override def fromBytes(bytes: ByteVector): GlobalPSBTRecord = {
import org.bitcoins.core.psbt.PSBTGlobalKeyId._ import org.bitcoins.core.psbt.PSBTGlobalKeyId.*
val (key, value) = PSBTRecord.fromBytes(bytes) val (key, value) = PSBTRecord.fromBytes(bytes)
PSBTGlobalKeyId.fromByte(key.head) match { PSBTGlobalKeyId.fromByte(key.head) match {
@ -141,7 +141,7 @@ sealed trait InputPSBTRecord extends PSBTRecord {
} }
object InputPSBTRecord extends Factory[InputPSBTRecord] { object InputPSBTRecord extends Factory[InputPSBTRecord] {
import org.bitcoins.core.psbt.PSBTInputKeyId._ import org.bitcoins.core.psbt.PSBTInputKeyId.*
case class NonWitnessOrUnknownUTXO(transactionSpent: Transaction) case class NonWitnessOrUnknownUTXO(transactionSpent: Transaction)
extends InputPSBTRecord { extends InputPSBTRecord {
@ -352,7 +352,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] {
case class TRLeafScript( case class TRLeafScript(
controlBlock: ControlBlock, controlBlock: ControlBlock,
script: RawScriptPubKey, script: RawScriptPubKey,
leafVersion: Byte) leafVersion: LeafVersion)
extends InputPSBTRecord { extends InputPSBTRecord {
override type KeyId = TRLeafScriptKeyId.type override type KeyId = TRLeafScriptKeyId.type
@ -361,7 +361,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] {
} }
override val value: ByteVector = { override val value: ByteVector = {
script.asmBytes ++ ByteVector.fromByte(leafVersion) script.asmBytes ++ ByteVector.fromByte(leafVersion.toByte)
} }
} }
@ -411,7 +411,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] {
} }
override def fromBytes(bytes: ByteVector): InputPSBTRecord = { override def fromBytes(bytes: ByteVector): InputPSBTRecord = {
import org.bitcoins.core.psbt.PSBTInputKeyId._ import org.bitcoins.core.psbt.PSBTInputKeyId.*
val (key, value) = PSBTRecord.fromBytes(bytes) val (key, value) = PSBTRecord.fromBytes(bytes)
PSBTInputKeyId.fromByte(key.head) match { PSBTInputKeyId.fromByte(key.head) match {
@ -530,7 +530,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] {
val script = RawScriptPubKey.fromAsmBytes(value.init) val script = RawScriptPubKey.fromAsmBytes(value.init)
TRLeafScript(controlBlock, script, value.last) TRLeafScript(controlBlock, script, LeafVersion.fromByte(value.last))
case TRBIP32DerivationPathKeyId => case TRBIP32DerivationPathKeyId =>
val pubKey = XOnlyPubKey(key.tail) val pubKey = XOnlyPubKey(key.tail)
val numHashes = CompactSizeUInt.fromBytes(value) val numHashes = CompactSizeUInt.fromBytes(value)
@ -575,7 +575,7 @@ sealed trait OutputPSBTRecord extends PSBTRecord {
} }
object OutputPSBTRecord extends Factory[OutputPSBTRecord] { object OutputPSBTRecord extends Factory[OutputPSBTRecord] {
import org.bitcoins.core.psbt.PSBTOutputKeyId._ import org.bitcoins.core.psbt.PSBTOutputKeyId.*
case class RedeemScript(redeemScript: ScriptPubKey) extends OutputPSBTRecord { case class RedeemScript(redeemScript: ScriptPubKey) extends OutputPSBTRecord {
override type KeyId = RedeemScriptKeyId.type override type KeyId = RedeemScriptKeyId.type

View file

@ -1,18 +1,12 @@
package org.bitcoins.core.script package org.bitcoins.core.script
import org.bitcoins.core.crypto._ import org.bitcoins.core.crypto.*
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{ import org.bitcoins.core.protocol.script.*
TapLeaf, import org.bitcoins.core.script.constant.*
TaprootKeyPath,
TaprootScriptPath,
TaprootUnknownPath,
TaprootWitness
}
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control.{OP_ELSE, OP_ENDIF, OP_IF, OP_NOTIF} import org.bitcoins.core.script.control.{OP_ELSE, OP_ENDIF, OP_IF, OP_NOTIF}
import org.bitcoins.core.script.flag.ScriptFlag import org.bitcoins.core.script.flag.ScriptFlag
import org.bitcoins.core.script.result._ import org.bitcoins.core.script.result.*
import org.bitcoins.core.util.BitcoinScriptUtil import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.crypto.Sha256Digest import org.bitcoins.crypto.Sha256Digest
@ -91,7 +85,7 @@ sealed trait ScriptProgram {
*/ */
def tapLeafHashOpt: Option[Sha256Digest] = { def tapLeafHashOpt: Option[Sha256Digest] = {
getTapscriptOpt.map { sp => getTapscriptOpt.map { sp =>
val leaf = TapLeaf(TaprootScriptPath.TAPROOT_LEAF_TAPSCRIPT, sp.script) val leaf = TapLeaf(LeafVersion.Tapscript, sp.script)
val hash = TaprootScriptPath.computeTapleafHash(leaf) val hash = TaprootScriptPath.computeTapleafHash(leaf)
hash hash
} }