@Test
  public void hashCodesAreEqualForEquivalentObjects() {
    // Arrange:
    final GroupElement g1 = MathUtils.getRandomGroupElement();
    final GroupElement g2 = MathUtils.toRepresentation(g1, GroupElement.Representation.P2);
    final GroupElement g3 = MathUtils.toRepresentation(g1, GroupElement.Representation.P1P1);
    final GroupElement g4 = MathUtils.getRandomGroupElement();

    // Assert
    Assert.assertThat(g2.hashCode(), IsEqual.equalTo(g1.hashCode()));
    Assert.assertThat(g3.hashCode(), IsEqual.equalTo(g1.hashCode()));
    Assert.assertThat(g1.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
    Assert.assertThat(g2.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
    Assert.assertThat(g3.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
  }
  @Test
  public void isOnCurveReturnsTrueForPointsOnTheCurve() {
    for (int i = 0; i < 100; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Assert:
      Assert.assertThat(g.isOnCurve(), IsEqual.equalTo(true));
    }
  }
  @Test
  public void scalarMultiplyBasePointWithZeroReturnsNeutralElement() {
    // Arrange:
    final GroupElement basePoint = ed25519.getB();

    // Act:
    final GroupElement g = basePoint.scalarMultiply(curve.getField().ZERO.toByteArray());

    // Assert:
    Assert.assertThat(curve.getZero(GroupElement.Representation.P3), IsEqual.equalTo(g));
  }
  @Test
  public void scalarMultiplyBasePointWithOneReturnsBasePoint() {
    // Arrange:
    final GroupElement basePoint = ed25519.getB();

    // Act:
    final GroupElement g = basePoint.scalarMultiply(curve.getField().ONE.toByteArray());

    // Assert:
    Assert.assertThat(basePoint, IsEqual.equalTo(g));
  }
  @Test
  public void isOnCurveReturnsFalseForPointsNotOnTheCurve() {
    for (int i = 0; i < 100; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();
      final GroupElement h =
          GroupElement.p2(curve, g.getX(), g.getY(), g.getZ().multiply(curve.getField().TWO));

      // Assert (can only fail for 5*Z^2=1):
      Assert.assertThat(h.isOnCurve(), IsEqual.equalTo(false));
    }
  }
  @Test
  public void addingNeutralGroupElementDoesNotChangeGroupElement() {
    final GroupElement neutral =
        GroupElement.p3(
            curve,
            curve.getField().ZERO,
            curve.getField().ONE,
            curve.getField().ONE,
            curve.getField().ZERO);
    for (int i = 0; i < 1000; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h1 = g.add(neutral.toCached());
      final GroupElement h2 = neutral.add(g.toCached());

      // Assert:
      Assert.assertThat(g, IsEqual.equalTo(h1));
      Assert.assertThat(g, IsEqual.equalTo(h2));
    }
  }
  @Test
  public void dblReturnsExpectedResult() {
    for (int i = 0; i < 1000; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h1 = g.dbl();
      final GroupElement h2 = MathUtils.doubleGroupElement(g);

      // Assert:
      Assert.assertThat(h2, IsEqual.equalTo(h1));
    }
  }
  @Test
  public void dblPrecomputedTableContainsExpectedGroupElements() {
    // Arrange:
    GroupElement g = ed25519.getB();
    GroupElement h = MathUtils.addGroupElements(g, g);

    // Act + Assert:
    for (int i = 0; i < 8; i++) {
      Assert.assertThat(
          MathUtils.toRepresentation(g, GroupElement.Representation.PRECOMP),
          IsEqual.equalTo(ed25519.getB().dblPrecmp[i]));
      g = MathUtils.addGroupElements(g, h);
    }
  }
  @Test
  public void subReturnsExpectedResult() {
    for (int i = 0; i < 1000; i++) {
      // Arrange:
      final GroupElement g1 = MathUtils.getRandomGroupElement();
      final GroupElement g2 = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h1 = g1.sub(g2.toCached());
      final GroupElement h2 = MathUtils.addGroupElements(g1, MathUtils.negateGroupElement(g2));

      // Assert:
      Assert.assertThat(h2, IsEqual.equalTo(h1));
    }
  }
  // This test is slow (~6s) due to math utils using an inferior algorithm to calculate the result.
  @Test
  public void scalarMultiplyBasePointReturnsExpectedResult() {
    for (int i = 0; i < 100; i++) {
      // Arrange:
      final GroupElement basePoint = ed25519.getB();
      final FieldElement f = MathUtils.getRandomFieldElement();

      // Act:
      final GroupElement g = basePoint.scalarMultiply(f.toByteArray());
      final GroupElement h = MathUtils.scalarMultiplyGroupElement(basePoint, f);

      // Assert:
      Assert.assertThat(g, IsEqual.equalTo(h));
    }
  }
  @Test
  public void constructorUsingByteArrayReturnsExpectedResult() {
    for (int i = 0; i < 100; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();
      final byte[] bytes = g.toByteArray();

      // Act:
      final GroupElement h1 = new GroupElement(curve, bytes);
      final GroupElement h2 = MathUtils.toGroupElement(bytes);

      // Assert:
      Assert.assertThat(h1, IsEqual.equalTo(h2));
    }
  }
  @Test
  public void toByteArrayReturnsExpectedResult() {
    for (int i = 0; i < 100; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final byte[] gBytes = g.toByteArray();
      final byte[] bytes = MathUtils.toByteArray(MathUtils.toBigInteger(g.getY()));
      if (MathUtils.toBigInteger(g.getX()).mod(new BigInteger("2")).equals(BigInteger.ONE)) {
        bytes[31] |= 0x80;
      }

      // Assert:
      Assert.assertThat(Arrays.equals(gBytes, bytes), IsEqual.equalTo(true));
    }
  }
  // This test is slow (~6s) due to math utils using an inferior algorithm to calculate the result.
  @Test
  public void doubleScalarMultiplyVariableTimeReturnsExpectedResult() {
    for (int i = 0; i < 50; i++) {
      // Arrange:
      final GroupElement basePoint = ed25519.getB();
      final GroupElement g = MathUtils.getRandomGroupElement();
      g.precompute(false);
      final FieldElement f1 = MathUtils.getRandomFieldElement();
      final FieldElement f2 = MathUtils.getRandomFieldElement();

      // Act:
      final GroupElement h1 =
          basePoint.doubleScalarMultiplyVariableTime(g, f2.toByteArray(), f1.toByteArray());
      final GroupElement h2 = MathUtils.doubleScalarMultiplyGroupElements(basePoint, f1, g, f2);

      // Assert:
      Assert.assertThat(h1, IsEqual.equalTo(h2));
    }
  }
  @Test
  public void precomputedTableContainsExpectedGroupElements() {
    // Arrange:
    GroupElement g = ed25519.getB();

    // Act + Assert:
    for (int i = 0; i < 32; i++) {
      GroupElement h = g;
      for (int j = 0; j < 8; j++) {
        Assert.assertThat(
            MathUtils.toRepresentation(h, GroupElement.Representation.PRECOMP),
            IsEqual.equalTo(ed25519.getB().precmp[i][j]));
        h = MathUtils.addGroupElements(h, g);
      }
      for (int k = 0; k < 8; k++) {
        g = MathUtils.addGroupElements(g, g);
      }
    }
  }
  @Test
  public void equalsOnlyReturnsTrueForEquivalentObjects() {
    // Arrange:
    final GroupElement g1 = MathUtils.getRandomGroupElement();
    final GroupElement g2 = MathUtils.toRepresentation(g1, GroupElement.Representation.P2);
    final GroupElement g3 = MathUtils.toRepresentation(g1, GroupElement.Representation.CACHED);
    final GroupElement g4 = MathUtils.toRepresentation(g1, GroupElement.Representation.P1P1);
    final GroupElement g5 = MathUtils.getRandomGroupElement();

    // Assert
    Assert.assertThat(g2, IsEqual.equalTo(g1));
    Assert.assertThat(g3, IsEqual.equalTo(g1));
    Assert.assertThat(g1, IsEqual.equalTo(g4));
    Assert.assertThat(g1, IsNot.not(IsEqual.equalTo(g5)));
    Assert.assertThat(g2, IsNot.not(IsEqual.equalTo(g5)));
    Assert.assertThat(g3, IsNot.not(IsEqual.equalTo(g5)));
    Assert.assertThat(g5, IsNot.not(IsEqual.equalTo(g4)));
  }
  @Test
  public void toP3ReturnsExpectedResultIfGroupElementHasP3Representation() {
    for (int i = 0; i < 10; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h = g.toP3();

      // Assert:
      Assert.assertThat(h, IsEqual.equalTo(g));
      Assert.assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P3));
      Assert.assertThat(h, IsEqual.equalTo(g));
      Assert.assertThat(h.getX(), IsEqual.equalTo(g.getX()));
      Assert.assertThat(h.getY(), IsEqual.equalTo(g.getY()));
      Assert.assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
      Assert.assertThat(h.getT(), IsEqual.equalTo(g.getT()));
    }
  }
  @Test
  public void toCachedReturnsExpectedResultIfGroupElementHasP3Representation() {
    for (int i = 0; i < 10; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h1 = g.toCached();
      final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.CACHED);

      // Assert:
      Assert.assertThat(h1, IsEqual.equalTo(h2));
      Assert.assertThat(
          h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.CACHED));
      Assert.assertThat(h1, IsEqual.equalTo(g));
      Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getY().add(g.getX())));
      Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY().subtract(g.getX())));
      Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
      Assert.assertThat(h1.getT(), IsEqual.equalTo(g.getT().multiply(curve.get2D())));
    }
  }
  @Test
  public void toP2ReturnsExpectedResultIfGroupElementHasP3Representation() {
    for (int i = 0; i < 10; i++) {
      // Arrange:
      final GroupElement g = MathUtils.getRandomGroupElement();

      // Act:
      final GroupElement h1 = g.toP2();
      final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.P2);

      // Assert:
      Assert.assertThat(h1, IsEqual.equalTo(h2));
      Assert.assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
      Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getX()));
      Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY()));
      Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
      Assert.assertThat(h1.getT(), IsEqual.equalTo(null));
    }
  }