Skip to content

Commit

Permalink
Added secp256k1 schnorrsig module bindings to JNI
Browse files Browse the repository at this point in the history
  • Loading branch information
nkohen committed Apr 14, 2020
1 parent 84ef92d commit 3a083a7
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 1 deletion.
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

0 comments on commit 3a083a7

Please sign in to comment.