diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala index 3c60729c59..3c26804176 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala @@ -419,6 +419,13 @@ class TransactionTest extends BitcoinSUnitTest { assert(tx.hex == hex) } + it must "parse 109c3834d74fc4788b42ca884f89c3fe3f95c9bba6d1b5b10ec4d3f426a7d12a" in { + val hex = + "0100000001214a8d0ae8eecd5a42dccfe5d9e9ab4738f28237651ed697139836007419e56b000000006b483045022100f501ce60b5b26d6b24f987378b32d64ce47124004f4e63be06a7748fcc078c160220444619b548700b6105bc896b36017d0f01999f5020ca1eeca3c313e9c2e3252c012103bc161c3a4663978ebfb7561588c85414fbcce0222ed19cdd525267e4ba1bbd37ffffffff0280841e0000000000035152ae50604302000000001976a914c2e88b055bdaae6c7996db9784433e9e7cbc96e088ac00000000" + val tx = Transaction.fromHex(hex) + assert(tx.hex == hex) + } + private def findInput( tx: Transaction, outPoint: TransactionOutPoint): Option[(TransactionInput, Int)] = { diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala index 2679ed6815..aa6ff1698d 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala @@ -177,10 +177,7 @@ sealed trait MultiSignatureScriptPubKey extends RawScriptPubKey { /** Returns the public keys encoded into the `scriptPubKey` */ def publicKeys: Seq[ECPublicKeyBytes] = { - asm - .filter(_.isInstanceOf[ScriptConstant]) - .slice(1, maxSigs + 1) - .map(key => ECPublicKeyBytes(key.bytes)) + MultiSignatureScriptPubKey.parsePublicKeys(maxSigs, asm) } override def toString = s"multi($requiredSigs,${publicKeys.mkString(",")})" @@ -252,36 +249,35 @@ object MultiSignatureScriptPubKey val containsMultiSigOp = cmsIdx != -1 if (asm.nonEmpty && containsMultiSigOp) { + //we need either the first or second asm operation to indicate how many signatures are required - val hasRequiredSignaturesTry = Try { + val hasRequiredSignaturesOpt: Option[Int] = { asm.headOption match { - case None => false + case None => None case Some(token) => //this is for the case that we have more than 16 public keys, the //first operation will be a push op, the second operation being the actual number of keys - if (token.isInstanceOf[BytesToPushOntoStack]) { - isValidPubKeyNumber(asm.tail.head) - } else isValidPubKeyNumber(token) + token match { + case _: BytesToPushOntoStack => + isValidPubKeyNumber(asm.tail.head) + case _ => isValidPubKeyNumber(token) + } } } //the second to last asm operation should be the maximum amount of public keys - val hasMaximumSignaturesTry = { + val hasMaximumSignaturesOpt: Option[Int] = { val maxSigsIdx = asm.length - 2 if (maxSigsIdx >= cmsIdx) { - val exn = new IllegalAccessException( - s"maxSigsIdx is after OP_CHECKMULTISIG/OP_CHECKMULTISIGVERIFY, maxSigsIx=$maxSigsIdx") - Failure(exn) + None } else { - Try { - asm(maxSigsIdx) match { - case token: ScriptToken => isValidPubKeyNumber(token) - } + asm(maxSigsIdx) match { + case token: ScriptToken => isValidPubKeyNumber(token) } } } - (hasRequiredSignaturesTry, hasMaximumSignaturesTry) match { - case (Success(hasRequiredSignatures), Success(hasMaximumSignatures)) => + (hasRequiredSignaturesOpt, hasMaximumSignaturesOpt) match { + case (Some(_), Some(maximumSignatures)) => val isStandardOps = asm.forall(op => op.isInstanceOf[ScriptConstant] || op .isInstanceOf[BytesToPushOntoStack] || op @@ -289,32 +285,57 @@ object MultiSignatureScriptPubKey op == OP_CHECKMULTISIGVERIFY) val result = - asm.nonEmpty && containsMultiSigOp && hasRequiredSignatures && - hasMaximumSignatures && isStandardOps + asm.nonEmpty && containsMultiSigOp && + hasMaximumSignatures(maximumSignatures, asm) && isStandardOps result - case (Success(_), Failure(_)) => false - case (Failure(_), Success(_)) => false - case (Failure(_), Failure(_)) => false + case (Some(_), None) => false + case (None, Some(_)) => false + case (None, None) => false } } else { false } + } + def parsePublicKeys( + maxSigs: Int, + asm: Seq[ScriptToken]): Seq[ECPublicKeyBytes] = { + asm + .filter(_.isInstanceOf[ScriptConstant]) + .slice(1, maxSigs + 1) + .map(t => ECPublicKeyBytes(t.bytes)) + } + + private def hasMaximumSignatures( + maxSigs: Int, + asm: Seq[ScriptToken]): Boolean = { + parsePublicKeys(maxSigs, asm).size == maxSigs } /** Checks that the given script token is with the range of the maximum amount of * public keys we can have in a * [[org.bitcoins.core.protocol.script.MultiSignatureScriptPubKey MultiSignatureScriptPubKey]] */ - @tailrec private def isValidPubKeyNumber(token: ScriptToken): Boolean = { + @tailrec private def isValidPubKeyNumber(token: ScriptToken): Option[Int] = { token match { case sn: ScriptNumber => - sn >= ScriptNumber.zero && sn <= ScriptNumber( - Consensus.maxPublicKeysPerMultiSig) + if ( + sn >= ScriptNumber.zero && sn <= ScriptNumber( + Consensus.maxPublicKeysPerMultiSig) + ) { + Some(sn.toInt) + } else { + None + } case constant: ScriptConstant => - val sn = ScriptNumber(constant.bytes) - isValidPubKeyNumber(sn) - case _: ScriptToken => false + //Consensus.maxPublicKeysPerMultiSig is at most 20, which can be represented by 2 bytes + if (constant.bytes.size <= 2) { + val sn = ScriptNumber.fromBytes(constant.bytes) + isValidPubKeyNumber(sn) + } else { + None + } + case _: ScriptToken => None } } }