3

Given the x and y coordinates of a public key and a curve name, I need to determine whether those coordinates represent a valid point on the curve or not. If yes, test passed. If no, test failed.

My code so far is:

                    String curve = (String) testGroupHeaders.get("curve");
                    String curve_num = curve.split("-")[1];
                    String specName = "secp" + curve_num + "r1";
                        String qx = (String) testsData.get("qx");
                        String qy = (String) testsData.get("qy");
                        BigInteger x = new BigInteger(qx, 16);
                        BigInteger y = new BigInteger(qy, 16);
                        ECPoint ecPoint = new ECPoint(x, y);
                        if (ecPoint.equals(ECPoint.POINT_INFINITY)) {
                            testResultsObject.put("testPassed", false);
                        } else {
                            try {
                                AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
                                parameters.init(new ECGenParameterSpec(specName));
                                ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
                                EllipticCurve ellCurve = ecParameters.getCurve();
                                ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
                                KeyFactory keyFactory = KeyFactory.getInstance("EC");
                                ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(keySpec);
                                testResultsObject.put("testPassed", true);
                            } catch (Exception e) {
                                System.out.println(e);
                                testResultsObject.put("testPassed", false);
                            }

My hope was that the construction of the public key would fail if the points were invalid, but this code still results in the tests passing when inputting known invalid points.

Any help would be greatly appreciated.

I found this question How to determine whether EC Point is on curve? which has a vague answer:

"There might be a more straightforward way but if you've got the EllipticCurve object, you can always just substitute the coordinates of your point into the equation of the curve."

This could be a potential solution, but I am unsure of how to implement it. I tried:

EllipticCurve ellC = publicKey.getParams().getCurve();

in the hopes that it would fail to construct a curve, but alas it still succeeds.

Note that BouncyCastle is not an option for me, I must use java.security provider.

I also found this answer Elliptic curve point

But I was not able to get anything out of that either.

EDIT

Okay, I'm very close with this code after adapting from the Bouncy Castle code in the last linked answer above:


                                EllipticCurve ellCurve = ecParameters.getCurve();
                                BigInteger a = ellCurve.getA();
                                BigInteger b = ellCurve.getB();
                                ECField ecField = ellCurve.getField();
                                ECFieldFp fp = (ECFieldFp) ecField;
                                BigInteger p = ((ECFieldFp) ecField).getP();
                                BigInteger rhs = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
                                BigInteger lhs = y.multiply(y).mod(p);
                                System.out.println(rhs);
                                System.out.println(lhs);

This is working for most cases, except for this particular case with secp521r1:


          {
            "tcId": 8,
            "qx": "02ED3613D77D9096DC0545F3EEC44270762BF52E63044D29FC880556114F4FC176DBE20A9BC717C58FA63BB3308D3136355A072704C0B8BE9A6B3CFFFE36467722C0",
            "qy": "00E857AE0D11FB1B79AC9531980A3E3BD1AC9E457E39A107D0AF5C9E09F2D6243F31C697C74CFD6A80F1CA5FCDA5950754DCC8724B9B21ED3A6705EBA1B9D2926B8F"
          },

When I apply the above code and compare lhs to rhs, I get the same output which indicates to me that this is a valid point on the curve.

rhs: 1634177850809752323211745106139613474061093186782816308703279366414782386677365518237753355552098931968864191666289807321598625802278923850729659158219971712
lhs: 1634177850809752323211745106139613474061093186782816308703279366414782386677365518237753355552098931968864191666289807321598625802278923850729659158219971712

So there must be a different check that I'm missing that indicates this as a failure.

factor2
  • 155
  • 9
  • The [accepted answer](https://stackoverflow.com/a/6664005/16317602) to the last posted link actually provides the answer. Basically, all you have to do is use the upper snippet and assign your values for curve, x and y coordinate to the first three variables. What was the problem with that? It is best to post your attempt. Because the link provides the answer, your question is actually a duplicate. – Topaco Jul 12 '21 at 18:05
  • That snippet is using the bouncycastle api, I don't think I have access to ECFieldElement with java.security – factor2 Jul 12 '21 at 18:12
  • Ah OK, I missed that you don't want apply BC. – Topaco Jul 12 '21 at 18:13
  • No worries. I may have written that answer off too early so I'm happy you made me go back and look. Perhaps there is still a way I can adapt without BouncyCastle. I just tried treating all those components as BigIntegers but that didn't work out unfortunately. Any ideas on that front? – factor2 Jul 12 '21 at 18:18
  • Getting close with that last answer as a reference. See my edit above. – factor2 Jul 12 '21 at 18:38
  • The testcase in your edit _is_ on the curve secp521r1 aka P-521/nistp521, so your code is now correct for Weierstrass (or X9) prime-field curves, though not others. (Although it would be easier to have the computer do the comparison `lhs.equals(rhs)` instead of doing it by hand.) – dave_thompson_085 Jul 12 '21 at 18:55
  • With the added check of affine x and affine y being in the range [0, p-1], the test case ends up being invalid as it is supposed to be. – factor2 Jul 13 '21 at 12:31

1 Answers1

2

Figured out the answer, posting it here for myself later or anyone else who runs into this. In addition to the edit in the main post, I was missing a check if the affine x and y were in the range [0, p-1]. Reference: https://neilmadden.blog/2017/05/17/so-how-do-you-validate-nist-ecdh-public-keys/

Here is the completed working code without bouncy castle:

                    String curve = (String) testGroupHeaders.get("curve");
                    String curve_num = curve.split("-")[1];
                    String specName = "secp" + curve_num + "r1";
                    JSONArray tests = (JSONArray) testGroupHeaders.get("tests");
                    JSONArray testResultsArray = new JSONArray();
                    for (int k = 0; k < tests.size(); k++){
                        JSONObject testResultsObject = new JSONObject();
                        JSONObject testsData = (JSONObject) tests.get(k);
                        long tcId = (long) testsData.get("tcId");
                        testResultsObject.put("tcId", tcId);
                        String qx = (String) testsData.get("qx");
                        String qy = (String) testsData.get("qy");
                        BigInteger x = new BigInteger(qx, 16);
                        BigInteger y = new BigInteger(qy, 16);
                        ECPoint ecPoint = new ECPoint(x, y);
                        if (ecPoint.equals(ECPoint.POINT_INFINITY)) {
                            testResultsObject.put("testPassed", false);
                        } else {
                            try {
                                AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
                                parameters.init(new ECGenParameterSpec(specName));
                                ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
                                EllipticCurve ellCurve = ecParameters.getCurve();
                                BigInteger a = ellCurve.getA();
                                BigInteger b = ellCurve.getB();
                                ECField ecField = ellCurve.getField();
                                BigInteger p = ((ECFieldFp) ecField).getP();
                                BigInteger rhs = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
                                BigInteger lhs = y.multiply(y).mod(p);

                                // Do this part to try and generate an exception
                                ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
                                KeyFactory keyFactory = KeyFactory.getInstance("EC");
                                ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(keySpec);

                                BigInteger affineX = publicKey.getW().getAffineX();
                                BigInteger affineY = publicKey.getW().getAffineY();
                                if (affineX.compareTo(BigInteger.ZERO) < 0 || affineX.compareTo(p) >= 0
                                        || affineY.compareTo(BigInteger.ZERO) < 0 || affineY.compareTo(p) >= 0) {
                                    testResultsObject.put("testPassed", false);
                                } else {
                                    if (rhs.equals(lhs)) {
                                        testResultsObject.put("testPassed", true);
                                    } else {
                                        testResultsObject.put("testPassed", false);
                                    }
                                }
                            } catch (Exception e) {
                                testResultsObject.put("testPassed", false);
                            }

Note that this is just POC code and could be cleaned up/optimized quite a bit. All of the .get calls are coming from the JSON requests that I am parsing, so how you get the String values/inputs may vary.

factor2
  • 155
  • 9