mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
2024 01 23 Validate we have maxSigs
number of public keys in MultiSignatureScriptPubKey
(#5369)
* Add test case for tx for 109c38 * Check that we have maxSigs' public keys in OP_CMS.isValidAsm() * Fix val names
This commit is contained in:
parent
039144c2c8
commit
fd09724f48
2 changed files with 58 additions and 30 deletions
|
@ -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)] = {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue