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
+ * [