diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7c41fba3733dfa2df783fc31fe6aeba236557dc
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java
@@ -0,0 +1,188 @@
+package net.i2p.crypto.eddsa;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Arrays;
+
+import net.i2p.crypto.eddsa.math.Curve;
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.math.ScalarOps;
+
+/**
+ * @author str4d
+ *
+ */
+public class EdDSAEngine extends Signature {
+    private MessageDigest digest;
+    private final ByteArrayOutputStream baos;
+    private EdDSAKey key;
+
+    /**
+     * No specific hash requested, allows any EdDSA key.
+     */
+    public EdDSAEngine() {
+        super("EdDSA");
+        baos = new ByteArrayOutputStream(256);
+    }
+
+    /**
+     * Specific hash requested, only matching keys will be allowed.
+     * @param digest the hash algorithm that keys must have to sign or verify.
+     */
+    public EdDSAEngine(MessageDigest digest) {
+        this();
+        this.digest = digest;
+    }
+
+    @Override
+    protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
+        if (digest != null)
+            digest.reset();
+        baos.reset();
+
+        if (privateKey instanceof EdDSAPrivateKey) {
+            EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
+            key = privKey;
+
+            if (digest == null) {
+                // Instantiate the digest from the key parameters
+                try {
+                    digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
+                } catch (NoSuchAlgorithmException e) {
+                    throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
+                }
+            } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
+                throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
+
+            // Preparing for hash
+            // r = H(h_b,...,h_2b-1,M)
+            int b = privKey.getParams().getCurve().getField().getb();
+            digest.update(privKey.getH(), b/8, b/4 - b/8);
+        } else
+            throw new InvalidKeyException("cannot identify EdDSA private key.");
+    }
+
+    @Override
+    protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
+        if (digest != null)
+            digest.reset();
+        baos.reset();
+
+        if (publicKey instanceof EdDSAPublicKey) {
+            key = (EdDSAPublicKey) publicKey;
+
+            if (digest == null) {
+                // Instantiate the digest from the key parameters
+                try {
+                    digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
+                } catch (NoSuchAlgorithmException e) {
+                    throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
+                }
+            } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
+                throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
+        } else
+            throw new InvalidKeyException("cannot identify EdDSA public key.");
+    }
+
+    @Override
+    protected void engineUpdate(byte b) throws SignatureException {
+        // We need to store the message because it is used in several hashes
+        // XXX Can this be done more efficiently?
+        baos.write(b);
+    }
+
+    @Override
+    protected void engineUpdate(byte[] b, int off, int len)
+            throws SignatureException {
+        // We need to store the message because it is used in several hashes
+        // XXX Can this be done more efficiently?
+        baos.write(b, off, len);
+    }
+
+    @Override
+    protected byte[] engineSign() throws SignatureException {
+        Curve curve = key.getParams().getCurve();
+        ScalarOps sc = key.getParams().getScalarOps();
+        byte[] a = ((EdDSAPrivateKey) key).geta();
+
+        byte[] message = baos.toByteArray();
+        // r = H(h_b,...,h_2b-1,M)
+        byte[] r = digest.digest(message);
+
+        // r mod l
+        // Reduces r from 64 bytes to 32 bytes
+        r = sc.reduce(r);
+
+        // R = rB
+        GroupElement R = key.getParams().getB().scalarMultiply(r);
+        byte[] Rbyte = R.toByteArray();
+
+        // S = (r + H(Rbar,Abar,M)*a) mod l
+        digest.update(Rbyte);
+        digest.update(((EdDSAPrivateKey) key).getAbyte());
+        byte[] h = digest.digest(message);
+        h = sc.reduce(h);
+        byte[] S = sc.multiplyAndAdd(h, a, r);
+
+        // R+S
+        int b = curve.getField().getb();
+        ByteBuffer out = ByteBuffer.allocate(b/4);
+        out.put(Rbyte).put(S);
+        return out.array();
+    }
+
+    @Override
+    protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
+        Curve curve = key.getParams().getCurve();
+        int b = curve.getField().getb();
+        if (sigBytes.length != b/4)
+            throw new SignatureException("signature length is wrong");
+
+        // R is first b/8 bytes of sigBytes, S is second b/8 bytes
+        digest.update(sigBytes, 0, b/8);
+        digest.update(((EdDSAPublicKey) key).getAbyte());
+        // h = H(Rbar,Abar,M)
+        byte[] message = baos.toByteArray();
+        byte[] h = digest.digest(message);
+
+        // h mod l
+        h = key.getParams().getScalarOps().reduce(h);
+
+        byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4);
+        // R = SB - H(Rbar,Abar,M)A
+        GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime(
+                ((EdDSAPublicKey) key).getNegativeA(), h, Sbyte);
+
+        // Variable time. This should be okay, because there are no secret
+        // values used anywhere in verification.
+        byte[] Rcalc = R.toByteArray();
+        for (int i = 0; i < Rcalc.length; i++) {
+            if (Rcalc[i] != sigBytes[i])
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
+     */
+    @Override
+    protected void engineSetParameter(String param, Object value) {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    @Override
+    protected Object engineGetParameter(String param) {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..e093660f06ff1b7b3c1b55ed0a38331e092c4863
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java
@@ -0,0 +1,16 @@
+package net.i2p.crypto.eddsa;
+
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+
+/**
+ * Common interface for all EdDSA keys.
+ * @author str4d
+ *
+ */
+public interface EdDSAKey {
+    /**
+     * return a parameter specification representing the EdDSA domain
+     * parameters for the key.
+     */
+    public EdDSAParameterSpec getParams();
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbe7e1c6d31e4e60068a04baf29fafde13f96f1b
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java
@@ -0,0 +1,68 @@
+package net.i2p.crypto.eddsa;
+
+import java.security.PrivateKey;
+
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+
+/**
+ * An EdDSA private key.
+ * @author str4d
+ *
+ */
+public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
+    private static final long serialVersionUID = 23495873459878957L;
+    private transient final byte[] seed;
+    private transient final byte[] h;
+    private transient final byte[] a;
+    private transient final GroupElement A;
+    private transient final byte[] Abyte;
+    private transient final EdDSAParameterSpec edDsaSpec;
+
+    public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
+        this.seed = spec.getSeed();
+        this.h = spec.getH();
+        this.a = spec.geta();
+        this.A = spec.getA();
+        this.Abyte = this.A.toByteArray();
+        this.edDsaSpec = spec.getParams();
+    }
+
+    public String getAlgorithm() {
+        return "EdDSA";
+    }
+
+    public String getFormat() {
+        return "PKCS#8";
+    }
+
+    public byte[] getEncoded() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public EdDSAParameterSpec getParams() {
+        return edDsaSpec;
+    }
+
+    public byte[] getSeed() {
+        return seed;
+    }
+
+    public byte[] getH() {
+        return h;
+    }
+
+    public byte[] geta() {
+        return a;
+    }
+
+    public GroupElement getA() {
+        return A;
+    }
+
+    public byte[] getAbyte() {
+        return Abyte;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7505013ae5e089da3498be6757cf7eaf55f6ab2
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java
@@ -0,0 +1,56 @@
+package net.i2p.crypto.eddsa;
+
+import java.security.PublicKey;
+
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ * An EdDSA public key.
+ * @author str4d
+ *
+ */
+public class EdDSAPublicKey implements EdDSAKey, PublicKey {
+    private static final long serialVersionUID = 9837459837498475L;
+    private final GroupElement A;
+    private final GroupElement Aneg;
+    private final byte[] Abyte;
+    private final EdDSAParameterSpec edDsaSpec;
+
+    public EdDSAPublicKey(EdDSAPublicKeySpec spec) {
+        this.A = spec.getA();
+        this.Aneg = spec.getNegativeA();
+        this.Abyte = this.A.toByteArray();
+        this.edDsaSpec = spec.getParams();
+    }
+
+    public String getAlgorithm() {
+        return "EdDSA";
+    }
+
+    public String getFormat() {
+        return "X.509";
+    }
+
+    public byte[] getEncoded() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public EdDSAParameterSpec getParams() {
+        return edDsaSpec;
+    }
+
+    public GroupElement getA() {
+        return A;
+    }
+
+    public GroupElement getNegativeA() {
+        return Aneg;
+    }
+
+    public byte[] getAbyte() {
+        return Abyte;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java b/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4daf36eb186033343681247c79c13204ab26c25
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java
@@ -0,0 +1,56 @@
+package net.i2p.crypto.eddsa;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ * @author str4d
+ *
+ */
+public class KeyFactory extends KeyFactorySpi {
+
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
+            throws InvalidKeySpecException {
+        if (keySpec instanceof EdDSAPrivateKeySpec) {
+            return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("key spec not recognised");
+    }
+
+    protected PublicKey engineGeneratePublic(KeySpec keySpec)
+            throws InvalidKeySpecException {
+        if (keySpec instanceof EdDSAPublicKeySpec) {
+            return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("key spec not recognised");
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
+            throws InvalidKeySpecException {
+        if (keySpec.isAssignableFrom(EdDSAPublicKeySpec.class) && key instanceof EdDSAPublicKey) {
+            EdDSAPublicKey k = (EdDSAPublicKey) key;
+            if (k.getParams() != null) {
+                return (T) new EdDSAPublicKeySpec(k.getA(), k.getParams());
+            }
+        } else if (keySpec.isAssignableFrom(EdDSAPrivateKeySpec.class) && key instanceof EdDSAPrivateKey) {
+            EdDSAPrivateKey k = (EdDSAPrivateKey) key;
+            if (k.getParams() != null) {
+                return (T) new EdDSAPrivateKeySpec(k.getSeed(), k.getH(), k.geta(), k.getA(), k.getParams());
+            }
+        }
+        throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
+    }
+
+    protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+        throw new InvalidKeyException("No other EdDSA key providers known");
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..81508f77c7fb4c3e2617c8edcd3f656cd3f629c1
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java
@@ -0,0 +1,86 @@
+package net.i2p.crypto.eddsa;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGeneratorSpi;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Hashtable;
+
+import net.i2p.crypto.eddsa.spec.EdDSAGenParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
+import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ *  Default strength is 256
+ */
+public class KeyPairGenerator extends KeyPairGeneratorSpi {
+    private static final int DEFAULT_STRENGTH = 256;
+    private EdDSAParameterSpec edParams;
+    private SecureRandom random;
+    private boolean initialized;
+
+    private static final Hashtable<Integer, AlgorithmParameterSpec> edParameters;
+
+    static {
+        edParameters = new Hashtable<Integer, AlgorithmParameterSpec>();
+
+        edParameters.put(Integer.valueOf(DEFAULT_STRENGTH), new EdDSAGenParameterSpec(EdDSANamedCurveTable.CURVE_ED25519_SHA512));
+    }
+
+    public void initialize(int strength, SecureRandom random) {
+        AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(strength));
+        if (edParams == null)
+            throw new InvalidParameterException("unknown key type.");
+        try {
+            initialize(edParams, random);
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new InvalidParameterException("key type not configurable.");
+        }
+    }
+
+    @Override
+    public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException {
+        if (params instanceof EdDSAParameterSpec) {
+            edParams = (EdDSAParameterSpec) params;
+        } else if (params instanceof EdDSAGenParameterSpec) {
+            edParams = createNamedCurveSpec(((EdDSAGenParameterSpec) params).getName());
+        } else
+            throw new InvalidAlgorithmParameterException("parameter object not a EdDSAParameterSpec");
+
+        this.random = random;
+        initialized = true;
+    }
+
+    public KeyPair generateKeyPair() {
+        if (!initialized)
+            initialize(DEFAULT_STRENGTH, new SecureRandom());
+
+        byte[] seed = new byte[edParams.getCurve().getField().getb()/8];
+        random.nextBytes(seed);
+
+        EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(seed, edParams);
+        EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(privKey.getA(), edParams);
+
+        return new KeyPair(new EdDSAPublicKey(pubKey), new EdDSAPrivateKey(privKey));
+    }
+
+    /**
+     * Create an EdDSANamedCurveSpec from the provided curve name. The current
+     * implementation fetches the pre-created curve spec from a table.
+     * @param curveName the EdDSA named curve.
+     * @return the specification for the named curve.
+     * @throws InvalidAlgorithmParameterException if the named curve is unknown.
+     */
+    protected EdDSANamedCurveSpec createNamedCurveSpec(String curveName) throws InvalidAlgorithmParameterException {
+        EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(curveName);
+        if (spec == null) {
+            throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+        }
+        return spec;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/Utils.java b/core/java/src/net/i2p/crypto/eddsa/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d743f81bf0cfb64ffe535b81c8b1c394603a6a9
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/Utils.java
@@ -0,0 +1,54 @@
+package net.i2p.crypto.eddsa;
+
+/**
+ * @author str4d
+ *
+ */
+public class Utils {
+    /**
+     * Constant-time byte comparison.
+     * @return 1 if b and c are equal, 0 otherwise.
+     */
+    public static int equal(int b, int c) {
+        int result = 0;
+        int xor = b ^ c;
+        for (int i = 0; i < 8; i++) {
+            result |= xor >> i;
+        }
+        return (result ^ 0x01) & 0x01;
+    }
+
+    /**
+     * Constant-time determine if byte is negative.
+     * @param b the byte to check.
+     * @return 1 if the byte is negative, 0 otherwise.
+     */
+    public static int negative(int b) {
+        return (b >> 8) & 1;
+    }
+
+    /**
+     * Get the i'th bit of a byte array.
+     * @param h the byte array.
+     * @param i the bit index.
+     * @return 0 or 1, the value of the i'th bit in h
+     */
+    public static int bit(byte[] h, int i) {
+        return (h[i/8] >> (i%8)) & 1;
+    }
+
+    /**
+     * Converts a hex string to bytes.
+     * @param s the hex string to be converted.
+     * @return the byte[]
+     */
+    public static byte[] hexToBytes(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i+1), 16));
+        }
+        return data;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Constants.java b/core/java/src/net/i2p/crypto/eddsa/math/Constants.java
new file mode 100644
index 0000000000000000000000000000000000000000..885bee57ff6c0b49602783c792a36f766e9e51f4
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/Constants.java
@@ -0,0 +1,12 @@
+package net.i2p.crypto.eddsa.math;
+
+import net.i2p.crypto.eddsa.Utils;
+
+public class Constants {
+    public static final byte[] ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
+    public static final byte[] ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
+    public static final byte[] TWO = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000");
+    public static final byte[] FOUR = Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000");
+    public static final byte[] FIVE = Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000");
+    public static final byte[] EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000");
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Curve.java b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java
new file mode 100644
index 0000000000000000000000000000000000000000..38f690e3faab189990ea11c567deaa04e96b54af
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java
@@ -0,0 +1,70 @@
+package net.i2p.crypto.eddsa.math;
+
+import java.io.Serializable;
+
+/**
+ * A twisted Edwards curve.
+ * Points on the curve satisfy -x^2 + y^2 = 1 + d x^2y^2
+ * @author str4d
+ *
+ */
+public class Curve implements Serializable {
+    private static final long serialVersionUID = 4578920872509827L;
+    private final Field f;
+    private final FieldElement d;
+    private final FieldElement d2;
+    private final FieldElement I;
+
+    private final GroupElement zeroP2;
+    private final GroupElement zeroP3;
+    private final GroupElement zeroPrecomp;
+
+    public Curve(Field f, byte[] d, FieldElement I) {
+        this.f = f;
+        this.d = f.fromByteArray(d);
+        this.d2 = this.d.add(this.d);
+        this.I = I;
+
+        FieldElement zero = f.zero;
+        FieldElement one = f.one;
+        zeroP2 = GroupElement.p2(this, zero, one, one);
+        zeroP3 = GroupElement.p3(this, zero, one, one, zero);
+        zeroPrecomp = GroupElement.precomp(this, one, one, zero);
+    }
+
+    public Field getField() {
+        return f;
+    }
+
+    public FieldElement getD() {
+        return d;
+    }
+
+    public FieldElement get2D() {
+        return d2;
+    }
+
+    public FieldElement getI() {
+        return I;
+    }
+
+    public GroupElement getZero(GroupElement.Representation repr) {
+        switch (repr) {
+        case P2:
+            return zeroP2;
+        case P3:
+            return zeroP3;
+        case PRECOMP:
+            return zeroPrecomp;
+        default:
+            return null;
+        }
+    }
+
+    public GroupElement createPoint(byte[] P, boolean precompute) {
+        GroupElement ge = new GroupElement(this, P);
+        if (precompute)
+            ge.precompute(true);
+        return ge;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Encoding.java b/core/java/src/net/i2p/crypto/eddsa/math/Encoding.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3789ce3b1879ef90417d750752a601a88e8ebc3
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/Encoding.java
@@ -0,0 +1,39 @@
+package net.i2p.crypto.eddsa.math;
+
+/**
+ * Common interface for all (b-1)-bit encodings of elements
+ * of EdDSA finite fields.
+ * @author str4d
+ *
+ */
+public abstract class Encoding {
+    protected Field f;
+
+    public void setField(Field f) {
+        this.f = f;
+    }
+
+    /**
+     * Encode a FieldElement in its (b-1)-bit encoding.
+     * @return the (b-1)-bit encoding of this FieldElement.
+     */
+    public abstract byte[] encode(FieldElement x);
+
+    /**
+     * Decode a FieldElement from its (b-1)-bit encoding.
+     * The highest bit is masked out.
+     * @param val the (b-1)-bit encoding of a FieldElement.
+     * @return the FieldElement represented by 'val'.
+     */
+    public abstract FieldElement decode(byte[] in);
+
+    /**
+     * From the Ed25519 paper:
+     * x is negative if the (b-1)-bit encoding of x is lexicographically larger
+     * than the (b-1)-bit encoding of -x. If q is an odd prime and the encoding
+     * is the little-endian representation of {0, 1,..., q-1} then the negative
+     * elements of F_q are {1, 3, 5,..., q-2}.
+     * @return
+     */
+    public abstract boolean isNegative(FieldElement x);
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Field.java b/core/java/src/net/i2p/crypto/eddsa/math/Field.java
new file mode 100644
index 0000000000000000000000000000000000000000..040b3ffe01740e5b3fc174dbb44c7eecadbf606d
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/Field.java
@@ -0,0 +1,88 @@
+package net.i2p.crypto.eddsa.math;
+
+import java.io.Serializable;
+
+/**
+ * An EdDSA finite field. Includes several pre-computed values.
+ * @author str4d
+ *
+ */
+public class Field implements Serializable {
+    private static final long serialVersionUID = 8746587465875676L;
+
+    public final FieldElement zero;
+    public final FieldElement one;
+    public final FieldElement two;
+    public final FieldElement four;
+    public final FieldElement five;
+    public final FieldElement eight;
+
+    private final int b;
+    private final FieldElement q;
+    /**
+     * q-2
+     */
+    private final FieldElement qm2;
+    /**
+     * (q-5) / 8
+     */
+    private final FieldElement qm5d8;
+    private final Encoding enc;
+
+    public Field(int b, byte[] q, Encoding enc) {
+        this.b = b;
+        this.enc = enc;
+        this.enc.setField(this);
+
+        this.q = fromByteArray(q);
+
+        // Set up constants
+        zero = fromByteArray(Constants.ZERO);
+        one = fromByteArray(Constants.ONE);
+        two = fromByteArray(Constants.TWO);
+        four = fromByteArray(Constants.FOUR);
+        five = fromByteArray(Constants.FIVE);
+        eight = fromByteArray(Constants.EIGHT);
+
+        // Precompute values
+        qm2 = this.q.subtract(two);
+        qm5d8 = this.q.subtract(five).divide(eight);
+    }
+
+    public FieldElement fromByteArray(byte[] x) {
+        return enc.decode(x);
+    }
+
+    public int getb() {
+        return b;
+    }
+
+    public FieldElement getQ() {
+        return q;
+    }
+
+    public FieldElement getQm2() {
+        return qm2;
+    }
+
+    public FieldElement getQm5d8() {
+        return qm5d8;
+    }
+
+    public Encoding getEncoding(){
+        return enc;
+    }
+
+    @Override
+    public int hashCode() {
+        return q.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Field))
+            return false;
+        Field f = (Field) obj;
+        return b == f.b && q.equals(f.q);
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2b10b9f1c57a32031904526cf9baf2715611f32
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java
@@ -0,0 +1,51 @@
+package net.i2p.crypto.eddsa.math;
+
+public abstract class FieldElement {
+    protected final Field f;
+
+    public FieldElement(Field f) {
+        this.f = f;
+    }
+
+    /**
+     * Encode a FieldElement in its (b-1)-bit encoding.
+     * @return the (b-1)-bit encoding of this FieldElement.
+     */
+    public byte[] toByteArray() {
+        return f.getEncoding().encode(this);
+    }
+
+    public abstract boolean isNonZero();
+
+    public boolean isNegative() {
+        return f.getEncoding().isNegative(this);
+    }
+
+    public abstract FieldElement add(FieldElement val);
+
+    public FieldElement addOne() {
+        return add(f.one);
+    }
+
+    public abstract FieldElement subtract(FieldElement val);
+
+    public FieldElement subtractOne() {
+        return subtract(f.one);
+    }
+
+    public abstract FieldElement negate();
+
+    public FieldElement divide(FieldElement val) {
+        return multiply(val.invert());
+    }
+
+    public abstract FieldElement multiply(FieldElement val);
+
+    public abstract FieldElement square();
+
+    public abstract FieldElement squareAndDouble();
+
+    public abstract FieldElement invert();
+
+    public abstract FieldElement pow22523();
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e34c1b8afc533a762773ff42b43f01097b27bb7
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java
@@ -0,0 +1,681 @@
+package net.i2p.crypto.eddsa.math;
+
+import java.io.Serializable;
+
+import net.i2p.crypto.eddsa.Utils;
+
+/**
+ * A point (x,y) on an EdDSA curve.
+ * @author str4d
+ *
+ */
+public class GroupElement implements Serializable {
+    private static final long serialVersionUID = 2395879087349587L;
+
+    public enum Representation {
+        /** Projective: (X:Y:Z) satisfying x=X/Z, y=Y/Z */
+        P2,
+        /** Extended: (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT */
+        P3,
+        /** Completed: ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T */
+        P1P1,
+        /** Precomputed (Duif): (y+x,y-x,2dxy) */
+        PRECOMP,
+        /** Cached: (Y+X,Y-X,Z,2dT) */
+        CACHED
+    }
+
+    public static GroupElement p2(Curve curve, FieldElement X,
+            FieldElement Y, FieldElement Z) {
+        return new GroupElement(curve, Representation.P2, X, Y, Z, null);
+    }
+
+    public static GroupElement p3(Curve curve, FieldElement X,
+            FieldElement Y, FieldElement Z, FieldElement T) {
+        return new GroupElement(curve, Representation.P3, X, Y, Z, T);
+    }
+
+    public static GroupElement p1p1(Curve curve, FieldElement X,
+            FieldElement Y, FieldElement Z, FieldElement T) {
+        return new GroupElement(curve, Representation.P1P1, X, Y, Z, T);
+    }
+
+    public static GroupElement precomp(Curve curve, FieldElement ypx,
+            FieldElement ymx, FieldElement xy2d) {
+        return new GroupElement(curve, Representation.PRECOMP, ypx, ymx, xy2d, null);
+    }
+
+    public static GroupElement cached(Curve curve, FieldElement YpX,
+            FieldElement YmX, FieldElement Z, FieldElement T2d) {
+        return new GroupElement(curve, Representation.CACHED, YpX, YmX, Z, T2d);
+    }
+
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final Curve curve;
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final Representation repr;
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final FieldElement X;
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final FieldElement Y;
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final FieldElement Z;
+    /**
+     * Variable is package private only so that tests run.
+     */
+    final FieldElement T;
+    /**
+     * Precomputed table for {@link GroupElement#scalarMultiply(byte[])},
+     * filled if necessary.
+     *
+     * Variable is package private only so that tests run.
+     */
+    GroupElement[][] precmp;
+    /**
+     * Precomputed table for {@link GroupElement#doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])},
+     * filled if necessary.
+     *
+     * Variable is package private only so that tests run.
+     */
+    GroupElement[] dblPrecmp;
+
+    public GroupElement(Curve curve, Representation repr, FieldElement X, FieldElement Y,
+            FieldElement Z, FieldElement T) {
+        this.curve = curve;
+        this.repr = repr;
+        this.X = X;
+        this.Y = Y;
+        this.Z = Z;
+        this.T = T;
+    }
+
+    public GroupElement(Curve curve, byte[] s) {
+        FieldElement x, y, yy, u, v, v3, vxx, check;
+        y = curve.getField().fromByteArray(s);
+        yy = y.square();
+
+        // u = y^2-1	
+        u = yy.subtractOne();
+
+        // v = dy^2+1
+        v = yy.multiply(curve.getD()).addOne();
+
+        // v3 = v^3
+        v3 = v.square().multiply(v);
+
+        // x = (v3^2)vu, aka x = uv^7
+        x = v3.square().multiply(v).multiply(u);	
+
+        //  x = (uv^7)^((q-5)/8)
+        x = x.pow22523();
+
+        // x = uv^3(uv^7)^((q-5)/8)
+        x = v3.multiply(u).multiply(x);
+
+        vxx = x.square().multiply(v);
+        check = vxx.subtract(u);			// vx^2-u
+        if (check.isNonZero()) {
+            check = vxx.add(u);				// vx^2+u
+
+            if (check.isNonZero())
+                throw new IllegalArgumentException("not a valid GroupElement");
+            x = x.multiply(curve.getI());
+        }
+
+        if ((x.isNegative() ? 1 : 0) != Utils.bit(s, curve.getField().getb()-1)) {
+            x = x.negate();
+        }
+
+        this.curve = curve;
+        repr = Representation.P3;
+        X = x;
+        Y = y;
+        Z = curve.getField().one;
+        T = X.multiply(Y);
+    }
+
+    public byte[] toByteArray() {
+        switch (repr) {
+        case P2:
+        case P3:
+            FieldElement recip = Z.invert();
+            FieldElement x = X.multiply(recip);
+            FieldElement y = Y.multiply(recip);
+            byte[] s = y.toByteArray();
+            s[s.length-1] |= (x.isNegative() ? (byte) 0x80 : 0);
+            return s;
+        default:
+            return toP2().toByteArray();
+        }
+    }
+
+    public GroupElement toP2() {
+        return toRep(Representation.P2);
+    }
+    public GroupElement toP3() {
+        return toRep(Representation.P3);
+    }
+    public GroupElement toCached() {
+        return toRep(Representation.CACHED);
+    }
+
+    /**
+     * Convert a GroupElement from one Representation to another.<br>
+     * r = p<br>
+     * <br>
+     * Supported conversions:<br>
+     * - P3 -> P2<br>
+     * - P3 -> CACHED (1 multiply, 1 add, 1 subtract)<br>
+     * - P1P1 -> P2 (3 multiply)<br>
+     * - P1P1 -> P3 (4 multiply)
+     * @param rep The Representation to convert to.
+     * @return A new GroupElement in the given Representation.
+     */
+    private GroupElement toRep(Representation repr) {
+        switch (this.repr) {
+        case P3:
+            switch (repr) {
+            case P2:
+                return p2(curve, X, Y, Z);
+            case CACHED:
+                return cached(curve, Y.add(X), Y.subtract(X), Z, T.multiply(curve.get2D()));
+            default:
+                throw new IllegalArgumentException();
+            }
+        case P1P1:
+            switch (repr) {
+            case P2:
+                return p2(curve, X.multiply(T), Y.multiply(Z), Z.multiply(T));
+            case P3:
+                return p3(curve, X.multiply(T), Y.multiply(Z), Z.multiply(T), X.multiply(Y));
+            default:
+                throw new IllegalArgumentException();
+            }
+        default:
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Precompute the tables for {@link GroupElement#scalarMultiply(byte[])}
+     * and {@link GroupElement#doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}.
+     *
+     * @param precomputeSingle should the matrix for scalarMultiply() be precomputed?
+     */
+    public synchronized void precompute(boolean precomputeSingle) {
+        GroupElement Bi;
+
+        if (precomputeSingle && precmp == null) {
+            // Precomputation for single scalar multiplication.
+            precmp = new GroupElement[32][8];
+            Bi = this;
+            for (int i = 0; i < 32; i++) {
+                GroupElement Bij = Bi;
+                for (int j = 0; j < 8; j++) {
+                    FieldElement recip = Bij.Z.invert();
+                    FieldElement x = Bij.X.multiply(recip);
+                    FieldElement y = Bij.Y.multiply(recip);
+                    precmp[i][j] = precomp(curve, y.add(x), y.subtract(x), x.multiply(y).multiply(curve.get2D()));
+                    Bij = Bij.add(Bi.toCached()).toP3();
+                }
+                for (int k = 0; k < 8; k++) {
+                    Bi = Bi.add(Bi.toCached()).toP3();
+                }
+            }
+        }
+
+        // Precomputation for double scalar multiplication.
+        // P,3P,5P,7P,9P,11P,13P,15P
+        if (dblPrecmp != null)
+            return;
+        dblPrecmp = new GroupElement[8];
+        Bi = this;
+        for (int i = 0; i < 8; i++) {
+            FieldElement recip = Bi.Z.invert();
+            FieldElement x = Bi.X.multiply(recip);
+            FieldElement y = Bi.Y.multiply(recip);
+            dblPrecmp[i] = precomp(curve, y.add(x), y.subtract(x), x.multiply(y).multiply(curve.get2D()));
+            // Bi = edwards(B,edwards(B,Bi))
+            Bi = add(add(Bi.toCached()).toP3().toCached()).toP3();
+        }
+    }
+
+    /**
+     * r = 2 * p
+     * @return The P1P1 representation
+     */
+    public GroupElement dbl() {
+        switch (repr) {
+        case P2:
+        case P3: // Ignore T for P3 representation
+            FieldElement XX, YY, B, A, AA, Yn, Zn;
+            XX = X.square();
+            YY = Y.square();
+            B = Z.squareAndDouble();
+            A = X.add(Y);
+            AA = A.square();
+            Yn = YY.add(XX);
+            Zn = YY.subtract(XX);
+            return p1p1(curve, AA.subtract(Yn), Yn, Zn, B.subtract(Zn));
+        default:
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * GroupElement addition using the twisted Edwards addition law with
+     * extended coordinates (Hisil2008).<br>
+     * r = p + q
+     * @param q the PRECOMP representation of the GroupElement to add.
+     * @return the P1P1 representation of the result.
+     */
+    private GroupElement madd(GroupElement q) {
+        if (this.repr != Representation.P3)
+            throw new UnsupportedOperationException();
+        if (q.repr != Representation.PRECOMP)
+            throw new IllegalArgumentException();
+
+        FieldElement YpX, YmX, A, B, C, D;
+        YpX = Y.add(X);
+        YmX = Y.subtract(X);
+        A = YpX.multiply(q.X); // q->y+x
+        B = YmX.multiply(q.Y); // q->y-x
+        C = q.Z.multiply(T); // q->2dxy
+        D = Z.add(Z);
+        return p1p1(curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C));
+    }
+
+    /**
+     * GroupElement subtraction using the twisted Edwards addition law with
+     * extended coordinates (Hisil2008).<br>
+     * r = p - q
+     * @param q the PRECOMP representation of the GroupElement to subtract.
+     * @return the P1P1 representation of the result.
+     */
+    private GroupElement msub(GroupElement q) {
+        if (this.repr != Representation.P3)
+            throw new UnsupportedOperationException();
+        if (q.repr != Representation.PRECOMP)
+            throw new IllegalArgumentException();
+
+        FieldElement YpX, YmX, A, B, C, D;
+        YpX = Y.add(X);
+        YmX = Y.subtract(X);
+        A = YpX.multiply(q.Y); // q->y-x
+        B = YmX.multiply(q.X); // q->y+x
+        C = q.Z.multiply(T); // q->2dxy
+        D = Z.add(Z);
+        return p1p1(curve, A.subtract(B), A.add(B), D.subtract(C), D.add(C));
+    }
+
+    /**
+     * GroupElement addition using the twisted Edwards addition law with
+     * extended coordinates (Hisil2008).<br>
+     * r = p + q
+     * @param q the CACHED representation of the GroupElement to add.
+     * @return the P1P1 representation of the result.
+     */
+    public GroupElement add(GroupElement q) {
+        if (this.repr != Representation.P3)
+            throw new UnsupportedOperationException();
+        if (q.repr != Representation.CACHED)
+            throw new IllegalArgumentException();
+
+        FieldElement YpX, YmX, A, B, C, ZZ, D;
+        YpX = Y.add(X);
+        YmX = Y.subtract(X);
+        A = YpX.multiply(q.X); // q->Y+X
+        B = YmX.multiply(q.Y); // q->Y-X
+        C = q.T.multiply(T); // q->2dT
+        ZZ = Z.multiply(q.Z);
+        D = ZZ.add(ZZ);
+        return p1p1(curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C));
+    }
+
+    /**
+     * GroupElement subtraction using the twisted Edwards addition law with
+     * extended coordinates (Hisil2008).<br>
+     * r = p - q
+     * @param q the PRECOMP representation of the GroupElement to subtract.
+     * @return the P1P1 representation of the result.
+     */
+    public GroupElement sub(GroupElement q) {
+        if (this.repr != Representation.P3)
+            throw new UnsupportedOperationException();
+        if (q.repr != Representation.CACHED)
+            throw new IllegalArgumentException();
+
+        FieldElement YpX, YmX, A, B, C, ZZ, D;
+        YpX = Y.add(X);
+        YmX = Y.subtract(X);
+        A = YpX.multiply(q.Y); // q->Y-X
+        B = YmX.multiply(q.X); // q->Y+X
+        C = q.T.multiply(T); // q->2dT
+        ZZ = Z.multiply(q.Z);
+        D = ZZ.add(ZZ);
+        return p1p1(curve, A.subtract(B), A.add(B), D.subtract(C), D.add(C));
+    }
+
+    public GroupElement negate() {
+        if (this.repr != Representation.P3)
+            throw new UnsupportedOperationException();
+        return curve.getZero(Representation.P3).sub(toCached()).toP3();
+    }
+
+    @Override
+    public int hashCode() {
+        // TODO
+        return 42;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GroupElement))
+            return false;
+        GroupElement ge = (GroupElement) obj;
+        if (!this.repr.equals(ge.repr)) {
+            try {
+                ge = ge.toRep(this.repr);
+            } catch (Exception e) {
+                return false;
+            }
+        }
+        switch (this.repr) {
+        case P2:
+        case P3:
+            // Try easy way first
+            if (Z.equals(ge.Z))
+                return X.equals(ge.X) && Y.equals(ge.Y);
+            // X1/Z1 = X2/Z2 --> X1*Z2 = X2*Z1
+            FieldElement x1 = X.multiply(ge.Z);
+            FieldElement y1 = Y.multiply(ge.Z);
+            FieldElement x2 = ge.X.multiply(Z);
+            FieldElement y2 = ge.Y.multiply(Z);
+            return x1.equals(x2) && y1.equals(y2);
+        case P1P1:
+            return toP2().equals(ge);
+        case PRECOMP:
+            // Compare directly, PRECOMP is derived directly from x and y
+            return X.equals(ge.X) && Y.equals(ge.Y) && Z.equals(ge.Z);
+        case CACHED:
+            // Try easy way first
+            if (Z.equals(ge.Z))
+                return X.equals(ge.X) && Y.equals(ge.Y) && T.equals(ge.T);
+            // (Y+X)/Z = y+x etc.
+            FieldElement x3 = X.multiply(ge.Z);
+            FieldElement y3 = Y.multiply(ge.Z);
+            FieldElement t3 = T.multiply(ge.Z);
+            FieldElement x4 = ge.X.multiply(Z);
+            FieldElement y4 = ge.Y.multiply(Z);
+            FieldElement t4 = ge.T.multiply(Z);
+            return x3.equals(x4) && y3.equals(y4) && t3.equals(t4);
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Convert a to radix 16.
+     *
+     * Method is package private only so that tests run.
+     *
+     * @param a = a[0]+256*a[1]+...+256^31 a[31]
+     * @return 64 bytes, each between -8 and 7
+     */
+    static byte[] toRadix16(byte[] a) {
+        byte[] e = new byte[64];
+        int i;
+        // Radix 16 notation
+        for (i = 0; i < 32; i++) {
+            e[2*i+0] = (byte) (a[i] & 15);
+            e[2*i+1] = (byte) ((a[i] >> 4) & 15);
+        }
+        /* each e[i] is between 0 and 15 */
+        /* e[63] is between 0 and 7 */
+        int carry = 0;
+        for (i = 0; i < 63; i++) {
+            e[i] += carry;
+            carry = e[i] + 8;
+            carry >>= 4;
+            e[i] -= carry << 4;
+        }
+        e[63] += carry;
+        /* each e[i] is between -8 and 7 */
+        return e;
+    }
+
+    /**
+     * Constant-time conditional move.
+     * Replaces this with u if b == 1.
+     * Replaces this with this if b == 0.
+     *
+     * Method is package private only so that tests run.
+     *
+     * @param u
+     * @param b in {0, 1}
+     * @return u if b == 1; this if b == 0; null otherwise.
+     */
+    GroupElement cmov(GroupElement u, int b) {
+        GroupElement ret = null;
+        for (int i = 0; i < b; i++) {
+            // Only for b == 1
+            ret = u;
+        }
+        for (int i = 0; i < 1-b; i++) {
+            // Only for b == 0
+            ret = this;
+        }
+        return ret;
+    }
+
+    /**
+     * Look up 16^i r_i B in the precomputed table.
+     * No secret array indices, no secret branching.
+     * Constant time.
+     *
+     * Must have previously precomputed.
+     *
+     * Method is package private only so that tests run.
+     *
+     * @param pos = i/2 for i in {0, 2, 4,..., 62}
+     * @param b = r_i
+     * @return
+     */
+    GroupElement select(int pos, int b) {
+        // Is r_i negative?
+        int bnegative = Utils.negative(b);
+        // |r_i|
+        int babs = b - (((-bnegative) & b) << 1);
+
+        // 16^i |r_i| B
+        GroupElement t = curve.getZero(Representation.PRECOMP)
+                .cmov(precmp[pos][0], Utils.equal(babs, 1))
+                .cmov(precmp[pos][1], Utils.equal(babs, 2))
+                .cmov(precmp[pos][2], Utils.equal(babs, 3))
+                .cmov(precmp[pos][3], Utils.equal(babs, 4))
+                .cmov(precmp[pos][4], Utils.equal(babs, 5))
+                .cmov(precmp[pos][5], Utils.equal(babs, 6))
+                .cmov(precmp[pos][6], Utils.equal(babs, 7))
+                .cmov(precmp[pos][7], Utils.equal(babs, 8));
+        // -16^i |r_i| B
+        GroupElement tminus = precomp(curve, t.Y, t.X, t.Z.negate());
+        // 16^i r_i B
+        return t.cmov(tminus, bnegative);
+    }
+
+    /**
+     * h = a * B where a = a[0]+256*a[1]+...+256^31 a[31] and
+     * B is this point. If its lookup table has not been precomputed, it
+     * will be at the start of the method (and cached for later calls). 
+     * Constant time.
+     *
+     * Preconditions: (TODO: Check this applies here)
+     *   a[31] <= 127
+     * @param a = a[0]+256*a[1]+...+256^31 a[31]
+     * @return
+     */
+    public GroupElement scalarMultiply(byte[] a) {
+        GroupElement t;
+        int i;
+
+        byte[] e = toRadix16(a);
+
+        GroupElement h = curve.getZero(Representation.P3);
+        synchronized(this) {
+            // TODO: Get opinion from a crypto professional.
+            // This should in practice never be necessary, the only point that
+            // this should get called on is EdDSA's B.
+            //precompute();
+            for (i = 1; i < 64; i += 2) {
+                t = select(i/2, e[i]);
+                h = h.madd(t).toP3();
+            }
+
+            h = h.dbl().toP2().dbl().toP2().dbl().toP2().dbl().toP3();
+
+            for (i = 0; i < 64; i += 2) {
+                t = select(i/2, e[i]);
+                h = h.madd(t).toP3();
+            }
+        }
+
+        return h;
+    }
+
+    /**
+     * I don't really know what this method does.
+     *
+     * Method is package private only so that tests run.
+     *
+     * @param a 32 bytes
+     * @return 256 bytes
+     */
+    static byte[] slide(byte[] a) {
+        byte[] r = new byte[256];
+
+        // put each bit of 'a' into a separate byte, 0 or 1
+        for (int i = 0; i < 256; ++i) {
+            r[i] = (byte) (1 & (a[i >> 3] >> (i & 7)));
+        }
+
+        for (int i = 0; i < 256; ++i) {
+            if (r[i] != 0) {
+                for (int b = 1; b <= 6 && i + b < 256; ++b) {
+                    if (r[i + b] != 0) {
+                        if (r[i] + (r[i + b] << b) <= 15) {
+                            r[i] += r[i + b] << b;
+                            r[i + b] = 0;
+                        } else if (r[i] - (r[i + b] << b) >= -15) {
+                            r[i] -= r[i + b] << b;
+                            for (int k = i + b; k < 256; ++k) {
+                                if (r[k] == 0) {
+                                    r[k] = 1;
+                                    break;
+                                }
+                                r[k] = 0;
+                            }
+                        } else
+                            break;
+                    }
+                }
+            }
+        }
+
+        return r;
+    }
+
+    /**
+     * r = a * A + b * B where a = a[0]+256*a[1]+...+256^31 a[31],
+     * b = b[0]+256*b[1]+...+256^31 b[31] and B is this point.
+     *
+     * A must have been previously precomputed.
+     *
+     * @param A in P3 representation.
+     * @param a = a[0]+256*a[1]+...+256^31 a[31]
+     * @param b = b[0]+256*b[1]+...+256^31 b[31]
+     * @return
+     */
+    public GroupElement doubleScalarMultiplyVariableTime(GroupElement A, byte[] a, byte[] b) {
+        byte[] aslide = slide(a);
+        byte[] bslide = slide(b);
+
+        GroupElement r = curve.getZero(Representation.P2);
+
+        int i;
+        for (i = 255; i >= 0; --i) {
+            if (aslide[i] != 0 || bslide[i] != 0) break;
+        }
+
+        synchronized(this) {
+            // TODO: Get opinion from a crypto professional.
+            // This should in practice never be necessary, the only point that
+            // this should get called on is EdDSA's B.
+            //precompute();
+            for (; i >= 0; --i) {
+                GroupElement t = r.dbl();
+
+                if (aslide[i] > 0) {
+                    t = t.toP3().madd(A.dblPrecmp[aslide[i]/2]);
+                } else if(aslide[i] < 0) {
+                    t = t.toP3().msub(A.dblPrecmp[(-aslide[i])/2]);
+                }
+
+                if (bslide[i] > 0) {
+                    t = t.toP3().madd(dblPrecmp[bslide[i]/2]);
+                } else if(bslide[i] < 0) {
+                    t = t.toP3().msub(dblPrecmp[(-bslide[i])/2]);
+                }
+
+                r = t.toP2();
+            }
+        }
+
+        return r;
+    }
+
+    /**
+     * Verify that a point is on its curve.
+     * @param P The point to check.
+     * @return true if the point lies on its curve.
+     */
+    public boolean isOnCurve() {
+        return isOnCurve(curve);
+    }
+
+    /**
+     * Verify that a point is on the curve.
+     * @param curve The curve to check.
+     * @return true if the point lies on the curve.
+     */
+    public boolean isOnCurve(Curve curve) {
+        switch (repr) {
+        case P2:
+        case P3:
+            FieldElement recip = Z.invert();
+            FieldElement x = X.multiply(recip);
+            FieldElement y = Y.multiply(recip);
+            FieldElement xx = x.square();
+            FieldElement yy = y.square();
+            FieldElement dxxyy = curve.getD().multiply(xx).multiply(yy);
+            return curve.getField().one.add(dxxyy).add(xx).equals(yy);
+
+        default:
+            return toP2().isOnCurve(curve);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "[GroupElement\nX="+X+"\nY="+Y+"\nZ="+Z+"\nT="+T+"\n]";
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/ScalarOps.java b/core/java/src/net/i2p/crypto/eddsa/math/ScalarOps.java
new file mode 100644
index 0000000000000000000000000000000000000000..7073bd73fb63cc0d70e6c1a32c9bbeb160eb7783
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/ScalarOps.java
@@ -0,0 +1,22 @@
+package net.i2p.crypto.eddsa.math;
+
+public interface ScalarOps {
+    /**
+     * Reduce the given scalar mod l.
+     * From the Ed25519 paper:
+     * Here we interpret 2b-bit strings in little-endian form as integers in
+     * {0, 1,..., 2^(2b)-1}.
+     * @param s
+     * @return s mod l
+     */
+    public byte[] reduce(byte[] s);
+
+    /**
+     * r = (a * b + c) mod l
+     * @param a
+     * @param b
+     * @param c
+     * @return (a*b + c) mod l
+     */
+    public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c);
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerFieldElement.java b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerFieldElement.java
new file mode 100644
index 0000000000000000000000000000000000000000..b52f5f21b9a9e4f7c1f41cc386ca8652c275227e
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerFieldElement.java
@@ -0,0 +1,113 @@
+package net.i2p.crypto.eddsa.math.bigint;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.FieldElement;
+
+/**
+ * A particular element of the field \Z/(2^255-19).
+ * @author str4d
+ *
+ */
+public class BigIntegerFieldElement extends FieldElement implements Serializable {
+    private static final long serialVersionUID = 4890398908392808L;
+    /**
+     * Variable is package private for encoding.
+     */
+    final BigInteger bi;
+
+    public BigIntegerFieldElement(Field f, BigInteger bi) {
+        super(f);
+        this.bi = bi;
+    }
+
+    public boolean isNonZero() {
+        return !bi.equals(BigInteger.ZERO);
+    }
+
+    public FieldElement add(FieldElement val) {
+        return new BigIntegerFieldElement(f, bi.add(((BigIntegerFieldElement)val).bi)).mod(f.getQ());
+    }
+
+    @Override
+    public FieldElement addOne() {
+        return new BigIntegerFieldElement(f, bi.add(BigInteger.ONE)).mod(f.getQ());
+    }
+
+    public FieldElement subtract(FieldElement val) {
+        return new BigIntegerFieldElement(f, bi.subtract(((BigIntegerFieldElement)val).bi)).mod(f.getQ());
+    }
+
+    @Override
+    public FieldElement subtractOne() {
+        return new BigIntegerFieldElement(f, bi.subtract(BigInteger.ONE)).mod(f.getQ());
+    }
+
+    public FieldElement negate() {
+        return f.getQ().subtract(this);
+    }
+
+    @Override
+    public FieldElement divide(FieldElement val) {
+        return divide(((BigIntegerFieldElement)val).bi);
+    }
+
+    public FieldElement divide(BigInteger val) {
+        return new BigIntegerFieldElement(f, bi.divide(val)).mod(f.getQ());
+    }
+
+    public FieldElement multiply(FieldElement val) {
+        return new BigIntegerFieldElement(f, bi.multiply(((BigIntegerFieldElement)val).bi)).mod(f.getQ());
+    }
+
+    public FieldElement square() {
+        return multiply(this);
+    }
+
+    public FieldElement squareAndDouble() {
+        FieldElement sq = square();
+        return sq.add(sq);
+    }
+
+    public FieldElement invert() {
+        // Euler's theorem
+        //return modPow(f.getQm2(), f.getQ());
+        return new BigIntegerFieldElement(f, bi.modInverse(((BigIntegerFieldElement)f.getQ()).bi));
+    }
+
+    public FieldElement mod(FieldElement m) {
+        return new BigIntegerFieldElement(f, bi.mod(((BigIntegerFieldElement)m).bi));
+    }
+
+    public FieldElement modPow(FieldElement e, FieldElement m) {
+        return new BigIntegerFieldElement(f, bi.modPow(((BigIntegerFieldElement)e).bi, ((BigIntegerFieldElement)m).bi));
+    }
+
+    public FieldElement pow(FieldElement e){
+        return modPow(e, f.getQ());
+    }
+
+    public FieldElement pow22523(){
+        return pow(f.getQm5d8());
+    }
+
+    @Override
+    public int hashCode() {
+        return bi.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof BigIntegerFieldElement))
+            return false;
+        BigIntegerFieldElement fe = (BigIntegerFieldElement) obj;
+        return bi.equals(fe.bi);
+    }
+
+    @Override
+    public String toString() {
+        return "[BigIntegerFieldElement val="+bi+"]";
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java
new file mode 100644
index 0000000000000000000000000000000000000000..f638b5f37d19434a43918a1f90af6dcc413649e6
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java
@@ -0,0 +1,73 @@
+package net.i2p.crypto.eddsa.math.bigint;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+import net.i2p.crypto.eddsa.math.Encoding;
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.FieldElement;
+
+public class BigIntegerLittleEndianEncoding extends Encoding implements Serializable {
+    private static final long serialVersionUID = 3984579843759837L;
+    /**
+     * Mask where only the first b-1 bits are set.
+     */
+    private BigInteger mask;
+
+    @Override
+    public void setField(Field f) {
+        super.setField(f);
+        mask = BigInteger.ONE.shiftLeft(f.getb()-1).subtract(BigInteger.ONE);
+    }
+
+    public byte[] encode(FieldElement x) {
+        return encode(((BigIntegerFieldElement)x).bi.and(mask));
+    }
+
+    /**
+     *  Convert x to little endian.
+     *  Constant time.
+     *
+     *  @return array of length b/8
+     */
+    public byte[] encode(BigInteger x) {
+        byte[] in = x.toByteArray();
+        byte[] out = new byte[f.getb()/8];
+        for (int i = 0; i < in.length; i++) {
+            out[i] = in[in.length-1-i];
+        }
+        for (int i = in.length; i < out.length; i++) {
+            out[i] = 0;
+        }
+        return out;
+    }
+
+    public FieldElement decode(byte[] in) {
+        if (in.length != f.getb()/8)
+            throw new IllegalArgumentException("Not a valid encoding");
+        return new BigIntegerFieldElement(f, toBigInteger(in).and(mask));
+    }
+
+    /**
+     *  Convert in to big endian
+     */
+    public BigInteger toBigInteger(byte[] in) {
+        byte[] out = new byte[in.length];
+        for (int i = 0; i < in.length; i++) {
+            out[i] = in[in.length-1-i];
+        }
+        return new BigInteger(1, out);
+    }
+
+    /**
+     * From the Ed25519 paper:
+     * x is negative if the (b-1)-bit encoding of x is lexicographically larger
+     * than the (b-1)-bit encoding of -x. If q is an odd prime and the encoding
+     * is the little-endian representation of {0, 1,..., q-1} then the negative
+     * elements of F_q are {1, 3, 5,..., q-2}.
+     * @return
+     */
+    public boolean isNegative(FieldElement x) {
+        return ((BigIntegerFieldElement)x).bi.testBit(0);
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerScalarOps.java b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerScalarOps.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e199791a17be6c7885d1a651ff1ea141698cedf
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerScalarOps.java
@@ -0,0 +1,26 @@
+package net.i2p.crypto.eddsa.math.bigint;
+
+import java.math.BigInteger;
+
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.ScalarOps;
+
+public class BigIntegerScalarOps implements ScalarOps {
+    private final BigInteger l;
+    private final BigIntegerLittleEndianEncoding enc;
+
+    public BigIntegerScalarOps(Field f, BigInteger l) {
+        this.l = l;
+        enc = new BigIntegerLittleEndianEncoding();
+        enc.setField(f);
+    }
+
+    public byte[] reduce(byte[] s) {
+        return enc.encode(enc.toBigInteger(s).mod(l));
+    }
+
+    public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) {
+        return enc.encode(enc.toBigInteger(a).multiply(enc.toBigInteger(b)).add(enc.toBigInteger(c)).mod(l));
+    }
+
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519FieldElement.java b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519FieldElement.java
new file mode 100644
index 0000000000000000000000000000000000000000..f19523feddd4805bd2c72c5806f0e7ef0f3c5326
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519FieldElement.java
@@ -0,0 +1,971 @@
+package net.i2p.crypto.eddsa.math.ed25519;
+
+import net.i2p.crypto.eddsa.TestUtils;
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.FieldElement;
+
+/**
+ * An element t, entries t[0]...t[9], represents the integer
+ * t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9].
+ * Bounds on each t[i] vary depending on context.
+ */
+public class Ed25519FieldElement extends FieldElement {
+    /**
+     * Variable is package private for encoding.
+     */
+    int[] t;
+
+    public Ed25519FieldElement(Field f, int[] t) {
+        super(f);
+        if (t.length != 10)
+            throw new IllegalArgumentException("Invalid radix-2^51 representation");
+        this.t = t;
+    }
+
+    private static final byte[] zero = new byte[32];
+
+    public boolean isNonZero() {
+        byte[] s = toByteArray();
+        int result = 0;
+        for (int i = 0; i < 32; i++) {
+            result |= s[i] ^ zero[i];
+        }
+        return result != 0;
+    }
+
+    /**
+     * h = f + g
+     * Can overlap h with f or g.
+     *
+     * Preconditions:
+     *    |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     *    |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     *
+     * Postconditions:
+     *    |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+     */
+    public FieldElement add(FieldElement val) {
+        int[] g = ((Ed25519FieldElement)val).t;
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int g0 = g[0];
+        int g1 = g[1];
+        int g2 = g[2];
+        int g3 = g[3];
+        int g4 = g[4];
+        int g5 = g[5];
+        int g6 = g[6];
+        int g7 = g[7];
+        int g8 = g[8];
+        int g9 = g[9];
+        int h0 = f0 + g0;
+        int h1 = f1 + g1;
+        int h2 = f2 + g2;
+        int h3 = f3 + g3;
+        int h4 = f4 + g4;
+        int h5 = f5 + g5;
+        int h6 = f6 + g6;
+        int h7 = f7 + g7;
+        int h8 = f8 + g8;
+        int h9 = f9 + g9;
+        int[] h = new int[10];
+        h[0] = h0;
+        h[1] = h1;
+        h[2] = h2;
+        h[3] = h3;
+        h[4] = h4;
+        h[5] = h5;
+        h[6] = h6;
+        h[7] = h7;
+        h[8] = h8;
+        h[9] = h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * h = f - g
+     * Can overlap h with f or g.
+     *
+     * Preconditions:
+     *    |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     *    |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     *
+     * Postconditions:
+     *    |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+     **/
+    public FieldElement subtract(FieldElement val) {
+        int[] g = ((Ed25519FieldElement)val).t;
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int g0 = g[0];
+        int g1 = g[1];
+        int g2 = g[2];
+        int g3 = g[3];
+        int g4 = g[4];
+        int g5 = g[5];
+        int g6 = g[6];
+        int g7 = g[7];
+        int g8 = g[8];
+        int g9 = g[9];
+        int h0 = f0 - g0;
+        int h1 = f1 - g1;
+        int h2 = f2 - g2;
+        int h3 = f3 - g3;
+        int h4 = f4 - g4;
+        int h5 = f5 - g5;
+        int h6 = f6 - g6;
+        int h7 = f7 - g7;
+        int h8 = f8 - g8;
+        int h9 = f9 - g9;
+        int[] h = new int[10];
+        h[0] = h0;
+        h[1] = h1;
+        h[2] = h2;
+        h[3] = h3;
+        h[4] = h4;
+        h[5] = h5;
+        h[6] = h6;
+        h[7] = h7;
+        h[8] = h8;
+        h[9] = h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * h = -f
+     *
+     * Preconditions:
+     *    |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     *
+     * Postconditions:
+     *    |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+     */
+    public FieldElement negate() {
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int h0 = -f0;
+        int h1 = -f1;
+        int h2 = -f2;
+        int h3 = -f3;
+        int h4 = -f4;
+        int h5 = -f5;
+        int h6 = -f6;
+        int h7 = -f7;
+        int h8 = -f8;
+        int h9 = -f9;
+        int[] h = new int[10];
+        h[0] = h0;
+        h[1] = h1;
+        h[2] = h2;
+        h[3] = h3;
+        h[4] = h4;
+        h[5] = h5;
+        h[6] = h6;
+        h[7] = h7;
+        h[8] = h8;
+        h[9] = h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * h = f * g Can overlap h with f or g.
+     * 
+     * Preconditions: |f| bounded by
+     * 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. |g| bounded by
+     * 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+     * 
+     * Postconditions: |h| bounded by
+     * 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+     *
+     * Notes on implementation strategy:
+     *
+     * Using schoolbook multiplication. Karatsuba would save a little in some
+     * cost models.
+     *
+     * Most multiplications by 2 and 19 are 32-bit precomputations; cheaper than
+     * 64-bit postcomputations.
+     *
+     * There is one remaining multiplication by 19 in the carry chain; one *19
+     * precomputation can be merged into this, but the resulting data flow is
+     * considerably less clean.
+     *
+     * There are 12 carries below. 10 of them are 2-way parallelizable and
+     * vectorizable. Can get away with 11 carries, but then data flow is much
+     * deeper.
+     *
+     * With tighter constraints on inputs can squeeze carries into int32.
+     */
+    public FieldElement multiply(FieldElement val) {
+        int[] g = ((Ed25519FieldElement)val).t;
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int g0 = g[0];
+        int g1 = g[1];
+        int g2 = g[2];
+        int g3 = g[3];
+        int g4 = g[4];
+        int g5 = g[5];
+        int g6 = g[6];
+        int g7 = g[7];
+        int g8 = g[8];
+        int g9 = g[9];
+        int g1_19 = 19 * g1; /* 1.959375*2^29 */
+        int g2_19 = 19 * g2; /* 1.959375*2^30; still ok */
+        int g3_19 = 19 * g3;
+        int g4_19 = 19 * g4;
+        int g5_19 = 19 * g5;
+        int g6_19 = 19 * g6;
+        int g7_19 = 19 * g7;
+        int g8_19 = 19 * g8;
+        int g9_19 = 19 * g9;
+        int f1_2 = 2 * f1;
+        int f3_2 = 2 * f3;
+        int f5_2 = 2 * f5;
+        int f7_2 = 2 * f7;
+        int f9_2 = 2 * f9;
+        long f0g0    = f0   * (long) g0;
+        long f0g1    = f0   * (long) g1;
+        long f0g2    = f0   * (long) g2;
+        long f0g3    = f0   * (long) g3;
+        long f0g4    = f0   * (long) g4;
+        long f0g5    = f0   * (long) g5;
+        long f0g6    = f0   * (long) g6;
+        long f0g7    = f0   * (long) g7;
+        long f0g8    = f0   * (long) g8;
+        long f0g9    = f0   * (long) g9;
+        long f1g0    = f1   * (long) g0;
+        long f1g1_2  = f1_2 * (long) g1;
+        long f1g2    = f1   * (long) g2;
+        long f1g3_2  = f1_2 * (long) g3;
+        long f1g4    = f1   * (long) g4;
+        long f1g5_2  = f1_2 * (long) g5;
+        long f1g6    = f1   * (long) g6;
+        long f1g7_2  = f1_2 * (long) g7;
+        long f1g8    = f1   * (long) g8;
+        long f1g9_38 = f1_2 * (long) g9_19;
+        long f2g0    = f2   * (long) g0;
+        long f2g1    = f2   * (long) g1;
+        long f2g2    = f2   * (long) g2;
+        long f2g3    = f2   * (long) g3;
+        long f2g4    = f2   * (long) g4;
+        long f2g5    = f2   * (long) g5;
+        long f2g6    = f2   * (long) g6;
+        long f2g7    = f2   * (long) g7;
+        long f2g8_19 = f2   * (long) g8_19;
+        long f2g9_19 = f2   * (long) g9_19;
+        long f3g0    = f3   * (long) g0;
+        long f3g1_2  = f3_2 * (long) g1;
+        long f3g2    = f3   * (long) g2;
+        long f3g3_2  = f3_2 * (long) g3;
+        long f3g4    = f3   * (long) g4;
+        long f3g5_2  = f3_2 * (long) g5;
+        long f3g6    = f3   * (long) g6;
+        long f3g7_38 = f3_2 * (long) g7_19;
+        long f3g8_19 = f3   * (long) g8_19;
+        long f3g9_38 = f3_2 * (long) g9_19;
+        long f4g0    = f4   * (long) g0;
+        long f4g1    = f4   * (long) g1;
+        long f4g2    = f4   * (long) g2;
+        long f4g3    = f4   * (long) g3;
+        long f4g4    = f4   * (long) g4;
+        long f4g5    = f4   * (long) g5;
+        long f4g6_19 = f4   * (long) g6_19;
+        long f4g7_19 = f4   * (long) g7_19;
+        long f4g8_19 = f4   * (long) g8_19;
+        long f4g9_19 = f4   * (long) g9_19;
+        long f5g0    = f5   * (long) g0;
+        long f5g1_2  = f5_2 * (long) g1;
+        long f5g2    = f5   * (long) g2;
+        long f5g3_2  = f5_2 * (long) g3;
+        long f5g4    = f5   * (long) g4;
+        long f5g5_38 = f5_2 * (long) g5_19;
+        long f5g6_19 = f5   * (long) g6_19;
+        long f5g7_38 = f5_2 * (long) g7_19;
+        long f5g8_19 = f5   * (long) g8_19;
+        long f5g9_38 = f5_2 * (long) g9_19;
+        long f6g0    = f6   * (long) g0;
+        long f6g1    = f6   * (long) g1;
+        long f6g2    = f6   * (long) g2;
+        long f6g3    = f6   * (long) g3;
+        long f6g4_19 = f6   * (long) g4_19;
+        long f6g5_19 = f6   * (long) g5_19;
+        long f6g6_19 = f6   * (long) g6_19;
+        long f6g7_19 = f6   * (long) g7_19;
+        long f6g8_19 = f6   * (long) g8_19;
+        long f6g9_19 = f6   * (long) g9_19;
+        long f7g0    = f7   * (long) g0;
+        long f7g1_2  = f7_2 * (long) g1;
+        long f7g2    = f7   * (long) g2;
+        long f7g3_38 = f7_2 * (long) g3_19;
+        long f7g4_19 = f7   * (long) g4_19;
+        long f7g5_38 = f7_2 * (long) g5_19;
+        long f7g6_19 = f7   * (long) g6_19;
+        long f7g7_38 = f7_2 * (long) g7_19;
+        long f7g8_19 = f7   * (long) g8_19;
+        long f7g9_38 = f7_2 * (long) g9_19;
+        long f8g0    = f8   * (long) g0;
+        long f8g1    = f8   * (long) g1;
+        long f8g2_19 = f8   * (long) g2_19;
+        long f8g3_19 = f8   * (long) g3_19;
+        long f8g4_19 = f8   * (long) g4_19;
+        long f8g5_19 = f8   * (long) g5_19;
+        long f8g6_19 = f8   * (long) g6_19;
+        long f8g7_19 = f8   * (long) g7_19;
+        long f8g8_19 = f8   * (long) g8_19;
+        long f8g9_19 = f8   * (long) g9_19;
+        long f9g0    = f9   * (long) g0;
+        long f9g1_38 = f9_2 * (long) g1_19;
+        long f9g2_19 = f9   * (long) g2_19;
+        long f9g3_38 = f9_2 * (long) g3_19;
+        long f9g4_19 = f9   * (long) g4_19;
+        long f9g5_38 = f9_2 * (long) g5_19;
+        long f9g6_19 = f9   * (long) g6_19;
+        long f9g7_38 = f9_2 * (long) g7_19;
+        long f9g8_19 = f9   * (long) g8_19;
+        long f9g9_38 = f9_2 * (long) g9_19;
+        long h0 = f0g0+f1g9_38+f2g8_19+f3g7_38+f4g6_19+f5g5_38+f6g4_19+f7g3_38+f8g2_19+f9g1_38;
+        long h1 = f0g1+f1g0   +f2g9_19+f3g8_19+f4g7_19+f5g6_19+f6g5_19+f7g4_19+f8g3_19+f9g2_19;
+        long h2 = f0g2+f1g1_2 +f2g0   +f3g9_38+f4g8_19+f5g7_38+f6g6_19+f7g5_38+f8g4_19+f9g3_38;
+        long h3 = f0g3+f1g2   +f2g1   +f3g0   +f4g9_19+f5g8_19+f6g7_19+f7g6_19+f8g5_19+f9g4_19;
+        long h4 = f0g4+f1g3_2 +f2g2   +f3g1_2 +f4g0   +f5g9_38+f6g8_19+f7g7_38+f8g6_19+f9g5_38;
+        long h5 = f0g5+f1g4   +f2g3   +f3g2   +f4g1   +f5g0   +f6g9_19+f7g8_19+f8g7_19+f9g6_19;
+        long h6 = f0g6+f1g5_2 +f2g4   +f3g3_2 +f4g2   +f5g1_2 +f6g0   +f7g9_38+f8g8_19+f9g7_38;
+        long h7 = f0g7+f1g6   +f2g5   +f3g4   +f4g3   +f5g2   +f6g1   +f7g0   +f8g9_19+f9g8_19;
+        long h8 = f0g8+f1g7_2 +f2g6   +f3g5_2 +f4g4   +f5g3_2 +f6g2   +f7g1_2 +f8g0   +f9g9_38;
+        long h9 = f0g9+f1g8   +f2g7   +f3g6   +f4g5   +f5g4   +f6g3   +f7g2   +f8g1   +f9g0   ;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+
+        /*
+        |h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38))
+          i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8
+        |h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19))
+          i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9
+        */
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+        /* |h0| <= 2^25 */
+        /* |h4| <= 2^25 */
+        /* |h1| <= 1.71*2^59 */
+        /* |h5| <= 1.71*2^59 */
+
+        carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
+        carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
+        /* |h1| <= 2^24; from now on fits into int32 */
+        /* |h5| <= 2^24; from now on fits into int32 */
+        /* |h2| <= 1.41*2^60 */
+        /* |h6| <= 1.41*2^60 */
+
+        carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
+        carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
+        /* |h2| <= 2^25; from now on fits into int32 unchanged */
+        /* |h6| <= 2^25; from now on fits into int32 unchanged */
+        /* |h3| <= 1.71*2^59 */
+        /* |h7| <= 1.71*2^59 */
+
+        carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
+        carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25;
+        /* |h3| <= 2^24; from now on fits into int32 unchanged */
+        /* |h7| <= 2^24; from now on fits into int32 unchanged */
+        /* |h4| <= 1.72*2^34 */
+        /* |h8| <= 1.41*2^60 */
+
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+        carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
+        /* |h4| <= 2^25; from now on fits into int32 unchanged */
+        /* |h8| <= 2^25; from now on fits into int32 unchanged */
+        /* |h5| <= 1.01*2^24 */
+        /* |h9| <= 1.71*2^59 */
+
+        carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
+        /* |h9| <= 2^24; from now on fits into int32 unchanged */
+        /* |h0| <= 1.1*2^39 */
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+        /* |h0| <= 2^25; from now on fits into int32 unchanged */
+        /* |h1| <= 1.01*2^24 */
+
+        int[] h = new int[10];
+        h[0] = (int) h0;
+        h[1] = (int) h1;
+        h[2] = (int) h2;
+        h[3] = (int) h3;
+        h[4] = (int) h4;
+        h[5] = (int) h5;
+        h[6] = (int) h6;
+        h[7] = (int) h7;
+        h[8] = (int) h8;
+        h[9] = (int) h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * h = f * f
+     * Can overlap h with f.
+     *
+     * Preconditions:
+     *    |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+     *
+     * Postconditions:
+     *    |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+     *
+     * See {@link Ed25519FieldElement#multiply(FieldElement)} for discussion
+     * of implementation strategy.
+     */
+    public FieldElement square() {
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int f0_2 = 2 * f0;
+        int f1_2 = 2 * f1;
+        int f2_2 = 2 * f2;
+        int f3_2 = 2 * f3;
+        int f4_2 = 2 * f4;
+        int f5_2 = 2 * f5;
+        int f6_2 = 2 * f6;
+        int f7_2 = 2 * f7;
+        int f5_38 = 38 * f5; /* 1.959375*2^30 */
+        int f6_19 = 19 * f6; /* 1.959375*2^30 */
+        int f7_38 = 38 * f7; /* 1.959375*2^30 */
+        int f8_19 = 19 * f8; /* 1.959375*2^30 */
+        int f9_38 = 38 * f9; /* 1.959375*2^30 */
+        long f0f0    = f0   * (long) f0;
+        long f0f1_2  = f0_2 * (long) f1;
+        long f0f2_2  = f0_2 * (long) f2;
+        long f0f3_2  = f0_2 * (long) f3;
+        long f0f4_2  = f0_2 * (long) f4;
+        long f0f5_2  = f0_2 * (long) f5;
+        long f0f6_2  = f0_2 * (long) f6;
+        long f0f7_2  = f0_2 * (long) f7;
+        long f0f8_2  = f0_2 * (long) f8;
+        long f0f9_2  = f0_2 * (long) f9;
+        long f1f1_2  = f1_2 * (long) f1;
+        long f1f2_2  = f1_2 * (long) f2;
+        long f1f3_4  = f1_2 * (long) f3_2;
+        long f1f4_2  = f1_2 * (long) f4;
+        long f1f5_4  = f1_2 * (long) f5_2;
+        long f1f6_2  = f1_2 * (long) f6;
+        long f1f7_4  = f1_2 * (long) f7_2;
+        long f1f8_2  = f1_2 * (long) f8;
+        long f1f9_76 = f1_2 * (long) f9_38;
+        long f2f2    = f2   * (long) f2;
+        long f2f3_2  = f2_2 * (long) f3;
+        long f2f4_2  = f2_2 * (long) f4;
+        long f2f5_2  = f2_2 * (long) f5;
+        long f2f6_2  = f2_2 * (long) f6;
+        long f2f7_2  = f2_2 * (long) f7;
+        long f2f8_38 = f2_2 * (long) f8_19;
+        long f2f9_38 = f2   * (long) f9_38;
+        long f3f3_2  = f3_2 * (long) f3;
+        long f3f4_2  = f3_2 * (long) f4;
+        long f3f5_4  = f3_2 * (long) f5_2;
+        long f3f6_2  = f3_2 * (long) f6;
+        long f3f7_76 = f3_2 * (long) f7_38;
+        long f3f8_38 = f3_2 * (long) f8_19;
+        long f3f9_76 = f3_2 * (long) f9_38;
+        long f4f4    = f4   * (long) f4;
+        long f4f5_2  = f4_2 * (long) f5;
+        long f4f6_38 = f4_2 * (long) f6_19;
+        long f4f7_38 = f4   * (long) f7_38;
+        long f4f8_38 = f4_2 * (long) f8_19;
+        long f4f9_38 = f4   * (long) f9_38;
+        long f5f5_38 = f5   * (long) f5_38;
+        long f5f6_38 = f5_2 * (long) f6_19;
+        long f5f7_76 = f5_2 * (long) f7_38;
+        long f5f8_38 = f5_2 * (long) f8_19;
+        long f5f9_76 = f5_2 * (long) f9_38;
+        long f6f6_19 = f6   * (long) f6_19;
+        long f6f7_38 = f6   * (long) f7_38;
+        long f6f8_38 = f6_2 * (long) f8_19;
+        long f6f9_38 = f6   * (long) f9_38;
+        long f7f7_38 = f7   * (long) f7_38;
+        long f7f8_38 = f7_2 * (long) f8_19;
+        long f7f9_76 = f7_2 * (long) f9_38;
+        long f8f8_19 = f8   * (long) f8_19;
+        long f8f9_38 = f8   * (long) f9_38;
+        long f9f9_38 = f9   * (long) f9_38;
+        long h0 = f0f0  +f1f9_76+f2f8_38+f3f7_76+f4f6_38+f5f5_38;
+        long h1 = f0f1_2+f2f9_38+f3f8_38+f4f7_38+f5f6_38;
+        long h2 = f0f2_2+f1f1_2 +f3f9_76+f4f8_38+f5f7_76+f6f6_19;
+        long h3 = f0f3_2+f1f2_2 +f4f9_38+f5f8_38+f6f7_38;
+        long h4 = f0f4_2+f1f3_4 +f2f2   +f5f9_76+f6f8_38+f7f7_38;
+        long h5 = f0f5_2+f1f4_2 +f2f3_2 +f6f9_38+f7f8_38;
+        long h6 = f0f6_2+f1f5_4 +f2f4_2 +f3f3_2 +f7f9_76+f8f8_19;
+        long h7 = f0f7_2+f1f6_2 +f2f5_2 +f3f4_2 +f8f9_38;
+        long h8 = f0f8_2+f1f7_4 +f2f6_2 +f3f5_4 +f4f4   +f9f9_38;
+        long h9 = f0f9_2+f1f8_2 +f2f7_2 +f3f6_2 +f4f5_2;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+
+        carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
+        carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
+
+        carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
+        carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
+
+        carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
+        carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25;
+
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+        carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
+
+        carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+
+        int[] h = new int[10];
+        h[0] = (int) h0;
+        h[1] = (int) h1;
+        h[2] = (int) h2;
+        h[3] = (int) h3;
+        h[4] = (int) h4;
+        h[5] = (int) h5;
+        h[6] = (int) h6;
+        h[7] = (int) h7;
+        h[8] = (int) h8;
+        h[9] = (int) h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * h = 2 * f * f
+     * Can overlap h with f.
+     *
+     * Preconditions:
+     *    |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+     *
+     * Postconditions:
+     *    |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+     *
+     * See {@link Ed25519FieldElement#multiply(FieldElement)} for discussion
+     * of implementation strategy.
+     */
+    public FieldElement squareAndDouble() {
+        int f0 = t[0];
+        int f1 = t[1];
+        int f2 = t[2];
+        int f3 = t[3];
+        int f4 = t[4];
+        int f5 = t[5];
+        int f6 = t[6];
+        int f7 = t[7];
+        int f8 = t[8];
+        int f9 = t[9];
+        int f0_2 = 2 * f0;
+        int f1_2 = 2 * f1;
+        int f2_2 = 2 * f2;
+        int f3_2 = 2 * f3;
+        int f4_2 = 2 * f4;
+        int f5_2 = 2 * f5;
+        int f6_2 = 2 * f6;
+        int f7_2 = 2 * f7;
+        int f5_38 = 38 * f5; /* 1.959375*2^30 */
+        int f6_19 = 19 * f6; /* 1.959375*2^30 */
+        int f7_38 = 38 * f7; /* 1.959375*2^30 */
+        int f8_19 = 19 * f8; /* 1.959375*2^30 */
+        int f9_38 = 38 * f9; /* 1.959375*2^30 */
+        long f0f0    = f0   * (long) f0;
+        long f0f1_2  = f0_2 * (long) f1;
+        long f0f2_2  = f0_2 * (long) f2;
+        long f0f3_2  = f0_2 * (long) f3;
+        long f0f4_2  = f0_2 * (long) f4;
+        long f0f5_2  = f0_2 * (long) f5;
+        long f0f6_2  = f0_2 * (long) f6;
+        long f0f7_2  = f0_2 * (long) f7;
+        long f0f8_2  = f0_2 * (long) f8;
+        long f0f9_2  = f0_2 * (long) f9;
+        long f1f1_2  = f1_2 * (long) f1;
+        long f1f2_2  = f1_2 * (long) f2;
+        long f1f3_4  = f1_2 * (long) f3_2;
+        long f1f4_2  = f1_2 * (long) f4;
+        long f1f5_4  = f1_2 * (long) f5_2;
+        long f1f6_2  = f1_2 * (long) f6;
+        long f1f7_4  = f1_2 * (long) f7_2;
+        long f1f8_2  = f1_2 * (long) f8;
+        long f1f9_76 = f1_2 * (long) f9_38;
+        long f2f2    = f2   * (long) f2;
+        long f2f3_2  = f2_2 * (long) f3;
+        long f2f4_2  = f2_2 * (long) f4;
+        long f2f5_2  = f2_2 * (long) f5;
+        long f2f6_2  = f2_2 * (long) f6;
+        long f2f7_2  = f2_2 * (long) f7;
+        long f2f8_38 = f2_2 * (long) f8_19;
+        long f2f9_38 = f2   * (long) f9_38;
+        long f3f3_2  = f3_2 * (long) f3;
+        long f3f4_2  = f3_2 * (long) f4;
+        long f3f5_4  = f3_2 * (long) f5_2;
+        long f3f6_2  = f3_2 * (long) f6;
+        long f3f7_76 = f3_2 * (long) f7_38;
+        long f3f8_38 = f3_2 * (long) f8_19;
+        long f3f9_76 = f3_2 * (long) f9_38;
+        long f4f4    = f4   * (long) f4;
+        long f4f5_2  = f4_2 * (long) f5;
+        long f4f6_38 = f4_2 * (long) f6_19;
+        long f4f7_38 = f4   * (long) f7_38;
+        long f4f8_38 = f4_2 * (long) f8_19;
+        long f4f9_38 = f4   * (long) f9_38;
+        long f5f5_38 = f5   * (long) f5_38;
+        long f5f6_38 = f5_2 * (long) f6_19;
+        long f5f7_76 = f5_2 * (long) f7_38;
+        long f5f8_38 = f5_2 * (long) f8_19;
+        long f5f9_76 = f5_2 * (long) f9_38;
+        long f6f6_19 = f6   * (long) f6_19;
+        long f6f7_38 = f6   * (long) f7_38;
+        long f6f8_38 = f6_2 * (long) f8_19;
+        long f6f9_38 = f6   * (long) f9_38;
+        long f7f7_38 = f7   * (long) f7_38;
+        long f7f8_38 = f7_2 * (long) f8_19;
+        long f7f9_76 = f7_2 * (long) f9_38;
+        long f8f8_19 = f8   * (long) f8_19;
+        long f8f9_38 = f8   * (long) f9_38;
+        long f9f9_38 = f9   * (long) f9_38;
+        long h0 = f0f0  +f1f9_76+f2f8_38+f3f7_76+f4f6_38+f5f5_38;
+        long h1 = f0f1_2+f2f9_38+f3f8_38+f4f7_38+f5f6_38;
+        long h2 = f0f2_2+f1f1_2 +f3f9_76+f4f8_38+f5f7_76+f6f6_19;
+        long h3 = f0f3_2+f1f2_2 +f4f9_38+f5f8_38+f6f7_38;
+        long h4 = f0f4_2+f1f3_4 +f2f2   +f5f9_76+f6f8_38+f7f7_38;
+        long h5 = f0f5_2+f1f4_2 +f2f3_2 +f6f9_38+f7f8_38;
+        long h6 = f0f6_2+f1f5_4 +f2f4_2 +f3f3_2 +f7f9_76+f8f8_19;
+        long h7 = f0f7_2+f1f6_2 +f2f5_2 +f3f4_2 +f8f9_38;
+        long h8 = f0f8_2+f1f7_4 +f2f6_2 +f3f5_4 +f4f4   +f9f9_38;
+        long h9 = f0f9_2+f1f8_2 +f2f7_2 +f3f6_2 +f4f5_2;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+
+        h0 += h0;
+        h1 += h1;
+        h2 += h2;
+        h3 += h3;
+        h4 += h4;
+        h5 += h5;
+        h6 += h6;
+        h7 += h7;
+        h8 += h8;
+        h9 += h9;
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+
+        carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
+        carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
+
+        carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
+        carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
+
+        carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
+        carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25;
+
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+        carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
+
+        carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+
+        int[] h = new int[10];
+        h[0] = (int) h0;
+        h[1] = (int) h1;
+        h[2] = (int) h2;
+        h[3] = (int) h3;
+        h[4] = (int) h4;
+        h[5] = (int) h5;
+        h[6] = (int) h6;
+        h[7] = (int) h7;
+        h[8] = (int) h8;
+        h[9] = (int) h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    public FieldElement invert() {
+        FieldElement t0, t1, t2, t3;
+
+        // z2 = z1^2^1
+        t0 = square();
+        for (int i = 1; i < 1; ++i) { // Don't remove this
+            t0 = t0.square();
+        }
+
+        // z8 = z2^2^2;
+        t1 = t0.square();
+        for (int i = 1; i < 2; ++i) {
+            t1 = t1.square();
+        }
+
+        // z9 = z1*z8
+        t1 = multiply(t1);
+
+        // z11 = z2*z9
+        t0 = t0.multiply(t1);
+
+        // z22 = z11^2^1
+        t2 = t0.square();
+        for (int i = 1; i < 1; ++i) { // Don't remove this
+            t2 = t2.square();
+        }
+
+        // z_5_0 = z9*z22
+        t1 = t1.multiply(t2);
+
+        // z_10_5 = z_5_0^2^5
+        t2 = t1.square();
+        for (int i = 1; i < 5; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_10_0 = z_10_5*z_5_0
+        t1 = t2.multiply(t1);
+
+        // z_20_10 = z_10_0^2^10
+        t2 = t1.square();
+        for (int i = 1; i < 10; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_20_0 = z_20_10*z_10_0
+        t2 = t2.multiply(t1);
+
+        // z_40_20 = z_20_0^2^20
+        t3 = t2.square();
+        for (int i = 1; i < 20; ++i) {
+            t3 = t3.square();
+        }
+
+        // z_40_0 = z_40_20*z_20_0
+        t2 = t3.multiply(t2);
+
+        // z_50_10 = z_40_0^2^10
+        t2 = t2.square();
+        for (int i = 1; i < 10; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_50_0 = z_50_10*z_10_0
+        t1 = t2.multiply(t1);
+
+        // z_100_50 = z_50_0^2^50
+        t2 = t1.square();
+        for (int i = 1; i < 50; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_100_0 = z_100_50*z_50_0
+        t2 = t2.multiply(t1);
+
+        // z_200_100 = z_100_0^2^100
+        t3 = t2.square();
+        for (int i = 1; i < 100; ++i) {
+            t3 = t3.square();
+        }
+
+        // z_200_0 = z_200_100*z_100_0
+        t2 = t3.multiply(t2);
+
+        // z_250_50 = z_200_0^2^50
+        t2 = t2.square();
+        for (int i = 1; i < 50; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_250_0 = z_250_50*z_50_0
+        t1 = t2.multiply(t1);
+
+        // z_255_5 = z_250_0^2^5
+        t1 = t1.square();
+        for (int i = 1; i < 5; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_255_21 = z_255_5*z11
+        return t1.multiply(t0);
+    }
+
+    public FieldElement pow22523() {
+        FieldElement t0, t1, t2;
+
+        // z2 = z1^2^1
+        t0 = square();
+        for (int i = 1; i < 1; ++i) { // Don't remove this
+            t0 = t0.square();
+        }
+
+        // z8 = z2^2^2;
+        t1 = t0.square();
+        for (int i = 1; i < 2; ++i) {
+            t1 = t1.square();
+        }
+
+        // z9 = z1*z8
+        t1 = multiply(t1);
+
+        // z11 = z2*z9
+        t0 = t0.multiply(t1);
+
+        // z22 = z11^2^1
+        t0 = t0.square();
+        for (int i = 1; i < 1; ++i) { // Don't remove this
+            t0 = t0.square();
+        }
+
+        // z_5_0 = z9*z22
+        t0 = t1.multiply(t0);
+
+        // z_10_5 = z_5_0^2^5
+        t1 = t0.square();
+        for (int i = 1; i < 5; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_10_0 = z_10_5*z_5_0
+        t0 = t1.multiply(t0);
+
+        // z_20_10 = z_10_0^2^10
+        t1 = t0.square();
+        for (int i = 1; i < 10; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_20_0 = z_20_10*z_10_0
+        t1 = t1.multiply(t0);
+
+        // z_40_20 = z_20_0^2^20
+        t2 = t1.square();
+        for (int i = 1; i < 20; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_40_0 = z_40_20*z_20_0
+        t1 = t2.multiply(t1);
+
+        // z_50_10 = z_40_0^2^10
+        t1 = t1.square();
+        for (int i = 1; i < 10; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_50_0 = z_50_10*z_10_0
+        t0 = t1.multiply(t0);
+
+        // z_100_50 = z_50_0^2^50
+        t1 = t0.square();
+        for (int i = 1; i < 50; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_100_0 = z_100_50*z_50_0
+        t1 = t1.multiply(t0);
+
+        // z_200_100 = z_100_0^2^100
+        t2 = t1.square();
+        for (int i = 1; i < 100; ++i) {
+            t2 = t2.square();
+        }
+
+        // z_200_0 = z_200_100*z_100_0
+        t1 = t2.multiply(t1);
+
+        // z_250_50 = z_200_0^2^50
+        t1 = t1.square();
+        for (int i = 1; i < 50; ++i) {
+            t1 = t1.square();
+        }
+
+        // z_250_0 = z_250_50*z_50_0
+        t0 = t1.multiply(t0);
+
+        // z_252_2 = z_250_0^2^2
+        t0 = t0.square();
+        for (int i = 1; i < 2; ++i) {
+            t0 = t0.square();
+        }
+
+        // z_252_3 = z_252_2*z1
+        return multiply(t0);
+    }
+
+    @Override
+    public int hashCode() {
+        return t.hashCode(); // TODO should this be something else?
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Ed25519FieldElement))
+            return false;
+        Ed25519FieldElement fe = (Ed25519FieldElement) obj;
+        // XXX why does direct byte[] comparison fail?
+        // TODO should this be constant time?
+        return TestUtils.getHex(toByteArray()).equals(TestUtils.getHex(fe.toByteArray()));
+    }
+
+    @Override
+    public String toString() {
+        return "[Ed25519FieldElement val="+TestUtils.getHex(toByteArray())+"]";
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519LittleEndianEncoding.java b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519LittleEndianEncoding.java
new file mode 100644
index 0000000000000000000000000000000000000000..aedbd9bd5c3f4308d62427ee179cdc98f5f96cf6
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519LittleEndianEncoding.java
@@ -0,0 +1,205 @@
+package net.i2p.crypto.eddsa.math.ed25519;
+
+import net.i2p.crypto.eddsa.math.Encoding;
+import net.i2p.crypto.eddsa.math.FieldElement;
+
+public class Ed25519LittleEndianEncoding extends Encoding {
+    /**
+     * Preconditions:<br>
+     *   |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.<br><br>
+     *
+     * Write p=2^255-19; q=floor(h/p).<br>
+     * Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
+     * <br><br>
+     * Proof:<br>
+     *   Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.<br>
+     *   Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4.
+     *   <br><br>
+     *   Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).<br>
+     *   Then 0 < y < 1.
+     *   <br><br>
+     *   Write r=h-pq.<br>
+     *   Have 0 <= r <= p-1=2^255-20.<br>
+     *   Thus 0 <= r+19(2^-255)r < r+19(2^-255)2^255 <= 2^255-1.
+     *   <br><br>
+     *   Write x=r+19(2^-255)r+y.<br>
+     *   Then 0 < x < 2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
+     *   <br><br>
+     *   Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))<br>
+     *   so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
+     */
+    public byte[] encode(FieldElement x) {
+        int[] h = ((Ed25519FieldElement)x).t;
+        int h0 = h[0];
+        int h1 = h[1];
+        int h2 = h[2];
+        int h3 = h[3];
+        int h4 = h[4];
+        int h5 = h[5];
+        int h6 = h[6];
+        int h7 = h[7];
+        int h8 = h[8];
+        int h9 = h[9];
+        int q;
+        int carry0;
+        int carry1;
+        int carry2;
+        int carry3;
+        int carry4;
+        int carry5;
+        int carry6;
+        int carry7;
+        int carry8;
+        int carry9;
+
+        q = (19 * h9 + (((int) 1) << 24)) >> 25;
+        q = (h0 + q) >> 26;
+        q = (h1 + q) >> 25;
+        q = (h2 + q) >> 26;
+        q = (h3 + q) >> 25;
+        q = (h4 + q) >> 26;
+        q = (h5 + q) >> 25;
+        q = (h6 + q) >> 26;
+        q = (h7 + q) >> 25;
+        q = (h8 + q) >> 26;
+        q = (h9 + q) >> 25;
+
+        /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */
+        h0 += 19 * q;
+        /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */
+
+        carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26;
+        carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25;
+        carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26;
+        carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25;
+        carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26;
+        carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25;
+        carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26;
+        carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25;
+        carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26;
+        carry9 = h9 >> 25;               h9 -= carry9 << 25;
+                        /* h10 = carry9 */
+
+        /*
+        Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
+        Have h0+...+2^230 h9 between 0 and 2^255-1;
+        evidently 2^255 h10-2^255 q = 0.
+        Goal: Output h0+...+2^230 h9.
+        */
+
+        byte[] s = new byte[32];
+        s[0] = (byte) (h0 >> 0);
+        s[1] = (byte) (h0 >> 8);
+        s[2] = (byte) (h0 >> 16);
+        s[3] = (byte) ((h0 >> 24) | (h1 << 2));
+        s[4] = (byte) (h1 >> 6);
+        s[5] = (byte) (h1 >> 14);
+        s[6] = (byte) ((h1 >> 22) | (h2 << 3));
+        s[7] = (byte) (h2 >> 5);
+        s[8] = (byte) (h2 >> 13);
+        s[9] = (byte) ((h2 >> 21) | (h3 << 5));
+        s[10] = (byte) (h3 >> 3);
+        s[11] = (byte) (h3 >> 11);
+        s[12] = (byte) ((h3 >> 19) | (h4 << 6));
+        s[13] = (byte) (h4 >> 2);
+        s[14] = (byte) (h4 >> 10);
+        s[15] = (byte) (h4 >> 18);
+        s[16] = (byte) (h5 >> 0);
+        s[17] = (byte) (h5 >> 8);
+        s[18] = (byte) (h5 >> 16);
+        s[19] = (byte) ((h5 >> 24) | (h6 << 1));
+        s[20] = (byte) (h6 >> 7);
+        s[21] = (byte) (h6 >> 15);
+        s[22] = (byte) ((h6 >> 23) | (h7 << 3));
+        s[23] = (byte) (h7 >> 5);
+        s[24] = (byte) (h7 >> 13);
+        s[25] = (byte) ((h7 >> 21) | (h8 << 4));
+        s[26] = (byte) (h8 >> 4);
+        s[27] = (byte) (h8 >> 12);
+        s[28] = (byte) ((h8 >> 20) | (h9 << 6));
+        s[29] = (byte) (h9 >> 2);
+        s[30] = (byte) (h9 >> 10);
+        s[31] = (byte) (h9 >> 18);
+        return s;
+    }
+
+    private static long load_3(byte[] in, int offset) {
+        int result = in[offset++] & 0xff;
+        result |= (in[offset++] & 0xff) << 8;
+        result |= (in[offset] & 0xff) << 16;
+        return result;
+    }
+
+    private static long load_4(byte[] in, int offset) {
+        int result = in[offset++] & 0xff;
+        result |= (in[offset++] & 0xff) << 8;
+        result |= (in[offset++] & 0xff) << 16;
+        result |= in[offset] << 24;
+        return ((long)result) & 0xffffffffL;
+    }
+
+    /**
+     * Ignores top bit.
+     */
+    public FieldElement decode(byte[] in) {
+        long h0 = load_4(in, 0);
+        long h1 = load_3(in, 4) << 6;
+        long h2 = load_3(in, 7) << 5;
+        long h3 = load_3(in, 10) << 3;
+        long h4 = load_3(in, 13) << 2;
+        long h5 = load_4(in, 16);
+        long h6 = load_3(in, 20) << 7;
+        long h7 = load_3(in, 23) << 5;
+        long h8 = load_3(in, 26) << 4;
+        long h9 = (load_3(in, 29) & 8388607) << 2;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+
+        carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
+        carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
+        carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
+        carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
+        carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25;
+
+        carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
+        carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
+        carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
+        carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
+        carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;
+
+        int[] h = new int[10];
+        h[0] = (int) h0;
+        h[1] = (int) h1;
+        h[2] = (int) h2;
+        h[3] = (int) h3;
+        h[4] = (int) h4;
+        h[5] = (int) h5;
+        h[6] = (int) h6;
+        h[7] = (int) h7;
+        h[8] = (int) h8;
+        h[9] = (int) h9;
+        return new Ed25519FieldElement(f, h);
+    }
+
+    /**
+     * Return true if x is in {1,3,5,...,q-2}<br>
+     * Return false if x is in {0,2,4,...,q-1}<br><br>
+     *
+     * Preconditions:<br>
+     *    |x| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+     * @return true if x is in {1,3,5,...,q-2}, false otherwise.
+     */
+    public boolean isNegative(FieldElement x) {
+        byte[] s = encode(x);
+        return (s[0] & 1) != 0;
+    }
+
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOps.java b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOps.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e9ab9bd32382ed6fcede77d8473a38b27782810
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOps.java
@@ -0,0 +1,617 @@
+package net.i2p.crypto.eddsa.math.ed25519;
+
+import net.i2p.crypto.eddsa.math.ScalarOps;
+
+public class Ed25519ScalarOps implements ScalarOps {
+    private static long load_3(byte[] in, int offset) {
+        int result = in[offset++] & 0xff;
+        result |= (in[offset++] & 0xff) << 8;
+        result |= (in[offset] & 0xff) << 16;
+        return result;
+    }
+
+    private static long load_4(byte[] in, int offset) {
+        int result = in[offset++] & 0xff;
+        result |= (in[offset++] & 0xff) << 8;
+        result |= (in[offset++] & 0xff) << 16;
+        result |= in[offset] << 24;
+        return ((long)result) & 0xffffffffL;
+    }
+
+    /**
+     * Input:<br>
+     *   s[0]+256*s[1]+...+256^63*s[63] = s<br><br>
+     *
+     * Output:<br>
+     *   s[0]+256*s[1]+...+256^31*s[31] = s mod l<br>
+     *   where l = 2^252 + 27742317777372353535851937790883648493.
+     */
+    public byte[] reduce(byte[] s) {
+        long s0 = 2097151 & load_3(s, 0);
+        long s1 = 2097151 & (load_4(s, 2) >> 5);
+        long s2 = 2097151 & (load_3(s, 5) >> 2);
+        long s3 = 2097151 & (load_4(s, 7) >> 7);
+        long s4 = 2097151 & (load_4(s, 10) >> 4);
+        long s5 = 2097151 & (load_3(s, 13) >> 1);
+        long s6 = 2097151 & (load_4(s, 15) >> 6);
+        long s7 = 2097151 & (load_3(s, 18) >> 3);
+        long s8 = 2097151 & load_3(s, 21);
+        long s9 = 2097151 & (load_4(s, 23) >> 5);
+        long s10 = 2097151 & (load_3(s, 26) >> 2);
+        long s11 = 2097151 & (load_4(s, 28) >> 7);
+        long s12 = 2097151 & (load_4(s, 31) >> 4);
+        long s13 = 2097151 & (load_3(s, 34) >> 1);
+        long s14 = 2097151 & (load_4(s, 36) >> 6);
+        long s15 = 2097151 & (load_3(s, 39) >> 3);
+        long s16 = 2097151 & load_3(s, 42);
+        long s17 = 2097151 & (load_4(s, 44) >> 5);
+        long s18 = 2097151 & (load_3(s, 47) >> 2);
+        long s19 = 2097151 & (load_4(s, 49) >> 7);
+        long s20 = 2097151 & (load_4(s, 52) >> 4);
+        long s21 = 2097151 & (load_3(s, 55) >> 1);
+        long s22 = 2097151 & (load_4(s, 57) >> 6);
+        long s23 = (load_4(s, 60) >> 3);
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+        long carry10;
+        long carry11;
+        long carry12;
+        long carry13;
+        long carry14;
+        long carry15;
+        long carry16;
+
+        s11 += s23 * 666643;
+        s12 += s23 * 470296;
+        s13 += s23 * 654183;
+        s14 -= s23 * 997805;
+        s15 += s23 * 136657;
+        s16 -= s23 * 683901;
+        s23 = 0;
+
+        s10 += s22 * 666643;
+        s11 += s22 * 470296;
+        s12 += s22 * 654183;
+        s13 -= s22 * 997805;
+        s14 += s22 * 136657;
+        s15 -= s22 * 683901;
+        s22 = 0;
+
+        s9 += s21 * 666643;
+        s10 += s21 * 470296;
+        s11 += s21 * 654183;
+        s12 -= s21 * 997805;
+        s13 += s21 * 136657;
+        s14 -= s21 * 683901;
+        s21 = 0;
+
+        s8 += s20 * 666643;
+        s9 += s20 * 470296;
+        s10 += s20 * 654183;
+        s11 -= s20 * 997805;
+        s12 += s20 * 136657;
+        s13 -= s20 * 683901;
+        s20 = 0;
+
+        s7 += s19 * 666643;
+        s8 += s19 * 470296;
+        s9 += s19 * 654183;
+        s10 -= s19 * 997805;
+        s11 += s19 * 136657;
+        s12 -= s19 * 683901;
+        s19 = 0;
+
+        s6 += s18 * 666643;
+        s7 += s18 * 470296;
+        s8 += s18 * 654183;
+        s9 -= s18 * 997805;
+        s10 += s18 * 136657;
+        s11 -= s18 * 683901;
+        s18 = 0;
+
+        carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
+        carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
+        carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
+        carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21;
+
+        carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
+        carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
+        carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21;
+
+        s5 += s17 * 666643;
+        s6 += s17 * 470296;
+        s7 += s17 * 654183;
+        s8 -= s17 * 997805;
+        s9 += s17 * 136657;
+        s10 -= s17 * 683901;
+        s17 = 0;
+
+        s4 += s16 * 666643;
+        s5 += s16 * 470296;
+        s6 += s16 * 654183;
+        s7 -= s16 * 997805;
+        s8 += s16 * 136657;
+        s9 -= s16 * 683901;
+        s16 = 0;
+
+        s3 += s15 * 666643;
+        s4 += s15 * 470296;
+        s5 += s15 * 654183;
+        s6 -= s15 * 997805;
+        s7 += s15 * 136657;
+        s8 -= s15 * 683901;
+        s15 = 0;
+
+        s2 += s14 * 666643;
+        s3 += s14 * 470296;
+        s4 += s14 * 654183;
+        s5 -= s14 * 997805;
+        s6 += s14 * 136657;
+        s7 -= s14 * 683901;
+        s14 = 0;
+
+        s1 += s13 * 666643;
+        s2 += s13 * 470296;
+        s3 += s13 * 654183;
+        s4 -= s13 * 997805;
+        s5 += s13 * 136657;
+        s6 -= s13 * 683901;
+        s13 = 0;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
+
+        carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
+        carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
+
+        byte[] result = new byte[32];
+        result[0] = (byte) (s0 >> 0);
+        result[1] = (byte) (s0 >> 8);
+        result[2] = (byte) ((s0 >> 16) | (s1 << 5));
+        result[3] = (byte) (s1 >> 3);
+        result[4] = (byte) (s1 >> 11);
+        result[5] = (byte) ((s1 >> 19) | (s2 << 2));
+        result[6] = (byte) (s2 >> 6);
+        result[7] = (byte) ((s2 >> 14) | (s3 << 7));
+        result[8] = (byte) (s3 >> 1);
+        result[9] = (byte) (s3 >> 9);
+        result[10] = (byte) ((s3 >> 17) | (s4 << 4));
+        result[11] = (byte) (s4 >> 4);
+        result[12] = (byte) (s4 >> 12);
+        result[13] = (byte) ((s4 >> 20) | (s5 << 1));
+        result[14] = (byte) (s5 >> 7);
+        result[15] = (byte) ((s5 >> 15) | (s6 << 6));
+        result[16] = (byte) (s6 >> 2);
+        result[17] = (byte) (s6 >> 10);
+        result[18] = (byte) ((s6 >> 18) | (s7 << 3));
+        result[19] = (byte) (s7 >> 5);
+        result[20] = (byte) (s7 >> 13);
+        result[21] = (byte) (s8 >> 0);
+        result[22] = (byte) (s8 >> 8);
+        result[23] = (byte) ((s8 >> 16) | (s9 << 5));
+        result[24] = (byte) (s9 >> 3);
+        result[25] = (byte) (s9 >> 11);
+        result[26] = (byte) ((s9 >> 19) | (s10 << 2));
+        result[27] = (byte) (s10 >> 6);
+        result[28] = (byte) ((s10 >> 14) | (s11 << 7));
+        result[29] = (byte) (s11 >> 1);
+        result[30] = (byte) (s11 >> 9);
+        result[31] = (byte) (s11 >> 17);
+        return result;
+    }
+
+
+    /**
+     * Input:<br>
+     *   a[0]+256*a[1]+...+256^31*a[31] = a<br>
+     *   b[0]+256*b[1]+...+256^31*b[31] = b<br>
+     *   c[0]+256*c[1]+...+256^31*c[31] = c<br><br>
+     *
+     * Output:<br>
+     *   result[0]+256*result[1]+...+256^31*result[31] = (ab+c) mod l<br>
+     *   where l = 2^252 + 27742317777372353535851937790883648493.
+     */
+    public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) {
+        long a0 = 2097151 & load_3(a, 0);
+        long a1 = 2097151 & (load_4(a, 2) >> 5);
+        long a2 = 2097151 & (load_3(a, 5) >> 2);
+        long a3 = 2097151 & (load_4(a, 7) >> 7);
+        long a4 = 2097151 & (load_4(a, 10) >> 4);
+        long a5 = 2097151 & (load_3(a, 13) >> 1);
+        long a6 = 2097151 & (load_4(a, 15) >> 6);
+        long a7 = 2097151 & (load_3(a, 18) >> 3);
+        long a8 = 2097151 & load_3(a, 21);
+        long a9 = 2097151 & (load_4(a, 23) >> 5);
+        long a10 = 2097151 & (load_3(a, 26) >> 2);
+        long a11 = (load_4(a, 28) >> 7);
+        long b0 = 2097151 & load_3(b, 0);
+        long b1 = 2097151 & (load_4(b, 2) >> 5);
+        long b2 = 2097151 & (load_3(b, 5) >> 2);
+        long b3 = 2097151 & (load_4(b, 7) >> 7);
+        long b4 = 2097151 & (load_4(b, 10) >> 4);
+        long b5 = 2097151 & (load_3(b, 13) >> 1);
+        long b6 = 2097151 & (load_4(b, 15) >> 6);
+        long b7 = 2097151 & (load_3(b, 18) >> 3);
+        long b8 = 2097151 & load_3(b, 21);
+        long b9 = 2097151 & (load_4(b, 23) >> 5);
+        long b10 = 2097151 & (load_3(b, 26) >> 2);
+        long b11 = (load_4(b, 28) >> 7);
+        long c0 = 2097151 & load_3(c, 0);
+        long c1 = 2097151 & (load_4(c, 2) >> 5);
+        long c2 = 2097151 & (load_3(c, 5) >> 2);
+        long c3 = 2097151 & (load_4(c, 7) >> 7);
+        long c4 = 2097151 & (load_4(c, 10) >> 4);
+        long c5 = 2097151 & (load_3(c, 13) >> 1);
+        long c6 = 2097151 & (load_4(c, 15) >> 6);
+        long c7 = 2097151 & (load_3(c, 18) >> 3);
+        long c8 = 2097151 & load_3(c, 21);
+        long c9 = 2097151 & (load_4(c, 23) >> 5);
+        long c10 = 2097151 & (load_3(c, 26) >> 2);
+        long c11 = (load_4(c, 28) >> 7);
+        long s0;
+        long s1;
+        long s2;
+        long s3;
+        long s4;
+        long s5;
+        long s6;
+        long s7;
+        long s8;
+        long s9;
+        long s10;
+        long s11;
+        long s12;
+        long s13;
+        long s14;
+        long s15;
+        long s16;
+        long s17;
+        long s18;
+        long s19;
+        long s20;
+        long s21;
+        long s22;
+        long s23;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+        long carry10;
+        long carry11;
+        long carry12;
+        long carry13;
+        long carry14;
+        long carry15;
+        long carry16;
+        long carry17;
+        long carry18;
+        long carry19;
+        long carry20;
+        long carry21;
+        long carry22;
+
+        s0 = c0 + a0*b0;
+        s1 = c1 + a0*b1 + a1*b0;
+        s2 = c2 + a0*b2 + a1*b1 + a2*b0;
+        s3 = c3 + a0*b3 + a1*b2 + a2*b1 + a3*b0;
+        s4 = c4 + a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0;
+        s5 = c5 + a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0;
+        s6 = c6 + a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0;
+        s7 = c7 + a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0;
+        s8 = c8 + a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0;
+        s9 = c9 + a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0;
+        s10 = c10 + a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0;
+        s11 = c11 + a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0;
+        s12 = a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1;
+        s13 = a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2;
+        s14 = a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3;
+        s15 = a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4;
+        s16 = a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5;
+        s17 = a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6;
+        s18 = a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7;
+        s19 = a8*b11 + a9*b10 + a10*b9 + a11*b8;
+        s20 = a9*b11 + a10*b10 + a11*b9;
+        s21 = a10*b11 + a11*b10;
+        s22 = a11*b11;
+        s23 = 0;
+
+        carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
+        carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
+        carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
+        carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21;
+        carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21;
+        carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21;
+        carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21;
+
+        carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
+        carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
+        carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21;
+        carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21;
+        carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21;
+        carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21;
+
+        s11 += s23 * 666643;
+        s12 += s23 * 470296;
+        s13 += s23 * 654183;
+        s14 -= s23 * 997805;
+        s15 += s23 * 136657;
+        s16 -= s23 * 683901;
+        s23 = 0;
+
+        s10 += s22 * 666643;
+        s11 += s22 * 470296;
+        s12 += s22 * 654183;
+        s13 -= s22 * 997805;
+        s14 += s22 * 136657;
+        s15 -= s22 * 683901;
+        s22 = 0;
+
+        s9 += s21 * 666643;
+        s10 += s21 * 470296;
+        s11 += s21 * 654183;
+        s12 -= s21 * 997805;
+        s13 += s21 * 136657;
+        s14 -= s21 * 683901;
+        s21 = 0;
+
+        s8 += s20 * 666643;
+        s9 += s20 * 470296;
+        s10 += s20 * 654183;
+        s11 -= s20 * 997805;
+        s12 += s20 * 136657;
+        s13 -= s20 * 683901;
+        s20 = 0;
+
+        s7 += s19 * 666643;
+        s8 += s19 * 470296;
+        s9 += s19 * 654183;
+        s10 -= s19 * 997805;
+        s11 += s19 * 136657;
+        s12 -= s19 * 683901;
+        s19 = 0;
+
+        s6 += s18 * 666643;
+        s7 += s18 * 470296;
+        s8 += s18 * 654183;
+        s9 -= s18 * 997805;
+        s10 += s18 * 136657;
+        s11 -= s18 * 683901;
+        s18 = 0;
+
+        carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
+        carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
+        carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
+        carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21;
+
+        carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
+        carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
+        carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21;
+
+        s5 += s17 * 666643;
+        s6 += s17 * 470296;
+        s7 += s17 * 654183;
+        s8 -= s17 * 997805;
+        s9 += s17 * 136657;
+        s10 -= s17 * 683901;
+        s17 = 0;
+
+        s4 += s16 * 666643;
+        s5 += s16 * 470296;
+        s6 += s16 * 654183;
+        s7 -= s16 * 997805;
+        s8 += s16 * 136657;
+        s9 -= s16 * 683901;
+        s16 = 0;
+
+        s3 += s15 * 666643;
+        s4 += s15 * 470296;
+        s5 += s15 * 654183;
+        s6 -= s15 * 997805;
+        s7 += s15 * 136657;
+        s8 -= s15 * 683901;
+        s15 = 0;
+
+        s2 += s14 * 666643;
+        s3 += s14 * 470296;
+        s4 += s14 * 654183;
+        s5 -= s14 * 997805;
+        s6 += s14 * 136657;
+        s7 -= s14 * 683901;
+        s14 = 0;
+
+        s1 += s13 * 666643;
+        s2 += s13 * 470296;
+        s3 += s13 * 654183;
+        s4 -= s13 * 997805;
+        s5 += s13 * 136657;
+        s6 -= s13 * 683901;
+        s13 = 0;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
+
+        carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
+        carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
+        carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
+        carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
+        carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
+        carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
+        carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
+        carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
+        carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
+        carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
+        carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
+        carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
+
+        byte[] result = new byte[32];
+        result[0] = (byte) (s0 >> 0);
+        result[1] = (byte) (s0 >> 8);
+        result[2] = (byte) ((s0 >> 16) | (s1 << 5));
+        result[3] = (byte) (s1 >> 3);
+        result[4] = (byte) (s1 >> 11);
+        result[5] = (byte) ((s1 >> 19) | (s2 << 2));
+        result[6] = (byte) (s2 >> 6);
+        result[7] = (byte) ((s2 >> 14) | (s3 << 7));
+        result[8] = (byte) (s3 >> 1);
+        result[9] = (byte) (s3 >> 9);
+        result[10] = (byte) ((s3 >> 17) | (s4 << 4));
+        result[11] = (byte) (s4 >> 4);
+        result[12] = (byte) (s4 >> 12);
+        result[13] = (byte) ((s4 >> 20) | (s5 << 1));
+        result[14] = (byte) (s5 >> 7);
+        result[15] = (byte) ((s5 >> 15) | (s6 << 6));
+        result[16] = (byte) (s6 >> 2);
+        result[17] = (byte) (s6 >> 10);
+        result[18] = (byte) ((s6 >> 18) | (s7 << 3));
+        result[19] = (byte) (s7 >> 5);
+        result[20] = (byte) (s7 >> 13);
+        result[21] = (byte) (s8 >> 0);
+        result[22] = (byte) (s8 >> 8);
+        result[23] = (byte) ((s8 >> 16) | (s9 << 5));
+        result[24] = (byte) (s9 >> 3);
+        result[25] = (byte) (s9 >> 11);
+        result[26] = (byte) ((s9 >> 19) | (s10 << 2));
+        result[27] = (byte) (s10 >> 6);
+        result[28] = (byte) ((s10 >> 14) | (s11 << 7));
+        result[29] = (byte) (s11 >> 1);
+        result[30] = (byte) (s11 >> 9);
+        result[31] = (byte) (s11 >> 17);
+        return result;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAGenParameterSpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAGenParameterSpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..860bc5b9f88c5a510cb8f89cb13028551849cc08
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAGenParameterSpec.java
@@ -0,0 +1,21 @@
+package net.i2p.crypto.eddsa.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Implementation of AlgorithmParameterSpec that holds the name of a named
+ * EdDSA curve specification.
+ * @author str4d
+ *
+ */
+public class EdDSAGenParameterSpec implements AlgorithmParameterSpec {
+    private final String name;
+
+    public EdDSAGenParameterSpec(String stdName) {
+        name = stdName;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveSpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveSpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5d63c8418b7dacedec452b9ef2de21296427485
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveSpec.java
@@ -0,0 +1,24 @@
+package net.i2p.crypto.eddsa.spec;
+
+import net.i2p.crypto.eddsa.math.Curve;
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.math.ScalarOps;
+
+/**
+ * EdDSA Curve specification that can also be referred to by name.
+ * @author str4d
+ *
+ */
+public class EdDSANamedCurveSpec extends EdDSAParameterSpec {
+    private final String name;
+
+    public EdDSANamedCurveSpec(String name, Curve curve,
+            String hashAlgo, ScalarOps sc, GroupElement B) {
+        super(curve, hashAlgo, sc, B);
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..076e2e660d68b4354b49e11390bad92cddde0b22
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java
@@ -0,0 +1,50 @@
+package net.i2p.crypto.eddsa.spec;
+
+import java.util.Hashtable;
+
+import net.i2p.crypto.eddsa.Utils;
+import net.i2p.crypto.eddsa.math.Curve;
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding;
+import net.i2p.crypto.eddsa.math.ed25519.Ed25519ScalarOps;
+
+/**
+ * The named EdDSA curves.
+ * @author str4d
+ *
+ */
+public class EdDSANamedCurveTable {
+    public static final String CURVE_ED25519_SHA512 = "ed25519-sha-512";
+
+    private static final Field ed25519field = new Field(
+                    256, // b
+                    Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q
+                    new Ed25519LittleEndianEncoding());
+
+    private static final Curve ed25519curve = new Curve(ed25519field,
+            Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d
+            ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I
+
+    private static final EdDSANamedCurveSpec ed25519sha512 = new EdDSANamedCurveSpec(
+            CURVE_ED25519_SHA512,
+            ed25519curve,
+            "SHA-512", // H
+            new Ed25519ScalarOps(), // l
+            ed25519curve.createPoint( // B
+                    Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"),
+                    true)); // Precompute tables for B
+
+    private static final Hashtable<String, EdDSANamedCurveSpec> curves = new Hashtable<String, EdDSANamedCurveSpec>();
+
+    public static void defineCurve(String name, EdDSANamedCurveSpec curve) {
+        curves.put(name, curve);
+    }
+
+    static {
+        defineCurve(CURVE_ED25519_SHA512, ed25519sha512);
+    }
+
+    public static EdDSANamedCurveSpec getByName(String name) {
+        return curves.get(name);
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..f24677476df710c21f1a57b90df0f3b712eabc70
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java
@@ -0,0 +1,60 @@
+package net.i2p.crypto.eddsa.spec;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import net.i2p.crypto.eddsa.math.Curve;
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.math.ScalarOps;
+
+import java.io.Serializable;
+
+/**
+ * Parameter specification for an EdDSA algorithm.
+ * @author str4d
+ *
+ */
+public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable {
+    private static final long serialVersionUID = 8274987108472012L;
+    private final Curve curve;
+    private final String hashAlgo;
+    private final ScalarOps sc;
+    private final GroupElement B;
+
+    /**
+     *  @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong
+     */
+    public EdDSAParameterSpec(Curve curve, String hashAlgo,
+            ScalarOps sc, GroupElement B) {
+        try {
+            MessageDigest hash = MessageDigest.getInstance(hashAlgo);
+            // EdDSA hash function must produce 2b-bit output
+            if (curve.getField().getb()/4 != hash.getDigestLength())
+                throw new IllegalArgumentException("Hash output is not 2b-bit");
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("Unsupported hash algorithm");
+        }
+
+        this.curve = curve;
+        this.hashAlgo = hashAlgo;
+        this.sc = sc;
+        this.B = B;
+    }
+
+    public Curve getCurve() {
+        return curve;
+    }
+
+    public String getHashAlgorithm() {
+        return hashAlgo;
+    }
+
+    public ScalarOps getScalarOps() {
+        return sc;
+    }
+
+    public GroupElement getB() {
+        return B;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPrivateKeySpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPrivateKeySpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..f09585e56af110a88b388326c5fb57b0a215d998
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPrivateKeySpec.java
@@ -0,0 +1,79 @@
+package net.i2p.crypto.eddsa.spec;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+import net.i2p.crypto.eddsa.math.GroupElement;
+
+/**
+ * @author str4d
+ *
+ */
+public class EdDSAPrivateKeySpec implements KeySpec {
+    private final byte[] seed;
+    private final byte[] h;
+    private final byte[] a;
+    private final GroupElement A;
+    private final EdDSAParameterSpec spec;
+
+    /**
+     *  @throws IllegalArgumentException if hash algorithm is unsupported
+     */
+    public EdDSAPrivateKeySpec(byte[] seed, EdDSAParameterSpec spec) {
+        this.spec = spec;
+        this.seed = seed;
+
+        try {
+            MessageDigest hash = MessageDigest.getInstance(spec.getHashAlgorithm());
+            int b = spec.getCurve().getField().getb();
+
+            // H(k)
+            h = hash.digest(seed);
+
+            /*a = BigInteger.valueOf(2).pow(b-2);
+            for (int i=3;i<(b-2);i++) {
+                a = a.add(BigInteger.valueOf(2).pow(i).multiply(BigInteger.valueOf(Utils.bit(h,i))));
+            }*/
+            // Saves ~0.4ms per key when running signing tests.
+            // TODO: are these bitflips the same for any hash function?
+            h[0] &= 248;
+            h[(b/8)-1] &= 63;
+            h[(b/8)-1] |= 64;
+            a = Arrays.copyOfRange(h, 0, b/8);
+
+            A = spec.getB().scalarMultiply(a);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("Unsupported hash algorithm");
+        }
+    }
+
+    public EdDSAPrivateKeySpec(byte[] seed, byte[] h, byte[] a, GroupElement A, EdDSAParameterSpec spec) {
+        this.seed = seed;
+        this.h = h;
+        this.a = a;
+        this.A = A;
+        this.spec = spec;        
+    }
+
+    public byte[] getSeed() {
+        return seed;
+    }
+
+    public byte[] getH() {
+        return h;
+    }
+
+    public byte[] geta() {
+        return a;
+    }
+
+    public GroupElement getA() {
+        return A;
+    }
+
+    public EdDSAParameterSpec getParams() {
+        return spec;
+    }
+}
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPublicKeySpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPublicKeySpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a27edb718d509704848f6f53f69e325ca6186f6
--- /dev/null
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAPublicKeySpec.java
@@ -0,0 +1,48 @@
+package net.i2p.crypto.eddsa.spec;
+
+import java.security.spec.KeySpec;
+
+import net.i2p.crypto.eddsa.math.GroupElement;
+
+/**
+ * @author str4d
+ *
+ */
+public class EdDSAPublicKeySpec implements KeySpec {
+    private final GroupElement A;
+    private final GroupElement Aneg;
+    private final EdDSAParameterSpec spec;
+
+    /**
+     *  @throws IllegalArgumentException if key length is wrong
+     */
+    public EdDSAPublicKeySpec(byte[] pk, EdDSAParameterSpec spec) {
+        if (pk.length != spec.getCurve().getField().getb()/8)
+            throw new IllegalArgumentException("public-key length is wrong");
+
+        this.A = new GroupElement(spec.getCurve(), pk);
+        // Precompute -A for use in verification.
+        this.Aneg = A.negate();
+        Aneg.precompute(false);
+        this.spec = spec;
+    }
+
+    public EdDSAPublicKeySpec(GroupElement A, EdDSAParameterSpec spec) {
+        this.A = A;
+        this.Aneg = A.negate();
+        Aneg.precompute(false);
+        this.spec = spec;
+    }
+
+    public GroupElement getA() {
+        return A;
+    }
+
+    public GroupElement getNegativeA() {
+        return Aneg;
+    }
+
+    public EdDSAParameterSpec getParams() {
+        return spec;
+    }
+}