GordianBlake2sDigest.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
/**
* Blake2s digest.
*/
@SuppressWarnings("checkstyle:MagicNumber")
public class GordianBlake2sDigest
extends GordianBlake2Base {
/**
* Number of Rounds.
*/
private static final int ROUNDS = 10;
/**
* Block length.
*/
private static final int BLOCK_LENGTH_BYTES = NUMWORDS * Integer.BYTES << 1;
/**
* Blake2s Initialization Vector.
*/
private static final int[] IV = {
// Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19.
// The same as SHA-256 IV.
0x6a09e667, 0xbb67ae85, 0x3c6ef372,
0xa54ff53a, 0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19
};
/**
* The state.
*/
private final int[] theH = new int[NUMWORDS];
/**
* The workBuffer.
*/
private final int[] theV = new int[NUMWORDS << 1];
/**
* The messageBuffer.
*/
private final int[] theM = new int[NUMWORDS << 1];
/**
* Low Counter.
*/
private int t0;
/**
* High Counter.
*/
private int t1;
/**
* Constructor.
*/
public GordianBlake2sDigest() {
/* Default to 256 bits */
this(256);
}
/**
* Constructor.
*
* @param pLength the digest length in bits.
*/
public GordianBlake2sDigest(final int pLength) {
/* Initialise underlying class */
super(ROUNDS, BLOCK_LENGTH_BYTES);
/* Check digest length */
if ((pLength % Byte.SIZE) != 0 || pLength < 0 || pLength > 256) {
throw new IllegalArgumentException("Incorrect digest length");
}
setDigestLength(pLength / Byte.SIZE);
activateH();
}
/**
* Constructor.
*
* @param pSource the source digest.
*/
private GordianBlake2sDigest(final GordianBlake2sDigest pSource) {
/* Initialise underlying class */
super(pSource);
/* Initialise from source */
reset((Memoable) pSource);
}
@Override
public String getAlgorithmName() {
return "Blake2s-" + getDigestSize() * Byte.SIZE;
}
@Override
public int getByteLength() {
return BLOCK_LENGTH_BYTES;
}
@Override
public void reset() {
/* Reset counter */
t0 = 0;
t1 = 0;
/* reset underlying class */
super.reset();
}
@Override
public void reset(final Memoable pSource) {
/* Access source */
final GordianBlake2sDigest mySource = (GordianBlake2sDigest) pSource;
/* reset underlying class */
super.reset(mySource);
/* Reset counter */
t0 = mySource.t0;
t1 = mySource.t1;
/* Copy state */
System.arraycopy(mySource.theH, 0, theH, 0, theH.length);
}
@Override
public GordianBlake2sDigest copy() {
return new GordianBlake2sDigest(this);
}
@Override
void adjustCounter(final int pCount) {
t0 += pCount;
if (t0 == 0) {
t1++;
}
}
@Override
void completeCounter(final int pCount) {
t0 += pCount;
if (pCount > 0 && t0 == 0) {
t1++;
}
}
@Override
void outputDigest(final byte[] pOut,
final int pOutOffset) {
/* Loop to provide the output */
final int myDigestLen = getDigestSize();
for (int i = 0, j = 0; i < NUMWORDS && j < myDigestLen; i++, j += Integer.BYTES) {
/* Convert the next word to bytes */
final byte[] bytes = Pack.intToLittleEndian(theH[i]);
if (j + Integer.BYTES < myDigestLen) {
System.arraycopy(bytes, 0, pOut, pOutOffset + j, Integer.BYTES);
} else {
System.arraycopy(bytes, 0, pOut, pOutOffset + j, myDigestLen - j);
}
}
}
@Override
protected void activateH() {
/* Initialise from IV */
System.arraycopy(IV, 0, theH, 0, IV.length);
/* Initialise first word */
theH[0] ^= getDigestSize() | (getKeyLen() << Byte.SIZE);
theH[0] ^= (getFanOut() | (getMaxDepth() << Byte.SIZE)) << Short.SIZE;
/* Initialise second word */
theH[1] ^= getLeafLen();
/* Initialise third word */
theH[2] ^= getNodeOffset();
/* Initialise fourth word */
theH[3] ^= getXofLen();
theH[3] ^= (getNodeDepth() | (getInnerLen() << Byte.SIZE)) << Short.SIZE;
/* Build salt section */
final byte[] mySalt = getSalt();
if (mySalt != null) {
theH[4] ^= Pack.littleEndianToInt(mySalt, 0);
theH[5] ^= Pack.littleEndianToInt(mySalt, Integer.BYTES);
}
/* Build personalisation section */
final byte[] myPersonal = getPersonal();
if (myPersonal != null) {
theH[6] ^= Pack.littleEndianToInt(myPersonal, 0);
theH[7] ^= Pack.littleEndianToInt(myPersonal, Integer.BYTES);
}
/* Initialise any keyBlock */
initKeyBlock();
}
@Override
protected void initV() {
/* Copy in H and IV */
System.arraycopy(theH, 0, theV, 0, NUMWORDS);
System.arraycopy(IV, 0, theV, NUMWORDS, NUMWORDS);
/* Fold in counters */
theV[12] ^= t0;
theV[13] ^= t1;
/* Fold in finalisation flags */
if (isLastBlock()) {
theV[14] ^= -1;
if (isLastNode()) {
theV[15] ^= -1;
}
}
}
@Override
protected void initM(final byte[] pMessage,
final int pMsgPos) {
/* Copy message bytes into word array */
for (int i = 0; i < NUMWORDS << 1; i++) {
theM[i] = Pack.littleEndianToInt(pMessage, pMsgPos + i * Integer.BYTES);
}
}
@Override
protected void adjustH() {
/* Combine V into H */
for (int i = 0; i < NUMWORDS; i++) {
theH[i] ^= theV[i] ^ theV[i + NUMWORDS];
}
}
@Override
protected void mixG(final int msgIdx1,
final int msgIdx2,
final int posA,
final int posB,
final int posC,
final int posD) {
/* Perform the Round */
theV[posA] += theV[posB] + theM[msgIdx1];
theV[posD] = rotr32(theV[posD] ^ theV[posA], 16);
theV[posC] += theV[posD];
theV[posB] = rotr32(theV[posB] ^ theV[posC], 12);
theV[posA] += theV[posB] + theM[msgIdx2];
theV[posD] = rotr32(theV[posD] ^ theV[posA], 8);
theV[posC] += theV[posD];
theV[posB] = rotr32(theV[posB] ^ theV[posC], 7);
}
/**
* Rotate an int right.
*
* @param x the value to rotate
* @param rot the number of bits to rotate
* @return the result
*/
private static int rotr32(final int x,
final int rot) {
return x >>> rot | (x << (Integer.SIZE - rot));
}
}