Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schnorr JNI #4

Merged
merged 1 commit into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions src/java/org/bitcoin/NativeSecp256k1.java
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,140 @@ public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws Asser
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
*
Expand Down Expand Up @@ -490,4 +624,11 @@ public static synchronized boolean randomize(byte[] seed) {

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);
}
49 changes: 49 additions & 0 deletions src/java/org/bitcoin/NativeSecp256k1Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,49 @@ public static void testCreateECDHSecret() throws AssertFailException{
assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret");
}

public static void testSchnorrSign() throws AssertFailException{
byte[] data = DatatypeConverter.parseHexBinary("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] secKey = DatatypeConverter.parseHexBinary("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF");
byte[] auxRand = DatatypeConverter.parseHexBinary("02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB");

byte[] sigArr = NativeSecp256k1.schnorrSign(data, secKey, auxRand);
String sigStr = DatatypeConverter.printHexBinary(sigArr);
String expectedSig = "F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33";
assertEquals(sigStr, expectedSig, "testSchnorrSign");
}

public static void testSchnorrSignWithNonce() throws AssertFailException{
byte[] data = DatatypeConverter.parseHexBinary("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] secKey = DatatypeConverter.parseHexBinary("688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF");
byte[] nonce = DatatypeConverter.parseHexBinary("8C8CA771D3C25EB38DE7401818EEDA281AC5446F5C1396148F8D9D67592440FE");

byte[] sigArr = NativeSecp256k1.schnorrSignWithNonce(data, secKey, nonce);
String sigStr = DatatypeConverter.printHexBinary(sigArr);
String expectedSig = "5DA618C1936EC728E5CCFF29207F1680DCF4146370BDCFAB0039951B91E3637A50A2A860B130D009405511C3EAFE943E157A0DF2C2020E3E50DF05ADB175332F";
assertEquals(sigStr, expectedSig, "testSchnorrSignWithNonce");
}

public static void testSchnorrComputeSigPoint() throws AssertFailException{
byte[] data = DatatypeConverter.parseHexBinary("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] nonce = DatatypeConverter.parseHexBinary("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B547");
byte[] pubKey = DatatypeConverter.parseHexBinary("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390");

byte[] pointArr = NativeSecp256k1.schnorrComputeSigPoint(data, nonce, pubKey, true);
String pointStr = DatatypeConverter.printHexBinary(pointArr);
String expectedPoint = "020D17280B8D2C2BD3B597B4446419C151DC237353D0FB9EC03D4EB7E8DE7EE0A8";
assertEquals(pointStr, expectedPoint, "testSchnorrComputeSigPoint");
}

public static void testSchnorrVerify() throws AssertFailException{
byte[] sig = DatatypeConverter.parseHexBinary("F14D7E54FF58C5D019CE9986BE4A0E8B7D643BD08EF2CDF1099E1A457865B5477C988C51634A8DC955950A58FF5DC8C506DDB796121E6675946312680C26CF33");
byte[] data = DatatypeConverter.parseHexBinary("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
byte[] pubx = DatatypeConverter.parseHexBinary("B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390");

boolean result = NativeSecp256k1.schnorrVerify(sig, data, pubx);

assertEquals(result, true, "testSchnorrVerify");
}

public static void main(String[] args) throws AssertFailException{

System.out.println("\n libsecp256k1 enabled: " + Secp256k1Context.isEnabled() + "\n");
Expand Down Expand Up @@ -255,6 +298,12 @@ public static void main(String[] args) throws AssertFailException{
//Test ECDH
testCreateECDHSecret();

//Test Schnorr Signing
testSchnorrSign();
testSchnorrSignWithNonce();
testSchnorrComputeSigPoint();
testSchnorrVerify();

NativeSecp256k1.cleanup();

System.out.println(" All tests passed." );
Expand Down
160 changes: 159 additions & 1 deletion src/java/org_bitcoin_NativeSecp256k1.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "include/secp256k1.h"
#include "include/secp256k1_ecdh.h"
#include "include/secp256k1_recovery.h"
#include "include/secp256k1_schnorrsig.h"


SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone
Expand Down Expand Up @@ -74,7 +75,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l)
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* data = (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* secKey = (unsigned char*) (data + 32);

jobjectArray retArray;
Expand Down Expand Up @@ -417,3 +418,160 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e

return retArray;
}

SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorrsig_1sign
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l)
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* msg32 = (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* seckey = (unsigned char*)(msg32 + 32);
unsigned char* rand32 = (unsigned char*)(seckey + 32);

jobjectArray retArray;
jbyteArray sigArray, intsByteArray;
unsigned char intsarray[1];
secp256k1_schnorrsig sig;
unsigned char output[64];

int ret = secp256k1_schnorrsig_sign(ctx, &sig, msg32, seckey, NULL, rand32);

if (ret) {
int ret2 = secp256k1_schnorrsig_serialize(ctx, output, &sig); (void)ret2;
}

intsarray[0] = ret;

retArray = (*env)->NewObjectArray(env, 2,
(*env)->FindClass(env, "[B"),
(*env)->NewByteArray(env, 1));

sigArray = (*env)->NewByteArray(env, 64);
(*env)->SetByteArrayRegion(env, sigArray, 0, 64, (jbyte*)output);
(*env)->SetObjectArrayElement(env, retArray, 0, sigArray);

intsByteArray = (*env)->NewByteArray(env, 1);
(*env)->SetByteArrayRegion(env, intsByteArray, 0, 1, (jbyte*)intsarray);
(*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

(void)classObject;

return retArray;
}

int constant_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *xonly_pk32, const unsigned char *algo16, void *data, unsigned int counter) {
memcpy(nonce32, (const unsigned char*)data, 32);
return 1;
}

const secp256k1_nonce_function_extended constant_nonce = constant_nonce_function;

SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorrsig_1sign_1with_1nonce
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l)
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* msg32 = (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* seckey = (unsigned char*)(msg32 + 32);
unsigned char* nonce = (unsigned char*)(seckey + 32);

jobjectArray retArray;
jbyteArray sigArray, intsByteArray;
unsigned char intsarray[1];
secp256k1_schnorrsig sig;
unsigned char output[64];

int ret = secp256k1_schnorrsig_sign(ctx, &sig, msg32, seckey, constant_nonce, nonce);

if (ret) {
int ret2 = secp256k1_schnorrsig_serialize(ctx, output, &sig); (void)ret2;
}

intsarray[0] = ret;

retArray = (*env)->NewObjectArray(env, 2,
(*env)->FindClass(env, "[B"),
(*env)->NewByteArray(env, 1));

sigArray = (*env)->NewByteArray(env, 64);
(*env)->SetByteArrayRegion(env, sigArray, 0, 64, (jbyte*)output);
(*env)->SetObjectArrayElement(env, retArray, 0, sigArray);

intsByteArray = (*env)->NewByteArray(env, 1);
(*env)->SetByteArrayRegion(env, intsByteArray, 0, 1, (jbyte*)intsarray);
(*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

(void)classObject;

return retArray;
}

SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorrsig_1compute_1sigpoint
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jboolean compressed)
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* msg32 = (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* nonce32 = (unsigned char*)(msg32 + 32);
unsigned char* pubkey32 = (unsigned char*)(nonce32 + 32);

secp256k1_xonly_pubkey pubkey;
secp256k1_pubkey sigpoint;
unsigned char intsarray[2];
jobjectArray retArray;
jbyteArray pointArray, intsByteArray;
unsigned char outputSer[65];
size_t outputLen = 65;

int ret = secp256k1_xonly_pubkey_parse(ctx, &pubkey, pubkey32);

if (ret) {
ret = secp256k1_schnorrsig_compute_sigpoint(ctx, &sigpoint, msg32, nonce32, &pubkey);
}

if( ret ) {
int ret2 = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &sigpoint, compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);(void)ret2;
}

intsarray[0] = outputLen;
intsarray[1] = ret;

retArray = (*env)->NewObjectArray(env, 2,
(*env)->FindClass(env, "[B"),
(*env)->NewByteArray(env, 1));

pointArray = (*env)->NewByteArray(env, outputLen);
(*env)->SetByteArrayRegion(env, pointArray, 0, outputLen, (jbyte*)outputSer);
(*env)->SetObjectArrayElement(env, retArray, 0, pointArray);

intsByteArray = (*env)->NewByteArray(env, 2);
(*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray);
(*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

(void)classObject;

return retArray;
}

SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1schnorrsig_1verify
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l)
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* sig64 = (*env)->GetDirectBufferAddress(env, byteBufferObject);
unsigned char* msg32 = (unsigned char*)(sig64 + 64);
unsigned char* pubx32 = (unsigned char*)(msg32 + 32);

secp256k1_xonly_pubkey pubx;
secp256k1_schnorrsig sig;

int ret = secp256k1_xonly_pubkey_parse(ctx, &pubx, pubx32);

if (ret) {
ret = secp256k1_schnorrsig_parse(ctx, &sig, sig64);
}

if (ret) {
ret = secp256k1_schnorrsig_verify(ctx, &sig, msg32, &pubx);
}

(void)classObject;

return ret;
}
Loading