All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.klaytn.caver.utils.Utils Maven / Gradle / Ivy

There is a newer version: 1.12.2-android
Show newest version
/*
 * Copyright 2020 The caver-java Authors
 *
 * 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.klaytn.caver.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.klaytn.caver.wallet.keyring.SignatureData;
import org.bouncycastle.math.ec.ECPoint;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.utils.Numeric;
import org.web3j.utils.Strings;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.List;
import java.util.regex.Pattern;

public class Utils {
    public static final int LENGTH_ADDRESS_STRING = 40;
    public static final int LENGTH_PRIVATE_KEY_STRING = 64;
    public static final int LENGTH_PUBLIC_KEY_STRING_DECOMPRESSED = 128;

    public static final String DEFAULT_ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

    /**
     * Check if string has address format.
     * @param address An address string.
     * @return boolean
     */
    public static boolean isAddress(String address) {
        Pattern baseAddrPattern = Pattern.compile("^(0x)?[0-9a-f]{40}$", Pattern.CASE_INSENSITIVE);
        Pattern lowerCase = Pattern.compile("^(0x|0X)?[0-9a-f]{40}$");
        Pattern upperCase = Pattern.compile("^(0x|0X)?[0-9A-F]{40}$");

        //check if it has the basic requirements of an address.
        if(!baseAddrPattern.matcher(address).matches()) {
            return false;
        }

        //check if it's ALL lowercase or ALL upppercase
        if(lowerCase.matcher(address).matches() || upperCase.matcher(address).matches()) {
            return true;
        }

        //check checksum address
        return checkAddressChecksum(address);
    }

    /**
     * Check if address has valid checksum.
     * @param address An address
     * @return boolean
     */
    public static boolean checkAddressChecksum(String address) {
        address = address.replaceFirst("0X", "0x");
        return (Keys.toChecksumAddress(address).equals(Utils.addHexPrefix(address)));
    }

    /**
     * Check if string has PrivateKey format.
     * @param privateKey A key string.
     * @return boolean
     */
    public static boolean isValidPrivateKey(String privateKey) {
        String noHexPrefixKey = stripHexPrefix(privateKey);
        if(noHexPrefixKey.length() != LENGTH_PRIVATE_KEY_STRING || !isHex(privateKey)) {
            return false;
        }

        ECPoint point = Sign.publicPointFromPrivate(Numeric.toBigInt(privateKey));
        return !point.isInfinity() && point.isValid();
    }

    /**
     * Check if string has Klaytn wallet key format.
     * @param key A key string.
     * @return boolean
     */
    public static boolean isKlaytnWalletKey(String key) {
        //0x{private key}0x{type}0x{address in hex}
        //[0] = privateKey
        //[1] = type - must be "00"
        //[2] = address
        key = stripHexPrefix(key);

        if(key.length() != 110) {
            return false;
        }

        String[] arr = key.split("0x");

        if(!arr[1].equals("00")) {
            return false;
        }
        if(!Utils.isAddress(arr[2])) {
            return false;
        }
        if(!Utils.isValidPrivateKey(arr[0])) {
            return false;
        }

        return true;
    }

    /**
     * Check if the given public key is valid.
     * It also check both compressed and uncompressed key.
     * @param publicKey public key
     * @return valid or not
     */
    public static boolean isValidPublicKey(String publicKey) {
        String noPrefixPubKey = stripHexPrefix(publicKey);
        boolean result;

        if(noPrefixPubKey.length() == 130 && noPrefixPubKey.startsWith("04")) {
            noPrefixPubKey = noPrefixPubKey.substring(2);
        }

        if(noPrefixPubKey.length() != 66 && noPrefixPubKey.length() != 128) {
            return false;
        }

        //Compressed Format
        if(noPrefixPubKey.length() == 66) {
            if(!noPrefixPubKey.startsWith("02") && !noPrefixPubKey.startsWith("03")) {
                return false;
            }
            result = AccountKeyPublicUtils.validateXYPoint(publicKey);
        } else { // Decompressed Format
            String x = noPrefixPubKey.substring(0, 64);
            String y = noPrefixPubKey.substring(64);

            result = AccountKeyPublicUtils.validateXYPoint(x, y);
        }

        return result;
    }

    /**
     * Convert a compressed public key to an uncompressed format.
     * Given public key has already uncompressed format, it will return
     * @param publicKey public key string(uncompressed or compressed)
     * @return uncompressed public key string
     */
    public static String decompressPublicKey(String publicKey) {
        if(AccountKeyPublicUtils.isUncompressedPublicKey(publicKey)) {
            return addHexPrefix(publicKey);
        }

        if(AccountKeyPublicUtils.isCompressedPublicKey(publicKey)) {
            ECPoint ecPoint = AccountKeyPublicUtils.getECPoint(publicKey);
            String pointXY = Numeric.toHexStringWithPrefixZeroPadded(ecPoint.getAffineXCoord().toBigInteger(), 64) +
                    Numeric.toHexStringNoPrefixZeroPadded(ecPoint.getAffineYCoord().toBigInteger(), 64);
            return pointXY;
        }

        throw new RuntimeException("Invalid public key.");
    }

    /**
     * Convert an uncompressed public key to a compressed public key
     * Given public key has already compressed format, it will return.
     * @param publicKey public key string(uncompressed or compressed)
     * @return compressed public key
     */
    public static String compressPublicKey(String publicKey) {
        if(AccountKeyPublicUtils.isCompressedPublicKey(publicKey)){
            return publicKey;
        }

        if(AccountKeyPublicUtils.isUncompressedPublicKey(publicKey)) {
            String noPrefixKey = stripHexPrefix(publicKey);

            if(noPrefixKey.startsWith("04") && noPrefixKey.length() == 130) {
                noPrefixKey = noPrefixKey.substring(2);
            }

            BigInteger publicKeyBN = Numeric.toBigInt(noPrefixKey);

            String publicKeyX = noPrefixKey.substring(0, 64);
            String pubKeyYPrefix = publicKeyBN.testBit(0) ? "03" : "02";
            return addHexPrefix(pubKeyYPrefix + publicKeyX);
        }

        throw new RuntimeException("Invalid public key.");
    }

    /**
     * Hashing message with added prefix("\x19Klaytn Signed Message:\n").
     * @param message A message to hash.
     * @return String
     */
    public static String hashMessage(String message) {
        final String preamble = "\u0019Klaytn Signed Message:\n";

        byte[] messageArr = Utils.isHexStrict(message)? Numeric.hexStringToByteArray(message) : message.getBytes();
        byte[] preambleArr = preamble.concat(String.valueOf(messageArr.length)).getBytes();

        // klayMessage is concatenated array (preambleArr + messageArr)
        byte[] klayMessage = BytesUtils.concat(preambleArr, messageArr);
        byte[] result =  Hash.sha3(klayMessage);

        //return data after converting to hex string.
        return Numeric.toHexString(result);
    }

    /**
     * Parse a klaytn wallet key string.
     * @param key klaytn wallet key string
     * @return String array
     */
    public static String[] parseKlaytnWalletKey(String key) {
        if(!isKlaytnWalletKey(key)) {
            throw new IllegalArgumentException("Invalid Klaytn wallet key.");
        }

        //0x{private key}0x{type}0x{address in hex}
        //[0] = privateKey
        //[1] = type - must be "00"
        //[2] = address
        key = stripHexPrefix(key);
        String[] arr = key.split("0x");

        for(int i=0; i< arr.length; i++) {
            arr[i] = addHexPrefix(arr[i]);
        }

        return arr;
    }

    /**
     * Check if string has hex format.
     * @param input A hex string
     * @return boolean
     */
    public static boolean isHex(String input) {
        Pattern pattern = Pattern.compile("^(-0x|0x|0X)?[0-9A-Fa-f]*$");
        return pattern.matcher(input).matches();
    }

    /**
     * Check if string has hex format with "0x" prefix.
     * @param input A hex string
     * @return boolean
     */
    public static boolean isHexStrict(String input) {
        Pattern pattern = Pattern.compile("^(-)?(0x|0X)[0-9A-Fa-f]*$");
        return pattern.matcher(input).matches();
    }

    static boolean isHexPrefixed(String str) {
        return !Strings.isEmpty(str) && (str.startsWith("0x") || str.startsWith("0X"));
    }

    /**
     * Add hex prefix("0x").
     * @param str A hex string
     * @return String
     */
    public static String addHexPrefix(String str) {
        if(!isHexPrefixed(str)) {
            return "0x" + str;
        }
        return "0x" + str.substring(2);
    }

    /**
     * Remove hex prefix("0x").
     * @param str A hex string
     * @return String
     */
    public static String stripHexPrefix(String str) {
        if(isHexPrefixed(str)) {
            return str.substring(2);
        }

        return str;
    }

    /**
     * Converts amount to peb amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToPeb(String num, String unit) {
        return convertToPeb(new BigDecimal(num), KlayUnit.fromString(unit));
    }

    /**
     * Converts amount to peb amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToPeb(BigDecimal num, String unit) {
        return convertToPeb(num, KlayUnit.fromString(unit));
    }

    /**
     * Converts amount to peb amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToPeb(String num, KlayUnit unit) {
        return convertToPeb(new BigDecimal(num), unit);
    }

    /**
     * Converts amount to peb amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToPeb(BigDecimal num, KlayUnit unit) {
        return num.multiply(unit.getPebFactor()).toString();
    }

    /**
     * Converts peb amount to specific unit amount.
     * @param num The peb amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromPeb(String num, String unit) {
        return convertFromPeb(new BigDecimal(num), KlayUnit.fromString(unit));
    }

    /**
     * Converts peb amount to specific unit amount.
     * @param num The peb amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromPeb(BigDecimal num, String unit) {
        return convertFromPeb(num, KlayUnit.fromString(unit));
    }

    /**
     * Converts peb amount to specific unit amount.
     * @param num The peb amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromPeb(String num, KlayUnit unit) {
        return convertFromPeb(new BigDecimal(num), unit).toString();
    }

    /**
     * Converts peb amount to specific unit amount.
     * @param num The peb amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromPeb(BigDecimal num, KlayUnit unit) {
        return num.divide(unit.getPebFactor()).toString();
    }

    /**
     * Converts amount to kei amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToKei(String num, String unit) {
        return convertToKei(new BigDecimal(num), KlayUnit.fromString(unit));
    }

    /**
     * Converts amount to kei amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToKei(BigDecimal num, String unit) {
        return convertToKei(num, KlayUnit.fromString(unit));
    }

    /**
     * Converts amount to kei amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToKei(String num, KlayUnit unit) {
        return convertToKei(new BigDecimal(num), unit);
    }

    /**
     * Converts amount to kei amount.
     * @param num The amount to convert.
     * @param unit Th unit to convert from.
     * @return String
     */
    public static String convertToKei(BigDecimal num, KlayUnit unit) {
        return num.multiply(unit.getPebFactor()).toString();
    }

    /**
     * Converts kei amount to specific unit amount.
     * @param num The kei amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromKei(String num, String unit) {
        return convertFromKei(new BigDecimal(num), KlayUnit.fromString(unit));
    }

    /**
     * Converts kei amount to specific unit amount.
     * @param num The kei amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromKei(BigDecimal num, String unit) {
        return convertFromKei(num, KlayUnit.fromString(unit));
    }

    /**
     * Converts kei amount to specific unit amount.
     * @param num The kei amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromKei(String num, KlayUnit unit) {
        return convertFromKei(new BigDecimal(num), unit).toString();
    }

    /**
     * Converts kei amount to specific unit amount.
     * @param num The kei amount
     * @param unit The unit to convert to
     * @return String
     */
    public static String convertFromKei(BigDecimal num, KlayUnit unit) {
        return num.divide(unit.getPebFactor()).toString();
    }

    /**
     * Recovers the public key that was used to sign the given data.

* This method hashes the message with klaytn prefix automatically. *

Example :
     * {@code
     * String message = "Some Message";
     * SignatureData signatureData = new SignatureData(
     *     "0x1b",
     *     "0x8213e560e7bbe1f2e28fd69cbbb41c9108b84c98cd7c2c88d3c8e3549fd6ab10",
     *     "0x3ca40c9e20c1525348d734a6724db152b9244bff6e0ff0c2b811d61d8f874f00"
     * );
     *
     * String publicKey = caver.utils.recoverPublicKey(message, signatureData);
     * }
     * 
* @param message The raw message string. * @param signatureData The {@link SignatureData} to recover public key. * @return String * @throws SignatureException */ public static String recoverPublicKey(String message, SignatureData signatureData) throws SignatureException { return recoverPublicKey(message, signatureData, false); } /** * Recovers the public key that was used to sign the given data. *
Example :
     * {@code
     * String message = "Some Message";
     * SignatureData signatureData = new SignatureData(
     *     "0x1b",
     *     "0x8213e560e7bbe1f2e28fd69cbbb41c9108b84c98cd7c2c88d3c8e3549fd6ab10",
     *     "0x3ca40c9e20c1525348d734a6724db152b9244bff6e0ff0c2b811d61d8f874f00"
     * );
     *
     * String publicKey = caver.utils.recoverPublicKey(message, signatureData, false);
     * }
     * 
* @param message The raw message string. If this message is already hashed with Klaytn prefix, the third parameter should be true. * @param signatureData The {@link SignatureData} to recover public key. * @param isHashed If true, the message param already hashed by appending a Klaytn sign prefix to the message. * @return String * @throws SignatureException */ public static String recoverPublicKey(String message, SignatureData signatureData, boolean isHashed) throws SignatureException { String messageHash = message; if(!isHashed) { messageHash = Utils.hashMessage(message); } byte[] r = Numeric.hexStringToByteArray(signatureData.getR()); byte[] s = Numeric.hexStringToByteArray(signatureData.getS()); if(r == null || r.length != 32) { throw new IllegalArgumentException("r must be 32 bytes"); } if(s == null || s.length != 32) { throw new IllegalArgumentException("s must be 32 bytes"); } int recId = signatureData.getRecoverId(); ECDSASignature sig = new ECDSASignature( new BigInteger(1, r), new BigInteger(1, s)); BigInteger key = Sign.recoverFromSignature(recId, sig, Numeric.hexStringToByteArray(messageHash)); if (key == null) { throw new SignatureException("Could not recover public key from signature"); } return Numeric.toHexStringWithPrefixZeroPadded(key, LENGTH_PUBLIC_KEY_STRING_DECOMPRESSED); } /** * Returns an address which is derived by a public key(handles both compressed format and decompressed format).

* This function simply converts the public key string into address from by hashing it.

* It has nothing to do with the actual account in the Klaytn. *

Example :
     * {@code
     * String address = caver.utils.publicKeyToAddress("0x{public key}");
     * }
     * 
* @param publicKey The public key string to get the address. * @return String */ public static String publicKeyToAddress(String publicKey) { publicKey = Utils.decompressPublicKey(publicKey); return addHexPrefix(Keys.getAddress(publicKey)); } /** * Recovers the address that was used to sign the given data. * This function automatically creates a message hash by appending a Klaytn sign prefix to the message. * @param message A plain message when using signed. * @param signatureData The signature values in KlaySignatureData * @return String * @throws SignatureException It throws when recover operation has failed. */ public static String recover(String message, SignatureData signatureData) throws SignatureException { return recover(message, signatureData, false); } /** * Recovers the address that was used to sign the given data. * @param message A plain message or hashed message. * @param signatureData The signature values in KlaySignatureData * @param isHashed If true, the message param already hashed by appending a Klaytn sign prefix to the message. * @return String * @throws SignatureException It throws when recover operation has failed. */ public static String recover(String message, SignatureData signatureData, boolean isHashed) throws SignatureException { String publicKey = recoverPublicKey(message, signatureData, isHashed); return publicKeyToAddress(publicKey); } /** * Decodes a raw signature data that composed of R(32 byte) + S(32 byte) + V(1byte). * @param rawSig A signature data to decode. It composed of R(32 byte) + S(32 byte) + V(1byte). * @return SignatureData */ public static SignatureData decodeSignature(String rawSig) { String noPrefixSigData = Utils.stripHexPrefix(rawSig); if(noPrefixSigData.length() != 130) { throw new RuntimeException("Invalid signature: The length of raw signature must be 65 byte."); } String r = noPrefixSigData.substring(0, 64); String s = noPrefixSigData.substring(64, 128); String v = noPrefixSigData.substring(128); return new SignatureData(v, r, s); } /** * Check if string has hex number format. * @param input A hex number format string * @return boolean */ public static boolean isNumber(String input) { try { Numeric.toBigInt(input); } catch (NumberFormatException e) { return false; } return true; } /** * Check if SignatureData instance has empty signature. * @param signatureData A SignatureData instance. * @return boolean */ public static boolean isEmptySig(SignatureData signatureData) { SignatureData emptySig = SignatureData.getEmptySignature(); return emptySig.equals(signatureData); } /** * Check if elements in SignatureData list has empty signature * @param signatureDataList A List of SignatureData * @return boolean */ public static boolean isEmptySig(List signatureDataList) { SignatureData emptySig = SignatureData.getEmptySignature(); //if stream is empty, allMatch() returns always true. boolean isMatched = signatureDataList.stream().allMatch(emptySig::equals); return isMatched; } /** * Generate random bytes * @param size A size to generate random byte * @return byte array */ public static byte[] generateRandomBytes(int size) { byte[] bytes = new byte[size]; SecureRandomUtils.secureRandom().nextBytes(bytes); return bytes; } public static String printString(Object o){ ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(); try { return mapper.writeValueAsString(o); } catch(JsonProcessingException e) { throw new RuntimeException("Cannot covert to a String.", e); } } public enum KlayUnit { peb("peb", 0), kpeb("kpeb", 3), Mpeb("Mpeb", 6), Gpeb("Gpeb", 9), ston("ston", 9), uKLAY("uKLAY", 12), mKLAY("mKLAY", 15), KLAY("KLAY", 18), kKLAY("kKLAY", 21), MKLAY("MKLAY", 24), GKLAY("GKLAY", 27), TKLAY("TKLAY", 30), kei("kei", 0), Gkei("Gkei", 9), KAIA("KAIA", 18); private String unit; private BigDecimal pebFactor; KlayUnit(String unit, int factor) { this.unit = unit; this.pebFactor = BigDecimal.TEN.pow(factor); } public BigDecimal getPebFactor() { return pebFactor; } @Override public String toString() { return unit; } public static KlayUnit fromString(String unitName) { if (unitName != null) { for (KlayUnit unit : KlayUnit.values()) { if (unitName.equals(unit.unit)) { return unit; } } } return KlayUnit.valueOf(unitName); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy