diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java
index d221c85c5faabdd9232a92643ee75d1d24194514..3b12c5cf75ae93f0b9fa0f84f549c88c39c0a3d1 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java
@@ -5,7 +5,7 @@ public interface CryptoConstants {
 	/**
 	 * The maximum length of an agreement public key in bytes.
 	 */
-	int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 65;
+	int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32;
 
 	/**
 	 * The maximum length of a signature public key in bytes.
diff --git a/bramble-core/build.gradle b/bramble-core/build.gradle
index af2d70bb945706b522fb358bc53d5e40c518920a..a6189c910fb2c1bd5d057ae65aa97e5f1761bc3e 100644
--- a/bramble-core/build.gradle
+++ b/bramble-core/build.gradle
@@ -12,6 +12,7 @@ dependencies {
 	implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
 	implementation 'org.bitlet:weupnp:0.1.4'
 	implementation 'net.i2p.crypto:eddsa:0.2.0'
+	implementation 'org.whispersystems:curve25519-java:0.4.1'
 
 	apt 'com.google.dagger:dagger-compiler:2.0.2'
 
@@ -53,6 +54,7 @@ dependencyVerification {
 			'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
 			'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
 			'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
+			'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
 	]
 }
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
index 4a0a3e4a6f8741c1278be10b24702671e413cebf..4161db31ab556f5183cd7476c25e1f64725ad1f6 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
@@ -14,15 +14,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.system.SecureRandomProvider;
 import org.briarproject.bramble.util.ByteUtils;
 import org.briarproject.bramble.util.StringUtils;
-import org.spongycastle.crypto.AsymmetricCipherKeyPair;
 import org.spongycastle.crypto.CryptoException;
 import org.spongycastle.crypto.Digest;
-import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
 import org.spongycastle.crypto.digests.Blake2bDigest;
-import org.spongycastle.crypto.generators.ECKeyPairGenerator;
-import org.spongycastle.crypto.params.ECKeyGenerationParameters;
-import org.spongycastle.crypto.params.ECPrivateKeyParameters;
-import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.whispersystems.curve25519.Curve25519;
+import org.whispersystems.curve25519.Curve25519KeyPair;
 
 import java.security.GeneralSecurityException;
 import java.security.NoSuchAlgorithmException;
@@ -35,7 +31,6 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static java.util.logging.Level.INFO;
-import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
 import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
 
 @NotNullByDefault
@@ -44,7 +39,6 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final Logger LOG =
 			Logger.getLogger(CryptoComponentImpl.class.getName());
 
-	private static final int AGREEMENT_KEY_PAIR_BITS = 256;
 	private static final int SIGNATURE_KEY_PAIR_BITS = 256;
 	private static final int STORAGE_IV_BYTES = 24; // 196 bits
 	private static final int PBKDF_SALT_BYTES = 32; // 256 bits
@@ -52,7 +46,7 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	private final SecureRandom secureRandom;
 	private final PasswordBasedKdf passwordBasedKdf;
-	private final ECKeyPairGenerator agreementKeyPairGenerator;
+	private final Curve25519 curve25519;
 	private final KeyPairGenerator signatureKeyPairGenerator;
 	private final KeyParser agreementKeyParser, signatureKeyParser;
 	private final MessageEncrypter messageEncrypter;
@@ -80,15 +74,11 @@ class CryptoComponentImpl implements CryptoComponent {
 		}
 		secureRandom = new SecureRandom();
 		this.passwordBasedKdf = passwordBasedKdf;
-		ECKeyGenerationParameters params = new ECKeyGenerationParameters(
-				PARAMETERS, secureRandom);
-		agreementKeyPairGenerator = new ECKeyPairGenerator();
-		agreementKeyPairGenerator.init(params);
+		curve25519 = Curve25519.getInstance("java");
 		signatureKeyPairGenerator = new KeyPairGenerator();
 		signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
 				secureRandom);
-		agreementKeyParser = new Sec1KeyParser(PARAMETERS,
-				AGREEMENT_KEY_PAIR_BITS);
+		agreementKeyParser = new Curve25519KeyParser();
 		signatureKeyParser = new EdKeyParser();
 		messageEncrypter = new MessageEncrypter(secureRandom);
 	}
@@ -133,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent {
 	// Package access for testing
 	byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
 			throws GeneralSecurityException {
-		if (!(priv instanceof Sec1PrivateKey))
+		if (!(priv instanceof Curve25519PrivateKey))
 			throw new IllegalArgumentException();
-		if (!(pub instanceof Sec1PublicKey))
+		if (!(pub instanceof Curve25519PublicKey))
 			throw new IllegalArgumentException();
-		ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
-		ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
 		long now = System.currentTimeMillis();
-		ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
-		agreement.init(ecPriv);
-		byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
+		byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
+				priv.getEncoded());
+		// If the shared secret is all zeroes, the public key is invalid
+		byte allZero = 0;
+		for (byte b : secret) allZero |= b;
+		if (allZero == 0) throw new GeneralSecurityException();
 		long duration = System.currentTimeMillis() - now;
 		if (LOG.isLoggable(INFO))
 			LOG.info("Deriving shared secret took " + duration + " ms");
@@ -151,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	@Override
 	public KeyPair generateAgreementKeyPair() {
-		AsymmetricCipherKeyPair keyPair =
-				agreementKeyPairGenerator.generateKeyPair();
-		// Return a wrapper that uses the SEC 1 encoding
-		ECPublicKeyParameters ecPublicKey =
-				(ECPublicKeyParameters) keyPair.getPublic();
-		PublicKey publicKey = new Sec1PublicKey(ecPublicKey
-		);
-		ECPrivateKeyParameters ecPrivateKey =
-				(ECPrivateKeyParameters) keyPair.getPrivate();
-		PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
-				AGREEMENT_KEY_PAIR_BITS);
-		return new KeyPair(publicKey, privateKey);
+		Curve25519KeyPair keyPair = curve25519.generateKeyPair();
+		PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
+		PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
+		return new KeyPair(pub, priv);
 	}
 
 	@Override
@@ -172,7 +155,8 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	@Override
 	public KeyPair generateSignatureKeyPair() {
-		java.security.KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair();
+		java.security.KeyPair keyPair =
+				signatureKeyPairGenerator.generateKeyPair();
 		EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
 		PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
 		EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7b39ad8d3dfbc8e41fbc1ab6d1337db8399e0f4
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java
@@ -0,0 +1,35 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.crypto.KeyParser;
+import org.briarproject.bramble.api.crypto.PrivateKey;
+import org.briarproject.bramble.api.crypto.PublicKey;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.security.GeneralSecurityException;
+
+@NotNullByDefault
+class Curve25519KeyParser implements KeyParser {
+
+	@Override
+	public PublicKey parsePublicKey(byte[] encodedKey)
+			throws GeneralSecurityException {
+		if (encodedKey.length != 32) throw new GeneralSecurityException();
+		return new Curve25519PublicKey(encodedKey);
+	}
+
+	@Override
+	public PrivateKey parsePrivateKey(byte[] encodedKey)
+			throws GeneralSecurityException {
+		if (encodedKey.length != 32) throw new GeneralSecurityException();
+		return new Curve25519PrivateKey(clamp(encodedKey));
+	}
+
+	static byte[] clamp(byte[] b) {
+		byte[] clamped = new byte[32];
+		System.arraycopy(b, 0, clamped, 0, 32);
+		clamped[0] &= 248;
+		clamped[31] &= 127;
+		clamped[31] |= 64;
+		return clamped;
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7f1a0cc51f4de7bc62bc6785b58775b6cd7e6eb
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java
@@ -0,0 +1,18 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.crypto.PrivateKey;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+@NotNullByDefault
+class Curve25519PrivateKey extends Bytes implements PrivateKey {
+
+	Curve25519PrivateKey(byte[] bytes) {
+		super(bytes);
+	}
+
+	@Override
+	public byte[] getEncoded() {
+		return getBytes();
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..94d703d01e51a7a6588cbb762bd00e23cfbfcbb7
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java
@@ -0,0 +1,18 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.crypto.PublicKey;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+@NotNullByDefault
+class Curve25519PublicKey extends Bytes implements PublicKey {
+
+	Curve25519PublicKey(byte[] bytes) {
+		super(bytes);
+	}
+
+	@Override
+	public byte[] getEncoded() {
+		return getBytes();
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java
deleted file mode 100644
index e4b4b04e8809ce8a5644c61fd722907d8695ae84..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
-import org.spongycastle.asn1.x9.X9ECParameters;
-import org.spongycastle.crypto.params.ECDomainParameters;
-import org.spongycastle.math.ec.ECCurve;
-import org.spongycastle.math.ec.ECMultiplier;
-import org.spongycastle.math.ec.ECPoint;
-import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
-
-import java.math.BigInteger;
-
-/**
- * Parameters for curve brainpoolp256r1 - see RFC 5639.
- */
-class EllipticCurveConstants {
-
-	static final ECDomainParameters PARAMETERS;
-
-	static {
-		// Start with the default implementation of the curve
-		X9ECParameters x9 = TeleTrusTNamedCurves.getByName("brainpoolp256r1");
-		// Use a constant-time multiplier
-		ECMultiplier monty = new MontgomeryLadderMultiplier();
-		ECCurve curve = x9.getCurve().configure().setMultiplier(monty).create();
-		BigInteger gX = x9.getG().getAffineXCoord().toBigInteger();
-		BigInteger gY = x9.getG().getAffineYCoord().toBigInteger();
-		ECPoint g = curve.createPoint(gX, gY);
-		// Convert to ECDomainParameters using the new multiplier
-		PARAMETERS = new ECDomainParameters(curve, g, x9.getN(), x9.getH());
-	}
-}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
index 9be3dc0ddc5b6b8b671759800e67a94d99d4fde9..b4e639047895c3fe7874a0c8605c157199931d0d 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
@@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
 import org.spongycastle.crypto.params.ECPublicKeyParameters;
 import org.spongycastle.math.ec.ECCurve;
 import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
 
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
-import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
 import static org.junit.Assert.assertEquals;
 
 public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@@ -31,15 +31,11 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
 		ECPoint defaultG = defaultX9Parameters.getG();
 		BigInteger defaultN = defaultX9Parameters.getN();
 		BigInteger defaultH = defaultX9Parameters.getH();
-		// Check that the default parameters are equal to our parameters
-		assertEquals(PARAMETERS.getCurve(), defaultCurve);
-		assertEquals(PARAMETERS.getG(), defaultG);
-		assertEquals(PARAMETERS.getN(), defaultN);
-		assertEquals(PARAMETERS.getH(), defaultH);
-		// ECDomainParameters doesn't have an equals() method, but it's just a
-		// container for the parameters
 		ECDomainParameters defaultParameters = new ECDomainParameters(
 				defaultCurve, defaultG, defaultN, defaultH);
+		// Instantiate an implementation using the Montgomery ladder multiplier
+		ECDomainParameters montgomeryParameters =
+				constantTime(defaultParameters);
 		// Generate two key pairs with each set of parameters, using the same
 		// deterministic PRNG for both sets of parameters
 		byte[] seed = new byte[32];
@@ -47,7 +43,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
 		// Montgomery ladder multiplier
 		SecureRandom random = new PseudoSecureRandom(seed);
 		ECKeyGenerationParameters montgomeryGeneratorParams =
-				new ECKeyGenerationParameters(PARAMETERS, random);
+				new ECKeyGenerationParameters(montgomeryParameters, random);
 		ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
 		montgomeryGenerator.init(montgomeryGeneratorParams);
 		AsymmetricCipherKeyPair montgomeryKeyPair1 =
@@ -107,4 +103,13 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
 		assertEquals(sharedSecretMontgomeryMontgomery,
 				sharedSecretDefaultDefault);
 	}
+
+	private static ECDomainParameters constantTime(ECDomainParameters in) {
+		ECCurve curve = in.getCurve().configure().setMultiplier(
+				new MontgomeryLadderMultiplier()).create();
+		BigInteger x = in.getG().getAffineXCoord().toBigInteger();
+		BigInteger y = in.getG().getAffineYCoord().toBigInteger();
+		ECPoint g = curve.createPoint(x, y);
+		return new ECDomainParameters(curve, g, in.getN(), in.getH());
+	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java
index ac42f290e31c8f5018c2a0af6d06fd24cef81cc2..abf2798b1c18e88cf7ff6bce5a1adbec5856c9ba 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java
@@ -1,17 +1,20 @@
 package org.briarproject.bramble.crypto;
 
+import net.i2p.crypto.eddsa.EdDSASecurityProvider;
+import net.i2p.crypto.eddsa.KeyPairGenerator;
+
 import org.spongycastle.asn1.sec.SECNamedCurves;
 import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
 import org.spongycastle.asn1.x9.X9ECParameters;
 import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.BasicAgreement;
 import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
 import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
 import org.spongycastle.crypto.digests.Blake2bDigest;
 import org.spongycastle.crypto.generators.ECKeyPairGenerator;
 import org.spongycastle.crypto.params.ECDomainParameters;
 import org.spongycastle.crypto.params.ECKeyGenerationParameters;
-import org.spongycastle.crypto.params.ECPrivateKeyParameters;
-import org.spongycastle.crypto.params.ECPublicKeyParameters;
 import org.spongycastle.crypto.params.ParametersWithRandom;
 import org.spongycastle.crypto.signers.DSADigestSigner;
 import org.spongycastle.crypto.signers.DSAKCalculator;
@@ -20,14 +23,22 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator;
 import org.spongycastle.math.ec.ECCurve;
 import org.spongycastle.math.ec.ECPoint;
 import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
+import org.whispersystems.curve25519.Curve25519;
+import org.whispersystems.curve25519.Curve25519KeyPair;
 
 import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.Provider;
 import java.security.SecureRandom;
+import java.security.Signature;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
+
 // Not a JUnit test
 public class EllipticCurvePerformanceTest {
 
@@ -38,8 +49,9 @@ public class EllipticCurvePerformanceTest {
 			"secp256k1", "secp256r1", "secp384r1", "secp521r1");
 	private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
 			"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
+	private static final Provider ED_PROVIDER = new EdDSASecurityProvider();
 
-	public static void main(String[] args) {
+	public static void main(String[] args) throws GeneralSecurityException {
 		for (String name : SEC_NAMES) {
 			ECDomainParameters params =
 					convertParams(SECNamedCurves.getByName(name));
@@ -52,43 +64,31 @@ public class EllipticCurvePerformanceTest {
 			runTest(name + " default", params);
 			runTest(name + " constant", constantTime(params));
 		}
-		runTest("ours", EllipticCurveConstants.PARAMETERS);
+		runCurve25519Test();
+		runEd25519Test();
 	}
 
 	private static void runTest(String name, ECDomainParameters params) {
 		// Generate two key pairs using the given parameters
-		ECKeyGenerationParameters generatorParams =
-				new ECKeyGenerationParameters(params, random);
 		ECKeyPairGenerator generator = new ECKeyPairGenerator();
-		generator.init(generatorParams);
+		generator.init(new ECKeyGenerationParameters(params, random));
 		AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
-		ECPublicKeyParameters public1 =
-				(ECPublicKeyParameters) keyPair1.getPublic();
-		ECPrivateKeyParameters private1 =
-				(ECPrivateKeyParameters) keyPair1.getPrivate();
 		AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
-		ECPublicKeyParameters public2 =
-				(ECPublicKeyParameters) keyPair2.getPublic();
-		// Time some ECDH key agreements
-		List<Long> samples = new ArrayList<>();
-		for (int i = 0; i < SAMPLES; i++) {
-			ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
-			long start = System.nanoTime();
-			agreement.init(private1);
-			agreement.calculateAgreement(public2);
-			samples.add(System.nanoTime() - start);
-		}
-		long agreementMedian = median(samples);
+		// Time some ECDH and ECDHC key agreements
+		long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
+		long agreementWithCofactorMedian =
+				runAgreementTest(keyPair1, keyPair2, true);
 		// Time some signatures
+		List<Long> samples = new ArrayList<>();
 		List<byte[]> signatures = new ArrayList<>();
-		samples.clear();
 		for (int i = 0; i < SAMPLES; i++) {
 			Digest digest = new Blake2bDigest(256);
 			DSAKCalculator calculator = new HMacDSAKCalculator(digest);
 			DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
 					calculator), digest);
 			long start = System.nanoTime();
-			signer.init(true, new ParametersWithRandom(private1, random));
+			signer.init(true,
+					new ParametersWithRandom(keyPair1.getPrivate(), random));
 			signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
 			signatures.add(signer.generateSignature());
 			samples.add(System.nanoTime() - start);
@@ -102,17 +102,83 @@ public class EllipticCurvePerformanceTest {
 			DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
 					calculator), digest);
 			long start = System.nanoTime();
-			signer.init(false, public1);
+			signer.init(false, keyPair1.getPublic());
 			signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
 			if (!signer.verifySignature(signatures.get(i)))
 				throw new AssertionError();
 			samples.add(System.nanoTime() - start);
 		}
 		long verificationMedian = median(samples);
-		System.out.println(name + ": "
-				+ agreementMedian + " "
-				+ signatureMedian + " "
-				+ verificationMedian);
+		System.out.println(String.format("%s: %,d %,d %,d %,d", name,
+				agreementMedian, agreementWithCofactorMedian,
+				signatureMedian, verificationMedian));
+	}
+
+	private static long runAgreementTest(AsymmetricCipherKeyPair keyPair1,
+			AsymmetricCipherKeyPair keyPair2, boolean withCofactor) {
+		List<Long> samples = new ArrayList<>();
+		for (int i = 0; i < SAMPLES; i++) {
+			BasicAgreement agreement = createAgreement(withCofactor);
+			long start = System.nanoTime();
+			agreement.init(keyPair1.getPrivate());
+			agreement.calculateAgreement(keyPair2.getPublic());
+			samples.add(System.nanoTime() - start);
+		}
+		return median(samples);
+	}
+
+	private static BasicAgreement createAgreement(boolean withCofactor) {
+		if (withCofactor) return new ECDHCBasicAgreement();
+		else return new ECDHBasicAgreement();
+	}
+
+	private static void runCurve25519Test() {
+		Curve25519 curve25519 = Curve25519.getInstance("java");
+		Curve25519KeyPair keyPair1 = curve25519.generateKeyPair();
+		Curve25519KeyPair keyPair2 = curve25519.generateKeyPair();
+		// Time some key agreements
+		List<Long> samples = new ArrayList<>();
+		for (int i = 0; i < SAMPLES; i++) {
+			long start = System.nanoTime();
+			curve25519.calculateAgreement(keyPair1.getPublicKey(),
+					keyPair2.getPrivateKey());
+			samples.add(System.nanoTime() - start);
+		}
+		long agreementMedian = median(samples);
+		System.out.println(String.format("Curve25519: %,d - - -",
+				agreementMedian));
+	}
+
+	private static void runEd25519Test() throws GeneralSecurityException {
+		KeyPair keyPair = new KeyPairGenerator().generateKeyPair();
+		// Time some signatures
+		List<Long> samples = new ArrayList<>();
+		List<byte[]> signatures = new ArrayList<>();
+		for (int i = 0; i < SAMPLES; i++) {
+			Signature signature =
+					Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
+			long start = System.nanoTime();
+			signature.initSign(keyPair.getPrivate(), random);
+			signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
+			signatures.add(signature.sign());
+			samples.add(System.nanoTime() - start);
+		}
+		long signatureMedian = median(samples);
+		// Time some signature verifications
+		samples.clear();
+		for (int i = 0; i < SAMPLES; i++) {
+			Signature signature =
+					Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
+			long start = System.nanoTime();
+			signature.initVerify(keyPair.getPublic());
+			signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
+			if (!signature.verify(signatures.get(i)))
+				throw new AssertionError();
+			samples.add(System.nanoTime() - start);
+		}
+		long verificationMedian = median(samples);
+		System.out.println(String.format("Ed25519: - - %,d %,d",
+				signatureMedian, verificationMedian));
 	}
 
 	private static long median(List<Long> list) {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
index d035bbe4b39f2b72e6c7e92c13b1c21bef05f046..f0505f4f1374d2bb3a45d99c7f559a80b6d26001 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
@@ -2,33 +2,80 @@ package org.briarproject.bramble.crypto;
 
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.PublicKey;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.junit.Test;
+import org.whispersystems.curve25519.Curve25519;
 
+import java.security.GeneralSecurityException;
 import java.util.Random;
 
 import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.util.StringUtils.fromHexString;
 import static org.junit.Assert.assertArrayEquals;
 
 public class KeyAgreementTest extends BrambleTestCase {
 
-	@Test
-	public void testDeriveSharedSecret() throws Exception {
-		CryptoComponent crypto =
-				new CryptoComponentImpl(new TestSecureRandomProvider(), null);
-		KeyPair aPair = crypto.generateAgreementKeyPair();
-		KeyPair bPair = crypto.generateAgreementKeyPair();
+	// Test vector from RFC 7748: Alice's private and public keys, Bob's
+	// private and public keys, and the shared secret
+	// https://tools.ietf.org/html/rfc7748#section-6.1
+	private static final String ALICE_PRIVATE =
+			"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
+	private static final String ALICE_PUBLIC =
+			"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
+	private static final String BOB_PRIVATE =
+			"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
+	private static final String BOB_PUBLIC =
+			"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
+	private static final String SHARED_SECRET =
+			"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742";
+
+	private final CryptoComponent crypto =
+			new CryptoComponentImpl(new TestSecureRandomProvider(), null);
+	private final byte[][] inputs;
+
+	public KeyAgreementTest() {
 		Random random = new Random();
-		byte[][] inputs = new byte[random.nextInt(10) + 1][];
+		inputs = new byte[random.nextInt(10) + 1][];
 		for (int i = 0; i < inputs.length; i++)
 			inputs[i] = getRandomBytes(random.nextInt(256));
+	}
+
+	@Test
+	public void testDerivesSharedSecret() throws Exception {
+		KeyPair aPair = crypto.generateAgreementKeyPair();
+		KeyPair bPair = crypto.generateAgreementKeyPair();
 		SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
 				bPair.getPublic(), aPair, inputs);
 		SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
 				aPair.getPublic(), bPair, inputs);
 		assertArrayEquals(aShared.getBytes(), bShared.getBytes());
 	}
+
+	@Test(expected = GeneralSecurityException.class)
+	public void testRejectsInvalidPublicKey() throws Exception {
+		KeyPair keyPair = crypto.generateAgreementKeyPair();
+		PublicKey invalid = new Curve25519PublicKey(new byte[32]);
+		crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair,
+				inputs);
+	}
+
+	@Test
+	public void testRfc7748TestVector() throws Exception {
+		// Private keys need to be clamped because curve25519-java does the
+		// clamping at key generation time, not multiplication time
+		byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE));
+		byte[] aPub = fromHexString(ALICE_PUBLIC);
+		byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE));
+		byte[] bPub = fromHexString(BOB_PUBLIC);
+		byte[] sharedSecret = fromHexString(SHARED_SECRET);
+		Curve25519 curve25519 = Curve25519.getInstance("java");
+		assertArrayEquals(sharedSecret,
+				curve25519.calculateAgreement(aPub, bPriv));
+		assertArrayEquals(sharedSecret,
+				curve25519.calculateAgreement(bPub, aPriv));
+	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
index 1f2cad4dc7cda8344aaea99f1f72b699f3e2cc1a..30d62ef8cd28ac85cb3f27b965bdeea0aad44981 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
@@ -26,7 +26,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
 	public void testAgreementPublicKeyLength() throws Exception {
 		// Generate 10 agreement key pairs
 		for (int i = 0; i < 10; i++) {
-			KeyPair keyPair = crypto.generateSignatureKeyPair();
+			KeyPair keyPair = crypto.generateAgreementKeyPair();
 			// Check the length of the public key
 			byte[] publicKey = keyPair.getPublic().getEncoded();
 			assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);