diff --git a/service/pom.xml b/service/pom.xml
index 30a7c6595..41d7f497f 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -57,6 +57,11 @@
signal-client-java
0.10.0
+
+ org.whispersystems
+ curve25519-java
+ 0.5.0
+
io.dropwizard
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java
index be372d30e..26b06b6b6 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java
@@ -7,8 +7,8 @@ package org.whispersystems.textsecuregcm.auth;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECPrivateKey;
+import org.whispersystems.textsecuregcm.crypto.Curve;
+import org.whispersystems.textsecuregcm.crypto.ECPrivateKey;
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
import org.whispersystems.textsecuregcm.storage.Account;
@@ -46,12 +46,7 @@ public class CertificateGenerator {
}
byte[] certificate = builder.build().toByteArray();
- byte[] signature;
- try {
- signature = Curve.calculateSignature(privateKey, certificate);
- } catch (org.whispersystems.libsignal.InvalidKeyException e) {
- throw new InvalidKeyException(e);
- }
+ byte[] signature = Curve.calculateSignature(privateKey, certificate);
return SenderCertificate.newBuilder()
.setCertificate(ByteString.copyFrom(certificate))
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java
index 3d5c58131..eddd1d9d0 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java
@@ -8,8 +8,8 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECPrivateKey;
+import org.whispersystems.textsecuregcm.crypto.Curve;
+import org.whispersystems.textsecuregcm.crypto.ECPrivateKey;
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
import javax.validation.constraints.NotNull;
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java
new file mode 100644
index 000000000..65eb4257d
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+import org.whispersystems.curve25519.Curve25519;
+import org.whispersystems.curve25519.Curve25519KeyPair;
+
+import java.security.InvalidKeyException;
+
+import static org.whispersystems.curve25519.Curve25519.BEST;
+
+public class Curve {
+
+ public static final int DJB_TYPE = 0x05;
+
+ public static ECKeyPair generateKeyPair() {
+ Curve25519KeyPair keyPair = Curve25519.getInstance(BEST).generateKeyPair();
+
+ return new ECKeyPair(new DjbECPublicKey(keyPair.getPublicKey()),
+ new DjbECPrivateKey(keyPair.getPrivateKey()));
+ }
+
+ public static ECPublicKey decodePoint(byte[] bytes, int offset)
+ throws InvalidKeyException
+ {
+ if (bytes == null || bytes.length - offset < 1) {
+ throw new InvalidKeyException("No key type identifier");
+ }
+
+ int type = bytes[offset] & 0xFF;
+
+ switch (type) {
+ case Curve.DJB_TYPE:
+ if (bytes.length - offset < 33) {
+ throw new InvalidKeyException("Bad key length: " + bytes.length);
+ }
+
+ byte[] keyBytes = new byte[32];
+ System.arraycopy(bytes, offset+1, keyBytes, 0, keyBytes.length);
+ return new DjbECPublicKey(keyBytes);
+ default:
+ throw new InvalidKeyException("Bad key type: " + type);
+ }
+ }
+
+ public static ECPrivateKey decodePrivatePoint(byte[] bytes) {
+ return new DjbECPrivateKey(bytes);
+ }
+
+ public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
+ throws InvalidKeyException
+ {
+ if (publicKey == null) {
+ throw new InvalidKeyException("public value is null");
+ }
+
+ if (privateKey == null) {
+ throw new InvalidKeyException("private value is null");
+ }
+
+ if (publicKey.getType() != privateKey.getType()) {
+ throw new InvalidKeyException("Public and private keys must be of the same type!");
+ }
+
+ if (publicKey.getType() == DJB_TYPE) {
+ return Curve25519.getInstance(BEST)
+ .calculateAgreement(((DjbECPublicKey) publicKey).getPublicKey(),
+ ((DjbECPrivateKey) privateKey).getPrivateKey());
+ } else {
+ throw new InvalidKeyException("Unknown type: " + publicKey.getType());
+ }
+ }
+
+ public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message)
+ throws InvalidKeyException
+ {
+ if (signingKey == null || message == null) {
+ throw new InvalidKeyException("Values must not be null");
+ }
+
+ if (signingKey.getType() == DJB_TYPE) {
+ return Curve25519.getInstance(BEST)
+ .calculateSignature(((DjbECPrivateKey) signingKey).getPrivateKey(), message);
+ } else {
+ throw new InvalidKeyException("Unknown type: " + signingKey.getType());
+ }
+ }
+
+ public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature)
+ throws InvalidKeyException
+ {
+ if (signingKey.getType() == DJB_TYPE) {
+ return Curve25519.getInstance(BEST)
+ .verifySignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature);
+ } else {
+ throw new InvalidKeyException("Unknown type: " + signingKey.getType());
+ }
+ }
+
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java
new file mode 100644
index 000000000..47ffc84bb
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+public class DjbECPrivateKey implements ECPrivateKey {
+
+ private final byte[] privateKey;
+
+ DjbECPrivateKey(byte[] privateKey) {
+ this.privateKey = privateKey;
+ }
+
+ @Override
+ public byte[] serialize() {
+ return privateKey;
+ }
+
+ @Override
+ public int getType() {
+ return Curve.DJB_TYPE;
+ }
+
+ public byte[] getPrivateKey() {
+ return privateKey;
+ }
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java
new file mode 100644
index 000000000..a24a96e35
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+import org.whispersystems.textsecuregcm.util.ByteUtil;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+
+public class DjbECPublicKey implements ECPublicKey {
+
+ private final byte[] publicKey;
+
+ DjbECPublicKey(byte[] publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ @Override
+ public byte[] serialize() {
+ byte[] type = {Curve.DJB_TYPE};
+ return ByteUtil.combine(type, publicKey);
+ }
+
+ @Override
+ public int getType() {
+ return Curve.DJB_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ if (!(other instanceof DjbECPublicKey)) return false;
+
+ DjbECPublicKey that = (DjbECPublicKey)other;
+ return Arrays.equals(this.publicKey, that.publicKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(publicKey);
+ }
+
+ @Override
+ public int compareTo(ECPublicKey another) {
+ return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey));
+ }
+
+ public byte[] getPublicKey() {
+ return publicKey;
+ }
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java
new file mode 100644
index 000000000..ad4120d22
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+public class ECKeyPair {
+
+ private final ECPublicKey publicKey;
+ private final ECPrivateKey privateKey;
+
+ ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) {
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ }
+
+ public ECPublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public ECPrivateKey getPrivateKey() {
+ return privateKey;
+ }
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java
new file mode 100644
index 000000000..bac0d3234
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+public interface ECPrivateKey {
+ public byte[] serialize();
+ public int getType();
+}
+
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java
new file mode 100644
index 000000000..5511b0aaf
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2013-2020 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.crypto;
+
+public interface ECPublicKey extends Comparable {
+
+ public static final int KEY_SIZE = 33;
+
+ public byte[] serialize();
+
+ public int getType();
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java
index 118cf0a9a..f3e25ccd4 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java
@@ -10,9 +10,9 @@ import com.google.protobuf.ByteString;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECKeyPair;
-import org.whispersystems.libsignal.ecc.ECPrivateKey;
+import org.whispersystems.textsecuregcm.crypto.Curve;
+import org.whispersystems.textsecuregcm.crypto.ECKeyPair;
+import org.whispersystems.textsecuregcm.crypto.ECPrivateKey;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import java.io.IOException;
@@ -91,12 +91,7 @@ public class CertificateCommand extends Command {
.build()
.toByteArray();
- byte[] signature;
- try {
- signature = Curve.calculateSignature(key, certificate);
- } catch (org.whispersystems.libsignal.InvalidKeyException e) {
- throw new InvalidKeyException(e);
- }
+ byte[] signature = Curve.calculateSignature(key, certificate);
byte[] signedCertificate = MessageProtos.ServerCertificate.newBuilder()
.setCertificate(ByteString.copyFrom(certificate))
diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java
index f5db90382..482ec7178 100644
--- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java
+++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java
@@ -6,7 +6,7 @@
package org.whispersystems.textsecuregcm.auth;
import org.junit.Test;
-import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.textsecuregcm.crypto.Curve;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java
index c01c11f97..1a8026b0a 100644
--- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java
+++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java
@@ -28,12 +28,12 @@ import org.signal.zkgroup.auth.AuthCredential;
import org.signal.zkgroup.auth.AuthCredentialResponse;
import org.signal.zkgroup.auth.ClientZkAuthOperations;
import org.signal.zkgroup.auth.ServerZkAuthOperations;
-import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
import org.whispersystems.textsecuregcm.controllers.CertificateController;
+import org.whispersystems.textsecuregcm.crypto.Curve;
import org.whispersystems.textsecuregcm.entities.DeliveryCertificate;
import org.whispersystems.textsecuregcm.entities.GroupCredentials;
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;