Signer UTXOSpendingInfo refactor (#830)

* Refactored Signer interface to use TxSigComponent and UTXOSpendingInfo

Fixed all the broken things

Replaced TxSigComponent with unsigned Transaction in Signer interface to remove illegal states

* Added p2sh throw case in Signer
This commit is contained in:
Nadav Kohen 2019-10-25 15:40:33 -05:00 committed by Chris Stewart
parent 19783ea546
commit 1b51ad689f
2 changed files with 146 additions and 227 deletions

View File

@ -6,7 +6,9 @@ import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.script.flag.ScriptFlag
import org.bitcoins.core.wallet.builder.TxBuilderError
import org.bitcoins.core.wallet.utxo.UTXOSpendingInfo
import scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
@ -16,50 +18,37 @@ sealed abstract class Signer {
/**
* The method used to sign a bitcoin unspent transaction output
* @param signers the [[Signer]] needed to sign the utxo
* @param output the utxo we are spending
* @param unsignedTx the unsigned transaction which is spending the utxo
* @param inputIndex the input index inside of the unsigned transaction which spends the utxo
* @param hashType the signature hashing algorithm we should use to sign the utxo
* @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
* @return
*/
def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
sign(signers,
output,
unsignedTx,
inputIndex,
hashType,
isDummySignature,
overrides = NestedSigning.NoOverrides)
sign(
spendingInfo,
unsignedTx,
isDummySignature,
scriptPubKeyToSatisfy = spendingInfo.output.scriptPubKey
)
}
/**
* The method used to sign a bitcoin unspent transaction output that is potentially nested
* @param signers the [[Signer]] needed to sign the utxo
* @param output the utxo we are spending
* @param unsignedTx the unsigned transaction which is spending the utxo
* @param inputIndex the input index inside of the unsigned transaction which spends the utxo
* @param hashType the signature hashing algorithm we should use to sign the utxo
* @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 overrides specifies things that should be overriden when signing such as TxSigComponent if this is nested
* @param scriptPubKeyToSatisfy - specifies the ScriptPubKey for which a ScriptSignature needs to be generated
* @return
*/
def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent]
def doSign(
@ -74,80 +63,61 @@ sealed abstract class Signer {
TransactionSignatureCreator.createSig(sigComponent, sign, hashType)
}
}
}
/** For use when signing nested ScriptPubKeys.
*
* This will require the hash to be signed to come from some external
* (to the Signer being used) TxSigComponent (think P2SH, P2WSH).
*
* This will also require that the ScriptPubKey that the nested Signer
* looks at needs to be overriden (not output.scriptPubKey)
*/
sealed abstract class OverridesForNestedSigning {
protected val flags: Seq[ScriptFlag] = Policy.standardFlags
/** Depending on the external [[ScriptPubKey scriptpubkey]] type, there
* are different rules for which script is meant to be signed.
*
* For instance, if you are spending a [[P2PKHScriptPubKey p2pkh]]
* you sign the p2pkh script itself.
*
* If you are signing a [[P2WPKHWitnessSPKV0 p2wpkh spk]], you sign the
* re-constructed [[P2PKHScriptPubKey p2pkh]] that is associated with p2wpkh script.
*
* If you are signing a [[LockTimeScriptPubKey locktime spk]], you must sign
* the entire [[LockTimeScriptPubKey]], not the nested script.
*/
def sigComponentToSignOpt: Option[TxSigComponent]
/** This represents the [[ScriptPubKey scriptpubkey]] for which a valid ScriptSignature is
* being generated. This is usually some nested SPK such as a redeem script in P2SH and P2WSH.
*/
def scriptPubKeyToSatisfyOpt: Option[ScriptPubKey]
}
object NestedSigning {
/** The NoOverrides case where a Signer is used raw, without nesting */
case object NoOverrides extends OverridesForNestedSigning {
override val sigComponentToSignOpt: Option[TxSigComponent] = None
override val scriptPubKeyToSatisfyOpt: Option[ScriptPubKey] = None
protected def relevantInfo(
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction): (Seq[Sign], TransactionOutput, UInt32, HashType) = {
(spendingInfo.signers,
spendingInfo.output,
inputIndex(spendingInfo, unsignedTx),
spendingInfo.hashType)
}
/** For P2WSH signing, this will be passed to the nested Signer */
case class P2WSHOverrides(
externalSigComponent: TxSigComponent,
redeemScript: ScriptPubKey)
extends OverridesForNestedSigning {
override val sigComponentToSignOpt: Option[TxSigComponent] = Some(
externalSigComponent)
override val scriptPubKeyToSatisfyOpt: Option[ScriptPubKey] = Some(
redeemScript)
protected def inputIndex(
spendingInfo: UTXOSpendingInfo,
tx: Transaction): UInt32 = {
tx.inputs.zipWithIndex
.find(_._1.previousOutput == spendingInfo.outPoint) match {
case Some((_, index)) => UInt32(index)
case None =>
throw new IllegalArgumentException(
"Transaction did not contain expected input.")
}
}
/** For Locktime signing, this will be passed to the nested Signer.
*
* @param nestedSPK This should be [[LockTimeScriptPubKey.nestedScriptPubKey]]
*/
case class LockTimeOverrides(nestedSPK: ScriptPubKey)
extends OverridesForNestedSigning {
override val sigComponentToSignOpt: Option[TxSigComponent] = None
override val scriptPubKeyToSatisfyOpt: Option[ScriptPubKey] = Some(
nestedSPK)
}
protected def sigComponent(
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction): TxSigComponent = {
val index = inputIndex(spendingInfo, unsignedTx)
/** For signing when a LockTime script is nested, such as within P2WSH.
*
* @param nestedSPK This should be [[LockTimeScriptPubKey.nestedScriptPubKey]]
*/
case class NestedLockTimeOverrides(
externalSigComponent: TxSigComponent,
nestedSPK: ScriptPubKey)
extends OverridesForNestedSigning {
override val sigComponentToSignOpt: Option[TxSigComponent] = Some(
externalSigComponent)
override val scriptPubKeyToSatisfyOpt: Option[ScriptPubKey] = Some(
nestedSPK)
spendingInfo.output.scriptPubKey match {
case _: WitnessScriptPubKey =>
val wtx = unsignedTx match {
case btx: BaseTransaction =>
val transactionWitnessOpt =
spendingInfo.scriptWitnessOpt.map(scriptWit =>
TransactionWitness(Vector(scriptWit)))
val transactionWitness =
transactionWitnessOpt.getOrElse(EmptyWitness)
WitnessTransaction(btx.version,
btx.inputs,
btx.outputs,
btx.lockTime,
transactionWitness)
case wtx: WitnessTransaction => wtx
}
WitnessTxSigComponent(wtx, index, spendingInfo.output, flags)
case _: P2SHScriptPubKey =>
throw new IllegalStateException(
"Signers do not currently interface with P2SH as this is handled externally in TxBuilder.scala"
)
case _: ScriptPubKey =>
BaseTxSigComponent(unsignedTx, index, spendingInfo.output, flags)
}
}
}
@ -158,34 +128,26 @@ sealed abstract class BitcoinSigner extends Signer
sealed abstract class P2PKSigner extends BitcoinSigner {
override def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val spk = overrides.scriptPubKeyToSatisfyOpt match {
case None => output.scriptPubKey
case Some(nestedScript) => nestedScript
}
val (signers, output, inputIndex, hashType) =
relevantInfo(spendingInfo, unsignedTx)
if (signers.size != 1) {
Future.fromTry(TxBuilderError.TooManySigners)
} else {
val sign: ByteVector => Future[ECDigitalSignature] =
signers.head.signFunction
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val signed: Future[TxSigComponent] = spk match {
val signed: Future[TxSigComponent] = scriptPubKeyToSatisfy match {
case _: P2PKScriptPubKey =>
val sigComponent = overrides.sigComponentToSignOpt match {
case None =>
BaseTxSigComponent(unsignedTx, inputIndex, output, flags)
case Some(sigComponent) => sigComponent
}
val signature = doSign(sigComponent, sign, hashType, isDummySignature)
val signature = doSign(sigComponent(spendingInfo, unsignedTx),
sign,
hashType,
isDummySignature)
signature.map { sig =>
val p2pkScriptSig = P2PKScriptSignature(sig)
val signedInput = TransactionInput(unsignedInput.previousOutput,
@ -226,38 +188,30 @@ object P2PKSigner extends P2PKSigner
sealed abstract class P2PKHSigner extends BitcoinSigner {
override def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val spk = overrides.scriptPubKeyToSatisfyOpt match {
case None => output.scriptPubKey
case Some(nestedScript) => nestedScript
}
val (signers, output, inputIndex, hashType) =
relevantInfo(spendingInfo, unsignedTx)
if (signers.size != 1) {
Future.fromTry(TxBuilderError.TooManySigners)
} else {
val sign = signers.head.signFunction
val pubKey = signers.head.publicKey
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val signed: Future[TxSigComponent] = spk match {
val signed: Future[TxSigComponent] = scriptPubKeyToSatisfy match {
case p2pkh: P2PKHScriptPubKey =>
if (p2pkh != P2PKHScriptPubKey(pubKey)) {
Future.fromTry(TxBuilderError.WrongPublicKey)
} else {
val sigComponent = overrides.sigComponentToSignOpt match {
case None =>
BaseTxSigComponent(unsignedTx, inputIndex, output, flags)
case Some(sigComponent) => sigComponent
}
val signature =
doSign(sigComponent, sign, hashType, isDummySignature)
doSign(sigComponent(spendingInfo, unsignedTx),
sign,
hashType,
isDummySignature)
signature.map { sig =>
val p2pkhScriptSig = P2PKHScriptSignature(sig, pubKey)
val signedInput = TransactionInput(unsignedInput.previousOutput,
@ -298,37 +252,30 @@ object P2PKHSigner extends P2PKHSigner
sealed abstract class MultiSigSigner extends BitcoinSigner {
override def sign(
signersWithPubKeys: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val spk = overrides.scriptPubKeyToSatisfyOpt match {
case None => output.scriptPubKey
case Some(nestedScript) => nestedScript
}
val (signersWithPubKeys, output, inputIndex, hashType) =
relevantInfo(spendingInfo, unsignedTx)
val signers = signersWithPubKeys.map(_.signFunction)
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val signed: Future[TxSigComponent] = spk match {
val signed: Future[TxSigComponent] = scriptPubKeyToSatisfy match {
case multiSigSPK: MultiSignatureScriptPubKey =>
val requiredSigs = multiSigSPK.requiredSigs
if (signers.size < requiredSigs) {
Future.fromTry(TxBuilderError.WrongSigner)
} else {
val sigComponent = overrides.sigComponentToSignOpt match {
case None =>
BaseTxSigComponent(unsignedTx, inputIndex, output, flags)
case Some(sigComponent) => sigComponent
}
val signaturesNested = 0
.until(requiredSigs)
.map(i =>
doSign(sigComponent, signers(i), hashType, isDummySignature))
.map(
i =>
doSign(sigComponent(spendingInfo, unsignedTx),
signers(i),
hashType,
isDummySignature))
val signatures = Future.sequence(signaturesNested)
signatures.map { sigs =>
val multiSigScriptSig = MultiSignatureScriptSignature(sigs)
@ -350,10 +297,7 @@ sealed abstract class MultiSigSigner extends BitcoinSigner {
wtx.lockTime,
wtx.witness)
}
BaseTxSigComponent(signedTx,
inputIndex,
output,
Policy.standardFlags)
BaseTxSigComponent(signedTx, inputIndex, output, flags)
}
}
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: P2SHScriptPubKey |
@ -370,18 +314,18 @@ sealed abstract class MultiSigSigner extends BitcoinSigner {
object MultiSigSigner extends MultiSigSigner
sealed abstract class P2WPKHSigner extends BitcoinSigner {
override def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
implicit ec: ExecutionContext): Future[TxSigComponent] =
if (overrides != NestedSigning.NoOverrides) {
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
if (scriptPubKeyToSatisfy != spendingInfo.output.scriptPubKey) {
Future.fromTry(TxBuilderError.NestedWitnessSPK)
} else {
val (signers, output, inputIndex, hashType) =
relevantInfo(spendingInfo, unsignedTx)
unsignedTx match {
case wtx: WitnessTransaction =>
if (signers.size != 1) {
@ -424,7 +368,7 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner {
val wtxComp = WitnessTxSigComponentRaw(unsignedWtx,
inputIndex,
witOutput,
Policy.standardFlags)
flags)
val signature = doSign(wtxComp, sign, hashType, isDummySignature)
@ -437,10 +381,7 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner {
unsignedWtx.outputs,
unsignedWtx.lockTime,
signedTxWitness)
WitnessTxSigComponentRaw(signedTx,
inputIndex,
witOutput,
Policy.standardFlags)
WitnessTxSigComponentRaw(signedTx, inputIndex, witOutput, flags)
}
}
@ -452,27 +393,27 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner {
btx.outputs,
btx.lockTime,
EmptyWitness)
sign(signers, output, wtx, inputIndex, hashType, isDummySignature)
sign(spendingInfo, wtx, isDummySignature)
}
}
}
}
object P2WPKHSigner extends P2WPKHSigner
sealed abstract class P2WSHSigner extends BitcoinSigner {
override def sign(
signers: Seq[Sign],
output: TransactionOutput,
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
if (overrides != NestedSigning.NoOverrides) {
if (scriptPubKeyToSatisfy != spendingInfo.output.scriptPubKey) {
Future.fromTry(TxBuilderError.NestedWitnessSPK)
} else {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
val spk = output.scriptPubKey
val flags = Policy.standardFlags
spk match {
case _: P2WSHWitnessSPKV0 =>
@ -491,8 +432,7 @@ sealed abstract class P2WSHSigner extends BitcoinSigner {
Future.fromTry(TxBuilderError.NoRedeemScript)
case EmptyScriptWitness => Future.fromTry(TxBuilderError.NoWitness)
}
val sigComponent =
WitnessTxSigComponentRaw(wtx, inputIndex, output, flags)
val signerF: Future[Signer] = redeemScriptF.flatMap {
case _: P2PKScriptPubKey => Future.successful(P2PKSigner)
case _: P2PKHScriptPubKey => Future.successful(P2PKHSigner)
@ -510,14 +450,7 @@ sealed abstract class P2WSHSigner extends BitcoinSigner {
}
val signedSigComponentF = signerF.flatMap { signer =>
redeemScriptF.flatMap { redeemScript =>
signer.sign(
signers,
output,
wtx,
inputIndex,
hashType,
isDummySignature,
NestedSigning.P2WSHOverrides(sigComponent, redeemScript))
signer.sign(spendingInfo, wtx, isDummySignature, redeemScript)
}
}
@ -552,21 +485,14 @@ sealed abstract class P2WSHSigner extends BitcoinSigner {
object P2WSHSigner extends P2WSHSigner
sealed abstract class LockTimeSigner extends BitcoinSigner {
override def sign(
signers: Seq[Sign],
output: TransactionOutput,
unsignedTx: Transaction,
inputIndex: UInt32,
hashType: HashType,
isDummySignature: Boolean,
overrides: OverridesForNestedSigning)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val spk = overrides.scriptPubKeyToSatisfyOpt match {
case None => output.scriptPubKey
case Some(nestedScript) => nestedScript
}
spk match {
override def sign(
spendingInfo: UTXOSpendingInfo,
unsignedTx: Transaction,
isDummySignature: Boolean,
scriptPubKeyToSatisfy: ScriptPubKey)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
scriptPubKeyToSatisfy match {
case lockSPK: LockTimeScriptPubKey =>
val signerF = lockSPK.nestedScriptPubKey match {
case _: P2PKScriptPubKey => Future.successful(P2PKSigner)
@ -580,23 +506,10 @@ sealed abstract class LockTimeSigner extends BitcoinSigner {
Future.fromTry(TxBuilderError.WrongSigner)
}
signerF.flatMap { signer =>
val overridesForNestedSigning =
overrides.sigComponentToSignOpt match {
case None =>
NestedSigning.LockTimeOverrides(lockSPK.nestedScriptPubKey)
case Some(sigComponent) =>
NestedSigning.NestedLockTimeOverrides(
externalSigComponent = sigComponent,
lockSPK.nestedScriptPubKey)
}
signer.sign(signers,
output,
signer.sign(spendingInfo,
unsignedTx,
inputIndex,
hashType,
isDummySignature,
overridesForNestedSigning)
lockSPK.nestedScriptPubKey)
}
case _: P2SHScriptPubKey => Future.fromTry(TxBuilderError.NestedP2SHSPK)
case _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 =>

View File

@ -11,6 +11,7 @@ import org.bitcoins.core.script.constant.ScriptNumber
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.core.wallet.signer.{MultiSigSigner, P2PKHSigner, P2PKSigner}
import org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfo
import org.scalacheck.Gen
import scala.concurrent.Await
@ -296,13 +297,14 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
creditingTx,
scriptSig,
outputIndex)
txSigComponentFuture = P2PKSigner.sign(
Seq(privateKey),
spendingInfo = BitcoinUTXOSpendingInfo(
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
creditingTx.outputs(outputIndex.toInt),
spendingTx,
inputIndex,
hashType,
false)
Seq(privateKey),
None,
None,
hashType)
txSigComponentFuture = P2PKSigner.sign(spendingInfo, spendingTx, false)
txSigComponent = Await.result(txSigComponentFuture, timeout)
//add the signature to the scriptSig instead of having an empty scriptSig
signedScriptSig = txSigComponent.scriptSignature
@ -330,13 +332,14 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
creditingTx,
EmptyScriptSignature,
outputIndex)
txSigComponentFuture = P2PKHSigner.sign(
Seq(privateKey),
spendingInfo = BitcoinUTXOSpendingInfo(
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
creditingTx.outputs(outputIndex.toInt),
unsignedTx,
inputIndex,
hashType,
false)
Seq(privateKey),
None,
None,
hashType)
txSigComponentFuture = P2PKHSigner.sign(spendingInfo, unsignedTx, false)
txSigComponent = Await.result(txSigComponentFuture, timeout)
signedScriptSig = txSigComponent.scriptSignature
.asInstanceOf[P2PKHScriptSignature]
@ -369,13 +372,16 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
creditingTx,
scriptSig,
outputIndex)
txSigComponentFuture = MultiSigSigner.sign(
privateKeys,
spendingInfo = BitcoinUTXOSpendingInfo(
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
creditingTx.outputs(outputIndex.toInt),
spendingTx,
inputIndex,
hashType,
false)
privateKeys,
None,
None,
hashType)
txSigComponentFuture = MultiSigSigner.sign(spendingInfo,
spendingTx,
false)
txSigComponent = Await.result(txSigComponentFuture, timeout)
signedScriptSig = txSigComponent.scriptSignature
.asInstanceOf[MultiSignatureScriptSignature]