package net.i2p.crypto;

/* 
 * Copyright (c) 2003, TheCrypto
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * -  Neither the name of the TheCrypto may be used to endorse or promote 
 *    products derived from this software without specific prior written 
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;

import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAKey;
import net.i2p.crypto.eddsa.RedDSAEngine;
import net.i2p.data.Hash;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;

/**
 *  Sign and verify using DSA-SHA1 and other signature algorithms.
 *  Also contains methods to sign and verify using a SHA-256 Hash.
 *
 *  The primary implementation is code from TheCryto.
 *  As of 0.8.7, also included is an alternate implementation using java.security libraries, which
 *  is slightly slower. This implementation could in the future be easily modified
 *  to use a new signing algorithm from java.security when we change the signing algorithm.
 *
 *  Params and rv's changed from Hash to SHA1Hash for version 0.8.1
 *  Hash variants of sign() and verifySignature() restored in 0.8.3, required by Syndie.
 *
 *  As of 0.9.9, certain methods support RSA and ECDSA keys and signatures, i.e. all types
 *  specified in SigType. The type is specified by the getType() method in
 *  Signature, SigningPublicKey, and SigningPrivateKey. See Javadocs for individual
 *  methods for the supported types. Methods encountering an unsupported type
 *  will throw an IllegalArgumentException.
 *
 *  EdDSA support added in 0.9.15
 */
public final class DSAEngine {
    private final Log _log;
    private final I2PAppContext _context;

    //private static final boolean _isAndroid = System.getProperty("java.vendor").contains("Android");
    private static final boolean _useJavaLibs = false;   // = _isAndroid;

    public DSAEngine(I2PAppContext context) {
        _log = context.logManager().getLog(DSAEngine.class);
        _context = context;
    }

    public static DSAEngine getInstance() {
        return I2PAppContext.getGlobalContext().dsa();
    }

    /**
     *  Verify using any sig type.
     *  Uses TheCrypto code for DSA-SHA1 unless configured to use the java.security libraries.
     */
    public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
        return verifySignature(signature, signedData, 0, signedData.length, verifyingKey);
    }

    /**
     *  Verify using any sig type as of 0.9.12 (DSA only prior to that)
     */
    public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
        boolean rv;
        SigType type = signature.getType();
        if (type != verifyingKey.getType())
            throw new IllegalArgumentException("type mismatch sig=" + signature.getType() + " key=" + verifyingKey.getType());
        if (type != SigType.DSA_SHA1) {
            try {
                rv = altVerifySig(signature, signedData, offset, size, verifyingKey);
                if ((!rv) && _log.shouldLog(Log.WARN))
                    _log.warn(type + " Sig Verify Fail");
                return rv;
            } catch (GeneralSecurityException gse) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn(type + " Sig Verify Fail", gse);
                return false;
            }
        }
        if (_useJavaLibs) {
            try {
                rv = altVerifySigSHA1(signature, signedData, offset, size, verifyingKey);
                if ((!rv) && _log.shouldLog(Log.WARN))
                    _log.warn("Lib DSA Sig Verify Fail");
                return rv;
            } catch (GeneralSecurityException gse) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Lib DSA Sig Verify Fail");
                // now try TheCrypto
            }
        }
        rv = verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey);
        if ((!rv) && _log.shouldLog(Log.WARN))
            _log.warn("TheCrypto DSA Sig Verify Fail");
        return rv;
    }

    /**
     *  Verify using DSA-SHA1 ONLY
     */
    public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) {
        return verifySignature(signature, calculateHash(in), verifyingKey);
    }

    /**
     *  Verify using DSA-SHA1 ONLY
     *  @param hash SHA-1 hash, NOT a SHA-256 hash
     */
    public boolean verifySignature(Signature signature, SHA1Hash hash, SigningPublicKey verifyingKey) {
        return verifySig(signature, hash, verifyingKey);
    }

    /**
     *  Nonstandard.
     *  Used by Syndie.
     *  @since 0.8.3 (restored, was removed in 0.8.1 and 0.8.2)
     */
    public boolean verifySignature(Signature signature, Hash hash, SigningPublicKey verifyingKey) {
        return verifySig(signature, hash, verifyingKey);
    }

    /**
     *  Generic signature type.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @since 0.9.9
     */
    public boolean verifySignature(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey) {
        SigType type = signature.getType();
        if (type != verifyingKey.getType())
            throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType());
        int hashlen = type.getHashLen();
        if (hash.length() != hashlen)
            throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " sig=" + type);
        if (type == SigType.DSA_SHA1)
            return verifySig(signature, hash, verifyingKey);
        try {
            return altVerifySigRaw(signature, hash, verifyingKey);
        } catch (GeneralSecurityException gse) {
            if (_log.shouldLog(Log.WARN))
                _log.warn(type + " Sig Verify Fail", gse);
            return false;
        }
    }

    /**
     *  Generic signature type.
     *  If you have a Java pubkey, use this, so you don't lose the key parameters,
     *  which may be different than the ones defined in SigType.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @param pubKey Java key
     *  @since 0.9.9
     */
    public boolean verifySignature(Signature signature, SimpleDataStructure hash, PublicKey pubKey) {
        try {
            return altVerifySigRaw(signature, hash, pubKey);
        } catch (GeneralSecurityException gse) {
            if (_log.shouldLog(Log.WARN))
                _log.warn(signature.getType() + " Sig Verify Fail", gse);
            return false;
        }
    }

    /**
     *  Verify using DSA-SHA1 or Syndie DSA-SHA256 ONLY.
     *  @param hash either a Hash or a SHA1Hash
     *  @since 0.8.3
     */
    private boolean verifySig(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey) {
        if (signature.getType() != SigType.DSA_SHA1)
            throw new IllegalArgumentException("Bad sig type " + signature.getType());
        if (verifyingKey.getType() != SigType.DSA_SHA1)
            throw new IllegalArgumentException("Bad key type " + verifyingKey.getType());
        long start = _context.clock().now();

        try {
            byte[] sigbytes = signature.getData();
            byte rbytes[] = new byte[20];
            byte sbytes[] = new byte[20];
            //System.arraycopy(sigbytes, 0, rbytes, 0, 20);
            //System.arraycopy(sigbytes, 20, sbytes, 0, 20);
            for (int x = 0; x < 40; x++) {
                if (x < 20) {
                    rbytes[x] = sigbytes[x];
                } else {
                    sbytes[x - 20] = sigbytes[x];
                }
            }

            BigInteger s = new NativeBigInteger(1, sbytes);
            BigInteger r = new NativeBigInteger(1, rbytes);
            BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
            BigInteger w;
            try {
                w = s.modInverse(CryptoConstants.dsaq);
            } catch (ArithmeticException ae) {
                _log.warn("modInverse() error", ae);
                return false;
            }
            byte data[] = hash.getData();
            NativeBigInteger bi = new NativeBigInteger(1, data);
            BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
            BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
            BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
            BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
            BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);

            boolean ok = v.compareTo(r) == 0;

            long diff = _context.clock().now() - start;
            if (diff > 1000) {
                if (_log.shouldLog(Log.WARN)) 
                    _log.warn("Took too long to verify the signature (" + diff + "ms)");
            }
            return ok;
        } catch (RuntimeException e) {
            _log.log(Log.CRIT, "Error verifying the signature", e);
            return false;
        }
    }

    /**
     *  Sign using any key type.
     *  Uses TheCrypto code unless configured to use the java.security libraries.
     *
     *  @return null on error
     */
    public Signature sign(byte data[], SigningPrivateKey signingKey) {
        return sign(data, 0, data.length, signingKey);
    }

    /**
     *  Sign using any key type as of 0.9.12 (DSA-SHA1 only prior to that)
     *
     *  @return null on error
     */
    public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) {
        if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
        SigType type = signingKey.getType();
        if (type != SigType.DSA_SHA1) {
            try {
                return altSign(data, offset, length, signingKey);
            } catch (GeneralSecurityException gse) {
                if (_log.shouldLog(Log.ERROR))
                    _log.error(type + " Sign Fail", gse);
                return null;
            }
        }
        if (_useJavaLibs) {
            try {
                return altSignSHA1(data, offset, length, signingKey);
            } catch (GeneralSecurityException gse) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Lib Sign Fail, privkey = " + signingKey, gse);
                // now try TheCrypto
            }
        }
        SHA1Hash h = calculateHash(data, offset, length);
        return sign(h, signingKey);
    }
    
    /**
     *  Sign using DSA-SHA1 ONLY.
     *  Reads the stream until EOF. Does not close the stream.
     *
     *  @return null on error
     */
    public Signature sign(InputStream in, SigningPrivateKey signingKey) {
        if ((signingKey == null) || (in == null) ) return null;
        SHA1Hash h = calculateHash(in);
        return sign(h, signingKey);
    }

    /**
     *  Sign using DSA-SHA1 ONLY.
     *
     *  @param hash SHA-1 hash, NOT a SHA-256 hash
     *  @return null on error
     */
    public Signature sign(SHA1Hash hash, SigningPrivateKey signingKey) {
        return signIt(hash, signingKey);
    }

    /**
     *  Nonstandard.
     *  Used by Syndie.
     *
     *  @return null on error
     *  @since 0.8.3 (restored, was removed in 0.8.1 and 0.8.2)
     */
    public Signature sign(Hash hash, SigningPrivateKey signingKey) {
        return signIt(hash, signingKey);
    }

    /**
     *  Generic signature type.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @return null on error
     *  @since 0.9.9
     */
    public Signature sign(SimpleDataStructure hash, SigningPrivateKey signingKey) {
        SigType type = signingKey.getType();
        int hashlen = type.getHashLen();
        if (hash.length() != hashlen)
            throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
        if (type == SigType.DSA_SHA1)
            return signIt(hash, signingKey);
        try {
            return altSignRaw(hash, signingKey);
        } catch (GeneralSecurityException gse) {
            if (_log.shouldLog(Log.WARN))
                _log.warn(type + " Sign Fail", gse);
            return null;
        }
    }

    /**
     *  Generic signature type.
     *  If you have a Java privkey, use this, so you don't lose the key parameters,
     *  which may be different than the ones defined in SigType.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @param privKey Java key
     *  @param type returns a Signature of this type
     *  @return null on error
     *  @since 0.9.9
     */
    public Signature sign(SimpleDataStructure hash, PrivateKey privKey, SigType type) {
        String algo = getRawAlgo(privKey);
        String talgo = getRawAlgo(type);
        if (!algo.equals(talgo))
            throw new IllegalArgumentException("type mismatch type=" + type + " key=" + privKey.getClass().getSimpleName());
        try {
            return altSignRaw(algo, hash, privKey, type);
        } catch (GeneralSecurityException gse) {
            if (_log.shouldLog(Log.WARN))
                _log.warn(type + " Sign Fail", gse);
            return null;
        }
    }

    /**
     *  Sign using DSA-SHA1 or Syndie DSA-SHA256 ONLY.
     *
     *  @param hash either a Hash or a SHA1Hash
     *  @return null on error
     *  @since 0.8.3
     */
    private Signature signIt(SimpleDataStructure hash, SigningPrivateKey signingKey) {
        if ((signingKey == null) || (hash == null)) return null;
        if (signingKey.getType() != SigType.DSA_SHA1)
            throw new IllegalArgumentException("Bad key type " + signingKey.getType());
        long start = _context.clock().now();

        BigInteger k;
        boolean ok;
        do {
            k = new NativeBigInteger(160, _context.random());
            ok = k.compareTo(CryptoConstants.dsaq) != 1;
            ok = ok && !k.equals(BigInteger.ZERO);
            //System.out.println("K picked (ok? " + ok + "): " + k.bitLength() + ": " + k.toString());
        } while (!ok);

        BigInteger r = CryptoConstants.dsag.modPowCT(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
        BigInteger kinv = k.modInverse(CryptoConstants.dsaq);

        BigInteger M = new NativeBigInteger(1, hash.getData());
        BigInteger x = new NativeBigInteger(1, signingKey.getData());
        BigInteger s = (kinv.multiply(M.add(x.multiply(r)))).mod(CryptoConstants.dsaq);

        byte[] rbytes = r.toByteArray();
        byte[] sbytes = s.toByteArray();
        byte[] out = new byte[40];

        // (q^random)%p is computationally random
        _context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length);
        
        if (rbytes.length == 20) {
            //System.arraycopy(rbytes, 0, out, 0, 20);
            for (int i = 0; i < 20; i++) {
                out[i] = rbytes[i];
            }
        } else if (rbytes.length == 21) {
            //System.arraycopy(rbytes, 1, out, 0, 20);
            for (int i = 0; i < 20; i++) {
                out[i] = rbytes[i + 1];
            }
        } else if (rbytes.length > 21) {
            _log.error("Bad R length " + rbytes.length);
            return null;
        } else {
            //if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]");
            //System.arraycopy(rbytes, 0, out, 20 - rbytes.length, rbytes.length);
            for (int i = 0; i < rbytes.length; i++)
                out[i + 20 - rbytes.length] = rbytes[i];
        }
        if (sbytes.length == 20) {
            //System.arraycopy(sbytes, 0, out, 20, 20);
            for (int i = 0; i < 20; i++) {
                out[i + 20] = sbytes[i];
            }
        } else if (sbytes.length == 21) {
            //System.arraycopy(sbytes, 1, out, 20, 20);
            for (int i = 0; i < 20; i++) {
                out[i + 20] = sbytes[i + 1];
            }
        } else if (sbytes.length > 21) {
            _log.error("Bad S length " + sbytes.length);
            return null;
        } else {
            //if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]");
            //System.arraycopy(sbytes, 0, out, 40 - sbytes.length, sbytes.length);
            for (int i = 0; i < sbytes.length; i++)
                out[i + 20 + 20 - sbytes.length] = sbytes[i];
        }

        long diff = _context.clock().now() - start;
        if (diff > 1000) {
            if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to sign (" + diff + "ms)");
        }

        return new Signature(out);
    }
    
    /**
     *  Reads the stream until EOF. Does not close the stream.
     *
     *  @return hash SHA-1 hash, NOT a SHA-256 hash
     *  @deprecated unused
     */
    @Deprecated
    public SHA1Hash calculateHash(InputStream in) {
        MessageDigest digest = SHA1.getInstance();
        byte buf[] = new byte[64];
        int read = 0;
        try {
            while ( (read = in.read(buf)) != -1) {
                digest.update(buf, 0, read);
            }
        } catch (IOException ioe) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Unable to hash the stream", ioe);
            return null;
        }
        return new SHA1Hash(digest.digest());
    }

    /** @return hash SHA-1 hash, NOT a SHA-256 hash */
    public static SHA1Hash calculateHash(byte[] source, int offset, int len) {
        MessageDigest h = SHA1.getInstance();
        h.update(source, offset, len);
        byte digested[] = h.digest();
        return new SHA1Hash(digested);
    }

    /**
     *  Generic verify any type.
     *
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.9.9 added off/len 0.9.12
     */
    private boolean altVerifySig(Signature signature, byte[] data, int offset, int len, SigningPublicKey verifyingKey)
                        throws GeneralSecurityException {
        SigType type = signature.getType();
        if (type != verifyingKey.getType())
            throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType());
        if (type == SigType.DSA_SHA1)
            return altVerifySigSHA1(signature, data, offset, len, verifyingKey);

        PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
        byte[] sigbytes = SigUtil.toJavaSig(signature);
        boolean rv;
        if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
            // take advantage of one-shot mode
            MessageDigest md = type.getDigestInstance();
            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine(md) : new EdDSAEngine(md);
            jsig.initVerify(pubKey);
            rv = jsig.verifyOneShot(data, offset, len, sigbytes);
        } else {
            java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
            jsig.initVerify(pubKey);
            jsig.update(data, offset, len);
            rv = jsig.verify(sigbytes);
        }
        return rv;
    }

    /**
     *  Generic raw verify any type
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.9.9
     */
    private boolean altVerifySigRaw(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey)
                        throws GeneralSecurityException {
        SigType type = signature.getType();
        if (type != verifyingKey.getType())
            throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType());

        PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
        return verifySignature(signature, hash, pubKey);
    }

    /**
     *  Generic raw verify any type.
     *  If you have a Java pubkey, use this, so you don't lose the key parameters,
     *  which may be different than the ones defined in SigType.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @param verifyingKey Java key
     *  @since 0.9.9
     */
    private boolean altVerifySigRaw(Signature signature, SimpleDataStructure hash, PublicKey pubKey)
                        throws GeneralSecurityException {
        SigType type = signature.getType();
        int hashlen = hash.length();
        if (type.getHashLen() != hashlen)
            throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);

        byte[] sigbytes = SigUtil.toJavaSig(signature);
        boolean rv;
        if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
            // take advantage of one-shot mode
            // Ignore algo, EdDSAKey includes a hash specification.
            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine() : new EdDSAEngine();
            jsig.initVerify(pubKey);
            rv = jsig.verifyOneShot(hash.getData(), sigbytes);
        } else {
            String algo = getRawAlgo(type);
            java.security.Signature jsig = java.security.Signature.getInstance(algo);
            jsig.initVerify(pubKey);
            jsig.update(hash.getData());
            rv = jsig.verify(sigbytes);
        }
        return rv;
    }

    /**
     *  Alternate to verifySignature() using java.security libraries.
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.8.7 added off/len 0.9.12
     */
    private boolean altVerifySigSHA1(Signature signature, byte[] data, int offset,
                                     int len, SigningPublicKey verifyingKey) throws GeneralSecurityException {
        java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA");
        PublicKey pubKey = SigUtil.toJavaDSAKey(verifyingKey);
        jsig.initVerify(pubKey);
        jsig.update(data, offset, len);
        boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
        //if (!rv) {
        //    System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(signature.getData()));
        //    System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(sigBytesToASN1(signature.getData())));
        //}
        return rv;
    }

    /**
     *  Generic sign any type.
     *
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.9.9 added off/len 0.9.12
     */
    private Signature altSign(byte[] data, int offset, int len,
                              SigningPrivateKey privateKey) throws GeneralSecurityException {
        SigType type = privateKey.getType();
        if (type == SigType.DSA_SHA1)
            return altSignSHA1(data, offset, len, privateKey);

        PrivateKey privKey = SigUtil.toJavaKey(privateKey);
        byte[] sigbytes;
        if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
            // take advantage of one-shot mode
            MessageDigest md = type.getDigestInstance();
            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine(md) : new EdDSAEngine(md);
            jsig.initSign(privKey);
            sigbytes = jsig.signOneShot(data, offset, len);
        } else {
            java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
            jsig.initSign(privKey, _context.random());
            jsig.update(data, offset, len);
            sigbytes = jsig.sign();
            if (type.getBaseAlgorithm() == SigAlgo.RSA) {
                // verify to prevent corrupted sig key factoring
                // (RSA fault attack) https://eprint.iacr.org/2023/1711.pdf
                SigningPrivateKey priv = SigUtil.fromJavaKey(privKey, type);
                SigningPublicKey pub = priv.toPublic();
                PublicKey pubKey = SigUtil.toJavaKey(pub);
                jsig.initVerify(pubKey);
                jsig.update(data, offset, len);
                if (!jsig.verify(sigbytes))
                    throw new GeneralSecurityException("Verify of RSA Signature failed");
            }
        }
        return SigUtil.fromJavaSig(sigbytes, type);
    }

    /**
     *  Generic raw sign any type.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.9.9
     */
    private Signature altSignRaw(SimpleDataStructure hash, SigningPrivateKey privateKey) throws GeneralSecurityException {
        SigType type = privateKey.getType();
        String algo = getRawAlgo(type);
        PrivateKey privKey = SigUtil.toJavaKey(privateKey);
        return altSignRaw(algo, hash, privKey, type);
    }

    /**
     *  Generic raw sign any type.
     *
     *  Warning, nonstandard for EdDSA, double-hashes, not recommended.
     *
     *  @param hash SHA1Hash, Hash, Hash384, or Hash512
     *  @param type returns a Signature of this type
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.9.9
     */
    private Signature altSignRaw(String algo, SimpleDataStructure hash, PrivateKey privKey, SigType type)
                                 throws GeneralSecurityException {
        int hashlen = hash.length();
        if (type.getHashLen() != hashlen)
            throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);

        byte[] sigbytes;
        if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
            // take advantage of one-shot mode
            // Ignore algo, EdDSAKey includes a hash specification.
            EdDSAEngine jsig = (type == SigType.RedDSA_SHA512_Ed25519) ? new RedDSAEngine() : new EdDSAEngine();
            jsig.initSign(privKey);
            sigbytes = jsig.signOneShot(hash.getData());
        } else {
            java.security.Signature jsig = java.security.Signature.getInstance(algo);
            jsig.initSign(privKey, _context.random());
            jsig.update(hash.getData());
            sigbytes = jsig.sign();
            if (type.getBaseAlgorithm() == SigAlgo.RSA) {
                // verify to prevent corrupted sig key factoring
                // (RSA fault attack) https://eprint.iacr.org/2023/1711.pdf
                SigningPrivateKey priv = SigUtil.fromJavaKey(privKey, type);
                SigningPublicKey pub = priv.toPublic();
                PublicKey pubKey = SigUtil.toJavaKey(pub);
                jsig.initVerify(pubKey);
                jsig.update(hash.getData());
                if (!jsig.verify(sigbytes))
                    throw new GeneralSecurityException("Verify of RSA Signature failed");
            }
        }
        return SigUtil.fromJavaSig(sigbytes, type);
    }

    /**
     *  Alternate to sign() using java.security libraries.
     *  @throws GeneralSecurityException if algorithm unvailable or on other errors
     *  @since 0.8.7 added off/len args 0.9.12
     */
    private Signature altSignSHA1(byte[] data, int offset, int len,
                                  SigningPrivateKey privateKey) throws GeneralSecurityException {
        java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA");
        PrivateKey privKey = SigUtil.toJavaDSAKey(privateKey);
        jsig.initSign(privKey, _context.random());
        jsig.update(data, offset, len);
        return SigUtil.fromJavaSig(jsig.sign(), SigType.DSA_SHA1);
    }

    /** @since 0.9.9 */
    private static String getRawAlgo(SigType type) {
        switch (type.getBaseAlgorithm()) {
            case DSA:
                return "NONEwithDSA";
            case EC:
                return "NONEwithECDSA";
            case EdDSA:
                return "NONEwithEdDSA";
            case RSA:
                return "NONEwithRSA";
            default:
                throw new UnsupportedOperationException("Raw signatures unsupported for " + type);
        }
    }

    /** @since 0.9.9 */
    private static String getRawAlgo(Key key) {
        if (key instanceof DSAKey)
            return "NONEwithDSA";
        if (key instanceof ECKey)
            return "NONEwithECDSA";
        if (key instanceof EdDSAKey)
            return "NONEwithEdDSA";
        if (key instanceof RSAKey)
            return "NONEwithRSA";
        throw new UnsupportedOperationException("Raw signatures unsupported for " + key.getClass().getName());
    }

    //private static final int RUNS = 1000;

    /**
     *  Run consistency and speed tests with both TheCrypto and java.security libraries.
     *
     *  TheCrypto is about 5-15% faster than java.security.
     */
/****
    public static void main(String args[]) {
        I2PAppContext ctx = I2PAppContext.getGlobalContext();
        byte data[] = new byte[1024];
        // warmump
        ctx.random().nextBytes(data);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ie) {}
        SimpleDataStructure keys[] = null;

        System.err.println("100 runs with new data and keys each time");
        for (int i = 0; i < 100; i++) {
            ctx.random().nextBytes(data);
            keys = ctx.keyGenerator().generateSigningKeys();
            Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
            Signature jsig = null;
            try {
                 jsig = ctx.dsa().altSignSHA1(data, (SigningPrivateKey)keys[1]);
            } catch (GeneralSecurityException gse) {
                gse.printStackTrace();
            }
            boolean ok = ctx.dsa().verifySignature(jsig, data, (SigningPublicKey)keys[0]);
            boolean usok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
            boolean jok = false;
            try {
                jok = ctx.dsa().altVerifySigSHA1(sig, data, (SigningPublicKey)keys[0]);
            } catch (GeneralSecurityException gse) {
                gse.printStackTrace();
            }
            boolean jjok = false;;
            try {
                jjok = ctx.dsa().altVerifySigSHA1(jsig, data, (SigningPublicKey)keys[0]);
            } catch (GeneralSecurityException gse) {
                gse.printStackTrace();
            }
            System.err.println("TC->TC OK: " + usok + "  JL->TC OK: " + ok + "  TC->JK OK: " + jok + "  JL->JL OK: " + jjok);
            if (!(ok && usok && jok && jjok)) {
                System.out.println("privkey\n" + net.i2p.util.HexDump.dump(keys[1].getData()));
                return;
            }
        }

        System.err.println("Starting speed test");
        long start = System.currentTimeMillis();
        for (int i = 0; i < RUNS; i++) {
            Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
            boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
            if (!ok) {
                System.err.println("TheCrypto FAIL");
                return;
            }
        }
        long time = System.currentTimeMillis() - start;
        System.err.println("Time for " + RUNS + " DSA sign/verifies:");
        System.err.println("TheCrypto time (ms): " + time);

        start = System.currentTimeMillis();
        for (int i = 0; i < RUNS; i++) {
            boolean ok = false;
            try {
                Signature jsig = ctx.dsa().altSignSHA1(data, (SigningPrivateKey)keys[1]);
                ok = ctx.dsa().altVerifySigSHA1(jsig, data, (SigningPublicKey)keys[0]);
            } catch (GeneralSecurityException gse) {
                gse.printStackTrace();
            }
            if (!ok) {
                System.err.println("JavaLib FAIL");
                return;
            }
        }
        time = System.currentTimeMillis() - start;
        System.err.println("JavaLib time (ms): " + time);
****/
/****  yes, arraycopy is slower for 20 bytes
        start = System.currentTimeMillis();
	byte b[] = new byte[20];
        for (int i = 0; i < 10000000; i++) {
            data[0] = data[i % 256];
            System.arraycopy(data, 0, b, 0, 20);
        }
        time = System.currentTimeMillis() - start;
        System.err.println("arraycopy time (ms): " + time);

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            data[0] = data[i % 256];
            for (int j = 0; j < 20; j++) {
                 b[j] = data[j];
            }
        }
        time = System.currentTimeMillis() - start;
        System.err.println("loop time (ms): " + time);
****/
/****
     }
****/
}
