Skip to content

Commit

Permalink
P2PKWithTimeoutSPK (#967)
Browse files Browse the repository at this point in the history
* Introduced P2PKWithTimeoutSPK

* Responded to review

* Added some testing for cltv and p2pkWithTimeout SPKs

* Responded to more review
  • Loading branch information
nkohen authored and Christewart committed Dec 19, 2019
1 parent d78bcaa commit 74f7f73
Show file tree
Hide file tree
Showing 15 changed files with 486 additions and 144 deletions.
Expand Up @@ -210,8 +210,8 @@ class TransactionTest extends BitcoinSUnitTest {
flags = testCase.flags)
}
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: CLTVScriptPubKey |
_: CSVScriptPubKey | _: CLTVScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: CLTVScriptPubKey | _: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | EmptyScriptPubKey) =>
val output = TransactionOutput(amount, x)
Expand Down Expand Up @@ -292,6 +292,7 @@ class TransactionTest extends BitcoinSUnitTest {
flags = testCase.flags)
}
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey |
_: MultiSignatureScriptPubKey | _: CLTVScriptPubKey |
_: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
Expand Down
Expand Up @@ -79,8 +79,8 @@ class ScriptInterpreterTest extends BitcoinSUnitTest {
flags = flags)
t
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: CLTVScriptPubKey |
_: CSVScriptPubKey | _: CLTVScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: CLTVScriptPubKey | _: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | EmptyScriptPubKey) =>
val output = TransactionOutput(amount, x)
Expand Down
Expand Up @@ -525,8 +525,8 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
Policy.standardFlags)
case _: UnassignedWitnessScriptPubKey => ???
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: WitnessCommitment |
_: CSVScriptPubKey | _: CLTVScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: WitnessCommitment | _: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
EmptyScriptPubKey) =>
val o = TransactionOutput(CurrencyUnits.zero, x)
Expand Down
26 changes: 14 additions & 12 deletions core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala
Expand Up @@ -104,8 +104,8 @@ sealed abstract class WitnessTxSigComponentP2SH extends WitnessTxSigComponent {
scriptSignature.redeemScript match {
case w: WitnessScriptPubKey => Success(w)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: P2SHScriptPubKey |
_: CSVScriptPubKey | _: CLTVScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: CSVScriptPubKey | _: CLTVScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | EmptyScriptPubKey) =>
Failure(new IllegalArgumentException(
Expand Down Expand Up @@ -179,9 +179,10 @@ object WitnessTxSigComponent {
case _: P2SHScriptPubKey =>
WitnessTxSigComponentP2SH(transaction, inputIndex, output, flags)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: WitnessCommitment |
_: NonStandardScriptPubKey | EmptyScriptPubKey) =>
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: WitnessCommitment | _: NonStandardScriptPubKey |
EmptyScriptPubKey) =>
throw new IllegalArgumentException(
s"Cannot create a WitnessTxSigComponent out of $x")
}
Expand All @@ -206,10 +207,10 @@ object WitnessTxSigComponentRaw {
case _: WitnessScriptPubKey =>
WitnessTxSigComponentRawImpl(transaction, inputIndex, output, flags)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: P2SHScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
EmptyScriptPubKey) =>
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | EmptyScriptPubKey) =>
throw new IllegalArgumentException(
s"Cannot create a WitnessTxSigComponentRaw with a spk of $x")
}
Expand All @@ -235,9 +236,10 @@ object WitnessTxSigComponentP2SH {
case _: P2SHScriptPubKey =>
WitnessTxSigComponentP2SHImpl(transaction, inputIndex, output, flags)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: WitnessScriptPubKey | EmptyScriptPubKey) =>
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: WitnessScriptPubKey | EmptyScriptPubKey) =>
throw new IllegalArgumentException(
s"Cannot create a WitnessTxSigComponentP2SH with a spk of $x")
}
Expand Down
111 changes: 60 additions & 51 deletions core/src/main/scala/org/bitcoins/core/protocol/Address.scala
Expand Up @@ -187,19 +187,20 @@ object Bech32Address extends AddressFactory[Bech32Address] {

override def fromScriptPubKey(
spk: ScriptPubKey,
np: NetworkParameters): Try[Bech32Address] = spk match {
case witSPK: WitnessScriptPubKey =>
Bech32Address.fromScriptPubKey(witSPK, np)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: P2SHScriptPubKey |
_: LockTimeScriptPubKey | _: WitnessScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: UnassignedWitnessScriptPubKey |
EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
np: NetworkParameters): Try[Bech32Address] =
spk match {
case witSPK: WitnessScriptPubKey =>
Bech32Address.fromScriptPubKey(witSPK, np)
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: LockTimeScriptPubKey |
_: WitnessScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}

}

Expand Down Expand Up @@ -258,17 +259,19 @@ object P2PKHAddress extends AddressFactory[P2PKHAddress] {

override def fromScriptPubKey(
spk: ScriptPubKey,
np: NetworkParameters): Try[P2PKHAddress] = spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case x @ (_: P2PKScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: WitnessScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
np: NetworkParameters): Try[P2PKHAddress] =
spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: MultiSignatureScriptPubKey | _: P2SHScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: WitnessScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: UnassignedWitnessScriptPubKey |
EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
}

object P2SHAddress extends AddressFactory[P2SHAddress] {
Expand Down Expand Up @@ -296,7 +299,8 @@ object P2SHAddress extends AddressFactory[P2SHAddress] {

def apply(
hash: Sha256Hash160Digest,
network: NetworkParameters): P2SHAddress = P2SHAddressImpl(hash, network)
network: NetworkParameters): P2SHAddress =
P2SHAddressImpl(hash, network)

override def fromString(address: String): Try[P2SHAddress] = {
val decodeCheckP2SH: Try[ByteVector] = Base58.decodeCheck(address)
Expand Down Expand Up @@ -329,17 +333,19 @@ object P2SHAddress extends AddressFactory[P2SHAddress] {

override def fromScriptPubKey(
spk: ScriptPubKey,
np: NetworkParameters): Try[P2SHAddress] = spk match {
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: WitnessScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
np: NetworkParameters): Try[P2SHAddress] =
spk match {
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: WitnessScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: UnassignedWitnessScriptPubKey |
EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
}

object BitcoinAddress extends AddressFactory[BitcoinAddress] {
Expand All @@ -358,18 +364,20 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] {

override def fromScriptPubKey(
spk: ScriptPubKey,
np: NetworkParameters): Try[BitcoinAddress] = spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case witSPK: WitnessScriptPubKey => Success(Bech32Address(witSPK, np))
case x @ (_: P2PKScriptPubKey | _: MultiSignatureScriptPubKey |
_: LockTimeScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
np: NetworkParameters): Try[BitcoinAddress] =
spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case witSPK: WitnessScriptPubKey => Success(Bech32Address(witSPK, np))
case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: UnassignedWitnessScriptPubKey |
EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}

}

Expand All @@ -392,9 +400,10 @@ object Address extends AddressFactory[Address] {
}
override def fromScriptPubKey(
spk: ScriptPubKey,
network: NetworkParameters): Try[Address] = network match {
case _: BitcoinNetwork => BitcoinAddress.fromScriptPubKey(spk, network)
}
network: NetworkParameters): Try[Address] =
network match {
case _: BitcoinNetwork => BitcoinAddress.fromScriptPubKey(spk, network)
}

def apply(
spk: ScriptPubKey,
Expand Down
Expand Up @@ -866,6 +866,79 @@ object NonStandardNotIfConditionalScriptPubKey
}
}

/** The type for ScriptPubKeys of the form:
* OP_IF
* <Public Key>
* OP_ELSE
* <Timeout> OP_CHECKLOCKTIMEVERIFY OP_DROP
* <Timeout Public Key>
* OP_ENDIF
* OP_CHECKSIG
*/
sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey {

lazy val pubKey: ECPublicKey =
ECPublicKey.fromBytes(asm(2).bytes)

lazy val lockTime: ScriptNumber = ScriptNumber.fromBytes(asm(5).bytes)

lazy val timeoutPubKey: ECPublicKey =
ECPublicKey.fromBytes(asm(9).bytes)
}

object P2PKWithTimeoutScriptPubKey
extends ScriptFactory[P2PKWithTimeoutScriptPubKey] {
private case class P2PKWithTimeoutScriptPubKeyImpl(asm: Vector[ScriptToken])
extends P2PKWithTimeoutScriptPubKey

override def fromAsm(asm: Seq[ScriptToken]): P2PKWithTimeoutScriptPubKey = {
buildScript(
asm = asm.toVector,
constructor = P2PKWithTimeoutScriptPubKeyImpl.apply,
invariant = isP2PKWithTimeoutScriptPubKey,
errorMsg = s"Given asm was not a P2PKWithTimeoutScriptPubKey, got $asm"
)
}

def apply(
pubKey: ECPublicKey,
lockTime: ScriptNumber,
timeoutPubKey: ECPublicKey): P2PKWithTimeoutScriptPubKey = {
val timeoutAsm = CLTVScriptPubKey(lockTime, EmptyScriptPubKey).asm.toVector
val pubKeyAsm = BitcoinScriptUtil
.calculatePushOp(pubKey.bytes)
.toVector ++ Vector(ScriptConstant(pubKey.bytes))
val timeoutPubKeyAsm = BitcoinScriptUtil
.calculatePushOp(timeoutPubKey.bytes)
.toVector ++ Vector(ScriptConstant(timeoutPubKey.bytes))

P2PKWithTimeoutScriptPubKeyImpl(
Vector(Vector(OP_IF),
pubKeyAsm,
Vector(OP_ELSE),
timeoutAsm,
timeoutPubKeyAsm,
Vector(OP_ENDIF, OP_CHECKSIG)).flatten
)
}

def isP2PKWithTimeoutScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
if (asm.length == 12) {
val pubKey = ECPublicKey.fromBytes(asm(2).bytes)
val lockTimeTry = Try(ScriptNumber.fromBytes(asm(5).bytes))
val timeoutPubKey = ECPublicKey.fromBytes(asm(9).bytes)

lockTimeTry match {
case Success(lockTime) =>
asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm
case Failure(_) => false
}
} else {
false
}
}
}

sealed trait NonStandardScriptPubKey extends RawScriptPubKey

object NonStandardScriptPubKey extends ScriptFactory[NonStandardScriptPubKey] {
Expand Down Expand Up @@ -896,6 +969,8 @@ object RawScriptPubKey extends ScriptFactory[RawScriptPubKey] {

def fromAsm(asm: Seq[ScriptToken]): RawScriptPubKey = asm match {
case Nil => EmptyScriptPubKey
case _ if P2PKWithTimeoutScriptPubKey.isP2PKWithTimeoutScriptPubKey(asm) =>
P2PKWithTimeoutScriptPubKey.fromAsm(asm)
case _
if MultiSignatureWithTimeoutScriptPubKey
.isMultiSignatureWithTimeoutScriptPubKey(asm) =>
Expand Down
Expand Up @@ -229,9 +229,9 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
redeemScript match {
case _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: P2PKScriptPubKey |
_: ConditionalScriptPubKey | _: CLTVScriptPubKey |
_: CSVScriptPubKey | _: WitnessScriptPubKeyV0 |
_: UnassignedWitnessScriptPubKey =>
_: P2PKWithTimeoutScriptPubKey | _: ConditionalScriptPubKey |
_: CLTVScriptPubKey | _: CSVScriptPubKey |
_: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey =>
true
case _: NonStandardScriptPubKey | _: WitnessCommitment => false
case EmptyScriptPubKey => false
Expand Down Expand Up @@ -365,6 +365,29 @@ object P2PKScriptSignature extends ScriptFactory[P2PKScriptSignature] {
}
}

object P2PKWithTimeoutScriptSignature
extends ScriptFactory[ConditionalScriptSignature] {
override def fromAsm(asm: Seq[ScriptToken]): ConditionalScriptSignature = {
buildScript(
asm.toVector,
ConditionalScriptSignature.fromAsm,
isP2PKWithTimeoutScriptSignature,
s"The given asm tokens were not a P2PKWithTimeoutScriptSignature, got $asm"
)
}

def apply(
beforeTimeout: Boolean,
signature: ECDigitalSignature): ConditionalScriptSignature = {
ConditionalScriptSignature(P2PKScriptSignature(signature), beforeTimeout)
}

def isP2PKWithTimeoutScriptSignature(asm: Seq[ScriptToken]): Boolean = {
P2PKScriptSignature.isP2PKScriptSignature(asm.dropRight(1)) && ConditionalScriptSignature
.isValidConditionalScriptSig(asm)
}
}

/** Parent type for all lock time script signatures, these spend [[LockTimeScriptPubKey]] */
sealed trait LockTimeScriptSignature extends ScriptSignature {
def scriptSig: ScriptSignature = ScriptSignature(hex)
Expand Down

0 comments on commit 74f7f73

Please sign in to comment.