package org.bouncycastle.crypto.signers;

import java.math.BigInteger;

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

The SM2 Digital Signature algorithm.
/** * The SM2 Digital Signature algorithm. */
public class SM2Signer implements Signer, ECConstants { private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); private final SM3Digest digest = new SM3Digest(); private final DSAEncoding encoding; private ECDomainParameters ecParams; private ECPoint pubPoint; private ECKeyParameters ecKey; private byte[] z; public SM2Signer() { this(StandardDSAEncoding.INSTANCE); } public SM2Signer(DSAEncoding encoding) { this.encoding = encoding; } public void init(boolean forSigning, CipherParameters param) { CipherParameters baseParam; byte[] userID; if (param instanceof ParametersWithID) { baseParam = ((ParametersWithID)param).getParameters(); userID = ((ParametersWithID)param).getID(); } else { baseParam = param; userID = Hex.decode("31323334353637383132333435363738"); // the default value } if (forSigning) { if (baseParam instanceof ParametersWithRandom) { ParametersWithRandom rParam = (ParametersWithRandom)baseParam; ecKey = (ECKeyParameters)rParam.getParameters(); ecParams = ecKey.getParameters(); kCalculator.init(ecParams.getN(), rParam.getRandom()); } else { ecKey = (ECKeyParameters)baseParam; ecParams = ecKey.getParameters(); kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom()); } pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters)ecKey).getD()).normalize(); } else { ecKey = (ECKeyParameters)baseParam; ecParams = ecKey.getParameters(); pubPoint = ((ECPublicKeyParameters)ecKey).getQ(); } z = getZ(userID); digest.update(z, 0, z.length); } public void update(byte b) { digest.update(b); } public void update(byte[] in, int off, int len) { digest.update(in, off, len); } public boolean verifySignature(byte[] signature) { try { BigInteger[] rs = encoding.decode(ecParams.getN(), signature); return verifySignature(rs[0], rs[1]); } catch (Exception e) { } return false; } public void reset() { digest.reset(); if (z != null) { digest.update(z, 0, z.length); } } public byte[] generateSignature() throws CryptoException { byte[] eHash = digestDoFinal(); BigInteger n = ecParams.getN(); BigInteger e = calculateE(eHash); BigInteger d = ((ECPrivateKeyParameters)ecKey).getD(); BigInteger r, s; ECMultiplier basePointMultiplier = createBasePointMultiplier(); // 5.2.1 Draft RFC: SM2 Public Key Algorithms do // generate s { BigInteger k; do // generate r { // A3 k = kCalculator.nextK(); // A4 ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); // A5 r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); } while (r.equals(ZERO) || r.add(k).equals(n)); // A6 BigInteger dPlus1ModN = d.add(ONE).modInverse(n); s = k.subtract(r.multiply(d)).mod(n); s = dPlus1ModN.multiply(s).mod(n); } while (s.equals(ZERO)); // A7 try { return encoding.encode(ecParams.getN(), r, s); } catch (Exception ex) { throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex); } } private boolean verifySignature(BigInteger r, BigInteger s) { BigInteger n = ecParams.getN(); // 5.3.1 Draft RFC: SM2 Public Key Algorithms // B1 if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) { return false; } // B2 if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) { return false; } // B3 byte[] eHash = digestDoFinal(); // B4 BigInteger e = calculateE(eHash); // B5 BigInteger t = r.add(s).mod(n); if (t.equals(ZERO)) { return false; } // B6 ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize(); if (x1y1.isInfinity()) { return false; } // B7 BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n); return expectedR.equals(r); } private byte[] digestDoFinal() { byte[] result = new byte[digest.getDigestSize()]; digest.doFinal(result, 0); reset(); return result; } private byte[] getZ(byte[] userID) { digest.reset(); addUserID(digest, userID); addFieldElement(digest, ecParams.getCurve().getA()); addFieldElement(digest, ecParams.getCurve().getB()); addFieldElement(digest, ecParams.getG().getAffineXCoord()); addFieldElement(digest, ecParams.getG().getAffineYCoord()); addFieldElement(digest, pubPoint.getAffineXCoord()); addFieldElement(digest, pubPoint.getAffineYCoord()); byte[] result = new byte[digest.getDigestSize()]; digest.doFinal(result, 0); return result; } private void addUserID(Digest digest, byte[] userID) { int len = userID.length * 8; digest.update((byte)(len >> 8 & 0xFF)); digest.update((byte)(len & 0xFF)); digest.update(userID, 0, userID.length); } private void addFieldElement(Digest digest, ECFieldElement v) { byte[] p = v.getEncoded(); digest.update(p, 0, p.length); } protected ECMultiplier createBasePointMultiplier() { return new FixedPointCombMultiplier(); } protected BigInteger calculateE(byte[] message) { return new BigInteger(1, message); } }