mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
2024 10 31 taproot signing (#5767)
* core: Implement TaprootKeyPath signing * core: Rebase, remove isDummySignature * Empty commit to run CI
This commit is contained in:
parent
80be2f5989
commit
67bb3ceabd
@ -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,8 +222,7 @@ class SignerTest extends BitcoinSUnitTest {
|
||||
utxos: Vector[InputSigningInfo[InputInfo]]
|
||||
): Boolean = {
|
||||
val programs: Vector[PreExecutionScriptProgram] =
|
||||
tx.inputs.zipWithIndex.toVector.map {
|
||||
case (input: TransactionInput, idx: Int) =>
|
||||
tx.inputs.zipWithIndex.map { case (input: TransactionInput, idx: Int) =>
|
||||
val utxo = utxos.find(_.outPoint == input.previousOutput).get
|
||||
createProgram(tx, idx, utxo)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 =>
|
||||
case _: TaprootKeyPath | EmptyScriptWitness =>
|
||||
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")
|
||||
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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 =>
|
||||
()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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] = {
|
||||
|
Loading…
Reference in New Issue
Block a user