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

View file

@ -2,9 +2,8 @@ package com.sparrowwallet.drongo.crypto;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
import org.bitcoin.Secp256k1Context;
import org.bitcoinj.secp.api.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
@ -280,11 +279,11 @@ public class ECKey {
}
if(Secp256k1Context.isEnabled()) {
try {
byte[] pubKeyBytes = NativeSecp256k1.computePubkey(Utils.bigIntegerToBytes(privKey, 32), false);
LazyECPoint lazyECPoint = new LazyECPoint(CURVE.getCurve(), pubKeyBytes);
try(Secp256k1 secp = Secp256k1.get()) {
P256k1PubKey pubkey = secp.ecPubKeyCreate(new BigIntegerP256k1PrivKey(privKey));
LazyECPoint lazyECPoint = new LazyECPoint(CURVE.getCurve(), pubkey.getCompressed());
return lazyECPoint.get();
} catch(NativeSecp256k1Util.AssertFailException e) {
} catch(Exception 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");
}
ECDSASignature signature;
if(!Secp256k1Context.isEnabled()) {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
ECDSASignature signature = null;
Integer counter = null;
do {
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(input.getBytes());
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
counter = (counter == null ? 1 : counter+1);
} while(!signature.hasLowR());
try(Secp256k1 secp = Secp256k1.get()) {
do {
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(input.getBytes());
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);
} while(!signature.hasLowR());
} catch(Exception e) {
log.error("Error signing ecdsa", e);
}
return signature;
}
@ -411,10 +428,10 @@ public class ECKey {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
try {
byte[] sigBytes = NativeSecp256k1.schnorrSign(input.getBytes(), Utils.bigIntegerToBytes(priv, 32), new byte[32]);
try(Secp256k1 secp = Secp256k1.get()) {
byte[] sigBytes = secp.schnorrSigSign32(input.getBytes(), secp.ecKeyPairCreate(new BigIntegerP256k1PrivKey(priv)));
return SchnorrSignature.decode(sigBytes);
} catch(NativeSecp256k1Util.AssertFailException e) {
} catch(Exception e) {
log.error("Error signing schnorr", e);
}
@ -883,4 +900,22 @@ public class ECKey {
public interface ECDSAHashSigner {
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.protocol.TransactionSignature;
import org.bitcoin.NativeSecp256k1;
import org.bitcoin.NativeSecp256k1Util;
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.LoggerFactory;
@ -70,9 +71,9 @@ public class SchnorrSignature {
throw new IllegalStateException("libsecp256k1 is not enabled");
}
try {
return NativeSecp256k1.schnorrVerify(encode(), data, pub);
} catch(NativeSecp256k1Util.AssertFailException e) {
try(Secp256k1 secp = Secp256k1.get()) {
return secp.schnorrSigVerify(encode(), data, new ByteArrayP256K1XOnlyPubKey(pub)).get();
} catch(Exception e) {
log.error("Error verifying schnorr signature", e);
}
@ -95,4 +96,22 @@ public class SchnorrSignature {
public int hashCode() {
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.classic;
requires json.simple;
requires org.bitcoinj.secp.api;
exports com.sparrowwallet.drongo;
exports com.sparrowwallet.drongo.psbt;
exports com.sparrowwallet.drongo.protocol;

View file

@ -9,17 +9,11 @@ import java.io.IOException;
public class Secp256k1Context {
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);
static { // static initializer
enabled = loadLibrary();
if(enabled) {
context = secp256k1_init_context();
} else {
context = -1;
}
}
public static boolean isEnabled() {
@ -29,7 +23,7 @@ public class Secp256k1Context {
public static long getContext() {
if (!enabled)
return -1; // sanity check
return context;
throw new UnsupportedOperationException();
}
private static boolean loadLibrary() {
@ -55,6 +49,4 @@ public class Secp256k1Context {
return false;
}
private static native long secp256k1_init_context();
}