2024 10 31 taproot signing (#5767)

* core: Implement TaprootKeyPath signing

* core: Rebase, remove isDummySignature

* Empty commit to run CI
This commit is contained in:
Chris Stewart 2024-11-18 09:43:29 -06:00 committed by GitHub
parent 80be2f5989
commit 67bb3ceabd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 295 additions and 48 deletions

View File

@ -2,10 +2,11 @@ package org.bitcoins.core.wallet.signer
import org.bitcoins.core.crypto.{
BaseTxSigComponent,
TaprootTxSigComponent,
WitnessTxSigComponentP2SH,
WitnessTxSigComponentRaw
}
import org.bitcoins.core.currency.{CurrencyUnits, Satoshis}
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.*
@ -14,13 +15,14 @@ import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.util.PreviousOutputMap
import org.bitcoins.core.wallet.builder.{
RawTxSigner,
StandardNonInteractiveFinalizer
}
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.*
import org.bitcoins.crypto.ECDigitalSignature
import org.bitcoins.crypto.{ECDigitalSignature, ECPrivateKey, HashType}
import org.bitcoins.testkitcore.gen.{
CreditingTxGen,
GenUtil,
@ -178,7 +180,14 @@ class SignerTest extends BitcoinSUnitTest {
o,
Policy.standardFlags
)
case _: UnassignedWitnessScriptPubKey | _: TaprootScriptPubKey => ???
case _: TaprootScriptPubKey =>
TaprootTxSigComponent(
tx.asInstanceOf[WitnessTransaction],
UInt32(idx),
utxo.inputInfo.asInstanceOf[TaprootInputInfo].previousOutputMap,
Policy.standardFlags
)
case _: UnassignedWitnessScriptPubKey => ???
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: WitnessCommitment | _: CSVScriptPubKey | _: CLTVScriptPubKey |
@ -213,10 +222,9 @@ class SignerTest extends BitcoinSUnitTest {
utxos: Vector[InputSigningInfo[InputInfo]]
): Boolean = {
val programs: Vector[PreExecutionScriptProgram] =
tx.inputs.zipWithIndex.toVector.map {
case (input: TransactionInput, idx: Int) =>
val utxo = utxos.find(_.outPoint == input.previousOutput).get
createProgram(tx, idx, utxo)
tx.inputs.zipWithIndex.map { case (input: TransactionInput, idx: Int) =>
val utxo = utxos.find(_.outPoint == input.previousOutput).get
createProgram(tx, idx, utxo)
}
ScriptInterpreter.runAllVerify(programs)
}
@ -275,4 +283,51 @@ class SignerTest extends BitcoinSUnitTest {
assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector))
}
}
it must "sign a p2tr keypath utxo" in {
val privKey = ECPrivateKey.freshPrivateKey
val xonly = privKey.toXOnly
val spk = TaprootScriptPubKey(xonly)
val output = TransactionOutput(Bitcoins.one, spk)
val creditingTx = BaseTransaction(
version = TransactionConstants.version,
inputs = Vector.empty,
outputs = Vector(output),
lockTime = TransactionConstants.lockTime
)
val outPoint = TransactionOutPoint(creditingTx.txIdBE, 0)
val input = TransactionInput(outPoint,
ScriptSignature.empty,
TransactionConstants.sequence)
val output1 = TransactionOutput(Bitcoins.one, EmptyScriptPubKey)
val unsignedTx = WitnessTransaction(
version = TransactionConstants.version,
inputs = Vector(input),
outputs = Vector(output1),
lockTime = TransactionConstants.lockTime,
witness = EmptyWitness.fromN(1)
)
val previousOutputMap = PreviousOutputMap(Map(outPoint -> output))
val inputInfo: TaprootKeyPathInputInfo = TaprootKeyPathInputInfo(
outPoint,
output.value,
spk,
previousOutputMap = previousOutputMap
)
val sigParams = ECSignatureParams(inputInfo = inputInfo,
prevTransaction = creditingTx,
signer = privKey,
hashType = HashType.sigHashDefault)
val sigComponent = TaprootKeyPathSigner.sign(
spendingInfo = sigParams.toScriptSignatureParams,
unsignedTx = unsignedTx,
spendingInfoToSatisfy = sigParams.toScriptSignatureParams)
val result =
verifyScripts(sigComponent.transaction, utxos = Vector(sigParams))
assert(result)
}
}

View File

@ -94,6 +94,7 @@ sealed abstract class TransactionSignatureCreator {
* level, a hardware wallet expects a scodec.bits.ByteVector as input, and
* returns an [[ECDigitalSignature]] if it is able to sign the
* scodec.bits.ByteVector's correctly.
*
* @param sign
* \- the implementation of the hardware wallet protocol to sign the
* scodec.bits.ByteVector w/ the given public key
@ -121,11 +122,11 @@ sealed abstract class TransactionSignatureCreator {
/** This is the same as createSig above, except the 'sign' function returns a
* Future[ECDigitalSignature]
*/
def createSig(
def createSig[Sig <: DigitalSignature](
spendingTransaction: Transaction,
signingInfo: InputSigningInfo[InputInfo],
sign: (ByteVector, HashType) => Future[ECDigitalSignature],
hashType: HashType): Future[ECDigitalSignature] = {
sign: (ByteVector, HashType) => Future[Sig],
hashType: HashType): Future[Sig] = {
val hash =
TransactionSignatureSerializer.hashForSignature(
spendingTransaction,

View File

@ -58,6 +58,8 @@ object TxSigComponent {
outputMap: PreviousOutputMap,
flags: Seq[ScriptFlag] = Policy.standardFlags): TxSigComponent = {
inputInfo match {
case kp: TaprootKeyPathInputInfo =>
fromWitnessInput(kp, unsignedTx, flags)
case segwit: SegwitV0NativeInputInfo =>
fromWitnessInput(segwit, unsignedTx, flags)
case unassigned: UnassignedSegwitNativeInputInfo =>
@ -113,6 +115,18 @@ object TxSigComponent {
WitnessTxSigComponent(wtx, UInt32(idx), inputInfo.output, outputMap, flags)
}
def fromWitnessInput(
info: TaprootKeyPathInputInfo,
unsignedTx: Transaction,
flags: Seq[ScriptFlag]): TaprootTxSigComponent = {
val inputIndex: UInt32 = UInt32(info.inputIndex)
val wtx = setTransactionWitness(info, unsignedTx)
TaprootTxSigComponent(transaction = wtx,
inputIndex = inputIndex,
outputMap = info.previousOutputMap,
flags = flags)
}
def fromWitnessInput(
inputInfo: P2SHNestedSegwitV0InputInfo,
unsignedTx: Transaction,

View File

@ -82,23 +82,17 @@ case object WitnessVersion1 extends WitnessVersion {
programBytes.size match {
case 32 =>
// p2tr
if (scriptWitness.stack.isEmpty) {
Left(ScriptErrorWitnessProgramWitnessEmpty)
} else {
val rebuiltSPK = scriptWitness match {
case _: TaprootKeyPath =>
Right(witnessSPK)
case sp: TaprootScriptPath =>
Right(sp.script)
case _: TaprootUnknownPath =>
Right(witnessSPK)
case w @ (EmptyScriptWitness | _: P2WPKHWitnessV0 |
_: P2WSHWitnessV0) =>
sys.error(
s"Cannot rebuild witnessv1 with a non v1 witness, got=$w")
}
rebuiltSPK
val rebuiltSPK = scriptWitness match {
case _: TaprootKeyPath | EmptyScriptWitness =>
Right(witnessSPK)
case sp: TaprootScriptPath =>
Right(sp.script)
case _: TaprootUnknownPath =>
Right(witnessSPK)
case w @ (_: P2WPKHWitnessV0 | _: P2WSHWitnessV0) =>
sys.error(s"Cannot rebuild witnessv1 with a non v1 witness, got=$w")
}
rebuiltSPK
case _ =>
// witness version 1 programs need to be 32 bytes in size
// this is technically wrong as this is dependent on a policy flag

View File

@ -82,7 +82,8 @@ object InputUtil {
loop(conditional.nestedInputInfo +: newRemaining, accum)
case _: P2WPKHV0InputInfo | _: UnassignedSegwitNativeInputInfo |
_: P2PKInputInfo | _: P2PKHInputInfo |
_: MultiSignatureInputInfo | _: EmptyInputInfo =>
_: MultiSignatureInputInfo | _: EmptyInputInfo |
_: TaprootKeyPathInputInfo =>
// none of these script types affect the sequence number of a tx so the defaultSequence is used
val input =
TransactionInput(spendingInfo.outPoint,

View File

@ -5,6 +5,7 @@ import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.script.util.PreviousOutputMap
import org.bitcoins.core.wallet.builder.{
AddWitnessDataFinalizer,
RawTxBuilder,
@ -111,7 +112,8 @@ object TxUtil {
currentLockTimeOpt)
case _: P2WPKHV0InputInfo | _: UnassignedSegwitNativeInputInfo |
_: P2PKInputInfo | _: P2PKHInputInfo |
_: MultiSignatureInputInfo | _: EmptyInputInfo =>
_: MultiSignatureInputInfo | _: EmptyInputInfo |
_: TaprootKeyPathInputInfo =>
// none of these scripts affect the locktime of a tx
loop(newRemaining, currentLockTimeOpt)
}
@ -134,7 +136,9 @@ object TxUtil {
outputs: Vector[TransactionOutput]): Transaction = {
val dummySpendingInfos = utxos.map { inputInfo =>
val mockSigners =
inputInfo.pubKeys.take(inputInfo.requiredSigs).map(Sign.dummySign)
inputInfo.pubKeys
.take(inputInfo.requiredSigs)
.map(p => Sign.dummySign(p))
inputInfo.toSpendingInfo(EmptyTransaction,
mockSigners,
@ -372,10 +376,30 @@ object TxUtil {
}
}
def inputIndexOpt(
inputInfo: InputInfo,
previousOutputMap: PreviousOutputMap): Option[Int] = {
previousOutputMap.zipWithIndex
.find(_._1._1 == inputInfo.outPoint)
.map(_._2)
}
def inputIndex(
inputInfo: InputInfo,
previousOutputMap: PreviousOutputMap): Int = {
inputIndexOpt(inputInfo, previousOutputMap) match {
case Some(i) => i
case None =>
throw new IllegalArgumentException(
s"The transaction did not contain the expected outPoint (${inputInfo.outPoint}), got $previousOutputMap")
}
}
/** Returns the index of the InputInfo in the transaction */
def inputIndexOpt(inputInfo: InputInfo, tx: Transaction): Option[Int] = {
tx.inputs.zipWithIndex
.find(_._1.previousOutput == inputInfo.outPoint)
.map(_._2)
}
}

View File

@ -913,7 +913,7 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
.sign(spendingInfo, unsignedTx)
val utxos = spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
case _: UnassignedSegwitNativeInputInfo | _: TaprootKeyPathInputInfo =>
Vector(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
@ -971,7 +971,7 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
val builder = Vector.newBuilder[InputPSBTRecord]
spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
case _: UnassignedSegwitNativeInputInfo | _: TaprootKeyPathInputInfo =>
builder.+=(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
@ -996,7 +996,7 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
case p2wsh: P2WSHV0InputInfo =>
builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript))
case _: RawInputInfo | _: P2WPKHV0InputInfo |
_: UnassignedSegwitNativeInputInfo =>
_: UnassignedSegwitNativeInputInfo | _: TaprootKeyPathInputInfo =>
()
}

View File

@ -34,7 +34,7 @@ sealed abstract class SignerUtils {
val tx = spendingInfo.inputInfo match {
case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo |
_: UnassignedSegwitNativeInputInfo =>
_: UnassignedSegwitNativeInputInfo | _: TaprootKeyPathInputInfo =>
TxUtil.addWitnessData(unsignedTx, spendingInfo)
case _: RawInputInfo | _: P2SHNonSegwitInputInfo =>
unsignedTx
@ -186,6 +186,10 @@ object BitcoinSigner extends SignerUtils {
P2WPKHSigner.sign(spendingInfo, unsignedTx, spendingFrom(p2wpkh))
case pw2sh: P2WSHV0InputInfo =>
P2WSHSigner.sign(spendingInfo, unsignedTx, spendingFrom(pw2sh))
case trk: TaprootKeyPathInputInfo =>
TaprootKeyPathSigner.sign(spendingInfo,
unsignedTx,
spendingInfoToSatisfy = spendingFrom(trk))
case _: UnassignedSegwitNativeInputInfo =>
throw new UnsupportedOperationException("Unsupported Segwit version")
}
@ -613,3 +617,70 @@ sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
}
}
object ConditionalSigner extends ConditionalSigner
sealed abstract class TaprootKeyPathSigner
extends Signer[TaprootKeyPathInputInfo] {
/** The method used to sign a bitcoin unspent transaction output that is
* potentially nested
*
* @param spendingInfo
* \- The information required for signing
* @param unsignedTx
* the external Transaction that needs an input signed
* @param isDummySignature
* \- do not sign the tx for real, just use a dummy signature this is
* useful for fee estimation
* @param spendingInfoToSatisfy
* \- specifies the NewSpendingInfo whose ScriptPubKey needs a
* ScriptSignature to be generated
* @return
*/
override def sign(
spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction,
spendingInfoToSatisfy: ScriptSignatureParams[TaprootKeyPathInputInfo])
: TaprootTxSigComponent = {
if (spendingInfoToSatisfy != spendingInfo) {
throw TxBuilderError.WrongSigner.exception
} else {
unsignedTx match {
case wtx: WitnessTransaction =>
val signer = spendingInfoToSatisfy.signer
val inputIndex = spendingInfoToSatisfy.inputInfo.inputIndex
val hashType = spendingInfo.hashType
val unsignedTxWitness = TransactionWitness(
wtx.witness
.updated(inputIndex,
spendingInfoToSatisfy.inputInfo.scriptWitness)
.toVector)
val unsignedWtx = wtx.copy(witness = unsignedTxWitness)
val signature: SchnorrDigitalSignature =
doSign(unsignedTx,
spendingInfo,
signer.schnorrSignWithHashType,
hashType)
val scriptWitness = TaprootKeyPath(signature)
val signedTxWitness =
wtx.witness.updated(inputIndex, scriptWitness)
val signedTx = unsignedWtx.copy(witness = signedTxWitness)
TaprootTxSigComponent(
transaction = signedTx,
inputIndex = UInt32(inputIndex),
outputMap = spendingInfoToSatisfy.inputInfo.previousOutputMap,
flags = flags)
case btx: NonWitnessTransaction =>
val wtx = WitnessTransaction.toWitnessTx(btx)
sign(spendingInfo, wtx, spendingInfoToSatisfy)
}
}
}
}
object TaprootKeyPathSigner extends TaprootKeyPathSigner

View File

@ -14,7 +14,10 @@ import org.bitcoins.crypto.{
ECPublicKeyBytes,
HashType,
NetworkElement,
Sign
PublicKey,
SchnorrDigitalSignature,
Sign,
XOnlyPubKey
}
import scala.annotation.tailrec
@ -43,7 +46,7 @@ sealed trait InputInfo {
def conditionalPath: ConditionalPath
def pubKeys: Vector[ECPublicKey]
def pubKeys: Vector[PublicKey]
def requiredSigs: Int
@ -87,7 +90,7 @@ object InputInfo {
def getRedeemScript(inputInfo: InputInfo): Option[ScriptPubKey] = {
inputInfo match {
case _: RawInputInfo | _: SegwitV0NativeInputInfo |
_: UnassignedSegwitNativeInputInfo =>
_: UnassignedSegwitNativeInputInfo | _: TaprootKeyPathInputInfo =>
None
case info: P2SHInputInfo => Some(info.redeemScript)
}
@ -99,6 +102,7 @@ object InputInfo {
case info: SegwitV0NativeInputInfo => Some(info.scriptWitness)
case info: P2SHNestedSegwitV0InputInfo => Some(info.scriptWitness)
case info: UnassignedSegwitNativeInputInfo => Some(info.scriptWitness)
case info: TaprootKeyPathInputInfo => Some(info.scriptWitness)
}
}
@ -111,7 +115,8 @@ object InputInfo {
case info: ConditionalInputInfo => info.hashPreImages
case _: UnassignedSegwitNativeInputInfo | _: EmptyInputInfo |
_: P2PKInputInfo | _: P2PKWithTimeoutInputInfo |
_: MultiSignatureInputInfo | _: P2WPKHV0InputInfo =>
_: MultiSignatureInputInfo | _: P2WPKHV0InputInfo |
_: TaprootKeyPathInputInfo =>
Vector.empty
}
}
@ -200,7 +205,8 @@ object InputInfo {
val dummyLowRHashType =
ECDigitalSignature.dummyLowR.appendHashType(HashType.sigHashAll)
info match {
case _: SegwitV0NativeInputInfo | _: UnassignedSegwitNativeInputInfo =>
case _: SegwitV0NativeInputInfo | _: UnassignedSegwitNativeInputInfo |
_: TaprootKeyPathInputInfo =>
ScriptSigLenAndStackHeight(0, 0)
case info: P2SHInputInfo =>
val serializedRedeemScript = ScriptConstant(info.redeemScript.asmBytes)
@ -263,6 +269,7 @@ object InputInfo {
(stackHeightByteSize + redeemScriptSize + scriptSigLen).toInt
case info: P2SHNestedSegwitV0InputInfo =>
maxWitnessLen(info.nestedInputInfo)
case _: TaprootKeyPathInputInfo => 65 // schnorr signature + hash type
case _: UnassignedSegwitNativeInputInfo =>
throw new IllegalArgumentException(
s"Cannot compute witness for unknown segwit InputInfo, got $info")
@ -351,6 +358,8 @@ object InputInfo {
sealed trait RawInputInfo extends InputInfo {
override def scriptPubKey: RawScriptPubKey
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
override def pubKeys: Vector[ECPublicKey]
}
object RawInputInfo {
@ -596,6 +605,8 @@ case class P2WPKHV0InputInfo(
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
override def requiredSigs: Int = 1
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
}
case class P2WSHV0InputInfo(
@ -619,6 +630,8 @@ case class P2WSHV0InputInfo(
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
override def requiredSigs: Int = nestedInputInfo.requiredSigs
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
}
case class UnassignedSegwitNativeInputInfo(
@ -630,6 +643,7 @@ case class UnassignedSegwitNativeInputInfo(
pubKeys: Vector[ECPublicKey])
extends InputInfo {
override def requiredSigs: Int = pubKeys.length
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
}
@ -642,7 +656,13 @@ sealed trait P2SHInputInfo extends InputInfo {
def nestedInputInfo: InputInfo
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
override def pubKeys: Vector[ECPublicKey] = {
val p = nestedInputInfo.pubKeys
require(
p.forall(_.isInstanceOf[ECPublicKey]),
s"Cannot have non ECPublicKey inside P2SHInputInfo, got=${nestedInputInfo.pubKeys}")
p.map(_.asInstanceOf[ECPublicKey])
}
override def requiredSigs: Int = nestedInputInfo.requiredSigs
@ -659,6 +679,8 @@ case class P2SHNonSegwitInputInfo(
override val nestedInputInfo: RawInputInfo =
RawInputInfo(outPoint, amount, redeemScript, conditionalPath, hashPreImages)
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
}
case class P2SHNestedSegwitV0InputInfo(
@ -681,4 +703,38 @@ case class P2SHNestedSegwitV0InputInfo(
scriptWitness,
conditionalPath,
hashPreImages)
override def previousOutputMap: PreviousOutputMap = PreviousOutputMap.empty
}
sealed trait TaprootInputInfo extends InputInfo {
def scriptWitness: TaprootWitness
override def scriptPubKey: TaprootScriptPubKey
override def pubKeys: Vector[XOnlyPubKey]
override def conditionalPath: ConditionalPath = ConditionalPath.NoCondition
}
case class TaprootKeyPathInputInfo(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: TaprootScriptPubKey,
previousOutputMap: PreviousOutputMap)
extends TaprootInputInfo {
require(
previousOutputMap.outputMap.exists(_._2.scriptPubKey == scriptPubKey),
s"PreviousOutputMap did not contain spk we are spending=$scriptPubKey")
require(
previousOutputMap.outputMap.exists(_._1 == outPoint),
s"PreviousOutputMap did not contain outpoint we are spending=$outPoint")
override val requiredSigs: Int = 1
override def pubKeys: Vector[XOnlyPubKey] = Vector(scriptPubKey.pubKey)
override def scriptWitness: TaprootWitness = TaprootKeyPath(
SchnorrDigitalSignature.dummy)
def inputIndex: Int = TxUtil.inputIndex(this, previousOutputMap)
}

View File

@ -3,11 +3,19 @@ package org.bitcoins.core.wallet.utxo
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.protocol.script.{
SigVersionBase,
SigVersionTaprootKeySpend,
SigVersionWitnessV0,
SignatureVersion
}
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.crypto.{HashType, Sign}
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.crypto.{
ECPublicKey,
HashType,
PublicKey,
SchnorrPublicKey,
Sign,
XOnlyPubKey
}
/** Stores the information required to generate a signature (ECSignatureParams)
* or to generate a script signature (ScriptSignatureParams) for a given
@ -33,8 +41,13 @@ sealed trait InputSigningInfo[+InputType <: InputInfo] {
.outputs(outPoint.vout.toInt)}) does match the corresponding value $amount"
)
private val keysToSignFor = inputInfo.pubKeys
require(signers.map(_.publicKey).forall(keysToSignFor.contains),
private val keysToSignFor = inputInfo.pubKeys.map {
case ec: ECPublicKey => ec.toXOnly
case s: SchnorrPublicKey => s.publicKey.toXOnly
case x: XOnlyPubKey => x.publicKey.toXOnly
case p: PublicKey => sys.error(s"Not supported=$p")
}
require(signers.map(_.publicKey.toXOnly).forall(keysToSignFor.contains),
s"Cannot have signers that do not sign for one of $keysToSignFor")
def outputReference: OutputReference = inputInfo.outputReference
@ -50,6 +63,7 @@ sealed trait InputSigningInfo[+InputType <: InputInfo] {
SigVersionWitnessV0
case _: P2SHNonSegwitInputInfo | _: RawInputInfo =>
SigVersionBase
case _: TaprootKeyPathInputInfo => SigVersionTaprootKeySpend
case i: InputInfo =>
sys.error(s"Cannot determine SigVersion for unsupported inputInfo=$i")
}

View File

@ -215,7 +215,15 @@ trait Sign extends AsyncSign {
final def schnorrSignWithHashType(
dataToSign: ByteVector,
hashType: HashType): SchnorrDigitalSignature = {
schnorrSign(dataToSign).appendHashType(hashType)
val sigNoHashType = schnorrSign(dataToSign)
if (hashType == HashType.sigHashDefault) {
// BIP341 states that if we use default sighash don't append hash type byte
// to avoid potential malleability
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-21
sigNoHashType
} else {
sigNoHashType.appendHashType(hashType)
}
}
}
@ -282,9 +290,16 @@ object Sign {
)
}
def dummySign(publicKey: ECPublicKey): Sign = {
def dummySign(publicKey: PublicKey): Sign = {
val ecPubKey = publicKey match {
case ec: ECPublicKey => ec
case schnorr: SchnorrPublicKey => schnorr.publicKey
case xonly: XOnlyPubKey => xonly.publicKey
case x: PublicKey =>
sys.error(s"Unsupported PublicKey type for dummySign, got=$x")
}
constant(ECDigitalSignature.dummyLowR,
publicKey,
ecPubKey,
SchnorrDigitalSignature.dummy)
}
}

View File

@ -420,6 +420,7 @@ sealed abstract class ScriptGenerators {
),
multiSigScriptPubKey,
p2wpkhSPKV0,
witnessScriptPubKeyV1,
unassignedWitnessScriptPubKey,
conditionalScriptPubKey(defaultMaxDepth),
multiSignatureWithTimeoutScriptPubKey
@ -465,7 +466,8 @@ sealed abstract class ScriptGenerators {
p2shScriptPubKey.map(truncate),
witnessCommitment,
conditionalScriptPubKey(defaultMaxDepth),
multiSignatureWithTimeoutScriptPubKey
multiSignatureWithTimeoutScriptPubKey,
witnessScriptPubKeyV1
)
}

View File

@ -35,7 +35,7 @@ sealed abstract class WitnessGenerators {
val spk = if (spkBytes.isEmpty) EmptyScriptPubKey else NonStandardScriptPubKey(cmpctSPK.bytes ++ spkBytes)
P2WSHWitnessV0(spk,scriptSig)
}*/
Gen.oneOf(p2wpkhWitnessV0, p2wshWitnessV0)
Gen.oneOf(p2wpkhWitnessV0, p2wshWitnessV0, taprootWitness)
}
def taprootWitness: Gen[TaprootWitness] = {