Adding to Secp256k1 JNI
Bitcoin-S uses a Java Native Interface (JNI) to execute functions in secp256k1-zkp from java/scala. The native java bindings used to be a part of the secp256k1 library that was maintained by bitcoin-core, but it was removed in October 2019. We maintain a fork of secp256k1 which forks off of bitcoin-core's master but re-introduces the jni. This is also the easiest way to add functionality from new projects such as Schnorr signatures and ECDSA adaptor signatures by rebasing the bitcoin-s branch with the JNI on top of these experimental branches. That said, it is quite tricky to hook up new functionality in secp256k1 into bitcoin-s and specifically NativeSecp256k1.java. The following is a description of this process.
Adding a new function to NativeSecp256k1.java
Adding to src/java/org_bitcoin_NativeSecp256k1.c
- Add an - #includeimport at the top (if applicable)- If your secp256k1 functions are not already included, you will need to - #includethe header file (should be in the- secp256k1/includedirectory).
- Function signature - Your new function signature should begin with - SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_- followed by the secp256k1 function name where - _are replaced with- _1(it's a weird jni thing). Finally, you add a parameter list that begins with- (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l- and ends with any - jints in the case that any of the secp256k1 function inputs have variable length (such as public keys which can be either- 33or- 65bytes, or ECDSA signatures), and lastly any- jbooleans in case there is some flag like- compressedpassed in.- As an example that includes everything, if you are making a call to - secp256k1_pubkey_tweak_addwhich takes in public keys that could be- 33or- 65bytes and outputs a public key that will either be compressed or decompressed based on an input flag, then the function signature would be- SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen, jboolean compressed)
- Reading - unsigned char*inputs- It is now time to create pointers for each of the secp256k1 function inputs that where passed in via the - byteBufferObject. We must first read in the- Secp256k1Contextwith the line- secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;- and we can then initialize the first pointer to be the beginning of the - byteBufferObjectwith the line- unsigned char* firstArg = (*env)->GetDirectBufferAddress(env, byteBufferObject);- and subsequent arguments' pointers where the previous argument's length is known (say - 32bytes for example) can be instantiated using- unsigned char* prevArg = ... unsigned char* nextArg = (unsigned char*) (prevArg + 32);- and in the case that a previous argument has variable length, then a - jinthas been provided as an input and can be used instead, such as in the example- unsigned char* prevArg = ... unsigned char* nextArg = (unsigned char*) (prevArg + publen);- where - publenis a- jintpassed to this C function.- As an example that includes everything, consider the function - secp256k1_ecdsa_verifywhich takes as input a- 32byte message, a variable length signature and a public key (of length- 33or- 65bytes). Our function will begin with- { 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*) (sigdata + siglen);- where - siglenis a- jintpassed into the C function.
- Initialize variables - Next we must declare all variables. We put all decelerations here as it is required by the C framework used by - libsecp256k1that definitions and assignments/function calls cannot be interleaved.- Specifically you will need to declare any secp256k1 specific structs here as well as all outputs (such as - jobjectArraysand- jByteArrays). Generally speaking this will include all inputs which are not raw data (public keys, signatures, etc). Lastly, you will also have an- int retwhich will store- 0if an error occurred and- 1otherwise.- As an example that includes everything, consider again the function - secp256k1_pubkey_tweak_addhas the following declarations- jobjectArray retArray; jbyteArray pubArray, intsByteArray; unsigned char intsarray[2]; unsigned char outputSer[65]; size_t outputLen = 65; secp256k1_pubkey pubkey; int ret;- Where - retArrayis eventually going to be the data returned, which will contain the- jbyteArrays- pubArrayand- intsByteArray, which will contain- outputSerand- intsarrayrespectively. Lastly- pubkeywill store a deserialized- secp256k1_pubkeycorresponding to the input- unsigned char*public key.
- Parse inputs when applicable - In the case where there are - unsigned char*inputs which need to be deserialized into secp256k1 structs, this is done now. As an example,- secp256k1_pubkey_tweak_addtakes a public key as input:- unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject);- where a - jint publenis passed in as a function parameter. This function already has a declaration for- secp256k1_pubkey pubkey;. The first call made after the above declarations is- ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen)- and if further parsing is necessary, it is put inside of - if (ret) { ret = [further parsing here] }.
- Make calls to secp256k1 functions to instantiate outputs - It is finally time to actually call the secp256k1 function we are binding to the jni! This is done by simply calling - ret = [call to secp function here];or- if (ret) { ret = [secp function call] };if there were any inputs that needed to be parsed. Note that some secp256k1 functions return outputs by populating variables you should have declared and for which pointers are passed as inputs, while other functions will mutate their inputs rather than returning outputs.
- Serialize variable length outputs if applicable - When dealing with variable length outputs such as signatures, you will likely need to serialize these outputs. This is done by having already instantiated such a variable as - unsigned char outputSer[72]; size_t outputLen = 72;- where in this case - 72is an upper bound on signature length. With these variables existing (as well as a- secp256k1_ecdsa_signature sigwhich has been populated), we call a secp256k1 serialization function to populate- outputSerand- outputLenfrom- sig- if(ret) { int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx, outputSer, &outputLen, &sig); (void)ret2; }- As you can see, in this case we do not which to alter the value returned in - retif serialization fails. If we did then- ret2would not be introduced and we would instead do- ret = [serialize].
- Populate return array when applicable - We now begin translating our serialized results back into Java entities. If you are returning any - ints containing meta-data (usually- retis included here, as are the variable lengths of outputs when applicable), you will want an- unsigned char intsarray[n]to be already declared where- nis the number of pieces of meta-data. For example, in- secp256k1_ecdsa_sign, we wish to return whether there were any errors (stored in- int ret) and the output signature's length,- size_t outputLen. Hence we have an- unsigned char intsarray[2]and we populate it as follows- intsarray[0] = outputLen; intsarray[1] = ret;- Next we populate the - jobjectArraywe wish to return, this will always begin with a call- retArray = (*env)->NewObjectArray(env, 2, (*env)->FindClass(env, "[B"), (*env)->NewByteArray(env, 1));- to instantiate an empty return array. Next we instantiate our - jbyteArrays with calls to- myByteArray = (*env)->NewByteArray(env, len);- where - myByteArrayis replaced with a real name (such as- intsByteArray) and- lenis replaced with the length of this array (either a constant or a populated- size_tvariable). Next we populate this array with our data by calling- (*env)->SetByteArrayRegion(env, myByteArray, 0, len, (jbyte*)myData);- where - myDatais a C array of- unsigned char(of length- len). Lastly, we place- myByteArrayinto its place in- retArraywith- (*env)->SetObjectArrayElement(env, retArray, index, myByteArray);- where - indexis a constant (- 0,- 1,- 2, etc.) for the index of- myByteArraywithin- retArray. Note that you should follow our conventions and have index- 0contain the actual data to be returned and index- 1(and onward) contain any meta-data.- Please note that if you wish not to return such meta-data (such as if you wish to return only a - boolean), then none of the code in this subsection is required
- void - classObject- Once we are ready to return, we first void the input - classObjectby making the call- (void)classObject;
- Return array when applicable, - retwhen applicable- Lastly, we return - retArrayin the case where we wish to return a- byte[], or- retin the case that we wish to return a- boolean.
Adding to src/java/org_bitcoin_NativeSecp256k1.h
Once your function is defined in src/java/org_bitcoin_NativeSecp256k1.c, you must define them in the corresponding header files by simply copying the function signature but without parameter names. For example, if secp256k1_pubkey_tweak_add has the function signature
SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
  (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen, jboolean compressed)
then in the header file we include
/*
 * Class:     org_bitcoin_NativeSecp256k1
 * Method:    secp256k1_pubkey_tweak_add
 * Signature: (Ljava/nio/ByteBuffer;JI)[[B
 */
SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
  (JNIEnv *, jclass, jobject, jlong, jint, jboolean);
Adding to src/java/org/bitcoin/NativeSecp256k1.java
We are now done writing C code! We have completed an interface in C for the JNI to hook up to. However, we must now write the corresponding Java code which hides the Java to C (and back) conversions from other Java code. We accomplish this with a class of static methods called NativeSecp256k1.
- Add - private static nativesecp256k1 function- We begin by adding a - private static nativemethod at the bottom of the file corresponding to our secp256k1 function. Notice that the syntax for- nativemethods is similar to that of Java abstract interface methods where instead of providing an implementation we simply end with a semi-colon.- For functions returning - booleans, we have their- nativemethods return- int(will be- 0or- 1). Otherwise, for functions returning- byte[]s, we have their- nativemethods return- byte[][](two dimensional array to allow for meta-data).
- Method signature - Next we add a method to the - NativeSecp256k1class- public static byte[] myFunc(byte[] input1, byte[] input2, boolean input3) throws AssertFailException- where - booleancould also be the return type instead of- byte[].
- checkArguments- We begin implementing this function by checking the input argument lengths using the - checkArumentfunction- checkArgument(input1.length == 32 && (input2.length == 33 || input2.length == 65));
- Initialize - ByteBuffer- We now initialize the - ByteBufferwhich we will be passing through the JNI as an input. This is done with a call to- ByteBuffer byteBuff = nativeECDSABuffer.get();- followed by allocation when necessary as follows - if (byteBuff == null || byteBuff.capacity() < input1.length + input2.length) { byteBuff = ByteBuffer.allocateDirect(input1.length + input2.length); byteBuff.order(ByteOrder.nativeOrder()); nativeECDSABuffer.set(byteBuff); }- where - input1.length + input2.lengthis replaced by whatever the total- ByteBufferlength needed.
- Fill - ByteBuffer- We now populate the - ByteBufferas follows- byteBuff.rewind(); byteBuff.put(input1); byteBuff.put(input2);- where generally, you will - rewind()and then- put()all inputs (in order).
- Make - nativecall- It is now time to make a call to our - nativeC function.- In the case where we are returning a - byte[], this is done by first declaring a- byte[][]to store the output and then locking the read lock,- r. Then we call the- nativefunction within a- tryclause which releases the lock in the- finallyclause.- byte[][] retByteArray; r.lock(); try { retByteArray = secp256k1_my_call(byteBuff, Secp256k1Context.getContext(), input3); } finally { r.unlock(); }- In the case where we are returning a - boolean, simply make the call in the- tryand compare the output to- 1like so- r.lock(); try { return secp256k1_my_bool_call(byteBuff, Secp256k1Context.getContext()) == 1; } finally { r.unlock(); }- If this is the case, you are now done and can ignore the following steps. 
- Parse outputs - retByteArrayshould now be populated and we want to read its two parts (data and meta-data). Getting the data should be as easy as- byte[] resultArr = retByteArr[0];- while for each piece of meta-data, you can read the corresponding - intas follows- int metaVal = new BigInteger(new byte[] { retByteArray[1][index] }).intValue();- where - indexis replaced with the index in the meta-data array.
- Validate outputs - In the case where we now have meta-data, we validate it with calls to - assertEquals.
- Return output - Finally, we return - resultArr.
Adding to src/java/org/bitcoin/NativeSecp256k1Test.java
I normally first build the C binaries and add to Bitcoin-S before coming back to this section because I use sbt core/console to generate values and make calls below, but this is not a requirement.
- Generate values and make calls to - org.bitcoin.NativeSecp256k1to generate inputs and their expected outputs
- Create regression unit tests with these values in NativeSecp256k1Test - Note that you can use - DatatypeConverter.parseHexBinaryto convert- Stringhex to a- byte[], and you can use- DatatypeConverter.printHexBinaryto convert a- byte[]to its- Stringhex. Lastly you will make assertions with calls to- assertEquals.
- Add test to - main
Adding to Bitcoin-S
- Translate - NativeSecp256k1and- NativeSecp256k1Testto jni project- By translate I mean to say that you must copy the functions from those files to the corresponding files in the - bitcoin-s/secp256k1jniproject. For tests this will require changing the methods to be non-- staticas well as adding the- @Testannotation above each method (rather than adding to a- mainmethod).
- Configure and build - secp256k1- You will need to go to the - bitcoin-s/secp256k1directory in a terminal and running the following where you may need to add to the- ./configurecommand if you are introducing a new module.- For Linux or OSx (64-bit) - You will have to make sure - JAVA_HOMEis set, and build tools are installed, for Linux this requires:- echo JAVA_HOME sudo apt install build-essential autotools-dev libtool automake- and for Mac this requires: - brew install automake libtool- You should then be able to build - libsecp256k1with the following:- ./autogen.sh ./configure --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor make CFLAGS="-std=c99" make check make check-java- For Windows (64-bit) - Windows bindings are cross-built on Linux. You need to install the - mingwtoolchain and have- JAVA_HOMEpoint to a Windows JDK:- sudo apt install g++-mingw-w64-x86-64 sudo update-alternatives --config x86_64-w64-mingw32-g++- You should then be able to build - libsecp256k1with the following:- echo "LDFLAGS = -no-undefined" >> Makefile.am ./configure --host=x86_64-w64-mingw32 --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor && make clean && make CFLAGS="-std=c99"- There may be some errors that can be ignored: - Could not determine the host path corresponding to
- redeclared without dllimport attribute: previous dllimport ignored
 
- Copy binaries into bitcoin-s natives for your system - You have now built the C binaries for your JNI bindings for your operating system and you should now find your operating system's directory in - bitcoin-s/secp256k1jni/nativesand replace its contents with the contents of- secp256k1/.libs(which contains the compiled binaries).
- Run - secp256k1jnitests- If you have not yet implemented tests, you should now be able to go back and do so as calls to - NativeSecp256k1should now succeed.- Once you have tests implemented, and assuming you've copied them correctly to the - bitcoin-s/secp256k1jniproject, you should be able to run them using- sbt secp256k1jni/test
Further Work to Enable Typed Invocations and Nice Tests
- Add new - NetworkElements where applicable- In the case where you are dealing in new kinds of data that are not yet defined in Bitcoin-S, you should add these as - case classes extending the- NetworkElementtrait, and give them companion objects extending- Factoryfor easy serialization and deserialization.- This step is not necessary if you are only dealing in raw data, - ECPrivateKeys,- ECPublicKeys, etc.
- Add new typed functions to relevant data types where applicable - In the case where your new function should be a static method, find a good - object(or introduce one) and give it a- defwhich takes in typed arguments and outputs typed arguments (using- ByteVectorin all places dealing with raw data rather than using- Array[Byte]). You will then implement these methods using calls to- NativeSecp256k1methods and getting the inputs into- Array[Byte]form by getting their- ByteVectors (usually through a call to- _.bytes) and then calling- _.toArray.- You will then need to take the data returned and deserialize it. - In the case where your new function belongs naturally as an action performed by some existing or newly introduced type, you can implement your new function as a call made by that class as described for the previous case but where the class will pass a serialized version of itself into the - NativeSecp256k1call.- It is often acceptable to implement the call in an - objectand then also add the call (via a call to the object, passing- this) to the interface of relevant types.
- Implement Bouncy Castle fallback in - BouncyCastleUtil.scalaif you can.
- Add unit and property-based tests. 
- If you implemented Bouncy Castle fallback, add tests to - BouncyCastleSecp256k1Testto compare implementations