// 
// Decompiled by Procyon v0.6.0
// 

package org.bouncycastle.crypto.signers;

import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.crypto.Signer;

public class SM2Signer implements Signer, ECConstants
{
    private final DSAKCalculator kCalculator;
    private final Digest digest;
    private final DSAEncoding encoding;
    private int state;
    private ECDomainParameters ecParams;
    private ECPoint pubPoint;
    private ECKeyParameters ecKey;
    private byte[] z;
    
    public SM2Signer() {
        this(StandardDSAEncoding.INSTANCE, new SM3Digest());
    }
    
    public SM2Signer(final Digest digest) {
        this(StandardDSAEncoding.INSTANCE, digest);
    }
    
    public SM2Signer(final DSAEncoding encoding) {
        this.kCalculator = new RandomDSAKCalculator();
        this.state = 0;
        this.encoding = encoding;
        this.digest = new SM3Digest();
    }
    
    public SM2Signer(final DSAEncoding encoding, final Digest digest) {
        this.kCalculator = new RandomDSAKCalculator();
        this.state = 0;
        this.encoding = encoding;
        this.digest = digest;
    }
    
    @Override
    public void init(final boolean b, final CipherParameters cipherParameters) {
        CipherParameters cipherParameters2;
        byte[] array;
        if (cipherParameters instanceof ParametersWithID) {
            cipherParameters2 = ((ParametersWithID)cipherParameters).getParameters();
            array = ((ParametersWithID)cipherParameters).getID();
            if (array.length >= 8192) {
                throw new IllegalArgumentException("SM2 user ID must be less than 2^16 bits long");
            }
        }
        else {
            cipherParameters2 = cipherParameters;
            array = Hex.decodeStrict("31323334353637383132333435363738");
        }
        if (b) {
            SecureRandom random = null;
            if (cipherParameters2 instanceof ParametersWithRandom) {
                final ParametersWithRandom parametersWithRandom = (ParametersWithRandom)cipherParameters2;
                cipherParameters2 = parametersWithRandom.getParameters();
                random = parametersWithRandom.getRandom();
            }
            final ECPrivateKeyParameters ecKey = (ECPrivateKeyParameters)cipherParameters2;
            this.ecKey = ecKey;
            this.ecParams = ecKey.getParameters();
            final BigInteger d = ecKey.getD();
            final BigInteger n = this.ecParams.getN();
            if (d.compareTo(SM2Signer.ONE) < 0 || d.compareTo(n.subtract(SM2Signer.ONE)) >= 0) {
                throw new IllegalArgumentException("SM2 private key out of range");
            }
            this.kCalculator.init(n, CryptoServicesRegistrar.getSecureRandom(random));
            this.pubPoint = this.createBasePointMultiplier().multiply(this.ecParams.getG(), d).normalize();
        }
        else {
            final ECPublicKeyParameters ecKey2 = (ECPublicKeyParameters)cipherParameters2;
            this.ecKey = ecKey2;
            this.ecParams = ecKey2.getParameters();
            this.pubPoint = ecKey2.getQ();
        }
        CryptoServicesRegistrar.checkConstraints(Utils.getDefaultProperties("ECNR", this.ecKey, b));
        this.digest.reset();
        this.z = this.getZ(array);
        this.state = 1;
    }
    
    @Override
    public void update(final byte b) {
        this.checkData();
        this.digest.update(b);
    }
    
    @Override
    public void update(final byte[] array, final int n, final int n2) {
        this.checkData();
        this.digest.update(array, n, n2);
    }
    
    @Override
    public boolean verifySignature(final byte[] array) {
        this.checkData();
        try {
            final BigInteger[] decode = this.encoding.decode(this.ecParams.getN(), array);
            return this.verifySignature(decode[0], decode[1]);
        }
        catch (final Exception ex) {}
        finally {
            this.reset();
        }
        return false;
    }
    
    @Override
    public void reset() {
        switch (this.state) {
            case 1: {
                return;
            }
            case 2: {
                this.digest.reset();
                this.state = 1;
                return;
            }
            default: {
                throw new IllegalStateException("SM2Signer needs to be initialized");
            }
        }
    }
    
    @Override
    public byte[] generateSignature() throws CryptoException {
        this.checkData();
        final byte[] digestDoFinal = this.digestDoFinal();
        final BigInteger n = this.ecParams.getN();
        final BigInteger calculateE = this.calculateE(n, digestDoFinal);
        final BigInteger d = ((ECPrivateKeyParameters)this.ecKey).getD();
        final ECMultiplier basePointMultiplier = this.createBasePointMultiplier();
        BigInteger mod;
        BigInteger mod2;
        while (true) {
            final BigInteger nextK = this.kCalculator.nextK();
            mod = calculateE.add(basePointMultiplier.multiply(this.ecParams.getG(), nextK).normalize().getAffineXCoord().toBigInteger()).mod(n);
            if (!mod.equals(SM2Signer.ZERO) && !mod.add(nextK).equals(n)) {
                mod2 = BigIntegers.modOddInverse(n, d.add(SM2Signer.ONE)).multiply(nextK.subtract(mod.multiply(d)).mod(n)).mod(n);
                if (!mod2.equals(SM2Signer.ZERO)) {
                    break;
                }
                continue;
            }
        }
        try {
            return this.encoding.encode(this.ecParams.getN(), mod, mod2);
        }
        catch (final Exception ex) {
            throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex);
        }
        finally {
            this.reset();
        }
    }
    
    private boolean verifySignature(final BigInteger x, final BigInteger val) {
        final BigInteger n = this.ecParams.getN();
        if (x.compareTo(SM2Signer.ONE) < 0 || x.compareTo(n) >= 0) {
            return false;
        }
        if (val.compareTo(SM2Signer.ONE) < 0 || val.compareTo(n) >= 0) {
            return false;
        }
        final BigInteger calculateE = this.calculateE(n, this.digestDoFinal());
        final BigInteger mod = x.add(val).mod(n);
        if (mod.equals(SM2Signer.ZERO)) {
            return false;
        }
        final ECPoint normalize = ECAlgorithms.sumOfTwoMultiplies(this.ecParams.getG(), val, ((ECPublicKeyParameters)this.ecKey).getQ(), mod).normalize();
        return !normalize.isInfinity() && calculateE.add(normalize.getAffineXCoord().toBigInteger()).mod(n).equals(x);
    }
    
    private void checkData() {
        switch (this.state) {
            case 1: {
                this.digest.update(this.z, 0, this.z.length);
                this.state = 2;
                return;
            }
            case 2: {
                return;
            }
            default: {
                throw new IllegalStateException("SM2Signer needs to be initialized");
            }
        }
    }
    
    private byte[] digestDoFinal() {
        final byte[] array = new byte[this.digest.getDigestSize()];
        this.digest.doFinal(array, 0);
        return array;
    }
    
    private byte[] getZ(final byte[] array) {
        this.addUserID(this.digest, array);
        this.addFieldElement(this.digest, this.ecParams.getCurve().getA());
        this.addFieldElement(this.digest, this.ecParams.getCurve().getB());
        this.addFieldElement(this.digest, this.ecParams.getG().getAffineXCoord());
        this.addFieldElement(this.digest, this.ecParams.getG().getAffineYCoord());
        this.addFieldElement(this.digest, this.pubPoint.getAffineXCoord());
        this.addFieldElement(this.digest, this.pubPoint.getAffineYCoord());
        return this.digestDoFinal();
    }
    
    private void addUserID(final Digest digest, final byte[] array) {
        final int n = array.length * 8;
        digest.update((byte)(n >>> 8));
        digest.update((byte)n);
        digest.update(array, 0, array.length);
    }
    
    private void addFieldElement(final Digest digest, final ECFieldElement ecFieldElement) {
        final byte[] encoded = ecFieldElement.getEncoded();
        digest.update(encoded, 0, encoded.length);
    }
    
    protected ECMultiplier createBasePointMultiplier() {
        return new FixedPointCombMultiplier();
    }
    
    protected BigInteger calculateE(final BigInteger bigInteger, final byte[] magnitude) {
        return new BigInteger(1, magnitude);
    }
    
    private static final class State
    {
        static final int UNINITIALIZED = 0;
        static final int INIT = 1;
        static final int DATA = 2;
    }
}
