private static byte[] generateChecksum(final byte[] input) {
    // step 1: sha3 hash of (input
    final byte[] sha3StepThreeHash = Hashes.sha3_256(input);

    // step 2: get the first X bytes of (1)
    return Arrays.copyOfRange(sha3StepThreeHash, 0, NUM_CHECKSUM_BYTES);
  }
  @Override
  public Signature sign(final byte[] data) {
    if (!this.getKeyPair().hasPrivateKey()) {
      throw new CryptoException("cannot sign without private key");
    }

    // Hash the private key to improve randomness.
    final byte[] hash =
        Hashes.sha3_512(ArrayUtils.toByteArray(this.getKeyPair().getPrivateKey().getRaw(), 32));

    // r = H(hash_b,...,hash_2b-1, data) where b=256.
    final Ed25519EncodedFieldElement r =
        new Ed25519EncodedFieldElement(
            Hashes.sha3_512(
                Arrays.copyOfRange(
                    hash, 32, 64), // only include the last 32 bytes of the private key hash
                data));

    // Reduce size of r since we are calculating mod group order anyway
    final Ed25519EncodedFieldElement rModQ = r.modQ();

    // R = rModQ * base point.
    final Ed25519GroupElement R = Ed25519Group.BASE_POINT.scalarMultiply(rModQ);
    final Ed25519EncodedGroupElement encodedR = R.encode();

    // S = (r + H(encodedR, encodedA, data) * a) mod group order where
    // encodedR and encodedA are the little endian encodings of the group element R and the public
    // key A and
    // a is the lower 32 bytes of hash after clamping.
    final Ed25519EncodedFieldElement h =
        new Ed25519EncodedFieldElement(
            Hashes.sha3_512(encodedR.getRaw(), this.getKeyPair().getPublicKey().getRaw(), data));
    final Ed25519EncodedFieldElement hModQ = h.modQ();
    final Ed25519EncodedFieldElement encodedS =
        hModQ.multiplyAndAddModQ(
            Ed25519Utils.prepareForScalarMultiply(this.getKeyPair().getPrivateKey()), rModQ);

    // Signature is (encodedR, encodedS)
    final Signature signature = new Signature(encodedR.getRaw(), encodedS.getRaw());
    if (!this.isCanonicalSignature(signature)) {
      throw new CryptoException("Generated signature is not canonical");
    }

    return signature;
  }
  private static String generateEncoded(final byte version, final byte[] publicKey) {
    // step 1: sha3 hash of the public key
    final byte[] sha3PublicKeyHash = Hashes.sha3_256(publicKey);

    // step 2: ripemd160 hash of (1)
    final byte[] ripemd160StepOneHash = Hashes.ripemd160(sha3PublicKeyHash);

    // step 3: store version byte in front of (2)
    final byte[] versionPrefixedRipemd160Hash =
        ArrayUtils.concat(new byte[] {version}, ripemd160StepOneHash);

    // step 4: get the checksum of (3)
    final byte[] stepThreeChecksum = generateChecksum(versionPrefixedRipemd160Hash);

    // step 5: concatenate (3) and (4)
    final byte[] concatStepThreeAndStepSix =
        ArrayUtils.concat(versionPrefixedRipemd160Hash, stepThreeChecksum);

    // step 6: base32 encode (5)
    return Base32Encoder.getString(concatStepThreeAndStepSix);
  }
  @Override
  public boolean verify(final byte[] data, final Signature signature) {
    if (!this.isCanonicalSignature(signature)) {
      return false;
    }

    if (1
        == ArrayUtils.isEqualConstantTime(
            this.getKeyPair().getPublicKey().getRaw(), new byte[32])) {
      return false;
    }

    // h = H(encodedR, encodedA, data).
    final byte[] rawEncodedR = signature.getBinaryR();
    final byte[] rawEncodedA = this.getKeyPair().getPublicKey().getRaw();
    final Ed25519EncodedFieldElement h =
        new Ed25519EncodedFieldElement(Hashes.sha3_512(rawEncodedR, rawEncodedA, data));

    // hReduced = h mod group order
    final Ed25519EncodedFieldElement hModQ = h.modQ();

    Ed25519GroupElement A = this.getKeyPair().getPublicKey().getAsGroupElement();
    if (null == A) {
      // Must compute A.
      A = new Ed25519EncodedGroupElement(rawEncodedA).decode();
      A.precomputeForDoubleScalarMultiplication();
    }

    // R = encodedS * B - H(encodedR, encodedA, data) * A
    final Ed25519GroupElement calculatedR =
        Ed25519Group.BASE_POINT.doubleScalarMultiplyVariableTime(
            A, hModQ, new Ed25519EncodedFieldElement(signature.getBinaryS()));

    // Compare calculated R to given R.
    final byte[] encodedCalculatedR = calculatedR.encode().getRaw();
    final int result = ArrayUtils.isEqualConstantTime(encodedCalculatedR, rawEncodedR);
    return 1 == result;
  }