Creating P2SHScriptPubKeySpec & P2SHScriptSignatureSpec, adding companion object to create P2SHScriptSigs & P2SHScriptPubKeys, adding functionality to calculate push operators for constants in scripts

This commit is contained in:
Chris Stewart 2016-06-24 13:07:52 -05:00
parent 44d01ef65e
commit b4b93bf49b
8 changed files with 127 additions and 14 deletions

View File

@ -170,6 +170,26 @@ object MultiSignatureScriptPubKey extends Factory[MultiSignatureScriptPubKey] wi
*/
trait P2SHScriptPubKey extends ScriptPubKey
object P2SHScriptPubKey extends Factory[P2SHScriptPubKey] {
override def fromBytes(bytes : Seq[Byte]): P2SHScriptPubKey = {
val scriptPubKey = RawScriptPubKeyParser.read(bytes)
matchP2SHScriptPubKey(scriptPubKey)
}
def apply(scriptPubKey: ScriptPubKey) : P2SHScriptPubKey = {
val hash = CryptoUtil.ripeMd160(scriptPubKey.bytes)
val asm = Seq(OP_HASH160, BytesToPushOntoStack(hash.bytes.size), ScriptConstant(hash.bytes), OP_EQUAL)
val p2shScriptPubKey = ScriptPubKey.fromAsm(asm)
matchP2SHScriptPubKey(p2shScriptPubKey)
}
private def matchP2SHScriptPubKey(scriptPubKey : ScriptPubKey): P2SHScriptPubKey = scriptPubKey match {
case p2shScriptPubKey : P2SHScriptPubKey => p2shScriptPubKey
case scriptPubKey : ScriptPubKey =>
throw new IllegalArgumentException("Exepected multisignature scriptPubKey, got: " + scriptPubKey)
}
}
/**
* Represents a pay to public key script public key
* https://bitcoin.org/en/developer-guide#pubkey

View File

@ -173,6 +173,27 @@ trait P2SHScriptSignature extends ScriptSignature {
//call .tail twice to remove the serialized redeemScript & it's bytesToPushOntoStack constant
(asm.reverse.tail.tail.reverse, Seq(asm.last))
}
}
object P2SHScriptSignature extends Factory[P2SHScriptSignature] with BitcoinSLogger {
override def fromBytes(bytes : Seq[Byte]): P2SHScriptSignature = {
val scriptSig = RawScriptSignatureParser.read(bytes)
logger.info("p2sh script sig asm: " + scriptSig.asm)
matchP2SHScriptSignature(scriptSig)
}
def apply(scriptSig : ScriptSignature, redeemScript : ScriptPubKey): P2SHScriptSignature = {
//we need to calculate the size of the redeemScript and add the corresponding push op
val pushOps = BitcoinScriptUtil.calculatePushOp(ScriptConstant(redeemScript.bytes))
val bytes = scriptSig.bytes ++ pushOps.flatMap(_.bytes) ++ redeemScript.bytes
fromBytes(bytes)
}
private def matchP2SHScriptSignature(scriptSig : ScriptSignature): P2SHScriptSignature = scriptSig match {
case p2shScriptSig : P2SHScriptSignature => p2shScriptSig
case x : ScriptSignature => throw new IllegalArgumentException("Expected p2sh script signature, got: " + x)
}
}

View File

@ -54,8 +54,6 @@ trait ConstantInterpreter extends BitcoinSLogger {
* @return
*/
def pushScriptNumberBytesToStack(program : ScriptProgram) : ScriptProgram = {
val bytesNeeded : Long = program.script.head match {
case OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 =>
bytesNeededForPushOp(program.script(1))

View File

@ -50,7 +50,6 @@ trait ScriptOperation extends ScriptToken {
sealed trait ScriptConstant extends ScriptToken {
/**
* Returns if the constant is encoded in the shortest possible way
*
* @return
*/
def isShortestEncoding : Boolean = BitcoinScriptUtil.isShortestEncoding(this)
@ -63,7 +62,6 @@ sealed trait ScriptConstant extends ScriptToken {
sealed trait ScriptNumber extends ScriptConstant {
/**
* The underlying number of the ScriptNumber
*
* @return
*/
def num : Long
@ -87,7 +85,6 @@ sealed trait ScriptNumber extends ScriptConstant {
* This equality just checks that the underlying scala numbers are equivalent, NOT if the numbers
* are bitwise equivalent in Script. For instance ScriptNumber(0x01).numEqual(ScriptNumber(0x00000000001)) == true
* but (ScriptNumber(0x01) == (ScriptNumber(0x00000000001))) == false
*
* @param that
* @return
*/
@ -179,6 +176,11 @@ object ScriptNumber extends Factory[ScriptNumber] {
*/
case object OP_PUSHDATA1 extends ScriptOperation {
override def opCode = 76
/**
* The maximum amount of bytes OP_PUSHDATA1 can push onto the stack
*/
def max = 255
}
/**
@ -186,6 +188,12 @@ case object OP_PUSHDATA1 extends ScriptOperation {
*/
case object OP_PUSHDATA2 extends ScriptOperation {
override def opCode = 77
/**
* The max amount of data that OP_PUSHDATA2 can push onto the stack
* @return
*/
def max = 65535
}
/**
@ -193,6 +201,12 @@ case object OP_PUSHDATA2 extends ScriptOperation {
*/
case object OP_PUSHDATA4 extends ScriptOperation {
override def opCode = 78
/**
* The maximum amount of data that OP_PUSHDATA4 can be push on the stack
* @return
*/
def max = 4294967295L
}

View File

@ -1,6 +1,8 @@
package org.bitcoins.core.util
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_CHECKSIGVERIFY}
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil}
@ -178,12 +180,36 @@ trait BitcoinScriptUtil {
}
}
/**
* Calculates the push operation for the given [[ScriptToken]]
* @param scriptToken
* @return
*/
def calculatePushOp(scriptToken : ScriptToken) : Seq[ScriptToken] = {
//push ops following an OP_PUSHDATA operation are interpreted as unsigned numbers
val scriptTokenSize = UInt32(scriptToken.bytes.size)
val bytes = scriptTokenSize.bytes
if (scriptTokenSize <= UInt32(75)) Seq(BytesToPushOntoStack(scriptToken.bytes.size))
else if (scriptTokenSize <= UInt32(OP_PUSHDATA1.max)) {
val pushConstant = ScriptConstant(BitcoinSUtil.flipEndianess(bytes.slice(bytes.length-1,bytes.length)))
Seq(OP_PUSHDATA1, pushConstant)
}
else if (scriptTokenSize <= UInt32(OP_PUSHDATA2.max)) {
val pushConstant = ScriptConstant(BitcoinSUtil.flipEndianess(bytes.slice(bytes.length-2,bytes.length)))
Seq(OP_PUSHDATA2, pushConstant)
}
else if (scriptTokenSize <= UInt32(OP_PUSHDATA4.max)) {
val pushConstant = ScriptConstant(BitcoinSUtil.flipEndianess(bytes))
Seq(OP_PUSHDATA4, pushConstant)
}
else throw new IllegalArgumentException("ScriptToken is to large for pushops, size: " + scriptTokenSize)
}
/**
* Whenever a script constant is interpreted to a number BIP62 could enforce that number to be encoded
* in the smallest encoding possible
* https://github.com/bitcoin/bitcoin/blob/a6a860796a44a2805a58391a009ba22752f64e32/src/script/script.h#L220-L237
*
* @param constant
* @return
*/
@ -193,7 +219,6 @@ trait BitcoinScriptUtil {
* Whenever a script constant is interpreted to a number BIP62 could enforce that number to be encoded
* in the smallest encoding possible
* https://github.com/bitcoin/bitcoin/blob/a6a860796a44a2805a58391a009ba22752f64e32/src/script/script.h#L220-L237
*
* @param bytes
* @return
*/
@ -217,7 +242,6 @@ trait BitcoinScriptUtil {
* Whenever a script constant is interpreted to a number BIP62 could enforce that number to be encoded
* in the smallest encoding possible
* https://github.com/bitcoin/bitcoin/blob/a6a860796a44a2805a58391a009ba22752f64e32/src/script/script.h#L220-L237
*
* @param hex
* @return
*/
@ -225,7 +249,6 @@ trait BitcoinScriptUtil {
/**
* Checks the public key encoding according to bitcoin core's function
* https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L202
*
* @param key the key whose encoding we are checking
* @param program the program whose flags which dictate the rules for the public keys encoding
* @return if the key is encoded correctly against the rules give in the flags parameter
@ -235,7 +258,6 @@ trait BitcoinScriptUtil {
/**
* Checks the public key encoding according to bitcoin core's function
* https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L202
*
* @param key the key whose encoding we are checking
* @param flags the flags which dictate the rules for the public keys encoding
* @return if the key is encoded correctly against the rules givein the flags parameter
@ -249,7 +271,6 @@ trait BitcoinScriptUtil {
/**
* Returns true if the key is compressed or uncompressed, false otherwise
* https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L66
*
* @param key the public key that is being checked
* @return true if the key is compressed/uncompressed otherwise false
*/

View File

@ -0,0 +1,14 @@
package org.bitcoins.core.protocol.script
import org.scalacheck.{Prop, Properties}
/**
* Created by chris on 6/24/16.
*/
class P2SHScriptPubKeySpec extends Properties("P2SHScriptPubKeySpec") {
property("Symmetrical serialization") =
Prop.forAll(ScriptGenerators.p2shScriptPubKey) { p2shScriptPubKey =>
P2SHScriptPubKey(p2shScriptPubKey.hex) == p2shScriptPubKey
}
}

View File

@ -0,0 +1,16 @@
package org.bitcoins.core.protocol.script
import org.scalacheck.{Prop, Properties}
/**
* Created by chris on 6/24/16.
*/
class P2SHScriptSignatureSpec extends Properties("P2SHScriptSignatureSpec") {
property("Symmetrical serialization") =
Prop.forAll(ScriptGenerators.p2shScriptSignature) { p2shScriptSig =>
P2SHScriptSignature(p2shScriptSig.hex) == p2shScriptSig
}
}

View File

@ -1,15 +1,15 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.{ECPublicKey}
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, OP_0, ScriptConstant}
import org.bitcoins.core.util.TestUtil
import org.bitcoins.core.util.{BitcoinSLogger, TestUtil}
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 3/8/16.
*/
class P2SHScriptSignatureTest extends FlatSpec with MustMatchers {
class P2SHScriptSignatureTest extends FlatSpec with MustMatchers with BitcoinSLogger {
"P2SHScriptSignature" must "find the public keys embedded inside of the redeemScript" in {
val rawP2SHScriptSig = TestUtil.rawP2shInputScript2Of2
@ -37,4 +37,13 @@ class P2SHScriptSignatureTest extends FlatSpec with MustMatchers {
ScriptConstant("3045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c801")
))
}
it must "create a p2sh script signature out of a large multisignature script signature & multiSignature scriptPubKey" in {
val multiSigScriptSig = MultiSignatureScriptSignature("00473045022100867aa98fe0210eef1dff246de2ada03f4846ac9ec006ab24c1d868e6f08fb4b30220131acbe68c3302ed7bf82949810d6daf780703021cdcf481f5142f76fe31af04")
logger.info("==============================================================")
val multiSigScriptPubKey = MultiSignatureScriptPubKey("542103a1d052fcc371fc80d0d268169620ee125167335d9895f1639897c774deb80e1221038d83a6ed9f25ada86473f00200e31e11e7f5756c2a152e58f12ba078536d86a921031078d342d0ad318d865b83aa8eab7b747e92382e1f361f897f280560c984b426210255715a21757122db811b3b806ab2a3f5c4b04e229907c5a9497d30a7e6803441210244fe1d3ec95e0c3c9aa796686fffd9ba98ae778b80fc1038f171ced12d9b5f662102fcaf377e285e759bc0f0afc0d43b9f1f76cb8cd1279542e739261b9f577f5954210335e6efec824fdd539132a63696dd1dc719fbfc5efffa3d2576601c98eae6619821028af3bf77385560fc68b2932b66cae607052b0c53ae74c015fecf2d7ee9837e5f2102e1404987c0ee03803487923998f862700dcf9bdf4518d744a94fa208ff1f33d02103eaea267ef814f1ba57f43fe8f8dd8a1e8ab99d9d0e34e9d0a144e2a5e4c70efc21035e7bdaeba5d370bf5565937d3734c6e5bc0746d9d9bbf6846c7f3219a406a0da21026eb05e10608a3543b182a69d1dc651bdbab69e684be98260041a89aed1c4e1e62102e407986723ccdf2858c0d2116ed450d7b9ee6609d96603018c31470b065569eb2103f4ca93db51e4c602eda84fbb6dd285b66b12e53f1606a992bbc5bde0323ee64621022c83ca35993a8e18d76c386bb1d8e21c2575b2f675528e162b4cf81e7502745e5fae")
val p2shScriptSig = P2SHScriptSignature(multiSigScriptSig, multiSigScriptPubKey)
}
}