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; + } +}