forked from Qortal/qortal
Browse Source
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.reduce-reward-shares
catbref
2 years ago
6 changed files with 1860 additions and 108 deletions
@ -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; |
||||
} |
||||
|
||||
} |
File diff suppressed because it is too large
Load Diff
@ -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); |
||||
} |
||||
} |
@ -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