diff --git a/build.gradle b/build.gradle index d9eff73..b7f32ec 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } diff --git a/libs/secp-api-0.0.1.jar b/libs/secp-api-0.0.1.jar new file mode 100644 index 0000000..9407c37 Binary files /dev/null and b/libs/secp-api-0.0.1.jar differ diff --git a/libs/secp-ffm-0.0.1.jar b/libs/secp-ffm-0.0.1.jar new file mode 100644 index 0000000..78eae64 Binary files /dev/null and b/libs/secp-ffm-0.0.1.jar differ diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ECDSASignature.java b/src/main/java/com/sparrowwallet/drongo/crypto/ECDSASignature.java index 0a7c6eb..9a8e465 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ECDSASignature.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ECDSASignature.java @@ -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; } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java index 676e193..e44232a 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java @@ -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() { + + } + } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/SchnorrSignature.java b/src/main/java/com/sparrowwallet/drongo/crypto/SchnorrSignature.java index 92ddb7b..49d44fc 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/SchnorrSignature.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/SchnorrSignature.java @@ -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; + } + } } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a733b58..4996985 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -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; diff --git a/src/main/java/org/bitcoin/Secp256k1Context.java b/src/main/java/org/bitcoin/Secp256k1Context.java index c5276d9..18844b2 100644 --- a/src/main/java/org/bitcoin/Secp256k1Context.java +++ b/src/main/java/org/bitcoin/Secp256k1Context.java @@ -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(); } diff --git a/src/main/resources/native/osx/aarch64/libsecp256k1.dylib b/src/main/resources/native/osx/aarch64/libsecp256k1.dylib index 02aa2ff..168fdac 100755 Binary files a/src/main/resources/native/osx/aarch64/libsecp256k1.dylib and b/src/main/resources/native/osx/aarch64/libsecp256k1.dylib differ