converted to use x25519 bouncycastle impl

This commit is contained in:
knaccc 2017-09-23 17:34:37 +01:00
parent 46d95551ff
commit 63dfd2bcae
15 changed files with 304 additions and 233 deletions

Binary file not shown.

View file

@ -6,7 +6,6 @@ import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.nem.core.crypto.ed25519.arithmetic.*;
import java.math.BigInteger;
import java.security.SecureRandom;
@ -14,7 +13,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static how.monero.hodl.crypto.HashToPoint.hashToPoint;
import static how.monero.hodl.util.ByteUtil.*;
public class CryptoUtil {
@ -32,12 +30,6 @@ public class CryptoUtil {
}
});
public static final Ed25519GroupElement G = Ed25519Group.BASE_POINT;
public static final Ed25519GroupElement H = new Ed25519EncodedGroupElement(hexToBytes("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94")).decode();
static {
H.precomputeForScalarMultiplication();
}
public static Scalar hashToScalar(byte[] a) {
return new Scalar(scReduce32(fastHash(a)));
}
@ -89,21 +81,15 @@ public class CryptoUtil {
}
public static final Random random = new SecureRandom();
public static byte[] randomPointAsBytes() {
return randomPoint().encode().getRaw();
}
public static Ed25519GroupElement randomPoint() {
return Ed25519Group.BASE_POINT.scalarMultiply(Scalar.randomScalar());
}
public static byte[] randomMessage(int len) {
byte[] m = new byte[len];
random.nextBytes(m);
return m;
}
public static byte[] toBytes(Ed25519GroupElement[] a) {
public static byte[] toBytes(Curve25519Point[] a) {
byte[] r = new byte[0];
for(Ed25519GroupElement ai : a) r = concat(r, ai.encode().getRaw());
for(Curve25519Point ai : a) r = concat(r, ai.toBytes());
return r;
}
@ -115,40 +101,39 @@ public class CryptoUtil {
public static PointPair COMeg(Scalar xAmount, Scalar rMask) {
return new PointPair(G.scalarMultiply(xAmount).add(getHpnGLookup(1).scalarMultiply(rMask).toCached()), G.scalarMultiply(rMask));
public static Curve25519PointPair COMeg(Scalar xAmount, Scalar rMask) {
return new Curve25519PointPair(Curve25519Point.G.scalarMultiply(xAmount).add(getHpnGLookup(1).scalarMultiply(rMask)), Curve25519Point.G.scalarMultiply(rMask));
}
public static Ed25519GroupElement COMp(Scalar xAmount, Scalar rMask) {
return G.scalarMultiply(xAmount).add(getHpnGLookup(1).scalarMultiply(rMask).toCached());
public static Curve25519Point COMp(Scalar xAmount, Scalar rMask) {
return Curve25519Point.G.scalarMultiply(xAmount).add(getHpnGLookup(1).scalarMultiply(rMask));
}
public static Ed25519GroupElement COMb(Scalar[][] x, Scalar r) {
public static Curve25519Point COMb(Scalar[][] x, Scalar r) {
int m = x.length;
int n = x[0].length;
Ed25519GroupElement A = G.scalarMultiply(r);
Curve25519Point A = Curve25519Point.G.scalarMultiply(r);
for(int j=0; j<m; j++) {
for(int i=0; i<n; i++) {
A = A.toP3().add(getHpnGLookup(j * n + i + 1).scalarMultiply(x[j][i]).toCached());
A = A.add(getHpnGLookup(j * n + i + 1).scalarMultiply(x[j][i]));
}
}
return A;
}
public static Map<Integer, Ed25519GroupElement> HpnGLookup = new HashMap<>();
public static Map<Integer, Curve25519Point> HpnGLookup = new HashMap<>();
public static Ed25519GroupElement getHpnGLookup(int n) {
public static Curve25519Point getHpnGLookup(int n) {
if(!HpnGLookup.containsKey(n)) {
Ed25519GroupElement HpnG = hashToPoint(G.scalarMultiply(Scalar.intToScalar(n)));
//HpnG.precomputeForScalarMultiplication(); // try precomputed vs non-precomputed to check best performance
Curve25519Point HpnG = Curve25519Point.hashToPoint(Curve25519Point.G.scalarMultiply(Scalar.intToScalar(n)));
HpnGLookup.put(n, HpnG);
}
return HpnGLookup.get(n);
}
public static PointPair ENCeg(Ed25519GroupElement X, Scalar r) {
return new PointPair(getHpnGLookup(1).scalarMultiply(r).toP3().add(X.toCached()), G.scalarMultiply(r));
public static Curve25519PointPair ENCeg(Curve25519Point X, Scalar r) {
return new Curve25519PointPair(getHpnGLookup(1).scalarMultiply(r).add(X), Curve25519Point.G.scalarMultiply(r));
}

View file

@ -0,0 +1,98 @@
package how.monero.hodl.crypto;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import java.security.Security;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import static how.monero.hodl.crypto.CryptoUtil.hashToScalar;
import static how.monero.hodl.util.ByteUtil.bytesToHex;
public class Curve25519Point {
public static ECParameterSpec ecsp;
static {
try {
Security.addProvider(new BouncyCastleProvider());
ecsp = ECNamedCurveTable.getParameterSpec("curve25519");
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public ECPoint point;
public Curve25519Point(ECPoint point) {
this.point = point;
}
public Curve25519Point(byte[] a) {
this.point = ecsp.getCurve().decodePoint(a);
}
public static Curve25519Point randomPoint() {
return BASE_POINT.scalarMultiply(Scalar.randomScalar());
}
public Curve25519Point scalarMultiply(Scalar a) {
scalarMults++;
if(this==BASE_POINT) scalarBaseMults++;
if(enableLineRecording) {
Optional<StackTraceElement> optionalCaller = Arrays.stream(new Exception().getStackTrace()).filter(e -> e.getFileName().equals(lineRecordingSourceFile)).findFirst();
if (optionalCaller.isPresent()) {
StackTraceElement caller = optionalCaller.get();
lineNumberCallFrequencyMap.putIfAbsent(caller.getLineNumber(), 0);
lineNumberCallFrequencyMap.computeIfPresent(caller.getLineNumber(), (key, oldValue) -> oldValue + 1);
}
}
return new Curve25519Point(point.multiply(a.toBigInteger()));
}
public Curve25519Point add(Curve25519Point a) {
return new Curve25519Point(point.add(a.point));
}
public Curve25519Point subtract(Curve25519Point a) {
return new Curve25519Point(point.subtract(a.point));
}
public byte[] toBytes() {
return point.getEncoded(true);
}
public boolean satisfiesCurveEquation() {
return true;
}
public static Curve25519Point hashToPoint(byte[] a) {
return BASE_POINT.scalarMultiply(hashToScalar(a));
}
public static Curve25519Point hashToPoint(Curve25519Point a) {
return hashToPoint(a.toBytes());
}
@Override
public String toString() {
return bytesToHex(toBytes());
}
public static Curve25519Point ZERO = new Curve25519Point(ecsp.getCurve().getInfinity());
public static Curve25519Point BASE_POINT = new Curve25519Point(ecsp.getG());
public static Curve25519Point G = BASE_POINT;
public static int scalarMults = 0;
public static int scalarBaseMults = 0;
public static String lineRecordingSourceFile = null;
public static boolean enableLineRecording = false;
public static Map<Integer, Integer> lineNumberCallFrequencyMap = new TreeMap<>((a, b)->a.compareTo(b));
@Override
public boolean equals(Object obj) {
return point.equals(((Curve25519Point) obj).point);
}
}

View file

@ -0,0 +1,35 @@
package how.monero.hodl.crypto;
import static how.monero.hodl.util.ByteUtil.*;
public class Curve25519PointPair {
public Curve25519Point P1;
public Curve25519Point P2;
public Curve25519PointPair(Curve25519Point P1, Curve25519Point P2) {
this.P1 = P1;
this.P2 = P2;
}
public byte[] toBytes() {
return concat(P1.toBytes(), P2.toBytes());
}
public Curve25519PointPair add(Curve25519PointPair a) {
return new Curve25519PointPair(P1.add(a.P1), P2.add(a.P2));
}
public Curve25519PointPair subtract(Curve25519PointPair a) {
return new Curve25519PointPair(P1.subtract(a.P1), P2.subtract(a.P2));
}
public Curve25519PointPair multiply(Scalar n) {
return new Curve25519PointPair(P1.scalarMultiply(n), P2.scalarMultiply(n));
}
public boolean equals(Curve25519PointPair obj) {
return P1.equals(obj.P1) && P2.equals(obj.P2);
}
@Override
public String toString() {
return "(P1: " + bytesToHex(P1.toBytes()) + ", P2: " + P2 + ")";
}
}

View file

@ -1,37 +0,0 @@
package how.monero.hodl.crypto;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import static how.monero.hodl.util.ByteUtil.*;
public class PointPair {
public Ed25519GroupElement P1;
public Ed25519GroupElement P2;
public PointPair(Ed25519GroupElement P1, Ed25519GroupElement P2) {
this.P1 = P1;
this.P2 = P2;
}
public byte[] toBytes() {
return concat(P1.encode().getRaw(), P2.encode().getRaw());
}
public PointPair add(PointPair a) {
return new PointPair(P1.toP3().add(a.P1.toP3().toCached()), P2.toP3().add(a.P2.toP3().toCached()));
}
public PointPair subtract(PointPair a) {
return new PointPair(P1.toP3().subtract(a.P1.toCached()), P2.toP3().subtract(a.P2.toCached()));
}
public PointPair multiply(Scalar n) {
return new PointPair(P1.toP3().scalarMultiply(n), P2.toP3().scalarMultiply(n));
}
public boolean equals(PointPair obj) {
return P1.toP3().equals(obj.P1.toP3()) && P2.toP3().equals(obj.P2.toP3());
}
@Override
public String toString() {
return "(P1: " + bytesToHex(P1.encode().getRaw()) + ", P2: " + P2 + ")";
}
}

View file

@ -1,11 +1,8 @@
package how.monero.hodl.cursor;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519EncodedFieldElement;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519EncodedGroupElement;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519FieldElement;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
public class BootleRuffingCursor extends Cursor {
public byte[] data;
@ -14,15 +11,12 @@ public class BootleRuffingCursor extends Cursor {
super(data);
}
public Ed25519GroupElement readGroupElement() {
return new Ed25519EncodedGroupElement(readBytes(32)).decode();
public Curve25519Point readGroupElement() {
return new Curve25519Point(readBytes(32));
}
public Ed25519FieldElement readFieldElement() {
return new Ed25519EncodedFieldElement(readBytes(32)).decode();
}
public PointPair[] readPointPairArray(int len) {
PointPair[] result = new PointPair[len];
for(int i=0; i<len; i++) result[i] = new PointPair(readGroupElement(), readGroupElement());
public Curve25519PointPair[] readPointPairArray(int len) {
Curve25519PointPair[] result = new Curve25519PointPair[len];
for(int i=0; i<len; i++) result[i] = new Curve25519PointPair(readGroupElement(), readGroupElement());
return result;
}
public Scalar[][] readScalar2DArray(int m, int n) {

View file

@ -1,7 +1,7 @@
package how.monero.hodl.ringSignature;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Scalar;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import java.util.*;
@ -12,10 +12,10 @@ import static how.monero.hodl.crypto.CryptoUtil.*;
public class Multisignature {
public static Ed25519GroupElement[] lexicographicalSort(Ed25519GroupElement[] X) {
SortedMap<String, Ed25519GroupElement> hexToPoint = new TreeMap<>();
for(Ed25519GroupElement Xi : X) hexToPoint.put(bytesToHex(Xi.encode().getRaw()), Xi);
return hexToPoint.values().stream().toArray(Ed25519GroupElement[]::new);
public static Curve25519Point[] lexicographicalSort(Curve25519Point[] X) {
SortedMap<String, Curve25519Point> hexToPoint = new TreeMap<>();
for(Curve25519Point Xi : X) hexToPoint.put(bytesToHex(Xi.toBytes()), Xi);
return hexToPoint.values().stream().toArray(Curve25519Point[]::new);
}
/*
@ -25,18 +25,18 @@ public class Multisignature {
2) For each i=1,2,...,n, compute c[i] = Hs(X[i], R, L*, M)
3) Accept if and only if sG = R + c[1]*X[1] + ... + c[n]*X[n]
*/
public static boolean verify(byte[] M, Ed25519GroupElement[] X, Signature signature) {
public static boolean verify(byte[] M, Curve25519Point[] X, Signature signature) {
int n = X.length;
Scalar XAsterisk = hashToScalar(toBytes(lexicographicalSort(X)));
Scalar[] c = new Scalar[n];
for(int i=0; i<n; i++) {
c[i] = hashToScalar(concat(X[i].encode().getRaw(), signature.R.encode().getRaw(), XAsterisk.bytes, M));
c[i] = hashToScalar(concat(X[i].toBytes(), signature.R.toBytes(), XAsterisk.bytes, M));
}
Ed25519GroupElement sG = G.scalarMultiply(signature.s);
Ed25519GroupElement sG1 = signature.R;
for(int i=0; i<n; i++) sG1 = sG1.toP3().add(X[i].scalarMultiply(c[i]).toCached());
Curve25519Point sG = Curve25519Point.G.scalarMultiply(signature.s);
Curve25519Point sG1 = signature.R;
for(int i=0; i<n; i++) sG1 = sG1.add(X[i].scalarMultiply(c[i]));
return sG.equals(sG1);
}
@ -53,12 +53,12 @@ public class Multisignature {
5) Compute s = s[1] + ... + s[n].
6) Output the signature sigma = (R, s)
*/
public static Signature sign(byte[] M, Scalar[] x, Ed25519GroupElement[] X) {
public static Signature sign(byte[] M, Scalar[] x, Curve25519Point[] X) {
int n = x.length;
if(X==null) {
X = new Ed25519GroupElement[n];
X = new Curve25519Point[n];
for(int i=0; i<n; i++) {
X[i] = G.scalarMultiply(x[i]);
X[i] = Curve25519Point.G.scalarMultiply(x[i]);
}
}
@ -68,11 +68,11 @@ public class Multisignature {
for(int i=0; i<n; i++) rArray[i] = randomScalar();
Scalar r = sumArray(rArray);
Ed25519GroupElement R = G.scalarMultiply(r);
Curve25519Point R = Curve25519Point.G.scalarMultiply(r);
Scalar[] c = new Scalar[n];
Scalar[] sArray = new Scalar[n];
for(int i=0; i<n; i++) {
c[i] = hashToScalar(concat(X[i].encode().getRaw(), R.encode().getRaw(), XAsterisk.bytes, M));
c[i] = hashToScalar(concat(X[i].toBytes(), R.toBytes(), XAsterisk.bytes, M));
sArray[i] = rArray[i].add(x[i].mul(c[i]));
}
Scalar s = sumArray(sArray);
@ -80,13 +80,13 @@ public class Multisignature {
}
public static class Signature {
Ed25519GroupElement R;
Curve25519Point R;
Scalar s;
public Signature(Ed25519GroupElement R, Scalar s) {
public Signature(Curve25519Point R, Scalar s) {
this.R = R; this.s = s;
}
public byte[] toBytes() {
return concat(R.encode().getRaw(), s.bytes);
return concat(R.toBytes(), s.bytes);
}
}
@ -96,13 +96,13 @@ public class Multisignature {
*/
public static KeyPair keygen() {
Scalar x = randomScalar();
Ed25519GroupElement X = G.scalarMultiply(x);
Curve25519Point X = Curve25519Point.G.scalarMultiply(x);
return new KeyPair(x, X);
}
public static class KeyPair {
public Scalar x;
public Ed25519GroupElement X;
public KeyPair(Scalar x, Ed25519GroupElement X) {
public Curve25519Point X;
public KeyPair(Scalar x, Curve25519Point X) {
this.x = x; this.X = X;
}
}

View file

@ -1,16 +1,16 @@
package how.monero.hodl.ringSignature;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
public class SpendParams {
public int iAsterisk;
public PointPair[][] pk;
public BootleRuffing.SK[] sk;
public Ed25519GroupElement[] ki;
public Ed25519GroupElement[] co;
public Curve25519PointPair[][] pk;
public StringCT.SK[] sk;
public Curve25519Point[] ki;
public Curve25519Point[] co;
public byte[] M;
public Scalar s;
public int decompositionBase;

View file

@ -1,23 +1,20 @@
package how.monero.hodl.ringSignature;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import how.monero.hodl.cursor.BootleRuffingCursor;
import how.monero.hodl.util.VarInt;
import org.nem.core.crypto.ed25519.arithmetic.*;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import static how.monero.hodl.crypto.CryptoUtil.*;
import static how.monero.hodl.crypto.HashToPoint.hashToPoint;
import static how.monero.hodl.crypto.Scalar.bigIntegerArrayToScalarArray;
import static how.monero.hodl.crypto.Scalar.randomScalar;
import static how.monero.hodl.util.ByteUtil.*;
public class BootleRuffing {
public class StringCT {
public static class SK {
public Scalar r;
@ -34,39 +31,39 @@ public class BootleRuffing {
public static KeyGenResult KEYGEN() {
SK sk = new SK(randomScalar(), randomScalar());
Ed25519GroupElement ki = G.scalarMultiply(sk.r1);
PointPair pk = ENCeg(ki, sk.r);
Curve25519Point ki = Curve25519Point.G.scalarMultiply(sk.r1);
Curve25519PointPair pk = ENCeg(ki, sk.r);
return new KeyGenResult(sk, ki, pk);
}
public static class KeyGenResult {
public SK sk;
public Ed25519GroupElement ki;
public PointPair pk = null;
public KeyGenResult(SK sk, Ed25519GroupElement ki, PointPair pk) {
public Curve25519Point ki;
public Curve25519PointPair pk = null;
public KeyGenResult(SK sk, Curve25519Point ki, Curve25519PointPair pk) {
this.sk = sk; this.ki = ki; this.pk = pk;
}
@Override
public String toString() {
return "sk: " + sk.toString() + ", ki: " + bytesToHex(ki.encode().getRaw()) + ", pk: " + (pk==null ? "(no pk)" : "pk: " + pk);
return "sk: " + sk.toString() + ", ki: " + bytesToHex(ki.toBytes()) + ", pk: " + (pk==null ? "(no pk)" : "pk: " + pk);
}
}
public static class F {
public Ed25519GroupElement[] ki;
public PointPair[][] pk;
public Ed25519GroupElement[] co;
public Ed25519GroupElement co1;
public Curve25519Point[] ki;
public Curve25519PointPair[][] pk;
public Curve25519Point[] co;
public Curve25519Point co1;
byte[] M;
public F(Ed25519GroupElement[] ki, PointPair[][] pk, Ed25519GroupElement[] co, Ed25519GroupElement co1, byte[] M) {
public F(Curve25519Point[] ki, Curve25519PointPair[][] pk, Curve25519Point[] co, Curve25519Point co1, byte[] M) {
this.ki = ki; this.pk = pk; this.co = co; this.co1 = co1; this.M = M;
}
byte[] toBytes() {
byte[] r = new byte[0];
for(int i=0; i<ki.length; i++) r = concat(r, ki[i].encode().getRaw());
for(int i=0; i<ki.length; i++) r = concat(r, ki[i].toBytes());
for(int i=0; i<pk.length; i++) for(int j=0; j<pk[i].length; j++) r = concat(r, pk[i][j].toBytes());
for(int i=0; i<co.length; i++) r = concat(r, co[i].encode().getRaw());
r = concat(r, co1.encode().getRaw());
for(int i=0; i<co.length; i++) r = concat(r, co[i].toBytes());
r = concat(r, co1.toBytes());
r = concat(r, M);
return r;
}
@ -75,16 +72,16 @@ public class BootleRuffing {
public static SpendSignature SPEND(SpendParams sp) {
int iAsterisk = sp.iAsterisk;
PointPair[][] pk = sp.pk;
Curve25519PointPair[][] pk = sp.pk;
SK[] sk = sp.sk;
Ed25519GroupElement[] ki = sp.ki;
Ed25519GroupElement[] co = sp.co;
Curve25519Point[] ki = sp.ki;
Curve25519Point[] co = sp.co;
byte[] M = sp.M;
Scalar s = sp.s;
int decompositionBase = sp.decompositionBase;
int decompositionExponent = sp.decompositionExponent;
Ed25519GroupElement co1 = G.scalarMultiply(s);
Curve25519Point co1 = Curve25519Point.G.scalarMultiply(s);
F f = new F(ki, pk, co, co1, M);
SubResult cf1 = SUB(f);
Scalar s1 = s;
@ -101,16 +98,16 @@ public class BootleRuffing {
public static class SpendSignature {
public int decompositionBase;
public int decompositionExponent;
public Ed25519GroupElement co1;
public Curve25519Point co1;
public Proof2 sigma1;
Multisignature.Signature sigma2;
public SpendSignature(int decompositionBase, int decompositionExponent, Ed25519GroupElement co1, Proof2 sigma1, Multisignature.Signature sigma2) {
public SpendSignature(int decompositionBase, int decompositionExponent, Curve25519Point co1, Proof2 sigma1, Multisignature.Signature sigma2) {
this.decompositionBase = decompositionBase; this.decompositionExponent = decompositionExponent; this.co1 = co1; this.sigma1 = sigma1; this.sigma2 = sigma2;
}
public byte[] toBytes() {
byte[] result;
result = concat(VarInt.writeVarInt(decompositionBase), VarInt.writeVarInt(decompositionExponent));
result = concat(result, co1.encode().getRaw(), sigma1.toBytes(decompositionBase, decompositionExponent), sigma2.toBytes());
result = concat(result, co1.toBytes(), sigma1.toBytes(decompositionBase, decompositionExponent), sigma2.toBytes());
return result;
}
public static SpendSignature fromBytes(byte[] a) {
@ -131,15 +128,15 @@ public class BootleRuffing {
public static SubResult SUB(F fin) {
int L = fin.pk.length; // inputs
int N = fin.pk[0].length; // ring size
PointPair[] pkz = new PointPair[L];
Curve25519PointPair[] pkz = new Curve25519PointPair[L];
Scalar[] f = new Scalar[L];
for(int j=0; j<L; j++) {
pkz[j] = new PointPair(fin.ki[j], Ed25519Group.ZERO_P3);
f[j] = hashToScalar(concat(fin.ki[j].encode().getRaw(), fin.toBytes(), longToLittleEndianUint32ByteArray(j)));
pkz[j] = new Curve25519PointPair(fin.ki[j], Curve25519Point.ZERO);
f[j] = hashToScalar(concat(fin.ki[j].toBytes(), fin.toBytes(), longToLittleEndianUint32ByteArray(j)));
}
PointPair[] c = new PointPair[N];
Curve25519PointPair[] c = new Curve25519PointPair[N];
for(int i=0; i<N; i++) {
c[i] = new PointPair(fin.co[i], fin.co1);
c[i] = new Curve25519PointPair(fin.co[i], fin.co1);
for(int j=0; j<L; j++) {
c[i] = c[i].add( (fin.pk[j][i].subtract(pkz[j])).multiply(f[j]) );
}
@ -147,9 +144,9 @@ public class BootleRuffing {
return new SubResult(c, f);
}
public static class SubResult {
public PointPair[] c;
public Curve25519PointPair[] c;
public Scalar[] f;
public SubResult(PointPair[] c, Scalar[] f) {
public SubResult(Curve25519PointPair[] c, Scalar[] f) {
this.c = c; this.f = f;
}
}
@ -177,7 +174,7 @@ public class BootleRuffing {
}
}
Ed25519GroupElement A = COMb(a, rA);
Curve25519Point A = COMb(a, rA);
Scalar[][] c = new Scalar[m][n];
Scalar[][] d = new Scalar[m][n];
@ -188,10 +185,10 @@ public class BootleRuffing {
}
}
Ed25519GroupElement C = COMb(c, rC);
Ed25519GroupElement D = COMb(d, rD);
Curve25519Point C = COMb(c, rC);
Curve25519Point D = COMb(d, rD);
Scalar x = hashToScalar(concat(A.encode().getRaw(), C.encode().getRaw(), D.encode().getRaw()));
Scalar x = hashToScalar(concat(A.toBytes(), C.toBytes(), D.toBytes()));
Scalar[][] f = new Scalar[m][n];
for(int j=0; j<m; j++) {
@ -214,20 +211,20 @@ public class BootleRuffing {
}
public static class Proof1 {
public Ed25519GroupElement A;
public Ed25519GroupElement C;
public Ed25519GroupElement D;
public Curve25519Point A;
public Curve25519Point C;
public Curve25519Point D;
private Scalar[][] fTrimmed;
private Scalar zA;
private Scalar zC;
public transient Scalar[][] a;
private Proof1(Ed25519GroupElement A, Ed25519GroupElement C, Ed25519GroupElement D, Scalar[][] fTrimmed,
private Proof1(Curve25519Point A, Curve25519Point C, Curve25519Point D, Scalar[][] fTrimmed,
Scalar zA, Scalar zC, Scalar[][] a) {
this.A = A; this.C = C; this.D = D; this.fTrimmed = fTrimmed; this.zA = zA; this.zC = zC; this.a = a;
}
private byte[] toBytes(int decompositionBase, int decompositionExponent) {
byte[] result = concat(A.encode().getRaw(), C.encode().getRaw(), D.encode().getRaw());
byte[] result = concat(A.toBytes(), C.toBytes(), D.toBytes());
for(int j=0; j<decompositionExponent; j++) {
for(int i=0; i<decompositionBase-1; i++) {
result = concat(result, fTrimmed[j][i].bytes);
@ -238,7 +235,7 @@ public class BootleRuffing {
}
}
public static Proof2 PROVE2(PointPair[] co, int iAsterisk, Scalar r, int inputs, int decompositionBase, int decompositionExponent) {
public static Proof2 PROVE2(Curve25519PointPair[] co, int iAsterisk, Scalar r, int inputs, int decompositionBase, int decompositionExponent) {
int ringSize = (int) Math.pow(decompositionBase, decompositionExponent);
@ -256,22 +253,22 @@ public class BootleRuffing {
}
}
Ed25519GroupElement B = COMb(d, rB);
Curve25519Point B = COMb(d, rB);
Proof1 P = PROVE1(d, rB);
Scalar[][] coefs = COEFS(P.a, iAsterisk);
PointPair[] G = new PointPair[decompositionExponent];
Curve25519PointPair[] G = new Curve25519PointPair[decompositionExponent];
for(int k=0; k<decompositionExponent; k++) {
G[k] = ENCeg(Ed25519Group.ZERO_P3, u[k]);
G[k] = ENCeg(Curve25519Point.ZERO, u[k]);
for (int i = 0; i < ringSize; i++) {
G[k] = G[k].add(co[i].multiply(coefs[i][k]));
}
}
byte[] bytes = concat(P.A.encode().getRaw(), P.C.encode().getRaw(), P.D.encode().getRaw());
byte[] bytes = concat(P.A.toBytes(), P.C.toBytes(), P.D.toBytes());
Scalar x1 = hashToScalar(bytes);
Scalar z = r.mul(x1.pow(decompositionExponent));
@ -284,11 +281,11 @@ public class BootleRuffing {
public static class Proof2 {
Proof1 P;
public Ed25519GroupElement B;
public PointPair[] G;
public Curve25519Point B;
public Curve25519PointPair[] G;
public Scalar z;
private Proof2(Proof1 P, Ed25519GroupElement B, PointPair[] G, Scalar z) {
private Proof2(Proof1 P, Curve25519Point B, Curve25519PointPair[] G, Scalar z) {
this.P = P;
this.B = B;
this.G = G;
@ -297,8 +294,8 @@ public class BootleRuffing {
private byte[] toBytes(int decompositionBase, int decompositionExponent) {
byte[] bytes;
bytes = concat(P.toBytes(decompositionBase, decompositionExponent), B.encode().getRaw());
for(PointPair g : G) bytes = concat(bytes, g.toBytes());
bytes = concat(P.toBytes(decompositionBase, decompositionExponent), B.toBytes());
for(Curve25519PointPair g : G) bytes = concat(bytes, g.toBytes());
bytes = concat(bytes, z.bytes);
return bytes;
}
@ -321,7 +318,7 @@ public class BootleRuffing {
return r;
}
public static boolean VALID1(Ed25519GroupElement B, Proof1 P) {
public static boolean VALID1(Curve25519Point B, Proof1 P) {
boolean abcdOnCurve =
P.A.satisfiesCurveEquation()
&& B.satisfiesCurveEquation()
@ -342,7 +339,7 @@ public class BootleRuffing {
}
}
Scalar x = hashToScalar(concat(P.A.encode().getRaw(), P.C.encode().getRaw(), P.D.encode().getRaw()));
Scalar x = hashToScalar(concat(P.A.toBytes(), P.C.toBytes(), P.D.toBytes()));
for(int j=0; j<m; j++) {
f[j][0] = x;
@ -369,11 +366,11 @@ public class BootleRuffing {
}
}
if(!B.toP3().scalarMultiply(x).toP3().add(P.A.toP3().toCached()).equals(COMb(f, P.zA))) {
if(!B.scalarMultiply(x).add(P.A).equals(COMb(f, P.zA))) {
System.out.println("VALID1: FAILED xB + A == COMp(f[0][0], ..., f[m-1][n-1]; z[A])");
return false;
}
if(!P.C.toP3().scalarMultiply(x).toP3().add(P.D.toP3().toCached()).equals(COMb(f1, P.zC))) {
if(!P.C.scalarMultiply(x).add(P.D).equals(COMb(f1, P.zC))) {
System.out.println("VALID1: FAILED xC + D == COMp(f'[0][0], ..., f'[m-1][n-1]; z[C])");
return false;
}
@ -382,7 +379,7 @@ public class BootleRuffing {
}
public static boolean VALID2(int decompositionBase, Proof2 P1, PointPair[] co) {
public static boolean VALID2(int decompositionBase, Proof2 P1, Curve25519PointPair[] co) {
boolean abcdOnCurve =
P1.P.A.satisfiesCurveEquation()
@ -399,7 +396,7 @@ public class BootleRuffing {
return false;
}
Scalar x1 = hashToScalar(concat(P1.P.A.encode().getRaw(), P1.P.C.encode().getRaw(), P1.P.D.encode().getRaw()));
Scalar x1 = hashToScalar(concat(P1.P.A.toBytes(), P1.P.C.toBytes(), P1.P.D.toBytes()));
int decompositionExponent = P1.P.fTrimmed.length;
Scalar[][] f = new Scalar[decompositionExponent][decompositionBase];
@ -411,9 +408,9 @@ public class BootleRuffing {
int ringSize = (int) Math.pow(decompositionBase, decompositionExponent);
PointPair c = ENCeg(Ed25519Group.ZERO_P3, P1.z);
Curve25519PointPair c = ENCeg(Curve25519Point.ZERO, P1.z);
Scalar x = hashToScalar(concat(P1.P.A.encode().getRaw(), P1.P.C.encode().getRaw(), P1.P.D.encode().getRaw()));
Scalar x = hashToScalar(concat(P1.P.A.toBytes(), P1.P.C.toBytes(), P1.P.D.toBytes()));
for(int j=0; j<decompositionExponent; j++) {
f[j][0] = x;
for(int i=1; i<decompositionBase; i++) {
@ -427,7 +424,7 @@ public class BootleRuffing {
g[0] = g[0].mul(f[j][0]);
}
PointPair c1 = co[0].multiply(g[0]);
Curve25519PointPair c1 = co[0].multiply(g[0]);
for(int i=1; i<ringSize; i++) {
int[] iSequence = nAryDecompose(decompositionBase, i, decompositionExponent);
g[i] = f[0][iSequence[0]];
@ -445,14 +442,14 @@ public class BootleRuffing {
boolean result = c1.equals(c);
if(!result) {
System.out.println("VALID2: FAILED: c' != c");
System.out.println("c: (" + bytesToHex(c.P1.encode().getRaw()) + ", " + bytesToHex(c.P2.encode().getRaw()));
System.out.println("c': (" + bytesToHex(c1.P1.encode().getRaw()) + ", " + bytesToHex(c1.P2.encode().getRaw()));
System.out.println("c: (" + bytesToHex(c.P1.toBytes()) + ", " + bytesToHex(c.P2.toBytes()));
System.out.println("c': (" + bytesToHex(c1.P1.toBytes()) + ", " + bytesToHex(c1.P2.toBytes()));
}
return result;
}
public static boolean VER(Ed25519GroupElement[] ki, PointPair[][] pk, Ed25519GroupElement[] co, Ed25519GroupElement co1, byte[] M, SpendSignature spendSignature) {
public static boolean VER(Curve25519Point[] ki, Curve25519PointPair[][] pk, Curve25519Point[] co, Curve25519Point co1, byte[] M, SpendSignature spendSignature) {
F f = new F(ki, pk, co, co1, M);
@ -473,11 +470,11 @@ public class BootleRuffing {
public static class Output {
public SK sk;
public Ed25519GroupElement ki;
public PointPair pk;
public Curve25519Point ki;
public Curve25519PointPair pk;
public Scalar mask;
public Ed25519GroupElement co;
public Curve25519Point co;
public BigInteger amount;
public static Output genRandomOutput(BigInteger amount) {
Output o = new Output();

View file

@ -1,7 +1,7 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.ringSignature.SpendParams;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import java.io.File;
import java.io.IOException;
@ -10,7 +10,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
import static test.how.monero.hodl.BootleRuffingSpendTest.createTestSpendParams;
public class BootleRuffingBenchmarks {
@ -54,12 +54,12 @@ public class BootleRuffingBenchmarks {
// create a transaction to spend the outputs, resulting in a signature that proves the authority to send them
SpendSignature[] spendSignature = new SpendSignature[testIterations];
for (int i=0; i<testIterations; i++) {
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
spendSignature[i] = SPEND(sp[i]);
}
int spendScalarMults = Ed25519GroupElement.scalarMults;
int spendScalarBaseMults = Ed25519GroupElement.scalarBaseMults;
int spendScalarMults = Curve25519Point.scalarMults;
int spendScalarBaseMults = Curve25519Point.scalarBaseMults;
long spendSignatureGenerationDuration = (new Date().getTime()-startMs);
System.out.println("Spend signature generation duration: " + spendSignatureGenerationDuration + " ms");
@ -72,13 +72,13 @@ public class BootleRuffingBenchmarks {
// verify the spend transaction
for (int i=0; i<testIterations; i++) {
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
boolean verified = VER(sp[i].ki, sp[i].pk, sp[i].co, spendSignature[i].co1, sp[i].M, spendSignature[i]);
System.out.println("verified: " + verified);
}
int verifyScalarMults = Ed25519GroupElement.scalarMults;
int verifyScalarBaseMults = Ed25519GroupElement.scalarBaseMults;
int verifyScalarMults = Curve25519Point.scalarMults;
int verifyScalarBaseMults = Curve25519Point.scalarBaseMults;
long spendSignatureVerificationDuration = (new Date().getTime()-startMs);

View file

@ -1,17 +1,16 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import how.monero.hodl.ringSignature.SpendParams;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import static how.monero.hodl.crypto.CryptoUtil.*;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
public class BootleRuffingSpendTest {
@ -39,35 +38,35 @@ public class BootleRuffingSpendTest {
// input commitments
// commitments to the amounts of all inputs referenced in the transaction (real inputs and decoys)
Ed25519GroupElement[][] inputCommitments = new Ed25519GroupElement[inputs][ringSize];
Curve25519Point[][] inputCommitments = new Curve25519Point[inputs][ringSize];
for(int j=0; j<inputs; j++) {
for(int i=0; i<ringSize; i++) {
if(i==sp.iAsterisk) inputCommitments[j][i] = realInputs[j].co;
else inputCommitments[j][i] = randomPoint();
else inputCommitments[j][i] = Curve25519Point.randomPoint();
}
}
// there is a co commitment for each ring
// each member of co is sum(COMp(input amt i)) - sum(COMp(output amt i))
sp.co = new Ed25519GroupElement[ringSize];
sp.co = new Curve25519Point[ringSize];
for(int i=0; i<ringSize; i++) {
sp.co[i] = inputCommitments[0][i];
for(int j=1; j<inputs; j++) {
sp.co[i] = sp.co[i].toP3().add(inputCommitments[j][i].toP3().toCached());
sp.co[i] = sp.co[i].add(inputCommitments[j][i]);
}
for(int k=0; k<outputs.length; k++) {
sp.co[i] = sp.co[i].toP3().subtract(outputs[k].co.toP3().toCached());
sp.co[i] = sp.co[i].subtract(outputs[k].co);
}
}
// the public keys for every input referenced (including real inputs and decoys)
sp.pk = new PointPair[inputs][ringSize];
sp.pk = new Curve25519PointPair[inputs][ringSize];
// the secret key for every real input referenced
sp.sk = new SK[inputs];
// the key image for every real input referenced
sp.ki = new Ed25519GroupElement[inputs];
sp.ki = new Curve25519Point[inputs];
for(int j=0; j<inputs; j++) {
for(int i=0; i<ringSize; i++) {
@ -84,14 +83,14 @@ public class BootleRuffingSpendTest {
for(int i=0; i<realInputs.length; i++) sp.s = sp.s.add(realInputs[i].mask);
for(int i=0; i<outputs.length; i++) sp.s = sp.s.sub(outputs[i].mask);
Ed25519GroupElement S = realInputs[0].co;
for(int i=1; i<realInputs.length; i++) S = S.toP3().add(realInputs[i].co.toP3().toCached());
S = S.toP3().subtract(G.scalarMultiply(new Scalar(fee)).toP3().toCached());
for(int i=0; i<outputs.length; i++) S = S.toP3().subtract(outputs[i].co.toP3().toCached());
Curve25519Point S = realInputs[0].co;
for(int i=1; i<realInputs.length; i++) S = S.add(realInputs[i].co);
S = S.subtract(Curve25519Point.G.scalarMultiply(new Scalar(fee)));
for(int i=0; i<outputs.length; i++) S = S.subtract(outputs[i].co);
Ed25519GroupElement S1 = getHpnGLookup(1).scalarMultiply(sp.s);
Curve25519Point S1 = getHpnGLookup(1).scalarMultiply(sp.s);
if(!S.toP3().equals(S1)) throw new RuntimeException("S != S'");
if(!S.equals(S1)) throw new RuntimeException("S != S'");
return sp;
}
@ -114,8 +113,8 @@ public class BootleRuffingSpendTest {
if(pauseAtEachStage) { System.out.println("Press enter to continue"); try { System.in.read(); } catch (Exception e) {}; System.out.println("Continuing..."); }
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
startMs = new Date().getTime();
// create a transaction to spend the outputs, resulting in a signature that proves the authority to send them
@ -133,29 +132,29 @@ public class BootleRuffingSpendTest {
if(pauseAtEachStage) { System.out.println("Press enter to continue"); try { System.in.read(); } catch (Exception e) {}; System.out.println("Continuing..."); }
startMs = new Date().getTime();
System.out.println("Spend ScalarMults: " + Ed25519GroupElement.scalarMults);
System.out.println("Spend BaseScalarMults: " + Ed25519GroupElement.scalarBaseMults);
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
System.out.println("Spend ScalarMults: " + Curve25519Point.scalarMults);
System.out.println("Spend BaseScalarMults: " + Curve25519Point.scalarBaseMults);
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
//Ed25519GroupElement.enableLineRecording = true;
Ed25519GroupElement.lineRecordingSourceFile = "BootleRuffing.java";
Curve25519Point.lineRecordingSourceFile = "BootleRuffing.java";
// verify the spend transaction
for (int i=0; i<testIterations; i++) {
spendSignature[i] = SpendSignature.fromBytes(spendSignatureBytes[i]);
//spendSignature[i] = SpendSignature.fromBytes(spendSignatureBytes[i]);
boolean verified = VER(sp[i].ki, sp[i].pk, sp[i].co, spendSignature[i].co1, sp[i].M, spendSignature[i]);
System.out.println("verified: " + verified);
}
System.out.println("Verify ScalarMults: " + Ed25519GroupElement.scalarMults);
System.out.println("Verify BaseScalarMults: " + Ed25519GroupElement.scalarBaseMults);
System.out.println("Verify ScalarMults: " + Curve25519Point.scalarMults);
System.out.println("Verify BaseScalarMults: " + Curve25519Point.scalarBaseMults);
System.out.println("Signature verification duration: " + (new Date().getTime()-startMs) + " ms");
if(Ed25519GroupElement.enableLineRecording) Ed25519GroupElement.lineNumberCallFrequencyMap.entrySet().stream().forEach(e->{System.out.println("line: " + e.getKey() + ", calls: " + e.getValue());});
if(Curve25519Point.enableLineRecording) Curve25519Point.lineNumberCallFrequencyMap.entrySet().stream().forEach(e->{System.out.println("line: " + e.getKey() + ", calls: " + e.getValue());});
}

View file

@ -1,10 +1,10 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Scalar;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import static how.monero.hodl.crypto.CryptoUtil.COMb;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
public class Prove1Valid1Test1 {
@ -19,7 +19,7 @@ public class Prove1Valid1Test1 {
Proof1 P = PROVE1(b, r);
Ed25519GroupElement B = COMb(b, r);
Curve25519Point B = COMb(b, r);
System.out.println("VALID1 returns " + VALID1(B, P));

View file

@ -1,10 +1,10 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import static how.monero.hodl.crypto.CryptoUtil.COMeg;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
public class Prove2Valid2Test1 {
@ -16,7 +16,7 @@ public class Prove2Valid2Test1 {
Scalar r = Scalar.ONE;
Scalar s = Scalar.ONE;
PointPair[] co = new PointPair[]{
Curve25519PointPair[] co = new Curve25519PointPair[]{
COMeg(Scalar.ZERO, r),
COMeg(Scalar.ONE, s)
};

View file

@ -1,10 +1,10 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import static how.monero.hodl.crypto.CryptoUtil.COMeg;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
public class Prove2Valid2Test1a {
@ -14,7 +14,7 @@ public class Prove2Valid2Test1a {
Scalar r = Scalar.ONE;
PointPair[] co = new PointPair[]{
Curve25519PointPair[] co = new Curve25519PointPair[]{
COMeg(Scalar.ONE,Scalar.ONE),
COMeg(Scalar.ONE,Scalar.ONE),
COMeg(Scalar.ONE,Scalar.ONE),

View file

@ -1,13 +1,13 @@
package test.how.monero.hodl;
import how.monero.hodl.crypto.PointPair;
import how.monero.hodl.crypto.Curve25519Point;
import how.monero.hodl.crypto.Curve25519PointPair;
import how.monero.hodl.crypto.Scalar;
import org.nem.core.crypto.ed25519.arithmetic.Ed25519GroupElement;
import java.util.Date;
import static how.monero.hodl.crypto.CryptoUtil.COMeg;
import static how.monero.hodl.ringSignature.BootleRuffing.*;
import static how.monero.hodl.ringSignature.StringCT.*;
public class Prove2Valid2Test1b {
@ -21,7 +21,7 @@ public class Prove2Valid2Test1b {
System.out.println("---------------------------------------------------------------------------");
int len = (int) Math.pow(2, k);
PointPair[] co = new PointPair[len];
Curve25519PointPair[] co = new Curve25519PointPair[len];
for (int i = 0; i < len; i++) co[i] = COMeg(Scalar.intToScalar(i), Scalar.ONE);
int iAsterisk = 0;
@ -34,23 +34,23 @@ public class Prove2Valid2Test1b {
System.out.println("decompositionBase: " + decompositionBase);
System.out.println("decompositionExponent: " + decompositionExponent);
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
long startMs = new Date().getTime();
Proof2 P2 = PROVE2(co, iAsterisk, r, inputs, decompositionBase, decompositionExponent);
System.out.println("PROVE2 duration: " + (new Date().getTime() - startMs) + " ms");
System.out.println("PROVE2 ScalarMults: " + Ed25519GroupElement.scalarMults);
System.out.println("PROVE2 BaseScalarMults: " + Ed25519GroupElement.scalarBaseMults);
Ed25519GroupElement.scalarMults = 0;
Ed25519GroupElement.scalarBaseMults = 0;
System.out.println("PROVE2 ScalarMults: " + Curve25519Point.scalarMults);
System.out.println("PROVE2 BaseScalarMults: " + Curve25519Point.scalarBaseMults);
Curve25519Point.scalarMults = 0;
Curve25519Point.scalarBaseMults = 0;
startMs = new Date().getTime();
System.out.println("VALID2 result: " + VALID2(decompositionBase, P2, co));
System.out.println("VALID2 ScalarMults: " + Ed25519GroupElement.scalarMults);
System.out.println("VALID2 BaseScalarMults: " + Ed25519GroupElement.scalarBaseMults);
System.out.println("VALID2 ScalarMults: " + Curve25519Point.scalarMults);
System.out.println("VALID2 BaseScalarMults: " + Curve25519Point.scalarBaseMults);
System.out.println("VALID2 duration: " + (new Date().getTime() - startMs) + " ms");
}