Skip to content

Commit

Permalink
Added SECP256R1 support to CLI sub command generate-blockchain-config (
Browse files Browse the repository at this point in the history
…#2183)

Signed-off-by: Daniel Lehrner <daniel@io.builders>
  • Loading branch information
daniel-iobuilders authored Apr 27, 2021
1 parent 8267f90 commit 456981c
Show file tree
Hide file tree
Showing 13 changed files with 364 additions and 25 deletions.
6 changes: 5 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2673,7 +2673,11 @@ private void instantiateSignatureAlgorithmFactory() {
try {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get()));
} catch (IllegalArgumentException e) {
throw new CommandLine.InitializationException(e.getMessage());
throw new CommandLine.InitializationException(
new StringBuilder()
.append("Invalid genesis file configuration for ecCurve. ")
.append(e.getMessage())
.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;

import org.hyperledger.besu.cli.DefaultCommandValues;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.JsonGenesisConfigOptions;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.consensus.ibft.IbftExtraDataCodec;
Expand All @@ -26,6 +28,7 @@
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.crypto.SignatureAlgorithmType;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Util;

Expand All @@ -37,6 +40,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;

Expand All @@ -60,7 +64,7 @@
class GenerateBlockchainConfig implements Runnable {
private static final Logger LOG = LogManager.getLogger();

private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
private final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);

@Option(
Expand Down Expand Up @@ -133,6 +137,7 @@ private void generateBlockchainConfig() {
try {
handleOutputDirectory();
parseConfig();
processEcCurve();
if (generateNodesKeys) {
generateNodesKeys();
} else {
Expand Down Expand Up @@ -167,6 +172,16 @@ private void importPublicKey(final JsonNode publicKeyJson) {
try {
final SECPPublicKey publicKey =
SIGNATURE_ALGORITHM.get().createPublicKey(Bytes.fromHexString(publicKeyText));

if (!SIGNATURE_ALGORITHM.get().isValidPublicKey(publicKey)) {
throw new IllegalArgumentException(
new StringBuilder()
.append(publicKeyText)
.append(" is not a valid public key for elliptic curve ")
.append(SIGNATURE_ALGORITHM.get().getCurveName())
.toString());
}

writeKeypair(publicKey, null);
LOG.info("Public key imported from configuration.({})", publicKey.toString());
} catch (final IOException e) {
Expand Down Expand Up @@ -256,6 +271,27 @@ private void parseConfig() throws IOException {
generateNodesKeys = JsonUtil.getBoolean(nodesConfig, "generate", false);
}

/** Sets the selected signature algorithm instance in SignatureAlgorithmFactory. */
private void processEcCurve() {
GenesisConfigOptions options = GenesisConfigFile.fromConfig(genesisConfig).getConfigOptions();
Optional<String> ecCurve = options.getEcCurve();

if (ecCurve.isEmpty()) {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.createDefault());
return;
}

try {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
new StringBuilder()
.append("Invalid parameter for ecCurve in genesis config: ")
.append(e.getMessage())
.toString());
}
}

/**
* Checks if the output directory exists.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4328,8 +4328,8 @@ public void invalidEcCurveFailsWithErrorMessage() throws IOException {
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString())
.contains(
"Invalid genesis file configuration. "
+ "Elliptic curve (ecCurve) abcd is not in the list of valid elliptic curves [secp256k1, secp256r1]");
"Invalid genesis file configuration for ecCurve. "
+ "abcd is not in the list of valid elliptic curves [secp256k1, secp256r1]");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.createTempDirectory;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand All @@ -28,22 +27,31 @@

import org.hyperledger.besu.cli.CommandTestAbstract;
import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.crypto.SECP256R1;
import org.hyperledger.besu.crypto.SECPPrivateKey;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import java.util.Optional;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.json.JsonObject;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Test;
import picocli.CommandLine;
Expand Down Expand Up @@ -72,6 +80,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract {

@Before
public void init() throws IOException {
SignatureAlgorithmFactory.resetInstance();
tmpOutputDirectoryPath = createTempDirectory(format("output-%d", currentTimeMillis()));
}

Expand Down Expand Up @@ -107,7 +116,8 @@ public void generateBlockchainConfigMustGenerateKeysWhenGenerateIsTrue() throws
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv"));
asList("key.pub", "key.priv"),
Optional.of(new SECP256K1()));
}

@Test
Expand All @@ -129,7 +139,8 @@ public void genesisFileNameShouldBeEqualToOption() throws IOException {
tmpOutputDirectoryPath,
"option.json",
true,
asList("key.pub", "key.priv"));
asList("key.pub", "key.priv"),
Optional.of(new SECP256K1()));
}

@Test
Expand All @@ -140,7 +151,8 @@ public void publicKeyFileNameShouldBeEqualToOption() throws IOException {
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("pub.test", "key.priv"));
asList("pub.test", "key.priv"),
Optional.of(new SECP256K1()));
}

@Test
Expand All @@ -151,7 +163,8 @@ public void privateKeyFileNameShouldBeEqualToOption() throws IOException {
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "priv.test"));
asList("key.pub", "priv.test"),
Optional.of(new SECP256K1()));
}

@Test
Expand Down Expand Up @@ -197,6 +210,59 @@ public void shouldFailIfOutputDirectoryNonEmpty() throws IOException {
asList("key.pub", "key.priv"));
}

@Test
public void shouldFailIfInvalidEcCurveIsSet() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput(
cmd(),
"/operator/config_generate_keys_ec_invalid.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "priv.test")))
.isInstanceOf(CommandLine.ExecutionException.class);
}

@Test
public void shouldGenerateSECP256R1KeysWhenSetAsEcCurve() throws IOException {
runCmdAndCheckOutput(
cmd(),
"/operator/config_generate_keys_secp256r1.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv"),
Optional.of(new SECP256R1()));
}

@Test
public void shouldFailIfImportedKeysAreFromDifferentEllipticCurve() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput(
cmd(),
"/operator/config_import_keys_secp256r1_invalid_keys.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv")))
.isInstanceOf(CommandLine.ExecutionException.class)
.hasMessageEndingWith(
"0xb295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680 is not a valid public key for elliptic curve secp256r1");
}

@Test
public void shouldImportSecp256R1Keys() throws IOException {
runCmdAndCheckOutput(
cmd(),
"/operator/config_import_keys_secp256r1.json",
tmpOutputDirectoryPath,
"genesis.json",
false,
singletonList("key.pub"));
}

private void runCmdAndCheckOutput(
final Cmd cmd,
final String configFile,
Expand All @@ -205,6 +271,25 @@ private void runCmdAndCheckOutput(
final boolean generate,
final Collection<String> expectedKeyFiles)
throws IOException {
runCmdAndCheckOutput(
cmd,
configFile,
outputDirectoryPath,
genesisFileName,
generate,
expectedKeyFiles,
Optional.empty());
}

private void runCmdAndCheckOutput(
final Cmd cmd,
final String configFile,
final Path outputDirectoryPath,
final String genesisFileName,
final boolean generate,
final Collection<String> expectedKeyFiles,
final Optional<SignatureAlgorithm> signatureAlgorithm)
throws IOException {
final URL configFilePath = this.getClass().getResource(configFile);
parseCommand(
cmd(
Expand Down Expand Up @@ -238,10 +323,47 @@ private void runCmdAndCheckOutput(
final int nodeCount = jsonNode.get("blockchain").get("nodes").get("count").asInt();
assertThat(nodeCount).isEqualTo(nodesKeysFolders.length);
}
final Stream<File> nodesKeysFoldersStream = stream(nodesKeysFolders);

nodesKeysFoldersStream.forEach(
nodeFolder -> assertThat(nodeFolder.list()).containsAll(expectedKeyFiles));
for (File nodeFolder : nodesKeysFolders) {
assertThat(nodeFolder.list()).containsAll(expectedKeyFiles);

if (signatureAlgorithm.isPresent()) {
checkPublicKey(nodeFolder, signatureAlgorithm.get());
}
}
}

private void checkPublicKey(final File dir, final SignatureAlgorithm signatureAlgorithm)
throws IOException {
String publicKeyHex = readPubFile(dir);
String privateKeyHex = readPrivFile(dir);

SECPPrivateKey privateKey =
signatureAlgorithm.createPrivateKey(Bytes32.fromHexString(privateKeyHex));
SECPPublicKey expectedPublicKey = signatureAlgorithm.createPublicKey(privateKey);

assertThat(publicKeyHex).isEqualTo(expectedPublicKey.getEncodedBytes().toHexString());
}

private String readPubFile(final File dir) throws IOException {
FilenameFilter pubFilter = (folder, name) -> name.contains("pub");

return readFile(dir, pubFilter);
}

private String readPrivFile(final File dir) throws IOException {
FilenameFilter privFilter = (folder, name) -> name.contains("priv");

return readFile(dir, privFilter);
}

private String readFile(final File dir, final FilenameFilter fileFilter) throws IOException {
File[] files = dir.listFiles(fileFilter);

assertThat(files).isNotNull();
assertThat(files.length).isEqualTo(1);

return Files.readString(Path.of(files[0].getAbsolutePath()));
}

static class Cmd {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"genesis": {
"config": {
"chainId": 2017,
"eip150Block": 0,
"ecCurve": "abcd",
"ibft2": {

}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"generate": true,
"count": 4
}
}
}
Loading

0 comments on commit 456981c

Please sign in to comment.