properly generate both spendable and unspendable csv transactions, add factory to CSV/CLTVScriptSignatures

This commit is contained in:
Tom McCabe 2016-09-15 22:28:56 -05:00
parent 10d4041efb
commit bb806716cb
7 changed files with 119 additions and 131 deletions

View file

@ -376,9 +376,13 @@ object CLTVScriptPubKey extends Factory[CLTVScriptPubKey] {
CLTVScriptPubKey(asm)
}
def isCLTVScriptPubKey(asm : Seq[ScriptToken]) : Boolean = asm.slice(0,4) match {
case List(lockTimeBytesToPush : BytesToPushOntoStack, lockTime : ScriptConstant, OP_CHECKLOCKTIMEVERIFY, OP_DROP) => true
case _ => false
def isCLTVScriptPubKey(asm : Seq[ScriptToken]) : Boolean = {
val tailTokens = asm.slice(4, asm.length)
if (P2SHScriptPubKey.isP2SHScriptPubKey(tailTokens) || tailTokens.contains(OP_CHECKLOCKTIMEVERIFY)) return false
asm.slice(0,4) match {
case List(lockTimeBytesToPush : BytesToPushOntoStack, lockTime : ScriptConstant, OP_CHECKLOCKTIMEVERIFY, OP_DROP) => true
case _ => false
}
}
}
@ -429,10 +433,15 @@ object CSVScriptPubKey extends Factory[CSVScriptPubKey] {
CSVScriptPubKey(asm)
}
def isCSVScriptPubKey(asm : Seq[ScriptToken]) : Boolean = asm.slice(0,4) match {
case List(lockTimeBytesToPush : BytesToPushOntoStack, lockTime : ScriptConstant, OP_CHECKSEQUENCEVERIFY, OP_DROP) => true
case _ => false
def isCSVScriptPubKey(asm : Seq[ScriptToken]) : Boolean = {
val tailTokens = asm.slice(4, asm.length)
if (P2SHScriptPubKey.isP2SHScriptPubKey(tailTokens) || tailTokens.contains(OP_CHECKSEQUENCEVERIFY)) return false
asm.slice(0,4) match {
case List(lockTimeBytesToPush : BytesToPushOntoStack, lockTime : ScriptConstant, OP_CHECKSEQUENCEVERIFY, OP_DROP) => true
case _ => false
}
}
}
sealed trait NonStandardScriptPubKey extends ScriptPubKey

View file

@ -416,52 +416,56 @@ object CLTVScriptSignature extends Factory[CLTVScriptSignature] {
* of [[ECPublicKey]] needed to satisfy the scriptPubKey. If a [[P2SHScriptPubKey]] is provided, a redeemScript must also be provided.
* @return
*/
def apply(scriptPubKey: ScriptPubKey, sigs : Seq[ECDigitalSignature], pubKeys : Seq[ECPublicKey], redeemScript : Option[ScriptPubKey]) : CLTVScriptSignature = scriptPubKey match {
def apply(scriptPubKey: ScriptPubKey, sigs : Seq[ECDigitalSignature], pubKeys : Seq[ECPublicKey]) : CLTVScriptSignature = scriptPubKey match {
case p2pkScriptPubKey : P2PKScriptPubKey => CLTVScriptSignature(P2PKScriptSignature(sigs.head))
case p2pkhScriptPubKey : P2PKHScriptPubKey => CLTVScriptSignature(P2PKHScriptSignature(sigs.head, pubKeys.head))
case multiSigScriptPubKey : MultiSignatureScriptPubKey => CLTVScriptSignature(MultiSignatureScriptSignature(sigs))
case cltvScriptPubKey : CLTVScriptPubKey => apply(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys, redeemScript)
case csvScriptPubKey : CSVScriptPubKey => apply(csvScriptPubKey.scriptPubKeyAfterCSV, sigs, pubKeys, redeemScript)
case p2shScriptPubKey : P2SHScriptPubKey =>
require(redeemScript.isDefined, "If the underlying scriptSig is a P2SHScriptSignature, a redeemScript must be defined.")
val cltvScriptSigBeforeRedeemScript = apply(redeemScript.get, sigs, pubKeys, None)
CLTVScriptSignature(P2SHScriptSignature(cltvScriptSigBeforeRedeemScript, redeemScript.get))
case cltvScriptPubKey : CLTVScriptPubKey => apply(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys)
case csvScriptPubKey : CSVScriptPubKey => apply(csvScriptPubKey.scriptPubKeyAfterCSV, sigs, pubKeys)
case EmptyScriptPubKey => CLTVScriptSignature(EmptyScriptSignature)
case nonstandard : NonStandardScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature cannot be" +
"the underlying scriptSig in a CLTVScriptSignature.")
case _ : NonStandardScriptPubKey | _ : P2SHScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature or P2SHScriptSignature cannot be" +
"the underlying scriptSig in a CLTVScriptSignature. Got: " + this)
}
}
sealed trait CSVScriptSignature extends ScriptSignature {
def scriptSig : ScriptSignature
def scriptSig : ScriptSignature = ScriptSignature(hex)
override def signatures : Seq[ECDigitalSignature] = scriptSig.signatures
override def hex = scriptSig.hex
}
object CSVScriptSignature {
private case class CSVScriptSignatureImpl(scriptSig : ScriptSignature) extends CSVScriptSignature
object CSVScriptSignature extends Factory[CSVScriptSignature] {
private case class CSVScriptSignatureImpl(override val hex : String) extends CSVScriptSignature
override def fromBytes(bytes : Seq[Byte]) : CSVScriptSignature = {
val hex = BitcoinSUtil.encodeHex(bytes)
fromHex(hex)
}
override def fromHex(hex : String) : CSVScriptSignature = {
CSVScriptSignatureImpl(hex)
}
def apply(scriptSig : ScriptSignature) : CSVScriptSignature = {
fromHex(scriptSig.hex)
}
/**
* Creates a CSVScriptSignature out the [[ScriptPubKey]] we are satisfying, a sequence of [[ECDigitalSignature]], and a sequence
* of [[ECPublicKey]] needed to satisfy the scriptPubKey. If a [[P2SHScriptPubKey]] is provided, a redeemScript must also be provided.
* @return
*/
def apply(scriptPubKey: ScriptPubKey, sigs : Seq[ECDigitalSignature], pubKeys : Seq[ECPublicKey], redeemScript : Option[ScriptPubKey]) : CSVScriptSignature = scriptPubKey match {
case p2pkScriptPubKey : P2PKScriptPubKey => CSVScriptSignatureImpl(P2PKScriptSignature(sigs.head))
case p2pkhScriptPubKey : P2PKHScriptPubKey => CSVScriptSignatureImpl(P2PKHScriptSignature(sigs.head, pubKeys.head))
case multiSigScriptPubKey : MultiSignatureScriptPubKey => CSVScriptSignatureImpl(MultiSignatureScriptSignature(sigs))
case cltvScriptPubKey : CLTVScriptPubKey => apply(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys, redeemScript)
case csvScriptPubKey : CSVScriptPubKey => apply(csvScriptPubKey.scriptPubKeyAfterCSV, sigs, pubKeys, redeemScript)
case p2shScriptPubKey : P2SHScriptPubKey =>
require(redeemScript.isDefined, "If the underlying scriptSig is a P2SHScriptSignature, a redeemScript must be defined.")
val csvScriptSigBeforeRedeemScript = apply(redeemScript.get, sigs, pubKeys, None)
CSVScriptSignatureImpl(P2SHScriptSignature(csvScriptSigBeforeRedeemScript, redeemScript.get))
case EmptyScriptPubKey => CSVScriptSignatureImpl(EmptyScriptSignature)
case nonstandard : NonStandardScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature cannot be" +
"the underlying scriptSig in a CSVScriptSignature.")
def apply(scriptPubKey: ScriptPubKey, sigs : Seq[ECDigitalSignature], pubKeys : Seq[ECPublicKey]) : CSVScriptSignature = scriptPubKey match {
case p2pkScriptPubKey : P2PKScriptPubKey => CSVScriptSignature(P2PKScriptSignature(sigs.head))
case p2pkhScriptPubKey : P2PKHScriptPubKey => CSVScriptSignature(P2PKHScriptSignature(sigs.head, pubKeys.head))
case multiSigScriptPubKey : MultiSignatureScriptPubKey => CSVScriptSignature(MultiSignatureScriptSignature(sigs))
case cltvScriptPubKey : CLTVScriptPubKey => apply(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys)
case csvScriptPubKey : CSVScriptPubKey => apply(csvScriptPubKey.scriptPubKeyAfterCSV, sigs, pubKeys)
case EmptyScriptPubKey => CSVScriptSignature(EmptyScriptSignature)
case _ : NonStandardScriptPubKey | _ : P2SHScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature or P2SHScriptSignature cannot be" +
"the underlying scriptSig in a CSVScriptSignature. Got: " + this)
}
}

View file

@ -31,7 +31,6 @@ trait LockTimeInterpreter extends BitcoinSLogger {
*/
@tailrec
final def opCheckLockTimeVerify(program : ScriptProgram) : ScriptProgram = {
logger.warn("+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!+_+_+_+_!+_+_!!!!")
require(program.script.headOption.isDefined && program.script.head == OP_CHECKLOCKTIMEVERIFY,
"Script top must be OP_CHECKLOCKTIMEVERIFY")
val input = program.txSignatureComponent.transaction.inputs(program.txSignatureComponent.inputIndex.toInt)
@ -176,7 +175,7 @@ trait LockTimeInterpreter extends BitcoinSLogger {
(txToSequenceMasked >= Int64(TransactionConstants.sequenceLockTimeTypeFlag.underlying) &&
nSequenceMasked >= Int64(TransactionConstants.sequenceLockTimeTypeFlag.underlying))
)) {
logger.error("The nSequence mask is not the same as it was in the transaction")
logger.error("The txSequence and nSequence (OP_CSV value) are not of the same type (timestamp/block-height).")
return false
}
@ -216,7 +215,6 @@ trait LockTimeInterpreter extends BitcoinSLogger {
// Now that we know we're comparing apples-to-apples, the
// comparison is a simple numeric one.
println("cltvLocktime: " + locktime + " and txLocktime: " + transaction.lockTime)
if (locktime > Int64(transaction.lockTime.underlying)) return false
// Finally the nLockTime feature can be disabled and thus
@ -231,10 +229,7 @@ trait LockTimeInterpreter extends BitcoinSLogger {
// required to prove correct CHECKLOCKTIMEVERIFY execution.
if (input.sequence == TransactionConstants.sequence) {
false
} else {
println("it passed")
true
}
} else true
}
/**

View file

@ -1,6 +1,7 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.gen.TransactionGenerators
import org.bitcoins.core.number.Int64
import org.bitcoins.core.protocol.script.{CLTVScriptPubKey, P2SHScriptPubKey}
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.{ScriptErrorUnsatisfiedLocktime, ScriptErrorPushSize, ScriptOk}
@ -12,59 +13,58 @@ import org.scalacheck.{Prop, Properties}
* Created by chris on 7/25/16.
*/
class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCreatorSpec") with BitcoinSLogger {
// property("Must generate a valid signature for a p2pk transaction") =
// Prop.forAll(TransactionGenerators.signedP2PKTransaction) {
// case (txSignatureComponent: TransactionSignatureComponent, _) =>
// //run it through the interpreter
// val program: PreExecutionScriptProgram = ScriptProgram(txSignatureComponent)
// val result = ScriptInterpreter.run(program)
// result == ScriptOk
// }
//
// property("generate a valid signature for a p2pkh transaction") =
// Prop.forAll(TransactionGenerators.signedP2PKHTransaction) {
// case (txSignatureComponent: TransactionSignatureComponent, _) =>
// //run it through the interpreter
// val program = ScriptProgram(txSignatureComponent)
// val result = ScriptInterpreter.run(program)
// result == ScriptOk
// }
//
// property("generate valid signatures for a multisignature transaction") =
// Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
// case (txSignatureComponent: TransactionSignatureComponent, _) =>
// //run it through the interpreter
// val program = ScriptProgram(txSignatureComponent)
//
// val result = ScriptInterpreter.run(program)
//
// result == ScriptOk
// }
//
// property("generate a valid signature for a p2sh transaction") =
// Prop.forAll(TransactionGenerators.signedP2SHTransaction) {
// case (txSignatureComponent: TransactionSignatureComponent, _) =>
// //run it through the interpreter
// val program = ScriptProgram(txSignatureComponent)
// val result = ScriptInterpreter.run(program)
// //can be ScriptErrorPushSize if the redeemScript is larger than 520 bytes
// Seq(ScriptOk, ScriptErrorPushSize).contains(result)
// }
//
// property("generate a valid signature for a valid and spendable cltv transaction") =
// Prop.forAllNoShrink(TransactionGenerators.spendableCLTVTransaction :| "cltv_spendable") {
// case (txSignatureComponent: TransactionSignatureComponent, _, scriptNumber) =>
// //run it through the interpreter
// require(txSignatureComponent.transaction.lockTime.underlying >= scriptNumber.underlying, "TxLocktime must be satisifed so it should be greater than or equal to " +
// "the cltv value. Got TxLockTime : " + txSignatureComponent.transaction.lockTime.underlying + " , and cltv Value: " +
// scriptNumber.underlying)
// val program = ScriptProgram(txSignatureComponent)
// val result = ScriptInterpreter.run(program)
// val cltv = CLTVScriptPubKey(txSignatureComponent.scriptPubKey.hex)
// if (cltv.scriptPubKeyAfterCLTV.isInstanceOf[P2SHScriptPubKey]) Seq(ScriptOk, ScriptErrorPushSize).contains(result)
// else Seq(ScriptOk).contains(result)
// }
//
property("Must generate a valid signature for a p2pk transaction") =
Prop.forAll(TransactionGenerators.signedP2PKTransaction) {
case (txSignatureComponent: TransactionSignatureComponent, _) =>
//run it through the interpreter
val program: PreExecutionScriptProgram = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
result == ScriptOk
}
property("generate a valid signature for a p2pkh transaction") =
Prop.forAll(TransactionGenerators.signedP2PKHTransaction) {
case (txSignatureComponent: TransactionSignatureComponent, _) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
result == ScriptOk
}
property("generate valid signatures for a multisignature transaction") =
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
case (txSignatureComponent: TransactionSignatureComponent, _) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
result == ScriptOk
}
property("generate a valid signature for a p2sh transaction") =
Prop.forAll(TransactionGenerators.signedP2SHTransaction) {
case (txSignatureComponent: TransactionSignatureComponent, _) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
//can be ScriptErrorPushSize if the redeemScript is larger than 520 bytes
Seq(ScriptOk, ScriptErrorPushSize).contains(result)
}
property("generate a valid signature for a valid and spendable cltv transaction") =
Prop.forAllNoShrink(TransactionGenerators.spendableCLTVTransaction :| "cltv_spendable") {
case (txSignatureComponent: TransactionSignatureComponent, _, scriptNumber) =>
//run it through the interpreter
require(txSignatureComponent.transaction.lockTime.underlying >= scriptNumber.underlying, "TxLocktime must be satisifed so it should be greater than or equal to " +
"the cltv value. Got TxLockTime : " + txSignatureComponent.transaction.lockTime.underlying + " , and cltv Value: " +
scriptNumber.underlying)
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
val cltv = CLTVScriptPubKey(txSignatureComponent.scriptPubKey.hex)
Seq(ScriptOk).contains(result)
}
property("generate a valid signature for a validly constructed, but NOT spendable cltv transaction") =
Prop.forAllNoShrink(TransactionGenerators.unspendableCLTVTransaction :| "cltv_unspendable") {
case (txSignatureComponent: TransactionSignatureComponent, _, scriptNumber) =>
@ -75,16 +75,25 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
val cltv = CLTVScriptPubKey(txSignatureComponent.scriptPubKey.hex)
println("result: " + result)
Seq(ScriptErrorUnsatisfiedLocktime, ScriptErrorPushSize).contains(result)
}
//
// property("generate a valid signature for a valid and spendable csv transaction") =
// Prop.forAllNoShrink(TransactionGenerators.spendableCSVTransaction :| "csv") {
// case (txSignatureComponent: TransactionSignatureComponent, keys, scriptNumber, sequence) =>
// //run it through the interpreter
// val program = ScriptProgram(txSignatureComponent)
// val result = ScriptInterpreter.run(program)
// Seq(ScriptOk, ScriptErrorPushSize).contains(result)
// }
property("generate a valid signature for a valid and spendable csv transaction") =
Prop.forAllNoShrink(TransactionGenerators.spendableCSVTransaction :| "spendable csv") {
case (txSignatureComponent: TransactionSignatureComponent, keys, scriptNumber, sequence) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
Seq(ScriptOk, ScriptErrorPushSize).contains(result)
}
property("generate a valid signature for a validly constructed but unspendable csv transaction") =
Prop.forAllNoShrink(TransactionGenerators.unspendableCSVTransaction :| "unspendable csv") {
case (txSignatureComponent: TransactionSignatureComponent, keys, scriptNumber, sequence) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
Seq(ScriptErrorUnsatisfiedLocktime, ScriptErrorPushSize).contains(result)
}
}

View file

@ -15,17 +15,4 @@ class CLTVScriptPubKeySpec extends Properties("CLTVScriptPubKeySpec") with Bitco
Prop.forAll(ScriptGenerators.cltvScriptPubKey) { case (cltvScriptPubKey, _) =>
CLTVScriptPubKey(cltvScriptPubKey.hex) == cltvScriptPubKey
}
property("a valid unspendable CLTV Transaction's locktime must be less than the script's CLTV value ") =
Prop.forAll(TransactionGenerators.unspendableCLTVTransaction) { txSigComponent =>
val locktime = txSigComponent._1.transaction.lockTime.underlying
val cltvScriptValue = txSigComponent._3.underlying
locktime < cltvScriptValue
}
property("a valid spendable CLTV Transaction's locktime must be greater than the script's CLTV value ") =
Prop.forAll(TransactionGenerators.spendableCLTVTransaction) { txSigComponent =>
val locktime = txSigComponent._1.transaction.lockTime.underlying
val cltvScriptValue = txSigComponent._3.underlying
locktime > cltvScriptValue
}
}

View file

@ -122,6 +122,6 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
val cltvScriptPubKey = CLTVScriptPubKey("04e71bbe57b17576a914da88dc82530f0a4d1327dcfe75cc60c44277532c88ac")
val pubKey = ECPublicKey("039ba48e162b1f47246f4ce9dc40f197fab7bde11da1b2fe9ac21113959e9f381b")
val sig = ECDigitalSignature("3045022100d71cfe32fa4545c5a0fd665b3701eb458a1bacbba868a05fa703fd1fa4b4f5c502204ee706334f976d0bee9b0f0ff919c1dfe9ba027993bf3e39fc03416ba4255b2401")
CLTVScriptSignature(cltvScriptPubKey, Seq(sig), Seq(pubKey), None).scriptSig.isInstanceOf[P2PKHScriptSignature]
CLTVScriptSignature(cltvScriptPubKey, Seq(sig), Seq(pubKey)).scriptSig.isInstanceOf[P2PKHScriptSignature]
}
}

View file

@ -20,7 +20,6 @@ import scala.io.Source
* Created by chris on 1/6/16.
*/
class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterpreter with BitcoinSLogger {
/*
"ScriptInterpreter" must "evaluate all the scripts from the bitcoin core script_tests.json" in {
@ -61,19 +60,4 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
}
}
}
*/
it must "fail CLTVScriptPubKey with nested P2SHScriptPubKey" in {
val privKey = ECPrivateKey("8fe79310a9a5400daf4f9690187db1607c668fca9f63a530d67cc7472c9de589")
val cltv = CLTVScriptPubKey("010ab175a914c280ffb6c00e2d8b333d84746dc38a807081590287")
val p2shScriptSig = P2SHScriptSignature("483045022100d817fbc5a5d71f0859b811c01e251a20ab897c2b07b96149340336183e80d1" +
"9e022065e20731db313b31c45b208919a8606f90d390620b65a71085d185618b6742bc0123210338a011f3d144d4af991b27d13bcac38ef312835e64a0971c7f5404e1252a332eac")
val cltvScriptSignature = CLTVScriptSignature(p2shScriptSig)
val redeemScript = p2shScriptSig.redeemScript
val (creditingTx, outputIndex) = TransactionGenerators.buildCreditingTransaction(cltv)
val (spendingTx, inputIndex) = TransactionGenerators.buildSpendingTransaction(UInt32.one, creditingTx, cltvScriptSignature, outputIndex, UInt32(5), UInt32.zero)
val txSigComp = TransactionSignatureComponent(spendingTx, inputIndex, redeemScript, Policy.standardScriptVerifyFlags)
ScriptInterpreter.run(ScriptProgram(txSigComp)) must be (ScriptErrorUnsatisfiedLocktime)
}
}