org.bouncycastle.crypto.digests.Blake3Digest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bcprov-jdk14 Show documentation
Show all versions of bcprov-jdk14 Show documentation
The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for JDK 1.4.
package org.bouncycastle.crypto.digests;
import java.util.Iterator;
import java.util.Stack;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.ExtendedDigest;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.crypto.params.Blake3Parameters;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
/**
* Blake3 implementation.
*/
public class Blake3Digest
implements ExtendedDigest, Memoable, Xof
{
/**
* Already outputting error.
*/
private static final String ERR_OUTPUTTING = "Already outputting";
/**
* Number of Words.
*/
private static final int NUMWORDS = 8;
/**
* Number of Rounds.
*/
private static final int ROUNDS = 7;
/**
* Buffer length.
*/
private static final int BLOCKLEN = NUMWORDS * Integers.BYTES * 2;
/**
* Chunk length.
*/
private static final int CHUNKLEN = 1024;
/**
* ChunkStart Flag.
*/
private static final int CHUNKSTART = 1;
/**
* ChunkEnd Flag.
*/
private static final int CHUNKEND = 2;
/**
* Parent Flag.
*/
private static final int PARENT = 4;
/**
* Root Flag.
*/
private static final int ROOT = 8;
/**
* KeyedHash Flag.
*/
private static final int KEYEDHASH = 16;
/**
* DeriveContext Flag.
*/
private static final int DERIVECONTEXT = 32;
/**
* DeriveKey Flag.
*/
private static final int DERIVEKEY = 64;
/**
* Chaining0 State Locations.
*/
private static final int CHAINING0 = 0;
/**
* Chaining1 State Location.
*/
private static final int CHAINING1 = 1;
/**
* Chaining2 State Location.
*/
private static final int CHAINING2 = 2;
/**
* Chaining3 State Location.
*/
private static final int CHAINING3 = 3;
/**
* Chaining4 State Location.
*/
private static final int CHAINING4 = 4;
/**
* Chaining5 State Location.
*/
private static final int CHAINING5 = 5;
/**
* Chaining6 State Location.
*/
private static final int CHAINING6 = 6;
/**
* Chaining7 State Location.
*/
private static final int CHAINING7 = 7;
/**
* IV0 State Locations.
*/
private static final int IV0 = 8;
/**
* IV1 State Location.
*/
private static final int IV1 = 9;
/**
* IV2 State Location.
*/
private static final int IV2 = 10;
/**
* IV3 State Location.
*/
private static final int IV3 = 11;
/**
* Count0 State Location.
*/
private static final int COUNT0 = 12;
/**
* Count1 State Location.
*/
private static final int COUNT1 = 13;
/**
* DataLen State Location.
*/
private static final int DATALEN = 14;
/**
* Flags State Location.
*/
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.
*/
private static final int[] IV = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
/**
* The byte input/output buffer.
*/
private final byte[] theBuffer = new byte[BLOCKLEN];
/**
* The key.
*/
private final int[] theK = new int[NUMWORDS];
/**
* The chaining value.
*/
private final int[] theChaining = new int[NUMWORDS];
/**
* The state.
*/
private final int[] theV = new int[NUMWORDS << 1];
/**
* The message Buffer.
*/
private final int[] theM = new int[NUMWORDS << 1];
/**
* The indices.
*/
private final byte[] theIndices = new byte[NUMWORDS << 1];
/**
* The chainingStack.
*/
private final Stack theStack = new Stack();
/**
* The default digestLength.
*/
private final int theDigestLen;
/**
* Are we outputting?
*/
private boolean outputting;
/**
* How many more bytes can we output?
*/
private long outputAvailable;
/**
* The current mode.
*/
private int theMode;
/**
* The output mode.
*/
private int theOutputMode;
/**
* The output dataLen.
*/
private int theOutputDataLen;
/**
* The block counter.
*/
private long theCounter;
/**
* The # of bytes in the current block.
*/
private int theCurrBytes;
/**
* The position of the next byte in the buffer.
*/
private int thePos;
// digest purpose
private final CryptoServicePurpose purpose;
/**
* Constructor.
*/
public Blake3Digest()
{
this((BLOCKLEN >> 1) * 8);
}
/**
* Constructor.
*
* @param pDigestSize the default digest size (in bits)
*/
public Blake3Digest(final int pDigestSize)
{
// In aid of less confusion -this is to deal with the fact the
// original checkin for this has the digest length in bytes
// and every other digest we have doesn't[].
this(pDigestSize > 100 ? pDigestSize : pDigestSize * 8, CryptoServicePurpose.ANY);
}
/**
* Base constructor with purpose.
*
* @param pDigestSize size of digest (in bits)
* @param purpose usage purpose.
*/
public Blake3Digest(final int pDigestSize, CryptoServicePurpose purpose)
{
this.purpose = purpose;
theDigestLen = pDigestSize / 8;
CryptoServicesRegistrar.checkConstraints(Utils.getDefaultProperties(this, getDigestSize() * 8, purpose));
init(null);
}
/**
* Constructor.
*
* @param pSource the source digest.
*/
public Blake3Digest(final Blake3Digest pSource)
{
/* Copy default digest length */
theDigestLen = pSource.theDigestLen;
purpose = pSource.purpose;
/* Initialise from source */
reset(pSource);
}
public int getByteLength()
{
return BLOCKLEN;
}
public String getAlgorithmName()
{
return "BLAKE3";
}
public int getDigestSize()
{
return theDigestLen;
}
/**
* Initialise.
*
* @param pParams the parameters.
*/
public void init(final Blake3Parameters pParams)
{
/* Access key/context */
final byte[] myKey = pParams == null ? null : pParams.getKey();
final byte[] myContext = pParams == null ? null : pParams.getContext();
/* Reset the digest */
reset();
/* If we have a key */
if (myKey != null)
{
/* Initialise with the key */
initKey(myKey);
Arrays.fill(myKey, (byte)0);
/* else if we have a context */
}
else if (myContext != null)
{
/* Initialise for deriving context */
initNullKey();
theMode = DERIVECONTEXT;
/* Derive key from context */
update(myContext, 0, myContext.length);
doFinal(theBuffer, 0);
initKeyFromContext();
reset();
/* Else init null key and reset mode */
}
else
{
initNullKey();
theMode = 0;
}
}
public void update(final byte b)
{
/* Check that we are not outputting */
if (outputting)
{
throw new IllegalStateException(ERR_OUTPUTTING);
}
/* If the buffer is full */
final int blockLen = theBuffer.length;
final int remainingLength = blockLen - thePos;
if (remainingLength == 0)
{
/* Process the buffer */
compressBlock(theBuffer, 0);
/* Reset the buffer */
Arrays.fill(theBuffer, (byte)0);
thePos = 0;
}
/* Store the byte */
theBuffer[thePos] = b;
thePos++;
}
public void update(final byte[] pMessage,
final int pOffset,
final int pLen)
{
/* Ignore null operation */
if (pMessage == null || pLen == 0)
{
return;
}
/* Check that we are not outputting */
if (outputting)
{
throw new IllegalStateException(ERR_OUTPUTTING);
}
/* Process any bytes currently in the buffer */
int remainingLen = 0; // left bytes of buffer
if (thePos != 0)
{
/* Calculate space remaining in the buffer */
remainingLen = BLOCKLEN - thePos;
/* If there is sufficient space in the buffer */
if (remainingLen >= pLen)
{
/* Copy data into buffer and return */
System.arraycopy(pMessage, pOffset, theBuffer, thePos, pLen);
thePos += pLen;
return;
}
/* Fill the buffer */
System.arraycopy(pMessage, pOffset, theBuffer, thePos, remainingLen);
/* Process the buffer */
compressBlock(theBuffer, 0);
/* Reset the buffer */
thePos = 0;
Arrays.fill(theBuffer, (byte)0);
}
/* process all blocks except the last one */
int messagePos;
final int blockWiseLastPos = pOffset + pLen - BLOCKLEN;
for (messagePos = pOffset + remainingLen; messagePos < blockWiseLastPos; messagePos += BLOCKLEN)
{
/* Process the buffer */
compressBlock(pMessage, messagePos);
}
/* Fill the buffer with the remaining bytes of the message */
final int len = pLen - messagePos;
System.arraycopy(pMessage, messagePos, theBuffer, 0, pOffset + len);
thePos += pOffset + len;
}
public int doFinal(final byte[] pOutput,
final int pOutOffset)
{
return doFinal(pOutput, pOutOffset, getDigestSize());
}
public int doFinal(final byte[] pOut,
final int pOutOffset,
final int pOutLen)
{
/* Build the required output */
final int length = doOutput(pOut, pOutOffset, pOutLen);
/* reset the underlying digest and return the length */
reset();
return length;
}
public int doOutput(final byte[] pOut,
final int pOutOffset,
final int pOutLen)
{
if (pOutOffset > (pOut.length - pOutLen))
{
throw new OutputLengthException("output buffer too short");
}
/* If we have not started outputting yet */
if (!outputting)
{
/* Process the buffer */
compressFinalBlock(thePos);
}
/* Reject if there is insufficient Xof remaining */
if (pOutLen < 0
|| (outputAvailable >= 0 && pOutLen > outputAvailable))
{
throw new IllegalArgumentException("Insufficient bytes remaining");
}
/* If we have some remaining data in the current buffer */
int dataLeft = pOutLen;
int outPos = pOutOffset;
if (thePos < BLOCKLEN)
{
/* Copy data from current hash */
final int dataToCopy = Math.min(dataLeft, BLOCKLEN - thePos);
System.arraycopy(theBuffer, thePos, pOut, outPos, dataToCopy);
/* Adjust counters */
thePos += dataToCopy;
outPos += dataToCopy;
dataLeft -= dataToCopy;
}
/* Loop until we have completed the request */
while (dataLeft > 0)
{
/* Calculate the next block */
nextOutputBlock();
/* Copy data from current hash */
final int dataToCopy = Math.min(dataLeft, BLOCKLEN);
System.arraycopy(theBuffer, 0, pOut, outPos, dataToCopy);
/* Adjust counters */
thePos += dataToCopy;
outPos += dataToCopy;
dataLeft -= dataToCopy;
}
/* Adjust outputAvailable */
outputAvailable -= pOutLen;
/* Return the number of bytes transferred */
return pOutLen;
}
public void reset()
{
resetBlockCount();
thePos = 0;
outputting = false;
Arrays.fill(theBuffer, (byte)0);
}
public void reset(final Memoable pSource)
{
/* Access source */
final Blake3Digest mySource = (Blake3Digest)pSource;
/* Reset counter */
theCounter = mySource.theCounter;
theCurrBytes = mySource.theCurrBytes;
theMode = mySource.theMode;
/* Reset output state */
outputting = mySource.outputting;
outputAvailable = mySource.outputAvailable;
theOutputMode = mySource.theOutputMode;
theOutputDataLen = mySource.theOutputDataLen;
/* Copy state */
System.arraycopy(mySource.theChaining, 0, theChaining, 0, theChaining.length);
System.arraycopy(mySource.theK, 0, theK, 0, theK.length);
System.arraycopy(mySource.theM, 0, theM, 0, theM.length);
/* Copy stack */
theStack.clear();
for (Iterator it = mySource.theStack.iterator(); it.hasNext(); )
{
theStack.push(Arrays.clone((int[])it.next()));
}
/* Copy buffer */
System.arraycopy(mySource.theBuffer, 0, theBuffer, 0, theBuffer.length);
thePos = mySource.thePos;
}
public Memoable copy()
{
return new Blake3Digest(this);
}
/**
* Compress next block of the message.
*
* @param pMessage the message buffer
* @param pMsgPos the position within the message buffer
*/
private void compressBlock(final byte[] pMessage,
final int pMsgPos)
{
/* Initialise state and compress message */
initChunkBlock(BLOCKLEN, false);
initM(pMessage, pMsgPos);
compress();
/* Adjust stack if we have completed a block */
if (theCurrBytes == 0)
{
adjustStack();
}
}
/**
* Adjust the stack.
*/
private void adjustStack()
{
/* Loop to combine blocks */
long myCount = theCounter;
while (myCount > 0)
{
/* Break loop if we are not combining */
if ((myCount & 1) == 1)
{
break;
}
/* Build the message to be hashed */
final int[] myLeft = (int[])theStack.pop();
System.arraycopy(myLeft, 0, theM, 0, NUMWORDS);
System.arraycopy(theChaining, 0, theM, NUMWORDS, NUMWORDS);
/* Create parent block */
initParentBlock();
compress();
/* Next block */
myCount >>= 1;
}
/* Add back to the stack */
theStack.push(Arrays.copyOf(theChaining, NUMWORDS));
}
/**
* Compress final block.
*
* @param pDataLen the data length
*/
private void compressFinalBlock(final int pDataLen)
{
/* Initialise state and compress message */
initChunkBlock(pDataLen, true);
initM(theBuffer, 0);
compress();
/* Finalise stack */
processStack();
}
/**
* Process the stack.
*/
private void processStack()
{
/* Finalise stack */
while (!theStack.isEmpty())
{
/* Build the message to be hashed */
final int[] myLeft = (int[])theStack.pop();
System.arraycopy(myLeft, 0, theM, 0, NUMWORDS);
System.arraycopy(theChaining, 0, theM, NUMWORDS, NUMWORDS);
/* Create parent block */
initParentBlock();
if (theStack.isEmpty())
{
setRoot();
}
compress();
}
}
/**
* Perform compression.
*/
private void compress()
{
/* Initialise the buffers */
initIndices();
/* Loop through the rounds */
for (int round = 0; round < ROUNDS - 1; round++)
{
/* Perform the round and permuteM */
performRound();
permuteIndices();
}
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);
/* 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);
}
/**
* Initialise M from message.
*
* @param pMessage the source message
* @param pMsgPos the message position
*/
private void initM(final byte[] pMessage,
final int pMsgPos)
{
/* Copy message bytes into word array */
Pack.littleEndianToInt(pMessage, pMsgPos, theM);
}
/**
* Adjust Chaining after compression.
*/
private void adjustChaining()
{
/* If we are outputting */
if (outputting)
{
/* Adjust full state */
for (int i = 0; i < NUMWORDS; i++)
{
theV[i] ^= theV[i + NUMWORDS];
theV[i + NUMWORDS] ^= theChaining[i];
}
/* Output state to buffer */
Pack.intToLittleEndian(theV, theBuffer, 0);
thePos = 0;
/* Else just build chain value */
}
else
{
/* Combine V into Chaining */
for (int i = 0; i < NUMWORDS; i++)
{
theChaining[i] = theV[i] ^ theV[i + NUMWORDS];
}
}
}
/**
* 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.
*/
private void initNullKey()
{
System.arraycopy(IV, 0, theK, 0, NUMWORDS);
}
/**
* Initialise key.
*
* @param pKey the keyBytes
*/
private void initKey(final byte[] pKey)
{
/* Copy message bytes into word array */
Pack.littleEndianToInt(pKey, 0, theK);
theMode = KEYEDHASH;
}
/**
* Initialise key from context.
*/
private void initKeyFromContext()
{
System.arraycopy(theV, 0, theK, 0, NUMWORDS);
theMode = DERIVEKEY;
}
/**
* Initialise chunk block.
*
* @param pDataLen the dataLength
* @param pFinal is this the final chunk?
*/
private void initChunkBlock(final int pDataLen,
final boolean pFinal)
{
/* Initialise the block */
System.arraycopy(theCurrBytes == 0 ? theK : theChaining, 0, theV, 0, NUMWORDS);
System.arraycopy(IV, 0, theV, NUMWORDS, NUMWORDS >> 1);
theV[COUNT0] = (int)theCounter;
theV[COUNT1] = (int)(theCounter >> Integers.SIZE);
theV[DATALEN] = pDataLen;
theV[FLAGS] = theMode
+ (theCurrBytes == 0 ? CHUNKSTART : 0)
+ (pFinal ? CHUNKEND : 0);
/* * Adjust block count */
theCurrBytes += pDataLen;
if (theCurrBytes >= CHUNKLEN)
{
incrementBlockCount();
theV[FLAGS] |= CHUNKEND;
}
/* If we are single chunk */
if (pFinal && theStack.isEmpty())
{
setRoot();
}
}
/**
* Initialise parent block.
*/
private void initParentBlock()
{
/* Initialise the block */
System.arraycopy(theK, 0, theV, 0, NUMWORDS);
System.arraycopy(IV, 0, theV, NUMWORDS, NUMWORDS >> 1);
theV[COUNT0] = 0;
theV[COUNT1] = 0;
theV[DATALEN] = BLOCKLEN;
theV[FLAGS] = theMode | PARENT;
}
/**
* Initialise output block.
*/
private void nextOutputBlock()
{
/* Increment the counter */
theCounter++;
/* Initialise the block */
System.arraycopy(theChaining, 0, theV, 0, NUMWORDS);
System.arraycopy(IV, 0, theV, NUMWORDS, NUMWORDS >> 1);
theV[COUNT0] = (int)theCounter;
theV[COUNT1] = (int)(theCounter >> Integers.SIZE);
theV[DATALEN] = theOutputDataLen;
theV[FLAGS] = theOutputMode;
/* Generate output */
compress();
}
/**
* IncrementBlockCount.
*/
private void incrementBlockCount()
{
theCounter++;
theCurrBytes = 0;
}
/**
* ResetBlockCount.
*/
private void resetBlockCount()
{
theCounter = 0;
theCurrBytes = 0;
}
/**
* Set root indication.
*/
private void setRoot()
{
theV[FLAGS] |= ROOT;
theOutputMode = theV[FLAGS];
theOutputDataLen = theV[DATALEN];
theCounter = 0;
outputting = true;
outputAvailable = -1;
System.arraycopy(theV, 0, theChaining, 0, NUMWORDS);
}
}