PublicKeySubCommand.java
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.cli.subcommands;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.COMMAND_NAME;
import org.hyperledger.besu.cli.BesuCommand;
import org.hyperledger.besu.cli.DefaultCommandValues;
import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption;
import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.AddressSubCommand;
import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.ExportSubCommand;
import org.hyperledger.besu.cli.util.VersionProvider;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.crypto.SignatureAlgorithmType;
import org.hyperledger.besu.ethereum.core.Util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParentCommand;
import picocli.CommandLine.Spec;
/** Node's public key related sub-command */
@Command(
name = COMMAND_NAME,
description = "This command provides node public key related actions.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class,
subcommands = {ExportSubCommand.class, AddressSubCommand.class})
public class PublicKeySubCommand implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(PublicKeySubCommand.class);
/** The constant COMMAND_NAME. */
public static final String COMMAND_NAME = "public-key";
@SuppressWarnings("unused")
@ParentCommand
private BesuCommand parentCommand; // Picocli injects reference to parent command
@SuppressWarnings("unused")
@Spec
private CommandSpec spec; // Picocli injects reference to command spec
private final PrintWriter out;
/**
* Instantiates a new Public key sub command.
*
* @param out the out
*/
public PublicKeySubCommand(final PrintWriter out) {
this.out = out;
}
@Override
public void run() {
spec.commandLine().usage(out);
}
/**
* Public key export sub-command
*
* <p>Export of the public key is writing the key to the standard output by default. An option
* enables to write it in a file. Indeed, a direct output of the value to standard out is not
* always recommended as reading can be made difficult as the value can be mixed with other
* information like logs that are in KeyPairUtil that is inevitable.
*/
@Command(
name = "export",
description = "This command outputs the node public key. Default output is standard output.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
static class ExportSubCommand extends KeyPairSubcommand implements Runnable {
@Option(
names = "--to",
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description = "File to write public key to instead of standard output",
arity = "1..1")
private final File publicKeyExportFile = null;
@Override
public void run() {
configureEcCurve(ecCurve, parentCommand.spec.commandLine());
run(publicKeyExportFile, keyPair -> keyPair.getPublicKey().toString());
}
}
/**
* Account address export sub-command
*
* <p>Export of the account address is writing the address to the standard output by default. An
* option enables to write it in a file. Indeed, a direct output of the value to standard out is
* not always recommended as reading can be made difficult as the value can be mixed with other
* information like logs that are in KeyPairUtil that is inevitable.
*/
@Command(
name = "export-address",
description =
"This command outputs the node's account address. "
+ "Default output is standard output.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
static class AddressSubCommand extends KeyPairSubcommand implements Runnable {
@Option(
names = "--to",
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description = "File to write address to instead of standard output",
arity = "1..1")
private final File addressExportFile = null;
@Override
public void run() {
configureEcCurve(ecCurve, parentCommand.spec.commandLine());
run(addressExportFile, keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()).toString());
}
}
private static class KeyPairSubcommand {
/** The Parent command. */
@SuppressWarnings("unused")
@ParentCommand
protected PublicKeySubCommand parentCommand; // Picocli injects reference to parent command
@Mixin private final NodePrivateKeyFileOption nodePrivateKeyFileOption = null;
/** The Ec curve. */
@Option(
names = "--ec-curve",
paramLabel = "<NAME>",
description =
"Elliptic curve to use when creating a new key (default: "
+ SignatureAlgorithmType.DEFAULT_EC_CURVE_NAME
+ ")",
arity = "0..1")
@SuppressWarnings("FieldCanBeFinal")
protected String ecCurve = null;
@Spec private final CommandSpec spec = null;
/**
* Run.
*
* @param exportFile the export file
* @param outputFunction the output function
*/
protected final void run(
final File exportFile, final Function<KeyPair, String> outputFunction) {
checkNotNull(parentCommand);
final BesuCommand besuCommand = parentCommand.parentCommand;
checkNotNull(besuCommand);
final File nodePrivateKeyFile = nodePrivateKeyFileOption.getNodePrivateKeyFile();
if (nodePrivateKeyFile != null && !nodePrivateKeyFile.exists()) {
throw new CommandLine.ParameterException(
spec.commandLine(), "Private key file doesn't exist");
}
final KeyPair keyPair;
try {
keyPair = besuCommand.loadKeyPair(nodePrivateKeyFileOption.getNodePrivateKeyFile());
} catch (IllegalArgumentException e) {
throw new CommandLine.ParameterException(
spec.commandLine(), "Private key cannot be loaded from file", e);
}
final String output = outputFunction.apply(keyPair);
if (exportFile != null) {
final Path path = exportFile.toPath();
try (final BufferedWriter fileWriter = Files.newBufferedWriter(path, UTF_8)) {
fileWriter.write(output);
} catch (final IOException e) {
LOG.error("An error occurred while trying to write to output file", e);
}
} else {
parentCommand.out.println(output);
}
}
/**
* Configure ec curve.
*
* @param ecCurve the ec curve
* @param commandLine the command line
*/
protected static void configureEcCurve(final String ecCurve, final CommandLine commandLine) {
if (ecCurve != null) {
try {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve));
} catch (IllegalArgumentException e) {
throw new CommandLine.ParameterException(commandLine, e.getMessage(), e);
}
}
}
}
}