GordianStreamKeySpec.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.api.cipher;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import org.bouncycastle.crypto.engines.ElephantEngine.ElephantParameters;
import org.bouncycastle.crypto.engines.ISAPEngine.IsapType;
import org.bouncycastle.crypto.engines.RomulusEngine.RomulusParameters;
import org.bouncycastle.crypto.engines.SparkleEngine.SparkleParameters;

import java.util.Objects;

/**
 * GordianKnot StreamKeySpec.
 */
public class GordianStreamKeySpec
        implements GordianKeySpec {
    /**
     * The Separator.
     */
    private static final String SEP = "-";

    /**
     * The StreamKey Type.
     */
    private final GordianStreamKeyType theStreamKeyType;

    /**
     * The Key Length.
     */
    private final GordianLength theKeyLength;

    /**
     * SubKeyType.
     */
    private final GordianStreamSubKeyType theSubKeyType;

    /**
     * The Validity.
     */
    private final boolean isValid;

    /**
     * The String name.
     */
    private String theName;

    /**
     * Constructor.
     *
     * @param pStreamKeyType the streamKeyType
     * @param pKeyLength     the keyLength
     */
    public GordianStreamKeySpec(final GordianStreamKeyType pStreamKeyType,
                                final GordianLength pKeyLength) {
        this(pStreamKeyType, pKeyLength, defaultSubKeyType(pStreamKeyType));
    }

    /**
     * Constructor.
     *
     * @param pStreamKeyType the streamKeyType
     * @param pKeyLength     the keyLength
     * @param pSubKeyType    the subKeyType
     */
    public GordianStreamKeySpec(final GordianStreamKeyType pStreamKeyType,
                                final GordianLength pKeyLength,
                                final GordianStreamSubKeyType pSubKeyType) {
        /* Store parameters */
        theStreamKeyType = pStreamKeyType;
        theKeyLength = pKeyLength;
        theSubKeyType = pSubKeyType;
        isValid = checkValidity();
    }

    /**
     * Obtain streamKey Type.
     *
     * @return the streamKeyType
     */
    public GordianStreamKeyType getStreamKeyType() {
        return theStreamKeyType;
    }

    @Override
    public GordianLength getKeyLength() {
        return theKeyLength;
    }

    /**
     * Obtain subKey Type.
     *
     * @return the subKeyType
     */
    public GordianStreamSubKeyType getSubKeyType() {
        return theSubKeyType;
    }

    /**
     * Is the keySpec valid?
     *
     * @return true/false.
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * Does this cipher need an IV?
     *
     * @return true/false
     */
    public boolean needsIV() {
        return getIVLength() > 0;
    }

    /**
     * Check spec validity.
     *
     * @return valid true/false
     */
    private boolean checkValidity() {
        /* Stream KeyType and Key length must be non-null */
        if (theStreamKeyType == null
                || theKeyLength == null) {
            return false;
        }

        /* Check subKeyTypes */
        switch (theStreamKeyType) {
            case SALSA20:
                return checkSalsaValidity();
            case CHACHA20:
                return checkChaChaValidity();
            case VMPC:
                return checkVMPCValidity();
            case SKEINXOF:
                return checkSkeinValidity();
            case BLAKE2XOF:
                return checkBlake2Validity();
            case ELEPHANT:
                return theSubKeyType instanceof GordianElephantKey
                        && theStreamKeyType.validForKeyLength(theKeyLength);
            case ISAP:
                return theSubKeyType instanceof GordianISAPKey
                        && theStreamKeyType.validForKeyLength(theKeyLength);
            case ROMULUS:
                return theSubKeyType instanceof GordianRomulusKey
                        && theStreamKeyType.validForKeyLength(theKeyLength);
            case SPARKLE:
                return checkSparkleValidity();
            default:
                return theSubKeyType == null
                        && theStreamKeyType.validForKeyLength(theKeyLength);
        }
    }

    /**
     * Check salsa spec validity.
     *
     * @return valid true/false
     */
    private boolean checkSalsaValidity() {
        /* SubKeyType must be a SalsaKey */
        if (!(theSubKeyType instanceof GordianSalsa20Key)) {
            return false;
        }

        /* Check keyLength validity */
        return theSubKeyType != GordianSalsa20Key.STD
                ? theKeyLength == GordianLength.LEN_256
                : theStreamKeyType.validForKeyLength(theKeyLength);
    }

    /**
     * Check chacha spec validity.
     *
     * @return valid true/false
     */
    private boolean checkChaChaValidity() {
        /* SubKeyType must be a ChaChaKey */
        if (!(theSubKeyType instanceof GordianChaCha20Key)) {
            return false;
        }

        /* Check keyLength validity */
        return theSubKeyType != GordianChaCha20Key.STD
                ? theKeyLength == GordianLength.LEN_256
                : theStreamKeyType.validForKeyLength(theKeyLength);
    }

    /**
     * Check vmpc spec validity.
     *
     * @return valid true/false
     */
    private boolean checkVMPCValidity() {
        /* SubKeyType must be a GordianVMPCKey */
        if (!(theSubKeyType instanceof GordianVMPCKey)) {
            return false;
        }

        /* Check keyLength validity */
        return theStreamKeyType.validForKeyLength(theKeyLength);
    }

    /**
     * Check skein spec validity.
     *
     * @return valid true/false
     */
    private boolean checkSkeinValidity() {
        /* SubKeyType must be a GordianSkeinXofKey */
        if (!(theSubKeyType instanceof GordianSkeinXofKey)) {
            return false;
        }

        /* Check keyLength validity */
        return theStreamKeyType.validForKeyLength(theKeyLength);
    }

    /**
     * Check blake2 spec validity.
     *
     * @return valid true/false
     */
    private boolean checkBlake2Validity() {
        /* SubKeyType must be a GordianBlakeXofKey */
        if (!(theSubKeyType instanceof GordianBlakeXofKey)) {
            return false;
        }

        /* Check keyLength validity */
        final GordianBlakeXofKey myType = (GordianBlakeXofKey) theSubKeyType;
        return theStreamKeyType.validForKeyLength(theKeyLength)
                && (myType != GordianBlakeXofKey.BLAKE2XS
                || theKeyLength != GordianLength.LEN_512);
    }

    /**
     * Check sparkle spec validity.
     *
     * @return valid true/false
     */
    private boolean checkSparkleValidity() {
        /* SubKeyType must be a GordianSparkleKey */
        if (!(theSubKeyType instanceof GordianSparkleKey)) {
            return false;
        }

        /* Check keyLength validity */
        return theKeyLength == ((GordianSparkleKey) theSubKeyType).requiredKeyLength();
    }

    @Override
    public String toString() {
        /* If we have not yet loaded the name */
        if (theName == null) {
            /* If the keySpec is valid */
            if (isValid) {
                /* Load the name */
                theName = getName();
                theName += SEP + theKeyLength;
            } else {
                /* Report invalid spec */
                theName = "InvalidStreamKeySpec: " + theStreamKeyType + ":" + theKeyLength;
            }
        }

        /* return the name */
        return theName;
    }

    /**
     * Determine the name for the KeySpec.
     *
     * @return the name
     */
    private String getName() {
        /* Handle VMPC-KSA */
        if (theStreamKeyType == GordianStreamKeyType.VMPC
                && theSubKeyType == GordianVMPCKey.KSA) {
            return theStreamKeyType + "KSA3";
        }

        /* Handle XSalsa20 */
        if (theStreamKeyType == GordianStreamKeyType.SALSA20
                && theSubKeyType == GordianSalsa20Key.XSALSA) {
            return "X" + theStreamKeyType;
        }

        /* Handle XChaCha20 */
        if (theStreamKeyType == GordianStreamKeyType.CHACHA20
                && theSubKeyType == GordianChaCha20Key.XCHACHA) {
            return "X" + theStreamKeyType;
        }

        /* Handle ChaCha7539 */
        if (theStreamKeyType == GordianStreamKeyType.CHACHA20
                && theSubKeyType == GordianChaCha20Key.ISO7539) {
            return "ChaCha7539";
        }

        /* Handle Skein */
        if (theStreamKeyType == GordianStreamKeyType.SKEINXOF) {
            return theStreamKeyType + SEP + theSubKeyType.toString();
        }

        /* Handle Remaining types */
        switch (theStreamKeyType) {
            case BLAKE2XOF:
            case ELEPHANT:
            case ISAP:
            case ROMULUS:
            case SPARKLE:
                return theSubKeyType.toString();
            default:
                return theStreamKeyType.toString();
        }
    }

    /**
     * Obtain the IV Length.
     *
     * @return the IV length.
     */
    public int getIVLength() {
        switch (theStreamKeyType) {
            case RABBIT:
                return GordianLength.LEN_64.getByteLength();
            case GRAIN:
            case ELEPHANT:
                return GordianLength.LEN_96.getByteLength();
            case SOSEMANUK:
            case SNOW3G:
            case BLAKE3XOF:
            case SKEINXOF:
            case ASCON:
            case ISAP:
            case PHOTONBEETLE:
            case ROMULUS:
            case XOODYAK:
                return GordianLength.LEN_128.getByteLength();
            case HC:
                return GordianLength.LEN_128 == theKeyLength
                        ? GordianLength.LEN_128.getByteLength()
                        : GordianLength.LEN_256.getByteLength();
            case ZUC:
                return GordianLength.LEN_128 == theKeyLength
                        ? GordianLength.LEN_128.getByteLength()
                        : GordianLength.LEN_200.getByteLength();
            case VMPC:
                return theKeyLength.getByteLength();
            case BLAKE2XOF:
                return ((GordianBlakeXofKey) theSubKeyType).requiredIVLength().getByteLength();
            case CHACHA20:
                return ((GordianChaCha20Key) theSubKeyType).requiredIVLength().getByteLength();
            case SALSA20:
                return ((GordianSalsa20Key) theSubKeyType).requiredIVLength().getByteLength();
            case SPARKLE:
                return ((GordianSparkleKey) theSubKeyType).requiredIVLength().getByteLength();
            case ISAAC:
            case RC4:
            default:
                return 0;
        }
    }

    /**
     * Does this keySpec optionally support AEAD?
     *
     * @return true/false
     */
    public boolean supportsAEAD() {
        return theStreamKeyType == GordianStreamKeyType.CHACHA20
                && theSubKeyType != GordianChaCha20Key.STD;
    }

    /**
     * Is this keySpec an AEAD keySpec?
     *
     * @return true/false
     */
    public boolean isAEAD() {
        switch (theStreamKeyType) {
            case ASCON:
            case ELEPHANT:
            case ISAP:
            case PHOTONBEETLE:
            case ROMULUS:
            case SPARKLE:
            case XOODYAK:
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean equals(final Object pThat) {
        /* Handle the trivial cases */
        if (this == pThat) {
            return true;
        }
        if (pThat == null) {
            return false;
        }

        /* Check subFields */
        return pThat instanceof GordianStreamKeySpec myThat
                && theStreamKeyType == myThat.getStreamKeyType()
                && theKeyLength == myThat.getKeyLength()
                && theSubKeyType == myThat.getSubKeyType();
    }

    @Override
    public int hashCode() {
        return Objects.hash(theStreamKeyType, theKeyLength, theSubKeyType);
    }

    /**
     * Default subKeyType.
     *
     * @param pKeyType the keyType
     * @return the default
     */
    private static GordianStreamSubKeyType defaultSubKeyType(final GordianStreamKeyType pKeyType) {
        /* Switch on keyType */
        switch (pKeyType) {
            case SALSA20:
                return GordianSalsa20Key.STD;
            case CHACHA20:
                return GordianChaCha20Key.STD;
            case VMPC:
                return GordianVMPCKey.STD;
            case SKEINXOF:
                return GordianSkeinXofKey.STATE1024;
            case BLAKE2XOF:
                return GordianBlakeXofKey.BLAKE2XB;
            case ELEPHANT:
                return GordianElephantKey.ELEPHANT160;
            case ISAP:
                return GordianISAPKey.ISAPA128;
            case ROMULUS:
                return GordianRomulusKey.ROMULUS_M;
            case SPARKLE:
                return GordianSparkleKey.SPARKLE128_128;
            default:
                return null;
        }
    }

    /**
     * SubKeyType.
     */
    public interface GordianStreamSubKeyType {
    }

    /**
     * VMPC Key styles.
     */
    public enum GordianVMPCKey
            implements GordianStreamSubKeyType {
        /**
         * VMPC.
         */
        STD,

        /**
         * VMPC-KSA.
         */
        KSA;
    }

    /**
     * Salsa20 Key styles.
     */
    public enum GordianSalsa20Key
            implements GordianStreamSubKeyType {
        /**
         * Salsa20.
         */
        STD,

        /**
         * XSalsa20.
         */
        XSALSA;

        /**
         * Obtain required ivLength.
         *
         * @return the ivLength
         */
        GordianLength requiredIVLength() {
            switch (this) {
                case STD:
                    return GordianLength.LEN_64;
                case XSALSA:
                default:
                    return GordianLength.LEN_192;
            }
        }
    }

    /**
     * ChaCha20 Key styles.
     */
    public enum GordianChaCha20Key
            implements GordianStreamSubKeyType {
        /**
         * ChaCha20.
         */
        STD,

        /**
         * ChaCha7539.
         */
        ISO7539,

        /**
         * XChaCha20.
         */
        XCHACHA;

        /**
         * Obtain required ivLength.
         *
         * @return the ivLength
         */
        GordianLength requiredIVLength() {
            switch (this) {
                case STD:
                    return GordianLength.LEN_64;
                case ISO7539:
                    return GordianLength.LEN_96;
                case XCHACHA:
                default:
                    return GordianLength.LEN_192;
            }
        }
    }

    /**
     * SkeinXof Key styles.
     */
    public enum GordianSkeinXofKey
            implements GordianStreamSubKeyType {
        /**
         * 256State.
         */
        STATE256(GordianLength.LEN_256),

        /**
         * 512State.
         */
        STATE512(GordianLength.LEN_512),

        /**
         * 1024State.
         */
        STATE1024(GordianLength.LEN_1024);

        /**
         * The length.
         */
        private final GordianLength theLength;

        /**
         * Constructor.
         *
         * @param pLength the stateLength
         */
        GordianSkeinXofKey(final GordianLength pLength) {
            theLength = pLength;
        }

        /**
         * Obtain length.
         *
         * @return the length.
         */
        public GordianLength getLength() {
            return theLength;
        }

        @Override
        public String toString() {
            return theLength.toString();
        }

        /**
         * Obtain subKeyType for stateLength.
         *
         * @param pLength the length
         * @return the subKeyType
         */
        public static GordianSkeinXofKey getKeyTypeForLength(final GordianLength pLength) {
            for (GordianSkeinXofKey myType : values()) {
                if (pLength == myType.theLength) {
                    return myType;
                }
            }
            throw new IllegalArgumentException("Unsupported state length");
        }
    }

    /**
     * BlakeXof Key styles.
     */
    public enum GordianBlakeXofKey
            implements GordianStreamSubKeyType {
        /**
         * Blake2S.
         */
        BLAKE2XS,

        /**
         * Blake2B.
         */
        BLAKE2XB;

        @Override
        public String toString() {
            return this == BLAKE2XB ? "Blake2Xb" : "Blake2Xs";
        }

        /**
         * Obtain required ivLength.
         *
         * @return the ivLength
         */
        GordianLength requiredIVLength() {
            switch (this) {
                case BLAKE2XS:
                    return GordianLength.LEN_64;
                case BLAKE2XB:
                default:
                    return GordianLength.LEN_128;
            }
        }
    }

    /**
     * Elephant Key styles.
     */
    public enum GordianElephantKey
            implements GordianStreamSubKeyType {
        /**
         * Elephant160.
         */
        ELEPHANT160,

        /**
         * Elephant176.
         */
        ELEPHANT176,

        /**
         * Elephant200.
         */
        ELEPHANT200;

        @Override
        public String toString() {
            final String myBase = GordianStreamKeyType.ELEPHANT.toString();
            switch (this) {
                case ELEPHANT160:
                    return myBase + "160";
                case ELEPHANT176:
                    return myBase + "176";
                case ELEPHANT200:
                default:
                    return myBase + "200";
            }
        }

        /**
         * Obtain the elephant parameters.
         *
         * @return the parameters
         */
        public ElephantParameters getParameters() {
            switch (this) {
                case ELEPHANT160:
                    return ElephantParameters.elephant160;
                case ELEPHANT176:
                    return ElephantParameters.elephant176;
                case ELEPHANT200:
                default:
                    return ElephantParameters.elephant200;
            }
        }
    }

    /**
     * ISAP Key styles.
     */
    public enum GordianISAPKey
            implements GordianStreamSubKeyType {
        /**
         * ISAPA128.
         */
        ISAPA128,

        /**
         * ISAPA128A.
         */
        ISAPA128A,

        /**
         * ISAPK128.
         */
        ISAPK128,

        /**
         * ISAPK128A.
         */
        ISAPK128A;

        @Override
        public String toString() {
            final String myBase = GordianStreamKeyType.ISAP.toString();
            switch (this) {
                case ISAPA128:
                    return myBase + "A128";
                case ISAPA128A:
                    return myBase + "A128A";
                case ISAPK128:
                    return myBase + "K128";
                case ISAPK128A:
                default:
                    return myBase + "K128A";
            }
        }

        /**
         * Obtain the ISAP type.
         *
         * @return the type
         */
        public IsapType getType() {
            switch (this) {
                case ISAPA128:
                    return IsapType.ISAP_A_128;
                case ISAPA128A:
                    return IsapType.ISAP_A_128A;
                case ISAPK128:
                    return IsapType.ISAP_K_128;
                case ISAPK128A:
                default:
                    return IsapType.ISAP_K_128A;
            }
        }
    }

    /**
     * Romulus Key styles.
     */
    public enum GordianRomulusKey
            implements GordianStreamSubKeyType {
        /**
         * Romulus-M.
         */
        ROMULUS_M,

        /**
         * Romulus-N.
         */
        ROMULUS_N,

        /**
         * Romulus-T.
         */
        ROMULUS_T;

        @Override
        public String toString() {
            final String myBase = GordianStreamKeyType.ROMULUS.toString();
            switch (this) {
                case ROMULUS_M:
                    return myBase + "-M";
                case ROMULUS_N:
                    return myBase + "-N";
                case ROMULUS_T:
                default:
                    return myBase + "-T";
            }
        }

        /**
         * Obtain the RomulusParameters.
         *
         * @return the parameters
         */
        public RomulusParameters getParameters() {
            switch (this) {
                case ROMULUS_M:
                    return RomulusParameters.RomulusM;
                case ROMULUS_N:
                    return RomulusParameters.RomulusN;
                case ROMULUS_T:
                default:
                    return RomulusParameters.RomulusT;
            }
        }
    }

    /**
     * Sparkle Key styles.
     */
    public enum GordianSparkleKey
            implements GordianStreamSubKeyType {
        /**
         * Sparkle128_128.
         */
        SPARKLE128_128,

        /**
         * Sparkle256_128.
         */
        SPARKLE256_128,

        /**
         * Sparkle192_192.
         */
        SPARKLE192_192,

        /**
         * Sparkle256_256.
         */
        SPARKLE256_256;

        @Override
        public String toString() {
            final String myBase = GordianStreamKeyType.SPARKLE.toString();
            switch (this) {
                case SPARKLE128_128:
                    return myBase + "128_128";
                case SPARKLE256_128:
                    return myBase + "256_128";
                case SPARKLE192_192:
                    return myBase + "192_192";
                case SPARKLE256_256:
                default:
                    return myBase + "256_256";
            }
        }

        /**
         * Obtain required keyLength.
         *
         * @return the keyLength
         */
        GordianLength requiredKeyLength() {
            switch (this) {
                case SPARKLE128_128:
                case SPARKLE256_128:
                    return GordianLength.LEN_128;
                case SPARKLE192_192:
                    return GordianLength.LEN_192;
                case SPARKLE256_256:
                default:
                    return GordianLength.LEN_256;
            }
        }

        /**
         * Obtain required ivLength.
         *
         * @return the ivLength
         */
        GordianLength requiredIVLength() {
            switch (this) {
                case SPARKLE128_128:
                    return GordianLength.LEN_128;
                case SPARKLE192_192:
                    return GordianLength.LEN_192;
                case SPARKLE256_128:
                case SPARKLE256_256:
                default:
                    return GordianLength.LEN_256;
            }
        }

        /**
         * Obtain the Sparkle parameters.
         *
         * @return the parameters
         */
        public SparkleParameters getParameters() {
            switch (this) {
                case SPARKLE128_128:
                    return SparkleParameters.SCHWAEMM128_128;
                case SPARKLE256_128:
                    return SparkleParameters.SCHWAEMM256_128;
                case SPARKLE192_192:
                    return SparkleParameters.SCHWAEMM192_192;
                case SPARKLE256_256:
                default:
                    return SparkleParameters.SCHWAEMM256_256;
            }
        }
    }
}