Skip to content

Commit

Permalink
Merge branch 'hotstuff_integration' into pk_sig_affine_le_format
Browse files Browse the repository at this point in the history
  • Loading branch information
linh2931 authored Sep 6, 2023
2 parents 4d01e5f + 64f7f89 commit d88aa98
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 1 deletion.
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 \"-f, --file\" or pass \"--to-console\"" << "\n";
return -1;
} else if (!opt->key_file.empty() && opt->print_console) {
std::cerr << "ERROR: Only one of \"-f, --file\" or pass \"--to-console\" can be provided" << "\n";
return -1;
}

// 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 << "\n";
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\"" << "\n";
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" << "\n";
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 << "\n";
return -1;
}

if (std::getline(key_file, private_key_str)) {
if (!key_file.eof()) {
std::cerr << "ERROR: file " << opt->key_file << " contains more than one line" << "\n";
return -1;
}
} else {
std::cerr << "ERROR: file " << opt->key_file << " is empty" << "\n";
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 << "\n";
std::cout << "Public key: " << public_key.to_string({}) << "\n";

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, 96> msg = public_key._pkey.toAffineBytesLE(true); // true means raw
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)

0 comments on commit d88aa98

Please sign in to comment.