mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
implementation of secp256k1-jdk 0.0.1
This commit is contained in:
parent
0e08478294
commit
14a1a4ec8f
9 changed files with 98 additions and 43 deletions
|
@ -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
BIN
libs/secp-api-0.0.1.jar
Normal file
Binary file not shown.
BIN
libs/secp-ffm-0.0.1.jar
Normal file
BIN
libs/secp-ffm-0.0.1.jar
Normal file
Binary file not shown.
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
do {
|
try(Secp256k1 secp = Secp256k1.get()) {
|
||||||
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
|
do {
|
||||||
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
|
ECDSASigner signer = new ECDSASigner(new HMacDSANonceKCalculator(new SHA256Digest(), counter));
|
||||||
signer.init(true, privKey);
|
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, CURVE);
|
||||||
BigInteger[] components = signer.generateSignature(input.getBytes());
|
signer.init(true, privKey);
|
||||||
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
|
BigInteger[] components = signer.generateSignature(input.getBytes());
|
||||||
counter = (counter == null ? 1 : counter+1);
|
signature = new ECDSASignature(components[0], components[1]).toCanonicalised();
|
||||||
} while(!signature.hasLowR());
|
|
||||||
|
//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;
|
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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue