diff --git a/.travis.yml b/.travis.yml index 2fa2ecb399..13f0905f9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -147,11 +147,11 @@ stages: - name: test if: commit_message !~ /(?i)^docs:/ AND NOT - ((branch = master AND type = push) OR (tag IS present)) + ((branch = schnorr-dlc AND type = push) OR (tag IS present)) # don't run tests on merge builds, just publish library # and website - name: release - if: ((branch = master AND type = push) OR (tag IS present)) AND NOT fork + if: ((branch = schnorr-dlc AND type = push) OR (tag IS present)) AND NOT fork script: # Modify PATH to include binaries we are about to download diff --git a/crypto-test/src/test/scala/org/bitcoins/crypto/BouncyCastleSecp256k1Test.scala b/crypto-test/src/test/scala/org/bitcoins/crypto/BouncyCastleSecp256k1Test.scala index 069682de99..f2759ed9bd 100644 --- a/crypto-test/src/test/scala/org/bitcoins/crypto/BouncyCastleSecp256k1Test.scala +++ b/crypto-test/src/test/scala/org/bitcoins/crypto/BouncyCastleSecp256k1Test.scala @@ -119,7 +119,6 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest { } } - /* it must "compute schnorr signatures the same" in { forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32), @@ -179,5 +178,4 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest { assert(bouncyCastleSigPoint == secpSigPoint) } } - */ } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala index 71c98ec4e2..2d70f8bcc1 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala @@ -113,18 +113,34 @@ sealed abstract class ECPrivateKey } def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = { - val auxRand = ECPrivateKey.freshPrivateKey.bytes - schnorrSign(dataToSign, auxRand) + schnorrSign(dataToSign, CryptoContext.default) + } + + def schnorrSign( + dataToSign: ByteVector, + context: CryptoContext): SchnorrDigitalSignature = { + val auxRand = ECPrivateKey.freshPrivateKey.bytes + schnorrSign(dataToSign, auxRand, context) } - // TODO: match on CryptoContext once secp version is added def schnorrSign( dataToSign: ByteVector, auxRand: ByteVector): SchnorrDigitalSignature = { - schnorrSignWithBouncyCastle(dataToSign, auxRand) + schnorrSign(dataToSign, auxRand, CryptoContext.default) + } + + def schnorrSign( + dataToSign: ByteVector, + auxRand: ByteVector, + context: CryptoContext): SchnorrDigitalSignature = { + context match { + case CryptoContext.LibSecp256k1 => + schnorrSignWithSecp(dataToSign, auxRand) + case CryptoContext.BouncyCastle => + schnorrSignWithBouncyCastle(dataToSign, auxRand) + } } - /* def schnorrSignWithSecp( dataToSign: ByteVector, auxRand: ByteVector): SchnorrDigitalSignature = { @@ -134,7 +150,6 @@ sealed abstract class ECPrivateKey auxRand.toArray) SchnorrDigitalSignature(ByteVector(sigBytes)) } - */ def schnorrSignWithBouncyCastle( dataToSign: ByteVector, @@ -142,14 +157,24 @@ sealed abstract class ECPrivateKey BouncyCastleUtil.schnorrSign(dataToSign, this, auxRand) } - // TODO: match on CryptoContext once secp version is added def schnorrSignWithNonce( dataToSign: ByteVector, nonce: ECPrivateKey): SchnorrDigitalSignature = { - schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce) + schnorrSignWithNonce(dataToSign, nonce, CryptoContext.default) + } + + def schnorrSignWithNonce( + dataToSign: ByteVector, + nonce: ECPrivateKey, + context: CryptoContext): SchnorrDigitalSignature = { + context match { + case CryptoContext.LibSecp256k1 => + schnorrSignWithNonceWithSecp(dataToSign, nonce) + case CryptoContext.BouncyCastle => + schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce) + } } - /* def schnorrSignWithNonceWithSecp( dataToSign: ByteVector, nonce: ECPrivateKey): SchnorrDigitalSignature = { @@ -159,7 +184,6 @@ sealed abstract class ECPrivateKey nonce.bytes.toArray) SchnorrDigitalSignature(ByteVector(sigBytes)) } - */ def schnorrSignWithNonceWithBouncyCastle( dataToSign: ByteVector, diff --git a/crypto/src/main/scala/org/bitcoins/crypto/SchnorrPublicKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/SchnorrPublicKey.scala index a1d1e60075..829e0365c2 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/SchnorrPublicKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/SchnorrPublicKey.scala @@ -12,12 +12,20 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement { require(Try(publicKey).isSuccess, s"Schnorr public key must be a valid x coordinate, got $bytes") - // TODO: match on CryptoContext once secp version is added def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = { - verifyWithBouncyCastle(data, signature) + verify(data, signature, CryptoContext.default) + } + + def verify( + data: ByteVector, + signature: SchnorrDigitalSignature, + context: CryptoContext): Boolean = { + context match { + case CryptoContext.LibSecp256k1 => verifyWithSecp(data, signature) + case CryptoContext.BouncyCastle => verifyWithBouncyCastle(data, signature) + } } - /* def verifyWithSecp( data: ByteVector, signature: SchnorrDigitalSignature): Boolean = { @@ -25,7 +33,6 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement { data.toArray, bytes.toArray) } - */ def verifyWithBouncyCastle( data: ByteVector, @@ -34,18 +41,29 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement { } def computeSigPoint(data: ByteVector, nonce: SchnorrNonce): ECPublicKey = { - computeSigPoint(data, nonce, compressed = true) + computeSigPoint(data, nonce, compressed = true, CryptoContext.default) } - // TODO: match on CryptoContext once secp version is added def computeSigPoint( data: ByteVector, nonce: SchnorrNonce, compressed: Boolean): ECPublicKey = { - computeSigPointWithBouncyCastle(data, nonce, compressed) + computeSigPoint(data, nonce, compressed, CryptoContext.default) + } + + def computeSigPoint( + data: ByteVector, + nonce: SchnorrNonce, + compressed: Boolean, + context: CryptoContext): ECPublicKey = { + context match { + case CryptoContext.LibSecp256k1 => + computeSigPointWithSecp(data, nonce, compressed) + case CryptoContext.BouncyCastle => + computeSigPointWithBouncyCastle(data, nonce, compressed) + } } - /* def computeSigPointWithSecp( data: ByteVector, nonce: SchnorrNonce, @@ -57,7 +75,6 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement { compressed) ECPublicKey(ByteVector(sigPointBytes)) } - */ def computeSigPointWithBouncyCastle( data: ByteVector, diff --git a/inThisBuild.sbt b/inThisBuild.sbt index d7bdf12288..467599dc73 100644 --- a/inThisBuild.sbt +++ b/inThisBuild.sbt @@ -1,5 +1,10 @@ import scala.util.Properties +version in ThisBuild ~= { version => + val withoutSuffix = version.dropRight(8) + withoutSuffix + "SCHNORR-DLC-SNAPSHOT" +} + val scala2_12 = "2.12.12" val scala2_13 = "2.13.2" diff --git a/secp256k1 b/secp256k1 index c9bab11ef0..da8fcb8c00 160000 --- a/secp256k1 +++ b/secp256k1 @@ -1 +1 @@ -Subproject commit c9bab11ef05ef0cdc0cb0f0e02d3c8e052799767 +Subproject commit da8fcb8c00611bf9a450763a8d1c41a28b7f76f4 diff --git a/secp256k1jni/natives/linux_64/bench_ecdh b/secp256k1jni/natives/linux_64/bench_ecdh index ccc23a7c07..24ba0c36d4 100755 Binary files a/secp256k1jni/natives/linux_64/bench_ecdh and b/secp256k1jni/natives/linux_64/bench_ecdh differ diff --git a/secp256k1jni/natives/linux_64/bench_sign b/secp256k1jni/natives/linux_64/bench_sign index 41d94bc8ef..5f9bd8a428 100755 Binary files a/secp256k1jni/natives/linux_64/bench_sign and b/secp256k1jni/natives/linux_64/bench_sign differ diff --git a/secp256k1jni/natives/linux_64/bench_verify b/secp256k1jni/natives/linux_64/bench_verify index 56e20d2668..2ca1ed792a 100755 Binary files a/secp256k1jni/natives/linux_64/bench_verify and b/secp256k1jni/natives/linux_64/bench_verify differ diff --git a/secp256k1jni/natives/linux_64/libsecp256k1.a b/secp256k1jni/natives/linux_64/libsecp256k1.a deleted file mode 100644 index 3df4d24415..0000000000 Binary files a/secp256k1jni/natives/linux_64/libsecp256k1.a and /dev/null differ diff --git a/secp256k1jni/natives/linux_64/libsecp256k1.so.0.0.0 b/secp256k1jni/natives/linux_64/libsecp256k1.so.0.0.0 index f1234594b0..85e0fbfaaf 100755 Binary files a/secp256k1jni/natives/linux_64/libsecp256k1.so.0.0.0 and b/secp256k1jni/natives/linux_64/libsecp256k1.so.0.0.0 differ diff --git a/secp256k1jni/natives/osx_64/bench_ecdh b/secp256k1jni/natives/osx_64/bench_ecdh index 948e5e2042..65b99174c9 100755 Binary files a/secp256k1jni/natives/osx_64/bench_ecdh and b/secp256k1jni/natives/osx_64/bench_ecdh differ diff --git a/secp256k1jni/natives/osx_64/bench_sign b/secp256k1jni/natives/osx_64/bench_sign index 1f7a8a6c19..abcecdecc2 100755 Binary files a/secp256k1jni/natives/osx_64/bench_sign and b/secp256k1jni/natives/osx_64/bench_sign differ diff --git a/secp256k1jni/natives/osx_64/bench_verify b/secp256k1jni/natives/osx_64/bench_verify index aa79627fc8..471158c62b 100755 Binary files a/secp256k1jni/natives/osx_64/bench_verify and b/secp256k1jni/natives/osx_64/bench_verify differ diff --git a/secp256k1jni/natives/osx_64/libsecp256k1.0.dylib b/secp256k1jni/natives/osx_64/libsecp256k1.0.dylib index ad72410f10..8bf6bfbfa1 100755 Binary files a/secp256k1jni/natives/osx_64/libsecp256k1.0.dylib and b/secp256k1jni/natives/osx_64/libsecp256k1.0.dylib differ diff --git a/secp256k1jni/natives/osx_64/libsecp256k1.a b/secp256k1jni/natives/osx_64/libsecp256k1.a index 43bc9a8bf1..5d0d0afc20 100644 Binary files a/secp256k1jni/natives/osx_64/libsecp256k1.a and b/secp256k1jni/natives/osx_64/libsecp256k1.a differ diff --git a/secp256k1jni/natives/osx_64/libsecp256k1.dylib b/secp256k1jni/natives/osx_64/libsecp256k1.dylib index ad72410f10..8bf6bfbfa1 100755 Binary files a/secp256k1jni/natives/osx_64/libsecp256k1.dylib and b/secp256k1jni/natives/osx_64/libsecp256k1.dylib differ diff --git a/secp256k1jni/natives/osx_64/libsecp256k1_jni.a b/secp256k1jni/natives/osx_64/libsecp256k1_jni.a index 1bae720698..59319f1558 100644 Binary files a/secp256k1jni/natives/osx_64/libsecp256k1_jni.a and b/secp256k1jni/natives/osx_64/libsecp256k1_jni.a differ diff --git a/secp256k1jni/natives/windows_64/libsecp256k1-0.dll b/secp256k1jni/natives/windows_64/libsecp256k1-0.dll index 9e0a57f5e9..0d2c26520e 100755 Binary files a/secp256k1jni/natives/windows_64/libsecp256k1-0.dll and b/secp256k1jni/natives/windows_64/libsecp256k1-0.dll differ diff --git a/secp256k1jni/src/main/java/org/bitcoin/NativeSecp256k1.java b/secp256k1jni/src/main/java/org/bitcoin/NativeSecp256k1.java index ea905bf813..16baaf50b7 100644 --- a/secp256k1jni/src/main/java/org/bitcoin/NativeSecp256k1.java +++ b/secp256k1jni/src/main/java/org/bitcoin/NativeSecp256k1.java @@ -515,6 +515,140 @@ public class NativeSecp256k1 { return resArr; } + /** + * libsecp256k1 schnorr sign - generates a BIP 340 Schnorr signature + * + * @param data message to sign + * @param secKey key to sign with + */ + public static byte[] schnorrSign(byte[] data, byte[] secKey, byte[] auxRand) throws AssertFailException { + checkArgument(data.length == 32 && secKey.length == 32 && auxRand.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) { + byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(secKey); + byteBuff.put(auxRand); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_schnorrsig_sign(byteBuff, Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] sigArray = retByteArray[0]; + int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); + + assertEquals(retVal, 1, "Failed return value check."); + + return sigArray; + } + + /** + * libsecp256k1 schnorr sign - generates a BIP 340 Schnorr signature + * + * @param data message to sign + * @param secKey key to sign with + * @param nonce the nonce (k value) used in signing + */ + public static byte[] schnorrSignWithNonce(byte[] data, byte[] secKey, byte[] nonce) throws AssertFailException { + checkArgument(data.length == 32 && secKey.length == 32 && nonce.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) { + byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(secKey); + byteBuff.put(nonce); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_schnorrsig_sign_with_nonce(byteBuff, Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] sigArray = retByteArray[0]; + int retVal = new BigInteger(new byte[]{retByteArray[1][0]}).intValue(); + + assertEquals(retVal, 1, "Failed return value check."); + + return sigArray; + } + + public static byte[] schnorrComputeSigPoint(byte[] data, byte[] nonce, byte[] pubkey, boolean compressed) throws AssertFailException { + checkArgument(data.length == 32 && nonce.length == 32 && pubkey.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + 32 + 32) { + byteBuff = ByteBuffer.allocateDirect(32 + 32 + 32); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(nonce); + byteBuff.put(pubkey); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_schnorrsig_compute_sigpoint(byteBuff, Secp256k1Context.getContext(), compressed); + } finally { + r.unlock(); + } + + byte[] pointArray = retByteArray[0]; + int outputLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(pointArray.length, outputLen, "Got bad point length."); + assertEquals(retVal, 1, "Failed return value check."); + + return pointArray; + } + + /** + * libsecp256k1 schnorr verify - verifies BIP 340 Schnorr signatures + * + * @param sig signature to verify + * @param data message the signature has signed + * @param pubx the key that did the signing + */ + public static boolean schnorrVerify(byte[] sig, byte[] data, byte[] pubx) throws AssertFailException { + checkArgument(sig.length == 64 && data.length == 32 && pubx.length == 32); + + ByteBuffer byteBuffer = nativeECDSABuffer.get(); + if (byteBuffer == null || byteBuffer.capacity() < 64 + 32 + 32) { + byteBuffer = ByteBuffer.allocateDirect(64 + 32 + 32); + byteBuffer.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuffer); + } + byteBuffer.rewind(); + byteBuffer.put(sig); + byteBuffer.put(data); + byteBuffer.put(pubx); + + r.lock(); + try { + return secp256k1_schnorrsig_verify(byteBuffer, Secp256k1Context.getContext()) == 1; + } finally { + r.unlock(); + } + } + /** * libsecp256k1 randomize - updates the context randomization * @@ -582,4 +716,11 @@ public class NativeSecp256k1 { private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); + private static native byte[][] secp256k1_schnorrsig_sign(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_schnorrsig_sign_with_nonce(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_schnorrsig_compute_sigpoint(ByteBuffer byteBuff, long context, boolean compressed); + + private static native int secp256k1_schnorrsig_verify(ByteBuffer byteBuffer, long context); } diff --git a/secp256k1jni/src/test/java/org/bitcoin/NativeSecp256k1Test.java b/secp256k1jni/src/test/java/org/bitcoin/NativeSecp256k1Test.java index 9484378e51..541b701f76 100644 --- a/secp256k1jni/src/test/java/org/bitcoin/NativeSecp256k1Test.java +++ b/secp256k1jni/src/test/java/org/bitcoin/NativeSecp256k1Test.java @@ -258,6 +258,52 @@ public class NativeSecp256k1Test { assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret"); } + @Test + public void testSchnorrSign() throws AssertFailException{ + byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + byte[] secKey = toByteArray("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF"); + byte[] auxRand = toByteArray("02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB"); + + byte[] sigArr = NativeSecp256k1.schnorrSign(data, secKey, auxRand); + String sigStr = toHex(sigArr); + String expectedSig = "F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33"; + assertEquals(sigStr, expectedSig, "testSchnorrSign"); + } + + @Test + public void testSchnorrSignWithNonce() throws AssertFailException{ + byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + byte[] secKey = toByteArray("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF"); + byte[] nonce = toByteArray("8C8CA771D3C25EB38DE7401818EEDA281AC5446F5C1396148F8D9D67592440FE"); + + byte[] sigArr = NativeSecp256k1.schnorrSignWithNonce(data, secKey, nonce); + String sigStr = toHex(sigArr); + String expectedSig = "5DA618C1936EC728E5CCFF29207F1680DCF4146370BDCFAB0039951B91E3637A50A2A860B130D009405511C3EAFE943E157A0DF2C2020E3E50DF05ADB175332F"; + assertEquals(sigStr, expectedSig, "testSchnorrSignWithNonce"); + } + + @Test + public void testSchnorrComputeSigPoint() throws AssertFailException{ + byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + byte[] nonce = toByteArray("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B547"); + byte[] pubKey = toByteArray("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390"); + + byte[] pointArr = NativeSecp256k1.schnorrComputeSigPoint(data, nonce, pubKey, true); + String pointStr = toHex(pointArr); + String expectedPoint = "020D17280B8D2C2BD3B597B4446419C151DC237353D0FB9EC03D4EB7E8DE7EE0A8"; + assertEquals(pointStr, expectedPoint, "testSchnorrComputeSigPoint"); + } + + @Test + public void testSchnorrVerify() throws AssertFailException{ + byte[] sig = toByteArray("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33"); + byte[] data = toByteArray("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"); + byte[] pubx = toByteArray("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390"); + + boolean result = NativeSecp256k1.schnorrVerify(sig, data, pubx); + + assertEquals(result, true, "testSchnorrVerify"); + } //https://stackoverflow.com/a/19119453/967713 private static byte[] toByteArray(final String hex) {