diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ConditionalScriptSignatureTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ConditionalScriptSignatureTest.scala new file mode 100644 index 0000000000..a1a1f48106 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ConditionalScriptSignatureTest.scala @@ -0,0 +1,51 @@ +package org.bitcoins.core.protocol.script + +import org.bitcoins.core.script.constant.{OP_FALSE, OP_TRUE} +import org.bitcoins.testkit.core.gen.{NumberGenerator, ScriptGenerators} +import org.bitcoins.testkit.util.BitcoinSUnitTest + +class ConditionalScriptSignatureTest extends BitcoinSUnitTest { + behavior of "ConditionalScriptSignature" + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = { + generatorDrivenConfigNewCode + } + + it should "correctly read true and false" in { + val trueScriptSig = ConditionalScriptSignature.fromAsm(Vector(OP_TRUE)) + val falseScriptSig = ConditionalScriptSignature.fromAsm(Vector(OP_FALSE)) + + assert(trueScriptSig.isTrue) + assert(!trueScriptSig.isFalse) + assert(!falseScriptSig.isTrue) + assert(falseScriptSig.isFalse) + assert(trueScriptSig.nestedScriptSig == EmptyScriptSignature) + assert(falseScriptSig.nestedScriptSig == EmptyScriptSignature) + } + + it should "have serialization symmetry" in { + forAll(ScriptGenerators.conditionalScriptSignature) { + conditionalScriptSignature => + assert( + ConditionalScriptSignature(conditionalScriptSignature.bytes) == conditionalScriptSignature) + } + } + + it should "have agreement with nesting ScriptSignatures" in { + forAll(ScriptGenerators.scriptSignature, NumberGenerator.bool) { + case (scriptSig, condition) => + val conditionalScriptSig = + ConditionalScriptSignature(scriptSig, condition) + + assert(conditionalScriptSig.nestedScriptSig == scriptSig) + } + } + + it should "have agreement with nested signatures" in { + forAll(ScriptGenerators.conditionalScriptSignature) { + conditionalScriptSignature => + assert( + conditionalScriptSignature.signatures == conditionalScriptSignature.nestedScriptSig.signatures) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/LockTimeScriptSignatureTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/LockTimeScriptSignatureTest.scala new file mode 100644 index 0000000000..7f4bfa13a2 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/LockTimeScriptSignatureTest.scala @@ -0,0 +1,29 @@ +package org.bitcoins.core.protocol.script + +import org.bitcoins.testkit.core.gen.ScriptGenerators +import org.bitcoins.testkit.util.BitcoinSUnitTest + +class LockTimeScriptSignatureTest extends BitcoinSUnitTest { + behavior of "LockTimeScriptSignature" + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = { + generatorDrivenConfigNewCode + } + + it should "have agreement with nesting ScriptSignatures" in { + forAll(ScriptGenerators.scriptSignature) { scriptSig => + val csvScriptSig = CSVScriptSignature(scriptSig) + val cltvScriptSig = CLTVScriptSignature(scriptSig) + + assert(csvScriptSig.scriptSig == scriptSig) + assert(cltvScriptSig.scriptSig == scriptSig) + } + } + + it should "have agreement with nested signatures" in { + forAll(ScriptGenerators.lockTimeScriptSig) { lockTimeScriptSignature => + assert( + lockTimeScriptSignature.signatures == lockTimeScriptSignature.scriptSig.signatures) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptSignatureTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptSignatureTest.scala index 73c9c5cf94..59b2db6ef6 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptSignatureTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptSignatureTest.scala @@ -15,4 +15,10 @@ class MultiSignatureScriptSignatureTest extends BitcoinSUnitTest { scriptSig.signatures.size must be(2) } + it must "Fail validation if asm is empty" in { + assert( + !MultiSignatureScriptSignature.isMultiSignatureScriptSignature( + Vector.empty)) + } + } diff --git a/core-test/src/test/scala/org/bitcoins/core/script/ScriptTypeTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/ScriptTypeTest.scala new file mode 100644 index 0000000000..472411bf19 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/script/ScriptTypeTest.scala @@ -0,0 +1,22 @@ +package org.bitcoins.core.script + +import org.bitcoins.testkit.util.BitcoinSUnitTest + +class ScriptTypeTest extends BitcoinSUnitTest { + behavior of "ScriptType" + + it must "have serialization symmetry" in { + ScriptType.all.foreach { scriptType => + val newScriptType = ScriptType.fromString(scriptType.toString) + + assert(newScriptType.contains(scriptType)) + } + } + + it must "fail when nonsense ScriptType is used" in { + val lyrics = "Never gonna give you up, never gonna let you down" + + assert(ScriptType.fromString(lyrics).isEmpty) + assertThrows[IllegalArgumentException](ScriptType.fromStringExn(lyrics)) + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala new file mode 100644 index 0000000000..fb446bef91 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala @@ -0,0 +1,63 @@ +package org.bitcoins.core.wallet.signer + +import org.bitcoins.core.protocol.script.WitnessScriptPubKey +import org.bitcoins.core.wallet.utxo.{ + P2SHSpendingInfo, + P2WPKHV0SpendingInfo, + P2WSHV0SpendingInfo, + UnassignedSegwitNativeUTXOSpendingInfo +} +import org.bitcoins.testkit.core.gen.{CreditingTxGen, TransactionGenerators} +import org.bitcoins.testkit.util.{BitcoinSAsyncTest, BitcoinSUnitTest} + +import scala.concurrent.ExecutionContext + +class SignerTest extends BitcoinSAsyncTest { + + implicit val ec: ExecutionContext = ExecutionContext.global + + behavior of "Signer" + + it should "fail to sign a UnassignedSegwit UTXO" in { + val p2wpkh = CreditingTxGen.p2wpkhOutput.sample.get + val tx = TransactionGenerators.baseTransaction.sample.get + val spendingInfo = UnassignedSegwitNativeUTXOSpendingInfo( + p2wpkh.outPoint, + p2wpkh.amount, + p2wpkh.scriptPubKey.asInstanceOf[WitnessScriptPubKey], + p2wpkh.signers, + p2wpkh.hashType, + p2wpkh.scriptWitnessOpt.get, + p2wpkh.conditionalPath + ) + assertThrows[UnsupportedOperationException]( + BitcoinSigner.sign(spendingInfo, tx, isDummySignature = false)) + } + + it should "fail to sign a P2SH UTXO" in { + val p2sh = CreditingTxGen.p2shOutput.sample.get + val tx = TransactionGenerators.baseTransaction.sample.get + assertThrows[IllegalArgumentException]( + BitcoinSigner.sign(p2sh, tx, isDummySignature = false)) + } + + it should "fail if there are inconsistent P2WPKH spending infos" in { + val dumbSpendingInfo = CreditingTxGen.output.sample.get + val p2wpkh = + CreditingTxGen.p2wpkhOutput.sample.get.asInstanceOf[P2WPKHV0SpendingInfo] + val tx = TransactionGenerators.baseTransaction.sample.get + recoverToSucceededIf[IllegalArgumentException] { + P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh) + } + } + + it should "fail if there are inconsistent P2WSH spending infos" in { + val dumbSpendingInfo = CreditingTxGen.output.sample.get + val p2wsh = + CreditingTxGen.p2wshOutput.sample.get.asInstanceOf[P2WSHV0SpendingInfo] + val tx = TransactionGenerators.baseTransaction.sample.get + recoverToSucceededIf[IllegalArgumentException] { + P2WSHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wsh) + } + } +} diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index 1fd31c2b25..1d6244fcaa 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -177,15 +177,6 @@ sealed trait P2SHScriptSignature extends ScriptSignature { sigs.map(s => ECDigitalSignature(s.hex)) } - /** - * Splits the given asm into two parts - * the first part is the digital signatures - * the second part is the redeem script - */ - def splitAtRedeemScript: (Seq[ScriptToken], Seq[ScriptToken]) = { - (scriptSignatureNoRedeemScript.asm, redeemScript.asm) - } - override def toString = "P2SHScriptSignature(" + hex + ")" } @@ -225,10 +216,8 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] { } /** Tests if the given asm tokens are a [[P2SHScriptSignature]] */ - def isP2SHScriptSig(asm: Seq[ScriptToken]): Boolean = asm match { - case _ if asm.size > 1 && isRedeemScript(asm.last) => true - case _ if WitnessScriptPubKey.isWitnessScriptPubKey(asm) => true - case _ => false + def isP2SHScriptSig(asm: Seq[ScriptToken]): Boolean = { + asm.size > 1 && isRedeemScript(asm.last) } /** Detects if the given script token is a redeem script */ @@ -508,9 +497,7 @@ object ScriptSignature extends ScriptFactory[ScriptSignature] { /** Creates a scriptSignature from the list of script tokens */ def fromAsm(tokens: Seq[ScriptToken]): ScriptSignature = tokens match { case Nil => EmptyScriptSignature - case _ - if (tokens.size > 1 && P2SHScriptSignature.isRedeemScript( - tokens.last)) => + case _ if P2SHScriptSignature.isP2SHScriptSig(tokens) => P2SHScriptSignature.fromAsm(tokens) case _ if ConditionalScriptSignature.isValidConditionalScriptSig(tokens) => ConditionalScriptSignature.fromAsm(tokens) @@ -524,41 +511,4 @@ object ScriptSignature extends ScriptFactory[ScriptSignature] { P2PKScriptSignature.fromAsm(tokens) case _ => NonStandardScriptSignature.fromAsm(tokens) } - - /** - * Creates a script signature from the given tokens and scriptPubKey - * @param tokens the script signature's tokens - * @param scriptPubKey the scriptPubKey which the script signature is trying to spend - * @return - */ - def fromScriptPubKey( - tokens: Seq[ScriptToken], - scriptPubKey: ScriptPubKey): Try[ScriptSignature] = scriptPubKey match { - case _: P2SHScriptPubKey => Try(P2SHScriptSignature.fromAsm(tokens)) - case _: P2PKHScriptPubKey => Try(P2PKHScriptSignature.fromAsm(tokens)) - case _: P2PKScriptPubKey => Try(P2PKScriptSignature.fromAsm(tokens)) - case _: MultiSignatureScriptPubKey => - Try(MultiSignatureScriptSignature.fromAsm(tokens)) - case _: NonStandardScriptPubKey => - Try(NonStandardScriptSignature.fromAsm(tokens)) - case s: CLTVScriptPubKey => fromScriptPubKey(tokens, s.nestedScriptPubKey) - case s: CSVScriptPubKey => fromScriptPubKey(tokens, s.nestedScriptPubKey) - case _: ConditionalScriptPubKey => - Try(ConditionalScriptSignature.fromAsm(tokens)) - case _: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey => - Success(EmptyScriptSignature) - case EmptyScriptPubKey => - if (tokens.isEmpty) Success(EmptyScriptSignature) - else Try(NonStandardScriptSignature.fromAsm(tokens)) - case _: WitnessCommitment => - Failure( - new IllegalArgumentException( - "Cannot spend witness commitment scriptPubKey")) - } - - def apply( - tokens: Seq[ScriptToken], - scriptPubKey: ScriptPubKey): Try[ScriptSignature] = { - fromScriptPubKey(tokens, scriptPubKey) - } } diff --git a/core/src/main/scala/org/bitcoins/core/script/ScriptType.scala b/core/src/main/scala/org/bitcoins/core/script/ScriptType.scala index cac82da3eb..5f3e046e81 100644 --- a/core/src/main/scala/org/bitcoins/core/script/ScriptType.scala +++ b/core/src/main/scala/org/bitcoins/core/script/ScriptType.scala @@ -9,7 +9,7 @@ package org.bitcoins.core.script */ sealed abstract class ScriptType { import org.bitcoins.core.script.ScriptType._ - override def toString = this match { + override def toString: String = this match { case NONSTANDARD => "nonstandard" case PUBKEY => "pubkey" case PUBKEYHASH => "pubkeyhash" @@ -30,15 +30,15 @@ sealed abstract class ScriptType { * from Bitcoin Core */ object ScriptType { - private val all: Seq[ScriptType] = Vector(NONSTANDARD, - PUBKEY, - PUBKEYHASH, - SCRIPTHASH, - MULTISIG, - NULLDATA, - WITNESS_V0_KEYHASH, - WITNESS_V0_SCRIPTHASH, - WITNESS_UNKNOWN) + private[script] val all: Seq[ScriptType] = Vector(NONSTANDARD, + PUBKEY, + PUBKEYHASH, + SCRIPTHASH, + MULTISIG, + NULLDATA, + WITNESS_V0_KEYHASH, + WITNESS_V0_SCRIPTHASH, + WITNESS_UNKNOWN) def fromString(string: String): Option[ScriptType] = all.find(_.toString == string) diff --git a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala index 7f02a603be..5a64710d3f 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala @@ -348,10 +348,7 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner[P2WPKHV0SpendingInfo] { unsignedTxWitness) val witSPK = output.scriptPubKey match { - case p2wpkh: P2WPKHWitnessSPKV0 => - if (p2wpkh != P2WPKHWitnessSPKV0(pubKey)) { - Future.fromTry(TxBuilderError.WrongPublicKey) - } else Future.successful(p2wpkh) + case p2wpkh: P2WPKHWitnessSPKV0 => Future.successful(p2wpkh) case _: UnassignedWitnessScriptPubKey | _: P2WSHWitnessSPKV0 => Future.fromTry(TxBuilderError.WrongSigner) case _: NonWitnessScriptPubKey =>