class Ed25519 {

  @SuppressWarnings("NarrowingCompoundAssignment")
  private static XYZ scalarMultWithBase(byte[] a) {
    byte[] e = new byte[2 * FIELD_LEN];
    for (int i = 0; i < FIELD_LEN; i++) {
      e[2 * i + 0] = (byte) (((a[i] & 0xff) >> 0) & 0xf);
      e[2 * i + 1] = (byte) (((a[i] & 0xff) >> 4) & 0xf);
    }
    // each e[i] is between 0 and 15
    // e[63] is between 0 and 7

    // Rewrite e in a way that each e[i] is in [-8, 8].
    // This can be done since a[63] is in [0, 7], the carry-over onto the most significant byte
    // a[63] can be at most 1.
    int carry = 0;
    for (int i = 0; i < e.length - 1; i++) {
      e[i] += carry;
      carry = e[i] + 8;
      carry >>= 4;
      e[i] -= carry << 4;
    }
    e[e.length - 1] += carry;

    PartialXYZT ret = new PartialXYZT(NEUTRAL);
    XYZT xyzt = new XYZT();
    // Although B_TABLE's i can be at most 31 (stores only 32 4bit multiples of B) and we have 64
    // 4bit values in e array, the below for loop adds cached values by iterating e by two in odd
    // indices. After the result, we can double the result point 4 times to shift the multiplication
    // scalar by 4 bits.
    for (int i = 1; i < e.length; i += 2) {
      CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
      select(t, i / 2, e[i]);
      add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
    }

    // Doubles the result 4 times to shift the multiplication scalar 4 bits to get the actual result
    // for the odd indices in e.
    XYZ xyz = new XYZ();
    doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
    doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
    doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
    doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));

    // Add multiples of B for even indices of e.
    for (int i = 0; i < e.length; i += 2) {
      CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
      select(t, i / 2, e[i]);
      add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
    }

    // This check is to protect against flaws, i.e. if there is a computation error through a
    // faulty CPU or if the implementation contains a bug.
    XYZ result = new XYZ(ret);
    if (!result.isOnCurve()) {
      throw new IllegalStateException("arithmetic error in scalar multiplication");
    }
    return result;
  }

}