com.hedera.hashgraph.sdk.PrivateKey Maven / Gradle / Ivy
/*-
*
* Hedera Java SDK
*
* Copyright (C) 2020 - 2023 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.hashgraph.sdk;
import com.google.errorprone.annotations.Var;
import com.hedera.hashgraph.sdk.proto.SignedTransaction;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.sec.ECPrivateKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.math.ec.rfc8032.Ed25519;
import org.bouncycastle.util.encoders.Hex;
import javax.annotation.Nonnegative;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
/**
* A private key on the Hedera™ network.
*/
public abstract class PrivateKey extends Key {
/**
* The public key derived from the private key
*/
@Nullable
protected PublicKey publicKey = null; // Cache the derivation of the public key
/**
* Generates a new Ed25519 private key.
*
* @deprecated use {@link #generateED25519()} or {@link #generateECDSA()} instead
*
* @return the new Ed25519 private key.
*/
public static PrivateKey generate() {
return generateED25519();
}
/**
* Extract the new ED25519 private key.
*
* @return the new ED25519 private key
*/
public static PrivateKey generateED25519() {
return PrivateKeyED25519.generateInternal();
}
/**
* Extract the new ECDSA private key.
*
* @return the new ECDSA private key
*/
public static PrivateKey generateECDSA() {
return PrivateKeyECDSA.generateInternal();
}
/**
* Extract the ED25519 private key from a seed.
*
* @param seed the seed
* @return the ED25519 private key
*/
public static PrivateKey fromSeedED25519(byte[] seed) {
return PrivateKeyED25519.fromSeed(seed);
}
/**
* Extract the ECDSA private key from a seed.
*
* @param seed the seed
* @return the ECDSA private key
*/
public static PrivateKey fromSeedECDSAsecp256k1(byte[] seed) {
return PrivateKeyECDSA.fromSeed(seed);
}
/**
* @deprecated use {@link Mnemonic#toStandardEd25519PrivateKey(String, int)} ()} or {@link Mnemonic#toStandardECDSAsecp256k1PrivateKey(String, int)} (String, int)} instead
* This function uses incomplete and the key should not be used directly.
*
* Recover a private key from a generated mnemonic phrase and a passphrase.
*
* This is not compatible with the phrases generated by the Android and iOS wallets;
* use the no-passphrase version instead.
*
* @param mnemonic the mnemonic phrase which should be a 24 byte list of words.
* @param passphrase the passphrase used to protect the mnemonic (not used in the
* mobile wallets, use {@link #fromMnemonic(Mnemonic)} instead.)
* @return the recovered key; use {@link #derive(int)} to get a key for an account index (0
* for default account)
*/
@Deprecated
public static PrivateKey fromMnemonic(Mnemonic mnemonic, String passphrase) {
var seed = mnemonic.toSeed(passphrase);
@Var PrivateKey derivedKey = fromSeedED25519(seed);
// BIP-44 path with the Hedera Hbar coin-type (omitting key index)
// we pre-derive most of the path as the mobile wallets don't expose more than the index
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// https://github.com/satoshilabs/slips/blob/master/slip-0044.md
for (int index : new int[]{44, 3030, 0, 0}) {
derivedKey = derivedKey.derive(index);
}
return derivedKey;
}
/**
* @deprecated use {@link Mnemonic#toStandardEd25519PrivateKey(String, int)} ()} or {@link Mnemonic#toStandardECDSAsecp256k1PrivateKey(String, int)} (String, int)} instead
* Recover a private key from a mnemonic phrase compatible with the iOS and Android wallets.
*
* An overload of {@link #fromMnemonic(Mnemonic, String)} which uses an empty string for the
* passphrase.
*
* @param mnemonic the mnemonic phrase which should be a 24 byte list of words.
* @return the recovered key; use {@link #derive(int)} to get a key for an account index (0
* for default account)
*/
@Deprecated
public static PrivateKey fromMnemonic(Mnemonic mnemonic) {
return fromMnemonic(mnemonic, "");
}
/**
* Retrieve a private key from a string.
*
* @param privateKey string representing a private key
* @return the private key
*/
public static PrivateKey fromString(String privateKey) {
return fromBytes(Hex.decode(privateKey.startsWith("0x") ? privateKey.substring(2) : privateKey));
}
/**
* Retrieve a private key from a DER encoded string.
*
* @param privateKey DER encoded string representing a private key
* @return the private key
*/
public static PrivateKey fromStringDER(String privateKey) {
return fromBytesDER(Hex.decode(privateKey));
}
/**
* Retrieve a private key from an ED25519 encoded string.
*
* @param privateKey ED25519 encoded string representing a private key
* @return the private key
*/
public static PrivateKey fromStringED25519(String privateKey) {
return fromBytesED25519(Hex.decode(privateKey));
}
/**
* Retrieve a private key from an ECDSA encoded string.
*
* @param privateKey ECDSA encoded string representing a private key
* @return the private key
*/
public static PrivateKey fromStringECDSA(String privateKey) {
return fromBytesECDSA(Hex.decode(privateKey));
}
/**
* Retrieve a private key from a byte array.
*
* @param privateKey byte array representing a private key
* @return the private key
*/
public static PrivateKey fromBytes(byte[] privateKey) {
if ((privateKey.length == Ed25519.SECRET_KEY_SIZE)
|| (privateKey.length == Ed25519.SECRET_KEY_SIZE + Ed25519.PUBLIC_KEY_SIZE)) {
// If this is a 32 or 64 byte string, assume an Ed25519 private key
return new PrivateKeyED25519(Arrays.copyOfRange(privateKey, 0, Ed25519.SECRET_KEY_SIZE), null);
}
// Assume a DER-encoded private key descriptor
return fromBytesDER(privateKey);
}
/**
* Retrieve a private key from an ED25519 encoded byte array.
*
* @param privateKey ED25519 encoded byte array representing a private key
* @return the private key
*/
public static PrivateKey fromBytesED25519(byte[] privateKey) {
return PrivateKeyED25519.fromBytesInternal(privateKey);
}
/**
* Retrieve a private key from an ECDSA encoded byte array.
*
* @param privateKey ECDSA encoded byte array representing a private key
* @return the private key
*/
public static PrivateKey fromBytesECDSA(byte[] privateKey) {
return PrivateKeyECDSA.fromBytesInternal(privateKey);
}
/**
* Retrieve a private key from a DER encoded byte array.
*
* @param privateKey DER encoded byte array representing a private key
* @return the private key
*/
public static PrivateKey fromBytesDER(byte[] privateKey) {
try {
return fromPrivateKeyInfo(PrivateKeyInfo.getInstance(privateKey));
} catch (ClassCastException | IllegalArgumentException e) {
return PrivateKeyECDSA.fromECPrivateKeyInternal(ECPrivateKey.getInstance(privateKey));
}
}
/**
* Retrieve a private key from a private key info object.
*
* @param privateKeyInfo private key info object
* @return the private key
*/
private static PrivateKey fromPrivateKeyInfo(PrivateKeyInfo privateKeyInfo) {
if (privateKeyInfo.getPrivateKeyAlgorithm().equals(new AlgorithmIdentifier(ID_ED25519))) {
return PrivateKeyED25519.fromPrivateKeyInfoInternal(privateKeyInfo);
} else {
// assume ECDSA
return PrivateKeyECDSA.fromPrivateKeyInfoInternal(privateKeyInfo);
}
}
/**
* Parse a private key from a PEM encoded reader.
*
* This will read the first "PRIVATE KEY" section in the stream as an Ed25519 private key.
*
* @param pemFile The Reader containing the pem file
* @return {@link com.hedera.hashgraph.sdk.PrivateKey}
* @throws IOException if one occurred while reading.
* @throws BadKeyException if no "PRIVATE KEY" section was found or the key was not an Ed25519
* private key.
*/
public static PrivateKey readPem(Reader pemFile) throws IOException {
return readPem(pemFile, null);
}
/**
* Parse a private key from a PEM encoded stream. The key may be encrypted, e.g. if it was
* generated by OpenSSL.
*
* If password is not null or empty, this will read the first "ENCRYPTED PRIVATE KEY"
* section in the stream as a PKCS#8
* EncryptedPrivateKeyInfo structure
* and use that algorithm to decrypt the private key with the given password. Otherwise,
* it will read the first "PRIVATE KEY" section as DER-encoded Ed25519 private key.
*
* To generate an encrypted private key with OpenSSL, open a terminal and enter the following
* command:
*
* {@code openssl genpkey -algorithm ed25519 -aes-128-cbc > key.pem}
*
*
* Then enter your password of choice when prompted. When the command completes, your encrypted
* key will be saved as `key.pem` in the working directory of your terminal.
*
* @param pemFile the PEM encoded file
* @param password the password to decrypt the PEM file; if null or empty, no decryption is performed.
* @return {@link com.hedera.hashgraph.sdk.PrivateKey}
* @throws IOException if one occurred while reading the PEM file
* @throws BadKeyException if no "ENCRYPTED PRIVATE KEY" or "PRIVATE KEY" section was found,
* if the passphrase is wrong or the key was not an Ed25519 private key.
*/
public static PrivateKey readPem(Reader pemFile, @Nullable String password) throws IOException {
return fromPrivateKeyInfo(Pem.readPrivateKey(pemFile, password));
}
/**
* Parse a private key from a PEM encoded string.
*
* @param pemEncoded The String containing the pem
* @return {@link com.hedera.hashgraph.sdk.PrivateKey}
* @throws IOException if the PEM string was improperly encoded
* @throws BadKeyException if no "PRIVATE KEY" section was found or the key was not an Ed25519
* private key.
* @see #readPem(Reader)
*/
public static PrivateKey fromPem(String pemEncoded) throws IOException {
return readPem(new StringReader(pemEncoded));
}
/**
* Parse a private key from a PEM encoded string.
*
* The private key may be encrypted, e.g. if it was generated by OpenSSL.
*
* @param encodedPem the encoded PEM string
* @param password the password to decrypt the PEM file; if null or empty, no decryption is performed.
* @return {@link com.hedera.hashgraph.sdk.PrivateKey}
* @throws IOException if the PEM string was improperly encoded
* @throws BadKeyException if no "ENCRYPTED PRIVATE KEY" or "PRIVATE KEY" section was found,
* if the passphrase is wrong or the key was not an Ed25519 private key.
* @see #readPem(Reader, String)
*/
public static PrivateKey fromPem(String encodedPem, @Nullable String password) throws IOException {
return readPem(new StringReader(encodedPem), password);
}
/**
* Derive a child key based on the index.
*
* @param index the index
* @return the derived child key
*/
public PrivateKey legacyDerive(int index) {
return legacyDerive((long) index);
}
/**
* Derive a child key based on the index.
*
* @param index the index
* @return the derived child key
*/
public abstract PrivateKey legacyDerive(long index);
/**
* Check if this private key supports derivation.
*
* This is currently only the case if this private key was created from a mnemonic.
*
* @return boolean
*/
public abstract boolean isDerivable();
/**
* Given a wallet/account index, derive a child key compatible with the iOS and Android wallets.
*
* Use index 0 for the default account.
*
* @param index the wallet/account index of the account, 0 for the default account.
* @return the derived key
* @throws IllegalStateException if this key does not support derivation.
* @see #isDerivable()
*/
public abstract PrivateKey derive(int index);
/**
* Derive a public key from this private key.
*
*
The public key can be freely given and used by other parties to verify the signatures
* generated by this private key.
*
* @return the corresponding public key for this private key.
*/
public abstract PublicKey getPublicKey();
/**
* Sign a message with this private key.
*
* @param message The array of bytes to sign with
* @return the signature of the message.
*/
public abstract byte[] sign(byte[] message);
/**
* Sign a transaction.
*
* @param transaction the transaction
* @return the signed transaction
*/
public byte[] signTransaction(Transaction> transaction) {
transaction.requireOneNodeAccountId();
if (!transaction.isFrozen()) {
transaction.freeze();
}
var builder = (SignedTransaction.Builder) transaction.innerSignedTransactions.get(0);
var signature = sign(builder.getBodyBytes().toByteArray());
transaction.addSignature(getPublicKey(), signature);
return signature;
}
@Override
public abstract byte[] toBytes();
/**
* Extract the byte array encoded as DER.
*
* @return the byte array encoded as DER
*/
public abstract byte[] toBytesDER();
/**
* Extract the raw byte array.
*
* @return the raw byte array
*/
public abstract byte[] toBytesRaw();
@Override
public String toString() {
return toStringDER();
}
/**
* Extract the DER encoded hex string.
*
* @return the DER encoded hex string
*/
public String toStringDER() {
return Hex.toHexString(toBytesDER());
}
/**
* Extract the raw hex string.
*
* @return the raw hex string
*/
public String toStringRaw() {
return Hex.toHexString(toBytesRaw());
}
/**
* Retrieve the account id.
*
* @param shard the shard
* @param realm the realm
* @return the account id
*/
public AccountId toAccountId(@Nonnegative long shard, @Nonnegative long realm) {
return getPublicKey().toAccountId(shard, realm);
}
@Override
com.hedera.hashgraph.sdk.proto.Key toProtobufKey() {
// Forward to the corresponding public key.
return getPublicKey().toProtobufKey();
}
/**
* Are we an ED25519 key?
*
* @return are we an ED25519 key
*/
public abstract boolean isED25519();
/**
* Are we an ECDSA key?
*
* @return are we an ECDSA key
*/
public abstract boolean isECDSA();
/**
* Get the chain code of the key
*
* @return the chainCode
*/
public abstract KeyParameter getChainCode();
}