diff --git a/src/Manifest.cpp b/src/Manifest.cpp new file mode 100644 index 0000000..8ab9f1a --- /dev/null +++ b/src/Manifest.cpp @@ -0,0 +1,79 @@ +#include "Manifest.h" +#include +#include +#include +#include +#include + +namespace vlist +{ + +Manifest::Manifest (std::string const& raw) : + m_ (ripple::SerialIter (raw.data(), raw.size()), ripple::sfGeneric) +{ ; } + +Manifest::Manifest (ripple::PublicKey const& master, ripple::PublicKey const& ephemeral, uint32_t seq) : + m_ (ripple::sfGeneric) +{ + using namespace ripple; + m_[sfPublicKey] = master; + m_[sfSigningPubKey] = ephemeral; + m_[sfSequence] = seq; +} + +bool Manifest::isValid () const +{ + using namespace ripple; + + // not a complete check + return + m_.isFieldPresent (sfSequence) && + m_.isFieldPresent (sfPublicKey) && + m_.isFieldPresent (sfSigningPubKey) && + publicKeyType (makeSlice(m_.getFieldVL (sfPublicKey))); +} + + +void Manifest::signMaster (ripple::SecretKey const& master) +{ + using namespace ripple; + ripple::sign (m_, HashPrefix::manifest, KeyType::ed25519, master, sfMasterSignature); +} + +void Manifest::signEphemeral (ripple::SecretKey const& ephemeral) +{ + using namespace ripple; + ripple::sign (m_, HashPrefix::manifest, KeyType::ed25519, ephemeral); +} + +std::string Manifest::getB64() const +{ + using namespace ripple; + + Serializer s; + m_.add (s); + return beast::detail::base64_encode (std::string { + reinterpret_cast(s.data()), + s.size()}); +} + +ripple::PublicKey Manifest::getPublicKey() const +{ + using namespace ripple; + + return PublicKey (makeSlice(m_.getFieldVL (sfPublicKey))); +} + +Manifest +makeManifest (std::pair const& mSecKey, + std::pair const& ephemKey, std::uint32_t seq) +{ + using namespace ripple; + + Manifest m (mSecKey.first, ephemKey.first, seq); + m.signMaster (mSecKey.second); + m.signEphemeral (ephemKey.second); + return m; +} + +} // vlist diff --git a/src/Manifest.h b/src/Manifest.h new file mode 100644 index 0000000..e02b1b4 --- /dev/null +++ b/src/Manifest.h @@ -0,0 +1,40 @@ +#ifndef _W_MANIFEST_H_ +#define _W_MANIFEST_H_ + +#include +#include + +namespace vlist +{ + +class Manifest +{ +protected: + ripple::STObject m_; + +public: + Manifest (ripple::STObject const& m) : m_ (m) { ; } + + Manifest () : m_ (ripple::sfGeneric) { ; } + + explicit Manifest (std::string const& raw); + + Manifest (ripple::PublicKey const& master, ripple::PublicKey const& ephemeral, uint32_t seq); + + bool isValid () const; + + void signMaster (ripple::SecretKey const& master); + + void signEphemeral (ripple::SecretKey const& ephemeral); + + std::string getB64() const; + + ripple::PublicKey getPublicKey() const; +}; + +Manifest +makeManifest (std::pair const& mSecKey, + std::pair const& ephemKey, std::uint32_t seq); + +} +#endif diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..fb8aa91 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,112 @@ +#include "utils.h" +#include +#include +#include +#include "Manifest.h" + +// Will need porting to other platforms +// Used to get password/passphrase without echoing +#include + +std::string toBase64 (std::string const& in) +{ + return beast::detail::base64_encode (in); +} + +boost::optional signUNL ( + ripple::SecretKey const& ephemSecKey, + std::string const& manifest, + uint32_t sequence, + uint32_t expiration, + std::vector const& manifests) +{ + using namespace ripple; + + std::string data = + "{\"sequence\":" + std::to_string(sequence) + + ",\"expiration\":" + std::to_string(expiration) + + ",\"validators\":["; + + std::string valsMsg = "Adding the following validator public keys to the list:\n"; + + for (auto const& manifest : manifests) + { + try + { + vlist::Manifest m (beast::detail::base64_decode(manifest)); + if (! m.isValid()) { + std::cout << "Invalid manifest:" << std::endl; + std::cout << manifest << std::endl; + return boost::none; + } + auto const pubKey = m.getPublicKey(); + valsMsg += toBase58(TokenType::TOKEN_NODE_PUBLIC, pubKey) + "\n"; + data += "{\"validation_public_key\":\"" + strHex(pubKey) + "\"," + "\"manifest\":\"" + manifest + "\"},"; + } + catch (...) + { + std::cout << "Invalid manifest:" << std::endl; + std::cout << manifest << std::endl; + return boost::none; + } + } + + data.pop_back(); + data += "]}"; + std::cout << valsMsg << std::endl; + + auto pubKey = derivePublicKey (KeyType::ed25519, ephemSecKey); + + Json::Value jv; + jv["blob"] = toBase64 (data); + jv["manifest"] = manifest; + jv["signature"] = strHex (sign (pubKey, ephemSecKey, makeSlice(data))); + jv["version"] = 1; + + return pretty(jv); +} + +// Read a character without echoing +// Will need porting to other platforms +int tc_getch() +{ + struct termios t_old, t_new; + + tcgetattr(0, &t_old); + t_new = t_old; + t_new.c_lflag &= ~(ICANON | ECHO); + + tcsetattr(0, TCSANOW, &t_new); + auto ch = getchar(); + tcsetattr(0, TCSANOW, &t_old); + + return ch; +} + +// Read a password, echoing *'s +ripple::SecretKey getPass() +{ + std::string pass; + int ch; + + while ((ch = tc_getch()) != 10) + { + if (ch == 127) + { + // backspace + if (pass.length() > 0) + { + std::cout << "\b \b" << std::flush; + pass.resize (pass.length() - 1); + } + } + else + { + pass += static_cast(ch); + std::cout << '*' << std::flush; + } + } + std::cout << std::endl; + return ripple::SecretKey(ripple::makeSlice(std::move(ripple::strUnHex(pass).first))); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..bdb11cd --- /dev/null +++ b/src/utils.h @@ -0,0 +1,18 @@ +#ifndef _W_UTILS_H_ +#define _W_UTILS_H_ + +#include +#include + +std::string toBase64 (std::string const& in); + +boost::optional signUNL ( + ripple::SecretKey const& ephemSecKey, + std::string const& manifest, + uint32_t sequence, + uint32_t expiration, + std::vector const& manifests); + +ripple::SecretKey getPass (); + +#endif diff --git a/src/validator-list-tool.cpp b/src/validator-list-tool.cpp new file mode 100644 index 0000000..6c3c47a --- /dev/null +++ b/src/validator-list-tool.cpp @@ -0,0 +1,147 @@ +#include "Manifest.h" +#include "utils.h" +#include +#include +#include + +int read_selection() +{ + std::string line; + do + { + std::getline(std::cin, line); + if (line.empty() && std::cout.fail()) + return -1; + else + return std::atoi(line.c_str()); + } while(1); +} + +void sign_unl () +{ + std::string manifest; + uint32_t sequence; + uint32_t expiration; + int days; + std::vector manifests; + + std::cout << std::endl << "Enter ephemeral private key:" << std::endl; + ripple::SecretKey const secretKey = getPass(); + + std::cout << std::endl << "Enter ephemeral key manifest:" << std::endl; + std::cin >> manifest; + std::cin.ignore(); + + std::cout << std::endl << "Sequence number: "; + std::cin >> sequence; + std::cin.ignore(); + + std::cout << std::endl << "Validity in days: "; + std::cin >> days; + std::cin.ignore(); + + // convert validity in days to seconds since 1/1/2000 + auto now = time (NULL) - 946684800; + expiration = now - (now % 86400) + ((days + 1) * 86400); + + std::cout << std::endl << "Enter validator manifests, ending with a blank line:" << + std::endl; + while (1) + { + std::string j; + std::getline (std::cin, j); + if (j.empty()) + break; + // check validity, WRITEME + manifests.push_back (j); + } + + auto unl = signUNL (secretKey, manifest, sequence, expiration, manifests); + if (unl) + { + std::cout << *unl << std::endl << std::endl; + } +} + +bool validator_list_operations () +{ + while (1) + { + std::cout << std::endl << std::endl << std::endl << std::endl; + printf("Validator List menu\n\n"); + + printf("1) Create validator list publisher keys\n\n"); + printf("2) Sign validator list\n\n"); + + printf("9) Quit\n\n"); + + switch (read_selection()) + { + case 1: + { + std::cout << "\nSelect a name for this credential set: "; + std::string name; + std::cin >> name; + std::cin.ignore(); + std::cout << std::endl; + if (mkdir (name.c_str(), 0700) != 0) + { + std::cout << "Unable to create directory" << std::endl; + break; + } + + using namespace ripple; + + auto const masterKey = randomKeyPair(KeyType::ed25519); + + for (std::uint32_t seq = 1; seq <= 10; ++seq) + { + auto const newKey = randomKeyPair(KeyType::ed25519); + auto manifest = vlist::makeManifest (masterKey, newKey, seq); + std::ofstream f; + f.open (name + "/ephkey" + std::to_string (seq) + ".txt"); + if (! f.is_open()) + { + std::cout << "Unable to open file" << std::endl; + break; + } + f << "Private Key:\n\n" << + newKey.second.to_string() << std::endl; + + f << "---------------------" << std::endl << std::endl; + f << "Manifest:\n\n" << manifest.getB64() << std::endl; + } + + std::ofstream priv, pub; + priv.open ("privkeys.txt", std::fstream::app); + pub.open ("pubkeys.txt", std::fstream::app); + priv << name << " privkey: " << + masterKey.second.to_string() << std::endl; + pub << name << " pubkey: " << strHex(masterKey.first) << std::endl; + std::cout << "Publisher keys stored in privkeys.txt and pubkeys.txt" << std::endl; + std::cout << "Ephemeral keys stored in " << name << "/" << std::endl; + break; + } + + case 2: + sign_unl (); + break; + + case 9: + case -1: + return false; + } + return true; + } +} + +int main (int argc, char *argv[]) +{ + while(1) + { + if (! validator_list_operations()) + break; + } + + return 0; +}