Skip to content

Commit

Permalink
Add ec-curve parameter public key export/export-address subcommands (h…
Browse files Browse the repository at this point in the history
…yperledger#3333)

* Logging curve name when creating a new key
* Add ec-curve parameter to export subcommand

Signed-off-by: Lucas Saldanha <[email protected]>
  • Loading branch information
lucassaldanha authored Feb 2, 2022
1 parent 048dbb4 commit a74ece7
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog
## 22.1.0
- Add `--ec-curve` parameter to export/export-address public-key subcommands [#3333](https://github.com/hyperledger/besu/pull/3333)

### 22.1.0 Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.AddressSubCommand;
import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.ExportSubCommand;
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;
Expand Down Expand Up @@ -97,6 +99,7 @@ static class ExportSubCommand extends KeyPairSubcommand implements Runnable {

@Override
public void run() {
configureEcCurve(ecCurve, parentCommand.spec.commandLine());
run(publicKeyExportFile, keyPair -> keyPair.getPublicKey().toString());
}
}
Expand Down Expand Up @@ -126,6 +129,7 @@ static class AddressSubCommand extends KeyPairSubcommand implements Runnable {

@Override
public void run() {
configureEcCurve(ecCurve, parentCommand.spec.commandLine());
run(addressExportFile, keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()).toString());
}
}
Expand All @@ -134,10 +138,20 @@ private static class KeyPairSubcommand {

@SuppressWarnings("unused")
@ParentCommand
private PublicKeySubCommand parentCommand; // Picocli injects reference to parent command
protected PublicKeySubCommand parentCommand; // Picocli injects reference to parent command

@Mixin private final NodePrivateKeyFileOption nodePrivateKeyFileOption = null;

@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")
protected String ecCurve = null;

@Spec private final CommandSpec spec = null;

protected final void run(
Expand Down Expand Up @@ -172,5 +186,15 @@ protected final void run(
parentCommand.out.println(output);
}
}

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);
}
}
}
}
}
170 changes: 157 additions & 13 deletions besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.SECPPrivateKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.ethereum.core.Util;

import java.io.File;
Expand All @@ -33,6 +34,7 @@
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -63,45 +65,57 @@ public class PublicKeySubCommandTest extends CommandTestAbstract {
+ System.lineSeparator();

private static final String EXPECTED_PUBLIC_KEY_EXPORT_USAGE =
"Usage: besu public-key export [-hV] [--node-private-key-file=<PATH>]"
"Usage: besu public-key export [-hV] [--ec-curve[=<NAME>]]"
+ System.lineSeparator()
+ " [--to=<FILE>]"
+ " [--node-private-key-file=<PATH>] [--to=<FILE>]"
+ System.lineSeparator()
+ "This command outputs the node public key. Default output is standard output."
+ System.lineSeparator()
+ " -h, --help Show this help message and exit."
+ " --ec-curve[=<NAME>] Elliptic curve to use when creating a new key"
+ System.lineSeparator()
+ " (default: secp256k1)"
+ System.lineSeparator()
+ " -h, --help Show this help message and exit."
+ System.lineSeparator()
+ " --node-private-key-file=<PATH>"
+ System.lineSeparator()
+ " The node's private key file (default: a file named \"key\" in"
+ " The node's private key file (default: a file named"
+ System.lineSeparator()
+ " the Besu data directory)"
+ " \"key\" in the Besu data directory)"
+ System.lineSeparator()
+ " --to=<FILE> File to write public key to instead of standard output"
+ " --to=<FILE> File to write public key to instead of standard"
+ System.lineSeparator()
+ " -V, --version Print version information and exit."
+ " output"
+ System.lineSeparator()
+ " -V, --version Print version information and exit."
+ System.lineSeparator();

private static final String EXPECTED_PUBLIC_KEY_EXPORT_ADDRESS_USAGE =
"Usage: besu public-key export-address [-hV] [--node-private-key-file=<PATH>]"
"Usage: besu public-key export-address [-hV] [--ec-curve[=<NAME>]]"
+ System.lineSeparator()
+ " [--node-private-key-file=<PATH>]"
+ System.lineSeparator()
+ " [--to=<FILE>]"
+ System.lineSeparator()
+ "This command outputs the node's account address. Default output is standard"
+ System.lineSeparator()
+ "output."
+ System.lineSeparator()
+ " -h, --help Show this help message and exit."
+ " --ec-curve[=<NAME>] Elliptic curve to use when creating a new key"
+ System.lineSeparator()
+ " (default: secp256k1)"
+ System.lineSeparator()
+ " -h, --help Show this help message and exit."
+ System.lineSeparator()
+ " --node-private-key-file=<PATH>"
+ System.lineSeparator()
+ " The node's private key file (default: a file named \"key\" in"
+ " The node's private key file (default: a file named"
+ System.lineSeparator()
+ " the Besu data directory)"
+ " \"key\" in the Besu data directory)"
+ System.lineSeparator()
+ " --to=<FILE> File to write address to instead of standard output"
+ " --to=<FILE> File to write address to instead of standard output"
+ System.lineSeparator()
+ " -V, --version Print version information and exit."
+ " -V, --version Print version information and exit."
+ System.lineSeparator();

private static final String PUBLIC_KEY_SUBCOMMAND_NAME = "public-key";
Expand All @@ -117,6 +131,11 @@ public static void setUp() {
curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
}

@Before
public void before() {
SignatureAlgorithmFactory.resetInstance();
}

// public-key sub-command
@Test
public void publicKeySubCommandExistsAndHasSubCommands() {
Expand Down Expand Up @@ -323,4 +342,129 @@ public void callingPublicKeyExportAddressSubCommandWithInvalidFileMustDisplayErr
assertThat(commandErrorOutput.toString(UTF_8))
.startsWith("Private key cannot be loaded from file");
}

@Test
public void
callingPublicKeyExportSubCommandWithEcCurveNameCorrectlyConfiguresSignatureAlgorithmFactory()
throws Exception {
assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();

final File file = File.createTempFile("public", "key");

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME,
PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME,
"--to",
file.getPath(),
"--ec-curve",
CURVE_NAME);

assertThat(SignatureAlgorithmFactory.isInstanceSet()).isTrue();
assertThat(SignatureAlgorithmFactory.getInstance().getCurveName()).isEqualTo(CURVE_NAME);
}

@Test
public void
callingPublicKeyExportSubCommandWithoutEcCurveNameDoesNotConfiguresSignatureAlgorithmFactory()
throws Exception {
assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();

final File file = File.createTempFile("public", "key");

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME, PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME, "--to", file.getPath());

assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();
}

@Test
public void callingPublicKeyExportSubCommandWithInvalidEcCurveNameFails() throws Exception {
final File file = File.createTempFile("public", "key");

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME,
PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME,
"--to",
file.getPath(),
"--ec-curve",
"foo");

assertThat(commandErrorOutput.toString(UTF_8))
.contains("foo is not in the list of valid elliptic curves");
}

@Test
public void
callingPublicKeyExportAddressSubCommandWithEcCurveNameCorrectlyConfiguresSignatureAlgorithmFactory()
throws Exception {
assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();

final SECPPrivateKey privateKey =
SECPPrivateKey.create(
Bytes32.fromHexString(
"0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"),
ALGORITHM);

final Path privateKeyFile = Files.createTempFile("private", "address");
Files.writeString(privateKeyFile, privateKey.toString());

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME,
PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME,
"--node-private-key-file",
privateKeyFile.toString(),
"--ec-curve",
CURVE_NAME);

assertThat(SignatureAlgorithmFactory.isInstanceSet()).isTrue();
assertThat(SignatureAlgorithmFactory.getInstance().getCurveName()).isEqualTo(CURVE_NAME);
}

@Test
public void
callingPublicKeyExportAddressSubCommandWithoutEcCurveNameDoesNotConfiguresSignatureAlgorithmFactory()
throws Exception {
assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();

final SECPPrivateKey privateKey =
SECPPrivateKey.create(
Bytes32.fromHexString(
"0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"),
ALGORITHM);

final Path privateKeyFile = Files.createTempFile("private", "address");
Files.writeString(privateKeyFile, privateKey.toString());

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME,
PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME,
"--node-private-key-file",
privateKeyFile.toString());

assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse();
}

@Test
public void callingPublicKeyExportAddressSubCommandWithInvalidEcCurveNameFails()
throws Exception {
final SECPPrivateKey privateKey =
SECPPrivateKey.create(
Bytes32.fromHexString(
"0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"),
ALGORITHM);

final Path privateKeyFile = Files.createTempFile("private", "address");
Files.writeString(privateKeyFile, privateKey.toString());

parseCommand(
PUBLIC_KEY_SUBCOMMAND_NAME,
PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME,
"--node-private-key-file",
privateKeyFile.toString(),
"--ec-curve",
"foo");

assertThat(commandErrorOutput.toString(UTF_8))
.contains("foo is not in the list of valid elliptic curves");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ public static KeyPair loadKeyPair(final File keyFile) {
LOG.info(
"Loaded public key {} from {}", key.getPublicKey().toString(), keyFile.getAbsolutePath());
} else {
key = SIGNATURE_ALGORITHM.get().generateKeyPair();
final SignatureAlgorithm signatureAlgorithm = SIGNATURE_ALGORITHM.get();
key = signatureAlgorithm.generateKeyPair();
storeKeyFile(key, keyFile.getParentFile().toPath());

LOG.info(
"Generated new public key {} and stored it to {}",
"Generated new {} public key {} and stored it to {}",
signatureAlgorithm.getCurveName(),
key.getPublicKey().toString(),
keyFile.getAbsolutePath());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public class SignatureAlgorithmType {

private static final String DEFAULT_EC_CURVE_NAME = "secp256k1";
public static final String DEFAULT_EC_CURVE_NAME = "secp256k1";
private static final ImmutableMap<String, Supplier<SignatureAlgorithm>> SUPPORTED_ALGORITHMS =
ImmutableMap.of(DEFAULT_EC_CURVE_NAME, SECP256K1::new, "secp256r1", SECP256R1::new);

Expand Down

0 comments on commit a74ece7

Please sign in to comment.