GordianSkeinXof.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 io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinBase.Configuration;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters;
import org.bouncycastle.crypto.ExtendedDigest;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.util.Memoable;
/**
* SkeinXof implementation.
* <p>This implementation supports the following elements of SkeinXParameters
* <ul>
* <li>Key
* <li>Nonce
* <li>Personalisation
* <li>PublicKey
* <li>Key Identifier
* <li>Max Output length
* </ul>
* <p>If OutputLen is set to zero, then SkeinXof defaults to the underlying digestLength.
*/
public class GordianSkeinXof
implements ExtendedDigest, Memoable, Xof {
/**
* The maximum Xof length.
*/
private static final int MAX_XOFLEN = -2;
/**
* The underlying Skein instance.
*/
private final GordianSkeinBase theUnderlying;
/**
* The single byte buffer.
*/
private final byte[] singleByte = new byte[1];
/**
* The next output index.
*/
private byte[] outputCache;
/**
* The XofLen.
*/
private long theXofLen;
/**
* The XofRemaining.
*/
private long theXofRemaining;
/**
* The next output index.
*/
private long nextOutputIdx;
/**
* Bytes in cache.
*/
private int bytesInCache;
/**
* Constructor.
*
* @param pDigest the underlying digest.
*/
public GordianSkeinXof(final GordianSkeinBase pDigest) {
/* Store the digest */
theUnderlying = pDigest;
outputCache = new byte[theUnderlying.getBlockSize()];
/* Clear outputting flag */
theXofRemaining = -1L;
theXofLen = -1;
}
/**
* Constructor.
*
* @param pSource the source digest.
*/
public GordianSkeinXof(final GordianSkeinXof pSource) {
/* Create hashes */
theUnderlying = (GordianSkeinBase) pSource.theUnderlying.copy();
outputCache = new byte[theUnderlying.getBlockSize()];
/* Initialise from source */
reset(pSource);
}
/**
* Constructor.
*
* @param pDigest the underlying digest.
*/
public GordianSkeinXof(final GordianSkeinDigest pDigest) {
this(pDigest.getBase());
}
/**
* Initialise.
*
* @param pParams the parameters.
*/
public void init(final GordianSkeinParameters pParams) {
/* Reject a negative Xof length */
final long myXofLen = pParams.getMaxOutputLength();
if (myXofLen < -1) {
throw new IllegalArgumentException("Invalid output length");
}
theXofLen = myXofLen;
/* Declare the configuration */
declareConfig();
/* Pass selective parameters to the underlying hash */
theUnderlying.init(pParams);
theXofRemaining = -1L;
}
/**
* Declare extended configuration.
*/
private void declareConfig() {
/* Declare the configuration */
long myLen = theXofLen == 0 ? theUnderlying.getOutputSize() : theXofLen;
myLen = myLen == -1 ? -1L : myLen * Byte.SIZE;
final Configuration myConfig = new Configuration(myLen);
theUnderlying.setConfiguration(myConfig);
}
@Override
public String getAlgorithmName() {
final String myBase = "SkeinXof";
if (theXofLen == -1) {
return myBase;
}
final long myLen = theXofLen == 0 ? theUnderlying.getOutputSize() : theXofLen;
return myBase + "-" + (theUnderlying.getBlockSize() * Byte.SIZE) + "-" + (myLen * Byte.SIZE);
}
@Override
public int getDigestSize() {
return (theXofLen == 0 || theXofLen == -1) ? theUnderlying.getOutputSize() : (int) theXofLen;
}
@Override
public int getByteLength() {
return theUnderlying.getBlockSize();
}
@Override
public void update(final byte b) {
singleByte[0] = b;
update(singleByte, 0, 1);
}
@Override
public void update(final byte[] pMessage,
final int pOffset,
final int pLen) {
if (theXofRemaining != -1) {
throw new IllegalStateException("Already outputting");
}
theUnderlying.update(pMessage, pOffset, pLen);
}
@Override
public int doFinal(final byte[] pOut,
final int pOutOffset) {
return doFinal(pOut, pOutOffset, getDigestSize());
}
@Override
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;
}
@Override
public int doOutput(final byte[] pOut,
final int pOutOffset,
final int pOutLen) {
/* If wa are switching to output */
if (theXofRemaining == -1) {
/* Initialise values */
nextOutputIdx = 0;
bytesInCache = 0;
/* If we have a null Xof */
if (theXofLen == 0) {
/* Calculate the number of bytes available */
theXofRemaining = theUnderlying.getOutputSize();
/* Else we are handling a normal Xof */
} else {
/* Calculate the number of bytes available */
theXofRemaining = theXofLen == -1
? MAX_XOFLEN
: theXofLen;
}
/* Initiate output */
theUnderlying.initiateOutput();
}
/* Reject if there is insufficient Xof remaining */
if (pOutLen < 0
|| (theXofRemaining > 0 && pOutLen > theXofRemaining)) {
throw new IllegalArgumentException("Insufficient bytes remaining");
}
/* If we have some remaining data in the cache */
int dataLeft = pOutLen;
int outPos = pOutOffset;
if (bytesInCache > 0) {
/* Copy data from current hash */
final int dataToCopy = Math.min(dataLeft, bytesInCache);
System.arraycopy(outputCache, outputCache.length - bytesInCache, pOut, outPos, dataToCopy);
/* Adjust counters */
theXofRemaining -= dataToCopy;
bytesInCache -= dataToCopy;
outPos += dataToCopy;
dataLeft -= dataToCopy;
}
/* Loop until we have completed the request */
while (dataLeft > 0) {
/* Obtain the next set of output bytes */
theUnderlying.output(nextOutputIdx++, outputCache, 0, outputCache.length);
bytesInCache = outputCache.length;
/* Copy data from current hash */
final int dataToCopy = Math.min(dataLeft, outputCache.length);
System.arraycopy(outputCache, 0, pOut, outPos, dataToCopy);
/* Adjust counters */
theXofRemaining -= dataToCopy;
bytesInCache -= dataToCopy;
outPos += dataToCopy;
dataLeft -= dataToCopy;
}
/* Return the number of bytes transferred */
return pOutLen;
}
@Override
public void reset() {
theUnderlying.reset();
theXofRemaining = -1L;
}
@Override
public void reset(final Memoable pSource) {
/* Access source */
final GordianSkeinXof mySource = (GordianSkeinXof) pSource;
/* Reset digests */
theUnderlying.reset(mySource.theUnderlying);
/* Copy state */
theXofLen = mySource.theXofLen;
theXofRemaining = mySource.theXofRemaining;
bytesInCache = mySource.bytesInCache;
nextOutputIdx = mySource.nextOutputIdx;
/* Copy cache */
System.arraycopy(mySource.outputCache, 0, outputCache, 0, outputCache.length);
/* Declare extended configuration */
declareConfig();
}
@Override
public GordianSkeinXof copy() {
return new GordianSkeinXof(this);
}
}