mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-15 20:30:17 +01:00
Nested OP_IF ScriptPubKey and signing (#857)
* Enabled nested conditional parsing * Added nested conditionals to tests * Responded to code review
This commit is contained in:
parent
d86acfffed
commit
3c7fd6c34a
3 changed files with 249 additions and 134 deletions
|
@ -4,7 +4,13 @@ import org.bitcoins.core.consensus.Consensus
|
|||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
|
||||
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, _}
|
||||
import org.bitcoins.core.script.control.{OP_ELSE, OP_ENDIF, OP_IF, OP_RETURN}
|
||||
import org.bitcoins.core.script.control.{
|
||||
OP_ELSE,
|
||||
OP_ENDIF,
|
||||
OP_IF,
|
||||
OP_NOTIF,
|
||||
OP_RETURN
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.{
|
||||
OP_CHECKMULTISIG,
|
||||
OP_CHECKMULTISIGVERIFY,
|
||||
|
@ -29,7 +35,11 @@ sealed abstract class ScriptPubKey extends Script
|
|||
/** Trait for all Non-SegWit ScriptPubKeys */
|
||||
sealed trait NonWitnessScriptPubKey extends ScriptPubKey
|
||||
|
||||
/** Trait for all raw, non-nested ScriptPubKeys (no P2SH) */
|
||||
/** Trait for all raw, non-nested ScriptPubKeys (no P2SH)
|
||||
*
|
||||
* Note that all WitnessScriptPubKeys including P2WPKH are not
|
||||
* considered to be non-nested and hence not RawScriptPubKeys.
|
||||
*/
|
||||
sealed trait RawScriptPubKey extends NonWitnessScriptPubKey
|
||||
|
||||
/**
|
||||
|
@ -557,17 +567,18 @@ object CSVScriptPubKey extends ScriptFactory[CSVScriptPubKey] {
|
|||
|
||||
/** Currently only supports a single OP_IF ... OP_ELSE ... OP_ENDIF ScriptPubKey */
|
||||
sealed trait ConditionalScriptPubKey extends RawScriptPubKey {
|
||||
require(asm.nonEmpty, "ConditionalScriptPubKey cannot be empty")
|
||||
require(asm.head.equals(OP_IF),
|
||||
require(asm.headOption.contains(OP_IF),
|
||||
"ConditionalScriptPubKey must begin with OP_IF")
|
||||
require(opElseIndex != -1,
|
||||
"ConditionalScriptPubKey has to contain OP_ELSE asm token")
|
||||
require(asm.last.equals(OP_ENDIF),
|
||||
"ConditionalScriptPubKey must end in OP_ENDIF")
|
||||
require(asm.count(_.equals(OP_IF)) == 1,
|
||||
"ConditionalScriptPubKey does not currently support nesting OP_IFs")
|
||||
require(asm.count(_.equals(OP_ELSE)) == 1,
|
||||
"ConditionalScriptPubKey does not currently support nesting OP_ELSEs")
|
||||
|
||||
val (isValidConditional: Boolean, opElseIndex: Int) = {
|
||||
ConditionalScriptPubKey.isConditionalScriptPubKeyWithElseIndex(asm)
|
||||
}
|
||||
|
||||
require(isValidConditional, "Must be valid ConditionalScriptPubKey syntax")
|
||||
require(opElseIndex != -1,
|
||||
"ConditionalScriptPubKey has to contain OP_ELSE asm token")
|
||||
|
||||
require(!P2SHScriptPubKey.isP2SHScriptPubKey(trueSPK.asm) && !P2SHScriptPubKey
|
||||
.isP2SHScriptPubKey(falseSPK.asm),
|
||||
|
@ -579,10 +590,6 @@ sealed trait ConditionalScriptPubKey extends RawScriptPubKey {
|
|||
"ConditionalScriptPubKey cannot wrap SegWit ScriptPubKey"
|
||||
)
|
||||
|
||||
def opElseIndex: Int = {
|
||||
asm.indexOf(OP_ELSE)
|
||||
}
|
||||
|
||||
def trueSPK: RawScriptPubKey = {
|
||||
RawScriptPubKey
|
||||
.fromAsm(asm.slice(1, opElseIndex))
|
||||
|
@ -620,17 +627,66 @@ object ConditionalScriptPubKey extends ScriptFactory[ConditionalScriptPubKey] {
|
|||
fromAsm(asm)
|
||||
}
|
||||
|
||||
def isConditionalScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
|
||||
/** Validates the correctness of the conditional syntax.
|
||||
* If valid, also returns the index of the first outer-most OP_ELSE
|
||||
*/
|
||||
def isConditionalScriptPubKeyWithElseIndex(
|
||||
asm: Seq[ScriptToken]): (Boolean, Int) = {
|
||||
val headIsOpIf = asm.headOption.contains(OP_IF)
|
||||
lazy val containsOpElse = {
|
||||
val opElseIndex = asm.indexOf(OP_ELSE)
|
||||
opElseIndex != -1
|
||||
}
|
||||
lazy val singleOpIf = asm.count(_.equals(OP_IF)) == 1
|
||||
lazy val singleOpElse = asm.count(_.equals(OP_ELSE)) == 1
|
||||
lazy val endsWithEndIF = asm.last == OP_ENDIF
|
||||
lazy val endsWithEndIf = asm.last == OP_ENDIF
|
||||
|
||||
headIsOpIf && containsOpElse && singleOpIf && singleOpElse && endsWithEndIF
|
||||
var opElseIndexOpt: Option[Int] = None
|
||||
|
||||
lazy val validConditionalTree = {
|
||||
// Already validate that the first token is OP_IF, go to tail
|
||||
// and start with depth 1 and no OP_ELSE found for that depth
|
||||
val opElsePendingOpt = asm.zipWithIndex.tail
|
||||
.foldLeft[Option[Vector[Boolean]]](Some(Vector(false))) {
|
||||
case (None, _) => // Invalid tree case, do no computation
|
||||
None
|
||||
case (Some(Vector()), _) => // Case of additional asm after final OP_ENDIF
|
||||
None
|
||||
case (Some(opElseFoundAtDepth), (OP_IF | OP_NOTIF, _)) =>
|
||||
// Increase depth by one with OP_ELSE yet to be found for this depth
|
||||
Some(opElseFoundAtDepth :+ false)
|
||||
case (Some(opElseFoundAtDepth), (OP_ELSE, asmIndex)) =>
|
||||
if (opElseFoundAtDepth == Vector(false)) {
|
||||
// If first OP_ELSE at depth 1, set opElseIndex
|
||||
opElseIndexOpt = Some(asmIndex)
|
||||
}
|
||||
|
||||
if (opElseFoundAtDepth.last) {
|
||||
// If OP_ELSE already found at this depth, invalid
|
||||
None
|
||||
} else {
|
||||
// Otherwise, set to found at this depth
|
||||
Some(
|
||||
opElseFoundAtDepth.updated(opElseFoundAtDepth.length - 1, true))
|
||||
}
|
||||
case (Some(opElseFoundAtDepth), (OP_ENDIF, _)) =>
|
||||
if (opElseFoundAtDepth.last) {
|
||||
// If OP_ELSE found at this depth then valid, decrease depth by 1
|
||||
Some(opElseFoundAtDepth.dropRight(1))
|
||||
} else {
|
||||
// Otherwise, invalid
|
||||
None
|
||||
}
|
||||
case (Some(opElseFoundAtDepth), (_, _)) =>
|
||||
// Token not related to conditional structure, ignore
|
||||
Some(opElseFoundAtDepth)
|
||||
}
|
||||
|
||||
// We should end on OP_ENDIF which will take us to depth 0
|
||||
opElsePendingOpt.contains(Vector.empty)
|
||||
}
|
||||
|
||||
lazy val opElseIndex = opElseIndexOpt.getOrElse(-1)
|
||||
|
||||
(headIsOpIf && endsWithEndIf && validConditionalTree, opElseIndex)
|
||||
}
|
||||
|
||||
def isConditionalScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
|
||||
isConditionalScriptPubKeyWithElseIndex(asm)._1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,19 +67,22 @@ sealed abstract class UTXOSpendingInfo {
|
|||
* If you over-specify a path, such as giving a condition where none is needed,
|
||||
* then the remaining over-specified path will be ignored.
|
||||
*
|
||||
* Note that we do not yet support nested conditionals.
|
||||
*
|
||||
* For example, if you wanted to spend a ConditionalScriptPubKey(P2PK1, P2PK2)
|
||||
* (which looks like OP_IF <P2PK1> OP_ELSE <P2PK2> OP_ENDIF) with the P2PK1 case,
|
||||
* then you would construct a ConditionalSpendingInfo using ConditionTrue as your
|
||||
* ConditionalPath. Otherwise if you wanted to use P2PK2 you would use ConditionFalse.
|
||||
* then you would construct a ConditionalSpendingInfo using nonNestedTrue as your
|
||||
* ConditionalPath. Otherwise if you wanted to use P2PK2 you would use nonNestedFalse.
|
||||
*/
|
||||
sealed trait ConditionalPath
|
||||
|
||||
object ConditionalPath {
|
||||
case object NoConditionsLeft extends ConditionalPath
|
||||
case object ConditionTrue extends ConditionalPath
|
||||
case object ConditionFalse extends ConditionalPath
|
||||
case class ConditionTrue(nextCondition: ConditionalPath)
|
||||
extends ConditionalPath
|
||||
case class ConditionFalse(nextCondition: ConditionalPath)
|
||||
extends ConditionalPath
|
||||
|
||||
val nonNestedTrue: ConditionalPath = ConditionTrue(NoConditionsLeft)
|
||||
val nonNestedFalse: ConditionalPath = ConditionFalse(NoConditionsLeft)
|
||||
}
|
||||
|
||||
sealed trait BitcoinUTXOSpendingInfo extends UTXOSpendingInfo {
|
||||
|
@ -352,15 +355,16 @@ case class ConditionalSpendingInfo(
|
|||
require(conditionalPath != ConditionalPath.NoConditionsLeft,
|
||||
"Must specify True or False")
|
||||
|
||||
val condition: Boolean = conditionalPath match {
|
||||
case ConditionalPath.ConditionTrue =>
|
||||
true
|
||||
case ConditionalPath.ConditionFalse =>
|
||||
false
|
||||
case ConditionalPath.NoConditionsLeft =>
|
||||
throw new IllegalStateException(
|
||||
"This should be covered by invariant above")
|
||||
}
|
||||
val (condition: Boolean, nextConditionalPath: ConditionalPath) =
|
||||
conditionalPath match {
|
||||
case ConditionalPath.ConditionTrue(nextCondition) =>
|
||||
(true, nextCondition)
|
||||
case ConditionalPath.ConditionFalse(nextCondition) =>
|
||||
(false, nextCondition)
|
||||
case ConditionalPath.NoConditionsLeft =>
|
||||
throw new IllegalStateException(
|
||||
"This should be covered by invariant above")
|
||||
}
|
||||
|
||||
val nestedSpendingInfo: RawScriptUTXOSpendingInfo = {
|
||||
val nestedSPK = if (condition) {
|
||||
|
@ -374,7 +378,7 @@ case class ConditionalSpendingInfo(
|
|||
nestedSPK,
|
||||
signers,
|
||||
hashType,
|
||||
ConditionalPath.NoConditionsLeft)
|
||||
nextConditionalPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.bitcoins.core.wallet.utxo.{
|
|||
}
|
||||
import org.scalacheck.Gen
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
@ -25,6 +26,7 @@ import scala.concurrent.duration.DurationInt
|
|||
//TODO: Need to provide generators for [[NonStandardScriptSignature]] and [[NonStandardScriptPubKey]]
|
||||
sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
||||
val timeout = 5.seconds
|
||||
private val defaultMaxDepth = 2
|
||||
|
||||
def p2pkScriptSignature: Gen[P2PKScriptSignature] =
|
||||
for {
|
||||
|
@ -54,11 +56,9 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
.oneOf(
|
||||
packageToSequenceOfPrivateKeys(signedP2PKHScriptSignature),
|
||||
packageToSequenceOfPrivateKeys(signedP2PKScriptSignature),
|
||||
signedMultiSignatureScriptSignature
|
||||
/* Can't have these since that would create nested Conditional(Timelock(Conditional))
|
||||
signedMultiSignatureScriptSignature,
|
||||
signedCLTVScriptSignature,
|
||||
signedCSVScriptSignature
|
||||
*/
|
||||
)
|
||||
.map(_._1)
|
||||
|
||||
|
@ -121,37 +121,84 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
p2pkh = P2PKHScriptPubKey(pubKey)
|
||||
} yield (p2pkh, privKey)
|
||||
|
||||
def cltvScriptPubKey: Gen[(CLTVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
def cltvScriptPubKey: Gen[(CLTVScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
cltvScriptPubKey(defaultMaxDepth)
|
||||
}
|
||||
|
||||
def cltvScriptPubKey(
|
||||
maxDepth: Int): Gen[(CLTVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
num <- NumberGenerator.scriptNumbers
|
||||
(cltv, privKeys, num) <- cltvScriptPubKey(num)
|
||||
(cltv, privKeys, num) <- cltvScriptPubKey(num, maxDepth)
|
||||
} yield (cltv, privKeys)
|
||||
|
||||
def cltvScriptPubKey(num: ScriptNumber): Gen[
|
||||
(CLTVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] =
|
||||
def cltvScriptPubKey(
|
||||
num: ScriptNumber,
|
||||
maxDepth: Int): Gen[(CLTVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- randomNonLockTimeNonP2SHScriptPubKey
|
||||
(scriptPubKey, privKeys) <- nonLocktimeRawScriptPubKey(maxDepth - 1)
|
||||
} yield {
|
||||
val cltv = CLTVScriptPubKey(num, scriptPubKey)
|
||||
(cltv, privKeys, num)
|
||||
}
|
||||
|
||||
def csvScriptPubKey(num: ScriptNumber): Gen[
|
||||
(CSVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] =
|
||||
def nonConditionalCltvScriptPubKey: Gen[
|
||||
(CLTVScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- randomNonLockTimeNonP2SHScriptPubKey
|
||||
num <- NumberGenerator.scriptNumbers
|
||||
(cltv, privKeys, num) <- nonConditionalCltvScriptPubKey(num)
|
||||
} yield (cltv, privKeys)
|
||||
}
|
||||
|
||||
def nonConditionalCltvScriptPubKey(num: ScriptNumber): Gen[
|
||||
(CLTVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- nonConditionalNonLocktimeRawScriptPubKey
|
||||
} yield {
|
||||
val cltv = CLTVScriptPubKey(num, scriptPubKey)
|
||||
(cltv, privKeys, num)
|
||||
}
|
||||
|
||||
def csvScriptPubKey: Gen[(CSVScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
csvScriptPubKey(defaultMaxDepth)
|
||||
}
|
||||
|
||||
def csvScriptPubKey(
|
||||
num: ScriptNumber,
|
||||
maxDepth: Int): Gen[(CSVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- nonLocktimeRawScriptPubKey(maxDepth - 1)
|
||||
} yield {
|
||||
val csv = CSVScriptPubKey(num, scriptPubKey)
|
||||
(csv, privKeys, num)
|
||||
}
|
||||
|
||||
def csvScriptPubKey: Gen[(CSVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
def csvScriptPubKey(
|
||||
maxDepth: Int): Gen[(CSVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- randomNonLockTimeNonP2SHScriptPubKey
|
||||
(scriptPubKey, privKeys) <- nonLocktimeRawScriptPubKey(maxDepth - 1)
|
||||
num <- NumberGenerator.scriptNumbers
|
||||
csv = CSVScriptPubKey(num, scriptPubKey)
|
||||
} yield (csv, privKeys)
|
||||
|
||||
def nonConditionalCsvScriptPubKey(num: ScriptNumber): Gen[
|
||||
(CSVScriptPubKey, Seq[ECPrivateKey], ScriptNumber)] = {
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- nonConditionalNonLocktimeRawScriptPubKey
|
||||
} yield {
|
||||
val csv = CSVScriptPubKey(num, scriptPubKey)
|
||||
(csv, privKeys, num)
|
||||
}
|
||||
}
|
||||
|
||||
def nonConditionalCsvScriptPubKey: Gen[(CSVScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- nonConditionalNonLocktimeRawScriptPubKey
|
||||
num <- NumberGenerator.scriptNumbers
|
||||
csv = CSVScriptPubKey(num, scriptPubKey)
|
||||
} yield (csv, privKeys)
|
||||
}
|
||||
|
||||
def multiSigScriptPubKey: Gen[
|
||||
(MultiSignatureScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
|
@ -179,29 +226,38 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
def emptyScriptPubKey: Gen[(EmptyScriptPubKey.type, Seq[ECPrivateKey])] =
|
||||
(EmptyScriptPubKey, Nil)
|
||||
|
||||
/* We cannot have this one until there is support for nesting since
|
||||
this allows Conditional(LockTime(Conditional))
|
||||
|
||||
/** Creates a ConditionalScriptPubKey with keys for the true case */
|
||||
def conditionalScriptPubKey: Gen[
|
||||
(ConditionalScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
nonConditionalRawScriptPubKey.flatMap {
|
||||
case (spk1, keys1) =>
|
||||
nonConditionalRawScriptPubKey.map(_._1).map { spk2 =>
|
||||
(ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
}
|
||||
/** Creates a ConditionalScriptPubKey with keys for the true case
|
||||
*
|
||||
* @param maxDepth The maximum level of nesting allowed within this conditional.
|
||||
*/
|
||||
def conditionalScriptPubKey(
|
||||
maxDepth: Int): Gen[(ConditionalScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
if (maxDepth > 0) {
|
||||
for {
|
||||
(spk1, keys1) <- rawScriptPubKey(maxDepth - 1)
|
||||
(spk2, _) <- rawScriptPubKey(maxDepth - 1)
|
||||
} yield (ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
} else {
|
||||
for {
|
||||
(spk1, keys1) <- nonConditionalRawScriptPubKey
|
||||
(spk2, _) <- nonConditionalRawScriptPubKey
|
||||
} yield (ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/** Creates a ConditionalScriptPubKey with keys for the true case */
|
||||
def nonLocktimeConditionalScriptPubKey: Gen[
|
||||
(ConditionalScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
nonConditionalNonLocktimeRawScriptPubKey.flatMap {
|
||||
case (spk1, keys1) =>
|
||||
nonConditionalNonLocktimeRawScriptPubKey.map(_._1).map { spk2 =>
|
||||
(ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
}
|
||||
def nonLocktimeConditionalScriptPubKey(
|
||||
maxDepth: Int): Gen[(ConditionalScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
if (maxDepth > 0) {
|
||||
for {
|
||||
(spk1, keys1) <- nonLocktimeRawScriptPubKey(maxDepth - 1)
|
||||
(spk2, _) <- nonLocktimeRawScriptPubKey(maxDepth - 1)
|
||||
} yield (ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
} else {
|
||||
for {
|
||||
(spk1, keys1) <- nonConditionalNonLocktimeRawScriptPubKey
|
||||
(spk2, _) <- nonConditionalNonLocktimeRawScriptPubKey
|
||||
} yield (ConditionalScriptPubKey(spk1, spk2), keys1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,29 +306,14 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq(_)),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq(_)),
|
||||
cltvScriptPubKey.suchThat(
|
||||
cltvScriptPubKey(defaultMaxDepth).suchThat(
|
||||
!_._1.nestedScriptPubKey.isInstanceOf[CSVScriptPubKey]),
|
||||
csvScriptPubKey.suchThat(
|
||||
csvScriptPubKey(defaultMaxDepth).suchThat(
|
||||
!_._1.nestedScriptPubKey.isInstanceOf[CLTVScriptPubKey]),
|
||||
multiSigScriptPubKey,
|
||||
p2wpkhSPKV0,
|
||||
unassignedWitnessScriptPubKey,
|
||||
nonLocktimeConditionalScriptPubKey
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used for creating time locked scriptPubKeys, we cannot nest CSV/CLTV/P2SH/Witness
|
||||
* ScriptPubKeys inside of timelock scriptPubKeys
|
||||
*/
|
||||
def randomNonLockTimeNonP2SHScriptPubKey: Gen[
|
||||
(ScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq(_)),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq(_)),
|
||||
multiSigScriptPubKey,
|
||||
nonLocktimeConditionalScriptPubKey,
|
||||
emptyScriptPubKey
|
||||
conditionalScriptPubKey(defaultMaxDepth)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -285,8 +326,13 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
nonLockTimeConditionalScriptSignature)
|
||||
}
|
||||
|
||||
def lockTimeScriptPubKey: Gen[(LockTimeScriptPubKey, Seq[ECPrivateKey])] =
|
||||
Gen.oneOf(cltvScriptPubKey, csvScriptPubKey)
|
||||
def lockTimeScriptPubKey(
|
||||
maxDepth: Int): Gen[(LockTimeScriptPubKey, Seq[ECPrivateKey])] =
|
||||
Gen.oneOf(cltvScriptPubKey(maxDepth), csvScriptPubKey(maxDepth))
|
||||
|
||||
def nonConditionalLockTimeScriptPubKey: Gen[
|
||||
(LockTimeScriptPubKey, Seq[ECPrivateKey])] =
|
||||
Gen.oneOf(nonConditionalCltvScriptPubKey, nonConditionalCsvScriptPubKey)
|
||||
|
||||
def lockTimeScriptSig: Gen[LockTimeScriptSignature] =
|
||||
Gen.oneOf(csvScriptSignature, cltvScriptSignature)
|
||||
|
@ -298,14 +344,14 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
p2pkhScriptPubKey.map(privKeyToSeq(_)),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
cltvScriptPubKey,
|
||||
csvScriptPubKey,
|
||||
cltvScriptPubKey(defaultMaxDepth),
|
||||
csvScriptPubKey(defaultMaxDepth),
|
||||
p2wpkhSPKV0,
|
||||
p2wshSPKV0,
|
||||
unassignedWitnessScriptPubKey,
|
||||
p2shScriptPubKey,
|
||||
witnessCommitment,
|
||||
nonLocktimeConditionalScriptPubKey
|
||||
conditionalScriptPubKey(defaultMaxDepth)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -315,10 +361,10 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
p2pkhScriptPubKey.map(privKeyToSeq),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
lockTimeScriptPubKey,
|
||||
lockTimeScriptPubKey(defaultMaxDepth),
|
||||
p2shScriptPubKey,
|
||||
witnessCommitment,
|
||||
nonLocktimeConditionalScriptPubKey
|
||||
conditionalScriptPubKey(defaultMaxDepth)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -332,25 +378,40 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
)
|
||||
}
|
||||
|
||||
def nonLocktimeRawScriptPubKey(
|
||||
maxDepth: Int): Gen[(RawScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
nonLocktimeConditionalScriptPubKey(maxDepth)
|
||||
)
|
||||
}
|
||||
|
||||
def nonConditionalRawScriptPubKey: Gen[(RawScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
lockTimeScriptPubKey
|
||||
nonConditionalLockTimeScriptPubKey
|
||||
)
|
||||
}
|
||||
|
||||
def rawScriptPubKey: Gen[(RawScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
rawScriptPubKey(defaultMaxDepth)
|
||||
}
|
||||
|
||||
def rawScriptPubKey(
|
||||
maxDepth: Int): Gen[(RawScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
lockTimeScriptPubKey,
|
||||
witnessCommitment,
|
||||
nonLocktimeConditionalScriptPubKey
|
||||
lockTimeScriptPubKey(maxDepth),
|
||||
conditionalScriptPubKey(maxDepth)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -512,12 +573,13 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
packageToSequenceOfPrivateKeys(signedP2PKScriptSignature),
|
||||
signedMultiSignatureScriptSignature,
|
||||
signedCLTVScriptSignature,
|
||||
signedCSVScriptSignature
|
||||
signedCSVScriptSignature,
|
||||
signedConditionalScriptSignature
|
||||
)
|
||||
|
||||
signed.flatMap {
|
||||
case (scriptSig, spk, keys) =>
|
||||
nonConditionalRawScriptPubKey.map(_._1).map { spk2 =>
|
||||
rawScriptPubKey(defaultMaxDepth).map(_._1).map { spk2 =>
|
||||
(ConditionalScriptSignature(scriptSig, true),
|
||||
ConditionalScriptPubKey(spk.asInstanceOf[RawScriptPubKey], spk2),
|
||||
keys)
|
||||
|
@ -542,6 +604,25 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
p2SHScriptSignature = P2SHScriptSignature(scriptSig, redeemScript)
|
||||
} yield (p2SHScriptSignature, p2SHScriptPubKey, privateKeys)
|
||||
|
||||
/** Utility function to compute how many signatures will be required in the inner-most true case.
|
||||
* For use with CLTV and CSV ScriptSignature generation.
|
||||
*/
|
||||
@tailrec
|
||||
private def findRequiredSigs(conditional: ConditionalScriptPubKey): Int = {
|
||||
conditional.trueSPK match {
|
||||
case multiSig: MultiSignatureScriptPubKey => multiSig.requiredSigs
|
||||
case nestedConditional: ConditionalScriptPubKey =>
|
||||
findRequiredSigs(nestedConditional)
|
||||
case _: LockTimeScriptPubKey =>
|
||||
throw new IllegalArgumentException(
|
||||
"This shouldn't happen since we are using nonLocktimeRawScriptPubKey")
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey => 1
|
||||
case EmptyScriptPubKey | _: WitnessCommitment |
|
||||
_: NonStandardScriptPubKey =>
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the signed `CLTVScriptSignature`, the
|
||||
* `CLTVScriptPubKey` it spends, and the
|
||||
|
@ -554,7 +635,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
sequence: UInt32): Gen[
|
||||
(CLTVScriptSignature, CLTVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- randomNonLockTimeNonP2SHScriptPubKey
|
||||
(scriptPubKey, privKeys) <- nonLocktimeRawScriptPubKey(defaultMaxDepth)
|
||||
hashType <- CryptoGenerators.hashType
|
||||
cltv = CLTVScriptPubKey(cltvLockTime, scriptPubKey)
|
||||
} yield scriptPubKey match {
|
||||
|
@ -568,24 +649,11 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
hashType)
|
||||
(cltvScriptSig.asInstanceOf[CLTVScriptSignature], cltv, privKeys)
|
||||
case conditional: ConditionalScriptPubKey =>
|
||||
val requiredSigs = conditional.trueSPK match {
|
||||
case multiSig: MultiSignatureScriptPubKey => multiSig.requiredSigs
|
||||
case _: ConditionalScriptPubKey =>
|
||||
throw new IllegalStateException(
|
||||
"No nesting of conditionals supported")
|
||||
case _: LockTimeScriptPubKey =>
|
||||
throw new IllegalArgumentException(
|
||||
"This shouldn't happen since we are using randomNonLockTimeNonP2SHScriptPubKey")
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey => 1
|
||||
case EmptyScriptPubKey | _: WitnessCommitment |
|
||||
_: NonStandardScriptPubKey =>
|
||||
0
|
||||
}
|
||||
val cltvScriptSig = lockTimeHelper(Some(lockTime),
|
||||
sequence,
|
||||
cltv,
|
||||
privKeys,
|
||||
Some(requiredSigs),
|
||||
Some(findRequiredSigs(conditional)),
|
||||
hashType)
|
||||
(cltvScriptSig.asInstanceOf[CLTVScriptSignature], cltv, privKeys)
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey =>
|
||||
|
@ -628,7 +696,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
sequence: UInt32): Gen[
|
||||
(CSVScriptSignature, CSVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(scriptPubKey, privKeys) <- randomNonLockTimeNonP2SHScriptPubKey
|
||||
(scriptPubKey, privKeys) <- nonLocktimeRawScriptPubKey(defaultMaxDepth)
|
||||
hashType <- CryptoGenerators.hashType
|
||||
csv = CSVScriptPubKey(csvScriptNum, scriptPubKey)
|
||||
} yield scriptPubKey match {
|
||||
|
@ -642,24 +710,11 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
hashType)
|
||||
(csvScriptSig.asInstanceOf[CSVScriptSignature], csv, privKeys)
|
||||
case conditional: ConditionalScriptPubKey =>
|
||||
val requiredSigs = conditional.trueSPK match {
|
||||
case multiSig: MultiSignatureScriptPubKey => multiSig.requiredSigs
|
||||
case _: ConditionalScriptPubKey =>
|
||||
throw new IllegalStateException(
|
||||
"No nesting of conditionals supported")
|
||||
case _: LockTimeScriptPubKey =>
|
||||
throw new IllegalArgumentException(
|
||||
"This shouldn't happen since we are using randomNonLockTimeNonP2SHScriptPubKey")
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey => 1
|
||||
case EmptyScriptPubKey | _: WitnessCommitment |
|
||||
_: NonStandardScriptPubKey =>
|
||||
0
|
||||
}
|
||||
val csvScriptSig = lockTimeHelper(None,
|
||||
sequence,
|
||||
csv,
|
||||
privKeys,
|
||||
Some(requiredSigs),
|
||||
Some(findRequiredSigs(conditional)),
|
||||
hashType)
|
||||
(csvScriptSig.asInstanceOf[CSVScriptSignature], csv, privKeys)
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey =>
|
||||
|
@ -683,7 +738,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
def signedCSVScriptSignature: Gen[
|
||||
(CSVScriptSignature, CSVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(csv, privKeys) <- csvScriptPubKey
|
||||
(csv, privKeys) <- csvScriptPubKey(defaultMaxDepth)
|
||||
sequence <- NumberGenerator.uInt32s
|
||||
scriptSig <- signedCSVScriptSignature(csv.locktime, sequence)
|
||||
} yield scriptSig
|
||||
|
@ -691,7 +746,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
def signedCLTVScriptSignature: Gen[
|
||||
(CLTVScriptSignature, CLTVScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(cltv, privKeys) <- cltvScriptPubKey
|
||||
(cltv, privKeys) <- cltvScriptPubKey(defaultMaxDepth)
|
||||
txLockTime <- NumberGenerator.uInt32s
|
||||
sequence <- NumberGenerator.uInt32s
|
||||
scriptSig <- signedCLTVScriptSignature(cltv.locktime,
|
||||
|
|
Loading…
Add table
Reference in a new issue