/*
 * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.crypto;

import sun.security.jca.GetInstance;

import java.security.*;
import java.security.InvalidAlgorithmParameterException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * This class provides the functionality of a Key Encapsulation Mechanism (KEM).
 * A KEM can be used to secure symmetric keys using asymmetric or public key
 * cryptography between two parties. The sender calls the encapsulate method
 * to generate a secret key and a key encapsulation message, and the receiver
 * calls the decapsulate method to recover the same secret key from
 * the key encapsulation message.
 * <p>
 * The {@code getInstance} method creates a new {@code KEM} object that
 * implements the specified algorithm.
 * <p>
 * A {@code KEM} object is immutable. It is safe to call multiple
 * {@code newEncapsulator} and {@code newDecapsulator} methods on the
 * same {@code KEM} object at the same time.
 * <p>
 * If a provider is not specified in the {@code getInstance} method when
 * instantiating a {@code KEM} object, the {@code newEncapsulator} and
 * {@code newDecapsulator} methods may return encapsulators or decapsulators
 * from different providers. The provider selected is based on the parameters
 * passed to the {@code newEncapsulator} or {@code newDecapsulator} methods:
 * the private or public key and the optional {@code AlgorithmParameterSpec}.
 * The {@link Encapsulator#providerName} and {@link Decapsulator#providerName}
 * methods return the name of the selected provider.
 * <p>
 * {@code Encapsulator} and {@code Decapsulator} objects are also immutable.
 * It is safe to invoke multiple {@code encapsulate} and {@code decapsulate}
 * methods on the same {@code Encapsulator} or {@code Decapsulator} object
 * at the same time. Each invocation of {@code encapsulate} will generate a
 * new shared secret and key encapsulation message.
 * <p>
 *
 * Example operation using a fictitious {@code KEM} algorithm {@code ABC}:
 * {@snippet lang = java:
 *     // Receiver side
 *     KeyPairGenerator g = KeyPairGenerator.getInstance("ABC");
 *     KeyPair kp = g.generateKeyPair();
 *     publishKey(kp.getPublic());
 *
 *     // Sender side
 *     KEM senderKEM = KEM.getInstance("ABC");
 *     PublicKey receiverPublicKey = retrieveKey();
 *     ABCKEMParameterSpec senderSpec = new ABCKEMParameterSpec(args);
 *     KEM.Encapsulator e = senderKEM.newEncapsulator(
 *             receiverPublicKey, senderSpec, null);
 *     KEM.Encapsulated enc = e.encapsulate();
 *     SecretKey senderSecret = enc.key();
 *
 *     sendBytes(enc.encapsulation());
 *     sendBytes(enc.params());
 *
 *     // Receiver side
 *     byte[] ciphertext = receiveBytes();
 *     byte[] params = receiveBytes();
 *
 *     KEM receiverKEM = KEM.getInstance("ABC");
 *     AlgorithmParameters algParams =
 *             AlgorithmParameters.getInstance("ABC");
 *     algParams.init(params);
 *     ABCKEMParameterSpec receiverSpec =
 *             algParams.getParameterSpec(ABCKEMParameterSpec.class);
 *     KEM.Decapsulator d =
 *             receiverKEM.newDecapsulator(kp.getPrivate(), receiverSpec);
 *     SecretKey receiverSecret = d.decapsulate(ciphertext);
 *
 *     // senderSecret and receiverSecret should now be equal.
 * }
 *
 * @since 21
 */
public final class KEM {

    /**
     * This class specifies the return value of the encapsulate method of
     * a Key Encapsulation Mechanism (KEM), which includes the shared secret
     * (as a {@code SecretKey}), the key encapsulation message,
     * and optional parameters.
     * <p>
     * Note: the key encapsulation message can be also referred to as ciphertext.
     *
     * @see #newEncapsulator(PublicKey, AlgorithmParameterSpec, SecureRandom)
     * @see Encapsulator#encapsulate(int, int, String)
     *
     * @since 21
     */
    public static final class Encapsulated {
        private final SecretKey key;
        private final byte[] encapsulation;
        private final byte[] params;

        /**
         * Constructs an {@code Encapsulated} object.
         *
         * @param key the shared secret as a key, must not be {@code null}.
         * @param encapsulation the key encapsulation message, must not
         *          be {@code null}. The contents of the array are copied
         *          to protect against subsequent modification.
         * @param params optional parameters, can be {@code null}.
         *          The contents of the array are copied to protect
         *          against subsequent modification.
         * @throws NullPointerException if {@code key} or {@code encapsulation}
         *          is {@code null}
         */
        public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params) {
            Objects.requireNonNull(key);
            Objects.requireNonNull(encapsulation);
            this.key = key;
            this.encapsulation = encapsulation.clone();
            this.params = params == null ? null : params.clone();
        }

        /**
         * Returns the {@code SecretKey}.
         *
         * @return the secret key
         */
        public SecretKey key() {
            return key;
        }

        /**
         * Returns the key encapsulation message.
         *
         * @return the key encapsulation message. A new copy of the byte array
         *      is returned.
         */
        public byte[] encapsulation() {
            return encapsulation.clone();
        }

        /**
         * Returns the optional parameters in a byte array.
         *
         * @return the optional parameters in a byte array or {@code null}
         *      if not specified. A new copy of the byte array is returned.
         */
        public byte[] params() {
            return params == null ? null : params.clone();
        }
    }

    /**
     * An encapsulator, generated by {@link #newEncapsulator} on the KEM
     * sender side.
     * <p>
     * This class represents the key encapsulation function of a KEM.
     * Each invocation of the {@code encapsulate} method generates a
     * new secret key and key encapsulation message that is returned
     * in an {@link Encapsulated} object.
     *
     * @since 21
     */
    public static final class Encapsulator {

        private final KEMSpi.EncapsulatorSpi e;
        private final Provider p;

        private Encapsulator(KEMSpi.EncapsulatorSpi e, Provider p) {
            assert e != null;
            assert p != null;
            this.e = e;
            this.p = p;
        }

        /**
         * Returns the name of the provider.
         *
         * @return the name of the provider
         */
        public String providerName() {
            return p.getName();
        }

        /**
         * The key encapsulation function.
         * <p>
         * This method is equivalent to
         * {@code encapsulate(0, secretSize(), "Generic")}. This combination
         * of arguments must be supported by every implementation.
         * <p>
         * The generated secret key is usually passed to a key derivation
         * function (KDF) as the input keying material.
         *
         * @return a {@link Encapsulated} object containing the shared
         *          secret, key encapsulation message, and optional parameters.
         *          The shared secret is a {@code SecretKey} containing all of
         *          the bytes of the secret, and an algorithm name of "Generic".
         */
        public Encapsulated encapsulate() {
            return encapsulate(0, secretSize(), "Generic");
        }

        /**
         * The key encapsulation function.
         * <p>
         * Each invocation of this method generates a new secret key and key
         * encapsulation message that is returned in an {@link Encapsulated} object.
         * <p>
         * An implementation may choose to not support arbitrary combinations
         * of {@code from}, {@code to}, and {@code algorithm}.
         *
         * @param from the initial index of the shared secret byte array
         *          to be returned, inclusive
         * @param to the final index of the shared secret byte array
         *          to be returned, exclusive
         * @param algorithm the algorithm name for the secret key that is returned.
         *          See the SecretKey Algorithms section in the
         *          <a href="{@docRoot}/../specs/security/standard-names.html#secretkey-algorithms">
         *          Java Security Standard Algorithm Names Specification</a>
         *          for information about standard secret key algorithm names.
         *          Specify "Generic" if the output will be used as the input keying
         *          material of a key derivation function (KDF).
         * @return a {@link Encapsulated} object containing a portion of
         *          the shared secret, key encapsulation message, and optional
         *          parameters. The portion of the shared secret is a
         *          {@code SecretKey} containing the bytes of the secret
         *          ranging from {@code from} to {@code to}, exclusive,
         *          and an algorithm name as specified. For example,
         *          {@code encapsulate(0, 16, "AES")} uses the first 16 bytes
         *          of the shared secret as a 128-bit AES key.
         * @throws IndexOutOfBoundsException if {@code from < 0},
         *     {@code from > to}, or {@code to > secretSize()}
         * @throws NullPointerException if {@code algorithm} is {@code null}
         * @throws UnsupportedOperationException if the combination of
         *          {@code from}, {@code to}, and {@code algorithm}
         *          is not supported by the encapsulator
         * @spec security/standard-names.html Java Security Standard Algorithm Names
         */
        public Encapsulated encapsulate(int from, int to, String algorithm) {
            return e.engineEncapsulate(from, to, algorithm);
        }

        /**
         * Returns the size of the shared secret.
         * <p>
         * This method can be called to find out the length of the shared secret
         * before {@code encapsulate} is called or if the obtained
         * {@code SecretKey} is not extractable.
         *
         * @return the size of the shared secret
         */
        public int secretSize() {
            int result = e.engineSecretSize();
            assert result >= 0 && result != Integer.MAX_VALUE
                    : "invalid engineSecretSize result";
            return result;
        }

        /**
         * Returns the size of the key encapsulation message.
         * <p>
         * This method can be called to find out the length of the encapsulation
         * message before {@code encapsulate} is called.
         *
         * @return the size of the key encapsulation message
         */
        public int encapsulationSize() {
            int result = e.engineEncapsulationSize();
            assert result >= 0 && result != Integer.MAX_VALUE
                    : "invalid engineEncapsulationSize result";
            return result;
        }
    }

    /**
     * A decapsulator, generated by {@link #newDecapsulator} on the KEM
     * receiver side.
     * <p>
     * This class represents the key decapsulation function of a KEM.
     * An invocation of the {@code decapsulate} method recovers the
     * secret key from the key encapsulation message.
     *
     * @since 21
     */
    public static final class Decapsulator {
        private final KEMSpi.DecapsulatorSpi d;
        private final Provider p;

        private Decapsulator(KEMSpi.DecapsulatorSpi d, Provider p) {
            assert d != null;
            assert p != null;
            this.d = d;
            this.p = p;
        }

        /**
         * Returns the name of the provider.
         *
         * @return the name of the provider
         */
        public String providerName() {
            return p.getName();
        }

        /**
         * The key decapsulation function.
         * <p>
         * This method is equivalent to
         * {@code decapsulate(encapsulation, 0, secretSize(), "Generic")}. This
         * combination of arguments must be supported by every implementation.
         * <p>
         * The generated secret key is usually passed to a key derivation
         * function (KDF) as the input keying material.
         *
         * @param encapsulation the key encapsulation message from the sender.
         *          The size must be equal to the value returned by
         *          {@link #encapsulationSize()}, or a {@code DecapsulateException}
         *          will be thrown.
         * @return the shared secret as a {@code SecretKey} with
         *          an algorithm name of "Generic"
         * @throws DecapsulateException if an error occurs during the
         *          decapsulation process
         * @throws NullPointerException if {@code encapsulation} is {@code null}
         */
        public SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException {
            return decapsulate(encapsulation, 0, secretSize(), "Generic");
        }

        /**
         * The key decapsulation function.
         * <p>
         * An invocation of this method recovers the secret key from the key
         * encapsulation message.
         * <p>
         * An implementation may choose to not support arbitrary combinations
         * of {@code from}, {@code to}, and {@code algorithm}.
         *
         * @param encapsulation the key encapsulation message from the sender.
         *          The size must be equal to the value returned by
         *          {@link #encapsulationSize()}, or a {@code DecapsulateException}
         *          will be thrown.
         * @param from the initial index of the shared secret byte array
         *          to be returned, inclusive
         * @param to the final index of the shared secret byte array
         *          to be returned, exclusive
         * @param algorithm the algorithm name for the secret key that is returned.
         *          See the SecretKey Algorithms section in the
         *          <a href="{@docRoot}/../specs/security/standard-names.html#secretkey-algorithms">
         *          Java Security Standard Algorithm Names Specification</a>
         *          for information about standard secret key algorithm names.
         *          Specify "Generic" if the output will be used as the input keying
         *          material of a key derivation function (KDF).
         * @return a portion of the shared secret as a {@code SecretKey}
         *          containing the bytes of the secret ranging from {@code from}
         *          to {@code to}, exclusive, and an algorithm name as specified.
         *          For example, {@code decapsulate(encapsulation, secretSize()
         *          - 16, secretSize(), "AES")} uses the last 16 bytes
         *          of the shared secret as a 128-bit AES key.
         * @throws DecapsulateException if an error occurs during the
         *          decapsulation process
         * @throws IndexOutOfBoundsException if {@code from < 0},
         *          {@code from > to}, or {@code to > secretSize()}
         * @throws NullPointerException if {@code encapsulation} or
         *          {@code algorithm} is {@code null}
         * @throws UnsupportedOperationException if the combination of
         *          {@code from}, {@code to}, and {@code algorithm}
         *          is not supported by the decapsulator
         * @spec security/standard-names.html Java Security Standard Algorithm Names
         */
        public SecretKey decapsulate(byte[] encapsulation,
                int from, int to, String algorithm)
                throws DecapsulateException {
            return d.engineDecapsulate(
                    encapsulation,
                    from, to,
                    algorithm);
        }

        /**
         * Returns the size of the shared secret.
         * <p>
         * This method can be called to find out the length of the shared secret
         * before {@code decapsulate} is called or if the obtained
         * {@code SecretKey} is not extractable.
         *
         * @return the size of the shared secret
         */
        public int secretSize() {
            int result = d.engineSecretSize();
            assert result >= 0 && result != Integer.MAX_VALUE
                    : "invalid engineSecretSize result";
            return result;
        }

        /**
         * Returns the size of the key encapsulation message.
         * <p>
         * This method can be used to extract the encapsulation message
         * from a longer byte array if no length information is provided
         * by a higher level protocol.
         *
         * @return the size of the key encapsulation message
         */
        public int encapsulationSize() {
            int result = d.engineEncapsulationSize();
            assert result >= 0 && result != Integer.MAX_VALUE
                    : "invalid engineEncapsulationSize result";
            return result;
        }
    }

    private static final class DelayedKEM {

        private final Provider.Service[] list; // non empty array

        private DelayedKEM(Provider.Service[] list) {
            this.list = list;
        }

        private Encapsulator newEncapsulator(PublicKey publicKey,
                AlgorithmParameterSpec spec, SecureRandom secureRandom)
                throws InvalidAlgorithmParameterException, InvalidKeyException {
            if (publicKey == null) {
                throw new InvalidKeyException("input key is null");
            }
            RuntimeException re = null;
            InvalidAlgorithmParameterException iape = null;
            InvalidKeyException ike = null;
            NoSuchAlgorithmException nsae = null;
            for (Provider.Service service : list) {
                if (!service.supportsParameter(publicKey)) {
                    continue;
                }
                try {
                    KEMSpi spi = (KEMSpi) service.newInstance(null);
                    return new Encapsulator(
                            spi.engineNewEncapsulator(publicKey, spec, secureRandom),
                            service.getProvider());
                } catch (NoSuchAlgorithmException e) {
                    nsae = merge(nsae, e);
                } catch (InvalidAlgorithmParameterException e) {
                    iape = merge(iape, e);
                } catch (InvalidKeyException e) {
                    ike = merge(ike, e);
                } catch (RuntimeException e) {
                    re = merge(re, e);
                }
            }
            if (iape != null) throw iape;
            if (ike != null) throw ike;
            if (nsae != null) {
                throw new InvalidKeyException("No installed provider found", nsae);
            }
            throw new InvalidKeyException("No installed provider supports this key: "
                            + publicKey.getClass().getName(), re);
        }

        private static <T extends Exception> T merge(T e1, T e2) {
            if (e1 == null) {
                return e2;
            } else {
                e1.addSuppressed(e2);
                return e1;
            }
        }

        private Decapsulator newDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
                throws InvalidAlgorithmParameterException, InvalidKeyException {
            if (privateKey == null) {
                throw new InvalidKeyException("input key is null");
            }
            RuntimeException re = null;
            InvalidAlgorithmParameterException iape = null;
            InvalidKeyException ike = null;
            NoSuchAlgorithmException nsae = null;
            for (Provider.Service service : list) {
                if (!service.supportsParameter(privateKey)) {
                    continue;
                }
                try {
                    KEMSpi spi = (KEMSpi) service.newInstance(null);
                    return new Decapsulator(
                            spi.engineNewDecapsulator(privateKey, spec),
                            service.getProvider());
                } catch (NoSuchAlgorithmException e) {
                    nsae = merge(nsae, e);
                } catch (InvalidAlgorithmParameterException e) {
                    iape = merge(iape, e);
                } catch (InvalidKeyException e) {
                    ike = merge(ike, e);
                } catch (RuntimeException e) {
                    re = merge(re, e);
                }
            }
            if (iape != null) throw iape;
            if (ike != null) throw ike;
            if (nsae != null) {
                throw new InvalidKeyException("No installed provider found", nsae);
            }
            throw new InvalidKeyException("No installed provider supports this key: "
                    + privateKey.getClass().getName(), re);
        }
    }

    // If delayed provider selection is needed
    private final DelayedKEM delayed;

    // otherwise
    private final KEMSpi spi;
    private final Provider provider;

    private final String algorithm;

    private KEM(String algorithm, KEMSpi spi, Provider provider) {
        assert spi != null;
        assert provider != null;
        this.delayed = null;
        this.spi = spi;
        this.provider = provider;
        this.algorithm = algorithm;
    }

    private KEM(String algorithm, DelayedKEM delayed) {
        assert delayed != null;
        this.delayed = delayed;
        this.spi = null;
        this.provider = null;
        this.algorithm = algorithm;
    }

    /**
     * Returns a {@code KEM} object that implements the specified algorithm.
     *
     * @param algorithm the name of the KEM algorithm.
     *          See the {@code KEM} section in the <a href=
     *          "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
     *          Java Security Standard Algorithm Names Specification</a>
     *          for information about standard KEM algorithm names.
     * @spec security/standard-names.html Java Security Standard Algorithm Names
     * @return the new {@code KEM} object
     * @throws NoSuchAlgorithmException if no {@code Provider} supports a
     *         {@code KEM} implementation for the specified algorithm
     * @throws NullPointerException if {@code algorithm} is {@code null}
     */
    public static KEM getInstance(String algorithm)
            throws NoSuchAlgorithmException {
        Iterator<Provider.Service> t = GetInstance.getServices(
                "KEM",
                Objects.requireNonNull(algorithm, "null algorithm name"));
        List<Provider.Service> allowed = new ArrayList<>();
        while (t.hasNext()) {
            Provider.Service s = t.next();
            if (!JceSecurity.canUseProvider(s.getProvider())) {
                continue;
            }
            allowed.add(s);
        }
        if (allowed.isEmpty()) {
            throw new NoSuchAlgorithmException
                    (algorithm + " KEM not available");
        }

        return new KEM(algorithm, new DelayedKEM(allowed.toArray(new Provider.Service[0])));
    }

    /**
     * Returns a {@code KEM} object that implements the specified algorithm
     * from the specified security provider.
     *
     * @param algorithm the name of the KEM algorithm.
     *          See the {@code KEM} section in the <a href=
     *          "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
     *          Java Security Standard Algorithm Names Specification</a>
     *          for information about standard KEM algorithm names.
     * @param provider the provider. If {@code null}, this method is equivalent
     *                 to {@link #getInstance(String)}.
     * @spec security/standard-names.html Java Security Standard Algorithm Names
     * @return the new {@code KEM} object
     * @throws NoSuchAlgorithmException if a {@code provider} is specified and
     *          it does not support the specified KEM algorithm,
     *          or if {@code provider} is {@code null} and there is no provider
     *          that supports a KEM implementation of the specified algorithm
     * @throws NullPointerException if {@code algorithm} is {@code null}
     */
    public static KEM getInstance(String algorithm, Provider provider)
            throws NoSuchAlgorithmException {
        if (provider == null) {
            return getInstance(algorithm);
        }
        GetInstance.Instance instance = JceSecurity.getInstance(
                "KEM",
                KEMSpi.class,
                Objects.requireNonNull(algorithm, "null algorithm name"),
                provider);
        return new KEM(algorithm, (KEMSpi) instance.impl, instance.provider);
    }

    /**
     * Returns a {@code KEM} object that implements the specified algorithm
     * from the specified security provider.
     *
     * @param algorithm the name of the KEM algorithm.
     *          See the {@code KEM} section in the <a href=
     *          "{@docRoot}/../specs/security/standard-names.html#kem-algorithms">
     *          Java Security Standard Algorithm Names Specification</a>
     *          for information about standard KEM algorithm names.
     * @param provider the provider. If {@code null}, this method is equivalent
     *                 to {@link #getInstance(String)}.
     * @spec security/standard-names.html Java Security Standard Algorithm Names
     * @return the new {@code KEM} object
     * @throws NoSuchAlgorithmException if a {@code provider} is specified and
     *          it does not support the specified KEM algorithm,
     *          or if {@code provider} is {@code null} and there is no provider
     *          that supports a KEM implementation of the specified algorithm
     * @throws NoSuchProviderException if the specified provider is not
     *         registered in the security provider list
     * @throws NullPointerException if {@code algorithm} is {@code null}
     */
    public static KEM getInstance(String algorithm, String provider)
            throws NoSuchAlgorithmException, NoSuchProviderException {
        if (provider == null) {
            return getInstance(algorithm);
        }
        GetInstance.Instance instance = JceSecurity.getInstance(
                "KEM",
                KEMSpi.class,
                Objects.requireNonNull(algorithm, "null algorithm name"),
                provider);
        return new KEM(algorithm, (KEMSpi) instance.impl, instance.provider);
    }

    /**
     * Creates a KEM encapsulator on the KEM sender side.
     * <p>
     * This method is equivalent to {@code newEncapsulator(publicKey, null, null)}.
     *
     * @param publicKey the receiver's public key, must not be {@code null}
     * @return the encapsulator for this key
     * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
     * @throws UnsupportedOperationException if this method is not supported
     *          because an {@code AlgorithmParameterSpec} must be provided
     */
    public Encapsulator newEncapsulator(PublicKey publicKey)
            throws InvalidKeyException {
        try {
            return newEncapsulator(publicKey, null, null);
        } catch (InvalidAlgorithmParameterException e) {
            throw new UnsupportedOperationException(
                    "AlgorithmParameterSpec must be provided", e);
        }
    }

    /**
     * Creates a KEM encapsulator on the KEM sender side.
     * <p>
     * This method is equivalent to {@code newEncapsulator(publicKey, null, secureRandom)}.
     *
     * @param publicKey the receiver's public key, must not be {@code null}
     * @param secureRandom the source of randomness for encapsulation.
     *                     If {@code} null, a default one from the
     *                     implementation will be used.
     * @return the encapsulator for this key
     * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
     * @throws UnsupportedOperationException if this method is not supported
     *          because an {@code AlgorithmParameterSpec} must be provided
     */
    public Encapsulator newEncapsulator(PublicKey publicKey, SecureRandom secureRandom)
            throws InvalidKeyException {
        try {
            return newEncapsulator(publicKey, null, secureRandom);
        } catch (InvalidAlgorithmParameterException e) {
            throw new UnsupportedOperationException(
                    "AlgorithmParameterSpec must be provided", e);
        }
    }

    /**
     * Creates a KEM encapsulator on the KEM sender side.
     * <p>
     * An algorithm can define an {@code AlgorithmParameterSpec} child class to
     * provide extra information in this method. This is especially useful if
     * the same key can be used to derive shared secrets in different ways.
     * If any extra information inside this object needs to be transmitted along
     * with the key encapsulation message so that the receiver is able to create
     * a matching decapsulator, it will be included as a byte array returned by the
     * {@link Encapsulated#params()} method within the encapsulation output.
     * In this case, the security provider should provide an
     * {@code AlgorithmParameters} implementation using the same algorithm name
     * as the KEM. The receiver can initiate such an {@code AlgorithmParameters}
     * instance with the {@code params} byte array received and recover
     * an {@code AlgorithmParameterSpec} object to be used in its
     * {@link #newDecapsulator(PrivateKey, AlgorithmParameterSpec)} call.
     *
     * @param publicKey the receiver's public key, must not be {@code null}
     * @param spec the optional parameter, can be {@code null}
     * @param secureRandom the source of randomness for encapsulation.
     *                     If {@code} null, a default one from the
     *                     implementation will be used.
     * @return the encapsulator for this key
     * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
     *          or one is required but {@code spec} is {@code null}
     * @throws InvalidKeyException if {@code publicKey} is {@code null} or invalid
     */
    public Encapsulator newEncapsulator(PublicKey publicKey,
            AlgorithmParameterSpec spec, SecureRandom secureRandom)
            throws InvalidAlgorithmParameterException, InvalidKeyException {
        return delayed != null
                ? delayed.newEncapsulator(publicKey, spec, secureRandom)
                : new Encapsulator(spi.engineNewEncapsulator(publicKey, spec, secureRandom), provider);
    }

    /**
     * Creates a KEM decapsulator on the KEM receiver side.
     * <p>
     * This method is equivalent to {@code newDecapsulator(privateKey, null)}.
     *
     * @param privateKey the receiver's private key, must not be {@code null}
     * @return the decapsulator for this key
     * @throws InvalidKeyException if {@code privateKey} is {@code null} or invalid
     * @throws UnsupportedOperationException if this method is not supported
     *          because an {@code AlgorithmParameterSpec} must be provided
     */
    public Decapsulator newDecapsulator(PrivateKey privateKey)
            throws InvalidKeyException {
        try {
            return newDecapsulator(privateKey, null);
        } catch (InvalidAlgorithmParameterException e) {
            throw new UnsupportedOperationException(e);
        }
    }

    /**
     * Creates a KEM decapsulator on the KEM receiver side.
     *
     * @param privateKey the receiver's private key, must not be {@code null}
     * @param spec the parameter, can be {@code null}
     * @return the decapsulator for this key
     * @throws InvalidAlgorithmParameterException if {@code spec} is invalid
     *          or one is required but {@code spec} is {@code null}
     * @throws InvalidKeyException if {@code privateKey} is {@code null} or invalid
     */
    public Decapsulator newDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
            throws InvalidAlgorithmParameterException, InvalidKeyException {
        return delayed != null
                ? delayed.newDecapsulator(privateKey, spec)
                : new Decapsulator(spi.engineNewDecapsulator(privateKey, spec), provider);
    }

    /**
     * Returns the name of the algorithm for this {@code KEM} object.
     *
     * @return the name of the algorithm for this {@code KEM} object.
     */
    public String getAlgorithm() {
        return this.algorithm;
    }
}
