Skip to content
Closed
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
139 changes: 52 additions & 87 deletions core/src/main/java/org/bouncycastle/crypto/digests/Blake3Digest.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,6 @@ public class Blake3Digest
*/
private static final int FLAGS = 15;

/**
* Message word permutations.
*/
private static final byte[] SIGMA = {2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8};

/**
* Blake3 Initialization Vector.
*/
Expand Down Expand Up @@ -197,11 +192,6 @@ public class Blake3Digest
*/
private final int[] theM = new int[NUMWORDS << 1];

/**
* The indices.
*/
private final byte[] theIndices = new byte[NUMWORDS << 1];

/**
* The chainingStack.
*/
Expand Down Expand Up @@ -668,40 +658,66 @@ private void processStack()
}

/**
* Perform compression.
* Perform compression: the seven rounds of the BLAKE3 compression function over theV, with the message words in
* theM, followed by the output transformation.
* <p>
* The state and message are worked on in local variables, with the fixed message permutation applied to them
* between rounds, so that C2 can keep them in registers; theV receives the final state and theM is left
* unchanged.
* </p>
*/
private void compress()
{
/* Initialise the buffers */
initIndices();
final int[] v = theV, m = theM;

/* Loop through the rounds */
for (int round = 0; round < ROUNDS - 1; round++)
int v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3], v4 = v[4], v5 = v[5], v6 = v[6], v7 = v[7];
int v8 = v[8], v9 = v[9], v10 = v[10], v11 = v[11], v12 = v[12], v13 = v[13], v14 = v[14], v15 = v[15];

int m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7];
int m8 = m[8], m9 = m[9], m10 = m[10], m11 = m[11], m12 = m[12], m13 = m[13], m14 = m[14], m15 = m[15];

for (int round = 0; round < ROUNDS; round++)
{
/* Perform the round and permuteM */
performRound();
permuteIndices();
/* Apply to columns of V */
v0 += v4 + m0; v12 = Integers.rotateRight(v12 ^ v0, 16); v8 += v12; v4 = Integers.rotateRight(v4 ^ v8, 12);
v0 += v4 + m1; v12 = Integers.rotateRight(v12 ^ v0, 8); v8 += v12; v4 = Integers.rotateRight(v4 ^ v8, 7);

v1 += v5 + m2; v13 = Integers.rotateRight(v13 ^ v1, 16); v9 += v13; v5 = Integers.rotateRight(v5 ^ v9, 12);
v1 += v5 + m3; v13 = Integers.rotateRight(v13 ^ v1, 8); v9 += v13; v5 = Integers.rotateRight(v5 ^ v9, 7);

v2 += v6 + m4; v14 = Integers.rotateRight(v14 ^ v2, 16); v10 += v14; v6 = Integers.rotateRight(v6 ^ v10, 12);
v2 += v6 + m5; v14 = Integers.rotateRight(v14 ^ v2, 8); v10 += v14; v6 = Integers.rotateRight(v6 ^ v10, 7);

v3 += v7 + m6; v15 = Integers.rotateRight(v15 ^ v3, 16); v11 += v15; v7 = Integers.rotateRight(v7 ^ v11, 12);
v3 += v7 + m7; v15 = Integers.rotateRight(v15 ^ v3, 8); v11 += v15; v7 = Integers.rotateRight(v7 ^ v11, 7);

/* Apply to diagonals of V */
v0 += v5 + m8; v15 = Integers.rotateRight(v15 ^ v0, 16); v10 += v15; v5 = Integers.rotateRight(v5 ^ v10, 12);
v0 += v5 + m9; v15 = Integers.rotateRight(v15 ^ v0, 8); v10 += v15; v5 = Integers.rotateRight(v5 ^ v10, 7);

v1 += v6 + m10; v12 = Integers.rotateRight(v12 ^ v1, 16); v11 += v12; v6 = Integers.rotateRight(v6 ^ v11, 12);
v1 += v6 + m11; v12 = Integers.rotateRight(v12 ^ v1, 8); v11 += v12; v6 = Integers.rotateRight(v6 ^ v11, 7);

v2 += v7 + m12; v13 = Integers.rotateRight(v13 ^ v2, 16); v8 += v13; v7 = Integers.rotateRight(v7 ^ v8, 12);
v2 += v7 + m13; v13 = Integers.rotateRight(v13 ^ v2, 8); v8 += v13; v7 = Integers.rotateRight(v7 ^ v8, 7);

v3 += v4 + m14; v14 = Integers.rotateRight(v14 ^ v3, 16); v9 += v14; v4 = Integers.rotateRight(v4 ^ v9, 12);
v3 += v4 + m15; v14 = Integers.rotateRight(v14 ^ v3, 8); v9 += v14; v4 = Integers.rotateRight(v4 ^ v9, 7);

/*
* Permute the message words for the next round: m'[i] = m[SIGMA[i]], with the BLAKE3 message
* permutation SIGMA = {2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8}.
*/
final int t0 = m0, t1 = m1, t2 = m2, t3 = m3, t4 = m4, t5 = m5, t6 = m6, t7 = m7;
final int t8 = m8, t9 = m9, t10 = m10, t11 = m11, t12 = m12, t13 = m13, t14 = m14, t15 = m15;
m0 = t2; m1 = t6; m2 = t3; m3 = t10; m4 = t7; m5 = t0; m6 = t4; m7 = t13;
m8 = t1; m9 = t11; m10 = t12; m11 = t5; m12 = t9; m13 = t14; m14 = t15; m15 = t8;
}
performRound();
adjustChaining();
}

/**
* Perform a round.
*/
private void performRound()
{
/* Apply to columns of V */
mixG(0, CHAINING0, CHAINING4, IV0, COUNT0);
mixG(1, CHAINING1, CHAINING5, IV1, COUNT1);
mixG(2, CHAINING2, CHAINING6, IV2, DATALEN);
mixG(3, CHAINING3, CHAINING7, IV3, FLAGS);
v[0] = v0; v[1] = v1; v[2] = v2; v[3] = v3; v[4] = v4; v[5] = v5; v[6] = v6; v[7] = v7;
v[8] = v8; v[9] = v9; v[10] = v10; v[11] = v11; v[12] = v12; v[13] = v13; v[14] = v14; v[15] = v15;

/* Apply to diagonals of V */
mixG(4, CHAINING0, CHAINING5, IV2, FLAGS);
mixG(5, CHAINING1, CHAINING6, IV3, COUNT0);
mixG(6, CHAINING2, CHAINING7, IV0, COUNT1);
mixG(7, CHAINING3, CHAINING4, IV1, DATALEN);
adjustChaining();
}

/**
Expand Down Expand Up @@ -748,57 +764,6 @@ private void adjustChaining()
}
}

/**
* Mix function G.
*
* @param msgIdx the message index
* @param posA position A in V
* @param posB position B in V
* @param posC position C in V
* @param posD poistion D in V
*/
private void mixG(final int msgIdx,
final int posA,
final int posB,
final int posC,
final int posD)
{
/* Determine indices */
int msg = msgIdx << 1;

/* Perform the Round */
theV[posA] += theV[posB] + theM[theIndices[msg++]];
theV[posD] = Integers.rotateRight(theV[posD] ^ theV[posA], 16);
theV[posC] += theV[posD];
theV[posB] = Integers.rotateRight(theV[posB] ^ theV[posC], 12);
theV[posA] += theV[posB] + theM[theIndices[msg]];
theV[posD] = Integers.rotateRight(theV[posD] ^ theV[posA], 8);
theV[posC] += theV[posD];
theV[posB] = Integers.rotateRight(theV[posB] ^ theV[posC], 7);
}

/**
* initialise the indices.
*/
private void initIndices()
{
for (byte i = 0; i < theIndices.length; i++)
{
theIndices[i] = i;
}
}

/**
* PermuteIndices.
*/
private void permuteIndices()
{
for (byte i = 0; i < theIndices.length; i++)
{
theIndices[i] = SIGMA[theIndices[i]];
}
}

/**
* Initialise null key.
*/
Expand Down
2 changes: 2 additions & 0 deletions docs/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Date: 2026, TBD

- The BCJSSE provider adds an org.bouncycastle.jsse.BCSSLContext interface exposing extended functionality of its SSLContext, obtained with org.bouncycastle.jsse.util.ContextUtil.getBCSSLContext() by way of the new BCSSLSessionContext interface the context's session contexts implement. Its getDefaultParameters(boolean) and getSupportedParameters(boolean) return the context's default and supported parameters as a BCSSLParameters for either client or server mode, including the BC-specific properties, where SSLContext.getDefaultSSLParameters() and getSupportedSSLParameters() report client mode only and cannot carry those properties. A BCSSLContext describes the initialization of the SSLContext it was obtained from, and is not updated if the SSLContext is re-initialized.

- Blake3Digest - and with it Blake3Mac and the provider's BLAKE3 digests - now runs its compression function on local variables, applying the BLAKE3 message permutation to them between rounds, where it used to work through its state arrays and permute an index array every round, which kept the JIT from holding the state in registers. In a JMH comparison on an x86-64 machine it hashed 2.7 to 2.9 times as fast for inputs from 64 bytes to 16 KB. The output is unchanged.

### 2.1.4 Additional Notes

- The sources and javadoc jars of the Ant-built distributions (jdk14, jdk15to18 and jdk13) no longer carry test material. Each module's javadoc target copies the package documentation it needs - org/bouncycastle/<area>/**/*.html - back into the module source directory that has already been compiled from, and zip-src zips that directory afterwards, so every test package's package.html arrived in the sources jar by that route; javadoc-util additionally copied org/bouncycastle/asn1/isismtt/**/*.java, which put test classes into the bcutil javadoc as generated pages, and javadoc-pg deliberately copied the gpg and bcpg test sources in order to document them. Separately the source copies excluded test material only one directory deep and only for *.java, because Ant reads ** as an any-depth wildcard just where it is a whole path segment, so anything nested further or with another extension - the PEM certificate fixtures under org/bouncycastle/est/test/san corrected in 1.86, and an ICAO master list under org/bouncycastle/asn1/icao/test - went through. The source and javadoc copies of every module now exclude test directories at any depth, and javadoc-pg no longer documents the test packages. org.bouncycastle.util.test is unaffected and still ships in the bcprov binary, sources and javadoc jars, as it does from the Gradle build: it is the SimpleTest framework the light-weight API's own test classes are written against, not test material of the distribution. No binary changes - the classes and resources of every Ant-built jar are identical to those of the 1.86 release - and the Gradle-built jdk18on artifacts never carried any of this.
Expand Down