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

com.hedera.node.app.spi.key.KeyUtils Maven / Gradle / Ivy

There is a newer version: 0.54.1
Show newest version
/*
 * Copyright (C) 2023-2024 Hedera Hashgraph, LLC
 *
 * 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.hedera.node.app.spi.key;

import com.hedera.hapi.node.base.ContractID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.Key.KeyOneOfType;
import com.hedera.hapi.node.base.KeyList;
import com.hedera.hapi.node.base.ThresholdKey;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.Nullable;

/**
 * Utility class for working with keys. This validates if the key is empty and valid.
 */
// NOTE: This class is not in the right place. But is needed by several modules.
// !!!!!!!!!!🔥🔥🔥 It should be moved once we find where to keep it. 🔥🔥🔥!!!!!!!!!!!
public class KeyUtils {

    public static final Key IMMUTABILITY_SENTINEL_KEY =
            Key.newBuilder().keyList(KeyList.DEFAULT).build();

    public static final int EVM_ADDRESS_BYTE_LENGTH = 20;
    public static final int ED25519_BYTE_LENGTH = 32;
    private static final byte ODD_PARITY = (byte) 0x03;
    private static final byte EVEN_PARITY = (byte) 0x02;
    public static final int ECDSA_SECP256K1_COMPRESSED_KEY_LENGTH = 33;

    private KeyUtils() {
        throw new UnsupportedOperationException("Utility Class");
    }

    /**
     * Checks if the given key is empty.
     * For a KeyList type checks if the list is empty.
     * For a ThresholdKey type checks if the list is empty.
     * For an Ed25519 or EcdsaSecp256k1 type checks if there are zero bytes.
     * @param pbjKey the key to check
     * @return true if the key is empty, false otherwise
     */
    public static boolean isEmpty(@Nullable final Key pbjKey) {
        return isEmptyInternal(pbjKey, false);
    }

    /**
     * Checks if the given key is empty.
     * For a KeyList type checks if the list is empty.
     * For a ThresholdKey type checks if the list is empty.
     * For an Ed25519 or EcdsaSecp256k1 type checks if there are zero bytes.
     * @param pbjKey the key to check
     * @param honorImmutable if true, the key is NOT considered EMPTY if it is the IMMUTABILITY_SENTINEL_KEY
     * @return true if the key is empty, false otherwise
     */
    private static boolean isEmptyInternal(@Nullable final Key pbjKey, boolean honorImmutable) {
        if (pbjKey == null) {
            return true;
        }
        final var key = pbjKey.key();
        if (key == null || KeyOneOfType.UNSET.equals(key.kind())) {
            return true;
        }
        if (honorImmutable && IMMUTABILITY_SENTINEL_KEY.equals(pbjKey)) {
            return false;
        }
        if (pbjKey.hasKeyList()) {
            final var keyList = (KeyList) key.value();
            if (keyList.keys().isEmpty()) {
                return true;
            }
            for (final var k : keyList.keys()) {
                if (!isEmpty(k)) {
                    return false;
                }
            }
            return true;
        } else if (pbjKey.hasThresholdKey()) {
            final var thresholdKey = (ThresholdKey) key.value();
            if ((!thresholdKey.hasKeys() || thresholdKey.keys().keys().size() == 0)) {
                return true;
            }
            for (final var k : thresholdKey.keys().keys()) {
                if (!isEmpty(k)) {
                    return false;
                }
            }
            return true;
        } else if (pbjKey.hasEd25519()) {
            return ((Bytes) key.value()).length() == 0;
        } else if (pbjKey.hasEcdsaSecp256k1()) {
            return ((Bytes) key.value()).length() == 0;
        } else if (pbjKey.hasDelegatableContractId() || pbjKey.hasContractID()) {
            return ((ContractID) key.value()).contractNumOrElse(0L) == 0
                    && ((ContractID) key.value()).evmAddressOrElse(Bytes.EMPTY).length() == 0L;
        }
        // ECDSA_384 and RSA_3072 are not supported yet
        return true;
    }

    /**
     * Checks if the given key is valid. Based on the key type it checks the basic requirements
     * for the key type.
     * @param pbjKey the key to check
     * @return true if the key is valid, false otherwise
     */
    public static boolean isValid(@Nullable final Key pbjKey) {
        if (isEmpty(pbjKey)) {
            return false;
        }
        final var key = pbjKey.key();
        if (pbjKey.hasKeyList()) {
            for (Key keys : ((KeyList) key.value()).keys()) {
                if (!isValid(keys)) {
                    return false;
                }
            }
            return true;
        } else if (pbjKey.hasThresholdKey()) {
            final int length = ((ThresholdKey) key.value()).keys().keys().size();
            final int threshold = ((ThresholdKey) key.value()).threshold();
            boolean isKeyListValid = true;
            for (Key keys : ((ThresholdKey) key.value()).keys().keys()) {
                if (!isValid(keys)) {
                    isKeyListValid = false;
                    break;
                }
            }
            return (threshold >= 1 && threshold <= length && isKeyListValid);
        } else if (pbjKey.hasEd25519()) {
            return ((Bytes) key.value()).length() == ED25519_BYTE_LENGTH;
        } else if (pbjKey.hasEcdsaSecp256k1()) {
            final var ecKey = ((Bytes) key.value());
            return ecKey.length() == ECDSA_SECP256K1_COMPRESSED_KEY_LENGTH
                    && (ecKey.getByte(0) == EVEN_PARITY || ecKey.getByte(0) == ODD_PARITY);
        } else if (pbjKey.hasDelegatableContractId() || pbjKey.hasContractID()) {
            return ((ContractID) key.value()).contractNumOrElse(0L) > 0
                    || ((ContractID) key.value()).evmAddressOrElse(Bytes.EMPTY).length() == EVM_ADDRESS_BYTE_LENGTH;
        }
        // ECDSA_384 and RSA_3072 are not supported yet
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy