forked from Qortal/qortal
Schnorr public key and signature aggregation for 'online accounts'.
Aggregated signature should reduce block payload significantly, as well as associated network, memory & CPU loads. org.qortal.crypto.BouncyCastle25519 renamed to Qortal25519Extras. Our class provides additional features such as DH-based shared secret, aggregating public keys & signatures and sign/verify for aggregate use. BouncyCastle's Ed25519 class copied in as BouncyCastleEd25519, but with 'private' modifiers changed to 'protected', to allow extension by our Qortal25519Extras class, and to avoid lots of messy reflection-based calls.
This commit is contained in:
parent
829ab1eb37
commit
c5e5316f2e
@ -1,99 +0,0 @@
|
||||
package org.qortal.crypto;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.math.ec.rfc7748.X25519;
|
||||
import org.bouncycastle.math.ec.rfc7748.X25519Field;
|
||||
import org.bouncycastle.math.ec.rfc8032.Ed25519;
|
||||
|
||||
/** Additions to BouncyCastle providing Ed25519 to X25519 key conversion. */
|
||||
public class BouncyCastle25519 {
|
||||
|
||||
private static final Class<?> pointAffineClass;
|
||||
private static final Constructor<?> pointAffineCtor;
|
||||
private static final Method decodePointVarMethod;
|
||||
private static final Field yField;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> ed25519Class = Ed25519.class;
|
||||
pointAffineClass = Arrays.stream(ed25519Class.getDeclaredClasses()).filter(clazz -> clazz.getSimpleName().equals("PointAffine")).findFirst().get();
|
||||
if (pointAffineClass == null)
|
||||
throw new ClassNotFoundException("Can't locate PointExt inner class inside Ed25519");
|
||||
|
||||
decodePointVarMethod = ed25519Class.getDeclaredMethod("decodePointVar", byte[].class, int.class, boolean.class, pointAffineClass);
|
||||
decodePointVarMethod.setAccessible(true);
|
||||
|
||||
pointAffineCtor = pointAffineClass.getDeclaredConstructors()[0];
|
||||
pointAffineCtor.setAccessible(true);
|
||||
|
||||
yField = pointAffineClass.getDeclaredField("y");
|
||||
yField.setAccessible(true);
|
||||
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | NoSuchFieldException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Can't initialize BouncyCastle25519 shim", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] obtainYFromPublicKey(byte[] ed25519PublicKey) {
|
||||
try {
|
||||
Object pA = pointAffineCtor.newInstance();
|
||||
|
||||
Boolean result = (Boolean) decodePointVarMethod.invoke(null, ed25519PublicKey, 0, true, pA);
|
||||
if (result == null || !result)
|
||||
return null;
|
||||
|
||||
return (int[]) yField.get(pA);
|
||||
} catch (SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new RuntimeException("Can't reflect into BouncyCastle", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] toX25519PublicKey(byte[] ed25519PublicKey) {
|
||||
int[] one = new int[X25519Field.SIZE];
|
||||
X25519Field.one(one);
|
||||
|
||||
int[] y = obtainYFromPublicKey(ed25519PublicKey);
|
||||
|
||||
int[] oneMinusY = new int[X25519Field.SIZE];
|
||||
X25519Field.sub(one, y, oneMinusY);
|
||||
|
||||
int[] onePlusY = new int[X25519Field.SIZE];
|
||||
X25519Field.add(one, y, onePlusY);
|
||||
|
||||
int[] oneMinusYInverted = new int[X25519Field.SIZE];
|
||||
X25519Field.inv(oneMinusY, oneMinusYInverted);
|
||||
|
||||
int[] u = new int[X25519Field.SIZE];
|
||||
X25519Field.mul(onePlusY, oneMinusYInverted, u);
|
||||
|
||||
X25519Field.normalize(u);
|
||||
|
||||
byte[] x25519PublicKey = new byte[X25519.SCALAR_SIZE];
|
||||
X25519Field.encode(u, x25519PublicKey, 0);
|
||||
|
||||
return x25519PublicKey;
|
||||
}
|
||||
|
||||
public static byte[] toX25519PrivateKey(byte[] ed25519PrivateKey) {
|
||||
Digest d = Ed25519.createPrehash();
|
||||
byte[] h = new byte[d.getDigestSize()];
|
||||
|
||||
d.update(ed25519PrivateKey, 0, ed25519PrivateKey.length);
|
||||
d.doFinal(h, 0);
|
||||
|
||||
byte[] s = new byte[X25519.SCALAR_SIZE];
|
||||
|
||||
System.arraycopy(h, 0, s, 0, X25519.SCALAR_SIZE);
|
||||
s[0] &= 0xF8;
|
||||
s[X25519.SCALAR_SIZE - 1] &= 0x7F;
|
||||
s[X25519.SCALAR_SIZE - 1] |= 0x40;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
1427
src/main/java/org/qortal/crypto/BouncyCastleEd25519.java
Normal file
1427
src/main/java/org/qortal/crypto/BouncyCastleEd25519.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -270,10 +270,10 @@ public abstract class Crypto {
|
||||
}
|
||||
|
||||
public static byte[] getSharedSecret(byte[] privateKey, byte[] publicKey) {
|
||||
byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(privateKey);
|
||||
byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(privateKey);
|
||||
X25519PrivateKeyParameters xPrivateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0);
|
||||
|
||||
byte[] x25519PublicKey = BouncyCastle25519.toX25519PublicKey(publicKey);
|
||||
byte[] x25519PublicKey = Qortal25519Extras.toX25519PublicKey(publicKey);
|
||||
X25519PublicKeyParameters xPublicKeyParams = new X25519PublicKeyParameters(x25519PublicKey, 0);
|
||||
|
||||
byte[] sharedSecret = new byte[SHARED_SECRET_LENGTH];
|
||||
|
234
src/main/java/org/qortal/crypto/Qortal25519Extras.java
Normal file
234
src/main/java/org/qortal/crypto/Qortal25519Extras.java
Normal file
@ -0,0 +1,234 @@
|
||||
package org.qortal.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.math.ec.rfc7748.X25519;
|
||||
import org.bouncycastle.math.ec.rfc7748.X25519Field;
|
||||
import org.bouncycastle.math.ec.rfc8032.Ed25519;
|
||||
import org.bouncycastle.math.raw.Nat256;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Additions to BouncyCastle providing:
|
||||
* <p></p>
|
||||
* <ul>
|
||||
* <li>Ed25519 to X25519 key conversion</li>
|
||||
* <li>Aggregate public keys</li>
|
||||
* <li>Aggregate signatures</li>
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class Qortal25519Extras extends BouncyCastleEd25519 {
|
||||
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
public static byte[] toX25519PublicKey(byte[] ed25519PublicKey) {
|
||||
int[] one = new int[X25519Field.SIZE];
|
||||
X25519Field.one(one);
|
||||
|
||||
PointAffine pA = new PointAffine();
|
||||
if (!decodePointVar(ed25519PublicKey, 0, true, pA))
|
||||
return null;
|
||||
|
||||
int[] y = pA.y;
|
||||
|
||||
int[] oneMinusY = new int[X25519Field.SIZE];
|
||||
X25519Field.sub(one, y, oneMinusY);
|
||||
|
||||
int[] onePlusY = new int[X25519Field.SIZE];
|
||||
X25519Field.add(one, y, onePlusY);
|
||||
|
||||
int[] oneMinusYInverted = new int[X25519Field.SIZE];
|
||||
X25519Field.inv(oneMinusY, oneMinusYInverted);
|
||||
|
||||
int[] u = new int[X25519Field.SIZE];
|
||||
X25519Field.mul(onePlusY, oneMinusYInverted, u);
|
||||
|
||||
X25519Field.normalize(u);
|
||||
|
||||
byte[] x25519PublicKey = new byte[X25519.SCALAR_SIZE];
|
||||
X25519Field.encode(u, x25519PublicKey, 0);
|
||||
|
||||
return x25519PublicKey;
|
||||
}
|
||||
|
||||
public static byte[] toX25519PrivateKey(byte[] ed25519PrivateKey) {
|
||||
Digest d = Ed25519.createPrehash();
|
||||
byte[] h = new byte[d.getDigestSize()];
|
||||
|
||||
d.update(ed25519PrivateKey, 0, ed25519PrivateKey.length);
|
||||
d.doFinal(h, 0);
|
||||
|
||||
byte[] s = new byte[X25519.SCALAR_SIZE];
|
||||
|
||||
System.arraycopy(h, 0, s, 0, X25519.SCALAR_SIZE);
|
||||
s[0] &= 0xF8;
|
||||
s[X25519.SCALAR_SIZE - 1] &= 0x7F;
|
||||
s[X25519.SCALAR_SIZE - 1] |= 0x40;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Mostly for test support
|
||||
public static PointAccum newPointAccum() {
|
||||
return new PointAccum();
|
||||
}
|
||||
|
||||
public static byte[] aggregatePublicKeys(Collection<byte[]> publicKeys) {
|
||||
PointAccum rAccum = null;
|
||||
|
||||
for (byte[] publicKey : publicKeys) {
|
||||
PointAffine pA = new PointAffine();
|
||||
if (!decodePointVar(publicKey, 0, false, pA))
|
||||
// Failed to decode
|
||||
return null;
|
||||
|
||||
if (rAccum == null) {
|
||||
rAccum = new PointAccum();
|
||||
pointCopy(pA, rAccum);
|
||||
} else {
|
||||
pointAdd(pointCopy(pA), rAccum);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] publicKey = new byte[SCALAR_BYTES];
|
||||
if (0 == encodePoint(rAccum, publicKey, 0))
|
||||
// Failed to encode
|
||||
return null;
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public static byte[] aggregateSignatures(Collection<byte[]> signatures) {
|
||||
// Signatures are (R, s)
|
||||
// R is a point
|
||||
// s is a scalar
|
||||
PointAccum rAccum = null;
|
||||
int[] sAccum = new int[SCALAR_INTS];
|
||||
|
||||
byte[] rEncoded = new byte[POINT_BYTES];
|
||||
int[] sPart = new int[SCALAR_INTS];
|
||||
for (byte[] signature : signatures) {
|
||||
System.arraycopy(signature,0, rEncoded, 0, rEncoded.length);
|
||||
|
||||
PointAffine pA = new PointAffine();
|
||||
if (!decodePointVar(rEncoded, 0, false, pA))
|
||||
// Failed to decode
|
||||
return null;
|
||||
|
||||
if (rAccum == null) {
|
||||
rAccum = new PointAccum();
|
||||
pointCopy(pA, rAccum);
|
||||
|
||||
decode32(signature, rEncoded.length, sAccum, 0, SCALAR_INTS);
|
||||
} else {
|
||||
pointAdd(pointCopy(pA), rAccum);
|
||||
|
||||
decode32(signature, rEncoded.length, sPart, 0, SCALAR_INTS);
|
||||
Nat256.addTo(sPart, sAccum);
|
||||
|
||||
// "mod L" on sAccum
|
||||
if (Nat256.gte(sAccum, L))
|
||||
Nat256.subFrom(L, sAccum);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_SIZE];
|
||||
if (0 == encodePoint(rAccum, signature, 0))
|
||||
// Failed to encode
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < sAccum.length; ++i) {
|
||||
encode32(sAccum[i], signature, POINT_BYTES + i * 4);
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
public static byte[] signForAggregation(byte[] privateKey, byte[] message) {
|
||||
// Very similar to BouncyCastle's implementation except we use secure random nonce and different hash
|
||||
Digest d = new SHA512Digest();
|
||||
byte[] h = new byte[d.getDigestSize()];
|
||||
|
||||
d.reset();
|
||||
d.update(privateKey, 0, privateKey.length);
|
||||
d.doFinal(h, 0);
|
||||
|
||||
byte[] sH = new byte[SCALAR_BYTES];
|
||||
pruneScalar(h, 0, sH);
|
||||
|
||||
byte[] publicKey = new byte[SCALAR_BYTES];
|
||||
scalarMultBaseEncoded(sH, publicKey, 0);
|
||||
|
||||
byte[] rSeed = new byte[d.getDigestSize()];
|
||||
SECURE_RANDOM.nextBytes(rSeed);
|
||||
|
||||
byte[] r = new byte[SCALAR_BYTES];
|
||||
pruneScalar(rSeed, 0, r);
|
||||
|
||||
byte[] R = new byte[POINT_BYTES];
|
||||
scalarMultBaseEncoded(r, R, 0);
|
||||
|
||||
d.reset();
|
||||
d.update(message, 0, message.length);
|
||||
d.doFinal(h, 0);
|
||||
byte[] k = reduceScalar(h);
|
||||
|
||||
byte[] s = calculateS(r, k, sH);
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_SIZE];
|
||||
System.arraycopy(R, 0, signature, 0, POINT_BYTES);
|
||||
System.arraycopy(s, 0, signature, POINT_BYTES, SCALAR_BYTES);
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
public static boolean verifyAggregated(byte[] publicKey, byte[] signature, byte[] message) {
|
||||
byte[] R = Arrays.copyOfRange(signature, 0, POINT_BYTES);
|
||||
|
||||
byte[] s = Arrays.copyOfRange(signature, POINT_BYTES, POINT_BYTES + SCALAR_BYTES);
|
||||
|
||||
if (!checkPointVar(R))
|
||||
// R out of bounds
|
||||
return false;
|
||||
|
||||
if (!checkScalarVar(s))
|
||||
// s out of bounds
|
||||
return false;
|
||||
|
||||
byte[] S = new byte[POINT_BYTES];
|
||||
scalarMultBaseEncoded(s, S, 0);
|
||||
|
||||
PointAffine pA = new PointAffine();
|
||||
if (!decodePointVar(publicKey, 0, true, pA))
|
||||
// Failed to decode
|
||||
return false;
|
||||
|
||||
Digest d = new SHA512Digest();
|
||||
byte[] h = new byte[d.getDigestSize()];
|
||||
|
||||
d.update(message, 0, message.length);
|
||||
d.doFinal(h, 0);
|
||||
|
||||
byte[] k = reduceScalar(h);
|
||||
|
||||
int[] nS = new int[SCALAR_INTS];
|
||||
decodeScalar(s, 0, nS);
|
||||
|
||||
int[] nA = new int[SCALAR_INTS];
|
||||
decodeScalar(k, 0, nA);
|
||||
|
||||
/*PointAccum*/
|
||||
PointAccum pR = new PointAccum();
|
||||
scalarMultStrausVar(nS, nA, pA, pR);
|
||||
|
||||
byte[] check = new byte[POINT_BYTES];
|
||||
if (0 == encodePoint(pR, check, 0))
|
||||
// Failed to encode
|
||||
return false;
|
||||
|
||||
return Arrays.equals(check, R);
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.AES;
|
||||
import org.qortal.crypto.BouncyCastle25519;
|
||||
import org.qortal.crypto.Qortal25519Extras;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.utils.Base58;
|
||||
@ -123,14 +123,14 @@ public class CryptoTests extends Common {
|
||||
random.nextBytes(ed25519PrivateKey);
|
||||
PrivateKeyAccount account = new PrivateKeyAccount(null, ed25519PrivateKey);
|
||||
|
||||
byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(account.getPrivateKey());
|
||||
byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(account.getPrivateKey());
|
||||
X25519PrivateKeyParameters x25519PrivateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0);
|
||||
|
||||
// Derive X25519 public key from X25519 private key
|
||||
byte[] x25519PublicKeyFromPrivate = x25519PrivateKeyParams.generatePublicKey().getEncoded();
|
||||
|
||||
// Derive X25519 public key from Ed25519 public key
|
||||
byte[] x25519PublicKeyFromEd25519 = BouncyCastle25519.toX25519PublicKey(account.getPublicKey());
|
||||
byte[] x25519PublicKeyFromEd25519 = Qortal25519Extras.toX25519PublicKey(account.getPublicKey());
|
||||
|
||||
assertEquals(String.format("Public keys do not match, from private key %s", Base58.encode(ed25519PrivateKey)), Base58.encode(x25519PublicKeyFromPrivate), Base58.encode(x25519PublicKeyFromEd25519));
|
||||
}
|
||||
@ -162,10 +162,10 @@ public class CryptoTests extends Common {
|
||||
}
|
||||
|
||||
private static byte[] calcBCSharedSecret(byte[] ed25519PrivateKey, byte[] ed25519PublicKey) {
|
||||
byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(ed25519PrivateKey);
|
||||
byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(ed25519PrivateKey);
|
||||
X25519PrivateKeyParameters privateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0);
|
||||
|
||||
byte[] x25519PublicKey = BouncyCastle25519.toX25519PublicKey(ed25519PublicKey);
|
||||
byte[] x25519PublicKey = Qortal25519Extras.toX25519PublicKey(ed25519PublicKey);
|
||||
X25519PublicKeyParameters publicKeyParams = new X25519PublicKeyParameters(x25519PublicKey, 0);
|
||||
|
||||
byte[] sharedSecret = new byte[32];
|
||||
@ -186,10 +186,10 @@ public class CryptoTests extends Common {
|
||||
final String expectedTheirX25519PublicKey = "ANjnZLRSzW9B1aVamiYGKP3XtBooU9tGGDjUiibUfzp2";
|
||||
final String expectedSharedSecret = "DTMZYG96x8XZuGzDvHFByVLsXedimqtjiXHhXPVe58Ap";
|
||||
|
||||
byte[] ourX25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(ourPrivateKey);
|
||||
byte[] ourX25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(ourPrivateKey);
|
||||
assertEquals("X25519 private key incorrect", expectedOurX25519PrivateKey, Base58.encode(ourX25519PrivateKey));
|
||||
|
||||
byte[] theirX25519PublicKey = BouncyCastle25519.toX25519PublicKey(theirPublicKey);
|
||||
byte[] theirX25519PublicKey = Qortal25519Extras.toX25519PublicKey(theirPublicKey);
|
||||
assertEquals("X25519 public key incorrect", expectedTheirX25519PublicKey, Base58.encode(theirX25519PublicKey));
|
||||
|
||||
byte[] sharedSecret = calcBCSharedSecret(ourPrivateKey, theirPublicKey);
|
||||
|
190
src/test/java/org/qortal/test/SchnorrTests.java
Normal file
190
src/test/java/org/qortal/test/SchnorrTests.java
Normal file
@ -0,0 +1,190 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crypto.Qortal25519Extras;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SchnorrTests extends Qortal25519Extras {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
}
|
||||
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
@Test
|
||||
public void testConversion() {
|
||||
// Scalar form
|
||||
byte[] scalarA = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
System.out.printf("a: %s%n", HashCode.fromBytes(scalarA));
|
||||
|
||||
byte[] pointA = HashCode.fromString("5866666666666666666666666666666666666666666666666666666666666666".toLowerCase()).asBytes();
|
||||
|
||||
BigInteger expectedY = new BigInteger("46316835694926478169428394003475163141307993866256225615783033603165251855960");
|
||||
|
||||
PointAccum pointAccum = Qortal25519Extras.newPointAccum();
|
||||
scalarMultBase(scalarA, pointAccum);
|
||||
|
||||
byte[] encoded = new byte[POINT_BYTES];
|
||||
if (0 == encodePoint(pointAccum, encoded, 0))
|
||||
fail("Point encoding failed");
|
||||
|
||||
System.out.printf("aG: %s%n", HashCode.fromBytes(encoded));
|
||||
assertArrayEquals(pointA, encoded);
|
||||
|
||||
byte[] yBytes = new byte[POINT_BYTES];
|
||||
System.arraycopy(encoded,0, yBytes, 0, encoded.length);
|
||||
Bytes.reverse(yBytes);
|
||||
|
||||
System.out.printf("yBytes: %s%n", HashCode.fromBytes(yBytes));
|
||||
BigInteger yBI = new BigInteger(yBytes);
|
||||
|
||||
System.out.printf("aG y: %s%n", yBI);
|
||||
assertEquals(expectedY, yBI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddition() {
|
||||
/*
|
||||
* 1G: b'5866666666666666666666666666666666666666666666666666666666666666'
|
||||
* 2G: b'c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022'
|
||||
* 3G: b'd4b4f5784868c3020403246717ec169ff79e26608ea126a1ab69ee77d1b16712'
|
||||
*/
|
||||
|
||||
// Scalar form
|
||||
byte[] s1 = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
byte[] s2 = HashCode.fromString("0200000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
|
||||
// Point form
|
||||
byte[] g1 = HashCode.fromString("5866666666666666666666666666666666666666666666666666666666666666".toLowerCase()).asBytes();
|
||||
byte[] g2 = HashCode.fromString("c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022".toLowerCase()).asBytes();
|
||||
byte[] g3 = HashCode.fromString("d4b4f5784868c3020403246717ec169ff79e26608ea126a1ab69ee77d1b16712".toLowerCase()).asBytes();
|
||||
|
||||
PointAccum p1 = Qortal25519Extras.newPointAccum();
|
||||
scalarMultBase(s1, p1);
|
||||
|
||||
PointAccum p2 = Qortal25519Extras.newPointAccum();
|
||||
scalarMultBase(s2, p2);
|
||||
|
||||
pointAdd(pointCopy(p1), p2);
|
||||
|
||||
byte[] encoded = new byte[POINT_BYTES];
|
||||
if (0 == encodePoint(p2, encoded, 0))
|
||||
fail("Point encoding failed");
|
||||
|
||||
System.out.printf("sum: %s%n", HashCode.fromBytes(encoded));
|
||||
assertArrayEquals(g3, encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleSign() {
|
||||
byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes();
|
||||
|
||||
byte[] signature = signForAggregation(privateKey, message);
|
||||
System.out.printf("signature: %s%n", HashCode.fromBytes(signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleVerify() {
|
||||
byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes();
|
||||
byte[] signature = HashCode.fromString("13e58e88f3df9e06637d2d5bbb814c028e3ba135494530b9d3b120bdb31168d62c70a37ae9cfba816fe6038ee1ce2fb521b95c4a91c7ff0bb1dd2e67733f2b0d".toLowerCase()).asBytes();
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0);
|
||||
|
||||
assertTrue(verifyAggregated(publicKey, signature, message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleSignAndVerify() {
|
||||
byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes();
|
||||
byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes();
|
||||
|
||||
byte[] signature = signForAggregation(privateKey, message);
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0);
|
||||
|
||||
assertTrue(verifyAggregated(publicKey, signature, message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleAggregate() {
|
||||
List<OnlineAccountData> onlineAccounts = generateOnlineAccounts(1);
|
||||
|
||||
byte[] aggregatePublicKey = aggregatePublicKeys(onlineAccounts.stream().map(OnlineAccountData::getPublicKey).collect(Collectors.toUnmodifiableList()));
|
||||
System.out.printf("Aggregate public key: %s%n", HashCode.fromBytes(aggregatePublicKey));
|
||||
|
||||
byte[] aggregateSignature = aggregateSignatures(onlineAccounts.stream().map(OnlineAccountData::getSignature).collect(Collectors.toUnmodifiableList()));
|
||||
System.out.printf("Aggregate signature: %s%n", HashCode.fromBytes(aggregateSignature));
|
||||
|
||||
OnlineAccountData onlineAccount = onlineAccounts.get(0);
|
||||
|
||||
assertArrayEquals(String.format("expected: %s, actual: %s", HashCode.fromBytes(onlineAccount.getPublicKey()), HashCode.fromBytes(aggregatePublicKey)), onlineAccount.getPublicKey(), aggregatePublicKey);
|
||||
assertArrayEquals(String.format("expected: %s, actual: %s", HashCode.fromBytes(onlineAccount.getSignature()), HashCode.fromBytes(aggregateSignature)), onlineAccount.getSignature(), aggregateSignature);
|
||||
|
||||
// This is the crucial test:
|
||||
long timestamp = onlineAccount.getTimestamp();
|
||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
||||
assertTrue(verifyAggregated(aggregatePublicKey, aggregateSignature, timestampBytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAggregate() {
|
||||
List<OnlineAccountData> onlineAccounts = generateOnlineAccounts(5000);
|
||||
|
||||
byte[] aggregatePublicKey = aggregatePublicKeys(onlineAccounts.stream().map(OnlineAccountData::getPublicKey).collect(Collectors.toUnmodifiableList()));
|
||||
System.out.printf("Aggregate public key: %s%n", HashCode.fromBytes(aggregatePublicKey));
|
||||
|
||||
byte[] aggregateSignature = aggregateSignatures(onlineAccounts.stream().map(OnlineAccountData::getSignature).collect(Collectors.toUnmodifiableList()));
|
||||
System.out.printf("Aggregate signature: %s%n", HashCode.fromBytes(aggregateSignature));
|
||||
|
||||
OnlineAccountData onlineAccount = onlineAccounts.get(0);
|
||||
|
||||
// This is the crucial test:
|
||||
long timestamp = onlineAccount.getTimestamp();
|
||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
||||
assertTrue(verifyAggregated(aggregatePublicKey, aggregateSignature, timestampBytes));
|
||||
}
|
||||
|
||||
private List<OnlineAccountData> generateOnlineAccounts(int numAccounts) {
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>();
|
||||
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] timestampBytes = Longs.toByteArray(timestamp);
|
||||
|
||||
for (int a = 0; a < numAccounts; ++a) {
|
||||
byte[] privateKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
SECURE_RANDOM.nextBytes(privateKey);
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0);
|
||||
|
||||
byte[] signature = signForAggregation(privateKey, timestampBytes);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey));
|
||||
}
|
||||
|
||||
return onlineAccounts;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user