mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 15:02:17 +01:00
Adding property to make sure multisig works in CSVEscrowTimeoutScriptPubKey
This commit is contained in:
parent
02e94ba1b6
commit
948b0021fe
6 changed files with 158 additions and 19 deletions
|
@ -7,7 +7,7 @@ import org.bitcoins.core.protocol.blockchain.Block
|
|||
import org.bitcoins.core.script.ScriptSettings
|
||||
import org.bitcoins.core.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
|
||||
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, _}
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.script.control.{OP_ELSE, OP_ENDIF, OP_IF, OP_RETURN}
|
||||
import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_HASH160}
|
||||
import org.bitcoins.core.script.locktime.{OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY}
|
||||
import org.bitcoins.core.script.reserved.UndefinedOP_NOP
|
||||
|
@ -515,6 +515,7 @@ object ScriptPubKey extends Factory[ScriptPubKey] with BitcoinSLogger {
|
|||
case _ if CSVScriptPubKey.isCSVScriptPubKey(asm) => CSVScriptPubKey(asm)
|
||||
case _ if WitnessScriptPubKey.isWitnessScriptPubKey(asm) => WitnessScriptPubKey(asm).get
|
||||
case _ if WitnessCommitment.isWitnessCommitment(asm) => WitnessCommitment(asm)
|
||||
case _ if CSVEscrowTimeoutScriptPubKey.isValidCSVEscrowTimeout(asm) => CSVEscrowTimeoutScriptPubKey.fromAsm(asm)
|
||||
case _ => NonStandardScriptPubKey(asm)
|
||||
}
|
||||
|
||||
|
@ -664,3 +665,58 @@ object WitnessCommitment extends ScriptFactory[WitnessCommitment] {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Represents a [[ScriptPubKey]] that either times out allowing Alice to spend from the scriptpubkey
|
||||
* or allows a federation to spend from the escrow
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki#contracts-with-expiration-deadlines]]
|
||||
* Format: OP_IF <multsig scriptpubkey> OP_ELSE <csv scriptPubKey> OP_ENDIF
|
||||
*/
|
||||
sealed trait CSVEscrowTimeoutScriptPubKey extends ScriptPubKey {
|
||||
/** The [[MultiSignatureScriptPubKey]] that can be used to spend funds */
|
||||
def escrow: MultiSignatureScriptPubKey = {
|
||||
val escrowAsm = asm.slice(1,opElseIndex)
|
||||
MultiSignatureScriptPubKey(escrowAsm)
|
||||
}
|
||||
|
||||
/** The [[CSVScriptPubKey]] that you can spend funds from after a certain timeout */
|
||||
def timeout: CSVScriptPubKey = {
|
||||
val timeoutAsm = asm.slice(opElseIndex+1, asm.length-1)
|
||||
CSVScriptPubKey(timeoutAsm)
|
||||
}
|
||||
|
||||
private def opElseIndex: Int = {
|
||||
val idx = asm.indexOf(OP_ELSE)
|
||||
require(idx != -1, "CSVEscrowWithTimeout has to contain OP_ELSE asm token")
|
||||
idx
|
||||
}
|
||||
}
|
||||
|
||||
object CSVEscrowTimeoutScriptPubKey extends ScriptFactory[CSVEscrowTimeoutScriptPubKey] {
|
||||
private case class CSVEscrowTimeoutScriptPubKeyImpl(hex: String) extends CSVEscrowTimeoutScriptPubKey
|
||||
|
||||
override def fromBytes(bytes: Seq[Byte]): CSVEscrowTimeoutScriptPubKey = {
|
||||
val asm = RawScriptPubKeyParser.read(bytes).asm
|
||||
fromAsm(asm)
|
||||
}
|
||||
|
||||
override def fromAsm(asm: Seq[ScriptToken]): CSVEscrowTimeoutScriptPubKey = {
|
||||
buildScript(asm, CSVEscrowTimeoutScriptPubKeyImpl(_), isValidCSVEscrowTimeout(_), "Given asm was not a valid CSVEscrowTimeout, got: " + asm)
|
||||
}
|
||||
|
||||
def isValidCSVEscrowTimeout(asm: Seq[ScriptToken]): Boolean = {
|
||||
val opElseIndex = asm.indexOf(OP_ELSE)
|
||||
val correctControlStructure = asm.headOption.contains(OP_IF) && asm.last == OP_ENDIF && opElseIndex != -1
|
||||
if (correctControlStructure) {
|
||||
val escrowAsm = asm.slice(1,opElseIndex)
|
||||
val escrow = Try(MultiSignatureScriptPubKey(escrowAsm))
|
||||
val timeoutAsm = asm.slice(opElseIndex+1, asm.length-1)
|
||||
val timeout = Try(CSVScriptPubKey(timeoutAsm))
|
||||
escrow.isSuccess && timeout.isSuccess
|
||||
} else false
|
||||
}
|
||||
|
||||
def apply(escrow: MultiSignatureScriptPubKey, timeout: CSVScriptPubKey): CSVEscrowTimeoutScriptPubKey = {
|
||||
fromAsm(Seq(OP_IF) ++ escrow.asm ++ Seq(OP_ELSE) ++ timeout.asm ++ Seq(OP_ENDIF))
|
||||
}
|
||||
}
|
|
@ -210,12 +210,9 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
|
|||
|
||||
/** Detects if the given script token is a redeem script */
|
||||
def isRedeemScript(token : ScriptToken) : Boolean = {
|
||||
logger.debug("Checking if last token is redeem script")
|
||||
val redeemScriptTry : Try[ScriptPubKey] = parseRedeemScript(token)
|
||||
redeemScriptTry match {
|
||||
case Success(redeemScript) =>
|
||||
logger.debug("Possible redeemScript: " + redeemScript.asm)
|
||||
logger.debug("Redeem script: " + redeemScript)
|
||||
redeemScript match {
|
||||
case x : P2PKHScriptPubKey => true
|
||||
case x : MultiSignatureScriptPubKey => true
|
||||
|
@ -299,8 +296,6 @@ object MultiSignatureScriptSignature extends ScriptFactory[MultiSignatureScriptS
|
|||
val restOfScriptIsPushOpsOrScriptConstants = asm.tail.map(
|
||||
token => token.isInstanceOf[ScriptConstant] || StackPushOperationFactory.isPushOperation(token)
|
||||
).exists(_ == false)
|
||||
logger.debug("First number is script op: " + firstTokenIsScriptNumberOperation)
|
||||
logger.debug("tail is true: " +restOfScriptIsPushOpsOrScriptConstants )
|
||||
firstTokenIsScriptNumberOperation && !restOfScriptIsPushOpsOrScriptConstants
|
||||
}
|
||||
}
|
||||
|
@ -423,11 +418,13 @@ object CSVScriptSignature extends Factory[CSVScriptSignature] {
|
|||
* @return
|
||||
*/
|
||||
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 _: P2PKScriptPubKey => CSVScriptSignature(P2PKScriptSignature(sigs.head))
|
||||
case _: P2PKHScriptPubKey => CSVScriptSignature(P2PKHScriptSignature(sigs.head, pubKeys.head))
|
||||
case _: MultiSignatureScriptPubKey => CSVScriptSignature(MultiSignatureScriptSignature(sigs))
|
||||
case cltvScriptPubKey : CLTVScriptPubKey => CSVScriptSignature(cltvScriptPubKey.scriptPubKeyAfterCLTV, sigs, pubKeys)
|
||||
case csvScriptPubKey : CSVScriptPubKey => CSVScriptSignature(csvScriptPubKey.scriptPubKeyAfterCSV, sigs, pubKeys)
|
||||
case csvEscrowTimeout: CSVEscrowTimeoutScriptPubKey =>
|
||||
CSVScriptSignature(csvEscrowTimeout.timeout.scriptPubKeyAfterCSV,sigs,pubKeys)
|
||||
case EmptyScriptPubKey => CSVScriptSignature(EmptyScriptSignature)
|
||||
case _: WitnessScriptPubKeyV0 | _ : UnassignedWitnessScriptPubKey =>
|
||||
//bare segwit always has an empty script sig, see BIP141
|
||||
|
@ -480,6 +477,10 @@ object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger {
|
|||
case _: NonStandardScriptPubKey => Try(NonStandardScriptSignature.fromAsm(tokens))
|
||||
case s : CLTVScriptPubKey => fromScriptPubKey(tokens, s.scriptPubKeyAfterCLTV)
|
||||
case s : CSVScriptPubKey => fromScriptPubKey(tokens, s.scriptPubKeyAfterCSV)
|
||||
case escrowWithTimeout : CSVEscrowTimeoutScriptPubKey =>
|
||||
val isMultiSig = BitcoinScriptUtil.castToBool(tokens.head)
|
||||
if (isMultiSig) Try(MultiSignatureScriptSignature.fromAsm(tokens.tail))
|
||||
else Try(CSVEscrowTimeoutScriptSignature.fromAsm(tokens,escrowWithTimeout))
|
||||
case _: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey => Success(EmptyScriptSignature)
|
||||
case EmptyScriptPubKey =>
|
||||
if (tokens.isEmpty) Success(EmptyScriptSignature) else Try(NonStandardScriptSignature.fromAsm(tokens))
|
||||
|
@ -491,3 +492,73 @@ object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger {
|
|||
}
|
||||
}
|
||||
|
||||
/** [[ScriptSignature]] that spends a [[CSVEscrowTimeoutScriptPubKey]], the underlying script signature can be
|
||||
* a [[MultiSignatureScriptSignature]] or a [[CSVScriptSignature]] as those are te two underlying scripts
|
||||
* of a [[CSVEscrowTimeoutScriptPubKey]]
|
||||
*
|
||||
* If the last element of the [[asm]] evaluates to true, it is a scriptsig that attempts to spend the escrow
|
||||
* if the last element of the [[asm]] evaluates to false, it is a scriptsig that attempts to spend the timeout
|
||||
* */
|
||||
sealed trait CSVEscrowTimeoutScriptSignature extends ScriptSignature {
|
||||
def scriptSig: ScriptSignature = ScriptSignature(hex)
|
||||
override def signatures = scriptSig.signatures
|
||||
override def toString = "CSVEscrowWithTimeoutScriptSignature(" + scriptSig + ")"
|
||||
|
||||
/** Checks if the given asm fulfills the timeout or escrow of the [[CSVEscrowTimeoutScriptPubKey]] */
|
||||
def isEscrow: Boolean = BitcoinScriptUtil.castToBool(asm.last)
|
||||
|
||||
def isTimeout: Boolean = !isEscrow
|
||||
}
|
||||
|
||||
|
||||
object CSVEscrowTimeoutScriptSignature extends Factory[CSVEscrowTimeoutScriptSignature] {
|
||||
private case class CSVEscrowTimeoutScriptSignatureImpl(hex: String) extends CSVEscrowTimeoutScriptSignature
|
||||
|
||||
override def fromBytes(bytes: Seq[Byte]): CSVEscrowTimeoutScriptSignature = {
|
||||
CSVEscrowTimeoutScriptSignatureImpl(BitcoinSUtil.encodeHex(bytes))
|
||||
}
|
||||
|
||||
def fromAsm(asm: Seq[ScriptToken], scriptPubKey: CSVEscrowTimeoutScriptPubKey): CSVEscrowTimeoutScriptSignature = {
|
||||
require(isValidCSVEscrowTimeoutScriptSig(asm,scriptPubKey), "Given asm was not a CSVEscrowWithTimeoutScriptSignature, got: " + asm)
|
||||
val asmHex = asm.map(_.hex).mkString
|
||||
val c = CompactSizeUInt.calculateCompactSizeUInt(asmHex)
|
||||
val fullHex = c.hex + asmHex
|
||||
fromHex(fullHex)
|
||||
}
|
||||
|
||||
def fromAsm(asm: Seq[ScriptToken]): CSVEscrowTimeoutScriptSignature = {
|
||||
require(asm.nonEmpty)
|
||||
val nestedScriptSig = if (BitcoinScriptUtil.castToBool(asm.head)) {
|
||||
require(MultiSignatureScriptSignature.isMultiSignatureScriptSignature(asm.tail), "Need multisigScriptSig, got: " + asm.tail)
|
||||
MultiSignatureScriptSignature.fromAsm(asm.tail)
|
||||
} else {
|
||||
CSVScriptSignature(ScriptSignature.fromAsm(asm.tail))
|
||||
}
|
||||
val bytes = asm.head.bytes ++ nestedScriptSig.asmBytes
|
||||
val c = CompactSizeUInt.calculateCompactSizeUInt(bytes)
|
||||
val fullBytes = c.bytes ++ bytes
|
||||
fromBytes(fullBytes)
|
||||
}
|
||||
|
||||
def isValidCSVEscrowTimeoutScriptSig(asm: Seq[ScriptToken],
|
||||
scriptPubKey: CSVEscrowTimeoutScriptPubKey): Boolean = {
|
||||
if (MultiSignatureScriptSignature.isMultiSignatureScriptSignature(asm.tail)) {
|
||||
true
|
||||
} else {
|
||||
val locktimeScript = scriptPubKey.timeout.scriptPubKeyAfterCSV
|
||||
Try(ScriptSignature(asm,locktimeScript)).isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
def apply(scriptSig: ScriptSignature): CSVEscrowTimeoutScriptSignature = fromBytes(scriptSig.bytes)
|
||||
|
||||
def apply(multiSigScriptSig: MultiSignatureScriptSignature): CSVEscrowTimeoutScriptSignature = {
|
||||
val asm = multiSigScriptSig.asm ++ Seq(OP_1)
|
||||
fromAsm(asm)
|
||||
}
|
||||
|
||||
def apply(csv: CSVScriptSignature): CSVEscrowTimeoutScriptSignature = {
|
||||
val asm = csv.asm ++ Seq(OP_0)
|
||||
fromAsm(asm)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
|
|||
//remove OP_ELSE from binary tree
|
||||
val newTreeWithoutOpElse = removeFirstOpElse(binaryTree)
|
||||
val newScript = newTreeWithoutOpElse.toList
|
||||
logger.debug("New script after removing OP_ELSE branch " + newScript)
|
||||
logger.debug("New script after removing OP_ELSE branch " + newScript.tail)
|
||||
logger.debug("New stack after removing OP_ELSE branch: " + program.stack.tail)
|
||||
ScriptProgram(program, program.stack.tail,newScript.tail)
|
||||
} else {
|
||||
logger.debug("OP_IF stack top was false")
|
||||
|
|
|
@ -76,8 +76,10 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
case p2sh : P2SHScriptPubKey =>
|
||||
if (p2shEnabled) executeP2shScript(scriptSigExecutedProgram, program, p2sh)
|
||||
else scriptPubKeyExecutedProgram
|
||||
case _ : P2PKHScriptPubKey | _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: CSVScriptPubKey |
|
||||
_ : CLTVScriptPubKey | _ : NonStandardScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey =>
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: CSVScriptPubKey
|
||||
| _: CLTVScriptPubKey | _: NonStandardScriptPubKey | _: WitnessCommitment
|
||||
| _: CSVEscrowTimeoutScriptPubKey | EmptyScriptPubKey =>
|
||||
logger.info("")
|
||||
scriptPubKeyExecutedProgram
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +163,9 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
//treat the segwit scriptpubkey as any other redeem script
|
||||
run(scriptPubKeyExecutedProgram,stack,w)
|
||||
}
|
||||
case s @ (_ : P2SHScriptPubKey | _ : P2PKHScriptPubKey | _ : P2PKScriptPubKey | _ : MultiSignatureScriptPubKey |
|
||||
_ : CLTVScriptPubKey | _ : CSVScriptPubKey | _: NonStandardScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey) =>
|
||||
case s @ (_ : P2SHScriptPubKey | _ : P2PKHScriptPubKey | _ : P2PKScriptPubKey | _ : MultiSignatureScriptPubKey
|
||||
| _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _: NonStandardScriptPubKey | _ : WitnessCommitment
|
||||
| _: CSVEscrowTimeoutScriptPubKey | EmptyScriptPubKey) =>
|
||||
logger.debug("redeemScript: " + s.asm)
|
||||
run(scriptPubKeyExecutedProgram,stack,s)
|
||||
}
|
||||
|
@ -515,8 +518,9 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
case _ : P2SHScriptPubKey =>
|
||||
val p2shScriptSig = P2SHScriptSignature(txSigComponent.scriptSignature.bytes)
|
||||
p2shScriptSig.redeemScript.isInstanceOf[WitnessScriptPubKey]
|
||||
case _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : MultiSignatureScriptPubKey | _ : NonStandardScriptPubKey |
|
||||
_ : P2PKScriptPubKey | _ : P2PKHScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey =>
|
||||
case _: CLTVScriptPubKey | _: CSVScriptPubKey | _: MultiSignatureScriptPubKey | _: NonStandardScriptPubKey
|
||||
| _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: WitnessCommitment
|
||||
| _: CSVEscrowTimeoutScriptPubKey | EmptyScriptPubKey =>
|
||||
w.witness.stack.isEmpty
|
||||
}
|
||||
!witnessedUsed
|
||||
|
|
|
@ -306,9 +306,9 @@ trait BitcoinScriptUtil extends BitcoinSLogger {
|
|||
//with a witness scriptPubKey
|
||||
script
|
||||
}
|
||||
case _ : P2PKHScriptPubKey | _ : P2PKScriptPubKey | _ : MultiSignatureScriptPubKey |
|
||||
_ : NonStandardScriptPubKey | _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey =>
|
||||
script
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey
|
||||
| _: NonStandardScriptPubKey | _: CLTVScriptPubKey | _: CSVScriptPubKey
|
||||
| _: WitnessCommitment | _: CSVEscrowTimeoutScriptPubKey | EmptyScriptPubKey => script
|
||||
}
|
||||
|
||||
/** Removes the given [[ECDigitalSignature]] from the list of [[ScriptToken]] if it exists. */
|
||||
|
|
|
@ -138,4 +138,11 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr
|
|||
if (result != ScriptOk) logger.warn("Result: " + result)
|
||||
Seq(ScriptErrorPushSize, ScriptOk).contains(result)
|
||||
}
|
||||
|
||||
property("generate a valid signature for a csv escrow timeout transaction") =
|
||||
Prop.forAll(TransactionGenerators.spendableCSVMultiSigEscrowTimeoutTransaction) { txSigComponent: TransactionSignatureComponent =>
|
||||
val program = ScriptProgram(txSigComponent)
|
||||
val result = ScriptInterpreter.run(program)
|
||||
result == ScriptOk
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue