mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 23:08:31 +01:00
Create signedCSVScriptSignature and spendableCSVTransaction generators, add CSVScriptSignature trait and object, added condition to fail CSV if txInput sequence has lockTimeDisableFlag set
This commit is contained in:
parent
f7b209709b
commit
b13d42f9bf
6 changed files with 104 additions and 25 deletions
|
@ -71,7 +71,7 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
|
|||
val sigsRemoved = removeSignaturesFromScript(s.signatures, s.redeemScript.asm)
|
||||
sigsRemoved
|
||||
case _ : P2PKHScriptSignature | _ : P2PKScriptSignature | _ : NonStandardScriptSignature
|
||||
| _ : MultiSignatureScriptSignature | _ : CLTVScriptSignature | EmptyScriptSignature =>
|
||||
| _ : MultiSignatureScriptSignature | _ : CLTVScriptSignature | _ : CSVScriptSignature | EmptyScriptSignature =>
|
||||
logger.debug("Script before sigRemoved: " + script)
|
||||
logger.debug("Signature: " + signature)
|
||||
logger.debug("PubKey: " + pubKey)
|
||||
|
@ -147,10 +147,7 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
|
|||
|
||||
|
||||
/**
|
||||
* Removes the given digital signature from the list of script tokens if it exists
|
||||
*
|
||||
* @param signature
|
||||
* @param script
|
||||
* Removes the given [[ECDigitalSignature]] from the list of [[ScriptToken]] if it exists
|
||||
* @return
|
||||
*/
|
||||
def removeSignatureFromScript(signature : ECDigitalSignature, script : Seq[ScriptToken]) : Seq[ScriptToken] = {
|
||||
|
@ -165,10 +162,7 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes the list of digital signatures from the list of script tokens
|
||||
*
|
||||
* @param sigs
|
||||
* @param script
|
||||
* Removes the list of [[ECDigitalSignature]] from the list of [[ScriptToken]]
|
||||
* @return
|
||||
*/
|
||||
def removeSignaturesFromScript(sigs : Seq[ECDigitalSignature], script : Seq[ScriptToken]) : Seq[ScriptToken] = {
|
||||
|
|
|
@ -339,11 +339,18 @@ object P2PKScriptPubKey extends Factory[P2PKScriptPubKey] {
|
|||
sealed trait CLTVScriptPubKey extends ScriptPubKey {
|
||||
/**
|
||||
* Determines the nested ScriptPubKey inside the CLTVScriptPubKey
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def scriptPubKeyAfterCLTV : ScriptPubKey = ScriptPubKey(asm.slice(4, asm.length))
|
||||
}
|
||||
|
||||
/**
|
||||
* The absolute CLTV-LockTime value (i.e. the output will remain unspendable until this timestamp or block height)
|
||||
* @return
|
||||
*/
|
||||
def locktime : UInt32 = {
|
||||
val hex = BitcoinSUtil.flipEndianess(asm(1).hex)
|
||||
UInt32(hex)
|
||||
}}
|
||||
|
||||
object CLTVScriptPubKey extends Factory[CLTVScriptPubKey] {
|
||||
private case class CLTVScriptPubKeyImpl(hex : String) extends CLTVScriptPubKey
|
||||
|
@ -383,13 +390,16 @@ object CLTVScriptPubKey extends Factory[CLTVScriptPubKey] {
|
|||
sealed trait CSVScriptPubKey extends ScriptPubKey {
|
||||
/**
|
||||
* Determines the nested ScriptPubKey inside the CSVScriptPubKey
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def scriptPubKeyAfterCSV : ScriptPubKey = ScriptPubKey(asm.slice(4, asm.length))
|
||||
|
||||
/**
|
||||
* The relative CSV-LockTime value (i.e. the amount of time the output should remain unspendable)
|
||||
* @return
|
||||
*/
|
||||
def locktime : UInt32 = {
|
||||
val hex = BitcoinSUtil.flipEndianess(asm.head.hex)
|
||||
val hex = BitcoinSUtil.flipEndianess(asm(1).hex)
|
||||
UInt32(hex)
|
||||
}
|
||||
}
|
||||
|
@ -424,7 +434,7 @@ object CSVScriptPubKey extends Factory[CSVScriptPubKey] {
|
|||
}
|
||||
}
|
||||
|
||||
trait NonStandardScriptPubKey extends ScriptPubKey
|
||||
sealed trait NonStandardScriptPubKey extends ScriptPubKey
|
||||
|
||||
object NonStandardScriptPubKey extends Factory[NonStandardScriptPubKey] {
|
||||
private case class NonStandardScriptPubKeyImpl(hex : String) extends NonStandardScriptPubKey
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.bitcoins.core.protocol.script
|
|||
import org.bitcoins.core.crypto.{ECDigitalSignature, ECPublicKey}
|
||||
import org.bitcoins.core.number.Int32
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.protocol.script.CLTVScriptSignature.CLTVScriptSignatureImpl
|
||||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL}
|
||||
import org.bitcoins.core.serializers.script.{RawScriptSignatureParser, ScriptParser}
|
||||
|
@ -58,8 +59,6 @@ object NonStandardScriptSignature extends Factory[NonStandardScriptSignature] {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* P2PKH script signatures have only one public key
|
||||
* https://bitcoin.org/en/developer-guide#pay-to-public-key-hash-p2pkh
|
||||
|
@ -120,6 +119,7 @@ object P2PKHScriptSignature extends Factory[P2PKHScriptSignature] {
|
|||
|
||||
/**
|
||||
* Determines if the given asm matches a [[P2PKHScriptSignature]]
|
||||
*
|
||||
* @param asm
|
||||
* @return
|
||||
*/
|
||||
|
@ -215,6 +215,7 @@ object P2SHScriptSignature extends Factory[P2SHScriptSignature] with BitcoinSLog
|
|||
|
||||
/**
|
||||
* Tests if the given asm tokens are a [[P2SHScriptSignature]]
|
||||
*
|
||||
* @param asm
|
||||
* @return
|
||||
*/
|
||||
|
@ -376,6 +377,7 @@ object P2PKScriptSignature extends Factory[P2PKScriptSignature] {
|
|||
|
||||
/**
|
||||
* P2PK scriptSigs always have the pattern [pushop, digitalSignature]
|
||||
*
|
||||
* @param asm
|
||||
* @return
|
||||
*/
|
||||
|
@ -396,16 +398,60 @@ sealed trait CLTVScriptSignature extends ScriptSignature {
|
|||
object CLTVScriptSignature {
|
||||
private case class CLTVScriptSignatureImpl(scriptSig : ScriptSignature) extends CLTVScriptSignature
|
||||
|
||||
def apply(scriptPubKey: ScriptPubKey, sigs : Seq[ECDigitalSignature], pubKeys : Seq[ECPublicKey]) : CLTVScriptSignature = scriptPubKey match {
|
||||
/**
|
||||
* Creates a CLTVScriptSignature 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]) : CLTVScriptSignature = scriptPubKey match {
|
||||
case p2pkScriptPubKey : P2PKScriptPubKey => CLTVScriptSignatureImpl(P2PKScriptSignature(sigs.head))
|
||||
case p2pkhScriptPubKey : P2PKHScriptPubKey => CLTVScriptSignatureImpl(P2PKHScriptSignature(sigs.head, pubKeys.head))
|
||||
case multiSigScriptPubKey : MultiSignatureScriptPubKey => CLTVScriptSignatureImpl(MultiSignatureScriptSignature(sigs))
|
||||
case cltvScriptPubKey : CLTVScriptPubKey => apply(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys)
|
||||
case p2shScriptPubKey : P2SHScriptPubKey => ???
|
||||
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)
|
||||
CLTVScriptSignatureImpl(P2SHScriptSignature(cltvScriptSigBeforeRedeemScript, redeemScript.get))
|
||||
case EmptyScriptPubKey => CLTVScriptSignatureImpl(EmptyScriptSignature)
|
||||
case nonstandard : NonStandardScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature cannot be" +
|
||||
"the underlying scriptSig in a CLTVScriptSignature.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed trait CSVScriptSignature extends ScriptSignature {
|
||||
def scriptSig : ScriptSignature
|
||||
|
||||
override def signatures : Seq[ECDigitalSignature] = scriptSig.signatures
|
||||
|
||||
override def hex = scriptSig.hex
|
||||
}
|
||||
|
||||
object CSVScriptSignature {
|
||||
private case class CSVScriptSignatureImpl(scriptSig : ScriptSignature) extends CSVScriptSignature
|
||||
|
||||
/**
|
||||
* 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 cltvScriptSigBeforeRedeemScript = apply(redeemScript.get, sigs, pubKeys, None)
|
||||
CSVScriptSignatureImpl(P2SHScriptSignature(cltvScriptSigBeforeRedeemScript, redeemScript.get))
|
||||
case EmptyScriptPubKey => CSVScriptSignatureImpl(EmptyScriptSignature)
|
||||
case nonstandard : NonStandardScriptPubKey => throw new IllegalArgumentException("A NonStandardScriptSignature cannot be" +
|
||||
"the underlying scriptSig in a CSVScriptSignature.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents the empty script signature
|
||||
|
|
|
@ -106,11 +106,11 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
case s : ScriptNumber =>
|
||||
if (s.bytes.size > 5) {
|
||||
//if the number size is larger than 5 bytes the number is invalid
|
||||
logger.error("The OP_CSV value in the script was larger than 5 bytes in size.")
|
||||
ScriptProgram(program,ScriptErrorUnknownError)
|
||||
} else if (checkSequence(program,s)) {
|
||||
ScriptProgram(program, program.stack, program.script.tail)
|
||||
} else {
|
||||
logger.error("Stack top sequence and transaction input's sequence number comparison failed")
|
||||
ScriptProgram(program, ScriptErrorUnsatisfiedLocktime)
|
||||
}
|
||||
case s : ScriptConstant =>
|
||||
|
@ -125,7 +125,7 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
/**
|
||||
* Mimics this function inside of bitcoin core
|
||||
* https://github.com/bitcoin/bitcoin/blob/e26b62093ae21e89ed7d36a24a6b863f38ec631d/src/script/interpreter.cpp#L1196
|
||||
*
|
||||
* https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki#specification
|
||||
* @param program the program whose transaction input's sequence is being compared
|
||||
* @param nSequence the script number on the stack top to compare to the input's sequence number
|
||||
* @return if the given script number is valid or not
|
||||
|
@ -134,13 +134,24 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
val inputIndex = program.txSignatureComponent.inputIndex.toInt
|
||||
logger.debug("inputIndex: " + inputIndex)
|
||||
val transaction = program.txSignatureComponent.transaction
|
||||
|
||||
// Relative lock times are supported by comparing the passed
|
||||
// in operand to the sequence number of the input.
|
||||
val txToSequence : Int64 = Int64(transaction.inputs(inputIndex).sequence.underlying)
|
||||
|
||||
// Fail if the transaction's version number is not set high
|
||||
// enough to trigger BIP 68 rules.
|
||||
if (program.txSignatureComponent.transaction.version < UInt32(2)) {
|
||||
logger.error("Transaction version is too low for OP_CSV")
|
||||
logger.error("OP_CSV fails the script if the transaction's version is less than 2.")
|
||||
return false
|
||||
}
|
||||
|
||||
// Sequence numbers with their most significant bit set are not
|
||||
// consensus constrained. Testing that the transaction's sequence
|
||||
// number do not have this bit set prevents using this property
|
||||
// to get around a CHECKSEQUENCEVERIFY check.
|
||||
if (!isLockTimeBitOff(txToSequence)) return false
|
||||
|
||||
val nLockTimeMask : UInt32 = TransactionConstants.sequenceLockTimeTypeFlag | TransactionConstants.sequenceLockTimeMask
|
||||
val txToSequenceMasked : Int64 = Int64(txToSequence.underlying & nLockTimeMask.underlying)
|
||||
val nSequenceMasked : ScriptNumber = nSequence & Int64(nLockTimeMask.underlying)
|
||||
|
@ -150,6 +161,7 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
logger.info("nSequence: " + nSequence)
|
||||
logger.info("nSequenceMasked: " + nSequenceMasked)
|
||||
logger.info("Sequence locktime flag: " + TransactionConstants.sequenceLockTimeTypeFlag)
|
||||
|
||||
// There are two kinds of nSequence: lock-by-blockheight
|
||||
// and lock-by-blocktime, distinguished by whether
|
||||
// nSequenceMasked < CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG.
|
||||
|
@ -170,7 +182,8 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
// Now that we know we're comparing apples-to-apples, the
|
||||
// comparison is a simple numeric one.
|
||||
if (nSequenceMasked > Int64(txToSequenceMasked.underlying)) {
|
||||
logger.error("OP_CSV fails because locktime in transaction has not been met yet")
|
||||
logger.error("OP_CSV fails because relative locktime in transaction has not been met yet. " +
|
||||
"(OP_CSV value was greater than the txInput's sequence)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -221,9 +234,10 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
|
||||
/**
|
||||
* The script number on the stack has the disable flag (1 << 31) unset
|
||||
*
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
def isLockTimeBitOff(s : ScriptNumber) : Boolean = (s.underlying & TransactionConstants.locktimeDisabledFlag.underlying) == 0
|
||||
|
||||
def isLockTimeBitOff(num : Int64) : Boolean = isLockTimeBitOff(ScriptNumber(num.hex))
|
||||
}
|
||||
|
|
|
@ -71,5 +71,14 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr
|
|||
Seq(ScriptErrorUnsatisfiedLocktime).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).contains(result)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.protocol.script
|
||||
|
||||
|
||||
import org.bitcoins.core.crypto.{DoubleSha256Digest, ECDigitalSignature, TransactionSignatureSerializer}
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.number.Int32
|
||||
import org.bitcoins.core.protocol.script.testprotocol.SignatureHashTestCase
|
||||
import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL, SIGHASH_SINGLE}
|
||||
|
@ -116,6 +116,12 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
|
|||
val flipHash = BitcoinSUtil.flipEndianess(testCase.hash.hex)
|
||||
hashForSig must be (DoubleSha256Digest(flipHash))
|
||||
}
|
||||
}
|
||||
|
||||
it must "create a cltvScriptSig with the correct underlying scriptSig" in {
|
||||
val cltvScriptPubKey = CLTVScriptPubKey("04e71bbe57b17576a914da88dc82530f0a4d1327dcfe75cc60c44277532c88ac")
|
||||
val pubKey = ECPublicKey("039ba48e162b1f47246f4ce9dc40f197fab7bde11da1b2fe9ac21113959e9f381b")
|
||||
val sig = ECDigitalSignature("3045022100d71cfe32fa4545c5a0fd665b3701eb458a1bacbba868a05fa703fd1fa4b4f5c502204ee706334f976d0bee9b0f0ff919c1dfe9ba027993bf3e39fc03416ba4255b2401")
|
||||
CLTVScriptSignature(cltvScriptPubKey, Seq(sig), Seq(pubKey), None).scriptSig.isInstanceOf[P2PKHScriptSignature]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue