content: /*
   * Copyright 2019 Web3 Labs Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
   * the License. You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
   * specific language governing permissions and limitations under the License.
   */
  /*
   * Copyright 2024 Ant Group
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alipay.antchain.bridge.commons.utils.crypto.secp256k1;
  
  import java.math.BigInteger;
  import java.security.SignatureException;
  import java.util.Arrays;
  
  import cn.hutool.core.util.ByteUtil;
  import cn.hutool.core.util.HexUtil;
  import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
  import org.bouncycastle.asn1.x9.X9ECParameters;
  import org.bouncycastle.asn1.x9.X9IntegerConverter;
  import org.bouncycastle.crypto.ec.CustomNamedCurves;
  import org.bouncycastle.crypto.params.ECDomainParameters;
  import org.bouncycastle.math.ec.ECAlgorithms;
  import org.bouncycastle.math.ec.ECPoint;
  import org.bouncycastle.math.ec.FixedPointCombMultiplier;
  import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
  
  public class Sign {
  
      public static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
      public static final int CHAIN_ID_INC = 35;
      public static final int LOWER_REAL_V = 27;
      // The v signature parameter starts at 37 because 1 is the first valid chainId so:
      // chainId >= 1 implies that 2 * chainId + CHAIN_ID_INC >= 37.
      // https://eips.ethereum.org/EIPS/eip-155
      public static final int REPLAY_PROTECTED_V_MIN = 37;
      static final ECDomainParameters CURVE =
              new ECDomainParameters(
                      CURVE_PARAMS.getCurve(),
                      CURVE_PARAMS.getG(),
                      CURVE_PARAMS.getN(),
                      CURVE_PARAMS.getH());
      static final BigInteger HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
  
      public static String getAddress(BigInteger publicKey) {
          String publicKeyNoPrefix = publicKey.toString(16);
  
          if (publicKeyNoPrefix.length() < 128) {
              publicKeyNoPrefix =
                      zeros(128 - publicKeyNoPrefix.length())
                              + publicKeyNoPrefix;
          }
          byte[] hash = HashAlgoEnum.KECCAK_256.hash(HexUtil.decodeHex(publicKeyNoPrefix));
          return HexUtil.encodeHexStr(hash).substring(24); // right most 160 bits
      }
  
      public static String zeros(int n) {
          return repeat('0', n);
      }
  
      public static String repeat(char value, int n) {
          return new String(new char[n]).replace("\0", String.valueOf(value));
      }
  
      public static SignatureData signMessage(byte[] message, ECKeyPair keyPair) {
          return signMessage(message, keyPair, true);
      }
  
      public static SignatureData signMessage(byte[] message, ECKeyPair keyPair, boolean needToHash) {
          BigInteger publicKey = keyPair.getPublicKey();
          byte[] messageHash;
          if (needToHash) {
              messageHash = HashAlgoEnum.KECCAK_256.hash(message);
          } else {
              messageHash = message;
          }
  
          ECDSASignature sig = keyPair.sign(messageHash);
  
          return createSignatureData(sig, publicKey, messageHash);
      }
  
      /**
       * Signature without EIP-155 (Simple replay attack protection)
       * https://eips.ethereum.org/EIPS/eip-155 To add EIP-155 call
       * TransactionEncoder.createEip155SignatureData after that.
       */
      public static SignatureData createSignatureData(
              ECDSASignature sig, BigInteger publicKey, byte[] messageHash) {
          // Now we have to work backwards to figure out the recId needed to recover the signature.
          int recId = -1;
          for (int i = 0; i < 4; i++) {
              BigInteger k = recoverFromSignature(i, sig, messageHash);
              if (k != null && k.equals(publicKey)) {
                  recId = i;
                  break;
              }
          }
          if (recId == -1) {
              throw new RuntimeException(
                      "Could not construct a recoverable key. Are your credentials valid?");
          }
  
          int headerByte = recId + 27;
  
          // 1 header + 32 bytes for R + 32 bytes for S
          byte[] v = new byte[] {(byte) headerByte};
          byte[] r = toBytesPadded(sig.r, 32);
          byte[] s = toBytesPadded(sig.s, 32);
  
          return new SignatureData(v, r, s);
      }
  
      public static byte[] toBytesPadded(BigInteger value, int length) {
          byte[] result = new byte[length];
          byte[] bytes = value.toByteArray();
  
          int bytesLength;
          int srcOffset;
          if (bytes[0] == 0) {
              bytesLength = bytes.length - 1;
              srcOffset = 1;
          } else {
              bytesLength = bytes.length;
              srcOffset = 0;
          }
  
          if (bytesLength > length) {
              throw new RuntimeException("Input is too large to put in byte array of size " + length);
          }
  
          int destOffset = length - bytesLength;
          System.arraycopy(bytes, srcOffset, result, destOffset, bytesLength);
          return result;
      }
  
      public static void verifyPrecondition(boolean assertionResult, String errorMessage) {
          if (!assertionResult) {
              throw new RuntimeException(errorMessage);
          }
      }
  
      /**
       * Given the components of a signature and a selector value, recover and return the public key
       * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.
       *
       * <p>The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the
       * correct one. Because the key recovery operation yields multiple potential keys, the correct
       * key must either be stored alongside the signature, or you must be willing to try each recId
       * in turn until you find one that outputs the key you are expecting.
       *
       * <p>If this method returns null it means recovery was not possible and recId should be
       * iterated.
       *
       * <p>Given the above two points, a correct usage of this method is inside a for loop from 0 to
       * 3, and if the output is null OR a key that is not the one you expect, you try again with the
       * next recId.
       *
       * @param recId Which possible key to recover.
       * @param sig the R and S components of the signature, wrapped.
       * @param message Hash of the data that was signed.
       * @return An ECKey containing only the public part, or null if recovery wasn't possible.
       */
      public static BigInteger recoverFromSignature(int recId, ECDSASignature sig, byte[] message) {
          verifyPrecondition(recId >= 0 && recId <= 3, "recId must be in the range of [0, 3]");
          verifyPrecondition(sig.r.signum() >= 0, "r must be positive");
          verifyPrecondition(sig.s.signum() >= 0, "s must be positive");
          verifyPrecondition(message != null, "message cannot be null");
  
          // 1.0 For j from 0 to h   (h == recId here and the loop is outside this function)
          //   1.1 Let x = r + jn
          BigInteger n = CURVE.getN(); // Curve order.
          BigInteger i = BigInteger.valueOf((long) recId / 2);
          BigInteger x = sig.r.add(i.multiply(n));
          //   1.2. Convert the integer x to an octet string X of length mlen using the conversion
          //        routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
          //   1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R
          //        using the conversion routine specified in Section 2.3.4. If this conversion
          //        routine outputs "invalid", then do another iteration of Step 1.
          //
          // More concisely, what these points mean is to use X as a compressed public key.
          BigInteger prime = SecP256K1Curve.q;
          if (x.compareTo(prime) >= 0) {
              // Cannot have point co-ordinates larger than this as everything takes place modulo Q.
              return null;
          }
          // Compressed keys require you to know an extra bit of data about the y-coord as there are
          // two possibilities. So it's encoded in the recId.
          ECPoint R = decompressKey(x, (recId & 1) == 1);
          //   1.4. If nR != point at infinity, then do another iteration of Step 1 (callers
          //        responsibility).
          if (!R.multiply(n).isInfinity()) {
              return null;
          }
          //   1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
          BigInteger e = new BigInteger(1, message);
          //   1.6. For k from 1 to 2 do the following.   (loop is outside this function via
          //        iterating recId)
          //   1.6.1. Compute a candidate public key as:
          //               Q = mi(r) * (sR - eG)
          //
          // Where mi(x) is the modular multiplicative inverse. We transform this into the following:
          //               Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
          // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n).
          // In the above equation ** is point multiplication and + is point addition (the EC group
          // operator).
          //
          // We can find the additive inverse by subtracting e from zero then taking the mod. For
          // example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and
          // -3 mod 11 = 8.
          BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
          BigInteger rInv = sig.r.modInverse(n);
          BigInteger srInv = rInv.multiply(sig.s).mod(n);
          BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
          ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv);
  
          byte[] qBytes = q.getEncoded(false);
          // We remove the prefix
          return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
      }
  
      /** Decompress a compressed public key (x co-ord and low-bit of y-coord). */
      private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
          X9IntegerConverter x9 = new X9IntegerConverter();
          byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve()));
          compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
          return CURVE.getCurve().decodePoint(compEnc);
      }
  
      /**
       * Given an arbitrary message hash and an Ethereum message signature encoded in bytes, returns
       * the public key that was used to sign it. This can then be compared to the expected public key
       * to determine if the signature was correct.
       *
       * @param messageHash The message hash.
       * @param signatureData The message signature components
       * @return the public key used to sign the message
       * @throws SignatureException If the public key could not be recovered or if there was a
       *     signature format error.
       */
      public static BigInteger signedMessageHashToKey(byte[] messageHash, SignatureData signatureData)
              throws SignatureException {
  
          byte[] r = signatureData.getR();
          byte[] s = signatureData.getS();
          verifyPrecondition(r != null && r.length == 32, "r must be 32 bytes");
          verifyPrecondition(s != null && s.length == 32, "s must be 32 bytes");
  
          int header = signatureData.getV()[0] & 0xFF;
          // The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
          //                  0x1D = second key with even y, 0x1E = second key with odd y
          if (header < 27 || header > 34) {
              throw new SignatureException("Header byte out of range: " + header);
          }
  
          ECDSASignature sig =
                  new ECDSASignature(
                          new BigInteger(1, signatureData.getR()),
                          new BigInteger(1, signatureData.getS()));
  
          int recId = header - 27;
          BigInteger key = recoverFromSignature(recId, sig, messageHash);
          if (key == null) {
              throw new SignatureException("Could not recover public key from signature");
          }
          return key;
      }
  
      /**
       * Returns public key from the given private key.
       *
       * @param privKey the private key to derive the public key from
       * @return BigInteger encoded public key
       */
      public static BigInteger publicKeyFromPrivate(BigInteger privKey) {
          ECPoint point = publicPointFromPrivate(privKey);
  
          byte[] encoded = point.getEncoded(false);
          return new BigInteger(1, Arrays.copyOfRange(encoded, 1, encoded.length)); // remove prefix
      }
  
      /**
       * Returns public key point from the given private key.
       *
       * @param privKey the private key to derive the public key from
       * @return ECPoint public key
       */
      public static ECPoint publicPointFromPrivate(BigInteger privKey) {
          /*
           * TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group
           * order, but that could change in future versions.
           */
          if (privKey.bitLength() > CURVE.getN().bitLength()) {
              privKey = privKey.mod(CURVE.getN());
          }
          return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
      }
  
      /**
       * Returns public key point from the given curve.
       *
       * @param bits representing the point on the curve
       * @return BigInteger encoded public key
       */
      public static BigInteger publicFromPoint(byte[] bits) {
          return new BigInteger(1, Arrays.copyOfRange(bits, 1, bits.length)); // remove prefix
      }
  
      public static class SignatureData {
          private final byte[] v;
          private final byte[] r;
          private final byte[] s;
  
          public SignatureData(byte v, byte[] r, byte[] s) {
              this(new byte[] {v}, r, s);
          }
  
          public SignatureData(byte[] v, byte[] r, byte[] s) {
              this.v = v;
              this.r = r;
              this.s = s;
          }
  
          public byte[] getV() {
              return v;
          }
  
          public byte[] getR() {
              return r;
          }
  
          public byte[] getS() {
              return s;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) {
                  return true;
              }
              if (o == null || getClass() != o.getClass()) {
                  return false;
              }
  
              SignatureData that = (SignatureData) o;
  
              if (!Arrays.equals(v, that.v)) {
                  return false;
              }
              if (!Arrays.equals(r, that.r)) {
                  return false;
              }
              return Arrays.equals(s, that.s);
          }
  
          @Override
          public int hashCode() {
              int result = Arrays.hashCode(v);
              result = 31 * result + Arrays.hashCode(r);
              result = 31 * result + Arrays.hashCode(s);
              return result;
          }
  
          public String toEthereumFormatHex() {
              return HexUtil.encodeHexStr(r) + HexUtil.encodeHexStr(s) + HexUtil.encodeHexStr(new byte[]{(byte) (v[0] - 27)});
          }
  
          public byte[] toEthereumFormat() {
              byte[] rawSig = new byte[65];
              rawSig[64] = ByteUtil.intToByte(v[0] - 27);
              System.arraycopy(r, 0, rawSig, 0, 32);
              System.arraycopy(s, 0, rawSig, 32, 32);
              return rawSig;
          }
      }
  }
  