Skip to content

Commit

Permalink
implement exported parse function in JNI
Browse files Browse the repository at this point in the history
This function was already exported but not implemented.

renamed `parse` to `decompress`

a little more consistency in method comments

removed dependency on google guava library

It seemed a little overkill given that we only use one trivial function
from this library.

fixed github link bitcoin->bitcoin-core

tests: removed guava dependency + cleanup

added pub key parsing tests

add `compressed` arg to methods returning a pubkey

removed unreachable null check

fixup: typo in tests

removed UnsatisfiedLinkError printed to stdout

removed download of guava lib in travis build

removed useless 'throws'

re-implemented 'isValidPubKey' by calling 'decompress'

tentatively remove guava from makefile.am
  • Loading branch information
pm47 authored and nkohen committed Apr 14, 2020
1 parent 8808e84 commit 3e2b1cf
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 154 deletions.
9 changes: 1 addition & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ addons:
compiler:
- clang
- gcc
cache:
directories:
- src/java/guava/
env:
global:
global:<<<<<<< HEAD
- FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ECMULTGENPRECISION=auto ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no SCHNORRSIG=no EXPERIMENTAL=no CTIMETEST=yes BENCH=yes SECP256K1_BENCH_ITERS=2 JNI=no
- GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar
matrix:
- SCALAR=32bit RECOVERY=yes
- SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes SCHNORRSIG=yes
Expand Down Expand Up @@ -84,9 +80,6 @@ matrix:
env: # The same as above but without endomorphism.
- BIGNUM=no ENDOMORPHISM=no ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes
- VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD=

before_install: mkdir -p `dirname $GUAVA_JAR`
install: if [ ! -f $GUAVA_JAR ]; then wget $GUAVA_URL -O $GUAVA_JAR; fi
before_script: ./autogen.sh

script:
Expand Down
11 changes: 3 additions & 8 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,15 @@ JAVA_FILES= \

if USE_JNI

$(JAVA_GUAVA):
@echo Guava is missing. Fetch it via: \
wget https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar -O $(@)
@false

.stamp-java: $(JAVA_FILES)
@echo Compiling $^
$(AM_V_at)$(CLASSPATH_ENV) javac $^
$(AM_V_at) javac $^
@touch $@

if USE_TESTS

check-java: libsecp256k1.la $(JAVA_GUAVA) .stamp-java
$(AM_V_at)java -Djava.library.path="./:./src:./src/.libs:.libs/" -cp "$(JAVA_GUAVA):$(JAVAROOT)" $(JAVAORG)/NativeSecp256k1Test
check-java: libsecp256k1.la .stamp-java
$(AM_V_at)java -Djava.library.path="./:./src:./src/.libs:.libs/" -cp "$(JAVAROOT)" $(JAVAORG)/NativeSecp256k1Test

endif
endif
Expand Down
149 changes: 98 additions & 51 deletions src/java/org/bitcoin/NativeSecp256k1.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@
import java.nio.ByteOrder;

import java.math.BigInteger;
import com.google.common.base.Preconditions;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.bitcoin.NativeSecp256k1Util.*;

/**
* <p>This class holds native methods to handle ECDSA verification.</p>
*
* <p>You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1</p>
* <p>You can find an example library that can be used for this at https://github.com/bitcoin-core/secp256k1</p>
*
* <p>To build secp256k1 for use with bitcoinj, run
* `./configure --enable-jni --enable-experimental --enable-module-ecdh`
Expand All @@ -51,8 +50,8 @@ public class NativeSecp256k1 {
* @param signature The signature
* @param pub The public key which did the signing
*/
public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{
Preconditions.checkArgument(data.length == 32 && signature.length <= 520 && pub.length <= 520);
public static boolean verify(byte[] data, byte[] signature, byte[] pub) {
checkArgument(data.length == 32 && signature.length <= 520 && pub.length <= 520);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 520) {
Expand All @@ -65,8 +64,6 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A
byteBuff.put(signature);
byteBuff.put(pub);

byte[][] retByteArray;

r.lock();
try {
return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.length, pub.length) == 1;
Expand All @@ -79,13 +76,11 @@ public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws A
* libsecp256k1 Create an ECDSA signature.
*
* @param data Message hash, 32 bytes
* @param key Secret key, 32 bytes
*
* Return values
* @param sig byte array of signature
* @param seckey ECDSA Secret key, 32 bytes
* @return sig byte array of signature
*/
public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{
Preconditions.checkArgument(data.length == 32 && sec.length <= 32);
public static byte[] sign(byte[] data, byte[] seckey) throws AssertFailException{
checkArgument(data.length == 32 && seckey.length <= 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + 32) {
Expand All @@ -95,7 +90,7 @@ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{
}
byteBuff.rewind();
byteBuff.put(data);
byteBuff.put(sec);
byteBuff.put(seckey);

byte[][] retByteArray;

Expand All @@ -116,12 +111,13 @@ public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{
}

/**
* libsecp256k1 Seckey Verify - returns 1 if valid, 0 if invalid
* libsecp256k1 Seckey Verify - Verifies an ECDSA secret key
*
* @param seckey ECDSA Secret key, 32 bytes
* @return true if valid, false if invalid
*/
public static boolean secKeyVerify(byte[] seckey) {
Preconditions.checkArgument(seckey.length == 32);
checkArgument(seckey.length == 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seckey.length) {
Expand All @@ -145,13 +141,11 @@ public static boolean secKeyVerify(byte[] seckey) {
* libsecp256k1 Compute Pubkey - computes public key from secret key
*
* @param seckey ECDSA Secret key, 32 bytes
*
* Return values
* @param pubkey ECDSA Public key, 33 or 65 bytes
* @param compressed Should the generated public key be compressed
* @return pubkey ECDSA Public key, 33 or 65 bytes
*/
//TODO add a 'compressed' arg
public static byte[] computePubkey(byte[] seckey) throws AssertFailException{
Preconditions.checkArgument(seckey.length == 32);
public static byte[] computePubkey(byte[] seckey, boolean compressed) throws AssertFailException{
checkArgument(seckey.length == 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seckey.length) {
Expand All @@ -166,7 +160,7 @@ public static byte[] computePubkey(byte[] seckey) throws AssertFailException{

r.lock();
try {
retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext());
retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext(), compressed);
} finally {
r.unlock();
}
Expand Down Expand Up @@ -201,22 +195,22 @@ public static long cloneContext() {
}

/**
* libsecp256k1 PrivKey Tweak-Mul - Tweak privkey by multiplying to it
* libsecp256k1 PrivKey Tweak-Mul - Tweak seckey by multiplying to it
*
* @param seckey ECDSA Secret key, 32 bytes
* @param tweak some bytes to tweak with
* @param seckey 32-byte seckey
*/
public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws AssertFailException{
Preconditions.checkArgument(privkey.length == 32);
public static byte[] privKeyTweakMul(byte[] seckey, byte[] tweak) throws AssertFailException{
checkArgument(seckey.length == 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length);
if (byteBuff == null || byteBuff.capacity() < seckey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(seckey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(privkey);
byteBuff.put(seckey);
byteBuff.put(tweak);

byte[][] retByteArray;
Expand All @@ -240,22 +234,22 @@ public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws Assert
}

/**
* libsecp256k1 PrivKey Tweak-Add - Tweak privkey by adding to it
* libsecp256k1 PrivKey Tweak-Add - Tweak seckey by adding to it
*
* @param seckey ECDSA Secret key, 32 bytes
* @param tweak some bytes to tweak with
* @param seckey 32-byte seckey
*/
public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws AssertFailException{
Preconditions.checkArgument(privkey.length == 32);
public static byte[] privKeyTweakAdd(byte[] seckey, byte[] tweak) throws AssertFailException{
checkArgument(seckey.length == 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length);
if (byteBuff == null || byteBuff.capacity() < seckey.length + tweak.length) {
byteBuff = ByteBuffer.allocateDirect(seckey.length + tweak.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(privkey);
byteBuff.put(seckey);
byteBuff.put(tweak);

byte[][] retByteArray;
Expand All @@ -281,11 +275,12 @@ public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws Assert
/**
* libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it
*
* @param pubkey ECDSA Public key, 33 or 65 bytes
* @param tweak some bytes to tweak with
* @param pubkey 32-byte seckey
* @param compressed should the output public key be compressed
*/
public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFailException{
Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65);
public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak, boolean compressed) throws AssertFailException{
checkArgument(pubkey.length == 33 || pubkey.length == 65);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) {
Expand All @@ -300,7 +295,7 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFa
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length);
retByteArray = secp256k1_pubkey_tweak_add(byteBuff, Secp256k1Context.getContext(), pubkey.length, compressed);
} finally {
r.unlock();
}
Expand All @@ -320,11 +315,12 @@ public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFa
/**
* libsecp256k1 PubKey Tweak-Mul - Tweak pubkey by multiplying to it
*
* @param pubkey ECDSA Public key, 33 or 65 bytes
* @param tweak some bytes to tweak with
* @param pubkey 32-byte seckey
* @param compressed should the output public key be compressed
*/
public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFailException{
Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65);
public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak, boolean compressed) throws AssertFailException{
checkArgument(pubkey.length == 33 || pubkey.length == 65);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) {
Expand All @@ -339,7 +335,7 @@ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFa
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length);
retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length, compressed);
} finally {
r.unlock();
}
Expand All @@ -356,14 +352,65 @@ public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFa
return pubArr;
}

/**
* libsecp256k1 Decompress - Parse and decompress a variable-length pub key
*
* @param pubkey ECDSA Public key, 33 or 65 bytes
*/
public static byte[] decompress(byte[] pubkey) throws AssertFailException{
checkArgument(pubkey.length == 33 || pubkey.length == 65);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < pubkey.length) {
byteBuff = ByteBuffer.allocateDirect(pubkey.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
byteBuff.rewind();
byteBuff.put(pubkey);

byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_ec_pubkey_decompress(byteBuff, Secp256k1Context.getContext(), pubkey.length);
} finally {
r.unlock();
}

byte[] pubArr = retByteArray[0];

int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF;
int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue();

assertEquals(pubArr.length, pubLen, "Got bad pubkey length.");

assertEquals(retVal, 1, "Failed return value check.");

return pubArr;
}

/**
* libsecp256k1 IsValidPubKey - Checks if a pubkey is valid
*
* @param pubkey ECDSA Public key, 33 or 65 bytes
*/
public static boolean isValidPubKey(byte[] pubkey) {
try {
decompress(pubkey);
return true;
} catch (Throwable e) {
return false;
}
}

/**
* libsecp256k1 create ECDH secret - constant time ECDH calculation
*
* @param seckey byte array of secret key used in exponentiaion
* @param pubkey byte array of public key used in exponentiaion
*/
public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws AssertFailException{
Preconditions.checkArgument(seckey.length <= 32 && pubkey.length <= 65);
checkArgument(seckey.length <= 32 && pubkey.length <= 65);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < 32 + pubkey.length) {
Expand Down Expand Up @@ -397,8 +444,8 @@ public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws Asser
*
* @param seed 32-byte random seed
*/
public static synchronized boolean randomize(byte[] seed) throws AssertFailException{
Preconditions.checkArgument(seed.length == 32 || seed == null);
public static synchronized boolean randomize(byte[] seed) {
checkArgument(seed.length == 32);

ByteBuffer byteBuff = nativeECDSABuffer.get();
if (byteBuff == null || byteBuff.capacity() < seed.length) {
Expand All @@ -425,9 +472,9 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep

private static native byte[][] secp256k1_privkey_tweak_mul(ByteBuffer byteBuff, long context);

private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen);
private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen, boolean compressed);

private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen);
private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen, boolean compressed);

private static native void secp256k1_destroy_context(long context);

Expand All @@ -437,9 +484,9 @@ public static synchronized boolean randomize(byte[] seed) throws AssertFailExcep

private static native int secp256k1_ec_seckey_verify(ByteBuffer byteBuff, long context);

private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context);
private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context, boolean compressed);

private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen);
private static native byte[][] secp256k1_ec_pubkey_decompress(ByteBuffer byteBuff, long context, int inputLen);

private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen);

Expand Down
Loading

0 comments on commit 3e2b1cf

Please sign in to comment.