core: Fix TapscriptControlBlock bugs (#5955)

* core: Fix TapscriptControlBlock bugs where we weren't correctly constructing the TapscriptControlBlock from the merkle path

* cleanup if statement
This commit is contained in:
Chris Stewart 2025-03-11 14:01:33 -05:00 committed by GitHub
parent 6d240d5c54
commit fb49548e21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 18 deletions

View file

@ -142,6 +142,7 @@ class TaprootWitnessTest extends BitcoinSUnitTest {
it must "have a correct constructor" in {
val x = TapscriptControlBlock.apply(controlBlock.leafVersion,
controlBlock.p,
controlBlock.parity,
leafHashes = controlBlock.hashes)
assert(x.bytes.toHex == controlBlock.bytes.toHex)
assert(x == controlBlock)

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.crypto.{Factory, NetworkElement, Sha256Digest, XOnlyPubKey}
import org.bitcoins.crypto.*
import scodec.bits.ByteVector
/** Control block as defined by BIP341
@ -38,6 +38,11 @@ sealed abstract class ControlBlock extends NetworkElement {
case class TapscriptControlBlock(bytes: ByteVector) extends ControlBlock {
require(TapscriptControlBlock.isValid(bytes),
s"Invalid tapscript control block, got=$bytes")
def parity: KeyParity = {
if ((bytes.head & 1) == 1) OddParity
else EvenParity
}
}
/** A control block that does not have a leaf version defined as per BIP342 This
@ -87,22 +92,32 @@ object TapscriptControlBlock extends Factory[TapscriptControlBlock] {
new TapscriptControlBlock(bytes)
}
def fromLeaves(
leafVersion: LeafVersion,
internalKey: XOnlyPubKey,
leafs: Vector[TapLeaf]): TapscriptControlBlock = {
TapscriptControlBlock(leafVersion, internalKey, leafs.map(_.sha256))
}
def apply(
leafVersion: LeafVersion,
internalKey: XOnlyPubKey,
parity: KeyParity,
leafHashes: Vector[Sha256Digest]): TapscriptControlBlock = {
val parityByte: Byte = parity match {
case OddParity => 0x01
case EvenParity => 0x0
}
val bytes =
((leafVersion.toByte | 0x1).toByte +: internalKey.bytes) ++ ByteVector
((parityByte | leafVersion.toByte).toByte +: internalKey.bytes) ++ ByteVector
.concat(leafHashes.map(_.bytes))
TapscriptControlBlock(bytes)
}
/** Constructs a tapscript control block for the case where we have one single
* [[TapLeaf]] in the entire tree In this case, we don't have any elements in
* the control block besides the internal key, leaf version and parity of the
* output xonly pubkey
*/
def fromSingleLeaf(
leafVersion: LeafVersion,
internalKey: XOnlyPubKey,
parity: KeyParity): TapscriptControlBlock = {
TapscriptControlBlock(leafVersion, internalKey, parity, Vector.empty)
}
}
object UnknownControlBlock extends Factory[UnknownControlBlock] {

View file

@ -6,17 +6,11 @@ import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, CurrencyUnits}
import org.bitcoins.core.hd.{HDChainType, HDCoinType, SegWitHDPath}
import org.bitcoins.core.number.{Int32, UInt32}
import org.bitcoins.core.protocol.Bech32mAddress
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.crypto.{
DoubleSha256Digest,
DoubleSha256DigestBE,
ECPrivateKey,
ECPublicKey,
ECPublicKeyBytes
}
import org.bitcoins.crypto.*
/** Created by chris on 2/12/16.
*/
@ -257,6 +251,16 @@ trait TransactionTestUtil {
EmptyTransaction.lockTime
)
def testWitnessTransaction: WitnessTransaction = {
WitnessTransaction(
EmptyTransaction.version,
Vector(EmptyTransactionInput),
Vector.empty,
EmptyTransaction.lockTime,
EmptyWitness.fromN(1)
)
}
/** Builds a dummy transaction that sends money to the given output */
def buildTransactionTo(output: TransactionOutput): Transaction = {
BaseTransaction(