package cn.hyperchain.sdk.crypto.sm.sm2;

import cn.hyperchain.sdk.common.utils.ByteUtil;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;

import java.math.BigInteger;
import java.security.SecureRandom;

public class SM2Util {

    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public static final BigInteger SM2_ECC_P = CURVE.getQ();
    public static final BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public static final BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public static final BigInteger SM2_ECC_N = CURVE.getOrder();
    public static final BigInteger SM2_ECC_H = CURVE.getCofactor();
    public static final BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public static final BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);

    /**
     * create a random key pair of sm.
     *
     * @return String array of publicKey and privateKey
     */
    public static AsymmetricCipherKeyPair generateKeyPair() {
        SecureRandom random = new SecureRandom();
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(DOMAIN_PARAMS, random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }

    /**
     * get signature by sm2 key pair, use default userID.
     *
     * @param keyPair private key bytes
     * @param srcData source data
     * @return signature bytes
     * @throws CryptoException -
     */
    public static byte[] sign(byte[] keyPair, byte[] srcData) throws CryptoException {
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, keyPair), SM2Util.DOMAIN_PARAMS);
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = new AsymmetricCipherKeyPair(null, privateKeyParameters);
        return sign(asymmetricCipherKeyPair, srcData);
    }

    /**
     * get signature by sm2 key pair, use default userID.
     *
     * @param keyPair ECC key pair
     * @param srcData source data
     * @return signature bytes
     * @throws CryptoException -
     */
    public static byte[] sign(AsymmetricCipherKeyPair keyPair, byte[] srcData) throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = new ParametersWithRandom(keyPair.getPrivate(), new SecureRandom());
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }

    /**
     * verify sm2 signature.
     *
     * @param publicKey publicKey bytes
     * @param sourceData source data
     * @param signature signature
     * @return is legal
     */
    public static boolean verify(byte[] sourceData, byte[] signature, byte[] publicKey) {
        ECPoint ecPoint = CURVE.decodePoint(publicKey);
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(ecPoint, DOMAIN_PARAMS);
        return verify(sourceData, signature, ecPublicKeyParameters);
    }

    /**
     * verify sm2 signature.
     *
     * @param ecPublicKeyParameters ecPublicKey param
     * @param sourceData source data
     * @param signature signature
     * @return is legal
     */
    public static boolean verify(byte[] sourceData, byte[] signature, ECPublicKeyParameters ecPublicKeyParameters) {
        SM2Signer signer = new SM2Signer();
        signer.init(false, ecPublicKeyParameters);
        signer.update(sourceData, 0, sourceData.length);
        return signer.verifySignature(signature);
    }
}