From dba1a9a2beb9d52ad9f08d42418b78fa7689ed5f Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 30 Oct 2024 13:04:20 +0200 Subject: [PATCH] add support for x25519 and secp256r1 keys --- .../java/com/sparrowwallet/drongo/Utils.java | 20 +++ .../drongo/crypto/Secp256r1Key.java | 39 ++++++ .../drongo/crypto/X25519Key.java | 127 ++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/Secp256r1Key.java create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/X25519Key.java diff --git a/src/main/java/com/sparrowwallet/drongo/Utils.java b/src/main/java/com/sparrowwallet/drongo/Utils.java index 554c279..9ae6c74 100644 --- a/src/main/java/com/sparrowwallet/drongo/Utils.java +++ b/src/main/java/com/sparrowwallet/drongo/Utils.java @@ -4,6 +4,8 @@ import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.protocol.ProtocolException; import com.sparrowwallet.drongo.protocol.Ripemd160; import com.sparrowwallet.drongo.protocol.Sha256Hash; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.params.KeyParameter; @@ -15,6 +17,9 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; public class Utils { @@ -338,6 +343,21 @@ public class Utils { return Sha256Hash.hash(buffer.array()); } + public static byte[] getRawKeyBytesFromPKCS8(PrivateKey pkcs8Key) { + try { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Key.getEncoded()); + PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(keySpec.getEncoded()); + return privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded(); + } catch(IOException e) { + throw new IllegalArgumentException("Error parsing private key", e); + } + } + + public static byte[] getRawKeyBytesFromX509(PublicKey x509Key) { + SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(x509Key.getEncoded()); + return spki.getPublicKeyData().getBytes(); + } + public static class LexicographicByteArrayComparator implements Comparator { @Override public int compare(byte[] left, byte[] right) { diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Secp256r1Key.java b/src/main/java/com/sparrowwallet/drongo/crypto/Secp256r1Key.java new file mode 100644 index 0000000..97bc32d --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Secp256r1Key.java @@ -0,0 +1,39 @@ +package com.sparrowwallet.drongo.crypto; + +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; + +public class Secp256r1Key { + private static final X9ECParameters CURVE_PARAMS = ECNamedCurveTable.getByName("P-256"); + + public static final ECDomainParameters CURVE; + + private final ECPoint point; + + static { + CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); + } + + public Secp256r1Key(byte[] publicKeyBytes) { + this.point = CURVE.getCurve().decodePoint(publicKeyBytes); + } + + public boolean verify(byte[] challenge, byte[] challengeSignature) { + ECDSASigner signer = new ECDSASigner(); + signer.init(false, new ECPublicKeyParameters(point, CURVE)); + + int halfLength = challengeSignature.length / 2; + byte[] r = new byte[halfLength]; + byte[] s = new byte[halfLength]; + System.arraycopy(challengeSignature, 0, r, 0, halfLength); + System.arraycopy(challengeSignature, halfLength, s, 0, halfLength); + + return signer.verifySignature(challenge, new BigInteger(1, r), new BigInteger(1, s)); + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/X25519Key.java b/src/main/java/com/sparrowwallet/drongo/crypto/X25519Key.java new file mode 100644 index 0000000..3b6f8a1 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/X25519Key.java @@ -0,0 +1,127 @@ +package com.sparrowwallet.drongo.crypto; + +import com.sparrowwallet.drongo.Utils; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.params.X25519PrivateKeyParameters; +import org.bouncycastle.crypto.params.X25519PublicKeyParameters; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.XECPrivateKey; +import java.security.interfaces.XECPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Optional; + +public class X25519Key { + private KeyPair keyPair; + private final AlgorithmParameterSpec ecSpec; + + public X25519Key() { + try { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("X25519"); + this.keyPair = keyPairGenerator.generateKeyPair(); + this.ecSpec = keyPairGenerator.generateKeyPair().getPrivate().getParams(); + } catch(NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public X25519Key(byte[] priv) { + this(); + + X25519PrivateKeyParameters privateKeyParams = new X25519PrivateKeyParameters(priv, 0); + X25519PublicKeyParameters publicKeyParams = privateKeyParams.generatePublicKey(); + + PrivateKey privateKey = new BouncyCastlePrivateKey(privateKeyParams); + PublicKey publicKey = new BouncyCastlePublicKey(publicKeyParams); + this.keyPair = new KeyPair(publicKey, privateKey); + } + + public KeyPair getKeyPair() { + return keyPair; + } + + public byte[] getRawPrivateKeyBytes() { + return Utils.getRawKeyBytesFromPKCS8(keyPair.getPrivate()); + } + + public byte[] getRawPublicKeyBytes() { + return Utils.getRawKeyBytesFromX509(keyPair.getPublic()); + } + + public class BouncyCastlePrivateKey implements XECPrivateKey { + private final X25519PrivateKeyParameters privateKeyParams; + + BouncyCastlePrivateKey(X25519PrivateKeyParameters privateKeyParams) { + this.privateKeyParams = privateKeyParams; + } + + @Override + public String getAlgorithm() { + return "X25519"; + } + + @Override + public String getFormat() { + return "RAW"; + } + + @Override + public byte[] getEncoded() { + return privateKeyParams.getEncoded(); + } + + @Override + public Optional getScalar() { + return Optional.of(getEncoded()); + } + + @Override + public AlgorithmParameterSpec getParams() { + return ecSpec; + } + } + + public class BouncyCastlePublicKey implements XECPublicKey { + private final X25519PublicKeyParameters publicKeyParams; + + BouncyCastlePublicKey(X25519PublicKeyParameters publicKeyParams) { + this.publicKeyParams = publicKeyParams; + } + + @Override + public String getAlgorithm() { + return "X25519"; + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + try { + ASN1ObjectIdentifier algOid = new ASN1ObjectIdentifier("1.3.101.110"); + AlgorithmIdentifier algId = new AlgorithmIdentifier(algOid); + SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algId, publicKeyParams.getEncoded()); + return spki.getEncoded(); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public BigInteger getU() { + return new BigInteger(1, publicKeyParams.getEncoded()); + } + + @Override + public AlgorithmParameterSpec getParams() { + return ecSpec; + } + } +}