Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide try-with-resources-like function to SHA256 #831

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/freenet/client/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
Expand Down Expand Up @@ -748,12 +747,10 @@ public static byte[] getCryptoKey(HashResult[] hashes) {
public static byte[] getCryptoKey(byte[] hash) {
// This is exactly the same algorithm used by e.g. JFK for generating multiple session keys from a single generated value.
// The only difference is we use a constant of more than one byte's length here, to avoid having to keep a registry.
MessageDigest md = SHA256.getMessageDigest();
md.update(hash);
md.update(SPLITKEY);
byte[] buf = md.digest();
SHA256.returnMessageDigest(md);
return buf;
return SHA256.digest(md -> {
md.update(hash);
md.update(SPLITKEY);
});
}

public static byte[] getCrossSegmentSeed(HashResult[] hashes, byte[] hashThisLayerOnly) {
Expand All @@ -769,12 +766,10 @@ public static byte[] getCrossSegmentSeed(HashResult[] hashes, byte[] hashThisLay
public static byte[] getCrossSegmentSeed(byte[] hash) {
// This is exactly the same algorithm used by e.g. JFK for generating multiple session keys from a single generated value.
// The only difference is we use a constant of more than one byte's length here, to avoid having to keep a registry.
MessageDigest md = SHA256.getMessageDigest();
md.update(hash);
md.update(CROSS_SEGMENT_SEED);
byte[] buf = md.digest();
SHA256.returnMessageDigest(md);
return buf;
return SHA256.digest(md -> {
md.update(hash);
md.update(CROSS_SEGMENT_SEED);
});
}

/**
Expand Down
15 changes: 6 additions & 9 deletions src/freenet/client/async/KeyListenerTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import static java.lang.String.format;

import java.security.MessageDigest;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -20,7 +19,6 @@
import freenet.keys.KeyBlock;
import freenet.keys.NodeSSK;
import freenet.node.SendableGet;
import freenet.node.SendableRequest;
import freenet.support.ByteArrayWrapper;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
Expand Down Expand Up @@ -464,14 +462,13 @@ public byte[] saltKey(Key key) {
}

private byte[] saltKey(byte[] key) {
if (isSSKScheduler)
if (isSSKScheduler) {
return key;
MessageDigest md = SHA256.getMessageDigest();
md.update(key);
md.update(globalSalt);
byte[] ret = md.digest();
SHA256.returnMessageDigest(md);
return ret;
}
return SHA256.digest(md -> {
md.update(key);
md.update(globalSalt);
});
}

protected void hintGlobalSalt(byte[] globalSalt2) {
Expand Down
11 changes: 4 additions & 7 deletions src/freenet/client/async/SplitFileFetcherKeyListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;

import freenet.client.FetchException;
import freenet.client.FetchException.FetchExceptionMode;
Expand Down Expand Up @@ -201,12 +200,10 @@ synchronized void finishedSetup() {
}

private byte[] localSaltKey(Key key) {
MessageDigest md = SHA256.getMessageDigest();
md.update(key.getRoutingKey());
md.update(localSalt);
byte[] ret = md.digest();
SHA256.returnMessageDigest(md);
return ret;
return SHA256.digest(md -> {
md.update(key.getRoutingKey());
md.update(localSalt);
});
}

/** The segment bloom filters should only need to be written ONCE, and can all be written at
Expand Down
19 changes: 18 additions & 1 deletion src/freenet/crypt/SHA256.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
import java.security.Provider;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

import org.tanukisoftware.wrapper.WrapperManager;

Expand Down Expand Up @@ -119,10 +120,26 @@ public static void returnMessageDigest(MessageDigest md256) {
}

public static byte[] digest(byte[] data) {
return digest(md -> md.update(data));
}

public static byte[] digest(Consumer<MessageDigest> updater) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we’re doing this (and we should!) I would also love to deprecate the getMessageDigest() and returnMessageDigest() methods (hash(), too, probably), with a note on how to digest stuff in 2024! 😁

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funny enough, on modern Java this get/return dance is actually slower* compared to just creating a fresh SHA256 digest & using it just once 🙃
Maybe we should get rid of getMessageDigest/returnMessageDigest altogether (or make them return a fresh instance / no-op respectively), but that's probably for later.

* I measured 30% performance decrease for get/return over fresh digests when used by 4 threads simultaneously in a microbenchmark, when digesting a moderate 272 bytes. Most of the time is spent in updating the queue & resetting the digest rather than doing the actual computations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it’s quite possible that this, too, is a remnant of times where certain things (like MessageDigest creation) were incredibly expensive, in which case we should get rid of them.

Can you set up a small benchmark (maybe using JMH or something similar) so we can run a couple of comparisons over various platforms to see if we can get rid of this? 🙂

Copy link
Contributor

@bertm bertm Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already had this set up in JMH so sharing it was just a cleanup away. Here you go!
https://gist.github.com/bertm/94f3c97fd138fe075a1f54076c6bd3cf

My output (on a laptop with 4c/8t Intel i7-10510U and OpenJDK 21):

Benchmark               (payloadLength)   Mode  Cnt         Score         Error  Units
DigestBenchmark.fresh                16  thrpt    5  10283055,648 ± 1009465,568  ops/s
DigestBenchmark.fresh               256  thrpt    5   2513375,885 ±  208951,386  ops/s
DigestBenchmark.fresh              2048  thrpt    5    468703,126 ±   98899,698  ops/s
DigestBenchmark.pooled               16  thrpt    5   3888433,541 ±  456434,379  ops/s
DigestBenchmark.pooled              256  thrpt    5   2052883,423 ±  269884,782  ops/s
DigestBenchmark.pooled             2048  thrpt    5    473900,467 ±   16243,795  ops/s

Same laptop with OpenJDK 8 is substantially slower, but still using fresh digests performs faster than pooling them:

Benchmark               (payloadLength)   Mode  Cnt        Score         Error  Units
DigestBenchmark.fresh                16  thrpt    5  4846875,123 ± 1454094,677  ops/s
DigestBenchmark.fresh               256  thrpt    5  1343279,656 ±   56002,017  ops/s
DigestBenchmark.fresh              2048  thrpt    5   232546,571 ±    2944,786  ops/s
DigestBenchmark.pooled               16  thrpt    5  3538942,103 ±   47069,863  ops/s
DigestBenchmark.pooled              256  thrpt    5  1302934,389 ±   27901,449  ops/s
DigestBenchmark.pooled             2048  thrpt    5   234428,038 ±    5611,622  ops/s

(Of course for large payloads the difference is negligible, as can be seen for the 2048 byte payload testcase.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My results from a 2020 MacBook Pro with Intel i7, running macOS 11.7.9.

Java 8.0.402-tem:

Benchmark               (payloadLength)   Mode  Cnt        Score         Error  Units
DigestBenchmark.fresh                16  thrpt    5  4590534.845 ± 1743148.084  ops/s
DigestBenchmark.fresh               256  thrpt    5   789453.566 ±  211538.845  ops/s
DigestBenchmark.fresh              2048  thrpt    5   121031.497 ±   54753.415  ops/s
DigestBenchmark.pooled               16  thrpt    5  2299581.955 ±  321764.398  ops/s
DigestBenchmark.pooled              256  thrpt    5   928764.410 ±  331213.950  ops/s
DigestBenchmark.pooled             2048  thrpt    5   186062.632 ±   33492.467  ops/s

Java 17.0.8-tem:

Benchmark               (payloadLength)   Mode  Cnt        Score         Error  Units
DigestBenchmark.fresh                16  thrpt    5  6849782.112 ± 3876040.782  ops/s
DigestBenchmark.fresh               256  thrpt    5  1424004.927 ±  670666.188  ops/s
DigestBenchmark.fresh              2048  thrpt    5   263265.018 ±   69039.721  ops/s
DigestBenchmark.pooled               16  thrpt    5  2749789.198 ±  351308.606  ops/s
DigestBenchmark.pooled              256  thrpt    5  1358076.282 ±  661234.574  ops/s
DigestBenchmark.pooled             2048  thrpt    5   291232.774 ±   99110.753  ops/s

Java 21.0.1-tem:

Benchmark               (payloadLength)   Mode  Cnt        Score         Error  Units
DigestBenchmark.fresh                16  thrpt    5  7157264.140 ± 3133981.665  ops/s
DigestBenchmark.fresh               256  thrpt    5  1540694.555 ±  414277.743  ops/s
DigestBenchmark.fresh              2048  thrpt    5   268549.257 ±   79066.360  ops/s
DigestBenchmark.pooled               16  thrpt    5  2778790.638 ±  210145.178  ops/s
DigestBenchmark.pooled              256  thrpt    5  1262859.045 ±  341595.845  ops/s
DigestBenchmark.pooled             2048  thrpt    5   323385.319 ±   74968.957  ops/s

I have to admit that I am slightly confused by these results. When using larger payloads the pooled digests are faster, indicating that creating a small number of digests is somehow slower than creating a large number of digests? I have the feeling we’re missing something here…

Copy link
Contributor

@bertm bertm Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your measurement error is sky high (for 16 bytes approx. 50% of your measurement). The confidence intervals of your pooled and fresh measurements overlap substantially for 2048 bytes, rendering those results inconclusive. Have you tried your MacBook in performance mode? If that does not help to reduce the jitter, try increasing the number of iterations or the duration of the iterations.

Here's a re-run on OpenJDK 21 with 20 seconds per iteration on my machine, note the errors are now in the realm of 1-2% of the score where they were previously >10%:

Benchmark               (payloadLength)   Mode  Cnt        Score        Error  Units
DigestBenchmark.fresh                16  thrpt    5  9585988,644 ± 112557,463  ops/s
DigestBenchmark.fresh               256  thrpt    5  2707033,466 ±  36179,251  ops/s
DigestBenchmark.fresh              2048  thrpt    5   499138,086 ±   1028,970  ops/s
DigestBenchmark.pooled               16  thrpt    5  4160434,838 ±  31751,576  ops/s
DigestBenchmark.pooled              256  thrpt    5  2193776,379 ±  12036,799  ops/s
DigestBenchmark.pooled             2048  thrpt    5   484132,530 ±   1403,232  ops/s

In any case, the 16 byte measurement is the most relevant: it highlights the overhead of borrow/return versus object allocation best. It would be reasonable to assume that once obtained, both digests perform equally well. At that point, both variants are are just instances of the exact same class with the exact same state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll probably repeat some of the measurements tomorrow; that’s my work machine and sometimes I actually do have to work, even while benchmarking. 😀

Anyway, most relevant would be the most common size we’re hashing, and I guess that would be the size of our routing keys. Are those 16 bytes? (I have the feeling that I really should know these things… 😁) Either way, most common size will probably indeed be a lot closer to 16 than to 2048 so getting rid of the pool will probably get a 👍 from me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this is definitely an improvement over the existing code, none of the places where this method is used actually seem to be interested in "consuming a MessageDigest". Instead, they just want to digest multiple sources, together.

Can we just go for the byte[] digest(byte[]... data) that is even less of an eyesore?
For example, this code:

byte[] outerKey = SHA256.digest(md -> {
    md.update(pwd);
    md.update(salt);
});

could simply become:

byte[] outerKey = SHA256.digest(pwd, salt);

(and yes, there are a few places that would like to update(byte) - well, those edge cases can just construct a new byte[])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, the MessageDigest here is a means to an end. Using a Consumer to remove the creation (and subsequent release) of a temporary object like the message digest is a common pattern, though, so I believe that the irritation you are hinting at does not actually exist. 🙂

But yeah, by adding the vararg byte[] we could probably get rid of all other methods… 😁

MessageDigest md = null;
try {
md = getMessageDigest();
updater.accept(md);
return md.digest();
} finally {
returnMessageDigest(md);
}
}

public static byte[] digest(InputStream is) throws IOException {
MessageDigest md = null;
try {
md = getMessageDigest();
return md.digest(data);
hash(is, md);
return md.digest();
} finally {
returnMessageDigest(md);
}
Expand Down
13 changes: 6 additions & 7 deletions src/freenet/keys/CHKBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* http://www.gnu.org/ for further details of the GPL. */
package freenet.keys;

import java.security.MessageDigest;
import java.util.Arrays;

import freenet.crypt.SHA256;
Expand Down Expand Up @@ -76,12 +75,12 @@ public CHKBlock(byte[] data2, byte[] header2, NodeCHK key, boolean verify, byte
// Check the hash
if(hashIdentifier != HASH_SHA256)
throw new CHKVerifyException("Hash not SHA-256");
MessageDigest md = SHA256.getMessageDigest();

md.update(headers);
md.update(data);
byte[] hash = md.digest();
SHA256.returnMessageDigest(md);

byte[] hash = SHA256.digest(md -> {
md.update(headers);
md.update(data);
});

if(key == null) {
chk = new NodeCHK(hash, cryptoAlgorithm);
} else {
Expand Down
5 changes: 2 additions & 3 deletions src/freenet/keys/ClientCHKBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,10 @@ public Bucket decodeOld(BucketFactory bf, int maxLength, boolean dontCompress) t
// Decipher header first - functions as IV
pcfb.blockDecipher(hbuf, 0, hbuf.length);
pcfb.blockDecipher(dbuf, 0, dbuf.length);
MessageDigest md256 = SHA256.getMessageDigest();
byte[] dkey = key.cryptoKey;
// Check: IV == hash of decryption key
byte[] predIV = md256.digest(dkey);
SHA256.returnMessageDigest(md256); md256 = null;
byte[] predIV = SHA256.digest(dkey);

// Extract the IV
byte[] iv = Arrays.copyOf(hbuf, 32);
if(!Arrays.equals(iv, predIV))
Expand Down
27 changes: 9 additions & 18 deletions src/freenet/keys/ClientKSK.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* requested and inserted. */
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

import freenet.support.math.MersenneTwister;
Expand Down Expand Up @@ -43,25 +44,15 @@ public static InsertableClientSSK create(FreenetURI uri) {
}

public static ClientKSK create(String keyword) {
MessageDigest md256 = SHA256.getMessageDigest();
byte[] keywordHash = SHA256.digest(keyword.getBytes(StandardCharsets.UTF_8));
MersenneTwister mt = new MersenneTwister(keywordHash);
DSAPrivateKey privKey = new DSAPrivateKey(Global.DSAgroupBigA, mt);
DSAPublicKey pubKey = new DSAPublicKey(Global.DSAgroupBigA, privKey);
byte[] pubKeyHash = SHA256.digest(pubKey.asBytes());
try {
byte[] keywordHash;
try {
keywordHash = md256.digest(keyword.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e);
ArneBab marked this conversation as resolved.
Show resolved Hide resolved
}
MersenneTwister mt = new MersenneTwister(keywordHash);
DSAPrivateKey privKey = new DSAPrivateKey(Global.DSAgroupBigA, mt);
DSAPublicKey pubKey = new DSAPublicKey(Global.DSAgroupBigA, privKey);
byte[] pubKeyHash = md256.digest(pubKey.asBytes());
try {
return new ClientKSK(keyword, pubKeyHash, pubKey, privKey, keywordHash);
} catch (MalformedURLException e) {
throw new Error(e);
}
} finally {
SHA256.returnMessageDigest(md256);
return new ClientKSK(keyword, pubKeyHash, pubKey, privKey, keywordHash);
} catch (MalformedURLException e) {
throw new Error(e);
}
}

Expand Down
44 changes: 19 additions & 25 deletions src/freenet/keys/ClientSSK.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;

Expand Down Expand Up @@ -73,33 +74,26 @@ public ClientSSK(String docName, byte[] pubKeyHash, byte[] extras, DSAPublicKey
throw new MalformedURLException("Pubkey hash wrong length: "+pubKeyHash.length+" should be "+NodeSSK.PUBKEY_HASH_SIZE);
if(cryptoKey.length != CRYPTO_KEY_LENGTH)
throw new MalformedURLException("Decryption key wrong length: "+cryptoKey.length+" should be "+CRYPTO_KEY_LENGTH);
MessageDigest md = SHA256.getMessageDigest();
try {
if (pubKey != null) {
byte[] pubKeyAsBytes = pubKey.asBytes();
md.update(pubKeyAsBytes);
byte[] otherPubKeyHash = md.digest();
if (!Arrays.equals(otherPubKeyHash, pubKeyHash))
throw new IllegalArgumentException();
}
this.cryptoKey = cryptoKey;
try {
md.update(docName.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e);
}
byte[] buf = md.digest();
try {
Rijndael aes = new Rijndael(256, 256);
aes.initialize(cryptoKey);
aes.encipher(buf, buf);
ehDocname = buf;
} catch (UnsupportedCipherException e) {
throw new Error(e);

if (pubKey != null) {
byte[] otherPubKeyHash = SHA256.digest(pubKey.asBytes());
if (!Arrays.equals(otherPubKeyHash, pubKeyHash)) {
throw new IllegalArgumentException();
}
} finally {
SHA256.returnMessageDigest(md);
}

this.cryptoKey = cryptoKey;

byte[] buf = SHA256.digest(docName.getBytes(StandardCharsets.UTF_8));
try {
Rijndael aes = new Rijndael(256, 256);
aes.initialize(cryptoKey);
aes.encipher(buf, buf);
ehDocname = buf;
} catch (UnsupportedCipherException e) {
throw new Error(e);
}

if(ehDocname == null)
throw new NullPointerException();
hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ Fields.hashCode(ehDocname) ^ docName.hashCode();
Expand Down
26 changes: 15 additions & 11 deletions src/freenet/keys/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.Arrays;

import freenet.crypt.CryptFormatException;
Expand Down Expand Up @@ -126,17 +125,22 @@ public static KeyBlock createBlock(short keyType, byte[] keyBytes, byte[] header
* make chosen-key attacks harder.
*/
public synchronized double toNormalizedDouble() {
if(cachedNormalizedDouble > 0) return cachedNormalizedDouble;
MessageDigest md = SHA256.getMessageDigest();
if(routingKey == null) throw new NullPointerException();
md.update(routingKey);
int TYPE = getType();
md.update((byte)(TYPE >> 8));
md.update((byte)TYPE);
byte[] digest = md.digest();
SHA256.returnMessageDigest(md); md = null;
cachedNormalizedDouble = Util.keyDigestAsNormalizedDouble(digest);
if (cachedNormalizedDouble > 0) {
return cachedNormalizedDouble;
}
if (routingKey == null) {
throw new NullPointerException();
}

byte[] digest = SHA256.digest(md -> {
md.update(routingKey);
int TYPE = getType();
md.update((byte) (TYPE >> 8));
md.update((byte) TYPE);
});

cachedNormalizedDouble = Util.keyDigestAsNormalizedDouble(digest);
return cachedNormalizedDouble;
}

/**
Expand Down
10 changes: 4 additions & 6 deletions src/freenet/keys/NodeSSK.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,10 @@ public Key cloneKey() {

// routingKey = H( E(H(docname)) + H(pubkey) )
private static byte[] makeRoutingKey(byte[] pkHash, byte[] ehDocname) {
MessageDigest md256 = SHA256.getMessageDigest();
md256.update(ehDocname);
md256.update(pkHash);
byte[] key = md256.digest();
SHA256.returnMessageDigest(md256);
return key;
return SHA256.digest(md -> {
md.update(ehDocname);
md.update(pkHash);
});
}

@Override
Expand Down
33 changes: 12 additions & 21 deletions src/freenet/node/FNPPacketMangler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
Expand Down Expand Up @@ -1766,31 +1767,21 @@ public void run() {
}

private int getInitialMessageID(byte[] identity) {
MessageDigest md = SHA256.getMessageDigest();
md.update(identity);
// Similar to JFK keygen, should be safe enough.
try {
md.update("INITIAL0".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
byte[] hashed = md.digest();
SHA256.returnMessageDigest(md);
byte[] hashed = SHA256.digest(md -> {
md.update(identity);
// Similar to JFK keygen, should be safe enough.
md.update("INITIAL0".getBytes(StandardCharsets.UTF_8));
});
return Fields.bytesToInt(hashed, 0);
}

private int getInitialMessageID(byte[] identity, byte[] otherIdentity) {
MessageDigest md = SHA256.getMessageDigest();
md.update(identity);
md.update(otherIdentity);
// Similar to JFK keygen, should be safe enough.
try {
md.update("INITIAL1".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
byte[] hashed = md.digest();
SHA256.returnMessageDigest(md);
byte[] hashed = SHA256.digest(md -> {
md.update(identity);
md.update(otherIdentity);
// Similar to JFK keygen, should be safe enough.
md.update("INITIAL1".getBytes(StandardCharsets.UTF_8));
});
return Fields.bytesToInt(hashed, 0);
}

Expand Down
Loading