/**

* EdDSA-Java by str4d
*
* To the extent possible under law, the person who associated CC0 with
* EdDSA-Java has waived all copyright and related or neighboring rights
* to EdDSA-Java.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
*
*/

package net.i2p.crypto.eddsa;

import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays;

import net.i2p.crypto.eddsa.math.Curve; import net.i2p.crypto.eddsa.math.GroupElement; import net.i2p.crypto.eddsa.math.ScalarOps; import sun.security.x509.X509Key;

/**

* Signing and verification for EdDSA.
*<p>
* The EdDSA sign and verify algorithms do not interact well with
* the Java Signature API, as one or more update() methods must be
* called before sign() or verify(). Using the standard API,
* this implementation must copy and buffer all data passed in
* via update().
*</p><p>
* This implementation offers two ways to avoid this copying,
* but only if all data to be signed or verified is available
* in a single byte array.
*</p><p>
*Option 1:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call setParameter(ONE_SHOT_MODE)
*</li><li>Call update(byte[]) or update(byte[], int, int) exactly once
*</li><li>Call sign() or verify() as usual.
*</li><li>If doing additional one-shot signs or verifies with this object, you must
*         call setParameter(ONE_SHOT_MODE) each time
*</li></ol>
*
*<p>
*Option 2:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call one of the signOneShot() or verifyOneShot() methods.
*</li><li>If doing additional one-shot signs or verifies with this object,
*         just call signOneShot() or verifyOneShot() again.
*</li></ol>
*
* @author str4d
*
*/

public final class EdDSAEngine extends Signature {

public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA";

private MessageDigest digest;
private ByteArrayOutputStream baos;
private EdDSAKey key;
private boolean oneShotMode;
private byte[] oneShotBytes;
private int oneShotOffset;
private int oneShotLength;

/**
 *  To efficiently sign or verify data in one shot, pass this to setParameters()
 *  after initSign() or initVerify() but BEFORE THE FIRST AND ONLY
 *  update(data) or update(data, off, len). The data reference will be saved
 *  and then used in sign() or verify() without copying the data.
 *  Violate these rules and you will get a SignatureException.
 */
public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec();

private static class OneShotSpec implements AlgorithmParameterSpec {}

/**
 * No specific EdDSA-internal hash requested, allows any EdDSA key.
 */
public EdDSAEngine() {
    super(SIGNATURE_ALGORITHM);
}

/**
 * Specific EdDSA-internal hash requested, only matching keys will be allowed.
 * @param digest the hash algorithm that keys must have to sign or verify.
 */
public EdDSAEngine(MessageDigest digest) {
    this();
    this.digest = digest;
}

private void reset() {
    if (digest != null)
        digest.reset();
    if (baos != null)
        baos.reset();
    oneShotMode = false;
    oneShotBytes = null;
}

@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
    reset();
    if (privateKey instanceof EdDSAPrivateKey) {
        EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
        key = privKey;

        if (digest == null) {
            // Instantiate the digest from the key parameters
            try {
                digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
            } catch (NoSuchAlgorithmException e) {
                throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
            }
        } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
            throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
        digestInitSign(privKey);
    } else {
        throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass());
    }
}

private void digestInitSign(EdDSAPrivateKey privKey) {
    // Preparing for hash
    // r = H(h_b,...,h_2b-1,M)
    int b = privKey.getParams().getCurve().getField().getb();
    digest.update(privKey.getH(), b/8, b/4 - b/8);
}

@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
    reset();
    if (publicKey instanceof EdDSAPublicKey) {
        key = (EdDSAPublicKey) publicKey;

        if (digest == null) {
            // Instantiate the digest from the key parameters
            try {
                digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
            } catch (NoSuchAlgorithmException e) {
                throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
            }
        } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
            throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
    } else if (publicKey instanceof X509Key) {
        // X509Certificate will sometimes contain an X509Key rather than the EdDSAPublicKey itself; the contained
        // key is valid but needs to be instanced as an EdDSAPublicKey before it can be used.
        EdDSAPublicKey parsedPublicKey;
        try {
            parsedPublicKey = new EdDSAPublicKey(new X509EncodedKeySpec(publicKey.getEncoded()));
        } catch (InvalidKeySpecException ex) {
            throw new InvalidKeyException("cannot handle X.509 EdDSA public key: " + publicKey.getAlgorithm());
        }
        engineInitVerify(parsedPublicKey);
    } else {
        throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
    }
}

/**
 * @throws SignatureException if in one-shot mode
 */
@Override
protected void engineUpdate(byte b) throws SignatureException {
    if (oneShotMode)
        throw new SignatureException("unsupported in one-shot mode");
    if (baos == null)
        baos = new ByteArrayOutputStream(256);
    baos.write(b);
}

/**
 * @throws SignatureException if one-shot rules are violated
 */
@Override
protected void engineUpdate(byte[] b, int off, int len)
        throws SignatureException {
    if (oneShotMode) {
        if (oneShotBytes != null)
            throw new SignatureException("update() already called");
        oneShotBytes = b;
        oneShotOffset = off;
        oneShotLength = len;
    } else {
        if (baos == null)
            baos = new ByteArrayOutputStream(256);
        baos.write(b, off, len);
    }
}

@Override
protected byte[] engineSign() throws SignatureException {
    try {
        return x_engineSign();
    } finally {
        reset();
        // must leave the object ready to sign again with
        // the same key, as required by the API
        EdDSAPrivateKey privKey = (EdDSAPrivateKey) key;
        digestInitSign(privKey);
    }
}

private byte[] x_engineSign() throws SignatureException {
    Curve curve = key.getParams().getCurve();
    ScalarOps sc = key.getParams().getScalarOps();
    byte[] a = ((EdDSAPrivateKey) key).geta();

    byte[] message;
    int offset, length;
    if (oneShotMode) {
        if (oneShotBytes == null)
            throw new SignatureException("update() not called first");
        message = oneShotBytes;
        offset = oneShotOffset;
        length = oneShotLength;
    } else {
        if (baos == null)
            message = new byte[0];
        else
            message = baos.toByteArray();
        offset = 0;
        length = message.length;
    }
    // r = H(h_b,...,h_2b-1,M)
    digest.update(message, offset, length);
    byte[] r = digest.digest();

    // r mod l
    // Reduces r from 64 bytes to 32 bytes
    r = sc.reduce(r);

    // R = rB
    GroupElement R = key.getParams().getB().scalarMultiply(r);
    byte[] Rbyte = R.toByteArray();

    // S = (r + H(Rbar,Abar,M)*a) mod l
    digest.update(Rbyte);
    digest.update(((EdDSAPrivateKey) key).getAbyte());
    digest.update(message, offset, length);
    byte[] h = digest.digest();
    h = sc.reduce(h);
    byte[] S = sc.multiplyAndAdd(h, a, r);

    // R+S
    int b = curve.getField().getb();
    ByteBuffer out = ByteBuffer.allocate(b/4);
    out.put(Rbyte).put(S);
    return out.array();
}

@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
    try {
        return x_engineVerify(sigBytes);
    } finally {
        reset();
    }
}

private boolean x_engineVerify(byte[] sigBytes) throws SignatureException {
    Curve curve = key.getParams().getCurve();
    int b = curve.getField().getb();
    if (sigBytes.length != b/4)
        throw new SignatureException("signature length is wrong");

    // R is first b/8 bytes of sigBytes, S is second b/8 bytes
    digest.update(sigBytes, 0, b/8);
    digest.update(((EdDSAPublicKey) key).getAbyte());
    // h = H(Rbar,Abar,M)
    byte[] message;
    int offset, length;
    if (oneShotMode) {
        if (oneShotBytes == null)
            throw new SignatureException("update() not called first");
        message = oneShotBytes;
        offset = oneShotOffset;
        length = oneShotLength;
    } else {
        if (baos == null)
            message = new byte[0];
        else
            message = baos.toByteArray();
        offset = 0;
        length = message.length;
    }
    digest.update(message, offset, length);
    byte[] h = digest.digest();

    // h mod l
    h = key.getParams().getScalarOps().reduce(h);

    byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4);
    // R = SB - H(Rbar,Abar,M)A
    GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime(
            ((EdDSAPublicKey) key).getNegativeA(), h, Sbyte);

    // Variable time. This should be okay, because there are no secret
    // values used anywhere in verification.
    byte[] Rcalc = R.toByteArray();
    for (int i = 0; i < Rcalc.length; i++) {
        if (Rcalc[i] != sigBytes[i])
            return false;
    }
    return true;
}

/**
 *  To efficiently sign all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data)
 *  sig = sign()
 *</pre>
 *
 * @param data the message to be signed
 * @return the signature
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public byte[] signOneShot(byte[] data) throws SignatureException {
    return signOneShot(data, 0, data.length);
}

/**
 *  To efficiently sign all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data, off, len)
 *  sig = sign()
 *</pre>
 *
 * @param data byte array containing the message to be signed
 * @param off the start of the message inside data
 * @param len the length of the message
 * @return the signature
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException {
    oneShotMode = true;
    update(data, off, len);
    return sign();
}

/**
 *  To efficiently verify all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data)
 *  ok = verify(signature)
 *</pre>
 *
 * @param data the message that was signed
 * @param signature of the message
 * @return true if the signature is valid, false otherwise
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException {
    return verifyOneShot(data, 0, data.length, signature, 0, signature.length);
}

/**
 *  To efficiently verify all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data, off, len)
 *  ok = verify(signature)
 *</pre>
 *
 * @param data byte array containing the message that was signed
 * @param off the start of the message inside data
 * @param len the length of the message
 * @param signature of the message
 * @return true if the signature is valid, false otherwise
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException {
    return verifyOneShot(data, off, len, signature, 0, signature.length);
}

/**
 *  To efficiently verify all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data)
 *  ok = verify(signature, sigoff, siglen)
 *</pre>
 *
 * @param data the message that was signed
 * @param signature byte array containing the signature
 * @param sigoff the start of the signature
 * @param siglen the length of the signature
 * @return true if the signature is valid, false otherwise
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException {
    return verifyOneShot(data, 0, data.length, signature, sigoff, siglen);
}

/**
 *  To efficiently verify all the data in one shot, if it is available,
 *  use this method, which will avoid copying the data.
 *
 * Same as:
 *<pre>
 *  setParameter(ONE_SHOT_MODE)
 *  update(data, off, len)
 *  ok = verify(signature, sigoff, siglen)
 *</pre>
 *
 * @param data byte array containing the message that was signed
 * @param off the start of the message inside data
 * @param len the length of the message
 * @param signature byte array containing the signature
 * @param sigoff the start of the signature
 * @param siglen the length of the signature
 * @return true if the signature is valid, false otherwise
 * @throws SignatureException if update() already called
 * @see #ONE_SHOT_MODE
 */
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException {
    oneShotMode = true;
    update(data, off, len);
    return verify(signature, sigoff, siglen);
}

/**
 * @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called
 * @see #ONE_SHOT_MODE
 */
@Override
protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
    if (spec.equals(ONE_SHOT_MODE)) {
        if (oneShotBytes != null || (baos != null && baos.size() > 0))
            throw new InvalidAlgorithmParameterException("update() already called");
        oneShotMode = true;
    } else {
        super.engineSetParameter(spec);
    }
}

/**
 * @deprecated
 */
@Override
protected void engineSetParameter(String param, Object value) {
    throw new UnsupportedOperationException("engineSetParameter unsupported");
}

/**
 * @deprecated
 */
@Override
protected Object engineGetParameter(String param) {
    throw new UnsupportedOperationException("engineSetParameter unsupported");
}

}