diff --git a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java --- a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java +++ b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java @@ -414,6 +414,36 @@ } } + /** + * Verifies the given Schnorr signature in native code. + * Calling when enabled == false is undefined (probably library not loaded) + * + * @param data The data which was signed, must be exactly 32 bytes + * @param signature The signature is exactly 64 bytes + * @param pub The public key which did the signing which is the same ECDSA + */ + public static boolean schnorrVerify(byte[] data, byte[] signature, byte[] pub) { + checkArgument(data.length == 32 && signature.length == 64 && (pub.length == 33 || pub.length == 65)); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + 64 + pub.length) { + byteBuff = ByteBuffer.allocateDirect(32 + 64 + pub.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(signature); + byteBuff.put(pub); + + r.lock(); + try { + return secp256k1_schnorr_verify(byteBuff, Secp256k1Context.getContext(), pub.length) == 1; + } finally { + r.unlock(); + } + } + private static native long secp256k1_ctx_clone(long context); private static native int secp256k1_context_randomize(ByteBuffer byteBuff, long context); @@ -436,7 +466,7 @@ private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context); - private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); + private static native int secp256k1_schnorr_verify(ByteBuffer byteBuff, long context, int pubLen); private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); diff --git a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java --- a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java +++ b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java @@ -164,6 +164,146 @@ assertEquals( result, true, "testRandomize"); } + /** + * This tests schnorrVerify() for a valid signature + * It tests the following test vectors + * https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr/test-vectors.csv + */ + private static boolean schnorrVerify(String dataS, String sigS, String pubS) { + byte[] data = DatatypeConverter.parseHexBinary(dataS); + byte[] sig = DatatypeConverter.parseHexBinary(sigS); + byte[] pub = DatatypeConverter.parseHexBinary(pubS); + return NativeSecp256k1.schnorrVerify(data, sig, pub); + } + + public static void testSchnorrVerifyPos() throws AssertFailException{ + boolean res0 = schnorrVerify( + "0000000000000000000000000000000000000000000000000000000000000000", + "787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF67031A98831859DC34DFFEEDDA86831842CCD0079E1F92AF177F7F22CC1DCED05", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + ); + assertEquals(res0, true, "testSchnorrVerifyPos0"); + + boolean res1 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res1, true, "testSchnorrVerifyPos1"); + + boolean res2 = schnorrVerify( + "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", + "00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380", + "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" + ); + assertEquals(res2, true, "testSchnorrVerifyPos2"); + + boolean res3 = schnorrVerify( + "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", + "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6302A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D", + "03DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" + ); + assertEquals(res3, true, "testSchnorrVerifyPos3"); + + boolean res4 = schnorrVerify( + "0000000000000000000000000000000000000000000000000000000000000000", + "52818579ACA59767E3291D91B76B637BEF062083284992F2D95F564CA6CB4E3530B1DA849C8E8304ADC0CFE870660334B3CFC18E825EF1DB34CFAE3DFC5D8187", + "031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F" + ); + assertEquals(res4, true, "testSchnorrVerifyPos4"); + + boolean res5 = schnorrVerify( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "570DD4CA83D4E6317B8EE6BAE83467A1BF419D0767122DE409394414B05080DCE9EE5F237CBD108EABAE1E37759AE47F8E4203DA3532EB28DB860F33D62D49BD", + "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" + ); + assertEquals(res5, true, "testSchnorrVerifyPos5"); + } + + /** + * This tests schnorrVerify() for a invalid signature + */ + + public static void testSchnorrVerifyNeg() throws AssertFailException{ + boolean res0 = schnorrVerify( + "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", + "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6302A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D", + "03EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" + ); + assertEquals(res0, false, "testSchnorrVerifyNeg0"); + + boolean res1 = schnorrVerify( + "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", + "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6302A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res1, false, "testSchnorrVerifyNeg1"); + + boolean res2 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1DFA16AEE06609280A19B67A24E1977E4697712B5FD2943914ECD5F730901B4AB7", + "03EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" + ); + assertEquals(res2, false, "testSchnorrVerifyNeg2"); + + boolean res3 = schnorrVerify( + "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", + "00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BED092F9D860F1776A1F7412AD8A1EB50DACCC222BC8C0E26B2056DF2F273EFDEC", + "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" + ); + assertEquals(res3, false, "testSchnorrVerifyNeg3"); + + boolean res4 = schnorrVerify( + "0000000000000000000000000000000000000000000000000000000000000000", + "787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF68FCE5677CE7A623CB20011225797CE7A8DE1DC6CCD4F754A47DA6C600E59543C", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + ); + assertEquals(res4, false, "testSchnorrVerifyNeg4"); + + boolean res5 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res5, false, "testSchnorrVerifyNeg5"); + + boolean res6 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "00000000000000000000000000000000000000000000000000000000000000009E9D01AF988B5CEDCE47221BFA9B222721F3FA408915444A4B489021DB55775F", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res6, false, "testSchnorrVerifyNeg6"); + + boolean res7 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "0000000000000000000000000000000000000000000000000000000000000001D37DDF0254351836D84B1BD6A795FD5D523048F298C4214D187FE4892947F728", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res7, false, "testSchnorrVerifyNeg7"); + + boolean res8 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res8, false, "testSchnorrVerifyNeg8"); + + boolean res9 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2F1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res9, false, "testSchnorrVerifyNeg9"); + + boolean res10 = schnorrVerify( + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ); + assertEquals(res10, false, "testSchnorrVerifyNeg10"); + + } + public static void testCreateECDHSecret() throws AssertFailException{ byte[] sec = DatatypeConverter.parseHexBinary("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"); @@ -212,6 +352,10 @@ //Test randomize() testRandomize(); + //Test verifySchnorr() success/fail + testSchnorrVerifyPos(); + testSchnorrVerifyNeg(); + //Test ECDH testCreateECDHSecret(); diff --git a/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java b/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java --- a/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java +++ b/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java @@ -31,7 +31,6 @@ System.loadLibrary("secp256k1"); contextRef = secp256k1_init_context(); } catch (UnsatisfiedLinkError e) { - System.out.println("UnsatisfiedLinkError: " + e.toString()); isEnabled = false; } enabled = isEnabled; diff --git a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h --- a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h +++ b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h @@ -106,6 +106,14 @@ /* * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_schnorr_verify + * Signature: (Ljava/nio/ByteBuffer;JI)I + */ +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorr_1verify + (JNIEnv *, jclass, jobject, jlong, jint); + +/* + * Class: org_bitcoin_NativeSecp256k1 * Method: secp256k1_ecdh * Signature: (Ljava/nio/ByteBuffer;JI)[[B */ diff --git a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c --- a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c +++ b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c @@ -5,7 +5,7 @@ #include "include/secp256k1.h" #include "include/secp256k1_ecdh.h" #include "include/secp256k1_recovery.h" - +#include "include/secp256k1_schnorr.h" SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone (JNIEnv* env, jclass classObject, jlong ctx_l) @@ -332,6 +332,27 @@ return 0; } +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorr_1verify + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + + unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* sigdata = { (unsigned char*) (data + 32) }; + const unsigned char* pubdata = { (unsigned char*) (data + 32 + 64) }; + + secp256k1_pubkey pubkey; + int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen); + + if( ret ) { + ret = secp256k1_schnorr_verify(ctx, sigdata, data, &pubkey); + } + + (void)classObject; + + return ret; +} + SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) {