implementation of secp256k1-jdk 0.0.1

This commit is contained in:
Craig Raw 2024-08-22 09:21:17 +02:00
parent 0e08478294
commit 14a1a4ec8f
9 changed files with 98 additions and 43 deletions

View file

@ -24,9 +24,15 @@ if(os.macOsX) {
repositories { repositories {
mavenCentral() mavenCentral()
flatDir {
dirs 'libs'
}
} }
dependencies { dependencies {
implementation files('libs/secp-api-0.0.1.jar')
implementation files('libs/secp-ffm-0.0.1.jar')
implementation ('org.jspecify:jspecify:1.0.0')
implementation ('com.googlecode.json-simple:json-simple:1.1.1') { implementation ('com.googlecode.json-simple:json-simple:1.1.1') {
exclude group: 'junit', module: 'junit' exclude group: 'junit', module: 'junit'
} }

BIN
libs/secp-api-0.0.1.jar Normal file

Binary file not shown.

BIN
libs/secp-ffm-0.0.1.jar Normal file

Binary file not shown.

View file

@ -1,12 +1,13 @@
package com.sparrowwallet.drongo.crypto; package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.SigHash; import com.sparrowwallet.drongo.protocol.SigHash;
import com.sparrowwallet.drongo.protocol.SignatureDecodeException; import com.sparrowwallet.drongo.protocol.SignatureDecodeException;
import com.sparrowwallet.drongo.protocol.TransactionSignature; import com.sparrowwallet.drongo.protocol.TransactionSignature;
import com.sparrowwallet.drongo.protocol.VerificationException; import com.sparrowwallet.drongo.protocol.VerificationException;
import org.bitcoin.Secp256k1Context;
import org.bitcoinj.secp.api.*;
import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.util.Properties; import org.bouncycastle.util.Properties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -131,15 +132,16 @@ public class ECDSASignature {
* @param pub The public key bytes to use. * @param pub The public key bytes to use.
*/ */
public boolean verify(byte[] data, byte[] pub) { public boolean verify(byte[] data, byte[] pub) {
ECDSASigner signer = new ECDSASigner(); if(!Secp256k1Context.isEnabled()) {
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); throw new IllegalStateException("libsecp256k1 is not enabled");
signer.init(false, params); }
try {
return signer.verifySignature(data, r, s); try(Secp256k1 secp = Secp256k1.get()) {
} catch (NullPointerException e) { byte[] sigBytes = Utils.concat(Utils.bigIntegerToBytes(r, 32), Utils.bigIntegerToBytes(s, 32));
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures SignatureData sig = secp.ecdsaSignatureParseCompact(() -> sigBytes).get();
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread. return secp.ecdsaVerify(sig, data, secp.ecPubKeyParse(() -> pub).get()).get();
log.error("Caught NPE inside bouncy castle", e); } catch(Exception e) {
log.error("Error verifying ecdsa", e);
return false; return false;
} }
} }

View file

@ -2,9 +2,8 @@ package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context; import org.bitcoin.Secp256k1Context;
import org.bitcoinj.secp.api.*;
import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter; import org.bouncycastle.asn1.x9.X9IntegerConverter;
@ -280,11 +279,11 @@ public class ECKey {
} }
if(Secp256k1Context.isEnabled()) { if(Secp256k1Context.isEnabled()) {
try { try(Secp256k1 secp = Secp256k1.get()) {
byte[] pubKeyBytes = NativeSecp256k1.computePubkey(Utils.bigIntegerToBytes(privKey, 32), false); P256k1PubKey pubkey = secp.ecPubKeyCreate(new BigIntegerP256k1PrivKey(privKey));
LazyECPoint lazyECPoint = new LazyECPoint(CURVE.getCurve(), pubKeyBytes); LazyECPoint lazyECPoint = new LazyECPoint(CURVE.getCurve(), pubkey.getCompressed());
return lazyECPoint.get(); return lazyECPoint.get();
} catch(NativeSecp256k1Util.AssertFailException e) { } catch(Exception e) {
log.error("Error computing public key from private", e); log.error("Error computing public key from private", e);
} }
} }
@ -385,16 +384,34 @@ public class ECKey {
throw new IllegalArgumentException("Private key cannot be null"); throw new IllegalArgumentException("Private key cannot be null");
} }
ECDSASignature signature; if(!Secp256k1Context.isEnabled()) {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
ECDSASignature signature = null;
Integer counter = null; Integer counter = null;
try(Secp256k1 secp = Secp256k1.get()) {
do { do {
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter)); ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE); ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
signer.init(true, privKey); signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(input.getBytes()); BigInteger[] components = signer.generateSignature(input.getBytes());
signature = new ECDSASignature(components[0], components[1]).toCanonicalised(); signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
//No way to specify counter as a parameter while grinding for low R
SignatureData sig = secp.ecdsaSign(input.getBytes(), new BigIntegerP256k1PrivKey(priv)).get();
CompressedSignatureData serialized_signature = secp.ecdsaSignatureSerializeCompact(sig).get();
BigInteger r = new BigInteger(1, Arrays.copyOfRange(serialized_signature.bytes(), 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(serialized_signature.bytes(),32, 64));
ECDSASignature signature2 = new ECDSASignature(r, s);
if(!signature.equals(signature2)) {
System.out.println("Signatures not equal " + signature.hasLowR() + " " + signature2.hasLowR() + " " + counter);
}
counter = (counter == null ? 1 : counter + 1); counter = (counter == null ? 1 : counter + 1);
} while(!signature.hasLowR()); } while(!signature.hasLowR());
} catch(Exception e) {
log.error("Error signing ecdsa", e);
}
return signature; return signature;
} }
@ -411,10 +428,10 @@ public class ECKey {
throw new IllegalStateException("libsecp256k1 is not enabled"); throw new IllegalStateException("libsecp256k1 is not enabled");
} }
try { try(Secp256k1 secp = Secp256k1.get()) {
byte[] sigBytes = NativeSecp256k1.schnorrSign(input.getBytes(), Utils.bigIntegerToBytes(priv, 32), new byte[32]); byte[] sigBytes = secp.schnorrSigSign32(input.getBytes(), secp.ecKeyPairCreate(new BigIntegerP256k1PrivKey(priv)));
return SchnorrSignature.decode(sigBytes); return SchnorrSignature.decode(sigBytes);
} catch(NativeSecp256k1Util.AssertFailException e) { } catch(Exception e) {
log.error("Error signing schnorr", e); log.error("Error signing schnorr", e);
} }
@ -883,4 +900,22 @@ public class ECKey {
public interface ECDSAHashSigner { public interface ECDSAHashSigner {
ECDSASignature sign(Sha256Hash hash); ECDSASignature sign(Sha256Hash hash);
} }
private static class BigIntegerP256k1PrivKey implements P256k1PrivKey {
private final BigInteger privKey;
public BigIntegerP256k1PrivKey(BigInteger privKey) {
this.privKey = privKey;
}
@Override
public byte[] getEncoded() {
return Utils.bigIntegerToBytes(privKey, 32);
}
@Override
public void destroy() {
}
}
} }

View file

@ -2,9 +2,10 @@ package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.TransactionSignature; import com.sparrowwallet.drongo.protocol.TransactionSignature;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context; import org.bitcoin.Secp256k1Context;
import org.bitcoinj.secp.api.P256K1XOnlyPubKey;
import org.bitcoinj.secp.api.Result;
import org.bitcoinj.secp.api.Secp256k1;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -70,9 +71,9 @@ public class SchnorrSignature {
throw new IllegalStateException("libsecp256k1 is not enabled"); throw new IllegalStateException("libsecp256k1 is not enabled");
} }
try { try(Secp256k1 secp = Secp256k1.get()) {
return NativeSecp256k1.schnorrVerify(encode(), data, pub); return secp.schnorrSigVerify(encode(), data, new ByteArrayP256K1XOnlyPubKey(pub)).get();
} catch(NativeSecp256k1Util.AssertFailException e) { } catch(Exception e) {
log.error("Error verifying schnorr signature", e); log.error("Error verifying schnorr signature", e);
} }
@ -95,4 +96,22 @@ public class SchnorrSignature {
public int hashCode() { public int hashCode() {
return Objects.hash(r, s); return Objects.hash(r, s);
} }
private static class ByteArrayP256K1XOnlyPubKey implements P256K1XOnlyPubKey {
private final byte[] pub;
public ByteArrayP256K1XOnlyPubKey(byte[] pub) {
this.pub = pub;
}
@Override
public BigInteger getX() {
return new BigInteger(1, pub);
}
@Override
public byte[] getSerialized() {
return pub;
}
}
} }

View file

@ -7,6 +7,7 @@ open module com.sparrowwallet.drongo {
requires ch.qos.logback.core; requires ch.qos.logback.core;
requires ch.qos.logback.classic; requires ch.qos.logback.classic;
requires json.simple; requires json.simple;
requires org.bitcoinj.secp.api;
exports com.sparrowwallet.drongo; exports com.sparrowwallet.drongo;
exports com.sparrowwallet.drongo.psbt; exports com.sparrowwallet.drongo.psbt;
exports com.sparrowwallet.drongo.protocol; exports com.sparrowwallet.drongo.protocol;

View file

@ -9,17 +9,11 @@ import java.io.IOException;
public class Secp256k1Context { public class Secp256k1Context {
private static final boolean enabled; // true if the library is loaded private static final boolean enabled; // true if the library is loaded
private static final long context; // ref to pointer to context obj
private static final Logger log = LoggerFactory.getLogger(Secp256k1Context.class); private static final Logger log = LoggerFactory.getLogger(Secp256k1Context.class);
static { // static initializer static { // static initializer
enabled = loadLibrary(); enabled = loadLibrary();
if(enabled) {
context = secp256k1_init_context();
} else {
context = -1;
}
} }
public static boolean isEnabled() { public static boolean isEnabled() {
@ -29,7 +23,7 @@ public class Secp256k1Context {
public static long getContext() { public static long getContext() {
if (!enabled) if (!enabled)
return -1; // sanity check return -1; // sanity check
return context; throw new UnsupportedOperationException();
} }
private static boolean loadLibrary() { private static boolean loadLibrary() {
@ -55,6 +49,4 @@ public class Secp256k1Context {
return false; return false;
} }
private static native long secp256k1_init_context();
} }