diff --git a/project/Build.scala b/project/Build.scala index d30293f6d3e..b33b29e15c1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,8 +1,8 @@ import sbt._ import Keys._ -object BitcoinSCoreBuild extends Build { +object BitcoinSidechainsBuild extends Build { - val appName = "bitcoin-s-core" + val appName = "bitcoin-s-sidechains" val appV = "0.0.1" val scalaV = "2.11.7" val organization = "org.bitcoins.core" diff --git a/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java b/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java index 1c67802fba8..6491d599dfb 100644 --- a/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java +++ b/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java @@ -21,7 +21,6 @@ import java.nio.ByteOrder; import java.math.BigInteger; -import com.google.common.base.Preconditions; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.bitcoin.NativeSecp256k1Util.*; @@ -29,7 +28,7 @@ /** *

This class holds native methods to handle ECDSA verification.

* - *

You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1

+ *

You can find an example library that can be used for this at https://github.com/bitcoin-core/secp256k1

* *

To build secp256k1 for use with bitcoinj, run * `./configure --enable-jni --enable-experimental --enable-module-ecdh` @@ -52,8 +51,8 @@ public class NativeSecp256k1 { * @param pub The public key which did the signing */ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{ - Preconditions.checkArgument(data.length == 32 && signature.length <= 520 && pub.length <= 520); - + checkInvariant(data.length == 32 && signature.length <= 520 && pub.length <= 520); + ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < 520) { byteBuff = ByteBuffer.allocateDirect(520); @@ -65,8 +64,6 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A byteBuff.put(signature); byteBuff.put(pub); - byte[][] retByteArray; - r.lock(); try { return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.length, pub.length) == 1; @@ -85,7 +82,7 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A * @param sig byte array of signature */ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ - Preconditions.checkArgument(data.length == 32 && sec.length <= 32); + checkInvariant(data.length == 32 && sec.length <= 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < 32 + 32) { @@ -121,7 +118,7 @@ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ * @param seckey ECDSA Secret key, 32 bytes */ public static boolean secKeyVerify(byte[] seckey) { - Preconditions.checkArgument(seckey.length == 32); + checkInvariant(seckey.length == 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < seckey.length) { @@ -149,9 +146,8 @@ public static boolean secKeyVerify(byte[] seckey) { * Return values * @param pubkey ECDSA Public key, 33 or 65 bytes */ - //TODO add a 'compressed' arg - public static byte[] computePubkey(byte[] seckey) throws AssertFailException{ - Preconditions.checkArgument(seckey.length == 32); + public static byte[] computePubkey(byte[] seckey, boolean fCompressed) throws AssertFailException{ + checkInvariant(seckey.length == 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < seckey.length) { @@ -166,7 +162,7 @@ public static byte[] computePubkey(byte[] seckey) throws AssertFailException{ r.lock(); try { - retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext()); + retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext(), fCompressed); } finally { r.unlock(); } @@ -207,7 +203,7 @@ public static long cloneContext() { * @param seckey 32-byte seckey */ public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(privkey.length == 32); + checkInvariant(privkey.length == 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { @@ -246,7 +242,7 @@ public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws Assert * @param seckey 32-byte seckey */ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(privkey.length == 32); + checkInvariant(privkey.length == 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { @@ -278,14 +274,43 @@ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws Assert return privArr; } + /** + * libsecp256k1 checks if a pubkey is valid + * [[https://github.com/bitcoin-core/secp256k1/blob/0f9e69db555ea35b90f49fa48925c366261452ec/src/secp256k1.c#L150]] + * @param pubkey + * @return + */ + public static boolean isValidPubKey(byte[] pubkey) { + if (!(pubkey.length == 33 || pubkey.length == 65)) { + return false; + } + final int expectedLen = pubkey.length; + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < pubkey.length) { + byteBuff = ByteBuffer.allocateDirect(pubkey.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(pubkey); + + r.lock(); + try { + return secp256k1_ec_pubkey_parse(byteBuff,Secp256k1Context.getContext(),expectedLen) == 1; + } finally { + r.unlock(); + } + } + + /** * libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it * * @param tweak some bytes to tweak with * @param pubkey 32-byte seckey */ - public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); + public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{ + checkInvariant((pubkey.length == 33 && fCompressed) || (pubkey.length == 65 && !fCompressed)); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { @@ -300,7 +325,7 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFa byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length); + retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); } finally { r.unlock(); } @@ -323,8 +348,8 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFa * @param tweak some bytes to tweak with * @param pubkey 32-byte seckey */ - public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); + public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{ + checkInvariant((pubkey.length == 33 && fCompressed) || (pubkey.length == 65 && !fCompressed)); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { @@ -339,7 +364,7 @@ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFa byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length); + retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); } finally { r.unlock(); } @@ -363,7 +388,7 @@ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFa * @param pubkey byte array of public key used in exponentiaion */ public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws AssertFailException{ - Preconditions.checkArgument(seckey.length <= 32 && pubkey.length <= 65); + checkInvariant(seckey.length <= 32 && pubkey.length <= 65); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < 32 + pubkey.length) { @@ -398,7 +423,7 @@ public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws Asser * @param seed 32-byte random seed */ public static synchronized boolean randomize(byte[] seed) throws AssertFailException{ - Preconditions.checkArgument(seed.length == 32 || seed == null); + checkInvariant(seed.length == 32 || seed == null); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < seed.length) { @@ -425,9 +450,9 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep private static native byte[][] secp256k1_privkey_tweak_mul(ByteBuffer byteBuff, long context); - private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen); + private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen, boolean fCompressed); - private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen); + private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen, boolean fCompressed); private static native void secp256k1_destroy_context(long context); @@ -437,10 +462,11 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep private static native int secp256k1_ec_seckey_verify(ByteBuffer byteBuff, long context); - private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context); + private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context, boolean fCompressed); - private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); + private static native int secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); } + diff --git a/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java b/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java index c00d08899b9..7adb6f8d53b 100644 --- a/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java +++ b/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java @@ -11,15 +11,16 @@ */ public class NativeSecp256k1Test { + private static final BaseEncoding hexEncoder = BaseEncoding.base16(); //TODO improve comments/add more tests /** * This tests verify() for a valid signature */ public static void testVerifyPos() throws AssertFailException{ boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sig = hexEncoder.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); result = NativeSecp256k1.verify( data, sig, pub); assertEquals( result, true , "testVerifyPos"); @@ -30,9 +31,9 @@ public static void testVerifyPos() throws AssertFailException{ */ public static void testVerifyNeg() throws AssertFailException{ boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91"); //sha256hash of "testing" + byte[] sig = hexEncoder.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); result = NativeSecp256k1.verify( data, sig, pub); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); @@ -44,7 +45,7 @@ public static void testVerifyNeg() throws AssertFailException{ */ public static void testSecKeyVerifyPos() throws AssertFailException{ boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); result = NativeSecp256k1.secKeyVerify( sec ); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); @@ -56,20 +57,41 @@ public static void testSecKeyVerifyPos() throws AssertFailException{ */ public static void testSecKeyVerifyNeg() throws AssertFailException{ boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); result = NativeSecp256k1.secKeyVerify( sec ); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); assertEquals( result, false , "testSecKeyVerifyNeg"); } + /** + * Tests that we can parse public keys + * @throws AssertFailException + */ + public static void testIsValidPubKeyPos() throws AssertFailException { + byte[] pubkey = hexEncoder.lowerCase().decode("0456b3817434935db42afda0165de529b938cf67c7510168a51b9297b1ca7e4d91ea59c64516373dd2fe6acc79bb762718bc2659fa68d343bdb12d5ef7b9ed002b"); + byte[] compressedPubKey = hexEncoder.lowerCase().decode("03de961a47a519c5c0fc8e744d1f657f9ea6b9a921d2a3bceb8743e1885f752676"); + + boolean result1 = NativeSecp256k1.isValidPubKey(pubkey); + boolean result2 = NativeSecp256k1.isValidPubKey(compressedPubKey); + + assertEquals(result1, true, "Uncompressed pubkey parsed failed"); + assertEquals(result2, true, "Compressed pubkey parsed failed"); + } + public static void testIsValidPubKeyNeg() throws AssertFailException { + //do we have test vectors some where to test this more thoroughly? + byte[] pubkey = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + boolean result1 = NativeSecp256k1.isValidPubKey(pubkey); + + assertEquals(result1, false, "Compressed pubkey parsed succeeded when it should have failed"); + } /** * This tests public key create() for a valid secretkey */ public static void testPubKeyCreatePos() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); - byte[] resultArr = NativeSecp256k1.computePubkey( sec); + byte[] resultArr = NativeSecp256k1.computePubkey( sec,false); String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); assertEquals( pubkeyString , "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6" , "testPubKeyCreatePos"); } @@ -78,9 +100,9 @@ public static void testPubKeyCreatePos() throws AssertFailException{ * This tests public key create() for a invalid secretkey */ public static void testPubKeyCreateNeg() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); - byte[] resultArr = NativeSecp256k1.computePubkey( sec); + byte[] resultArr = NativeSecp256k1.computePubkey( sec,false); String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); assertEquals( pubkeyString, "" , "testPubKeyCreateNeg"); } @@ -90,8 +112,8 @@ public static void testPubKeyCreateNeg() throws AssertFailException{ */ public static void testSignPos() throws AssertFailException{ - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); byte[] resultArr = NativeSecp256k1.sign(data, sec); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -102,8 +124,8 @@ public static void testSignPos() throws AssertFailException{ * This tests sign() for a invalid secretkey */ public static void testSignNeg() throws AssertFailException{ - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); byte[] resultArr = NativeSecp256k1.sign(data, sec); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -114,8 +136,8 @@ public static void testSignNeg() throws AssertFailException{ * This tests private key tweak-add */ public static void testPrivKeyTweakAdd_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.privKeyTweakAdd( sec , data ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -126,8 +148,8 @@ public static void testPrivKeyTweakAdd_1() throws AssertFailException { * This tests private key tweak-mul */ public static void testPrivKeyTweakMul_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.privKeyTweakMul( sec , data ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -138,10 +160,10 @@ public static void testPrivKeyTweakMul_1() throws AssertFailException { * This tests private key tweak-add uncompressed */ public static void testPrivKeyTweakAdd_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" - byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data ); + byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data, false ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); assertEquals( sigString , "0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF" , "testPrivKeyAdd_2"); } @@ -150,10 +172,10 @@ public static void testPrivKeyTweakAdd_2() throws AssertFailException { * This tests private key tweak-mul uncompressed */ public static void testPrivKeyTweakMul_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" - byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data ); + byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data, false ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); assertEquals( sigString , "04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589" , "testPrivKeyMul_2"); } @@ -162,15 +184,15 @@ public static void testPrivKeyTweakMul_2() throws AssertFailException { * This tests seed randomization */ public static void testRandomize() throws AssertFailException { - byte[] seed = BaseEncoding.base16().lowerCase().decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()); //sha256hash of "random" + byte[] seed = hexEncoder.decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11"); //sha256hash of "random" boolean result = NativeSecp256k1.randomize(seed); assertEquals( result, true, "testRandomize"); } public static void testCreateECDHSecret() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); byte[] resultArr = NativeSecp256k1.createECDHSecret(sec, pub); String ecdhString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -192,6 +214,10 @@ public static void main(String[] args) throws AssertFailException{ testSecKeyVerifyPos(); testSecKeyVerifyNeg(); + //Test parsing public keys + testIsValidPubKeyPos(); + testIsValidPubKeyNeg(); + //Test computePubkey() success/fail testPubKeyCreatePos(); testPubKeyCreateNeg(); diff --git a/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java b/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java index 04732ba0443..df8a3b166d9 100644 --- a/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java +++ b/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java @@ -37,6 +37,11 @@ public static void assertEquals( String val, String val2, String message ) throw System.out.println("PASS: " + message); } + public static void checkInvariant(boolean result) throws IllegalArgumentException { + if (!result) throw new IllegalArgumentException(); + } + + public static class AssertFailException extends Exception { public AssertFailException(String message) { super( message ); diff --git a/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c b/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c index a6d3725fbf3..7922c08a209 100644 --- a/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c +++ b/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c @@ -235,6 +235,20 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1p return retArray; } +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse + (JNIEnv * env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject); + + secp256k1_pubkey pubkey; + int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); + + (void)classObject; + + return ret; +} + SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen, jboolean fCompressed) { diff --git a/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h b/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h index 66d14aada55..27900039a16 100644 --- a/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h +++ b/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h @@ -101,7 +101,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e * Method: secp256k1_ec_pubkey_parse * Signature: (Ljava/nio/ByteBuffer;JI)[[B */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse (JNIEnv *, jclass, jobject, jlong, jint); /* diff --git a/src/main/java/org/bitcoin/NativeSecp256k1.java b/src/main/java/org/bitcoin/NativeSecp256k1.java index 72f248f0031..6491d599dfb 100644 --- a/src/main/java/org/bitcoin/NativeSecp256k1.java +++ b/src/main/java/org/bitcoin/NativeSecp256k1.java @@ -21,7 +21,6 @@ import java.nio.ByteOrder; import java.math.BigInteger; -import com.google.common.base.Preconditions; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.bitcoin.NativeSecp256k1Util.*; @@ -29,7 +28,7 @@ /** *

This class holds native methods to handle ECDSA verification.

* - *

You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1

+ *

You can find an example library that can be used for this at https://github.com/bitcoin-core/secp256k1

* *

To build secp256k1 for use with bitcoinj, run * `./configure --enable-jni --enable-experimental --enable-module-ecdh` @@ -53,7 +52,7 @@ public class NativeSecp256k1 { */ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{ checkInvariant(data.length == 32 && signature.length <= 520 && pub.length <= 520); - checkInvariant(Secp256k1Context.isEnabled()); + ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < 520) { byteBuff = ByteBuffer.allocateDirect(520); @@ -65,14 +64,11 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A byteBuff.put(signature); byteBuff.put(pub); - byte[][] retByteArray; - r.lock(); try { - return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), - signature.length, pub.length) == 1; + return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.length, pub.length) == 1; } finally { - r.unlock(); + r.unlock(); } } @@ -86,9 +82,7 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A * @param sig byte array of signature */ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ - checkInvariant(data != null); - checkInvariant(data.length == 32); - checkInvariant(sec.length <= 32); + checkInvariant(data.length == 32 && sec.length <= 32); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < 32 + 32) { @@ -104,9 +98,9 @@ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ r.lock(); try { - retByteArray = secp256k1_ecdsa_sign(byteBuff, Secp256k1Context.getContext()); + retByteArray = secp256k1_ecdsa_sign(byteBuff, Secp256k1Context.getContext()); } finally { - r.unlock(); + r.unlock(); } byte[] sigArr = retByteArray[0]; @@ -137,9 +131,9 @@ public static boolean secKeyVerify(byte[] seckey) { r.lock(); try { - return secp256k1_ec_seckey_verify(byteBuff,Secp256k1Context.getContext()) == 1; + return secp256k1_ec_seckey_verify(byteBuff,Secp256k1Context.getContext()) == 1; } finally { - r.unlock(); + r.unlock(); } } @@ -152,7 +146,6 @@ public static boolean secKeyVerify(byte[] seckey) { * Return values * @param pubkey ECDSA Public key, 33 or 65 bytes */ - //TODO add a 'compressed' arg public static byte[] computePubkey(byte[] seckey, boolean fCompressed) throws AssertFailException{ checkInvariant(seckey.length == 32); @@ -169,9 +162,9 @@ public static byte[] computePubkey(byte[] seckey, boolean fCompressed) throws As r.lock(); try { - retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext(), fCompressed); + retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext(), fCompressed); } finally { - r.unlock(); + r.unlock(); } byte[] pubArr = retByteArray[0]; @@ -190,17 +183,17 @@ public static byte[] computePubkey(byte[] seckey, boolean fCompressed) throws As public static synchronized void cleanup() { w.lock(); try { - secp256k1_destroy_context(Secp256k1Context.getContext()); + secp256k1_destroy_context(Secp256k1Context.getContext()); } finally { - w.unlock(); + w.unlock(); } } public static long cloneContext() { - r.lock(); - try { - return secp256k1_ctx_clone(Secp256k1Context.getContext()); - } finally { r.unlock(); } + r.lock(); + try { + return secp256k1_ctx_clone(Secp256k1Context.getContext()); + } finally { r.unlock(); } } /** @@ -225,9 +218,9 @@ public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws Assert byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_privkey_tweak_mul(byteBuff,Secp256k1Context.getContext()); + retByteArray = secp256k1_privkey_tweak_mul(byteBuff,Secp256k1Context.getContext()); } finally { - r.unlock(); + r.unlock(); } byte[] privArr = retByteArray[0]; @@ -264,9 +257,9 @@ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws Assert byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_privkey_tweak_add(byteBuff,Secp256k1Context.getContext()); + retByteArray = secp256k1_privkey_tweak_add(byteBuff,Secp256k1Context.getContext()); } finally { - r.unlock(); + r.unlock(); } byte[] privArr = retByteArray[0]; @@ -281,6 +274,35 @@ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws Assert return privArr; } + /** + * libsecp256k1 checks if a pubkey is valid + * [[https://github.com/bitcoin-core/secp256k1/blob/0f9e69db555ea35b90f49fa48925c366261452ec/src/secp256k1.c#L150]] + * @param pubkey + * @return + */ + public static boolean isValidPubKey(byte[] pubkey) { + if (!(pubkey.length == 33 || pubkey.length == 65)) { + return false; + } + final int expectedLen = pubkey.length; + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < pubkey.length) { + byteBuff = ByteBuffer.allocateDirect(pubkey.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(pubkey); + + r.lock(); + try { + return secp256k1_ec_pubkey_parse(byteBuff,Secp256k1Context.getContext(),expectedLen) == 1; + } finally { + r.unlock(); + } + } + + /** * libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it * @@ -288,7 +310,7 @@ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws Assert * @param pubkey 32-byte seckey */ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{ - checkInvariant(pubkey.length == 33 || pubkey.length == 65); + checkInvariant((pubkey.length == 33 && fCompressed) || (pubkey.length == 65 && !fCompressed)); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { @@ -303,9 +325,9 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean fCompre byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); + retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); } finally { - r.unlock(); + r.unlock(); } byte[] pubArr = retByteArray[0]; @@ -327,7 +349,7 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean fCompre * @param pubkey 32-byte seckey */ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak, boolean fCompressed) throws AssertFailException{ - checkInvariant(pubkey.length == 33 || pubkey.length == 65); + checkInvariant((pubkey.length == 33 && fCompressed) || (pubkey.length == 65 && !fCompressed)); ByteBuffer byteBuff = nativeECDSABuffer.get(); if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { @@ -342,9 +364,9 @@ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak, boolean fCompre byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); + retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length, fCompressed); } finally { - r.unlock(); + r.unlock(); } byte[] pubArr = retByteArray[0]; @@ -381,9 +403,9 @@ public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws Asser byte[][] retByteArray; r.lock(); try { - retByteArray = secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.length); + retByteArray = secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.length); } finally { - r.unlock(); + r.unlock(); } byte[] resArr = retByteArray[0]; @@ -414,9 +436,9 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep w.lock(); try { - return secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1; + return secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1; } finally { - w.unlock(); + w.unlock(); } } @@ -442,16 +464,9 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context, boolean fCompressed); - private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); + private static native int secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); - - private static void checkInvariant(boolean result) throws IllegalArgumentException { - if (!result) throw new IllegalArgumentException(); - } - } - - diff --git a/src/main/java/org/bitcoin/NativeSecp256k1Util.java b/src/main/java/org/bitcoin/NativeSecp256k1Util.java index b09015d9df0..df8a3b166d9 100644 --- a/src/main/java/org/bitcoin/NativeSecp256k1Util.java +++ b/src/main/java/org/bitcoin/NativeSecp256k1Util.java @@ -16,30 +16,35 @@ package org.bitcoin; -public class NativeSecp256k1Util { +public class NativeSecp256k1Util{ public static void assertEquals( int val, int val2, String message ) throws AssertFailException{ - if( val != val2 ) - throw new AssertFailException("FAIL: " + message); + if( val != val2 ) + throw new AssertFailException("FAIL: " + message); } public static void assertEquals( boolean val, boolean val2, String message ) throws AssertFailException{ - if( val != val2 ) - throw new AssertFailException("FAIL: " + message); - else - System.out.println("PASS: " + message); + if( val != val2 ) + throw new AssertFailException("FAIL: " + message); + else + System.out.println("PASS: " + message); } public static void assertEquals( String val, String val2, String message ) throws AssertFailException{ - if( !val.equals(val2) ) - throw new AssertFailException("FAIL: " + message); - else - System.out.println("PASS: " + message); + if( !val.equals(val2) ) + throw new AssertFailException("FAIL: " + message); + else + System.out.println("PASS: " + message); } + public static void checkInvariant(boolean result) throws IllegalArgumentException { + if (!result) throw new IllegalArgumentException(); + } + + public static class AssertFailException extends Exception { - public AssertFailException(String message) { - super( message ); - } + public AssertFailException(String message) { + super( message ); + } } -} \ No newline at end of file +} diff --git a/src/main/java/org/bitcoin/Secp256k1Context.java b/src/main/java/org/bitcoin/Secp256k1Context.java index 1cca1b683ce..216c986a8b5 100644 --- a/src/main/java/org/bitcoin/Secp256k1Context.java +++ b/src/main/java/org/bitcoin/Secp256k1Context.java @@ -21,31 +21,31 @@ * to handle ECDSA operations. */ public class Secp256k1Context { - private static final boolean enabled; //true if the library is loaded - private static final long context; //ref to pointer to context obj + private static final boolean enabled; //true if the library is loaded + private static final long context; //ref to pointer to context obj - static { //static initializer - boolean isEnabled = true; - long contextRef = -1; - try { - System.loadLibrary("secp256k1"); - contextRef = secp256k1_init_context(); - } catch (UnsatisfiedLinkError e) { - System.out.println("UnsatisfiedLinkError: " + e.toString()); - isEnabled = false; - } - enabled = isEnabled; - context = contextRef; - } + static { //static initializer + boolean isEnabled = true; + long contextRef = -1; + try { + System.loadLibrary("secp256k1"); + contextRef = secp256k1_init_context(); + } catch (UnsatisfiedLinkError e) { + System.out.println("UnsatisfiedLinkError: " + e.toString()); + isEnabled = false; + } + enabled = isEnabled; + context = contextRef; + } - public static boolean isEnabled() { - return enabled; - } + public static boolean isEnabled() { + return enabled; + } - public static long getContext() { - if(!enabled) return -1; //sanity check - return context; - } + public static long getContext() { + if(!enabled) return -1; //sanity check + return context; + } - private static native long secp256k1_init_context(); -} \ No newline at end of file + private static native long secp256k1_init_context(); +} diff --git a/src/main/scala/org/bitcoins/core/crypto/ECPrivateKey.scala b/src/main/scala/org/bitcoins/core/crypto/ECPrivateKey.scala index ca1f35b9035..a4eb8bea661 100644 --- a/src/main/scala/org/bitcoins/core/crypto/ECPrivateKey.scala +++ b/src/main/scala/org/bitcoins/core/crypto/ECPrivateKey.scala @@ -25,6 +25,7 @@ sealed trait ECPrivateKey extends BaseECKey { /** Derives the public for a the private key */ def publicKey : ECPublicKey = { val pubKeyBytes : Seq[Byte] = NativeSecp256k1.computePubkey(bytes.toArray, isCompressed) + require(NativeSecp256k1.isValidPubKey(pubKeyBytes.toArray), "secp256k1 failed to generate a valid public key, got: " + BitcoinSUtil.encodeHex(pubKeyBytes)) ECPublicKey(pubKeyBytes) } @@ -49,7 +50,7 @@ sealed trait ECPrivateKey extends BaseECKey { object ECPrivateKey extends Factory[ECPrivateKey] with BitcoinSLogger { private case class ECPrivateKeyImpl(bytes : Seq[Byte], isCompressed: Boolean) extends ECPrivateKey { - require(bytes.size == 32, "Keys are required to be 32 bytes in size as per bitcoin core, got: " + bytes.size + " hex: " + BitcoinSUtil.encodeHex(bytes)) + require(NativeSecp256k1.secKeyVerify(bytes.toArray), "Invalid key according to secp256k1, hex: " + BitcoinSUtil.encodeHex(bytes)) } override def fromBytes(bytes : Seq[Byte]) : ECPrivateKey = fromBytes(bytes,true) diff --git a/src/main/scala/org/bitcoins/core/crypto/ECPublicKey.scala b/src/main/scala/org/bitcoins/core/crypto/ECPublicKey.scala index 45610ef91e9..479eb97c349 100644 --- a/src/main/scala/org/bitcoins/core/crypto/ECPublicKey.scala +++ b/src/main/scala/org/bitcoins/core/crypto/ECPublicKey.scala @@ -60,11 +60,25 @@ sealed trait ECPublicKey extends BaseECKey with BitcoinSLogger { } resultTry.getOrElse(false) } + + /** Checks if the [[ECPublicKey]] is compressed */ + def isCompressed: Boolean = bytes.size == 33 + + /** Checks if the [[ECPublicKey]] is valid according to secp256k1 */ + def isFullyValid = ECPublicKey.isFullyValid(bytes) } object ECPublicKey extends Factory[ECPublicKey] { - private case class ECPublicKeyImpl(bytes : Seq[Byte]) extends ECPublicKey + private case class ECPublicKeyImpl(bytes : Seq[Byte]) extends ECPublicKey { + //unfortunately we cannot place ANY invariants here + //because of old transactions on the blockchain that have weirdly formatted public keys. Look at example in script_tests.json + //https://github.com/bitcoin/bitcoin/blob/master/src/test/data/script_tests.json#L457 + //bitcoin core only checks CPubKey::IsValid() + //this means we can have public keys with only one byte i.e. 0x00 or no bytes. + //Eventually we would like this to be CPubKey::IsFullyValid() but since we are remaining backwards compatible + //we cannot do this. If there ever is a hard fork this would be a good thing to add. + } override def fromBytes(bytes : Seq[Byte]) : ECPublicKey = ECPublicKeyImpl(bytes) @@ -73,6 +87,13 @@ object ECPublicKey extends Factory[ECPublicKey] { /** Generates a fresh [[ECPublicKey]] that has not been used before. */ def freshPublicKey = ECPrivateKey.freshPrivateKey.publicKey + + /** Checks if the public key is valid according to secp256k1 + * Mimics this function in bitcoin core + * [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.cpp#L207-L212]] + */ + def isFullyValid(bytes: Seq[Byte]): Boolean = Try(NativeSecp256k1.isValidPubKey(bytes.toArray)).isSuccess + } diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala index eb3ff6c9c03..7e53db60f98 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala @@ -59,6 +59,9 @@ trait TransactionSignatureChecker extends BitcoinSLogger { sigsRemovedScript, hashType) case w : WitnessV0TransactionSignatureComponent => TransactionSignatureSerializer.hashForSignature(w.transaction,w.inputIndex,sigsRemovedScript, hashType, w.amount,w.sigVersion) + case f : FedPegTransactionSignatureComponent => + val w = f.witnessTxSigComponent + TransactionSignatureSerializer.hashForSignature(w.transaction,w.inputIndex,sigsRemovedScript, hashType, w.amount,w.sigVersion) } logger.debug("Hash for signature: " + BitcoinSUtil.encodeHex(hashForSignature.bytes)) diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureComponent.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureComponent.scala index d1acedd982d..929884ece5f 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureComponent.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureComponent.scala @@ -1,11 +1,15 @@ package org.bitcoins.core.crypto +import org.bitcoins.core.crypto.WitnessV0TransactionSignatureComponent.WitnessV0TransactionSignatureComponentImpl import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction.{BaseTransaction, Transaction, TransactionOutput, WitnessTransaction} +import org.bitcoins.core.script.constant.ScriptToken import org.bitcoins.core.script.flag.ScriptFlag +import scala.annotation.tailrec + /** * Created by chris on 4/6/16. * Represents a transaction whose input is being checked against the spending conditions of the @@ -52,6 +56,32 @@ sealed trait WitnessV0TransactionSignatureComponent extends TransactionSignature } +sealed trait FedPegTransactionSignatureComponent extends TransactionSignatureComponent { + def witnessTxSigComponent: WitnessV0TransactionSignatureComponent + def fedPegScript: ScriptPubKey + + + override def flags = witnessTxSigComponent.flags + override def inputIndex = witnessTxSigComponent.inputIndex + override def scriptPubKey = witnessTxSigComponent.scriptPubKey + override def sigVersion = witnessTxSigComponent.sigVersion + override def transaction = witnessTxSigComponent.transaction + + /** Grabs the [[TransactionOutput]] that is offset from the given inputIndex */ + def getOutputOffSetFromCurrent(offset: Int): Option[TransactionOutput] = { + require(witnessTxSigComponent.inputIndex >= UInt32.zero && witnessTxSigComponent.transaction.outputs.size > 0) + val c = witnessTxSigComponent + val tx = witnessTxSigComponent.transaction + val inputIndex = c.inputIndex.toInt + val outputSize = c.transaction.outputs.size + if (inputIndex + offset < 0 || outputSize <= (inputIndex + offset)) { + None + } else { + Some(transaction.outputs(inputIndex + offset)) + } + } +} + object TransactionSignatureComponent { private case class BaseTransactionSignatureComponentImpl(transaction : Transaction, inputIndex : UInt32, @@ -81,6 +111,9 @@ object TransactionSignatureComponent { base.inputIndex,scriptPubKey, base.flags) case w: WitnessV0TransactionSignatureComponent => TransactionSignatureComponent(w.transaction,w.inputIndex,scriptPubKey,w.flags,w.amount,w.sigVersion) + case f : FedPegTransactionSignatureComponent => + FedPegTransactionSignatureComponent(f.transaction,f.inputIndex,scriptPubKey,f.flags, + f.witnessTxSigComponent.amount, f.witnessTxSigComponent.sigVersion, f.fedPegScript) } } @@ -100,4 +133,21 @@ object WitnessV0TransactionSignatureComponent { flags : Seq[ScriptFlag], sigVersion: SignatureVersion): WitnessV0TransactionSignatureComponent = { WitnessV0TransactionSignatureComponent(transaction,inputIndex,output.scriptPubKey,flags,output.value, sigVersion) } +} + +object FedPegTransactionSignatureComponent { + private case class FedPegTransactionSignatureComponentImpl(witnessTxSigComponent: WitnessV0TransactionSignatureComponent, + fedPegScript: ScriptPubKey) extends FedPegTransactionSignatureComponent + + + def apply(witnessTxSigComponent : WitnessV0TransactionSignatureComponent, + fedPegScript: ScriptPubKey): FedPegTransactionSignatureComponent = { + FedPegTransactionSignatureComponentImpl(witnessTxSigComponent,fedPegScript) + } + + def apply(transaction : Transaction, inputIndex : UInt32, scriptPubKey : ScriptPubKey, + flags : Seq[ScriptFlag], amount: CurrencyUnit, sigVersion: SignatureVersion, fedPegScript: ScriptPubKey) : FedPegTransactionSignatureComponent = { + val w = WitnessV0TransactionSignatureComponent(transaction,inputIndex,scriptPubKey,flags,amount,sigVersion) + FedPegTransactionSignatureComponentImpl(w,fedPegScript) + } } \ No newline at end of file diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala index 0ec24fc4a45..6748ca0822e 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala @@ -227,6 +227,9 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit hashForSignature(t.transaction,t.inputIndex,script,hashType) case w : WitnessV0TransactionSignatureComponent => hashForSignature(w.transaction,w.inputIndex, script, hashType,w.amount, w.sigVersion) + case f : FedPegTransactionSignatureComponent => + hashForSignature(f.transaction,f.inputIndex,script,hashType,f.witnessTxSigComponent.amount, + f.witnessTxSigComponent.sigVersion) } } diff --git a/src/main/scala/org/bitcoins/core/gen/BlockchainElementsGenerator.scala b/src/main/scala/org/bitcoins/core/gen/BlockchainElementsGenerator.scala index 4cf0ae0f295..fbd9176dd07 100644 --- a/src/main/scala/org/bitcoins/core/gen/BlockchainElementsGenerator.scala +++ b/src/main/scala/org/bitcoins/core/gen/BlockchainElementsGenerator.scala @@ -1,8 +1,10 @@ package org.bitcoins.core.gen +import org.bitcoins.core.consensus.Merkle import org.bitcoins.core.crypto.DoubleSha256Digest import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader} +import org.bitcoins.core.protocol.transaction.Transaction import org.scalacheck.Gen import scala.annotation.tailrec @@ -11,13 +13,19 @@ import scala.annotation.tailrec * Created by tom on 7/6/16. */ trait BlockchainElementsGenerator { + + /** Generates a block that contains the given txs, plus some more randomly generated ones */ + def block(txs: Seq[Transaction]): Gen[Block] = for { + randomNum <- Gen.choose(1,10) + neededTxs = if ((randomNum - txs.size) >= 0) randomNum else 0 + genTxs <- Gen.listOfN(neededTxs, TransactionGenerators.transaction) + allTxs = genTxs ++ txs + header <- blockHeader(allTxs) + } yield Block(header,allTxs) + /** Generates a random [[Block]], note that we limit this * to 10 transactions currently */ - def block : Gen[Block] = for { - header <- blockHeader - randomNum <- Gen.choose(1,10) - txs <- Gen.listOfN(randomNum, TransactionGenerators.transactions) - } yield Block(header, txs) + def block : Gen[Block] = block(Nil) /** Generates a random [[BlockHeader]] */ @@ -34,12 +42,26 @@ trait BlockchainElementsGenerator { /** Generates a random [[BlockHeader]] where you can specify the previousBlockHash and nBits */ def blockHeader(previousBlockHash: DoubleSha256Digest, nBits: UInt32): Gen[BlockHeader] = for { + numTxs <- Gen.choose(1,10) + txs <- Gen.listOfN(numTxs, TransactionGenerators.transaction) + header<- blockHeader(previousBlockHash,nBits,txs) + } yield header + + /** Generates a [[BlockHeader]]] that has the fields set to the given values */ + def blockHeader(previousBlockHash: DoubleSha256Digest, nBits: UInt32, txs: Seq[Transaction]): Gen[BlockHeader] = for { version <- NumberGenerator.uInt32s - merkleRootHash <- CryptoGenerators.doubleSha256Digest + merkleRootHash = Merkle.computeMerkleRoot(txs) time <- NumberGenerator.uInt32s nonce <- NumberGenerator.uInt32s } yield BlockHeader(version, previousBlockHash,merkleRootHash,time,nBits,nonce) + /** Generates a [[BlockHeader]] that has a merkle root hash corresponding to the given txs */ + def blockHeader(txs: Seq[Transaction]): Gen[BlockHeader] = for { + previousBlockHash <- CryptoGenerators.doubleSha256Digest + nBits <- NumberGenerator.uInt32s + header <- blockHeader(previousBlockHash,nBits,txs) + } yield header + /** Generates a chain of valid headers of the size specified by num, * 'valid' means their nBits are the same and each header properly * references the previous block header's hash */ diff --git a/src/main/scala/org/bitcoins/core/gen/MerkleGenerators.scala b/src/main/scala/org/bitcoins/core/gen/MerkleGenerators.scala index 12d327b1ae7..cefd4d47c62 100644 --- a/src/main/scala/org/bitcoins/core/gen/MerkleGenerators.scala +++ b/src/main/scala/org/bitcoins/core/gen/MerkleGenerators.scala @@ -3,6 +3,7 @@ package org.bitcoins.core.gen import org.bitcoins.core.bloom.BloomFilter import org.bitcoins.core.crypto.DoubleSha256Digest import org.bitcoins.core.protocol.blockchain.{Block, MerkleBlock, PartialMerkleTree} +import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.util.BitcoinSLogger import org.scalacheck.Gen @@ -11,20 +12,23 @@ import org.scalacheck.Gen */ trait MerkleGenerator extends BitcoinSLogger { - /** Returns a [[MerkleBlock]] including the sequence of hashes inserted in to the bloom filter */ - def merkleBlockWithInsertedTxIds: Gen[(MerkleBlock,Block,Seq[DoubleSha256Digest])] = for { - block <- BlockchainElementsGenerator.block - txIds <- Gen.someOf(block.transactions.map(_.txId)) + + /** Generates a merkle block with the given txs matched inside the [[PartialMerkleTree]] */ + def merkleBlockWithInsertedTxIds(txs: Seq[Transaction]): Gen[(MerkleBlock, Block, Seq[DoubleSha256Digest])] = for { + block <- BlockchainElementsGenerator.block(txs) + txIds = txs.map(_.txId) merkleBlock = MerkleBlock(block,txIds) } yield (merkleBlock, block, txIds) + /** Returns a [[MerkleBlock]] including the sequence of hashes inserted in to the bloom filter */ + def merkleBlockWithInsertedTxIds: Gen[(MerkleBlock,Block,Seq[DoubleSha256Digest])] = merkleBlockWithInsertedTxIds(Nil) + /** Returns a [[MerkleBlock]] created with a [[org.bitcoins.core.bloom.BloomFilter]], with the block it was created from * and the transactions that were matched inside of that block * NOTE: Since bloom filters can produce false positives, it is possible that there will be * matches in the parital merkle tree that SHOULD NOT be matched. Bloom filters do not guaratnee no * false negatives. - * @return */ def merkleBlockCreatedWithBloomFilter: Gen[(MerkleBlock, Block,Seq[DoubleSha256Digest], BloomFilter)] = for { block <- BlockchainElementsGenerator.block diff --git a/src/main/scala/org/bitcoins/core/gen/ScriptGenerators.scala b/src/main/scala/org/bitcoins/core/gen/ScriptGenerators.scala index f097b21d23c..451d0ac5d7e 100644 --- a/src/main/scala/org/bitcoins/core/gen/ScriptGenerators.scala +++ b/src/main/scala/org/bitcoins/core/gen/ScriptGenerators.scala @@ -1,15 +1,17 @@ package org.bitcoins.core.gen +import java.security.SecureRandom + import org.bitcoins.core.crypto.{TransactionSignatureCreator, _} import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits} import org.bitcoins.core.number.UInt32 import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script.{P2SHScriptPubKey, _} -import org.bitcoins.core.protocol.transaction.{TransactionConstants, TransactionWitness} +import org.bitcoins.core.protocol.transaction.{TransactionConstants, TransactionOutput, TransactionWitness} import org.bitcoins.core.script.ScriptSettings import org.bitcoins.core.script.constant.{OP_16, ScriptNumber} import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL} -import org.bitcoins.core.util.BitcoinSLogger +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil} import org.scalacheck.Gen /** @@ -70,6 +72,25 @@ trait ScriptGenerators extends BitcoinSLogger { sigs = privKeys.map(key => key.sign(hash)) } yield CSVScriptSignature(csv, sigs, pubKeys) + def contract(hash: Sha256Hash160Digest): Gen[Contract] = for { + prefix <- contractPrefix + c = Contract(prefix,hash) + } yield c + + /** Generator for a contract used within a [[WithdrawScriptSignature]] */ + def contract: Gen[Contract] = for { + hash <- CryptoGenerators.sha256Hash160Digest + c <- contract(hash) + } yield c + + def contractPrefix: Gen[ContractPrefix] = Gen.oneOf(Seq(P2PHContractPrefix, P2SHContractPrefix)) + + def withdrawScriptSignature: Gen[WithdrawScriptSignature] = for { + c <- contract + (merkleBlock,_,_) <- MerkleGenerator.merkleBlockWithInsertedTxIds + lockingTx <- TransactionGenerators.transaction + outputIndex <- Gen.choose(0,lockingTx.outputs.size) + } yield WithdrawScriptSignature(c,merkleBlock,lockingTx,UInt32(outputIndex)) def p2pkScriptPubKey : Gen[(P2PKScriptPubKey, ECPrivateKey)] = for { privKey <- CryptoGenerators.privateKey @@ -141,11 +162,16 @@ trait ScriptGenerators extends BitcoinSLogger { hash <- CryptoGenerators.doubleSha256Digest } yield (WitnessCommitment(hash),Nil) + def withdrawScriptPubKey: Gen[(WithdrawScriptPubKey, Seq[ECPrivateKey])] = for { + hash <- CryptoGenerators.doubleSha256Digest + } yield (WithdrawScriptPubKey(hash),Nil) + def pickRandomNonP2SHScriptPubKey: Gen[(ScriptPubKey, Seq[ECPrivateKey])] = { Gen.oneOf(p2pkScriptPubKey.map(privKeyToSeq(_)), p2pkhScriptPubKey.map(privKeyToSeq(_)), cltvScriptPubKey.suchThat(!_._1.scriptPubKeyAfterCLTV.isInstanceOf[CSVScriptPubKey]), csvScriptPubKey.suchThat(!_._1.scriptPubKeyAfterCSV.isInstanceOf[CLTVScriptPubKey]), - multiSigScriptPubKey, witnessScriptPubKeyV0, unassignedWitnessScriptPubKey + multiSigScriptPubKey, witnessScriptPubKeyV0, unassignedWitnessScriptPubKey/*, + withdrawScriptPubKey*/ ) } @@ -162,13 +188,13 @@ trait ScriptGenerators extends BitcoinSLogger { Gen.oneOf(p2pkScriptPubKey.map(privKeyToSeq(_)),p2pkhScriptPubKey.map(privKeyToSeq(_)), multiSigScriptPubKey,emptyScriptPubKey, cltvScriptPubKey,csvScriptPubKey,witnessScriptPubKeyV0,unassignedWitnessScriptPubKey, - p2shScriptPubKey, witnessCommitment) + p2shScriptPubKey, witnessCommitment/*, withdrawScriptPubKey*/) } /** Generates an arbitrary [[ScriptSignature]] */ def scriptSignature : Gen[ScriptSignature] = { Gen.oneOf(p2pkScriptSignature,p2pkhScriptSignature,multiSignatureScriptSignature, - emptyScriptSignature,p2shScriptSignature) + emptyScriptSignature,p2shScriptSignature/*, withdrawScriptSignature*/) } /** @@ -183,6 +209,7 @@ trait ScriptGenerators extends BitcoinSLogger { case cltv : CLTVScriptPubKey => cltvScriptSignature case csv : CSVScriptPubKey => csvScriptSignature case _ : WitnessScriptPubKeyV0 | _ : UnassignedWitnessScriptPubKey => emptyScriptSignature + case w: WithdrawScriptPubKey => withdrawlScript.map(_._1) case x @ (_: P2SHScriptPubKey | _: NonStandardScriptPubKey | _ : WitnessCommitment) => throw new IllegalArgumentException("Cannot pick for p2sh script pubkey, " + "non standard script pubkey or witness commitment got: " + x) @@ -298,7 +325,7 @@ trait ScriptGenerators extends BitcoinSLogger { case _: UnassignedWitnessScriptPubKey | _: WitnessScriptPubKeyV0 => throw new IllegalArgumentException("Cannot created a witness scriptPubKey for a CSVScriptSig since we do not have a witness") case _ : P2SHScriptPubKey | _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : NonStandardScriptPubKey - | _ : WitnessCommitment | EmptyScriptPubKey => throw new IllegalArgumentException("We only " + + | _ : WitnessCommitment | _ : WithdrawScriptPubKey | EmptyScriptPubKey => throw new IllegalArgumentException("We only " + "want to generate P2PK, P2PKH, and MultiSig ScriptSignatures when creating a CSVScriptSignature") } @@ -324,7 +351,7 @@ trait ScriptGenerators extends BitcoinSLogger { case _: UnassignedWitnessScriptPubKey | _: WitnessScriptPubKeyV0 => throw new IllegalArgumentException("Cannot created a witness scriptPubKey for a CSVScriptSig since we do not have a witness") case _ : P2SHScriptPubKey | _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : NonStandardScriptPubKey - | _ : WitnessCommitment | EmptyScriptPubKey => throw new IllegalArgumentException("We only " + + | _ : WitnessCommitment | _ : WithdrawScriptPubKey | EmptyScriptPubKey => throw new IllegalArgumentException("We only " + "want to generate P2PK, P2PKH, and MultiSig ScriptSignatures when creating a CLTVScriptSignature.") } @@ -423,6 +450,26 @@ trait ScriptGenerators extends BitcoinSLogger { val (s,key) = tuple (s,Seq(key)) } + + + /** Returns a valid [[WithdrawScriptSignature]], the [[WithdrawScriptPubKey]] it spends and + * the federated peg scriptPubkey */ + def withdrawlScript: Gen[(WithdrawScriptSignature, WithdrawScriptPubKey, ScriptPubKey, CurrencyUnit)] = for { + genesisBlockHash <- CryptoGenerators.doubleSha256Digest + userSidechainAddr <- CryptoGenerators.sha256Hash160Digest + reserveAmount <- CurrencyUnitGenerator.satoshis + amount <- CurrencyUnitGenerator.satoshis.suchThat(_ <= reserveAmount) + (sidechainCreditingTx,outputIndex) = TransactionGenerators.buildSidechainCreditingTx(genesisBlockHash, reserveAmount) + sidechainCreditingOutput = sidechainCreditingTx.outputs(outputIndex.toInt) + c <- contract(userSidechainAddr) + (fedPegScript,_) <- ScriptGenerators.multiSigScriptPubKey + lockingScriptPubKey = P2SHScriptPubKey(fedPegScript) + (lockTx,lockTxOutputIndex) = TransactionGenerators.buildCreditingTransaction(lockingScriptPubKey,amount) + (merkleBlock,_,_) <- MerkleGenerator.merkleBlockWithInsertedTxIds(Seq(lockTx)) + withdrawlScriptSig = WithdrawScriptSignature(c,merkleBlock,lockTx,outputIndex) + } yield (withdrawlScriptSig,sidechainCreditingOutput.scriptPubKey.asInstanceOf[WithdrawScriptPubKey],fedPegScript,reserveAmount) + + } object ScriptGenerators extends ScriptGenerators diff --git a/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala b/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala index 4fa060f5349..510aefa7012 100644 --- a/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala +++ b/src/main/scala/org/bitcoins/core/gen/TransactionGenerators.scala @@ -1,14 +1,15 @@ package org.bitcoins.core.gen -import org.bitcoins.core.crypto.{ECPrivateKey, TransactionSignatureComponent, WitnessV0TransactionSignatureComponent} -import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits} +import org.bitcoins.core.crypto._ +import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis} import org.bitcoins.core.number.{Int64, UInt32} import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction.{TransactionInput, TransactionOutPoint, TransactionOutput, _} -import org.bitcoins.core.script.constant.ScriptNumber +import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant, ScriptNumber, ScriptNumberOperation} +import org.bitcoins.core.script.crypto.OP_WITHDRAWPROOFVERIFY import org.bitcoins.core.script.interpreter.ScriptInterpreter -import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil, BitcoinScriptUtil} import org.scalacheck.Gen /** @@ -17,20 +18,20 @@ import org.scalacheck.Gen trait TransactionGenerators extends BitcoinSLogger { /** Responsible for generating [[org.bitcoins.core.protocol.transaction.TransactionOutPoint]] */ - def outPoints : Gen[TransactionOutPoint] = for { + def outPoint : Gen[TransactionOutPoint] = for { txId <- CryptoGenerators.doubleSha256Digest vout <- NumberGenerator.uInt32s } yield TransactionOutPoint(txId, vout) /** Generates a random [[org.bitcoins.core.protocol.transaction.TransactionOutput]] */ - def outputs : Gen[TransactionOutput] = for { + def output : Gen[TransactionOutput] = for { satoshis <- CurrencyUnitGenerator.satoshis (scriptPubKey, _) <- ScriptGenerators.scriptPubKey } yield TransactionOutput(satoshis, scriptPubKey) /** Generates a random [[org.bitcoins.core.protocol.transaction.TransactionInput]] */ - def inputs : Gen[TransactionInput] = for { - outPoint <- outPoints + def input : Gen[TransactionInput] = for { + outPoint <- outPoint scriptSig <- ScriptGenerators.scriptSignature sequenceNumber <- NumberGenerator.uInt32s randomNum <- Gen.choose(0,10) @@ -46,15 +47,15 @@ trait TransactionGenerators extends BitcoinSLogger { * This transaction's [[TransactionInput]]s will not evaluate to true * inside of the [[org.bitcoins.core.script.interpreter.ScriptInterpreter]] */ - def transactions : Gen[Transaction] = Gen.oneOf(baseTransaction,witnessTransaction) + def transaction : Gen[Transaction] = Gen.oneOf(baseTransaction,witnessTransaction) def baseTransaction: Gen[BaseTransaction] = for { version <- NumberGenerator.uInt32s randomInputNum <- Gen.choose(1,10) - inputs <- Gen.listOfN(randomInputNum, inputs) + inputs <- Gen.listOfN(randomInputNum, input) randomOutputNum <- Gen.choose(1,10) - outputs <- Gen.listOfN(randomOutputNum, outputs) + outputs <- Gen.listOfN(randomOutputNum, output) lockTime <- NumberGenerator.uInt32s } yield BaseTransaction(version, inputs, outputs, lockTime) @@ -62,9 +63,9 @@ trait TransactionGenerators extends BitcoinSLogger { def witnessTransaction: Gen[WitnessTransaction] = for { version <- NumberGenerator.uInt32s randomInputNum <- Gen.choose(1,10) - inputs <- Gen.listOfN(randomInputNum, inputs) + inputs <- Gen.listOfN(randomInputNum, input) randomOutputNum <- Gen.choose(1,10) - outputs <- Gen.listOfN(randomOutputNum, outputs) + outputs <- Gen.listOfN(randomOutputNum, output) lockTime <- NumberGenerator.uInt32s witness <- WitnessGenerators.transactionWitness(inputs.size) } yield WitnessTransaction(version,inputs,outputs,lockTime, witness) @@ -224,6 +225,34 @@ trait TransactionGenerators extends BitcoinSLogger { p2shScriptPubKey, Policy.standardScriptVerifyFlags, wtxSigComponent.amount, sigVersion) } yield (signedTxSignatureComponent,privKeys) + /** Generates a valid withdrawl transaction. + * This represents a transaction on blockchain B that is withdrawing money from blockchain A. + * A concrete example of this is a blockchain B being a sidechain, while blockchain A is the bitcoin blockchain + */ + def withdrawlTransaction: Gen[(FedPegTransactionSignatureComponent, Seq[ECPrivateKey])] = for { + genesisBlockHash <- CryptoGenerators.doubleSha256Digest + (scriptSig,scriptPubKey,fedPegScript,reserveAmount) <- ScriptGenerators.withdrawlScript + (sidechainCreditingTx,outputIndex) = buildSidechainCreditingTx(genesisBlockHash, reserveAmount) + sidechainCreditingOutput = sidechainCreditingTx.outputs(outputIndex.toInt) + contract = scriptSig.contract + sidechainUserOutput = TransactionOutput(scriptSig.withdrawlAmount,createUserSidechainScriptPubKey(contract)) + + n = ScriptNumberOperation.fromNumber(scriptSig.lockTxOutputIndex.toInt).getOrElse(ScriptNumber(scriptSig.lockTxOutputIndex.toInt)) + + federationChange = reserveAmount - scriptSig.withdrawlAmount + outputs = Seq(sidechainUserOutput, + TransactionOutput(federationChange, sidechainCreditingTx.outputs(outputIndex.toInt).scriptPubKey)) + sequence <- NumberGenerator.uInt32s + inputs = Seq(TransactionInput(TransactionOutPoint(sidechainCreditingTx.txId, outputIndex), scriptSig, sequence)) + inputIndex = UInt32.zero + version <- NumberGenerator.uInt32s + lockTime <- NumberGenerator.uInt32s + sidechainSpendingTx = Transaction(version,inputs,outputs,lockTime) + wtxSigComponent = WitnessV0TransactionSignatureComponent(sidechainSpendingTx,inputIndex,sidechainCreditingOutput, + Policy.standardScriptVerifyFlags, SigVersionWitnessV0) + fPegSigComponent = FedPegTransactionSignatureComponent(wtxSigComponent,fedPegScript) + } yield (fPegSigComponent,Nil) + /** * Builds a spending transaction according to bitcoin core * @return the built spending transaction and the input index for the script signature @@ -300,6 +329,15 @@ trait TransactionGenerators extends BitcoinSLogger { buildCreditingTransaction(version, TransactionOutput(amount,scriptPubKey)) } + /** Builds the crediting OP_WPV and the output index it is located at */ + def buildSidechainCreditingTx(genesisBlockHash: DoubleSha256Digest, reserveAmount: CurrencyUnit): (Transaction,UInt32) = { + val scriptPubKey = ScriptPubKey.fromAsm(Seq(BytesToPushOntoStack(32), + ScriptConstant(genesisBlockHash.bytes), OP_WITHDRAWPROOFVERIFY)) + val outputs = Seq(TransactionOutput(reserveAmount,scriptPubKey)) + val inputs = Seq(EmptyTransactionInput) + (Transaction(TransactionConstants.version,inputs,outputs,TransactionConstants.lockTime),UInt32.zero) + } + /** * Helper function to create validly constructed CLTVTransactions. @@ -378,6 +416,13 @@ trait TransactionGenerators extends BitcoinSLogger { sequence <- NumberGenerator.uInt32s csvScriptNum <- NumberGenerator.uInt32s.map(x => ScriptNumber(x.underlying)).suchThat(x => ScriptInterpreter.isLockTimeBitOff(x)) } yield (csvScriptNum, sequence)).suchThat(x => !csvLockTimesOfSameType(x)) + + + /** Creates the [[ScriptPubKey]] we are paying to on the sidechain */ + private def createUserSidechainScriptPubKey(contract: Contract): ScriptPubKey = contract.prefix match { + case P2PHContractPrefix => P2PKHScriptPubKey(contract.hash) + case P2SHContractPrefix => P2SHScriptPubKey(contract.hash) + } } object TransactionGenerators extends TransactionGenerators diff --git a/src/main/scala/org/bitcoins/core/policy/Policy.scala b/src/main/scala/org/bitcoins/core/policy/Policy.scala index 9e44c45e183..2c50d97b6da 100644 --- a/src/main/scala/org/bitcoins/core/policy/Policy.scala +++ b/src/main/scala/org/bitcoins/core/policy/Policy.scala @@ -26,7 +26,10 @@ trait Policy { ScriptVerifyMinimalData, ScriptVerifyNullDummy, ScriptVerifyDiscourageUpgradableNOPs, ScriptVerifyCleanStack, ScriptVerifyCheckLocktimeVerify, ScriptVerifyCheckSequenceVerify, ScriptVerifyLowS, ScriptVerifyWitness, ScriptVerifyMinimalIf, ScriptVerifyNullFail, - ScriptVerifyNullDummy, ScriptVerifyWitnessPubKeyType, ScriptVerifyDiscourageUpgradableWitnessProgram) + ScriptVerifyNullDummy, ScriptVerifyWitnessPubKeyType, ScriptVerifyDiscourageUpgradableWitnessProgram, + //sidechain script flags + ScriptVerifyWithdraw + ) } diff --git a/src/main/scala/org/bitcoins/core/protocol/script/Contract.scala b/src/main/scala/org/bitcoins/core/protocol/script/Contract.scala new file mode 100644 index 00000000000..c1cdce78b34 --- /dev/null +++ b/src/main/scala/org/bitcoins/core/protocol/script/Contract.scala @@ -0,0 +1,77 @@ +package org.bitcoins.core.protocol.script + +import java.security.SecureRandom +import java.util.Random + +import org.bitcoins.core.crypto.Sha256Hash160Digest +import org.bitcoins.core.protocol.NetworkElement +import org.bitcoins.core.util.{BitcoinSUtil, Factory} + +/** + * Created by chris on 3/15/17. + */ + +/** Pays to contract hash + * This data structure represents a contract inside of a [[WithdrawScriptSignature]] + * */ +sealed trait Contract extends NetworkElement { + /** Prefix is either [[P2SHContractPrefix]] or [[P2PHContractPrefix]] */ + def prefix: ContractPrefix = ContractPrefix(bytes.take(4)) + + /** 16 Bytes of randomness */ + def nonce: Seq[Byte] = bytes.slice(4,20) + + /** The hash we are paying to */ + def hash: Sha256Hash160Digest = Sha256Hash160Digest(bytes.takeRight(20)) + + override def hex = BitcoinSUtil.encodeHex(bytes) + + def bytes: Seq[Byte] + + override def toString = "Contract(" + hex + ")" +} + +object Contract extends Factory[Contract] { + private case class ContractImpl(override val bytes: Seq[Byte]) extends Contract { + require(bytes.length == 40, "Contract must be 40 bytes in size") + } + + def apply(prefix: ContractPrefix, nonce: Seq[Byte], hash: Sha256Hash160Digest): Contract = { + require(nonce.length == 16, "Nonce must be 16 bytes to create a contract") + val bytes: Seq[Byte] = prefix.bytes ++ nonce ++ hash.bytes + Contract(bytes) + } + + def apply(prefix: ContractPrefix, hash: Sha256Hash160Digest): Contract = { + val nonce = new Array[Byte](16) + scala.util.Random.nextBytes(nonce) + Contract(prefix,nonce,hash) + } + + override def fromBytes(bytes: Seq[Byte]): Contract = ContractImpl(bytes) +} + +sealed trait ContractPrefix extends NetworkElement { + val prefix: Seq[Byte] + override def bytes: Seq[Byte] = prefix + override def hex = BitcoinSUtil.encodeHex(bytes) +} + +object ContractPrefix { + def apply(bytes: Seq[Byte]): ContractPrefix = { + require(bytes.length == 4, "Contract prefix must be 4 bytes in size") + val p = prefixes.find(_.prefix == bytes) + require(p.isDefined) + p.get + } + + + def prefixes = Seq(P2PHContractPrefix,P2SHContractPrefix) +} + +case object P2SHContractPrefix extends ContractPrefix { + val prefix = Seq('P','2','S','H').map(_.toByte) +} +case object P2PHContractPrefix extends ContractPrefix { + val prefix = Seq('P', '2','P','H').map(_.toByte) +} diff --git a/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala b/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala index 97655d6923d..406fd0867d4 100644 --- a/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala +++ b/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala @@ -8,7 +8,7 @@ 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.crypto.{OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_HASH160} +import org.bitcoins.core.script.crypto._ import org.bitcoins.core.script.locktime.{OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY} import org.bitcoins.core.script.reserved.UndefinedOP_NOP import org.bitcoins.core.script.stack.{OP_DROP, OP_DUP} @@ -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 WithdrawScriptPubKey.isValidWithdrawScriptPubKey(asm) => WithdrawScriptPubKey.fromAsm(asm) case _ => NonStandardScriptPubKey(asm) } @@ -664,3 +665,48 @@ object WitnessCommitment extends ScriptFactory[WitnessCommitment] { } } } + +/** Represents the ScriptPubKey we spend from to get coins on our sidechain + * Always of the format + * [ OP_DROP] OP_WITHDRAWPROOFVERIFY + * */ +sealed trait WithdrawScriptPubKey extends ScriptPubKey { + def genesisHash: DoubleSha256Digest = DoubleSha256Digest(asm(1).bytes) +} + +object WithdrawScriptPubKey extends ScriptFactory[WithdrawScriptPubKey] { + private case class WithdrawScriptPubKeyImpl(hex: String) extends WithdrawScriptPubKey + + def apply(hash: DoubleSha256Digest): WithdrawScriptPubKey = { + val asm = Seq(BytesToPushOntoStack(32), ScriptConstant(hash.bytes), OP_WITHDRAWPROOFVERIFY) + fromAsm(asm) + } + + /** Checks if asm tokens are a valid withdraw script pubkey + * [[https://github.com/ElementsProject/elements/blob/a6c67028619da3d5f55fb620b9a83dc45c8f2a8e/src/script/script.cpp#L237]] + * */ + def isValidWithdrawScriptPubKey(asm: Seq[ScriptToken]): Boolean = { + if (asm.size == 6) { + asm.head.isInstanceOf[BytesToPushOntoStack] && + asm(1).bytes.size == 32 && + asm(2) == OP_DROP && + asm(3).bytes.size == 32 && + asm(4) == OP_WITHDRAWPROOFVERIFY + } else { + asm.size == 3 && + asm.head == BytesToPushOntoStack(32) && + asm(1).bytes.size == 32 && + asm(2) == OP_WITHDRAWPROOFVERIFY + } + } + + override def fromAsm(asm: Seq[ScriptToken]): WithdrawScriptPubKey = { + buildScript(asm,WithdrawScriptPubKeyImpl(_), isValidWithdrawScriptPubKey(_), + "Given asm was not a valid withdrawl scriptPubKey, got: " + asm) + } + + override def fromBytes(bytes: Seq[Byte]): WithdrawScriptPubKey = { + val asm = RawScriptPubKeyParser.read(bytes).asm + fromAsm(asm) + } +} \ No newline at end of file diff --git a/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index 54a691f29c0..1b2b6bac3ef 100644 --- a/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -1,9 +1,14 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.core.crypto.{ECDigitalSignature, ECPublicKey} +import org.bitcoins.core.crypto.{ECDigitalSignature, ECPublicKey, Sha256Hash160Digest} +import org.bitcoins.core.currency.CurrencyUnit +import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.protocol.blockchain.MerkleBlock +import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.protocol.{CompactSizeUInt, NetworkElement} import org.bitcoins.core.script.constant._ import org.bitcoins.core.serializers.script.{RawScriptSignatureParser, ScriptParser} +import org.bitcoins.core.util import org.bitcoins.core.util._ import scala.util.{Failure, Success, Try} @@ -107,7 +112,8 @@ object P2PKHScriptSignature extends ScriptFactory[P2PKHScriptSignature] { def isP2PKHScriptSig(asm: Seq[ScriptToken]): Boolean = asm match { case List(w : BytesToPushOntoStack, x : ScriptConstant, y : BytesToPushOntoStack, z : ScriptConstant) => - !P2SHScriptSignature.isRedeemScript(z) + if (ECPublicKey.isFullyValid(z.bytes)) true + else !P2SHScriptSignature.isRedeemScript(z) case _ => false } } @@ -223,6 +229,7 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] { case x : UnassignedWitnessScriptPubKey => true case x : NonStandardScriptPubKey => false case x : WitnessCommitment => false + case x : WithdrawScriptPubKey => false case EmptyScriptPubKey => false } case Failure(_) => false @@ -383,7 +390,7 @@ object CLTVScriptSignature extends Factory[CLTVScriptSignature] { case _: WitnessScriptPubKeyV0 | _ : UnassignedWitnessScriptPubKey => //bare segwit always has an empty script sig, see BIP141 CLTVScriptSignature(EmptyScriptSignature) - case x @ (_ : NonStandardScriptPubKey | _ : P2SHScriptPubKey | _ : WitnessCommitment) => + case x @ (_ : NonStandardScriptPubKey | _ : P2SHScriptPubKey | _ : WitnessCommitment | _ : WithdrawScriptPubKey) => throw new IllegalArgumentException("A NonStandardScriptSignature or P2SHScriptSignature or WitnessCommitment cannot be" + "the underlying scriptSig in a CLTVScriptSignature. Got: " + x) } @@ -428,7 +435,7 @@ object CSVScriptSignature extends Factory[CSVScriptSignature] { case _: WitnessScriptPubKeyV0 | _ : UnassignedWitnessScriptPubKey => //bare segwit always has an empty script sig, see BIP141 CSVScriptSignature(EmptyScriptSignature) - case x @ (_ : NonStandardScriptPubKey | _ : P2SHScriptPubKey | _: WitnessCommitment) => + case x @ (_ : NonStandardScriptPubKey | _ : P2SHScriptPubKey | _: WitnessCommitment | _: WithdrawScriptPubKey) => throw new IllegalArgumentException("A NonStandardScriptPubKey/P2SHScriptPubKey/WitnessCommitment cannot be" + "the underlying scriptSig in a CSVScriptSignature. Got: " + x) } @@ -458,6 +465,7 @@ object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger { MultiSignatureScriptSignature.fromAsm(tokens) case _ if P2PKHScriptSignature.isP2PKHScriptSig(tokens) => P2PKHScriptSignature.fromAsm(tokens) case _ if P2PKScriptSignature.isP2PKScriptSignature(tokens) => P2PKScriptSignature.fromAsm(tokens) + case _ if WithdrawScriptSignature.isValidWithdrawScriptSig(tokens) => WithdrawScriptSignature.fromAsm(tokens) case _ => NonStandardScriptSignature.fromAsm(tokens) } @@ -479,8 +487,98 @@ object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger { case _: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey => EmptyScriptSignature case EmptyScriptPubKey => if (tokens.isEmpty) EmptyScriptSignature else NonStandardScriptSignature.fromAsm(tokens) case _ : WitnessCommitment => throw new IllegalArgumentException("Cannot spend witness commitment scriptPubKey") + case s : WithdrawScriptPubKey => WithdrawScriptSignature.fromAsm(tokens) } def apply(tokens : Seq[ScriptToken], scriptPubKey : ScriptPubKey) : ScriptSignature = fromScriptPubKey(tokens, scriptPubKey) } +/** Withdraw signatures have the format + * + * */ +sealed trait WithdrawScriptSignature extends ScriptSignature { + override def signatures = Nil + + /** This is the contract the user used to lock up coins on the blockchain we are pegged to. + * It contains an address that we pay to on the sidechain if the OP_WPV script passes. */ + def contract: Contract = Contract(asm(1).bytes) + + /** The hash for which we need to pay to on the sidechain + * This is the address the user specified when paying to our + * p2sh address on the blockchain we are pegged to */ + def userHash: Sha256Hash160Digest = contract.hash + + /** [[MerkleBlock]] that proves the [[lockingTransaction]] is included in the block */ + def merkleBlock: MerkleBlock = MerkleBlock(asm(merkleBlockIndex).bytes) + + /** Index of merkle block constant in [[asm]] Sequence */ + private def merkleBlockIndex: Int = { + if (Seq(OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4).contains(asm(3))) 5 + else 4 + } + + /** The transaction that locks coins on the blockchain we are pegged to */ + def lockingTransaction: Transaction = { + val mbIndex = merkleBlockIndex + val bytes = if (Seq(OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4).contains(asm(mbIndex+1))) { + asm(mbIndex + 3).bytes + } else asm(mbIndex + 2).bytes + Transaction(bytes) + } + + /** The amount the user is withdrawing from the blockchain we are pegged to to our sidechain */ + def withdrawlAmount: CurrencyUnit = lockingTransaction.outputs(lockTxOutputIndex.toInt).value + + + def lockTxOutputIndex: UInt32 = asm.last match { + case scriptNumOp : ScriptNumberOperation => + UInt32(scriptNumOp.underlying) + case num: ScriptNumber => + //TODO: Investigate this further, can we technically have negative output indexes as script numbers can be negative? + UInt32(num.toLong) + case constant: ScriptConstant => + throw new IllegalArgumentException("Cannot have a constant represent lockTxOutputIndex, got:" + constant) + case scriptOp: ScriptOperation => + throw new IllegalArgumentException("Cannot have a script operation represent a lockTxOutputIndex, got: " + scriptOp) + } +} + +object WithdrawScriptSignature extends ScriptFactory[WithdrawScriptSignature] { + private case class WithdrawScriptSignatureImpl(hex: String) extends WithdrawScriptSignature + + def apply(contract: Contract, merkleBlock: MerkleBlock, lockingTx: Transaction, outputIndex: UInt32): WithdrawScriptSignature = { + + val num: ScriptNumber = ScriptNumberOperation.fromNumber(outputIndex.toInt).getOrElse(ScriptNumber(outputIndex.toInt)) + val asm: Seq[ScriptToken] = + BitcoinScriptUtil.calculatePushOp(contract.bytes) ++ + Seq(ScriptConstant(contract.bytes)) ++ + BitcoinScriptUtil.calculatePushOp(merkleBlock.bytes) ++ + Seq(ScriptConstant(merkleBlock.bytes)) ++ + BitcoinScriptUtil.calculatePushOp(lockingTx.bytes) ++ + Seq(ScriptConstant(lockingTx.bytes)) ++ + BitcoinScriptUtil.calculatePushOp(num) ++ + Seq(num) + val scriptSig = fromAsm(asm) + require(scriptSig.asm == asm, "passed in contract: " + contract + "\nexpected asm: " + asm + "\n got asm: " + scriptSig.asm) + scriptSig + } + override def fromBytes(bytes: Seq[Byte]): WithdrawScriptSignature = { + val asm = RawScriptSignatureParser.read(bytes).asm + fromAsm(asm) + } + + override def fromAsm(asm: Seq[ScriptToken]): WithdrawScriptSignature = { + buildScript(asm, WithdrawScriptSignatureImpl(_),isValidWithdrawScriptSig(_),"Expected valid withdraw scriptsig, got: " + asm) + } + + /** + * Checks if we have a valid [[WithdrawScriptSignature]] + * [[https://github.com/ElementsProject/elements/blob/a6c67028619da3d5f55fb620b9a83dc45c8f2a8e/src/script/script.cpp#L217]] + */ + def isValidWithdrawScriptSig(asm: Seq[ScriptToken]): Boolean = { + val isPushOnly = BitcoinScriptUtil.isPushOnly(asm) + isPushOnly + } + + +} diff --git a/src/main/scala/org/bitcoins/core/script/ScriptProgram.scala b/src/main/scala/org/bitcoins/core/script/ScriptProgram.scala index 5da76375a12..33af288845d 100644 --- a/src/main/scala/org/bitcoins/core/script/ScriptProgram.scala +++ b/src/main/scala/org/bitcoins/core/script/ScriptProgram.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.script -import org.bitcoins.core.crypto.{BaseTransactionSignatureComponent, TransactionSignatureComponent, WitnessV0TransactionSignatureComponent} +import org.bitcoins.core.crypto.{BaseTransactionSignatureComponent, FedPegTransactionSignatureComponent, TransactionSignatureComponent, WitnessV0TransactionSignatureComponent} import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ @@ -262,6 +262,9 @@ object ScriptProgram { case w: WitnessV0TransactionSignatureComponent => ScriptProgram(w.transaction, w.scriptPubKey, w.inputIndex, stack, script, w.flags, w.witness, w.amount) + case f : FedPegTransactionSignatureComponent => + ScriptProgram(f.transaction, f.scriptPubKey, f.inputIndex, stack, script, f.flags, + f.witnessTxSigComponent.witness, f.witnessTxSigComponent.amount) } def apply(txSignatureComponent: TransactionSignatureComponent, stack: Seq[ScriptToken], script: Seq[ScriptToken], @@ -271,6 +274,9 @@ object ScriptProgram { script.toList,originalScript.toList,Nil,b.flags,None) case w: WitnessV0TransactionSignatureComponent => ScriptProgram(w,stack,script,originalScript,Nil,w.flags,w.amount) + case f : FedPegTransactionSignatureComponent => + //TODO: + PreExecutionScriptProgramImpl(f,stack.toList,script.toList,originalScript.toList,Nil,f.flags) } @@ -281,6 +287,10 @@ object ScriptProgram { case w : WitnessV0TransactionSignatureComponent => ScriptProgram(w.transaction, w.scriptPubKey, w.inputIndex, w.flags, w.amount) + case f : FedPegTransactionSignatureComponent => + //TODO: Review this + PreExecutionScriptProgramImpl(f,Nil,f.scriptSignature.asm.toList,f.scriptSignature.asm.toList,Nil,f.flags) + //ScriptProgram(f.transaction,f.scriptPubKey, f.inputIndex, f.flags, f.witnessTxSigComponent.amount) } /** Creates a fresh [[PreExecutionScriptProgram]] */ @@ -300,6 +310,12 @@ object ScriptProgram { script,originalScript, altStack,flags, txSigComponent.sigVersion, amount) } + def apply(txSigComponent: FedPegTransactionSignatureComponent, stack: Seq[ScriptToken], script: Seq[ScriptToken], + originalScript: Seq[ScriptToken], altStack: Seq[ScriptToken], flags: Seq[ScriptFlag], amount: CurrencyUnit): PreExecutionScriptProgram = { + ScriptProgram(txSigComponent.transaction, txSigComponent.scriptPubKey, txSigComponent.inputIndex, stack, + script,originalScript, altStack,flags, txSigComponent.sigVersion, amount) + } + def apply(transaction: BaseTransaction, scriptPubKey: ScriptPubKey, inputIndex: UInt32, stack: Seq[ScriptToken], script: Seq[ScriptToken], originalScript: Seq[ScriptToken], altStack: Seq[ScriptToken], diff --git a/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala b/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala index fdb8ee10dae..0a757c9ad92 100644 --- a/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala +++ b/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala @@ -37,7 +37,6 @@ trait ConstantInterpreter extends BitcoinSLogger { bytesNeededForPushOp(program.script(1)) case _ : ScriptToken => bytesNeededForPushOp(program.script.head) } - /** Parses the script tokens that need to be pushed onto our stack. */ @tailrec def takeUntilBytesNeeded(scriptTokens : List[ScriptToken], accum : List[ScriptToken]) : (List[ScriptToken],List[ScriptToken]) = { @@ -56,8 +55,8 @@ trait ConstantInterpreter extends BitcoinSLogger { } val (newScript,bytesToPushOntoStack) = program.script.head match { - case OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => takeUntilBytesNeeded(program.script.tail.tail, List()) - case _: ScriptToken => takeUntilBytesNeeded(program.script.tail, List()) + case OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => takeUntilBytesNeeded(program.script.tail.tail, Nil) + case _: ScriptToken => takeUntilBytesNeeded(program.script.tail, Nil) } logger.debug("new script: " + newScript) logger.debug("Bytes to push onto stack: " + bytesToPushOntoStack) @@ -73,13 +72,16 @@ trait ConstantInterpreter extends BitcoinSLogger { logger.error("We can push this constant onto the stack with OP_0 - OP_16 instead of using a script constant") ScriptProgram(program,ScriptErrorMinimalData) } else if (bytesNeeded != bytesToPushOntoStack.map(_.bytes.size).sum) { + logger.error("Incorrect amount of bytes being pushed onto the stack") + logger.error("Bytes needed: " + bytesNeeded) + logger.error("Number of byte received: " + bytesToPushOntoStack.map(_.bytes.size).sum) ScriptProgram(program,ScriptErrorBadOpCode) } else if (ScriptFlagUtil.requireMinimalData(program.flags) && !BitcoinScriptUtil.isMinimalPush(program.script.head,constant)) { - logger.debug("Pushing operation: " + program.script.head) - logger.debug("Constant parsed: " + constant) - logger.debug("Constant size: " + constant.bytes.size) - ScriptProgram(program,ScriptErrorMinimalData) + logger.debug("Pushing operation: " + program.script.head) + logger.debug("Constant parsed: " + constant) + logger.debug("Constant size: " + constant.bytes.size) + ScriptProgram(program,ScriptErrorMinimalData) } else ScriptProgram.apply(program, constant :: program.stack, newScript) } @@ -90,13 +92,14 @@ trait ConstantInterpreter extends BitcoinSLogger { //constant telling OP_PUSHDATA how many bytes need to go onto the stack //for instance OP_PUSHDATA1 OP_0 val scriptNumOp = program.script(1).bytes match { - case h :: t => ScriptNumberOperation(h) + case h :: t => ScriptNumberOperation.fromNumber(h.toInt) case Nil => None } if (ScriptFlagUtil.requireMinimalData(program.flags) && program.script(1).bytes.size == 1 && scriptNumOp.isDefined) { logger.error("We cannot use an OP_PUSHDATA operation for pushing " + - "a script number operation onto the stack, scriptNumberOperation: " + scriptNumOp) + "a script number operation onto the stack, pushop: " + program.script.head + " scriptNumberOperation: " + scriptNumOp + + "\nrest of script: " + program.script) ScriptProgram(program,ScriptErrorMinimalData) } else if (ScriptFlagUtil.requireMinimalData(program.flags) && program.script.size > 2 && !BitcoinScriptUtil.isMinimalPush(program.script.head, program.script(2))) { @@ -117,6 +120,7 @@ trait ConstantInterpreter extends BitcoinSLogger { /** Parses the bytes needed for a push op (for instance OP_PUSHDATA1). */ private def bytesNeededForPushOp(token : ScriptToken) : Long = token match { case scriptNumber: BytesToPushOntoStack => scriptNumber.opCode + case scriptNumOp: ScriptNumberOperation => scriptNumOp.opCode case scriptNumber: ScriptNumber => scriptNumber.underlying case scriptConstant : ScriptConstant => val constantFlippedEndianness = BitcoinSUtil.flipEndianness(scriptConstant.hex) diff --git a/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala b/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala index b6c6e0a1c4b..a37858a422f 100644 --- a/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala +++ b/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala @@ -1,14 +1,19 @@ package org.bitcoins.core.script.crypto import org.bitcoins.core.crypto._ +import org.bitcoins.core.currency.CurrencyUnits +import org.bitcoins.core.protocol.blockchain.MerkleBlock +import org.bitcoins.core.protocol.script._ +import org.bitcoins.core.protocol.transaction.{EmptyTransactionOutput, Transaction, TransactionOutput} import org.bitcoins.core.script.{ScriptProgram, _} import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.control.{ControlOperationsInterpreter, OP_VERIFY} -import org.bitcoins.core.script.flag.ScriptFlagUtil +import org.bitcoins.core.script.flag.{ScriptFlagUtil, ScriptVerifyWithdraw} import org.bitcoins.core.script.result._ import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, CryptoUtil} import scala.annotation.tailrec +import scala.util.Try /** @@ -243,12 +248,209 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger * outputs with equal denomination on the parent chain * * Implementation in elements: - * https://github.com/ElementsProject/elements/blob/alpha/src/script/interpreter.cpp#L1308 + * [[https://github.com/ElementsProject/elements/blob/elements-0.13.1/src/script/interpreter.cpp#L1419]] + * * @param program * @return */ def opWithdrawProofVerify(program : ScriptProgram) : ScriptProgram = { - // comment from bitcoin core + require(program.script.headOption == Some(OP_WITHDRAWPROOFVERIFY), "Script operation is required to be OP_WITHDRAWPROOFVERIFY") + if (program.stack.size >= 7) { + return ScriptProgram(program,ScriptErrorInvalidStackOperation) + } + + val genesisHashToken = program.stack.head + + if (genesisHashToken.bytes.size != 32) { + return ScriptProgram(program,ScriptErrorWithdrawVerifyFormat) + } + + val genesisHash = DoubleSha256Digest(genesisHashToken.bytes) + + //we need the amount of the output, which is contained inside FedPegTransactionSignatureComponent + require(program.txSignatureComponent.isInstanceOf[FedPegTransactionSignatureComponent]) + val fPegTxSigComponent = program.txSignatureComponent.asInstanceOf[FedPegTransactionSignatureComponent] + + val relockScript: ScriptPubKey = WithdrawScriptPubKey(DoubleSha256Digest(genesisHashToken.bytes)) + + //regular withdraw from the sidechain + + val outputIndex: Int = program.stack(1) match { + case number : ScriptNumber => number.toInt + case err @ (_: ScriptConstant | _ : ScriptOperation) => + throw new IllegalArgumentException("We expected a ScriptNumber for output index in OP_WITHDRAWPROOFVERIFY, got: " + err) + } + + val lockTx: Transaction = program.stack(2) match { + case txConstant: ScriptConstant => Transaction(txConstant.bytes) + case scriptOp : ScriptOperation => + throw new IllegalArgumentException("We expect a ScriptConstant for lockTx in OP_WITHDRAWPROOFVERIFY, got: " + scriptOp) + } + + val merkleBlock: MerkleBlock = program.stack(3) match { + case merkleBlockConstant: ScriptConstant => MerkleBlock(merkleBlockConstant.bytes) + case scriptOp: ScriptOperation => + throw new IllegalArgumentException("We expect a ScriptConstant for a MerkleBlock in OP_WITHDRAWPROOFVERIFY, got: " + scriptOp) + } + + val contract: Try[Contract] = program.stack(4) match { + case constant: ScriptConstant => + Try(Contract(constant.bytes)) + case scriptOp: ScriptOperation => + throw new IllegalArgumentException("We expect a constant for our contract in OP_WITHDRAWPROOFVERIFY, got: " + scriptOp) + } + + val isValidPoW : Boolean = checkBitcoinProofOfWork(merkleBlock) + + if (!isValidPoW) { + logger.error("Invalid proof of work on the given block") + return ScriptProgram(program,ScriptErrorWithdrawVerifyBlock) + } + val blockHeader = merkleBlock.blockHeader + val partialMerkleTree = merkleBlock.partialMerkleTree + val matchedTxs: Seq[DoubleSha256Digest] = partialMerkleTree.extractMatches + + if (!partialMerkleTree.tree.value.contains(merkleBlock.blockHeader.merkleRootHash) || matchedTxs.length != 1) { + logger.error("Same root value: " + (!partialMerkleTree.tree.value.contains(merkleBlock.blockHeader.merkleRootHash))) + logger.error("Matched more than one tx: " + (matchedTxs.length != 1) + " matchedTxs length: " + matchedTxs.length) + logger.error("Incorrect partial merkle tree root hash or matched more than one tx in merkle tree") + return ScriptProgram(program,ScriptErrorWithdrawVerifyBlock) + } + + //We disallow returns from the genesis block, allowing sidechains to + //make genesis outputs spendable with a 21m initially-locked-to-btc + //distributing transactions + if (blockHeader.hash == genesisHash) { + logger.error("Incorrect genesis block hash") + return ScriptProgram(program, ScriptErrorWithdrawVerifyBlock) + } + +/* CTransaction locktx; + CDataStream locktxStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX); + locktxStream >> locktx; + if (!locktxStream.empty()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX);*/ + + if (outputIndex < 0 || outputIndex >= lockTx.outputs.length) { + logger.error("Incorrect output on the withdrawl locking tx, output index: " + outputIndex) + return ScriptProgram(program, ScriptErrorWithdrawVerifyLockTx) + } + + if (matchedTxs.head != lockTx.txId) { + logger.error("Incorrect withdrawl locking tx ") + return ScriptProgram(program, ScriptErrorWithdrawVerifyLockTx) + } + + if (contract.isFailure) { + logger.error("Incorrect withdrawl contract format") + return ScriptProgram(program, ScriptErrorWithdrawVerifyFormat) + } + + val scriptDestination: ScriptPubKey = fPegTxSigComponent.fedPegScript + + + /** + * This tool allows you to take a redeemScript as a template and, + * using basic EC math, replace public keys with ones which are only + * spendable by the original key's private key holder and which cryptographically + * commit to the contract hash specified. In this way, it provides a transparent and + * undetectable way of sending payments which commit to some data without adding extra data + * to the chain. It does, however, require some small amount of out-of-band communication. + * This implements the neccessary parts of appendix A of the sidechains whitepaper, + * though it is generally useful in many other cases. + * https://botbot.me/freenode/sidechains-dev/2017-03-07/?tz=America/Chicago + * */ +/* { + CScript::iterator sdpc = scriptDestination.begin(); + vector vch; + while (scriptDestination.GetOp(sdpc, opcodeTmp, vch)) + { + assert((vch.size() == 33 && opcodeTmp < OP_PUSHDATA4) || + (opcodeTmp <= OP_16 && opcodeTmp >= OP_1) || opcodeTmp == OP_CHECKMULTISIG); + if (vch.size() == 33) + { + unsigned char tweak[32]; + size_t pub_len = 33; + unsigned char *pub_start = &(*(sdpc - pub_len)); + CHMAC_SHA256(pub_start, pub_len).Write(&vcontract[0], 40).Finalize(tweak); + secp256k1_pubkey pubkey; + assert(secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey, pub_start, pub_len) == 1); + // If someone creates a tweak that makes this fail, they broke SHA256 + assert(secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &pubkey, tweak) == 1); + assert(secp256k1_ec_pubkey_serialize(secp256k1_ctx, pub_start, &pub_len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + assert(pub_len == 33); + } + } + }*/ + + val expectedP2SH = P2SHScriptPubKey(scriptDestination) + val lockedOutput = lockTx.outputs(outputIndex) + if (lockedOutput.scriptPubKey != expectedP2SH) { + logger.error("Incorrect withdrawl output script destination") + logger.error("Expected p2sh: " + expectedP2SH) + logger.error("Locked output scriptPubKey: " + lockedOutput.scriptPubKey) + return ScriptProgram(program, ScriptErrorWithdrawVerifyOutputScriptDest) + } + +/* + val contractWithoutNonce = contract.take(4) ++ contract.slice(20,contract.length) + + require(contractWithoutNonce.length == 24, "Contract must be 24 bytes in size after removing nonce") +*/ + + // We check values by doing the following: + + // * Tx must relock at least - + // * Tx must send at least the withdraw value to its P2SH withdraw, but may send more + + //not sure what this is + //assert(locktx.vout[nlocktxOut].nValue.IsAmount()); // Its a SERIALIZE_BITCOIN_BLOCK_OR_TX + val peginAmount = fPegTxSigComponent.witnessTxSigComponent.amount + val withdrawlAmount = lockedOutput.value + +/* if (!checker.GetValueIn().IsAmount()) // Heh, you just destroyed coins + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLINDED_AMOUNTS);*/ + val lockValueRequired = peginAmount - withdrawlAmount + + if (lockValueRequired > CurrencyUnits.zero) { + val newLockOutput: Option[TransactionOutput] = fPegTxSigComponent.getOutputOffSetFromCurrent(1) +/* if (!newLockOutput.nValue.IsAmount()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLINDED_AMOUNTS);*/ + if (newLockOutput.isEmpty || newLockOutput.get.scriptPubKey != relockScript || + newLockOutput.get.value < lockValueRequired) { + logger.error("Incorrect withdrawl relock script, got: " + newLockOutput) + logger.error("Expected relock script: " + relockScript) + logger.error("Lock valued required: " + lockValueRequired) + return ScriptProgram(program, ScriptErrorWithdrawVerifyRelockScriptVal) + } + } + + val withdrawOutput: TransactionOutput = fPegTxSigComponent.getOutputOffSetFromCurrent(0).get + +/* if (!withdrawOutput.nValue.IsAmount()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLINDED_AMOUNTS);*/ + + if (withdrawOutput.value < withdrawlAmount) { + logger.error("Incorrect withdrawl amount") + return ScriptProgram(program, ScriptErrorWithdrawVerifyOutputVal) + } + + val expectedWithdrawScriptPubKey: ScriptPubKey = parseWithdrawScriptPubKey(contract.get) + + if (expectedWithdrawScriptPubKey != withdrawOutput.scriptPubKey) { + logger.error("Incorrect withdrawl scriptPubKey") + logger.error("Expected withdraw scriptPubKey: " + expectedWithdrawScriptPubKey) + logger.error("Actual withdraw scriptPubKey: " + withdrawOutput.scriptPubKey) + return ScriptProgram(program,ScriptErrorWithdrawVerifyOutputScript) + } + +/* + #ifndef BITCOIN_SCRIPT_NO_CALLRPC + if (GetBoolArg("-validatepegin", false) && !checker.IsConfirmedBitcoinBlock(genesishash, merkleBlock.header.GetHash(), flags & SCRIPT_VERIFY_INCREASE_CONFIRMATIONS_REQUIRED)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCKCONFIRMED); + #endif +*/ + // comment from elements project // In the make-withdraw case, reads the following from the stack: // 1. genesis block hash of the chain the withdraw is coming from // 2. the index within the locking tx's outputs we are claiming @@ -259,8 +461,10 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger // // In the combine-outputs case, reads the following from the stack: // 1. genesis block hash of the chain the withdraw is coming from - ??? + val newScript = program.script.tail + + ScriptProgram(program,newScript, ScriptProgram.Script) } @@ -278,17 +482,22 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger * * Implementation in elements: * https://github.com/ElementsProject/elements/blob/alpha/src/script/interpreter.cpp#L1584 + * * @param program * @return */ - def opReorgProofVerify(program : ScriptProgram) : ScriptProgram = ??? + def opReorgProofVerify(program : ScriptProgram) : ScriptProgram = { + //TODO: Implement this later + ScriptProgram(program,program.script.tail, ScriptProgram.Script) + } /** * This is a higher order function designed to execute a hash function on the stack top of the program * For instance, we could pass in CryptoUtil.sha256 function as the 'hashFunction' argument, which would then * apply sha256 to the stack top - * @param program the script program whose stack top needs to be hashed + * + * @param program the script program whose stack top needs to be hashed * @param hashFunction the hash function which needs to be used on the stack top (sha256,ripemd160,etc..) * @return */ @@ -335,4 +544,24 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger case SignatureValidationErrorNullFail => ScriptProgram(program,ScriptErrorSigNullFail) } + + /** Checks the given [[MerkleBlock]] to see if we have enough proof of work + * [[https://github.com/ElementsProject/elements/blob/edaaa8b0f92653d9f770e671c7493f4bab4b48c7/src/pow.cpp#L40]] + * */ + private def checkBitcoinProofOfWork(merkleBlock: MerkleBlock): Boolean = { + true + } + + /** Parses the withdraw script pubkey from the given contract + * [[https://github.com/ElementsProject/elements/blob/6a3b75d257eeb9b4729e658821d3999430a5d5be/src/script/interpreter.cpp#L1567-L1573]] + */ + private def parseWithdrawScriptPubKey(contract: Contract): ScriptPubKey = contract.prefix match { + case P2PHContractPrefix => + val hash = contract.hash + P2PKHScriptPubKey(hash) + case P2SHContractPrefix => + val hash = contract.hash + P2SHScriptPubKey(hash) + } + } diff --git a/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala b/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala index de5baacf11f..d707224cb99 100644 --- a/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala +++ b/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala @@ -77,9 +77,13 @@ case object OP_CHECKMULTISIGVERIFY extends CryptoSignatureEvaluation { case object OP_WITHDRAWPROOFVERIFY extends CryptoOperation { override def opCode = 179 + /** Currently overriden to OP_NOP4 so we can read it correctly for tests in script_tests.json */ + override def toString = "OP_NOP4" } case object OP_REORGPROOFVERIFY extends CryptoOperation { override def opCode = 180 + /** Currently overriden to OP_NOP5 so we can read it correctly for tests in script_tests.json */ + override def toString = "OP_NOP5" } object CryptoOperation extends ScriptOperationFactory[CryptoOperation] { diff --git a/src/main/scala/org/bitcoins/core/script/flag/ScriptFlags.scala b/src/main/scala/org/bitcoins/core/script/flag/ScriptFlags.scala index ed40515308c..a7138d91d9c 100644 --- a/src/main/scala/org/bitcoins/core/script/flag/ScriptFlags.scala +++ b/src/main/scala/org/bitcoins/core/script/flag/ScriptFlags.scala @@ -132,4 +132,16 @@ case object ScriptVerifyWitnessPubKeyType extends ScriptFlag { override def name = "WITNESS_PUBKEYTYPE" } +// Execute sidechain-related opcodes instead of treating them as NOPs +case object ScriptVerifyWithdraw extends ScriptFlag { + override def flag = 1 << 16 + override def name = "VERIFY_WITHDRAW" +} + +// Dirty hack to require a higher bar of bitcoin block confirmation in mempool +case object ScriptVerifyIncreaseConfirmationsRequired extends ScriptFlag { + override def flag = 1 << 17 + override def name = "INCREASE_CONFIRMATIONS_REQUIRED" +} + diff --git a/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala b/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala index 1fb4bcfd133..59232fd9dba 100644 --- a/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala +++ b/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.script.interpreter import org.bitcoins.core.consensus.Consensus -import org.bitcoins.core.crypto.{BaseTransactionSignatureComponent, WitnessV0TransactionSignatureComponent} +import org.bitcoins.core.crypto.{BaseTransactionSignatureComponent, FedPegTransactionSignatureComponent, WitnessV0TransactionSignatureComponent} import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.script._ @@ -75,25 +75,26 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con if (p2shEnabled) executeP2shScript(scriptSigExecutedProgram, program, p2sh) else scriptPubKeyExecutedProgram case _ : P2PKHScriptPubKey | _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: CSVScriptPubKey | - _ : CLTVScriptPubKey | _ : NonStandardScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey => + _ : CLTVScriptPubKey | _ : NonStandardScriptPubKey | _ : WitnessCommitment | _: WithdrawScriptPubKey | EmptyScriptPubKey => scriptPubKeyExecutedProgram } } } logger.debug("Executed Script Program: " + executedProgram) - if (executedProgram.error.isDefined) executedProgram.error.get + val result = if (executedProgram.error.isDefined) executedProgram.error.get else if (hasUnexpectedWitness(program)) { //note: the 'program' value we pass above is intentional, we need to check the original program //as the 'executedProgram' may have had the scriptPubKey value changed to the rebuilt ScriptPubKey of the witness program - ScriptErrorWitnessUnexpected } else if (executedProgram.stackTopIsTrue && flags.contains(ScriptVerifyCleanStack)) { //require that the stack after execution has exactly one element on it - if (executedProgram.stack.size == 1) ScriptOk + //unless we have the ScriptVerifyWithdraw flag -- this is because we need it to be soft fork compatible + if (executedProgram.stack.size == 1 || flags.contains(ScriptVerifyWithdraw)) ScriptOk else ScriptErrorCleanStack } else if (executedProgram.stackTopIsTrue) ScriptOk else ScriptErrorEvalFalse + result } /** @@ -158,8 +159,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 | _: WithdrawScriptPubKey | EmptyScriptPubKey) => logger.debug("redeemScript: " + s.asm) run(scriptPubKeyExecutedProgram,stack,s) } @@ -191,6 +193,18 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con if (scriptSig != EmptyScriptSignature && !w.scriptPubKey.isInstanceOf[P2SHScriptPubKey]) ScriptProgram(scriptPubKeyExecutedProgram,ScriptErrorWitnessMalleated) else if (witness.stack.exists(_.size > maxPushSize)) ScriptProgram(scriptPubKeyExecutedProgram, ScriptErrorPushSize) else verifyWitnessProgram(witnessVersion, witness, witnessProgram, w) + + case f: FedPegTransactionSignatureComponent => + val scriptSig = scriptPubKeyExecutedProgram.txSignatureComponent.scriptSignature + val (witnessVersion,witnessProgram) = (witnessScriptPubKey.witnessVersion, witnessScriptPubKey.witnessProgram) + val witness = f.witnessTxSigComponent.witness + + //scriptsig must be empty if we have raw p2wsh + //if script pubkey is a P2SHScriptPubKey then we have P2SH(P2WSH) + if (scriptSig != EmptyScriptSignature && !f.scriptPubKey.isInstanceOf[P2SHScriptPubKey]) ScriptProgram(scriptPubKeyExecutedProgram,ScriptErrorWitnessMalleated) + else if (witness.stack.exists(_.size > maxPushSize)) ScriptProgram(scriptPubKeyExecutedProgram, ScriptErrorPushSize) + else verifyWitnessProgram(witnessVersion, witness, witnessProgram, f.witnessTxSigComponent) + } } @@ -397,9 +411,24 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con val newOpCount = calcOpCount(opCount,OP_CHECKMULTISIGVERIFY) + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt loop(newProgram,newOpCount) } - case OP_WITHDRAWPROOFVERIFY :: t => loop(opWithdrawProofVerify(p), calcOpCount(opCount, OP_WITHDRAWPROOFVERIFY)) + case OP_WITHDRAWPROOFVERIFY :: t => + //check if OP_WPV is enforced yet + if (program.flags.contains(ScriptVerifyWithdraw)) { + loop(opWithdrawProofVerify(p),calcOpCount(opCount,OP_WITHDRAWPROOFVERIFY)) + } + //if not, check to see if we should discourage p + else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) { + logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set") + loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs),calcOpCount(opCount,OP_WITHDRAWPROOFVERIFY)) + } + //in this case, just reat OP_CLTV just like a NOP and remove it from the stack + else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script), calcOpCount(opCount, OP_WITHDRAWPROOFVERIFY)) - case OP_REORGPROOFVERIFY :: t => loop(opReorgProofVerify(p),calcOpCount(opCount, OP_REORGPROOFVERIFY)) + case OP_REORGPROOFVERIFY :: t => + if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) { + logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set") + loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs),calcOpCount(opCount,OP_REORGPROOFVERIFY)) + } else loop(opReorgProofVerify(p),calcOpCount(opCount, OP_REORGPROOFVERIFY)) //reserved operations case OP_NOP :: t => //script discourage upgradeable flag does not apply to a OP_NOP @@ -499,7 +528,21 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con * Return true if witness was NOT used, return false if witness was used. */ private def hasUnexpectedWitness(program: ScriptProgram): Boolean = { val txSigComponent = program.txSignatureComponent - logger.debug("TxSigComponent: " + txSigComponent) + /** Helper function to check if the witness was used */ + def witnessUsed(w: WitnessV0TransactionSignatureComponent) : Boolean = { + val witnessedUsed = w.scriptPubKey match { + case _ : WitnessScriptPubKey => true + case _ : P2SHScriptPubKey => + val p2shScriptSig = P2SHScriptSignature(txSigComponent.scriptSignature.bytes) + p2shScriptSig.redeemScript.isInstanceOf[WitnessScriptPubKey] + case _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : MultiSignatureScriptPubKey | _ : NonStandardScriptPubKey | + _ : P2PKScriptPubKey | _ : P2PKHScriptPubKey | _ : WitnessCommitment | + _: WithdrawScriptPubKey | EmptyScriptPubKey => + w.witness.stack.isEmpty + } + witnessedUsed + } + val unexpectedWitness = txSigComponent match { case b : BaseTransactionSignatureComponent => b.transaction match { @@ -508,16 +551,9 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con case _ : BaseTransaction => false } case w : WitnessV0TransactionSignatureComponent => - val witnessedUsed = w.scriptPubKey match { - case _ : WitnessScriptPubKey => true - case _ : P2SHScriptPubKey => - val p2shScriptSig = P2SHScriptSignature(txSigComponent.scriptSignature.bytes) - p2shScriptSig.redeemScript.isInstanceOf[WitnessScriptPubKey] - case _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : MultiSignatureScriptPubKey | _ : NonStandardScriptPubKey | - _ : P2PKScriptPubKey | _ : P2PKHScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey => - w.witness.stack.isEmpty - } - !witnessedUsed + !witnessUsed(w) + case f : FedPegTransactionSignatureComponent => + !witnessUsed(f.witnessTxSigComponent) } if (unexpectedWitness) logger.error("Found unexpected witness that was not used by the ScriptProgram: " + program) diff --git a/src/main/scala/org/bitcoins/core/script/result/ScriptResult.scala b/src/main/scala/org/bitcoins/core/script/result/ScriptResult.scala index a692b097111..45763859f0e 100644 --- a/src/main/scala/org/bitcoins/core/script/result/ScriptResult.scala +++ b/src/main/scala/org/bitcoins/core/script/result/ScriptResult.scala @@ -237,6 +237,52 @@ case object ScriptErrorWitnessPubKeyType extends ScriptError { override def description = "WITNESS_PUBKEYTYPE" } +//SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT, +case object ScriptErrorWithdrawVerifyFormat extends ScriptError { + override def description = "WITHDRAW_VERIFY_FORMAT" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK, +case object ScriptErrorWithdrawVerifyBlock extends ScriptError { + override def description = "WITHDRAW_VERIFY_BLOCK" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX, +case object ScriptErrorWithdrawVerifyLockTx extends ScriptError { + override def description = "WITHDRAW_VERIFY_LOCKTX" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT, +case object ScriptErrorWithdrawVerifyOutput extends ScriptError { + override def description = "WITHDRAW_VERIFY_OUTPUT" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT_SCRIPTDEST, +case object ScriptErrorWithdrawVerifyOutputScriptDest extends ScriptError { + override def description = "WITHDRAW_VERIFY_OUTPUT_SCRIPTDEST" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_RELOCK_SCRIPTVAL, +case object ScriptErrorWithdrawVerifyRelockScriptVal extends ScriptError { + override def description = "WITHDRAW_VERIFY_RELOCK_SCRIPTVAL" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT_VAL, +case object ScriptErrorWithdrawVerifyOutputVal extends ScriptError { + override def description = "WITHDRAW_VERIFY_OUTPUT_VAL" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT_SCRIPT, +case object ScriptErrorWithdrawVerifyOutputScript extends ScriptError { + override def description = "WITHDRAW_VERIFY_OUTPUT_SCRIPT" +} + +//SCRIPT_ERR_WITHDRAW_VERIFY_BLOCKCONFIRMED, +case object ScriptErrorWithdrawVerifyBlockConfirmed extends ScriptError { + override def description = "WITHDRAW_VERIFY_BLOCKCONFIRMED" +} +//SCRIPT_ERR_WITHDRAW_VERIFY_BLINDED_AMOUNTS, +case object ScriptErrorWithdrawVerifyBlindedAmounts extends ScriptError { + override def description = "WITHDRAW_VERIFY_BLINDED_AMOUNTS" +} + +//SCRIPT_ERR_ERROR_COUNT +case object ScriptErrorErrorCount extends ScriptError { + override def description = "ERROR_COUNT" +} /** diff --git a/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala b/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala index 8fd8434bb1c..48860fe4c35 100644 --- a/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala +++ b/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala @@ -157,7 +157,8 @@ trait BitcoinScriptUtil extends BitcoinSLogger { //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)) + if (scriptToken.isInstanceOf[ScriptNumberOperation]) Nil + else if (scriptTokenSize <= UInt32(75)) Seq(BytesToPushOntoStack(scriptToken.bytes.size)) else if (scriptTokenSize <= UInt32(OP_PUSHDATA1.max)) { //we need the push op to be only 1 byte in size val pushConstant = ScriptConstant(BitcoinSUtil.flipEndianness(bytes.slice(bytes.length-1,bytes.length))) @@ -288,7 +289,8 @@ trait BitcoinScriptUtil extends BitcoinSLogger { } } - def calculateScriptForSigning(txSignatureComponent: TransactionSignatureComponent, script: Seq[ScriptToken]): Seq[ScriptToken] = txSignatureComponent.scriptPubKey match { + @tailrec + final def calculateScriptForSigning(txSignatureComponent: TransactionSignatureComponent, script: Seq[ScriptToken]): Seq[ScriptToken] = txSignatureComponent.scriptPubKey match { case p2shScriptPubKey: P2SHScriptPubKey => val p2shScriptSig = P2SHScriptSignature(txSignatureComponent.scriptSignature.bytes) val sigsRemoved = removeSignaturesFromScript(p2shScriptSig.signatures,p2shScriptSig.redeemScript.asm) @@ -298,12 +300,15 @@ trait BitcoinScriptUtil extends BitcoinSLogger { case wtxSigComponent: WitnessV0TransactionSignatureComponent => val scriptEither: Either[(Seq[ScriptToken], ScriptPubKey), ScriptError] = w.witnessVersion.rebuild(wtxSigComponent.witness,w.witnessProgram) parseScriptEither(scriptEither) + case f : FedPegTransactionSignatureComponent => + calculateScriptForSigning(f.witnessTxSigComponent,script) case base : BaseTransactionSignatureComponent => //shouldn't have BaseTransactionSignatureComponent with a witness scriptPubKey script } - case _ : P2PKHScriptPubKey | _ : P2PKScriptPubKey | _ : MultiSignatureScriptPubKey | - _ : NonStandardScriptPubKey | _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey => + case _ : P2PKHScriptPubKey | _ : P2PKScriptPubKey | _ : MultiSignatureScriptPubKey + | _ : NonStandardScriptPubKey | _ : CLTVScriptPubKey | _ : CSVScriptPubKey | _ : WitnessCommitment + | _: WithdrawScriptPubKey | EmptyScriptPubKey => script } @@ -360,7 +365,7 @@ trait BitcoinScriptUtil extends BitcoinSLogger { val s = P2SHScriptSignature(tx.inputs(inputIndex.toInt).scriptSignature.bytes) parseSigVersion(tx,s.redeemScript,inputIndex) case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey | _: NonStandardScriptPubKey - | _: CLTVScriptPubKey | _: CSVScriptPubKey | _ : WitnessCommitment | EmptyScriptPubKey => SigVersionBase + | _: CLTVScriptPubKey | _: CSVScriptPubKey | _ : WitnessCommitment | _: WithdrawScriptPubKey | EmptyScriptPubKey => SigVersionBase } diff --git a/src/test/java/org/bitcoin/NativeSecp256k1Test.java b/src/test/java/org/bitcoin/NativeSecp256k1Test.java index b4f6eb3718e..8c13f1b1613 100644 --- a/src/test/java/org/bitcoin/NativeSecp256k1Test.java +++ b/src/test/java/org/bitcoin/NativeSecp256k1Test.java @@ -1,3 +1,4 @@ + package org.bitcoin; import com.google.common.io.BaseEncoding; @@ -11,6 +12,9 @@ */ public class NativeSecp256k1Test { + + private static final BaseEncoding hexEncoder = BaseEncoding.base16(); + //TODO improve comments/add more tests /** * This tests verify() for a valid signature @@ -18,9 +22,9 @@ public class NativeSecp256k1Test { @Test public void testVerifyPos() throws AssertFailException { boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sig = hexEncoder.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); result = NativeSecp256k1.verify( data, sig, pub); assertEquals( result, true , "testVerifyPos"); @@ -32,9 +36,9 @@ public void testVerifyPos() throws AssertFailException { @Test public void testVerifyNeg() throws AssertFailException{ boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91"); //sha256hash of "testing" + byte[] sig = hexEncoder.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); result = NativeSecp256k1.verify( data, sig, pub); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); @@ -47,7 +51,7 @@ public void testVerifyNeg() throws AssertFailException{ @Test public void testSecKeyVerifyPos() throws AssertFailException{ boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); result = NativeSecp256k1.secKeyVerify( sec ); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); @@ -60,7 +64,7 @@ public void testSecKeyVerifyPos() throws AssertFailException{ @Test public void testSecKeyVerifyNeg() throws AssertFailException{ boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); result = NativeSecp256k1.secKeyVerify( sec ); //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); @@ -72,7 +76,7 @@ public void testSecKeyVerifyNeg() throws AssertFailException{ */ @Test public void testPubKeyCreatePos() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); byte[] resultArr = NativeSecp256k1.computePubkey( sec, true); String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -84,7 +88,7 @@ public void testPubKeyCreatePos() throws AssertFailException{ */ @Test public void testPubKeyCreateNeg() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); byte[] resultArr = NativeSecp256k1.computePubkey( sec, true); String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -97,8 +101,8 @@ public void testPubKeyCreateNeg() throws AssertFailException{ @Test public void testSignPos() throws AssertFailException{ - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); byte[] resultArr = NativeSecp256k1.sign(data, sec); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -110,8 +114,8 @@ public void testSignPos() throws AssertFailException{ */ @Test public void testSignNeg() throws AssertFailException{ - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + byte[] data = hexEncoder.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90"); //sha256hash of "testing" + byte[] sec = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); byte[] resultArr = NativeSecp256k1.sign(data, sec); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -123,8 +127,8 @@ public void testSignNeg() throws AssertFailException{ */ @Test public void testPrivKeyTweakAdd_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.privKeyTweakAdd( sec , data ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -136,8 +140,8 @@ public void testPrivKeyTweakAdd_1() throws AssertFailException { */ @Test public void testPrivKeyTweakMul_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.privKeyTweakMul( sec , data ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -149,8 +153,8 @@ public void testPrivKeyTweakMul_1() throws AssertFailException { */ @Test public void testPrivKeyTweakAdd_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data, false ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -162,8 +166,8 @@ public void testPrivKeyTweakAdd_2() throws AssertFailException { */ @Test public void testPrivKeyTweakMul_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); + byte[] data = hexEncoder.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3"); //sha256hash of "tweak" byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data, false ); String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); @@ -174,7 +178,7 @@ public void testPrivKeyTweakMul_2() throws AssertFailException { */ @Test public void testRandomize() throws AssertFailException { - byte[] seed = BaseEncoding.base16().lowerCase().decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()); //sha256hash of "random" + byte[] seed = hexEncoder.decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11"); //sha256hash of "random" boolean result = NativeSecp256k1.randomize(seed); assertEquals( result, true, "testRandomize"); } @@ -182,12 +186,36 @@ public void testRandomize() throws AssertFailException { @Test public void testCreateECDHSecret() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] sec = hexEncoder.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); + byte[] pub = hexEncoder.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40"); byte[] resultArr = NativeSecp256k1.createECDHSecret(sec, pub); String ecdhString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret"); } -} \ No newline at end of file + /** + * Tests that we can parse public keys + * @throws AssertFailException + */ + @Test + public void testIsValidPubKeyPos() throws AssertFailException { + byte[] pubkey = hexEncoder.lowerCase().decode("0456b3817434935db42afda0165de529b938cf67c7510168a51b9297b1ca7e4d91ea59c64516373dd2fe6acc79bb762718bc2659fa68d343bdb12d5ef7b9ed002b"); + byte[] compressedPubKey = hexEncoder.lowerCase().decode("03de961a47a519c5c0fc8e744d1f657f9ea6b9a921d2a3bceb8743e1885f752676"); + + boolean result1 = NativeSecp256k1.isValidPubKey(pubkey); + boolean result2 = NativeSecp256k1.isValidPubKey(compressedPubKey); + + assertEquals(result1, true, "Uncompressed pubkey parsed failed"); + assertEquals(result2, true, "Compressed pubkey parsed failed"); + } + @Test + public void testIsValidPubKeyNeg() throws AssertFailException { + //do we have test vectors some where to test this more thoroughly? + byte[] pubkey = hexEncoder.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + boolean result1 = NativeSecp256k1.isValidPubKey(pubkey); + + assertEquals(result1, false, "Compressed pubkey parsed succeeded when it should have failed"); + } + +} diff --git a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala index 3ea82e740f2..9adb6e15272 100644 --- a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala +++ b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala @@ -138,4 +138,12 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr if (result != ScriptOk) logger.warn("Result: " + result) Seq(ScriptErrorPushSize, ScriptOk).contains(result) } + + property("generate a valid withdrawl tx from a sidechain") = + Prop.forAll(TransactionGenerators.withdrawlTransaction) { case (fPegSigComponent, privKeys) => + val program = ScriptProgram(fPegSigComponent) + val result = ScriptInterpreter.run(program) + if (result != ScriptOk) logger.error("Script result: " + result) + result == ScriptOk + } } diff --git a/src/test/scala/org/bitcoins/core/protocol/script/ContractSpec.scala b/src/test/scala/org/bitcoins/core/protocol/script/ContractSpec.scala new file mode 100644 index 00000000000..600feba6df8 --- /dev/null +++ b/src/test/scala/org/bitcoins/core/protocol/script/ContractSpec.scala @@ -0,0 +1,27 @@ +package org.bitcoins.core.protocol.script + +import org.bitcoins.core.gen.ScriptGenerators +import org.scalacheck.{Prop, Properties} + +/** + * Created by chris on 3/15/17. + */ +class ContractSpec extends Properties("ContractSpec") { + + property("serialization symmetry") = { + Prop.forAll(ScriptGenerators.contract) { contract => + Contract(contract.hex) == contract + } + } + + property("field access serialization symmetry") = { + Prop.forAll(ScriptGenerators.contract) { contract => + val (prefix,nonce,hash) = (contract.prefix, contract.nonce, contract.hash) + val c = Contract(prefix,nonce,hash) + c.prefix == contract.prefix && + c.nonce == contract.nonce && + c.hash == contract.hash + + } + } +} diff --git a/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptPubKeySpec.scala b/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptPubKeySpec.scala new file mode 100644 index 00000000000..4e5b205ead7 --- /dev/null +++ b/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptPubKeySpec.scala @@ -0,0 +1,21 @@ +package org.bitcoins.core.protocol.script + +import org.bitcoins.core.gen.{CryptoGenerators, ScriptGenerators} +import org.scalacheck.{Prop, Properties} + +/** + * Created by chris on 3/13/17. + */ +class WithdrawScriptPubKeySpec extends Properties("WithdrawScriptPubKeySpec") { + + property("get genesis hash for the blockchain we are pegged to") = + Prop.forAll(CryptoGenerators.doubleSha256Digest) { hash => + val w = WithdrawScriptPubKey(hash) + w.genesisHash == hash + } + + property("serialization symmetry") = + Prop.forAll(ScriptGenerators.withdrawScriptPubKey) { case (withdrawScriptPubKey,_) => + WithdrawScriptPubKey(withdrawScriptPubKey.hex) == withdrawScriptPubKey + } +} diff --git a/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptSignatureSpec.scala b/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptSignatureSpec.scala new file mode 100644 index 00000000000..71564df416e --- /dev/null +++ b/src/test/scala/org/bitcoins/core/protocol/script/WithdrawScriptSignatureSpec.scala @@ -0,0 +1,37 @@ +package org.bitcoins.core.protocol.script + +import org.bitcoins.core.crypto.DoubleSha256Digest +import org.bitcoins.core.gen.{MerkleGenerator, ScriptGenerators, TransactionGenerators} +import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.protocol.blockchain.{Block, MerkleBlock} +import org.bitcoins.core.protocol.transaction.Transaction +import org.bitcoins.core.util.BitcoinSLogger +import org.scalacheck.{Gen, Prop, Properties} + +/** + * Created by chris on 3/13/17. + */ +class WithdrawScriptSignatureSpec extends Properties("WithdrawScriptSignatureSpec") with BitcoinSLogger { + + property("serialization symmetry") = { + Prop.forAll(ScriptGenerators.withdrawScriptSignature) { scriptSig => + WithdrawScriptSignature(scriptSig.hex) == scriptSig && + WithdrawScriptSignature.fromAsm(scriptSig.asm) == scriptSig + } + } + + property("methods to access individual fields must work") = + Prop.forAllNoShrink(ScriptGenerators.contract, MerkleGenerator.merkleBlockWithInsertedTxIds, + TransactionGenerators.transaction) { case (contract: Contract, + merkleBlockTuple: (MerkleBlock,Block,Seq[DoubleSha256Digest]), + lockingTx: Transaction) => + val (merkleBlock,_,_) = merkleBlockTuple + val outputIndex = UInt32(Gen.choose(0,lockingTx.outputs.size).sample.get) + val scriptSig = WithdrawScriptSignature(contract,merkleBlock,lockingTx,outputIndex) + scriptSig.contract == contract && + scriptSig.merkleBlock == merkleBlock && + scriptSig.lockingTransaction == lockingTx && + scriptSig.lockTxOutputIndex == outputIndex + } + +} diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala index 7cbbb0e246d..8c7644560f1 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala @@ -10,7 +10,7 @@ import org.scalacheck.{Prop, Properties} class TransactionInputSpec extends Properties("TranactionInputSpec") with BitcoinSLogger { property("Serialization symmetry") = - Prop.forAll(TransactionGenerators.inputs) { input => + Prop.forAll(TransactionGenerators.input) { input => TransactionInput(input.hex) == input } } diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutPointSpec.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutPointSpec.scala index b3308127b46..37f9aa30597 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutPointSpec.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutPointSpec.scala @@ -10,7 +10,7 @@ import org.scalacheck.{Prop, Properties} class TransactionOutPointSpec extends Properties("TransactionOutPointSpec") with BitcoinSLogger { property("Serialization symmetry") = - Prop.forAll(TransactionGenerators.outPoints) { outPoint => + Prop.forAll(TransactionGenerators.outPoint) { outPoint => TransactionOutPoint(outPoint.hex) == outPoint } } diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutputSpec.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutputSpec.scala index 7f5346559fa..972018283fd 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutputSpec.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionOutputSpec.scala @@ -9,7 +9,7 @@ import org.scalacheck.{Prop, Properties} class TransactionOutputSpec extends Properties("TransactionOutputSpec") { property("Serialization symmetry") = - Prop.forAll(TransactionGenerators.outputs) { output => + Prop.forAll(TransactionGenerators.output) { output => TransactionOutput(output.hex) == output output.hex == TransactionOutput(output.hex).hex } diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala index 315f72bfc07..b847b27bd1f 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala @@ -10,7 +10,7 @@ import org.scalacheck.{Prop, Properties} class TransactionSpec extends Properties("TransactionSpec") with BitcoinSLogger { property("Serialization symmetry") = - Prop.forAll(TransactionGenerators.transactions) { tx => + Prop.forAll(TransactionGenerators.transaction) { tx => val result = Transaction(tx.hex) == tx if (!result) logger.error("Incorrect tx hex: " + tx.hex) result diff --git a/src/test/scala/org/bitcoins/core/script/constant/ConstantInterpreterTest.scala b/src/test/scala/org/bitcoins/core/script/constant/ConstantInterpreterTest.scala index fe70e14d011..6d187f111f0 100644 --- a/src/test/scala/org/bitcoins/core/script/constant/ConstantInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/core/script/constant/ConstantInterpreterTest.scala @@ -127,12 +127,22 @@ class ConstantInterpreterTest extends FlatSpec with MustMatchers with ConstantIn } } - it must "return ScriptErrorMinimalData if program contains ScriptVerifyMinimalData flag and 2nd item in script is" + - " zero" in { + it must "return ScriptErrorMinimalData if program contains ScriptVerifyMinimalData flag and 2nd item in script is zero" in { val stack = List() val script = List(OP_PUSHDATA4,ScriptNumber.zero) val program = ScriptProgram(ScriptProgram(TestUtil.testProgram, stack,script),Seq[ScriptFlag](ScriptVerifyMinimalData)) val newProgram = ScriptProgramTestUtil.toExecutedScriptProgram(opPushData4(program)) newProgram.error must be (Some(ScriptErrorMinimalData)) } + + it must "push a constant onto the stack that is using OP_PUSHDATA1 where the pushop can be interpreted as a script number operation" in { + val constant = ScriptConstant("01000000010000000000000000000000000000000000000000000000000000000000000000" + + "ffffffff00ffffffff014a7afa8f7d52fd9e17a914b167f19394cd656c34f843ac2387e602007fd15b8700000000") + val stack = Nil + val script = List(OP_PUSHDATA1, OP_3, constant) + val program = ScriptProgram(TestUtil.testProgram, stack,script) + val newProgram = opPushData1(program) + newProgram.stack must be (Seq(constant)) + + } } diff --git a/src/test/scala/org/bitcoins/core/script/crypto/CryptoInterpreterTest.scala b/src/test/scala/org/bitcoins/core/script/crypto/CryptoInterpreterTest.scala index 18cb31c053f..364c01fa044 100644 --- a/src/test/scala/org/bitcoins/core/script/crypto/CryptoInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/core/script/crypto/CryptoInterpreterTest.scala @@ -1,14 +1,21 @@ package org.bitcoins.core.script.crypto -import org.bitcoins.core.currency.CurrencyUnits -import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.protocol.script.{P2SHScriptSignature, ScriptPubKey, ScriptSignature, SigVersionBase} +import org.bitcoins.core.config.TestNet3 +import org.bitcoins.core.crypto.{DoubleSha256Digest, FedPegTransactionSignatureComponent, Sha256Hash160Digest, WitnessV0TransactionSignatureComponent} +import org.bitcoins.core.currency.{CurrencyUnits, Satoshis} +import org.bitcoins.core.gen.{CryptoGenerators, MerkleGenerator, ScriptGenerators, TransactionGenerators} +import org.bitcoins.core.number.{Int64, UInt32} +import org.bitcoins.core.policy.Policy +import org.bitcoins.core.protocol.blockchain.TestNetChainParams +import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script._ import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.flag.{ScriptFlagFactory, ScriptVerifyDerSig, ScriptVerifyNullDummy} +import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result._ -import org.bitcoins.core.util.{BitcoinSLogger, ScriptProgramTestUtil, TestUtil, TransactionTestUtil} +import org.bitcoins.core.script.stack.OP_DROP +import org.bitcoins.core.util._ import org.scalatest.{FlatSpec, MustMatchers} /** @@ -174,4 +181,112 @@ class CryptoInterpreterTest extends FlatSpec with MustMatchers with CryptoInterp newProgram.lastCodeSeparator must be (Some(0)) } + it must "verify an attempt to withdraw coins from a blockchain" in { + // 1. genesis block hash of the chain the withdraw is coming from + // 2. the index within the locking tx's outputs we are claiming + // 3. the locking tx itself, this is the locking tx on the mainchain, not the relocking tx on sidechain (WithdrawProofReadStackItem) + // 4. the merkle block structure which contains the block in which + // the locking transaction is present (WithdrawProofReadStackItem) + // 5. The contract which we are expected to send coins to + + //genesis block hash on the chain we are pegged to (bitcoin regtest chain in this case) + val genesisBlockHash = DoubleSha256Digest("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f") + + val inputIndex = UInt32.zero + val usersHash = Sha256Hash160Digest("d76885e2754fcd108c0204eab323e071e24e9a98") + val contract: Seq[Byte] = BitcoinSUtil.decodeHex("5032504800000000000000000000000000000000") ++ usersHash.bytes + val amount = Satoshis(Int64(500)) + //note: this has the pushop on the front for the script which elements does not have + val fedPegScript = ScriptPubKey("255121025015a9e8e8831cf859e314b5a6a283d8fd6d201ba6aa8c7d20453dfd12fa2bea51ae") + val lockingScriptPubKey: ScriptPubKey = P2SHScriptPubKey(fedPegScript) + val lockingOutput: TransactionOutput = TransactionOutput(amount,lockingScriptPubKey) + val lockTx = buildLockingTx(lockingOutput) + //TODO: This will fail to generate every now and then because generating a merkle block is expensive + val (merkleBlock,_,_) = MerkleGenerator.merkleBlockWithInsertedTxIds(Seq(lockTx)).sample.get + + val (sidechainCreditingTx,outputIndex) = buildSidechainCreditingTx(genesisBlockHash) + val sidechainCreditingOutput = sidechainCreditingTx.outputs(outputIndex) + val sidechainUserOutput = TransactionOutput(amount,P2PKHScriptPubKey(usersHash)) + val relockScriptPubKey = WithdrawScriptPubKey(DoubleSha256Digest(genesisBlockHash.bytes)) + val relockAmount = sidechainCreditingOutput.value - amount + val sidechainFederationRelockOutput = TransactionOutput(relockAmount, relockScriptPubKey) + val sidechainReceivingTx = Transaction(TransactionConstants.version,Seq(TransactionGenerators.input.sample.get), + Seq(sidechainUserOutput, sidechainFederationRelockOutput), TransactionConstants.lockTime) + + val wtxSigComponent = WitnessV0TransactionSignatureComponent(sidechainReceivingTx,inputIndex, sidechainCreditingOutput, + Policy.standardScriptVerifyFlags, SigVersionWitnessV0) + val fPegSigComponent = FedPegTransactionSignatureComponent(wtxSigComponent,fedPegScript) + val stack: Seq[ScriptToken] = Seq(ScriptConstant(genesisBlockHash.bytes), ScriptNumber.zero, + ScriptConstant(lockTx.bytes), ScriptConstant(merkleBlock.bytes), ScriptConstant(contract)) + val script: Seq[ScriptToken] = Seq(OP_WITHDRAWPROOFVERIFY) + + val program = ScriptProgram(ScriptProgram(fPegSigComponent), stack, script) + + val newProgram = opWithdrawProofVerify(program) + + newProgram.isInstanceOf[ExecutedScriptProgram] must be (false) + newProgram.script must be (Nil) + newProgram.stack must be (stack) + } + + it must "fail to verify an attempt to withdraw coins from a blockchain if we do not have a valid relock script" in { + // 1. genesis block hash of the chain the withdraw is coming from + // 2. the index within the locking tx's outputs we are claiming + // 3. the locking tx itself, this is the locking tx on the mainchain, not the relocking tx on sidechain (WithdrawProofReadStackItem) + // 4. the merkle block structure which contains the block in which + // the locking transaction is present (WithdrawProofReadStackItem) + // 5. The contract which we are expected to send coins to + + //genesis block hash on the chain we are pegged to (bitcoin regtest chain in this case) + val genesisBlockHash = DoubleSha256Digest("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f") + + val inputIndex = UInt32.zero + + val contract: Seq[Byte] = BitcoinSUtil.decodeHex("5032504800000000000000000000000000000000d76885e2754fcd108c0204eab323e071e24e9a98") + val amount = Satoshis(Int64(500)) + //note: this has the pushop on the front for the script which elements does not have + val fedPegScript = ScriptPubKey("255121025015a9e8e8831cf859e314b5a6a283d8fd6d201ba6aa8c7d20453dfd12fa2bea51ae") + val lockingScriptPubKey: ScriptPubKey = P2SHScriptPubKey(fedPegScript) + val lockingOutput: TransactionOutput = TransactionOutput(amount,lockingScriptPubKey) + val lockTx = buildLockingTx(lockingOutput) + //TODO: This will fail to generate every now and then because generating a merkle block is expensive + val (merkleBlock,_,_) = MerkleGenerator.merkleBlockWithInsertedTxIds(Seq(lockTx)).sample.get + + val (sidechainCreditingTx,outputIndex) = buildSidechainCreditingTx(genesisBlockHash) + val sidechainCreditingOutput = sidechainCreditingTx.outputs(outputIndex) + val sidechainOutput = TransactionOutput(amount,P2PKHScriptPubKey(Sha256Hash160Digest("d76885e2754fcd108c0204eab323e071e24e9a98"))) + val sidechainReceivingTx = Transaction(TransactionConstants.version,Seq(TransactionGenerators.input.sample.get), + Seq(sidechainOutput), TransactionConstants.lockTime) + + val wtxSigComponent = WitnessV0TransactionSignatureComponent(sidechainReceivingTx,inputIndex, sidechainCreditingOutput, + Policy.standardScriptVerifyFlags, SigVersionWitnessV0) + val fPegSigComponent = FedPegTransactionSignatureComponent(wtxSigComponent,fedPegScript) + val stack: Seq[ScriptToken] = Seq(ScriptConstant(genesisBlockHash.bytes), ScriptNumber.zero, + ScriptConstant(lockTx.bytes), ScriptConstant(merkleBlock.bytes), ScriptConstant(contract)) + val script: Seq[ScriptToken] = Seq(OP_WITHDRAWPROOFVERIFY) + + val program = ScriptProgram(ScriptProgram(fPegSigComponent), stack, script) + + val newProgram = opWithdrawProofVerify(program) + + newProgram.isInstanceOf[ExecutedScriptProgram] must be (true) + val errorProgram = newProgram.asInstanceOf[ExecutedScriptProgram] + errorProgram.error must be (Some(ScriptErrorWithdrawVerifyRelockScriptVal)) + } + + + private def buildLockingTx(lockingOutput: TransactionOutput): Transaction = { + val randomInput: TransactionInput = TransactionGenerators.input.sample.get + Transaction(TransactionConstants.version,Seq(randomInput), Seq(lockingOutput), TransactionConstants.lockTime) + } + + /** Builds the crediting OP_WPV and the output index it is located at */ + private def buildSidechainCreditingTx(genesisBlockHash: DoubleSha256Digest): (Transaction,Int) = { + val scriptPubKey = ScriptPubKey.fromAsm(Seq(BytesToPushOntoStack(32), + ScriptConstant(genesisBlockHash.bytes), OP_WITHDRAWPROOFVERIFY)) + val amount = Satoshis(Int64(1000)) + val outputs = Seq(TransactionOutput(amount,scriptPubKey)) + val inputs = Seq(TransactionGenerators.input.sample.get) + (Transaction(TransactionConstants.version,inputs,outputs,TransactionConstants.lockTime),0) + } } diff --git a/src/test/scala/org/bitcoins/core/script/interpreter/ScriptInterpreterTest.scala b/src/test/scala/org/bitcoins/core/script/interpreter/ScriptInterpreterTest.scala index 6e189cfb5cd..b0b7871a916 100644 --- a/src/test/scala/org/bitcoins/core/script/interpreter/ScriptInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/core/script/interpreter/ScriptInterpreterTest.scala @@ -31,7 +31,7 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp //use this to represent a single test case from script_valid.json /* val lines = """ - | [ ["0 0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "NULLFAIL", "BIP66-compliant but not NULLFAIL-compliant"]] + | [ ["", "0 0 0 1 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", "P2SH,STRICTENC", "OK", "Zero sigs means no sigs are checked"]] """.stripMargin*/ val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close() val json = lines.parseJson @@ -45,6 +45,7 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp logger.info("Raw test case: " + testCase.raw) logger.info("Parsed ScriptSig: " + testCase.scriptSig) logger.info("Parsed ScriptPubKey: " + testCase.scriptPubKey) + logger.debug("Parsed scriptPubKey asm: " + testCase.scriptPubKey.asm) logger.info("Parsed tx: " + tx.hex) logger.info("Flags: " + testCase.flags) logger.info("Comments: " + testCase.comments) diff --git a/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala b/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala index 7c7068a1301..d5303794d8a 100644 --- a/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala +++ b/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala @@ -5,8 +5,9 @@ import org.bitcoins.core.script.arithmetic.{OP_1ADD, OP_ADD} import org.bitcoins.core.script.bitwise.OP_EQUAL import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.control.{OP_ENDIF, OP_IF} -import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_HASH160} -import org.bitcoins.core.script.reserved.{OP_NOP10, OP_NOP} +import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_HASH160, OP_REORGPROOFVERIFY, OP_WITHDRAWPROOFVERIFY} +import org.bitcoins.core.script.locktime.{OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY} +import org.bitcoins.core.script.reserved.{OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, _} import org.bitcoins.core.script.stack.OP_PICK import org.bitcoins.core.util.{BitcoinSUtil, TestUtil} import org.scalatest.{FlatSpec, MustMatchers} @@ -212,4 +213,11 @@ class ScriptParserTest extends FlatSpec with MustMatchers with ScriptParser with ))) } + + it must "parse a string repurposed OP_NOPs correctly" in { + val str = "NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL" + val asm = ScriptParser.fromString(str) + asm must be (List(OP_NOP1, OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY, + OP_WITHDRAWPROOFVERIFY, OP_REORGPROOFVERIFY, OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10, OP_1, OP_EQUAL)) + } }