diff --git a/.gitignore b/.gitignore index 52c11c2e..15930d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ .*.sw? .DS_Store build/** -.vscode/** \ No newline at end of file +.vscode/** +Makefile +CMakeCache.txt +cmake_install.cmake +CMakeFiles/** \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..3903752c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.2) + +add_definitions("-std=c++17") + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY build) + +add_executable(hpcore + src/main.cpp + src/conf.cpp + src/crypto.cpp + src/proc.cpp + src/shared.cpp + src/usr/usr.cpp + src/shared.cpp +) + +target_link_libraries(hpcore + libsodium.a + libboost_system.a + libboost_filesystem.a +) \ No newline at end of file diff --git a/README.md b/README.md index 05b61aa3..741f2d87 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A C++ version of hotpocket designed for production envrionments, original protot ## Steps to setup Hot Pocket -### Install Libsodium +#### Install Libsodium Instructions are based on [this](https://libsodium.gitbook.io/doc/installation). 1. Download and extract Libsodium 1.0.18 from [here](https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz). @@ -54,10 +54,42 @@ Instructions are based on [this](https://github.com/protocolbuffers/protobuf/tre This will update your library cache and avoid potential issues when running your compiled C++ program which links to newly installed libraries. +#### Install CMAKE +If you use apt, run `sudo apt install cmake` +Or follow [this](https://cmake.org/install/) + #### Build and run Hot Pocket 1. navigate to hotpocket repo root. -2. Run `make` -3. Run `./build/hpcore new ~/mycontract`. This will initialize a new contract directory `mycontract` in your home directory. -4. Take a look at `~/mycontract/cfg/hp.cfg`. This is your new contract config file. You can modify it according to your contract hosting requirements. -5. Optional: Run `./build/hpcore rekey ~/mycontract` to generate new public/private key pair. -6. Run `./build/hpcore run ~/mycontract` to run your smart contract (to do). \ No newline at end of file +1. Run `cmake .` (You only have to do this once) +1. Run `make` +1. Run `./build/hpcore new ~/mycontract`. This will initialize a new contract directory `mycontract` in your home directory. +1. Take a look at `~/mycontract/cfg/hp.cfg`. This is your new contract config file. You can modify it according to your contract hosting requirements. +1. Optional: Run `./build/hpcore rekey ~/mycontract` to generate new public/private key pair. +1. Run `./build/hpcore run ~/mycontract` to run your smart contract (to do). + +## Code structure +Code is divided into subsystems via namespaces. Some subsystems mentioned here are yet to be introduced. + +#### conf +Handles contract configuration. Loads and holds the central configuration object. Used by most of the subsystems. + +#### crypto +Handles cryptographic activities. Wraps libsodium and offers convinience functions. + +#### proc +Handles contract process execution. + +#### usr +Handles user connections and processing of user I/O with the smart contract. Makes use of **crypto** and **sock**. + +#### ntn +Handles node-to-node connections and message exchange between nodes. Also handles smart contract node-party-line (npl) I/O. Makes use of **crypto** and **sock**. + +#### cons +Handles consensus and proposal rounds. Makes use of **usr**, **ntn** and **proc** + +#### sock +Handles generic web sockets functionality. Mainly acts as a wrapper for boost/beast. + +#### shared +Contains shared data structures/helper functions used by multiple subsystems. Used by most of the subsystems. \ No newline at end of file diff --git a/makefile b/makefile deleted file mode 100644 index a04bd4ae..00000000 --- a/makefile +++ /dev/null @@ -1,4 +0,0 @@ -all: - mkdir -p build - g++ src/*.cpp src/p2p/*.cc -lsodium -lboost_system -lboost_filesystem -pthread -lprotobuf -std=c++17 -o build/hpcore - echo 'build successful, binary in '`pwd`'/build/hpcore' diff --git a/src/conf.cpp b/src/conf.cpp index 03c4d29a..96b6b0cc 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,6 +9,8 @@ #include #include #include "conf.h" +#include "crypto.h" +#include "shared.h" using namespace std; using namespace rapidjson; @@ -18,74 +21,35 @@ namespace conf ContractCtx ctx; ContractConfig cfg; -const char *cfg_schema = - "{" - "\"type\": \"object\"," - "\"required\": [ \"version\", \"pubkeyb64\", \"seckeyb64\", \"binary\", \"binargs\", \"listenip\"" - ", \"peers\", \"unl\", \"peerport\", \"roundtime\", \"pubport\", \"pubmaxsize\", \"pubmaxcpm\" ]," - "\"properties\": {" - "\"version\": { \"type\": \"string\" }," - "\"pubkeyb64\": { \"type\": \"string\" }," - "\"seckeyb64\": { \"type\": \"string\" }," - "\"binary\": { \"type\": \"string\" }," - "\"binargs\": { \"type\": \"string\" }," - "\"listenip\": { \"type\": \"string\" }," - "\"peers\": {" - "\"type\": \"array\"," - "\"items\": { \"type\": \"string\" }" - "}," - "\"unl\": {" - "\"type\": \"array\"," - "\"items\": { \"type\": \"string\" }" - "}," - "\"peerport\": { \"type\": \"integer\" }," - "\"roundtime\": { \"type\": \"integer\" }," - "\"pubport\": { \"type\": \"integer\" }," - "\"pubmaxsize\": { \"type\": \"integer\" }," - "\"pubmaxcpm\": { \"type\": \"integer\" }" - "}" - "}"; +bool validate_config(); +int load_config(); +void save_config(); +void binpair_to_b64(); +int b64pair_to_bin(); +bool validate_contract_dir_paths(); +bool is_schema_valid(Document &d); -// v1 < v2 -> -1 -// v1 == v2 -> 0 -// v1 > v2 -> +1 -int version_compare(std::string v1, std::string v2) +int init() { - size_t i = 0, j = 0; - while (i < v1.length() || j < v2.length()) - { - int acc1 = 0, acc2 = 0; + if (!validate_contract_dir_paths() || load_config() != 0 || !validate_config()) + return -1; - while (i < v1.length() && v1[i] != '.') - { - acc1 = acc1 * 10 + (v1[i] - '0'); - i++; - } - while (j < v2.length() && v2[j] != '.') - { - acc2 = acc2 * 10 + (v2[j] - '0'); - j++; - } - - if (acc1 < acc2) - return -1; - if (acc1 > acc2) - return +1; - - ++i; - ++j; - } return 0; } -bool is_schema_valid(Document &d) +int rekey() { - Document sd; - sd.Parse(cfg_schema); - SchemaDocument schema(sd); + if (load_config() != 0) + return -1; - SchemaValidator validator(schema); - return d.Accept(validator); + crypto::generate_signing_keys(cfg.pubkey, cfg.seckey); + binpair_to_b64(); + + save_config(); + + cout << "New signing keys generated at " << ctx.configFile << endl; + + return 0; } int load_config() @@ -97,22 +61,29 @@ int load_config() if (d.ParseStream(isw).HasParseError()) { cerr << "Invalid config file format. Parser error at position " << d.GetErrorOffset() << endl; - return 0; + return -1; } else if (!is_schema_valid(d)) { cerr << "Invalid config file format.\n"; - return 0; + return -1; } //Check contract version. - string cfgVersion = d["version"].GetString(); - if (version_compare(cfgVersion, _HP_MIN_CONTRACT_VERSION_) == -1) + string cfgversion = d["version"].GetString(); + if (cfgversion.empty()) + { + cerr << "Contract config version missing.\n"; + return -1; + } + + string minversion = string(_HP_MIN_CONTRACT_VERSION_); + if (shared::version_compare(cfgversion, minversion) == -1) { cerr << "Contract version too old. Minimum " << _HP_MIN_CONTRACT_VERSION_ << " required. " - << cfgVersion << " found.\n"; - return 0; + << cfgversion << " found.\n"; + return -1; } cfg.pubkeyb64 = d["pubkeyb64"].GetString(); @@ -135,7 +106,10 @@ int load_config() cfg.pubmaxsize = d["pubmaxsize"].GetInt(); cfg.pubmaxcpm = d["pubmaxcpm"].GetInt(); - return 1; + if (b64pair_to_bin() != 0) + return -1; + + return 0; } void save_config() @@ -151,22 +125,22 @@ void save_config() d.AddMember("listenip", StringRef(cfg.listenip.data()), allocator); Value peers(kArrayType); - d.AddMember("peers", peers, allocator); - for (int i = 0; i < cfg.peers.size(); i++) + for (string &peer : cfg.peers) { Value v; - v.SetString(StringRef(cfg.peers[i].data()), allocator); + v.SetString(StringRef(peer.data()), allocator); peers.PushBack(v, allocator); } + d.AddMember("peers", peers, allocator); Value unl(kArrayType); - d.AddMember("unl", unl, allocator); - for (int i = 0; i < cfg.unl.size(); i++) + for (string &node : cfg.unl) { Value v; - v.SetString(StringRef(cfg.unl[i].data()), allocator); + v.SetString(StringRef(node.data()), allocator); unl.PushBack(v, allocator); } + d.AddMember("unl", unl, allocator); d.AddMember("peerport", cfg.peerport, allocator); d.AddMember("roundtime", cfg.roundtime, allocator); @@ -181,6 +155,100 @@ void save_config() d.Accept(writer); } +int create_contract() +{ + if (boost::filesystem::exists(ctx.contractDir)) + { + cerr << "Contract dir already exists. Cannot create contract at the same location.\n"; + return -1; + } + + boost::filesystem::create_directories(ctx.configDir); + boost::filesystem::create_directories(ctx.histDir); + boost::filesystem::create_directories(ctx.stateDir); + + //Create config file with default settings. + + crypto::generate_signing_keys(cfg.pubkey, cfg.seckey); + binpair_to_b64(); + + cfg.listenip = "0.0.0.0"; + cfg.peerport = 22860; + cfg.roundtime = 1000; + cfg.pubport = 8080; + cfg.pubmaxsize = 65536; + cfg.pubmaxcpm = 100; + save_config(); + + cout << "Contract directory created at " << ctx.contractDir << endl; + + if (load_config() != 0) + return -1; + + return 0; +} + +void binpair_to_b64() +{ + shared::base64_encode((unsigned char *)cfg.pubkey.data(), crypto_sign_PUBLICKEYBYTES, cfg.pubkeyb64); + shared::base64_encode((unsigned char *)cfg.seckey.data(), crypto_sign_SECRETKEYBYTES, cfg.seckeyb64); +} + +int b64pair_to_bin() +{ + unsigned char decoded_pubkey[crypto_sign_PUBLICKEYBYTES]; + unsigned char decoded_seckey[crypto_sign_SECRETKEYBYTES]; + + if (shared::base64_decode(cfg.pubkeyb64, decoded_pubkey, crypto_sign_PUBLICKEYBYTES) != 0) + { + cerr << "Error decoding base64 public key.\n"; + return -1; + } + + if (shared::base64_decode(cfg.seckeyb64, decoded_seckey, crypto_sign_SECRETKEYBYTES) != 0) + { + cerr << "Error decoding base64 secret key.\n"; + return -1; + } + + shared::replace_string_contents(cfg.pubkey, (char *)decoded_pubkey, crypto_sign_PUBLICKEYBYTES); + shared::replace_string_contents(cfg.seckey, (char *)decoded_seckey, crypto_sign_SECRETKEYBYTES); + return 0; +} + +bool validate_config() +{ + if (cfg.pubkeyb64.empty() || cfg.seckeyb64.empty()) + { + cerr << "Signing keys missing. Run with 'rekey' to generate new keys.\n"; + return false; + } + + if (cfg.binary.empty() || cfg.listenip.empty() || + cfg.peerport == 0 || cfg.roundtime == 0 || cfg.pubport == 0 || cfg.pubmaxsize == 0 || cfg.pubmaxcpm == 0) + { + cerr << "Required configuration fields missing at " << ctx.configFile << endl; + return false; + } + + if (!boost::filesystem::exists(cfg.binary)) + { + cerr << "Contract binary does not exist: " << cfg.binary << endl; + return false; + } + + //Sign and verify a sample to ensure we have a matching signing key pair. + string msg = "hotpocket"; + string sigb64 = crypto::sign_b64(msg, cfg.seckeyb64); + if (!crypto::verify_b64(msg, sigb64, cfg.pubkeyb64)) + { + cerr << "Invalid signing keys. Run with 'rekey' to generate new keys.\n"; + return false; + } + + return true; +} + void set_contract_dir_paths(string basedir) { if (basedir[basedir.size() - 1] == '/') @@ -191,62 +259,60 @@ void set_contract_dir_paths(string basedir) ctx.configFile = ctx.configDir + "/hp.cfg"; ctx.histDir = basedir + "/hist"; ctx.stateDir = basedir + "/state"; - ctx.binDir = basedir + "/bin"; } -int create_contract() +bool validate_contract_dir_paths() { - if (boost::filesystem::exists(ctx.contractDir)) + string dirs[4] = {ctx.contractDir, ctx.configFile, ctx.histDir, ctx.stateDir}; + + for (string &dir : dirs) { - cerr << "Contract dir already exists.\n"; - return 0; + if (!boost::filesystem::exists(dir)) + { + cerr << dir << " does not exist.\n"; + return false; + } } - boost::filesystem::create_directories(ctx.configDir); - boost::filesystem::create_directories(ctx.binDir); - boost::filesystem::create_directories(ctx.histDir); - boost::filesystem::create_directories(ctx.stateDir); - - //Create config file with default settings. - cfg.listenip = "0.0.0.0"; - cfg.peerport = 22860; - cfg.roundtime = 1000; - cfg.pubport = 8080; - cfg.pubmaxsize = 65536; - cfg.pubmaxcpm = 100; - save_config(); - return 1; + return true; } -int clear_keys() +bool is_schema_valid(Document &d) { - cfg.pubkeyb64 = ""; - cfg.seckeyb64 = ""; - save_config(); -} + const char *cfg_schema = + "{" + "\"type\": \"object\"," + "\"required\": [ \"version\", \"pubkeyb64\", \"seckeyb64\", \"binary\", \"binargs\", \"listenip\"" + ", \"peers\", \"unl\", \"peerport\", \"roundtime\", \"pubport\", \"pubmaxsize\", \"pubmaxcpm\" ]," + "\"properties\": {" + "\"version\": { \"type\": \"string\" }," + "\"pubkeyb64\": { \"type\": \"string\" }," + "\"seckeyb64\": { \"type\": \"string\" }," + "\"binary\": { \"type\": \"string\" }," + "\"binargs\": { \"type\": \"string\" }," + "\"listenip\": { \"type\": \"string\" }," + "\"peers\": {" + "\"type\": \"array\"," + "\"items\": { \"type\": \"string\" }" + "}," + "\"unl\": {" + "\"type\": \"array\"," + "\"items\": { \"type\": \"string\" }" + "}," + "\"peerport\": { \"type\": \"integer\" }," + "\"roundtime\": { \"type\": \"integer\" }," + "\"pubport\": { \"type\": \"integer\" }," + "\"pubmaxsize\": { \"type\": \"integer\" }," + "\"pubmaxcpm\": { \"type\": \"integer\" }" + "}" + "}"; -int init(int argc, char **argv) -{ - if (ctx.command == "new") - { - if (!create_contract()) - return 0; - } + Document sd; + sd.Parse(cfg_schema); + SchemaDocument schema(sd); - if (!load_config()) - return 0; - - if (ctx.command == "rekey") - { - //Clear the keys. crpyto::init will automatically init the keys. - clear_keys(); - } - else if (ctx.command == "run") - { - //TO DO: Contract run logic. - } - - return 1; + SchemaValidator validator(schema); + return d.Accept(validator); } } // namespace conf \ No newline at end of file diff --git a/src/conf.h b/src/conf.h index 5a424f7e..b137a8ed 100644 --- a/src/conf.h +++ b/src/conf.h @@ -24,7 +24,6 @@ struct ContractCtx string contractDir; string histDir; string stateDir; - string binDir; string configDir; string configFile; }; @@ -35,9 +34,9 @@ struct ContractConfig Config elements which are only initialized in memory (these are not loaded from the config file) */ //public key bytes - unsigned char *pubkey; + string pubkey; //secret key bytes - unsigned char *seckey; + string seckey; /* Config elements which are loaded from the config file. @@ -58,8 +57,11 @@ struct ContractConfig extern ContractCtx ctx; extern ContractConfig cfg; -int init(int argc, char **argv); +int init(); +int rekey(); +int create_contract(); void set_contract_dir_paths(string basedir); +int load_config(); void save_config(); } // namespace conf diff --git a/src/crypto.cpp b/src/crypto.cpp index aa3456a0..973b3746 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,48 +1,54 @@ #include #include #include -#include "conf.h" #include "crypto.h" +#include "shared.h" using namespace std; -using namespace rapidjson; namespace crypto { -void generate_crypto_keys(); -string base64_encode(unsigned char *bin, size_t bin_len); -int base64_decode(string base64_str, unsigned char *decoded, size_t decoded_len); +void generate_signing_keys(); void binpair_to_b64(); int b64pair_to_bin(); -void sign(const unsigned char *msg, unsigned long long msg_len, unsigned char *sig) +string sign(string &msg, string &seckey) { - crypto_sign_detached(sig, NULL, msg, msg_len, conf::cfg.seckey); + unsigned char sigchars[crypto_sign_BYTES]; + crypto_sign_detached(sigchars, NULL, (unsigned char *)msg.data(), msg.length(), (unsigned char *)seckey.data()); + string sig((char *)sigchars, crypto_sign_BYTES); + return sig; } -string sign_b64(string msg) +string sign_b64(string &msg, string &seckeyb64) { + unsigned char seckey[crypto_sign_SECRETKEYBYTES]; + shared::base64_decode(seckeyb64, seckey, crypto_sign_SECRETKEYBYTES); + unsigned char sig[crypto_sign_BYTES]; - crypto_sign_detached(sig, NULL, (unsigned char *)msg.data(), msg.size() + 1, conf::cfg.seckey); - return base64_encode(sig, crypto_sign_BYTES); + crypto_sign_detached(sig, NULL, (unsigned char *)msg.data(), msg.length(), seckey); + string sigb64; + shared::base64_encode(sig, crypto_sign_BYTES, sigb64); + return sigb64; } -bool verify(const unsigned char *msg, unsigned long long msg_len, const unsigned char *sig, const unsigned char *pubkey) +bool verify(string &msg, string &sig, string &pubkey) { - int result = crypto_sign_verify_detached(sig, msg, msg_len, pubkey); + int result = crypto_sign_verify_detached( + (unsigned char *)sig.data(), (unsigned char *)msg.data(), msg.length(), (unsigned char *)pubkey.data()); return result == 0; } -bool verify_b64(string msg, string sigb64, string pubkeyb64) +bool verify_b64(string &msg, string &sigb64, string &pubkeyb64) { unsigned char decoded_pubkey[crypto_sign_PUBLICKEYBYTES]; - base64_decode(pubkeyb64, decoded_pubkey, crypto_sign_PUBLICKEYBYTES); + shared::base64_decode(pubkeyb64, decoded_pubkey, crypto_sign_PUBLICKEYBYTES); unsigned char decoded_sig[crypto_sign_BYTES]; - base64_decode(sigb64, decoded_sig, crypto_sign_BYTES); + shared::base64_decode(sigb64, decoded_sig, crypto_sign_BYTES); - int result = crypto_sign_verify_detached(decoded_sig, (unsigned char *)msg.data(), msg.size() + 1, decoded_pubkey); + int result = crypto_sign_verify_detached(decoded_sig, (unsigned char *)msg.data(), msg.length(), decoded_pubkey); return result == 0; } @@ -51,122 +57,20 @@ int init() if (sodium_init() < 0) { cerr << "sodium_init failed.\n"; - return 0; + return -1; } - if (conf::ctx.command == "new" || conf::ctx.command == "rekey") - { - cout << "Generating new keys.\n"; - generate_crypto_keys(); - binpair_to_b64(); - - conf::save_config(); - } - else if (conf::ctx.command == "run") - { - if (conf::cfg.pubkeyb64.empty() || conf::cfg.seckeyb64.empty()) - { - cerr << "Signing keys missing. Run with 'rekey' to generate new keys.\n"; - return 0; - } - else - { - //Decode b64 keys into bytes and store in memory. - if (!b64pair_to_bin()) - return 0; - - //Sign and verify a sample to ensure we have a matching key pair. - string msg = "hotpocket"; - string sigb64 = sign_b64(msg); - if (!verify_b64(msg, sigb64, conf::cfg.pubkeyb64)) - { - cerr << "Invalid signing keys. Run with 'rekey' to generate new keys.\n"; - return 0; - } - } - } - - return 1; + return 0; } -void generate_crypto_keys() +void generate_signing_keys(string &pubkey, string &seckey) { - if (conf::cfg.pubkey != NULL) - free(conf::cfg.pubkey); + unsigned char pubkeychars[crypto_sign_PUBLICKEYBYTES]; + unsigned char seckeychars[crypto_sign_SECRETKEYBYTES]; + crypto_sign_keypair(pubkeychars, seckeychars); - if (conf::cfg.seckey != NULL) - free(conf::cfg.seckey); - - conf::cfg.pubkey = (unsigned char *)malloc(crypto_sign_PUBLICKEYBYTES); - conf::cfg.seckey = (unsigned char *)malloc(crypto_sign_SECRETKEYBYTES); - crypto_sign_keypair(conf::cfg.pubkey, conf::cfg.seckey); -} - -string base64_encode(unsigned char *bin, size_t bin_len) -{ - const size_t base64_max_len = sodium_base64_encoded_len(bin_len, sodium_base64_VARIANT_ORIGINAL); - char base64_str[base64_max_len]; - - char *encoded_str_char = sodium_bin2base64( - base64_str, base64_max_len, - bin, bin_len, - sodium_base64_VARIANT_ORIGINAL); - - if (encoded_str_char == NULL) - throw "Base64 Error: Failed to encode string"; - - string s(base64_str); - return s; -} - -int base64_decode(string base64_str, unsigned char *decoded, size_t decoded_len) -{ - const char *b64_end; - size_t bin_len; - if (sodium_base642bin( - decoded, decoded_len, - base64_str.data(), base64_str.size() + 1, - "", &bin_len, &b64_end, - sodium_base64_VARIANT_ORIGINAL)) - { - return 0; - } - - return 1; -} - -void binpair_to_b64() -{ - conf::cfg.pubkeyb64 = base64_encode(conf::cfg.pubkey, crypto_sign_PUBLICKEYBYTES); - conf::cfg.seckeyb64 = base64_encode(conf::cfg.seckey, crypto_sign_SECRETKEYBYTES); -} - -int b64pair_to_bin() -{ - unsigned char *decoded_pubkey = (unsigned char *)malloc(crypto_sign_PUBLICKEYBYTES); - unsigned char *decoded_seckey = (unsigned char *)malloc(crypto_sign_SECRETKEYBYTES); - - if (!base64_decode(conf::cfg.pubkeyb64, decoded_pubkey, crypto_sign_PUBLICKEYBYTES)) - { - cerr << "Error decoding public key.\n"; - return 0; - } - - if (!base64_decode(conf::cfg.seckeyb64, decoded_seckey, crypto_sign_SECRETKEYBYTES)) - { - cerr << "Error decoding secret key.\n"; - return 0; - } - - if (conf::cfg.pubkey != NULL) - free(conf::cfg.pubkey); - - if (conf::cfg.seckey != NULL) - free(conf::cfg.seckey); - - conf::cfg.pubkey = decoded_pubkey; - conf::cfg.seckey = decoded_seckey; - return 1; + shared::replace_string_contents(pubkey, (char *)pubkeychars, crypto_sign_PUBLICKEYBYTES); + shared::replace_string_contents(seckey, (char *)seckeychars, crypto_sign_SECRETKEYBYTES); } } // namespace crypto \ No newline at end of file diff --git a/src/crypto.h b/src/crypto.h index 925553c9..dd6e0e14 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -8,25 +8,27 @@ namespace crypto int init(); -/** - * Generates the signature for the given message using the contract's secret key. - */ -void sign(const unsigned char *msg, unsigned long long msg_len, unsigned char *sig); +void generate_signing_keys(string &pubkey, string &seckey); /** - * Returns the base64 signature for the given message using the contract's secret key. + * Returns the signature bytes for the given message bytes using the provided secret key bytes. */ -string sign_b64(string msg); +string sign(string &msg, string &seckey); /** - * Verifies the given signature with the message using the provided public key. + * Returns the base64 signature for the given message bytes using the provided base64 secret key. */ -bool verify(const unsigned char *msg, unsigned long long msg_len, const unsigned char *sig, const unsigned char *pubkey); +string sign_b64(string &msg, string &seckeyb64); /** - * Verifies the given base64 signature with the message using the provided base64 public key. + * Verifies the given signature bytes for the message bytes using the provided public key bytes. */ -bool verify_b64(string msg, string sigb64, string pubkeyb64); +bool verify(string &msg, string &sig, string &pubkey); + +/** + * Verifies the given base64 signature with the message bytes using the provided base64 public key. + */ +bool verify_b64(string &msg, string &sigb64, string &pubkeyb64); } // namespace crypto diff --git a/src/main.cpp b/src/main.cpp index fbb58387..46586f59 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include "conf.h" #include "crypto.h" +#include "usr/usr.h" using namespace std; @@ -13,7 +14,7 @@ int parse_cmd(int argc, char **argv); int main(int argc, char **argv) { - if (!parse_cmd(argc, argv)) + if (parse_cmd(argc, argv) != 0) return -1; if (conf::ctx.command == "version") @@ -22,11 +23,26 @@ int main(int argc, char **argv) } else { - bool initSuccess = conf::init(argc, argv) && crypto::init(); - if (!initSuccess) - { - cerr << "Init error\n"; + if (crypto::init() != 0) return -1; + + if (conf::ctx.command == "new") + { + if (conf::create_contract() != 0) + return -1; + } + else + { + if (conf::ctx.command == "rekey") + { + if (conf::rekey() != 0) + return -1; + } + else if (conf::ctx.command == "run") + { + if (conf::init() != 0 || usr::init() != 0) + return -1; + } } } @@ -49,13 +65,14 @@ int parse_cmd(int argc, char **argv) else { conf::set_contract_dir_paths(argv[2]); - return 1; + + return 0; } } else if (command == "version") { if (argc == 2) - return 1; + return 0; } } @@ -65,5 +82,5 @@ int parse_cmd(int argc, char **argv) cout << "hpcore (command = run | new | rekey)\n"; cout << "Example: hpcore run ~/mycontract\n"; - return 0; + return -1; } \ No newline at end of file diff --git a/src/proc.cpp b/src/proc.cpp new file mode 100644 index 00000000..72daca67 --- /dev/null +++ b/src/proc.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "proc.h" +#include "conf.h" + +using namespace std; +using namespace shared; + +namespace proc +{ + +/** + * Keeps the currently executing contract process id (if any) + */ +int contract_pid; + +int write_to_stdin(ContractExecArgs &args); +bool is_contract_running(); + +int exec_contract(ContractExecArgs &args) +{ + if (is_contract_running()) + { + cerr << "Contract process still running.\n"; + return -1; + } + + int pid = fork(); + if (pid > 0) + { + //HotPocket process. + + contract_pid = pid; + } + else if (pid == 0) + { + //Contract process. + //Set up the process environment and overlay the contract binary program with execv(). + + //Set the contract process working directory. + chdir(conf::ctx.contractDir.data()); + + //Write the contract input message from HotPocket to the stdin (0) of the contract process. + write_to_stdin(args); + + char *execv_args[] = {conf::cfg.binary.data(), conf::cfg.binargs.data(), NULL}; + execv(execv_args[0], execv_args); + } + else + { + cerr << "fork() failed.\n"; + return -1; + } + + return 0; +} + +/** + * Writes the contract input message into the stdin of the contract process. + * Input format: + * { + * "version":"", + * "pubkey": "", + * "ts": , + * "usrfd":{ "pkb64":[fd0, fd1], ... }, + * "nplfd":{ "pkb64":[fd0, fd1], ... }, + * "unl":[ "pkb64", ... ] + * } + */ +int write_to_stdin(ContractExecArgs &args) +{ + Document d; + d.SetObject(); + Document::AllocatorType &allocator = d.GetAllocator(); + + d.AddMember("version", StringRef(_HP_VERSION_), allocator); + d.AddMember("pubkey", StringRef(conf::cfg.pubkeyb64.data()), allocator); + d.AddMember("ts", args.timestamp, allocator); + + Value users(kObjectType); + for (auto &[pk, user] : args.users) + { + Value fdlist(kArrayType); + fdlist.PushBack(user.inpipe[0], allocator); + fdlist.PushBack(user.outpipe[1], allocator); + users.AddMember(StringRef(user.pubkeyb64.data()), fdlist, allocator); + } + d.AddMember("usrfd", users, allocator); + + Value peers(kObjectType); + for (auto &[pk, peer] : args.peers) + { + Value fdlist(kArrayType); + fdlist.PushBack(peer.inpipe[0], allocator); + fdlist.PushBack(peer.outpipe[1], allocator); + peers.AddMember(StringRef(peer.pubkeyb64.data()), fdlist, allocator); + } + d.AddMember("nplfd", peers, allocator); + + Value unl(kArrayType); + for (string &node : conf::cfg.unl) + unl.PushBack(StringRef(node.data()), allocator); + d.AddMember("unl", unl, allocator); + + StringBuffer buffer; + Writer writer(buffer); + d.Accept(writer); + const char *json = buffer.GetString(); + + int stdinpipe[2]; + if (pipe(stdinpipe) != 0) + { + cerr << "Failed to create pipe to the contract process.\n"; + return -1; + } + + dup2(stdinpipe[0], STDIN_FILENO); + close(stdinpipe[0]); + + write(stdinpipe[1], json, buffer.GetSize()); + close(stdinpipe[1]); + + return 0; +} + +bool is_contract_running() +{ + if (contract_pid > 0) + { + int status = 0; + if (!waitpid(contract_pid, &status, WNOHANG)) + return true; + contract_pid = 0; + } + + return false; +} + +} // namespace proc \ No newline at end of file diff --git a/src/proc.h b/src/proc.h new file mode 100644 index 00000000..5d98aa57 --- /dev/null +++ b/src/proc.h @@ -0,0 +1,32 @@ +#ifndef _HP_PROC_H_ +#define _HP_PROC_H_ + +#include +#include +#include "shared.h" + +using namespace std; +using namespace shared; + +namespace proc +{ + +struct ContractExecArgs +{ + map &users; + map &peers; + uint64_t timestamp; + + ContractExecArgs(map &_users, map &_peers, uint64_t _timestamp) + : users(_users), peers(_peers) + { + timestamp = _timestamp; + } +}; + +int exec_contract(ContractExecArgs &args); +bool is_contract_running(); + +} // namespace proc + +#endif \ No newline at end of file diff --git a/src/shared.cpp b/src/shared.cpp new file mode 100644 index 00000000..f5beba17 --- /dev/null +++ b/src/shared.cpp @@ -0,0 +1,83 @@ +#include +#include + +using namespace std; + +namespace shared +{ + +void replace_string_contents(string &str, const char *bytes, size_t bytes_len); + +int base64_encode(unsigned char *bin, size_t bin_len, string &encoded_string) +{ + const size_t base64_len = sodium_base64_encoded_len(bin_len, sodium_base64_VARIANT_ORIGINAL); + char base64chars[base64_len]; + + char *encoded_str_char = sodium_bin2base64( + base64chars, base64_len, + bin, bin_len, + sodium_base64_VARIANT_ORIGINAL); + + if (encoded_str_char == NULL) + return -1; + + replace_string_contents(encoded_string, base64chars, base64_len); + return 0; +} + +int base64_decode(string &base64_str, unsigned char *decoded, size_t decoded_len) +{ + const char *b64_end; + size_t bin_len; + if (sodium_base642bin( + decoded, decoded_len, + base64_str.data(), base64_str.size() + 1, + "", &bin_len, &b64_end, + sodium_base64_VARIANT_ORIGINAL)) + { + return -1; + } + + return 0; +} + +void replace_string_contents(string &str, const char *bytes, size_t bytes_len) +{ + if (str.length() > 0) + str.clear(); + str.append(bytes, bytes_len); +} + +// v1 < v2 -> -1 +// v1 == v2 -> 0 +// v1 > v2 -> +1 +int version_compare(string &v1, string &v2) +{ + size_t i = 0, j = 0; + while (i < v1.length() || j < v2.length()) + { + int acc1 = 0, acc2 = 0; + + while (i < v1.length() && v1[i] != '.') + { + acc1 = acc1 * 10 + (v1[i] - '0'); + i++; + } + while (j < v2.length() && v2[j] != '.') + { + acc2 = acc2 * 10 + (v2[j] - '0'); + j++; + } + + if (acc1 < acc2) + return -1; + if (acc1 > acc2) + return +1; + + ++i; + ++j; + } + return 0; +} + +} // namespace shared \ No newline at end of file diff --git a/src/shared.h b/src/shared.h new file mode 100644 index 00000000..39725a3c --- /dev/null +++ b/src/shared.h @@ -0,0 +1,52 @@ +#ifndef _HP_SHARED_H_ +#define _HP_SHARED_H_ + +#include +#include + +using namespace std; + +namespace shared +{ + +struct ContractUser +{ + string pubkeyb64; + int inpipe[2]; + int outpipe[2]; + string outbuffer; + + ContractUser(string _pubkeyb64, int _inpipe[2], int _outpipe[2]) + { + pubkeyb64 = _pubkeyb64; + inpipe[0] = _inpipe[0]; + inpipe[1] = _inpipe[1]; + outpipe[0] = _outpipe[0]; + outpipe[1] = _outpipe[1]; + } +}; + +struct PeerNode +{ + string pubkeyb64; + int inpipe[2]; + int outpipe[2]; + + PeerNode(string _pubkeyb64, int _inpipe[2], int _outpipe[2]) + { + pubkeyb64 = _pubkeyb64; + inpipe[0] = _inpipe[0]; + inpipe[1] = _inpipe[1]; + outpipe[0] = _outpipe[0]; + outpipe[1] = _outpipe[1]; + } +}; + +int base64_encode(unsigned char *bin, size_t bin_len, string &encoded_string); +int base64_decode(string &base64_str, unsigned char *decoded, size_t decoded_len); +void replace_string_contents(string &str, const char* bytes, size_t bytes_len); +int version_compare(string &v1, string &v2); + +} // namespace usr + +#endif \ No newline at end of file diff --git a/src/usr/usr.cpp b/src/usr/usr.cpp new file mode 100644 index 00000000..78dcaadc --- /dev/null +++ b/src/usr/usr.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../shared.h" +#include "../conf.h" +#include "../crypto.h" +#include "usr.h" + +using namespace std; +using namespace shared; +using namespace rapidjson; + +namespace usr +{ + +map users; +Document challenge_response_schemadoc; + +void create_user_challenge(string &msg, string &challenge) +{ + unsigned char challenge_bytes[USER_CHALLENGE_LEN]; + randombytes_buf(challenge_bytes, USER_CHALLENGE_LEN); + + base64_encode(challenge_bytes, USER_CHALLENGE_LEN, challenge); + + Document d; + d.SetObject(); + Document::AllocatorType &allocator = d.GetAllocator(); + d.AddMember("version", StringRef(_HP_VERSION_), allocator); + d.AddMember("type", "public_challenge", allocator); + d.AddMember("challenge", StringRef(challenge.data()), allocator); + + StringBuffer buffer; + Writer writer(buffer); + d.Accept(writer); + msg = buffer.GetString(); +} + +bool verify_user_challenge_response(string &response, string &original_challenge, string &extracted_pubkeyb64) +{ + Document d; + d.Parse(response.data()); + + SchemaDocument schema(challenge_response_schemadoc); + SchemaValidator validator(schema); + if (!d.Accept(validator)) + { + cerr << "User challenge resposne schema invalid.\n"; + return false; + } + + string type = d["type"].GetString(); + if (type != "challenge_response") + { + cerr << "User challenge response type invalid. 'challenge_response' expeced.\n"; + return false; + } + + string challenge = d["challenge"].GetString(); + string sigb64 = d["sig"].GetString(); + string pubkeyb64 = d["pubkey"].GetString(); + + if (challenge != original_challenge) + { + cerr << "User challenge resposne: challenge mismatch.\n"; + return false; + } + + if (!crypto::verify_b64(original_challenge, sigb64, pubkeyb64)) + { + cerr << "User challenge response signature verification failed.\n"; + return false; + } + + extracted_pubkeyb64 = pubkeyb64; + return true; +} + +void add_user(string &pubkeyb64) +{ + if (users.count(pubkeyb64) == 1) + { + cerr << pubkeyb64 << " already exist. Cannot add user.\n"; + return; + } + + int inpipe[2]; + int outpipe[2]; + if (pipe(inpipe) != 0 || pipe(outpipe) != 0) + { + cerr << "User pipe creation failed. pubkey:" << pubkeyb64 << endl; + return; + } + + users.insert(pair(pubkeyb64, ContractUser(pubkeyb64, inpipe, outpipe))); +} + +void remove_user(string &pubkeyb64) +{ + if (users.count(pubkeyb64) == 0) + { + cerr << pubkeyb64 << " does not exist. Cannot remove user.\n"; + return; + } + + auto itr = users.find(pubkeyb64); + ContractUser user = itr->second; + close(user.inpipe[0]); + close(user.inpipe[1]); + close(user.outpipe[0]); + close(user.outpipe[1]); + + users.erase(itr); +} + +//Read per-user outputs produced by the contract process. +int read_contract_user_outputs() +{ + for (auto &[pk, user] : users) + { + int fdout = user.outpipe[0]; + int bytes_available = 0; + ioctl(fdout, FIONREAD, &bytes_available); + + if (bytes_available > 0) + { + char data[bytes_available]; + read(fdout, data, bytes_available); + + //Populate the user output buffer with new data + shared::replace_string_contents(user.outbuffer, data, bytes_available); + + cout << "Read " + to_string(bytes_available) << " bytes into user output buffer. user:" + user.pubkeyb64 << endl; + } + } + + return 0; +} + +int init() +{ + const char *challenge_response_schema = + "{" + "\"type\": \"object\"," + "\"required\": [ \"type\", \"challenge\", \"sig\", \"pubkey\" ]," + "\"properties\": {" + "\"type\": { \"type\": \"string\" }," + "\"challenge\": { \"type\": \"string\" }," + "\"sig\": { \"type\": \"string\" }," + "\"pubkey\": { \"type\": \"string\" }" + "}" + "}"; + + challenge_response_schemadoc.Parse(challenge_response_schema); + + return 0; +} + +} // namespace usr \ No newline at end of file diff --git a/src/usr/usr.h b/src/usr/usr.h new file mode 100644 index 00000000..cdd0e987 --- /dev/null +++ b/src/usr/usr.h @@ -0,0 +1,26 @@ +#ifndef _HP_USR_H_ +#define _HP_USR_H_ + +#define USER_CHALLENGE_LEN 16 + +#include +#include +#include +#include "../shared.h" + +using namespace std; +using namespace shared; + +namespace usr +{ +extern map users; + +int init(); +void create_user_challenge(string &msg, string &challenge); +bool verify_user_challenge_response(string &response, string &original_challenge, string &extracted_pubkey); +void add_user(string &pubkeyb64); +void remove_user(string &pubkeyb64); + +} // namespace usr + +#endif \ No newline at end of file