package org.bouncycastle.crypto.engines;

import org.bouncycastle.util.Pack;

/**
 * Implementation of Daniel J. Bernstein's XSalsa20 stream cipher - Salsa20 with an extended nonce.
 * <p>
 * XSalsa20 requires a 256 bit key, and a 192 bit nonce.
 */
public class XSalsa20Engine extends Salsa20Engine
{
    public String getAlgorithmName()
    {
        return "XSalsa20";
    }

    protected int getNonceSize()
    {
        return 24;
    }

    /**
     * XSalsa20 key generation: process 256 bit input key and 128 bits of the input nonce
     * using a core Salsa20 function without input addition to produce 256 bit working key
     * and use that with the remaining 64 bits of nonce to initialize a standard Salsa20 engine state.
     */
    protected void setKey(byte[] keyBytes, byte[] ivBytes)
    {
        if (keyBytes == null)
        {
            throw new IllegalArgumentException(getAlgorithmName() + " doesn't support re-init with null key");
        }

        if (keyBytes.length != 32)
        {
            throw new IllegalArgumentException(getAlgorithmName() + " requires a 256 bit key");
        }

        // Set key for HSalsa20
        super.setKey(keyBytes, ivBytes);

        // Pack next 64 bits of IV into engine state instead of counter
        Pack.littleEndianToInt(ivBytes, 8, engineState, 8, 2);

        // Process engine state to generate Salsa20 key
        int[] hsalsa20Out = new int[engineState.length];
        salsaCore(20, engineState, hsalsa20Out);

        // Set new key, removing addition in last round of salsaCore
        engineState[1] = hsalsa20Out[0] - engineState[0];
        engineState[2] = hsalsa20Out[5] - engineState[5];
        engineState[3] = hsalsa20Out[10] - engineState[10];
        engineState[4] = hsalsa20Out[15] - engineState[15];

        engineState[11] = hsalsa20Out[6] - engineState[6];
        engineState[12] = hsalsa20Out[7] - engineState[7];
        engineState[13] = hsalsa20Out[8] - engineState[8];
        engineState[14] = hsalsa20Out[9] - engineState[9];

        // Last 64 bits of input IV
        Pack.littleEndianToInt(ivBytes, 16, engineState, 6, 2);
    }
}
