BouncyDigestFactory.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.bc;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSubSpec.GordianDigestState;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.digest.GordianCoreDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.digest.GordianCoreDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.digest.GordianCoreDigestSubSpec.GordianCoreDigestState;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2Base;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2Xof;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2bDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake2sDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake3Digest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianCubeHashDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianGroestlDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianJHDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianKangarooDigest.GordianKangarooBase;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianKangarooDigest.GordianKangarooTwelve;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianKangarooDigest.GordianMarsupilamiFourteen;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinDigest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinXof;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.crypto.digests.AsconHash256;
import org.bouncycastle.crypto.digests.AsconXof128;
import org.bouncycastle.crypto.digests.DSTU7564Digest;
import org.bouncycastle.crypto.digests.GOST3411Digest;
import org.bouncycastle.crypto.digests.GOST3411_2012_256Digest;
import org.bouncycastle.crypto.digests.GOST3411_2012_512Digest;
import org.bouncycastle.crypto.digests.Haraka256Digest;
import org.bouncycastle.crypto.digests.Haraka512Digest;
import org.bouncycastle.crypto.digests.ISAPDigest;
import org.bouncycastle.crypto.digests.MD2Digest;
import org.bouncycastle.crypto.digests.MD4Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.PhotonBeetleDigest;
import org.bouncycastle.crypto.digests.RIPEMD128Digest;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.bouncycastle.crypto.digests.RIPEMD256Digest;
import org.bouncycastle.crypto.digests.RIPEMD320Digest;
import org.bouncycastle.crypto.digests.RomulusDigest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA224Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.digests.SHA512tDigest;
import org.bouncycastle.crypto.digests.SHAKEDigest;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.digests.SparkleDigest;
import org.bouncycastle.crypto.digests.SparkleDigest.SparkleParameters;
import org.bouncycastle.crypto.digests.TigerDigest;
import org.bouncycastle.crypto.digests.WhirlpoolDigest;
import org.bouncycastle.crypto.digests.XoodyakDigest;

/**
 * BouncyCastle Digest Factory.
 */
public class BouncyDigestFactory
        extends GordianCoreDigestFactory {
    /**
     * Constructor.
     *
     * @param pFactory the factory
     */
    BouncyDigestFactory(final GordianBaseFactory pFactory) {
        /* Initialize underlying class */
        super(pFactory);
    }

    @Override
    public BouncyDigest createDigest(final GordianDigestSpec pDigestSpec) throws GordianException {
        /* Check validity of DigestSpec */
        checkDigestSpec(pDigestSpec);
        final GordianCoreDigestSpec mySpec = (GordianCoreDigestSpec) pDigestSpec;

        /* Create digest */
        final Digest myBCDigest = getBCDigest(mySpec);
        return myBCDigest instanceof Xof myXof
                ? new BouncyDigestXof(mySpec, myXof)
                : new BouncyDigest(mySpec, myBCDigest);
    }

    /**
     * Create the BouncyCastle digest.
     *
     * @param pDigestSpec the digestSpec
     * @return the digest
     * @throws GordianException on error
     */
    private static Digest getBCDigest(final GordianCoreDigestSpec pDigestSpec) throws GordianException {
        /* Access digest details */
        final GordianDigestType myType = pDigestSpec.getDigestType();
        final GordianLength myLen = pDigestSpec.getDigestLength();

        /* Switch on digest type */
        return switch (myType) {
            case SHA2 -> getSHA2Digest(pDigestSpec);
            case RIPEMD -> getRIPEMDDigest(myLen);
            case SKEIN -> pDigestSpec.isXofMode()
                    ? getSkeinXof(pDigestSpec.getCoreDigestState())
                    : getSkeinDigest(pDigestSpec.getCoreDigestState(), myLen);
            case SHA3 -> getSHA3Digest(myLen);
            case SHAKE -> new SHAKEDigest(pDigestSpec.getCoreDigestState().getLength().getLength());
            case KANGAROO -> getKangarooDigest(pDigestSpec);
            case HARAKA -> getHarakaDigest(pDigestSpec);
            case BLAKE2 -> pDigestSpec.isXofMode()
                    ? getBlake2Xof(pDigestSpec)
                    : getBlake2Digest(pDigestSpec);
            case BLAKE3 -> new GordianBlake3Digest(myLen.getByteLength());
            case STREEBOG -> getStreebogDigest(myLen);
            case KUPYNA -> getKupynaDigest(myLen);
            case GROESTL -> new GordianGroestlDigest(myLen.getLength());
            case JH -> new GordianJHDigest(myLen.getLength());
            case CUBEHASH -> new GordianCubeHashDigest(myLen.getLength());
            case GOST -> new GOST3411Digest();
            case TIGER -> new TigerDigest();
            case WHIRLPOOL -> new WhirlpoolDigest();
            case SM3 -> new SM3Digest();
            case SHA1 -> new SHA1Digest();
            case MD5 -> new MD5Digest();
            case MD4 -> new MD4Digest();
            case MD2 -> new MD2Digest();
            case ASCON -> getAsconDigest(pDigestSpec);
            case ISAP -> new ISAPDigest();
            case PHOTONBEETLE -> new PhotonBeetleDigest();
            case ROMULUS -> new RomulusDigest();
            case SPARKLE -> getSparkleDigest(pDigestSpec);
            case XOODYAK -> new XoodyakDigest();
            default -> throw new GordianDataException(GordianBaseData.getInvalidText(pDigestSpec.toString()));
        };
    }

    /**
     * Create the BouncyCastle RIPEMD digest.
     *
     * @param pLength the digest length
     * @return the digest
     */
    private static Digest getRIPEMDDigest(final GordianLength pLength) {
        return switch (pLength) {
            case LEN_128 -> new RIPEMD128Digest();
            case LEN_160 -> new RIPEMD160Digest();
            case LEN_256 -> new RIPEMD256Digest();
            default -> new RIPEMD320Digest();
        };
    }

    /**
     * Create the BouncyCastle Blake2 digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    static GordianBlake2Base getBlake2Digest(final GordianCoreDigestSpec pSpec) {
        final int myLength = pSpec.getDigestLength().getLength();
        return pSpec.getCoreDigestState().isBlake2bState()
                ? new GordianBlake2bDigest(myLength)
                : new GordianBlake2sDigest(myLength);
    }

    /**
     * Create the BouncyCastle Blake2Xof digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    static GordianBlake2Xof getBlake2Xof(final GordianCoreDigestSpec pSpec) {
        final GordianCoreDigestState myState = pSpec.getCoreDigestState();
        final int myLength = pSpec.getDigestLength().getLength();
        return myState.isBlake2bState()
                ? new GordianBlake2Xof(new GordianBlake2bDigest(myLength))
                : new GordianBlake2Xof(new GordianBlake2sDigest(myLength));
    }

    /**
     * Create the BouncyCastle Kangaroo digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    private static GordianKangarooBase getKangarooDigest(final GordianCoreDigestSpec pSpec) {
        final int myLength = pSpec.getDigestLength().getByteLength();
        return GordianDigestState.STATE128.equals(pSpec.getDigestState())
                ? new GordianKangarooTwelve(myLength)
                : new GordianMarsupilamiFourteen(myLength);
    }

    /**
     * Create the BouncyCastle Haraka digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    private static Digest getHarakaDigest(final GordianCoreDigestSpec pSpec) {
        return GordianDigestState.STATE256.equals(pSpec.getDigestState())
                ? new Haraka256Digest()
                : new Haraka512Digest();
    }

    /**
     * Create the BouncyCastle Ascon digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    private static Digest getAsconDigest(final GordianCoreDigestSpec pSpec) {
        return pSpec.isXofMode() ? new AsconXof128()
                : new AsconHash256();
    }

    /**
     * Create the BouncyCastle Sparkle digest.
     *
     * @param pSpec the digest spec
     * @return the digest
     */
    private static Digest getSparkleDigest(final GordianCoreDigestSpec pSpec) {
        return GordianLength.LEN_256 == pSpec.getDigestLength()
                ? new SparkleDigest(SparkleParameters.ESCH256)
                : new SparkleDigest(SparkleParameters.ESCH384);
    }

    /**
     * Create the BouncyCastle SHA2 digest.
     *
     * @param pSpec the digestSpec
     * @return the digest
     */
    private static Digest getSHA2Digest(final GordianCoreDigestSpec pSpec) {
        final GordianLength myLen = pSpec.getDigestLength();
        final GordianDigestState myState = pSpec.getDigestState();
        return switch (myLen) {
            case LEN_224 -> GordianDigestState.STATE256.equals(myState)
                    ? new SHA224Digest()
                    : new SHA512tDigest(myLen.getLength());
            case LEN_256 -> GordianDigestState.STATE256.equals(myState)
                    ? new SHA256Digest()
                    : new SHA512tDigest(myLen.getLength());
            case LEN_384 -> new SHA384Digest();
            default -> new SHA512Digest();
        };
    }

    /**
     * Create the BouncyCastle SHA3 digest.
     *
     * @param pLength the digest length
     * @return the digest
     */
    private static Digest getSHA3Digest(final GordianLength pLength) {
        return new SHA3Digest(pLength.getLength());
    }

    /**
     * Create the BouncyCastle Kupyna digest.
     *
     * @param pLength the digest length
     * @return the digest
     */
    private static Digest getKupynaDigest(final GordianLength pLength) {
        return new DSTU7564Digest(pLength.getLength());
    }

    /**
     * Create the BouncyCastle skeinDigest.
     *
     * @param pState  the state
     * @param pLength the digest length
     * @return the digest
     */
    private static Digest getSkeinDigest(final GordianCoreDigestState pState,
                                         final GordianLength pLength) {
        return new GordianSkeinDigest(pState.getLength().getLength(), pLength.getLength());
    }

    /**
     * Create the BouncyCastle skeinXof.
     *
     * @param pState the state
     * @return the digest
     */
    private static Digest getSkeinXof(final GordianCoreDigestState pState) {
        final int myLength = pState.getLength().getLength();
        return new GordianSkeinXof(new GordianSkeinDigest(myLength, myLength));
    }

    /**
     * Create the BouncyCastle Streebog digest.
     *
     * @param pLength the digest length
     * @return the digest
     */
    private static Digest getStreebogDigest(final GordianLength pLength) {
        return GordianLength.LEN_256.equals(pLength)
                ? new GOST3411_2012_256Digest()
                : new GOST3411_2012_512Digest();
    }
}