Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IF: Generate bls key pair #1594

Merged
merged 7 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion programs/leap-util/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_executable( ${LEAP_UTIL_EXECUTABLE_NAME} main.cpp actions/subcommand.cpp actions/generic.cpp actions/blocklog.cpp actions/snapshot.cpp actions/chain.cpp)
add_executable( ${LEAP_UTIL_EXECUTABLE_NAME} main.cpp actions/subcommand.cpp actions/generic.cpp actions/blocklog.cpp actions/bls.cpp actions/snapshot.cpp actions/chain.cpp)

if( UNIX AND NOT APPLE )
set(rt_library rt )
Expand Down
125 changes: 125 additions & 0 deletions programs/leap-util/actions/bls.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include "bls.hpp"

#include <fc/crypto/bls_private_key.hpp>
#include <fc/crypto/bls_public_key.hpp>
#include <fc/crypto/bls_signature.hpp>

#include <boost/program_options.hpp>

using namespace fc::crypto::blslib;
namespace bpo = boost::program_options;
using bpo::options_description;

void bls_actions::setup(CLI::App& app) {
// callback helper with error code handling
auto err_guard = [this](int (bls_actions::*fun)()) {
try {
int rc = (this->*fun)();
if(rc) throw(CLI::RuntimeError(rc));
} catch(...) {
print_exception();
throw(CLI::RuntimeError(-1));
}
};

// main command
auto* sub = app.add_subcommand("bls", "BLS utility");
sub->require_subcommand();

// Create subcommand
auto create = sub->add_subcommand("create", "Create BLS items");
create->require_subcommand();

// sub-subcommand - key
auto* create_key = create->add_subcommand("key", "Create a new BLS keypair and print the public and private keys")->callback([err_guard]() { err_guard(&bls_actions::create_key); });
create_key->add_option("-f,--file", opt->key_file, "Name of file to write private/public key output to. (Must be set, unless \"--to-console\" is passed");
create_key->add_flag( "--to-console", opt->print_console, "Print private/public keys to console.");

// sub-subcommand - pop (proof of possession)
auto* create_pop = create->add_subcommand("pop", "Create proof of possession of the corresponding private key for a given public key")->callback([err_guard]() { err_guard(&bls_actions::create_pop); });
create_pop->add_option("-f,--file", opt->key_file, "Name of file storing the private key. (one and only one of \"-f,--file\" and \"--private-key\" must be set)");
create_pop->add_option("--private-key", opt->private_key_str, "The private key. (one and only one of \"-f,--file\" and \"--private-key\" must be set)");
}

int bls_actions::create_key() {
if (opt->key_file.empty() && !opt->print_console) {
std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--to-console\"" << std::endl;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\"-f, --file\" (add -f to the message)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

return -1;
} else if (!opt->key_file.empty() && opt->print_console) {
std::cerr << "ERROR: Only one of \"--file\" or pass \"--to-console\" can be provided" << std::endl;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\"-f, --file\" (add -f to the message)

return -1;
}
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved

// create a private key and get its corresponding public key
const bls_private_key private_key = bls_private_key::generate();
const bls_public_key public_key = private_key.get_public_key();

// generate pop
const std::string pop_str = generate_pop_str(private_key);

// prepare output
std::string out_str = "Private key: " + private_key.to_string({}) + "\n";
out_str += "Public key: " + public_key.to_string({}) + "\n";
out_str += "Proof of Possession: " + pop_str + "\n";
if (opt->print_console) {
std::cout << out_str;
} else {
std::cout << "saving keys to " << opt->key_file << std::endl;
std::ofstream out( opt->key_file.c_str() );
out << out_str;
}

return 0;
}

int bls_actions::create_pop() {
if (opt->key_file.empty() && opt->private_key_str.empty()) {
std::cerr << "ERROR: Either indicate a file using \"-f, --file\" or pass \"--private-key\"" << std::endl;
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
return -1;
} else if (!opt->key_file.empty() && !opt->private_key_str.empty()) {
std::cerr << "ERROR: Only one of \"-f, --file\" and \"--private-key\" can be provided" << std::endl;
return -1;
}

std::string private_key_str;
if (!opt->private_key_str.empty()) {
private_key_str = opt->private_key_str;
} else {
std::ifstream key_file(opt->key_file);

if (!key_file.is_open()) {
std::cerr << "ERROR: failed to open file " << opt->key_file << std::endl;
return -1;
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
}

if (std::getline(key_file, private_key_str)) {
if (!key_file.eof()) {
std::cerr << "ERROR: file " << opt->key_file << " contains more than one line" << std::endl;
return -1;
}
} else {
std::cerr << "ERROR: file " << opt->key_file << " is empty" << std::endl;
return -1;
}
}

// create private key object using input private key string
const bls_private_key private_key = bls_private_key(private_key_str);
const bls_public_key public_key = private_key.get_public_key();
std::string pop_str = generate_pop_str(private_key);

std::cout << "Proof of Possession: " << pop_str << std::endl;
std::cout << "Public key: " << public_key.to_string({}) << std::endl;

return 0;
}

std::string bls_actions::generate_pop_str(const bls_private_key& private_key) {
const bls_public_key public_key = private_key.get_public_key();

const std::array<uint8_t, 48> msg = public_key._pkey.toCompressedBytesBE();
const std::vector<uint8_t> msg_vector = std::vector<uint8_t>(msg.begin(), msg.end());
const bls_signature pop = private_key.sign(msg_vector);

return pop.to_string({});
}
26 changes: 26 additions & 0 deletions programs/leap-util/actions/bls.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "subcommand.hpp"
#include <eosio/chain/config.hpp>

#include <fc/crypto/bls_private_key.hpp>

using namespace eosio::chain;

struct bls_options {
std::string key_file;
std::string private_key_str;

// flags
bool print_console{false};
};

class bls_actions : public sub_command<bls_options> {
public:
void setup(CLI::App& app);

protected:
int create_key();
int create_pop();

private:
std::string generate_pop_str(const fc::crypto::blslib::bls_private_key& private_key);
};
5 changes: 5 additions & 0 deletions programs/leap-util/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <CLI/CLI.hpp>

#include "actions/blocklog.hpp"
#include "actions/bls.hpp"
#include "actions/chain.hpp"
#include "actions/generic.hpp"
#include "actions/snapshot.hpp"
Expand Down Expand Up @@ -33,6 +34,10 @@ int main(int argc, char** argv) {
auto blocklog_subcommand = std::make_shared<blocklog_actions>();
blocklog_subcommand->setup(app);

// bls sc tree
auto bls_subcommand = std::make_shared<bls_actions>();
bls_subcommand->setup(app);

// snapshot sc tree
auto snapshot_subcommand = std::make_shared<snapshot_actions>();
snapshot_subcommand->setup(app);
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/block_log_util_test.py ${CMAKE_CURREN
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/block_log_retain_blocks_test.py ${CMAKE_CURRENT_BINARY_DIR}/block_log_retain_blocks_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cluster_launcher.py ${CMAKE_CURRENT_BINARY_DIR}/cluster_launcher.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/distributed-transactions-test.py ${CMAKE_CURRENT_BINARY_DIR}/distributed-transactions-test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/leap_util_bls_test.py ${CMAKE_CURRENT_BINARY_DIR}/leap_util_bls_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sample-cluster-map.json ${CMAKE_CURRENT_BINARY_DIR}/sample-cluster-map.json COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/restart-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/restart-scenarios-test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/terminate-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/terminate-scenarios-test.py COPYONLY)
Expand Down Expand Up @@ -241,6 +242,8 @@ set_property(TEST cli_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME larger_lib_test COMMAND tests/large-lib-test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST larger_lib_test PROPERTY LABELS nonparallelizable_tests)

add_test(NAME leap_util_bls_test COMMAND tests/leap_util_bls_test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

add_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_tests_properties(http_plugin_test PROPERTIES TIMEOUT 100)
set_property(TEST http_plugin_test PROPERTY LABELS nonparallelizable_tests)
Expand Down
145 changes: 145 additions & 0 deletions tests/leap_util_bls_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python3

import os
import re

from TestHarness import Utils

###############################################################
# leap_util_bls_test
#
# Test leap-util's BLS commands.
# - Create a key pair
# - Create a POP (Proof of Possession)
# - Error handlings
#
###############################################################

Print=Utils.Print
testSuccessful=False

def test_create_key_to_console():
rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False)
check_create_key_results(rslts)

def test_create_key_to_file():
tmp_file = "tmp_key_file_dlkdx1x56pjy"
Utils.processLeapUtilCmd("bls create key --file {}".format(tmp_file), "create key to file", silentErrors=False)

with open(tmp_file, 'r') as file:
rslts = file.read()
check_create_key_results(rslts)

os.remove(tmp_file)

def test_create_pop_from_command_line():
# Create a pair of keys
rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False)
results = get_results(rslts)

# save results
private_key = results["Private key"]
public_key = results["Public key"]
pop = results["Proof of Possession"]

# use the private key to create POP
rslts = Utils.processLeapUtilCmd("bls create pop --private-key {}".format(private_key), "create pop from command line", silentErrors=False)
results = get_results(rslts)

# check pop and public key are the same as those generated before
assert results["Public key"] == public_key
assert results["Proof of Possession"] == pop

def test_create_pop_from_file():
# Create a pair of keys
rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False)
results = get_results(rslts)

# save results
private_key = results["Private key"]
public_key = results["Public key"]
pop = results["Proof of Possession"]

# save private key to a file
private_key_file = "tmp_key_file_dlkdx1x56pjy"
with open(private_key_file, 'w') as file:
file.write(private_key)

# use the private key file to create POP
rslts = Utils.processLeapUtilCmd("bls create pop --file {}".format(private_key_file), "create pop from command line", silentErrors=False)
os.remove(private_key_file)
results = get_results(rslts)

# check pop and public key are the same as those generated before
assert results["Public key"] == public_key
assert results["Proof of Possession"] == pop

def test_create_key_error_handling():
# should fail with missing arguments (processLeapUtilCmd returning None)
assert Utils.processLeapUtilCmd("bls create key", "missing arguments") == None

# should fail when both arguments are present
assert Utils.processLeapUtilCmd("bls create key --file out_file --to-console", "conflicting arguments") == None

def test_create_pop_error_handling():
# should fail with missing arguments (processLeapUtilCmd returning None)
assert Utils.processLeapUtilCmd("bls create pop", "missing arguments") == None

# should fail when both arguments are present
assert Utils.processLeapUtilCmd("bls create pop --file private_key_file --private-key", "conflicting arguments") == None

# should fail when private key file does not exist
temp_file = "aRandomFileT6bej2pjsaz"
if os.path.exists(temp_file):
os.remove(temp_file)
assert Utils.processLeapUtilCmd("bls create pop --file {}".format(temp_file), "private file not existing") == None

def check_create_key_results(rslts):
results = get_results(rslts)

# check each output has valid value
assert "PVT_BLS_" in results["Private key"]
assert "PUB_BLS_" in results["Public key"]
assert "SIG_BLS_" in results["Proof of Possession"]

def get_results(rslts):
# sample output looks like
# Private key: PVT_BLS_kRhJJ2MsM+/CddO...
# Public key: PUB_BLS_lbUE8922wUfX0Iy5...
# Proof of Possession: SIG_BLS_olZfcFw...
pattern = r'(\w+[^:]*): ([^\n]+)'
matched= re.findall(pattern, rslts)

results = {}
for k, v in matched:
results[k.strip()] = v.strip()

return results

# tests start
try:
# test create key to console
test_create_key_to_console()

# test create key to file
test_create_key_to_file()

# test create pop from private key in command line
test_create_pop_from_command_line()

# test create pop from private key in file
test_create_pop_from_file()

# test error handling in create key
test_create_key_error_handling()

# test error handling in create pop
test_create_pop_error_handling()

testSuccessful=True
except Exception as e:
Print(e)
Utils.errorExit("exception during processing")

exitCode = 0 if testSuccessful else 1
exit(exitCode)