diff --git a/common/src/main/java/org/whispersystems/curve25519/BaseJavaCurve25519Provider.java b/common/src/main/java/org/whispersystems/curve25519/BaseJavaCurve25519Provider.java index 18e7941..139accc 100644 --- a/common/src/main/java/org/whispersystems/curve25519/BaseJavaCurve25519Provider.java +++ b/common/src/main/java/org/whispersystems/curve25519/BaseJavaCurve25519Provider.java @@ -9,6 +9,7 @@ import org.whispersystems.curve25519.java.Sha512; import org.whispersystems.curve25519.java.curve_sigs; import org.whispersystems.curve25519.java.scalarmult; +import org.whispersystems.curve25519.java.gen_x; abstract class BaseJavaCurve25519Provider implements Curve25519Provider { @@ -74,13 +75,26 @@ public boolean verifySignature(byte[] publicKey, byte[] message, byte[] signatur } public byte[] calculateVrfSignature(byte[] random, byte[] privateKey, byte[] message) { - throw new AssertionError("NYI"); + byte[] result = new byte[96]; + byte[] random32 = new byte[32]; + if (random.length >= 32) { + System.arraycopy(random, 0, random32, 0, 32); + } else throw new IllegalArgumentException("too small random"); + if (gen_x.generalized_xveddsa_25519_sign(sha512provider, result, privateKey, message, random32)) { + return result; + } else { + throw new IllegalArgumentException(); + } } public byte[] verifyVrfSignature(byte[] publicKey, byte[] message, byte[] signature) throws VrfSignatureVerificationFailedException { - throw new AssertionError("NYI"); + byte[] result = new byte[32]; + if (gen_x.generalized_xveddsa_25519_verify(sha512provider, result, signature, publicKey, message) != 0) { + throw new VrfSignatureVerificationFailedException(); + } + return result; } public byte[] getRandom(int length) { diff --git a/common/src/main/java/org/whispersystems/curve25519/Curve25519.java b/common/src/main/java/org/whispersystems/curve25519/Curve25519.java index 78f7b25..ebbb581 100644 --- a/common/src/main/java/org/whispersystems/curve25519/Curve25519.java +++ b/common/src/main/java/org/whispersystems/curve25519/Curve25519.java @@ -140,6 +140,8 @@ public byte[] calculateVrfSignature(byte[] privateKey, byte[] message) { * @param message The message that was signed. * @param signature The unique signature to verify. * + * @throws VrfSignatureVerificationFailedException when verification is failed + * * @return The vrf for this signature. */ public byte[] verifyVrfSignature(byte[] publicKey, byte[] message, byte[] signature) diff --git a/common/src/main/java/org/whispersystems/curve25519/java/elligator.java b/common/src/main/java/org/whispersystems/curve25519/java/elligator.java new file mode 100644 index 0000000..2ae2770 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/elligator.java @@ -0,0 +1,74 @@ +package org.whispersystems.curve25519.java; + +public class elligator { + public static int legendre_is_nonsquare(int[] in) + { + int[] temp = new int[10]; + byte[] bytes = new byte[32]; + fe_pow22523.fe_pow22523(temp, in); /* temp = in^((q-5)/8) */ + fe_sq.fe_sq(temp, temp); /* in^((q-5)/4) */ + fe_sq.fe_sq(temp, temp); /* in^((q-5)/2) */ + fe_mul.fe_mul(temp, temp, in); /* in^((q-3)/2) */ + fe_mul.fe_mul(temp, temp, in); /* in^((q-1)/2) */ + + /* temp is now the Legendre symbol: + * 1 = square + * 0 = input is zero + * -1 = nonsquare + */ + fe_tobytes.fe_tobytes(bytes, temp); + return 1 & bytes[31]; + } + + public static void elligator(int[] u, int[] r) + { + /* r = input + * gen_x = -A/(1+2r^2) # 2 is nonsquare + * e = (gen_x^3 + Ax^2 + gen_x)^((q-1)/2) # legendre symbol + * if e == 1 (square) or e == 0 (because gen_x == 0 and 2r^2 + 1 == 0) + * u = gen_x + * if e == -1 (nonsquare) + * u = -gen_x - A + */ + int[] A = new int[10], one = new int[10], twor2 = new int[10], twor2plus1 = new int[10], twor2plus1inv = new int[10]; + int[] x = new int[10], e = new int[10], Atemp = new int[10], uneg = new int[10]; + int nonsquare; + + fe_1.fe_1(one); + fe_0.fe_0(A); + A[0] = 486662; /* A = 486662 */ + + fe_sq2.fe_sq2(twor2, r); /* 2r^2 */ + fe_add.fe_add(twor2plus1, twor2, one); /* 1+2r^2 */ + fe_invert.fe_invert(twor2plus1inv, twor2plus1); /* 1/(1+2r^2) */ + fe_mul.fe_mul(x, twor2plus1inv, A); /* A/(1+2r^2) */ + fe_neg.fe_neg(x, x); /* gen_x = -A/(1+2r^2) */ + + fe_mont_rhs.fe_mont_rhs(e, x); /* e = gen_x^3 + Ax^2 + gen_x */ + nonsquare = legendre_is_nonsquare(e); + + fe_0.fe_0(Atemp); + fe_cmov.fe_cmov(Atemp, A, nonsquare); /* 0, or A if nonsquare */ + fe_add.fe_add(u, x, Atemp); /* gen_x, or gen_x+A if nonsquare */ + fe_neg.fe_neg(uneg, u); /* -gen_x, or -gen_x-A if nonsquare */ + fe_cmov.fe_cmov(u, uneg, nonsquare); /* gen_x, or -gen_x-A if nonsquare */ + } + + public static void hash_to_point(Sha512 sha512provider, ge_p3 p, byte[] in) + { + byte[] hash = new byte[64]; + int[] h = new int[10], u = new int[10]; + ge_p3 p3 = new ge_p3(); + + sha512provider.calculateDigest(hash, in, in.length); + + /* take the high bit as Edwards sign bit */ + byte sign_bit = (byte)((hash[31] & 0x80) >> 7); + hash[31] &= 0x7F; + fe_frombytes.fe_frombytes(h, hash); + elligator(u, h); + + ge_montx_to_p3.ge_montx_to_p3(p3, u, sign_bit); + ge_scalarmult_cofactor.ge_scalarmult_cofactor(p, p3); + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/fe_isequal.java b/common/src/main/java/org/whispersystems/curve25519/java/fe_isequal.java new file mode 100644 index 0000000..1d4f7fb --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/fe_isequal.java @@ -0,0 +1,15 @@ +package org.whispersystems.curve25519.java; + +public class fe_isequal { + /* + return 1 if f == g + return 0 if f != g + */ + public static int fe_isequal(int[] f, int[] g) + { + int[] h = new int[10]; + fe_sub.fe_sub(h, f, g); + return 1 & (((fe_isnonzero.fe_isnonzero(h) & 0xff) - 1) >> 8); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/fe_isreduced.java b/common/src/main/java/org/whispersystems/curve25519/java/fe_isreduced.java new file mode 100644 index 0000000..d3fca92 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/fe_isreduced.java @@ -0,0 +1,14 @@ +package org.whispersystems.curve25519.java; + +public class fe_isreduced { + public static boolean fe_isreduced(byte[] s) + { + int[] f = new int[10]; + byte[] strict = new byte[32]; + + fe_frombytes.fe_frombytes(f, s); + fe_tobytes.fe_tobytes(strict, f); + return crypto_verify_32.crypto_verify_32(strict, s) == 0; + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/fe_mont_rhs.java b/common/src/main/java/org/whispersystems/curve25519/java/fe_mont_rhs.java new file mode 100644 index 0000000..fdf5f9e --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/fe_mont_rhs.java @@ -0,0 +1,18 @@ +package org.whispersystems.curve25519.java; + +public class fe_mont_rhs { + public static void fe_mont_rhs(int[] v2, int[] u) { + int[] A = new int[10], one= new int[10]; + int[] u2= new int[10], Au= new int[10], inner= new int[10]; + + fe_1.fe_1(one); + fe_0.fe_0(A); + A[0] = 486662; /* A = 486662 */ + + fe_sq.fe_sq(u2, u); /* u^2 */ + fe_mul.fe_mul(Au, A, u); /* Au */ + fe_add.fe_add(inner, u2, Au); /* u^2 + Au */ + fe_add.fe_add(inner, inner, one); /* u^2 + Au + 1 */ + fe_mul.fe_mul(v2, u, inner); /* u(u^2 + Au + 1) */ + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/fe_montx_to_edy.java b/common/src/main/java/org/whispersystems/curve25519/java/fe_montx_to_edy.java new file mode 100644 index 0000000..2e0e80c --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/fe_montx_to_edy.java @@ -0,0 +1,20 @@ +package org.whispersystems.curve25519.java; + +public class fe_montx_to_edy { + public static void fe_montx_to_edy(int[] y, int[] u) + { + /* + y = (u - 1) / (u + 1) + + NOTE: u=-1 is converted to y=0 since fe_invert is mod-exp + */ + int[] one = new int[10], um1 = new int[10], up1 = new int[10]; + + fe_1.fe_1(one); + fe_sub.fe_sub(um1, u, one); + fe_add.fe_add(up1, u, one); + fe_invert.fe_invert(up1, up1); + fe_mul.fe_mul(y, um1, up1); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/fe_sqrt.java b/common/src/main/java/org/whispersystems/curve25519/java/fe_sqrt.java new file mode 100644 index 0000000..eac6d39 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/fe_sqrt.java @@ -0,0 +1,34 @@ +package org.whispersystems.curve25519.java; + +public class fe_sqrt { + + /* sqrt(-1) */ + public static byte[] i_bytes = { + (byte)0xb0, (byte)0xa0, 0x0e, 0x4a, 0x27, 0x1b, (byte)0xee, (byte)0xc4, + 0x78, (byte)0xe4, 0x2f, (byte)0xad, 0x06, 0x18, 0x43, 0x2f, + (byte)0xa7, (byte)0xd7, (byte)0xfb, 0x3d, (byte)0x99, 0x00, 0x4d, 0x2b, + 0x0b, (byte)0xdf, (byte)0xc1, 0x4f, (byte)0x80, 0x24, (byte)0x83, 0x2b + }; + + /* Preconditions: a is square or zero */ + + public static void fe_sqrt(int[] out, int[] a) + { + int[] exp = new int[10], b = new int[10], b2 = new int[10], bi = new int[10], i = new int[10]; + + fe_frombytes.fe_frombytes(i, i_bytes); + fe_pow22523.fe_pow22523(exp, a); /* b = a^(q-5)/8 */ + + + fe_mul.fe_mul(b, a, exp); /* b = a * a^(q-5)/8 */ + fe_sq.fe_sq(b2, b); /* b^2 = a * a^(q-1)/4 */ + + /* note b^4 == a^2, so b^2 == a or -a + * if b^2 != a, multiply it by sqrt(-1) */ + fe_mul.fe_mul(bi, b, i); + fe_cmov.fe_cmov(b, bi, 1 ^ fe_isequal.fe_isequal(b2, a)); + fe_copy.fe_copy(out, b); + + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_isneutral.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_isneutral.java new file mode 100644 index 0000000..0921993 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_isneutral.java @@ -0,0 +1,17 @@ +package org.whispersystems.curve25519.java; + +public class ge_isneutral { + /* + return 1 if p is the neutral point + return 0 otherwise + */ + + public static boolean ge_isneutral(ge_p3 p) + { + int[] zero = new int[10]; + fe_0.fe_0(zero); + + /* Check if p == neutral element == (0, 1) */ + return (fe_isequal.fe_isequal(p.X, zero) & fe_isequal.fe_isequal(p.Y, p.Z)) == 1; + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_montx_to_p3.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_montx_to_p3.java new file mode 100644 index 0000000..7b5c4d6 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_montx_to_p3.java @@ -0,0 +1,39 @@ +package org.whispersystems.curve25519.java; + +public class ge_montx_to_p3 { + /* sqrt(-(A+2)) */ + private static byte[] A_bytes = { + 0x06, 0x7e, 0x45, (byte)0xff, (byte)0xaa, 0x04, 0x6e, (byte)0xcc, + (byte)0x82, 0x1a, 0x7d, 0x4b, (byte)0xd1, (byte)0xd3, (byte)0xa1, (byte)0xc5, + 0x7e, 0x4f, (byte)0xfc, 0x03, (byte)0xdc, 0x08, 0x7b, (byte)0xd2, + (byte)0xbb, 0x06, (byte)0xa0, 0x60, (byte)0xf4, (byte)0xed, 0x26, 0x0f + }; + + public static void ge_montx_to_p3(ge_p3 p, int[] u, byte ed_sign_bit) + { + int[] x = new int[10], y = new int[10], A = new int[10], v = new int[10], v2 = new int[10], iv = new int[10], nx = new int[10]; + + fe_frombytes.fe_frombytes(A, A_bytes); + + /* given u, recover edwards y */ + /* given u, recover v */ + /* given u and v, recover edwards gen_x */ + + fe_montx_to_edy.fe_montx_to_edy(y, u); /* y = (u - 1) / (u + 1) */ + + fe_mont_rhs.fe_mont_rhs(v2, u); /* v^2 = u(u^2 + Au + 1) */ + fe_sqrt.fe_sqrt(v, v2); /* v = sqrt(v^2) */ + + fe_mul.fe_mul(x, u, A); /* gen_x = u * sqrt(-(A+2)) */ + fe_invert.fe_invert(iv, v); /* 1/v */ + fe_mul.fe_mul(x, x, iv); /* gen_x = (u/v) * sqrt(-(A+2)) */ + + fe_neg.fe_neg(nx, x); /* negate gen_x to match sign bit */ + fe_cmov.fe_cmov(x, nx, fe_isnegative.fe_isnegative(x) ^ ed_sign_bit); + + fe_copy.fe_copy(p.X, x); + fe_copy.fe_copy(p.Y, y); + fe_1.fe_1(p.Z); + fe_mul.fe_mul(p.T, p.X, p.Y); + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_neg.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_neg.java new file mode 100644 index 0000000..9d1eb89 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_neg.java @@ -0,0 +1,11 @@ +package org.whispersystems.curve25519.java; + +public class ge_neg { + public static void ge_neg(ge_p3 r, ge_p3 p) + { + fe_neg.fe_neg(r.X, p.X); + fe_copy.fe_copy(r.Y, p.Y); + fe_copy.fe_copy(r.Z, p.Z); + fe_neg.fe_neg(r.T, p.T); + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_p3_add.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_p3_add.java new file mode 100644 index 0000000..77ce114 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_p3_add.java @@ -0,0 +1,14 @@ +package org.whispersystems.curve25519.java; + +public class ge_p3_add { + public static void ge_p3_add(ge_p3 r, ge_p3 p, ge_p3 q) + { + ge_cached p_cached = new ge_cached(); + ge_p1p1 r_p1p1 = new ge_p1p1(); + + ge_p3_to_cached.ge_p3_to_cached(p_cached, p); + ge_add.ge_add(r_p1p1, q, p_cached); + ge_p1p1_to_p3.ge_p1p1_to_p3(r, r_p1p1); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult.java new file mode 100644 index 0000000..6ecaf3b --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult.java @@ -0,0 +1,131 @@ +package org.whispersystems.curve25519.java; + +import static org.whispersystems.curve25519.java.ge_scalarmult_base.equal; +import static org.whispersystems.curve25519.java.ge_scalarmult_base.negative; + +public class ge_scalarmult { + + static void cmov(ge_cached t, ge_cached u, int b) { + fe_cmov.fe_cmov(t.YplusX, u.YplusX, b); + fe_cmov.fe_cmov(t.YminusX, u.YminusX, b); + fe_cmov.fe_cmov(t.Z, u.Z, b); + fe_cmov.fe_cmov(t.T2d, u.T2d, b); + } + + static void select(ge_cached t, ge_cached[] pre, byte b) { + ge_cached minust = new ge_cached(); + int bnegative = negative(b); + int babs = b - (((-bnegative) & b) << 1); + + fe_1.fe_1(t.YplusX); + fe_1.fe_1(t.YminusX); + fe_1.fe_1(t.Z); + fe_0.fe_0(t.T2d); + + cmov(t, pre[0], equal((byte) babs, (byte) 1)); + cmov(t, pre[1], equal((byte) babs, (byte) 2)); + cmov(t, pre[2], equal((byte) babs, (byte) 3)); + cmov(t, pre[3], equal((byte) babs, (byte) 4)); + cmov(t, pre[4], equal((byte) babs, (byte) 5)); + cmov(t, pre[5], equal((byte) babs, (byte) 6)); + cmov(t, pre[6], equal((byte) babs, (byte) 7)); + cmov(t, pre[7], equal((byte) babs, (byte) 8)); + fe_copy.fe_copy(minust.YplusX, t.YminusX); + fe_copy.fe_copy(minust.YminusX, t.YplusX); + fe_copy.fe_copy(minust.Z, t.Z); + fe_neg.fe_neg(minust.T2d, t.T2d); + cmov(t, minust, bnegative); + } + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + + public static void ge_scalarmult(ge_p3 h, byte[] a, ge_p3 A) { + byte[] e = new byte[64]; + byte carry; + ge_p1p1 r = new ge_p1p1(); + ge_p2 s = new ge_p2(); + ge_p3 t0 = new ge_p3(), t1 = new ge_p3(), t2 = new ge_p3(); + ge_cached t = new ge_cached(); + ge_cached[] pre = new ge_cached[8]; + for (int i = 0; i < 8; i++) { + pre[i] = new ge_cached(); + } + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (byte) ((a[i] >>> 0) & 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 */ + + carry = 0; + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = (byte) (e[i] + 8); + carry >>= 4; + e[i] -= carry << 4; + } + e[63] += carry; + /* each e[i] is between -8 and 8 */ + + // Precomputation: + ge_p3_to_cached.ge_p3_to_cached(pre[0], A); // A + + ge_p3_dbl.ge_p3_dbl(r, A); + ge_p1p1_to_p3.ge_p1p1_to_p3(t0, r); + ge_p3_to_cached.ge_p3_to_cached(pre[1], t0); // 2A + + ge_add.ge_add(r, A, pre[1]); + ge_p1p1_to_p3.ge_p1p1_to_p3(t1, r); + ge_p3_to_cached.ge_p3_to_cached(pre[2], t1); // 3A + + ge_p3_dbl.ge_p3_dbl(r, t0); + ge_p1p1_to_p3.ge_p1p1_to_p3(t0, r); + ge_p3_to_cached.ge_p3_to_cached(pre[3], t0); // 4A + + ge_add.ge_add(r, A, pre[3]); + ge_p1p1_to_p3.ge_p1p1_to_p3(t2, r); + ge_p3_to_cached.ge_p3_to_cached(pre[4], t2); // 5A + + ge_p3_dbl.ge_p3_dbl(r, t1); + ge_p1p1_to_p3.ge_p1p1_to_p3(t1, r); + ge_p3_to_cached.ge_p3_to_cached(pre[5], t1); // 6A + + ge_add.ge_add(r, A, pre[5]); + ge_p1p1_to_p3.ge_p1p1_to_p3(t1, r); + ge_p3_to_cached.ge_p3_to_cached(pre[6], t1); // 7A + + ge_p3_dbl.ge_p3_dbl(r, t0); + ge_p1p1_to_p3.ge_p1p1_to_p3(t0, r); + ge_p3_to_cached.ge_p3_to_cached(pre[7], t0); // 8A + + ge_p3_0.ge_p3_0(h); + + for (i = 63; i > 0; i--) { + select(t, pre, e[i]); + ge_add.ge_add(r, h, t); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p3.ge_p1p1_to_p3(h, r); + } + select(t, pre, e[0]); + ge_add.ge_add(r, h, t); + ge_p1p1_to_p3.ge_p1p1_to_p3(h, r); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult_cofactor.java b/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult_cofactor.java new file mode 100644 index 0000000..963ba26 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/ge_scalarmult_cofactor.java @@ -0,0 +1,22 @@ +package org.whispersystems.curve25519.java; + +public class ge_scalarmult_cofactor { + /* + return 8 * p + */ + public static void ge_scalarmult_cofactor(ge_p3 q, ge_p3 p) + { + ge_p1p1 p1p1 = new ge_p1p1(); + ge_p2 p2 = new ge_p2(); + + ge_p3_dbl.ge_p3_dbl(p1p1, p); + ge_p1p1_to_p2.ge_p1p1_to_p2(p2, p1p1); + + ge_p2_dbl.ge_p2_dbl(p1p1, p2); + ge_p1p1_to_p2.ge_p1p1_to_p2(p2, p1p1); + + ge_p2_dbl.ge_p2_dbl(p1p1, p2); + ge_p1p1_to_p3.ge_p1p1_to_p3(q, p1p1); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/gen_labelset.java b/common/src/main/java/org/whispersystems/curve25519/java/gen_labelset.java new file mode 100644 index 0000000..3d49652 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/gen_labelset.java @@ -0,0 +1,86 @@ +package org.whispersystems.curve25519.java; + +import java.nio.ByteBuffer; + +public class gen_labelset { + final static int LABELSETMAXLEN = 512; + final static int LABELMAXLEN = 128; + + final static byte[] B_bytes = { + 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + }; + + public static boolean labelset_is_empty(byte[] bb) { + return bb.length == 3; + } + + public static boolean labelset_validate(byte[] labelset) + { + if (labelset == null) + return false; + if (labelset.length < 3 || labelset.length > LABELSETMAXLEN) + return false; + + int num_labels = labelset[0]; + int offset = 1; + for (int count = 0; count < num_labels; count++) { + int label_len = labelset[offset]; + offset += 1 + label_len; + if (offset > labelset.length) + return false; + } + return offset == labelset.length; + } + + + public static byte[] labelset_new(String protocol_name, + byte[] customization_label) { + if (LABELSETMAXLEN < 3 + protocol_name.length() + customization_label.length) + throw new IllegalArgumentException(); + if (protocol_name.length() > LABELMAXLEN) + throw new IllegalArgumentException(); + if (customization_label.length > LABELMAXLEN) + throw new IllegalArgumentException(); + + byte[] protocol_name_bytes = protocol_name.getBytes(); + + ByteBuffer byteBuffer = ByteBuffer.allocate(3 + protocol_name_bytes.length + customization_label.length); + byteBuffer.put((byte)2); + byteBuffer.put((byte)protocol_name_bytes.length); + byteBuffer.put(protocol_name_bytes); + if (byteBuffer.position() < LABELSETMAXLEN) { + byteBuffer.put((byte)customization_label.length); + } + + byteBuffer.put(customization_label); + + assert byteBuffer.position() == 3 + protocol_name.length() + customization_label.length; + + return byteBuffer.array(); + } + + public static byte[] labelset_add(byte[] labelset, String label) + { + if (labelset.length > LABELSETMAXLEN) + throw new IllegalStateException(); + if (labelset.length >= LABELMAXLEN || labelset.length + label.length() + 1 > LABELSETMAXLEN) + throw new IllegalStateException(); + if (labelset.length < 3 || LABELSETMAXLEN < 4) + throw new IllegalStateException(); + if (label.length() > LABELMAXLEN) + throw new IllegalStateException(); + + ByteBuffer bb = ByteBuffer.allocate(labelset.length + label.length() + 1); + bb.put((byte)(labelset[0]+1)); + bb.put(labelset, 1, labelset.length - 1); + bb.put((byte)label.getBytes().length); + bb.put(label.getBytes()); + + assert bb.position() < LABELSETMAXLEN; + + return bb.array(); + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/gen_x.java b/common/src/main/java/org/whispersystems/curve25519/java/gen_x.java new file mode 100644 index 0000000..07ab606 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/gen_x.java @@ -0,0 +1,79 @@ +package org.whispersystems.curve25519.java; + +public class gen_x { + public static final int SCALARLEN = 32; + public static final int POINTLEN = 32; + + /* + * Convert the X25519 public key into an Ed25519 public key. + * y = (u - 1) / (u + 1) + * NOTE: u=-1 is converted to y=0 since fe_invert is mod-exp + */ + public static int convert_25519_pubkey(byte[] ed_pubkey_bytes, byte[] x25519_pubkey_bytes) { + int[] u = new int[10]; + int[] y = new int[10]; + + if (!fe_isreduced.fe_isreduced(x25519_pubkey_bytes)) + return -1; + + fe_frombytes.fe_frombytes(u, x25519_pubkey_bytes); + + fe_montx_to_edy.fe_montx_to_edy(y, u); + + fe_tobytes.fe_tobytes(ed_pubkey_bytes, y); + return 0; + } + + public static int calculate_25519_keypair(byte[] K_bytes, byte[] k_scalar, + byte[] x25519_privkey_scalar) { + byte[] kneg = new byte[SCALARLEN]; + ge_p3 ed_pubkey_point = new ge_p3(); /* Ed25519 pubkey point */ + + /* Convert the Curve25519 privkey to an Ed25519 public key */ + ge_scalarmult_base.ge_scalarmult_base(ed_pubkey_point, x25519_privkey_scalar); + ge_p3_tobytes.ge_p3_tobytes(K_bytes, ed_pubkey_point); + + /* Force Edwards sign bit to zero */ + byte sign_bit = (byte) ((K_bytes[31] & 0x80) >> 7); + System.arraycopy(x25519_privkey_scalar, 0, k_scalar, 0, 32); + sc_neg.sc_neg(kneg, k_scalar); + sc_cmov.sc_cmov(k_scalar, kneg, sign_bit); + K_bytes[31] &= 0x7F; + + Arrays.fill(kneg, (byte) 0); + return 0; + } + + public static boolean generalized_xveddsa_25519_sign( + Sha512 sha512provider, + byte[] signature_out, + byte[] x25519_privkey_scalar, + byte[] msg, + byte[] random) { + byte[] K_bytes = new byte[POINTLEN]; + byte[] k_scalar = new byte[SCALARLEN]; + if (calculate_25519_keypair(K_bytes, k_scalar, x25519_privkey_scalar) != 0) + return false; + + boolean retval = veddsa.generalized_veddsa_25519_sign(sha512provider, signature_out, K_bytes, k_scalar, + msg, random, new byte[]{}); + Arrays.fill(k_scalar, (byte) 0); + return retval; + } + + public static int generalized_xveddsa_25519_verify( + Sha512 sha512provider, + byte[] vrf_output, + byte[] signature, + byte[] x25519_pubkey_bytes, + byte[] msg) { + byte[] K_bytes = new byte[POINTLEN]; + + if (convert_25519_pubkey(K_bytes, x25519_pubkey_bytes) != 0) + return -1; + + return veddsa.generalized_veddsa_25519_verify(sha512provider, vrf_output, signature, K_bytes, msg, + new byte[]{}); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/point_isreduced.java b/common/src/main/java/org/whispersystems/curve25519/java/point_isreduced.java new file mode 100644 index 0000000..cdec22b --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/point_isreduced.java @@ -0,0 +1,13 @@ +package org.whispersystems.curve25519.java; + +public class point_isreduced { + public static boolean point_isreduced(byte[] p) + { + byte[] strict = new byte[32]; + + System.arraycopy(p, 0, strict, 0, 32); + strict[31] &= 0x7F; /* mask off sign bit */ + return fe_isreduced.fe_isreduced(strict); + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/sc_cmov.java b/common/src/main/java/org/whispersystems/curve25519/java/sc_cmov.java new file mode 100644 index 0000000..a549e0b --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/sc_cmov.java @@ -0,0 +1,22 @@ +package org.whispersystems.curve25519.java; + +public class sc_cmov { + /* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. + */ + + public static void sc_cmov(byte[] f, byte[] g, byte b) { + int count = 32; + byte[] x = new byte[32]; + for (count = 0; count < 32; count++) + x[count] = (byte) (f[count] ^ g[count]); + b = (byte)-b; + for (count = 0; count < 32; count++) + x[count] &= b; + for (count = 0; count < 32; count++) + f[count] = (byte) (f[count] ^ x[count]); + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/sc_isreduced.java b/common/src/main/java/org/whispersystems/curve25519/java/sc_isreduced.java new file mode 100644 index 0000000..2d35f1b --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/sc_isreduced.java @@ -0,0 +1,13 @@ +package org.whispersystems.curve25519.java; + +public class sc_isreduced { + public static boolean sc_isreduced(byte[] s) + { + byte[] strict = new byte[64]; + + System.arraycopy(s, 0, strict, 0, 32); + sc_reduce.sc_reduce(strict); + return crypto_verify_32.crypto_verify_32(strict, s) == 0; + } + +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/sc_neg.java b/common/src/main/java/org/whispersystems/curve25519/java/sc_neg.java new file mode 100644 index 0000000..f3b0187 --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/sc_neg.java @@ -0,0 +1,15 @@ +package org.whispersystems.curve25519.java; + +public class sc_neg { + static byte[] lminus1 = {(byte)0xec, (byte)0xd3, (byte)0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + (byte)0xd6, (byte)0x9c, (byte)0xf7, (byte)0xa2, (byte)0xde, (byte)0xf9, (byte)0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10}; + + /* b = -a (mod l) */ + public static void sc_neg(byte[] b, byte[] a) + { + byte[] zero = new byte[32]; + sc_muladd.sc_muladd(b, lminus1, a, zero); /* b = (-1)a + 0 (mod l) */ + } +} diff --git a/common/src/main/java/org/whispersystems/curve25519/java/veddsa.java b/common/src/main/java/org/whispersystems/curve25519/java/veddsa.java new file mode 100644 index 0000000..d33620a --- /dev/null +++ b/common/src/main/java/org/whispersystems/curve25519/java/veddsa.java @@ -0,0 +1,401 @@ +package org.whispersystems.curve25519.java; + +public class veddsa { + final static int BLOCKLEN = 128; /* SHA512 */ + final static int HASHLEN = 64; /* SHA512 */ + final static int POINTLEN = 32; + final static int SCALARLEN = 32; + final static int RANDLEN = 32; + final static int MSTART = 2048; + final static int MSGMAXLEN = 1048576; + final static int BUFLEN = 1024; + final static int VRFOUTPUTLEN = 32; + + /* B: base point + * R: commitment (point), + r: private nonce (scalar) + K: encoded public key + k: private key (scalar) + Z: 32-bytes random + M: buffer containing message, message starts at M_start, continues for M_len + r = hash(B || gen_labelset || Z || pad1 || k || pad2 || gen_labelset || K || extra || M) (mod q) + */ + public static boolean generalized_commit(Sha512 sha512provider, byte[] R_bytes, byte[] r_scalar, + byte[] labelset, + byte[] extra, int extra_len, + byte[] K_bytes, byte[] k_scalar, + byte[] Z, byte[] msg, + byte[] M_buf, int M_start, int M_len) { + ge_p3 R_point = new ge_p3(); + byte[] hash = new byte[64]; + + if (!gen_labelset.labelset_validate(labelset)) { + return false; + } + if (R_bytes == null || r_scalar == null || + K_bytes == null || k_scalar == null || + Z == null) { + return false; + } + if (extra == null || extra.length == 0) { + return false; + } + if (gen_labelset.labelset_is_empty(labelset)) { + return false; + } + + int prefix_len = 0; + prefix_len += POINTLEN + labelset.length + RANDLEN; + int pad_len1 = ((BLOCKLEN - (prefix_len % BLOCKLEN)) % BLOCKLEN); + prefix_len += pad_len1; + prefix_len += SCALARLEN; + int pad_len2 = ((BLOCKLEN - (prefix_len % BLOCKLEN)) % BLOCKLEN); + prefix_len += pad_len2; + prefix_len += labelset.length + POINTLEN + extra_len; + if (prefix_len > M_start) { + return false; + } + + int startIndex = M_start - prefix_len; + + System.arraycopy(gen_labelset.B_bytes, 0, M_buf, startIndex, POINTLEN); + System.arraycopy(labelset, 0, M_buf, startIndex + POINTLEN, labelset.length); + System.arraycopy(Z, 0, M_buf, startIndex + POINTLEN + labelset.length, RANDLEN); + System.arraycopy(k_scalar, 0, M_buf, startIndex + POINTLEN + labelset.length + pad_len1 + RANDLEN, POINTLEN); + System.arraycopy(labelset, 0, M_buf, startIndex + 2 * POINTLEN + labelset.length + pad_len1 + RANDLEN + pad_len2, labelset.length); + System.arraycopy(K_bytes, 0, M_buf, startIndex + 2 * POINTLEN + 2 * labelset.length + pad_len1 + RANDLEN + pad_len2, POINTLEN); + System.arraycopy(extra, 0, M_buf, startIndex + 3 * POINTLEN + 2 * labelset.length + pad_len1 + RANDLEN + pad_len2, extra_len); + + byte[] in = java.util.Arrays.copyOfRange(M_buf, startIndex, M_start + M_len); + sha512provider.calculateDigest(hash, in, in.length); + + sc_reduce.sc_reduce(hash); + ge_scalarmult_base.ge_scalarmult_base(R_point, hash); + ge_p3_tobytes.ge_p3_tobytes(R_bytes, R_point); + System.arraycopy(hash, 0, r_scalar, 0, SCALARLEN); + return true; + } + + + + /* if is_labelset_empty(gen_labelset): + return hash(R || K || M) (mod q) + else: + return hash(B || gen_labelset || R || gen_labelset || K || extra || M) (mod q) + */ + public static int generalized_challenge(Sha512 sha512provider, byte[] h_scalar, + byte[] labelset, + byte[] extra, + byte[] R_bytes, + byte[] K_bytes, + byte[] M_buf, int M_start, int M_len) { + + byte[] hash = new byte[HASHLEN]; + + if (h_scalar == null) return -1; + + if (!gen_labelset.labelset_validate(labelset)) return -1; + if (R_bytes == null || K_bytes == null) return -1; + if (extra != null && gen_labelset.labelset_is_empty(labelset)) return -1; + + int prefix_len; + + if (gen_labelset.labelset_is_empty(labelset)) { + if (2 * POINTLEN > MSTART) return -1; + prefix_len = 2 * POINTLEN; + int startIndex = M_start - prefix_len; + System.arraycopy(R_bytes, 0, M_buf, startIndex, POINTLEN); + System.arraycopy(K_bytes, 0, M_buf, startIndex + POINTLEN, POINTLEN); + } else { + prefix_len = 3 * POINTLEN + 2 * labelset.length + extra.length; + int startIndex = M_start - prefix_len; + System.arraycopy(gen_labelset.B_bytes, 0, M_buf, startIndex, POINTLEN); + System.arraycopy(labelset, 0, M_buf, startIndex + POINTLEN, labelset.length); + System.arraycopy(R_bytes, 0, M_buf, startIndex + POINTLEN + labelset.length, POINTLEN); + System.arraycopy(labelset, 0, M_buf, startIndex + 2 * POINTLEN + labelset.length, labelset.length); + System.arraycopy(K_bytes, 0, M_buf, startIndex + 2 * POINTLEN + 2 * labelset.length, POINTLEN); + System.arraycopy(extra, 0, M_buf, startIndex + 3 * POINTLEN + 2 * labelset.length, extra.length); + } + + byte[] in = java.util.Arrays.copyOfRange(M_buf, M_start - prefix_len, M_start + M_len); + sha512provider.calculateDigest(hash, in, in.length); + sc_reduce.sc_reduce(hash); + System.arraycopy(hash, 0, h_scalar, 0, SCALARLEN); + return 0; + } + + /* return r + kh (mod q) */ + public static int generalized_prove(byte[] out_scalar, byte[] r_scalar, byte[] k_scalar, byte[] h_scalar) { + sc_muladd.sc_muladd(out_scalar, h_scalar, k_scalar, r_scalar); + return 0; + } + + /* R = s*B - h*K */ + public static int generalized_solve_commitment(byte[] R_bytes_out, ge_p3 K_point_out, + ge_p3 B_point, byte[] s_scalar, + byte[] K_bytes, byte[] h_scalar) { + ge_p3 Kneg_point = new ge_p3(); + ge_p2 R_calc_point_p2 = new ge_p2(); + + ge_p3 sB = new ge_p3(); + ge_p3 hK = new ge_p3(); + ge_p3 R_calc_point_p3 = new ge_p3(); + + if (ge_frombytes.ge_frombytes_negate_vartime(Kneg_point, K_bytes) != 0) { + return -1; + } + + if (B_point == null) { + ge_double_scalarmult.ge_double_scalarmult_vartime(R_calc_point_p2, h_scalar, Kneg_point, s_scalar); + ge_tobytes.ge_tobytes(R_bytes_out, R_calc_point_p2); + } else { + // s * Bv + ge_scalarmult.ge_scalarmult(sB, s_scalar, B_point); + + // h * -K + ge_scalarmult.ge_scalarmult(hK, h_scalar, Kneg_point); + + // R = sB - hK + ge_p3_add.ge_p3_add(R_calc_point_p3, sB, hK); + ge_p3_tobytes.ge_p3_tobytes(R_bytes_out, R_calc_point_p3); + } + + if (K_point_out != null) { + ge_neg.ge_neg(K_point_out, Kneg_point); + } + + return 0; + } + + public static boolean generalized_calculate_Bv(Sha512 sha512provider, ge_p3 Bv_point, + byte[] labelset, byte[] K_bytes, + byte[] M_buf, int M_start, int M_len) { + if (!gen_labelset.labelset_validate(labelset)) + return false; + if (Bv_point == null || K_bytes == null || M_buf == null) + return false; + + int prefix_len = 2 * POINTLEN + labelset.length; + if (prefix_len > M_start) + return false; + + int startIndex = M_start - prefix_len; + System.arraycopy(gen_labelset.B_bytes, 0, M_buf, startIndex, POINTLEN); + System.arraycopy(labelset, 0, M_buf, startIndex + POINTLEN, labelset.length); + System.arraycopy(K_bytes, 0, M_buf, startIndex + POINTLEN + labelset.length, POINTLEN); + + byte[] in = java.util.Arrays.copyOfRange(M_buf, startIndex, M_start + M_len); + System.arraycopy(M_buf, M_start, in, in.length - M_len, M_len); + elligator.hash_to_point(sha512provider, Bv_point, in); + return !ge_isneutral.ge_isneutral(Bv_point); + } + + public static int generalized_calculate_vrf_output(Sha512 sha512provider, + byte[] vrf_output, + byte[] labelset, + ge_p3 cKv_point) { + byte[] cKv_bytes = new byte[POINTLEN]; + byte[] hash = new byte[HASHLEN]; + + if (vrf_output == null) return -1; + Arrays.fill(vrf_output, (byte) 0); + + if (labelset.length + 2 * POINTLEN > BUFLEN) + return -1; + if (!gen_labelset.labelset_validate(labelset)) + return -1; + if (cKv_point == null) + return -1; + + ge_p3_tobytes.ge_p3_tobytes(cKv_bytes, cKv_point); + + byte[] buf = new byte[2 * POINTLEN + labelset.length]; + System.arraycopy(gen_labelset.B_bytes, 0, buf, 0, POINTLEN); + System.arraycopy(labelset, 0, buf, POINTLEN, labelset.length); + System.arraycopy(cKv_bytes, 0, buf, POINTLEN + labelset.length, POINTLEN); + + sha512provider.calculateDigest(hash, buf, buf.length); + System.arraycopy(hash, 0, vrf_output, 0, VRFOUTPUTLEN); + return 0; + } + + public static boolean generalized_veddsa_25519_sign( + Sha512 sha512provider, + byte[] signature_out, + byte[] eddsa_25519_pubkey_bytes, + byte[] eddsa_25519_privkey_scalar, + byte[] msg, + byte[] random, + byte[] customization_label) { + if (signature_out == null) { + return false; + } + + if (eddsa_25519_pubkey_bytes == null) { + return false; + } + if (eddsa_25519_privkey_scalar == null) { + return false; + } + if (customization_label == null || customization_label.length > gen_labelset.LABELMAXLEN) { + return false; + } + if (msg == null || msg.length > MSGMAXLEN) { + return false; + } + + ge_p3 Bv_point = new ge_p3(); + ge_p3 Kv_point = new ge_p3(); + ge_p3 Rv_point = new ge_p3(); + + byte[] Bv_bytes = new byte[POINTLEN]; + byte[] Kv_bytes = new byte[POINTLEN]; + byte[] Rv_bytes = new byte[POINTLEN]; + byte[] R_bytes = new byte[POINTLEN]; + byte[] r_scalar = new byte[SCALARLEN]; + byte[] h_scalar = new byte[SCALARLEN]; + byte[] s_scalar = new byte[SCALARLEN]; + byte[] extra = new byte[3 * POINTLEN]; + byte[] M_buf = new byte[msg.length + MSTART]; + String protocol_name = "VEdDSA_25519_SHA512_Elligator2"; + + System.arraycopy(msg, 0, M_buf, MSTART, msg.length); + + byte[] labelset = gen_labelset.labelset_new(protocol_name, customization_label); + + // labelset1 = add_label(labels, "1") + // Bv = hash(hash(labelset1 || K) || M) + // Kv = k * Bv + labelset = gen_labelset.labelset_add(labelset, "1"); + generalized_calculate_Bv(sha512provider, Bv_point, labelset, + eddsa_25519_pubkey_bytes, M_buf, MSTART, msg.length); + ge_scalarmult.ge_scalarmult(Kv_point, eddsa_25519_privkey_scalar, Bv_point); + ge_p3_tobytes.ge_p3_tobytes(Bv_bytes, Bv_point); + ge_p3_tobytes.ge_p3_tobytes(Kv_bytes, Kv_point); + + // labelset2 = add_label(labels, "2") + // R, r = commit(labelset2, (Bv || Kv), (K,k), Z, M) + labelset[labelset.length - 1] = '2'; + System.arraycopy(Bv_bytes, 0, extra, 0, POINTLEN); + System.arraycopy(Kv_bytes, 0, extra, POINTLEN, POINTLEN); + if (!generalized_commit(sha512provider, R_bytes, r_scalar, + labelset, + extra, 2 * POINTLEN, + eddsa_25519_pubkey_bytes, eddsa_25519_privkey_scalar, + random, msg, M_buf, MSTART, msg.length)) { + return false; + } + + // Rv = r * Bv + ge_scalarmult.ge_scalarmult(Rv_point, r_scalar, Bv_point); + ge_p3_tobytes.ge_p3_tobytes(Rv_bytes, Rv_point); + + // labelset3 = add_label(labels, "3") + // h = challenge(labelset3, (Bv || Kv || Rv), R, K, M) + labelset[labelset.length - 1] = '3'; +// memcpy(extra + 2*POINTLEN, Rv_bytes, POINTLEN); + System.arraycopy(Rv_bytes, 0, extra, 2 * POINTLEN, POINTLEN); + if (generalized_challenge(sha512provider, h_scalar, + labelset, extra, R_bytes, eddsa_25519_pubkey_bytes, M_buf, MSTART, msg.length) != 0) { + return false; + } + + // s = prove(r, k, h) + if (generalized_prove(s_scalar, r_scalar, eddsa_25519_privkey_scalar, h_scalar) != 0) { + return false; + } + + // return (Kv || h || s) + System.arraycopy(Kv_bytes, 0, signature_out, 0, POINTLEN); + System.arraycopy(h_scalar, 0, signature_out, POINTLEN, SCALARLEN); + System.arraycopy(s_scalar, 0, signature_out, POINTLEN + SCALARLEN, SCALARLEN); + + Arrays.fill(r_scalar, (byte) 0); + + return true; + } + + public static int generalized_veddsa_25519_verify( + Sha512 sha512provider, + byte[] vrf_output, + byte[] signature, + byte[] eddsa_25519_pubkey_bytes, + byte[] msg, + byte[] customization_label) { + if (signature == null) return -1; + if (eddsa_25519_pubkey_bytes == null) return -1; + if (msg == null) return -1; + if (customization_label == null) return -1; + if (customization_label.length > gen_labelset.LABELMAXLEN) return -1; + if (msg.length > MSGMAXLEN) return -1; + + ge_p3 Bv_point = new ge_p3(); + ge_p3 K_point = new ge_p3(); + ge_p3 Kv_point = new ge_p3(); + ge_p3 cK_point = new ge_p3(); + ge_p3 cKv_point = new ge_p3(); + + byte[] Bv_bytes = new byte[POINTLEN]; + byte[] R_calc_bytes = new byte[POINTLEN]; + byte[] Rv_calc_bytes = new byte[POINTLEN]; + byte[] h_calc_scalar = new byte[SCALARLEN]; + byte[] extra = new byte[3 * POINTLEN]; + String protocol_name = "VEdDSA_25519_SHA512_Elligator2"; + + byte[] M_buf = new byte[msg.length + MSTART]; + System.arraycopy(msg, 0, M_buf, MSTART, msg.length); + + byte[] Kv_bytes = new byte[POINTLEN]; + System.arraycopy(signature, 0, Kv_bytes, 0, POINTLEN); + byte[] h_scalar = new byte[SCALARLEN]; + System.arraycopy(signature, POINTLEN, h_scalar, 0, SCALARLEN); + byte[] s_scalar = new byte[SCALARLEN]; + System.arraycopy(signature, POINTLEN + SCALARLEN, s_scalar, 0, SCALARLEN); + + if (!point_isreduced.point_isreduced(eddsa_25519_pubkey_bytes)) return -1; + if (!point_isreduced.point_isreduced(Kv_bytes)) return -1; + if (!sc_isreduced.sc_isreduced(h_scalar)) return -1; + if (!sc_isreduced.sc_isreduced(s_scalar)) return -1; + + // gen_labelset = new_labelset(protocol_name, customization_label) + byte[] labelset = gen_labelset.labelset_new(protocol_name, customization_label); + + // labelset1 = add_label(labels, "1") + // Bv = hash(hash(labelset1 || K) || M) + labelset = gen_labelset.labelset_add(labelset, "1"); + if (!generalized_calculate_Bv(sha512provider, Bv_point, labelset, eddsa_25519_pubkey_bytes, M_buf, MSTART, msg.length)) return -1; + ge_p3_tobytes.ge_p3_tobytes(Bv_bytes, Bv_point); + + // R = solve_commitment(B, s, K, h) + if (generalized_solve_commitment(R_calc_bytes, K_point, null, + s_scalar, eddsa_25519_pubkey_bytes, h_scalar) != 0) return -1; + + // Rv = solve_commitment(Bv, s, Kv, h) + if (generalized_solve_commitment(Rv_calc_bytes, Kv_point, Bv_point, + s_scalar, Kv_bytes, h_scalar) != 0) return -1; + + ge_scalarmult_cofactor.ge_scalarmult_cofactor(cK_point, K_point); + ge_scalarmult_cofactor.ge_scalarmult_cofactor(cKv_point, Kv_point); + if (ge_isneutral.ge_isneutral(cK_point) || ge_isneutral.ge_isneutral(cKv_point) || ge_isneutral.ge_isneutral(Bv_point)) return -1; + + // labelset3 = add_label(labels, "3") + // h = challenge(labelset3, (Bv || Kv || Rv), R, K, M) + labelset[labelset.length - 1] = '3'; + System.arraycopy(Bv_bytes, 0, extra, 0, POINTLEN); + System.arraycopy(Kv_bytes, 0, extra, POINTLEN, POINTLEN); + System.arraycopy(Rv_calc_bytes, 0, extra, 2 * POINTLEN, POINTLEN); + if (generalized_challenge(sha512provider, h_calc_scalar, + labelset, + extra, + R_calc_bytes, eddsa_25519_pubkey_bytes, M_buf, MSTART, msg.length) != 0) return -1; + + // if bytes_equal(h, h') + if (crypto_verify_32.crypto_verify_32(h_scalar, h_calc_scalar) != 0) return -1; + + // labelset4 = add_label(labels, "4") + // v = hash(labelset4 || c*Kv) + labelset[labelset.length - 1] = '4'; + + return generalized_calculate_vrf_output(sha512provider, vrf_output, labelset, cKv_point); + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2322723..5c2d1cf 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a1a7687..7c4388a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Oct 17 15:10:52 PDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradlew b/gradlew index 91a7e26..8e25e6c 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,20 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## ## @@ -6,20 +22,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +64,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +75,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +105,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +129,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +170,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..24467a1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -8,14 +24,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +62,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +75,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/java/build.gradle b/java/build.gradle index 3373133..d1fa4df 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -20,12 +20,34 @@ sourceSets { } } +buildscript { + repositories { + jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "me.champeau.gradle:jmh-gradle-plugin:0.5.0-rc-2" + } +} + +apply plugin: "me.champeau.gradle.jmh" + +jmh { + dependencies { + compileClasspath 'org.openjdk.jmh:jmh-core:1.4.1' + } + humanOutputFile = null + duplicateClassesStrategy = 'exclude' +} + dependencies { testCompile project(':tests') } signing { - required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } + required { hasProperty("release") && gradle.taskGraph.hasTask("uploadArchives") } sign configurations.archives } diff --git a/java/src/jmh/java/org/whispersystems/curve25519/JavaCurve25519ProviderBenchmark.java b/java/src/jmh/java/org/whispersystems/curve25519/JavaCurve25519ProviderBenchmark.java new file mode 100644 index 0000000..b8d5a4e --- /dev/null +++ b/java/src/jmh/java/org/whispersystems/curve25519/JavaCurve25519ProviderBenchmark.java @@ -0,0 +1,88 @@ +package org.whispersystems.curve25519; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Mode.AverageTime) +@Threads(4) +@Fork(1) +@Warmup(iterations = 10) +@Measurement(iterations = 5) +public class JavaCurve25519ProviderBenchmark { + @State(Scope.Thread) + public static class BenchmarkState { + public JavaCurve25519Provider provider = new JavaCurve25519Provider(); + public byte[] privateKey; + public byte[] publicKey; + public byte[] random = new byte[64]; + public byte[] message = new byte[256]; + public byte[] editedMessage = new byte[256]; + public byte[] signature; + public byte[] vrfSignature; + + @Setup(Level.Iteration) + public void doSetup() { + Random r = new Random(); + r.nextBytes(random); + r.nextBytes(message); + System.arraycopy(message, 0, editedMessage, 0, 256); + // invert 1 byte + editedMessage[0] ^= 0xff; + privateKey = provider.generatePrivateKey(random); + publicKey = provider.generatePublicKey(privateKey); + signature = provider.calculateSignature(random, privateKey, message); + vrfSignature = provider.calculateVrfSignature(random, privateKey, message); + } + } + + @Benchmark + public void generatePrivateKey(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.generatePrivateKey()); + } + + @Benchmark + public void generatePrivateKeyWithRandom(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.generatePrivateKey(bs.random)); + } + + @Benchmark + public void generatePublicKey(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.generatePublicKey(bs.privateKey)); + } + + @Benchmark + public void calculateSignature(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.calculateSignature(bs.random, bs.privateKey, bs.message)); + } + + @Benchmark + public void verifyValidSignature(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.verifySignature(bs.publicKey, bs.message, bs.signature)); + } + + @Benchmark + public void verifyInvalidSignature(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.verifySignature(bs.publicKey, bs.editedMessage, bs.signature)); + } + + @Benchmark + public void calculateVRFSignature(Blackhole blackhole, BenchmarkState bs) { + blackhole.consume(bs.provider.calculateVrfSignature(bs.random, bs.privateKey, bs.message)); + } + + @Benchmark + public void verifyValidVrfSignature(Blackhole blackhole, BenchmarkState bs) throws VrfSignatureVerificationFailedException { + blackhole.consume(bs.provider.verifyVrfSignature(bs.publicKey, bs.message, bs.vrfSignature)); + } + + @Benchmark + public void verifyInvalidVrfSignature(Blackhole blackhole, BenchmarkState bs) { + try { + blackhole.consume(bs.provider.verifyVrfSignature(bs.publicKey, bs.editedMessage, bs.vrfSignature)); + } catch (VrfSignatureVerificationFailedException ignored) {} + } +} diff --git a/tests/src/main/java/org/whispersystems/curve25519/Curve25519ProviderTest.java b/tests/src/main/java/org/whispersystems/curve25519/Curve25519ProviderTest.java index 65f4aec..0d6b652 100644 --- a/tests/src/main/java/org/whispersystems/curve25519/Curve25519ProviderTest.java +++ b/tests/src/main/java/org/whispersystems/curve25519/Curve25519ProviderTest.java @@ -1,7 +1,11 @@ package org.whispersystems.curve25519; +import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin; import junit.framework.TestCase; +import java.util.Arrays; +import java.util.Random; + import static org.fest.assertions.Assertions.assertThat; public abstract class Curve25519ProviderTest extends TestCase { @@ -86,4 +90,162 @@ public void testSignVerify() throws NoSuchProviderException { System.arraycopy(sig_out, 0, privkey, 0, 32); } } + + public void testVRFSign() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("5468697320697320756E697175652E"); + byte[] privkey = HexBin.decode("38611D253BEA85A203805343B74A936D3B13B9E3121453E9740B6B827E337E5D"); + byte[] signature = HexBin.decode("5D501685D744424DE3EF5CA49ECDDD880FA7421C975CDF94BAE48CA16EC0899737721200EED1A8B0D2D6852826A1EAB78B0DF27F35B3F3E89C96E7AE3DAAA30F037297547886E554AFFC81DE54B575768FB30493C537ECDD5A87577DEB7D8E03"); + + byte[] sig_out = provider.calculateVrfSignature(new byte[32], privkey, msg); + assertTrue(Arrays.equals(signature, sig_out)); + } + + public void testVRFVerify() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("5468697320697320756E697175652E"); + byte[] pubkey = HexBin.decode("21F7345F56D9602F1523298F4F6FCECB14DDE2D5B9A9B48BCA8242681492B920"); + byte[] vrf = HexBin.decode("45DC7B816B01B36CFA1645DCAE8AC9BC8E523CD86D007D19953F03E7D54554A0"); + byte[] signature = HexBin.decode("5D501685D744424DE3EF5CA49ECDDD880FA7421C975CDF94BAE48CA16EC0899737721200EED1A8B0D2D6852826A1EAB78B0DF27F35B3F3E89C96E7AE3DAAA30F037297547886E554AFFC81DE54B575768FB30493C537ECDD5A87577DEB7D8E03"); + + byte[] calc_vrf = provider.verifyVrfSignature(pubkey, msg, signature ); + assertTrue(Arrays.equals(vrf, calc_vrf)); + } + + public void testVRFSignVerify() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("CE0827E6381654D3FFBE22F546E00199B5761C1E541108E56D5A66213A1569E969A02B1D27D91553B69984010F25331A13EA62BA53B6F5B86DA11F8C22ABBF11D6839E11626D0FEF191BD2D5251D371F57C53240F7CD2B435BE6213C7C8F36D47F3DE23A"); + byte[] privkey = HexBin.decode("C80827E6381654D3FFBE22F546E00199B5761C1E541108E56D5A66213A156969"); + byte[] publickey = provider.generatePublicKey(privkey); + byte[] random = HexBin.decode("B33734A591BAB70644D731530B67E8734C157DD72B796B7FA3D7FF6885D4C122"); + byte[] vrf = HexBin.decode("5669EC30C0F39E2696BB048B574236DEFA325D307116D6A89612958793192FF5"); + + byte[] sig_out = provider.calculateVrfSignature(random, privkey, msg); + byte[] calc_vrf = provider.verifyVrfSignature(publickey, msg, sig_out); + assertTrue(Arrays.equals(calc_vrf, vrf)); + } + + public void testVRFFailedVerifyByMessage() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("5468697320697320756E697175652E"); + byte[] pubkey = HexBin.decode("21F7345F56D9602F1523298F4F6FCECB14DDE2D5B9A9B48BCA8242681492B920"); + byte[] signature = HexBin.decode("5D501685D744424DE3EF5CA49ECDDD880FA7421C975CDF94BAE48CA16EC0899737721200EED1A8B0D2D6852826A1EAB78B0DF27F35B3F3E89C96E7AE3DAAA30F037297547886E554AFFC81DE54B575768FB30493C537ECDD5A87577DEB7D8E03"); + + msg[4] ^= 0xff; + + try { + provider.verifyVrfSignature(pubkey, msg, signature); + fail(); + } catch(VrfSignatureVerificationFailedException ignored) {} + } + + public void testVRFFailedVerifyByPublicKey() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("5468697320697320756E697175652E"); + byte[] pubkey = HexBin.decode("21F7345F56D9602F1523298F4F6FCECB14DDE2D5B9A9B48BCA8242681492B920"); + byte[] signature = HexBin.decode("5D501685D744424DE3EF5CA49ECDDD880FA7421C975CDF94BAE48CA16EC0899737721200EED1A8B0D2D6852826A1EAB78B0DF27F35B3F3E89C96E7AE3DAAA30F037297547886E554AFFC81DE54B575768FB30493C537ECDD5A87577DEB7D8E03"); + + pubkey[4] ^= 0xff; + + try { + provider.verifyVrfSignature(pubkey, msg, signature); + fail(); + } catch(VrfSignatureVerificationFailedException ignored) {} + } + + public void testVRFFailedVerifyBySignature() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + + byte[] msg = HexBin.decode("5468697320697320756E697175652E"); + byte[] pubkey = HexBin.decode("21F7345F56D9602F1523298F4F6FCECB14DDE2D5B9A9B48BCA8242681492B920"); + byte[] signature = HexBin.decode("5D501685D744424DE3EF5CA49ECDDD880FA7421C975CDF94BAE48CA16EC0899737721200EED1A8B0D2D6852826A1EAB78B0DF27F35B3F3E89C96E7AE3DAAA30F037297547886E554AFFC81DE54B575768FB30493C537ECDD5A87577DEB7D8E03"); + + signature[4] ^= 0xff; + + try { + provider.verifyVrfSignature(pubkey, msg, signature); + fail(); + } catch(VrfSignatureVerificationFailedException ignored) {} + } + + public void testVRFIntegrationTest() throws NoSuchProviderException, VrfSignatureVerificationFailedException { + Curve25519Provider provider = createProvider(); + Random r = new Random(1244); + + byte[] msg = new byte[100]; + byte[] privkey; + byte[] pubkey; + byte[] random = new byte[64]; + + for (int count=0; count < 1000; count++) { + r.nextBytes(random); + r.nextBytes(msg); + + privkey = provider.generatePrivateKey(msg); + pubkey = provider.generatePublicKey(privkey); + + byte[] sig_out = provider.calculateVrfSignature(random, privkey, msg); + byte[] sig_out2 = provider.calculateVrfSignature(random, privkey, msg); + r.nextBytes(random); + byte[] sig_out3 = provider.calculateVrfSignature(random, privkey, msg); + + assertTrue(Arrays.equals(sig_out, sig_out2)); + assertFalse(Arrays.equals(sig_out, sig_out3)); + + byte[] vrf1 = provider.verifyVrfSignature(pubkey, msg, sig_out); + byte[] vrf2 = provider.verifyVrfSignature(pubkey, msg, sig_out); + + assertTrue(Arrays.equals(vrf1, vrf2)); + assertFalse(provider.verifySignature(pubkey, msg, sig_out)); + + try { + byte[] wrongPubkey = pubkey.clone(); + wrongPubkey[3] ^= 0xff; + provider.verifyVrfSignature(wrongPubkey, msg, sig_out); + fail(); + } catch (VrfSignatureVerificationFailedException ignored) {} + + try { + byte[] wrongMsg = msg.clone(); + wrongMsg[3] ^= 0xff; + provider.verifyVrfSignature(pubkey, wrongMsg, sig_out); + fail(); + } catch (VrfSignatureVerificationFailedException ignored) {} + + try { + byte[] wrongSignature = sig_out.clone(); + wrongSignature[3] ^= 0xff; + provider.verifyVrfSignature(pubkey, msg, wrongSignature); + fail(); + } catch (VrfSignatureVerificationFailedException ignored) {} + } + } + + public void testFeIsequal1() { + int[] one = new int[] {0,0,0,0,0,0,0,0,0,1}; + int[] zero = new int[] {0,0,0,0,0,0,0,0,0,0}; + assertTrue(fe_isequal.fe_isequal(one, zero) == 0); + } + public void testFeIsequal2() { + int[] one = new int[] {0,0,0,0,0,0,0,0,0,1}; + int[] zero = new int[] {0,0,0,0,0,0,0,0,0,1}; + assertTrue(fe_isequal.fe_isequal(one, zero) == 1); + } + + public void testFeIsequal3() { + int[] one = new int[] {0,0,0,0,0,0,0,0,0,0}; + int[] zero = new int[] {0,0,0,0,0,0,0,0,0,0}; + assertTrue(fe_isequal.fe_isequal(one, zero) == 1); + } + + public void testFeIsequal4() { + int[] one = new int[] {0,0,0,0,0,0,0,0,0,0}; + int[] zero = new int[] {0,0,0,0,0,0,0,0,0,1}; + assertTrue(fe_isequal.fe_isequal(one, zero) == 0); + } }