From a040b7d9559bd358b89960f068121ecfb454c259 Mon Sep 17 00:00:00 2001 From: edtubbs Date: Fri, 12 Jan 2024 14:48:45 -0600 Subject: [PATCH] wallet: added prompt to dogecoin_wallet_load headersdb_file: added prompt to dogecoin_headers_db_load headersdb_file: removed check from dogecoin_headers_db_free spv: moved fcntl to dogecoin_spv_client_discover_peers spvnode: added handle_sigint to reset stdin to blocking spvnode: added dogecoin_spv_client_free and dogecoin_ecc_stop on failure wallet: added prompt to dogecoin_wallet_init wallet: added encryption of random seed with software wallet: added dogecoin_wallet_free to dogecoin_wallet_init on failures such: added seed_to_master_key command doc: added seed_to_master_key to tools.md --- doc/tools.md | 16 +++++++++--- src/cli/spvnode.c | 14 ++++++++++ src/cli/such.c | 46 +++++++++++++++++++++++++++++++++ src/headersdb_file.c | 30 +++++++++++++++++++--- src/spv.c | 12 ++++----- src/wallet.c | 61 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 164 insertions(+), 15 deletions(-) diff --git a/doc/tools.md b/doc/tools.md index a8f461f59..900916918 100644 --- a/doc/tools.md +++ b/doc/tools.md @@ -20,6 +20,7 @@ The `such` tool can be used by simply running the command `./such` in the top le - list_encryption_keys_in_tpm - decrypt_master_key - decrypt_mnemonic +- seed_to_master_key - mnemonic_to_key - mnemonic_to_addresses - print_keys @@ -47,10 +48,10 @@ Most of these commands require a flag following them to denote things like exist | -o, --account_int | account_int | yes | mnemonic_to_key or mnemonic_to_addresses -n -o | | -g, --change_level | change_level | yes | mnemonic_to_key or mnemonic_to_addresses -n -g | | -i, --address_index | address_index | yes | mnemonic_to_key or mnemonic_to_addresses -n -i | -| -y, --encrypted_file | file_num | yes | generate_mnemonic, bip32_extended_master_key, decrypt_master_key, decrypt_mnemonic, mnemonic_to_key or mnemonic_to_addresses -y +| -y, --encrypted_file | file_num | yes | generate_mnemonic, bip32_extended_master_key, decrypt_master_key, decrypt_mnemonic, seed_to_master_key, mnemonic_to_key or mnemonic_to_addresses -y | -w, --overwrite | overwrite | no | generate_mnemonic or bip32_extended_master_key -w | | -b, --silent | silent | no | generate_mnemonic or bip32_extended_master_key -b | -| -j, --use_tpm | use_tpm | no | generate_mnemonic, bip32_extended_master_key, decrypt_master_key, decrypt_mnemonic, mnemonic_to_key or mnemonic_to_addresses -j | +| -j, --use_tpm | use_tpm | no | generate_mnemonic, bip32_extended_master_key, decrypt_master_key, decrypt_mnemonic, seed_to_master_key, mnemonic_to_key or mnemonic_to_addresses -j | | -t, --testnet | designate_testnet | no | generate_private_key -t | | -s | script_hex | yes | comp2der -s | | -x | transaction_hex | yes | sign -x -s -i -h | @@ -71,6 +72,7 @@ Below is a list of all the commands and the flags that they require. As a remind | list_encryption_keys_in_tpm | None | None | List the encryption keys in the TPM. | | decrypt_master_key | -y | -j | Decrypt the master key with the TPM or SW. | | decrypt_mnemonic | -y | -j | Decrypt the mnemonic with the TPM or SW. | +| seed_to_master_key | -y | -j, -t | Generates an extended master private key from a seed for either mainnet or testnet. | | mnemonic_to_key | -n | -a, -y, -o, g, -i, -t | Generates a private key from a seed phrase with a default path or specified account, change level and index for either mainnet or testnet. | | mnemonic_to_addresses | -n | -a, -y, -o, g, -i, -t | Generates an address from a seed phrase with a default path or specified account, change level and index for either mainnet or testnet. | | print_keys | -p | -t | Print all keys associated with the provided private key. @@ -172,9 +174,9 @@ Below are some examples on how to use the `such` tool in practice. ./such -c verifymessage -x bleh -s ICrbftD0KamyaB68IoXbeke3w4CpcIvv+Q4pncBNpMk8fF5+xsR9H9gqmfM0JrjlfzZZA3E8AJ0Nug1KWeoVw3g= -k D8mQ2sKYpLbFCQLhGeHCPBmkLJRi6kRoSg Message is verified! -## Encrypted Mnemonics and Key Backups +## Encrypted Mnemonics, Key and Seed Backups -The `such` tool provides functionality to securely manage your encrypted mnemonics and key backups. With the ability to generate mnemonics and encrypt them for safe storage, and to decrypt them when needed, managing your cryptographic assets is made easier. To use encrypted files with `spvnode`, you must first use the `such` tool to generate and encrypt your mnemonic or master key. You can then use the `spvnode` tool to import the encrypted file and use it to connect to the network. +The `such` tool provides functionality to securely manage your encrypted mnemonics, key and seed backups. With the ability to generate mnemonics and encrypt them for safe storage, and to decrypt them when needed, managing your cryptographic assets is made easier. To use encrypted files with `spvnode`, you must first use the `such` tool to generate and encrypt your mnemonic or master key. You can then use the `spvnode` tool to import the encrypted file and use it to connect to the network. ### Generating and Encrypting Mnemonics @@ -218,6 +220,12 @@ And to decrypt it back when required: Always ensure to replace `` with the actual number of the encrypted file. +### Handling Seed Backups + +You can also decrypt your seed backups using the `seed_to_master_key` command. This command will decrypt the seed and generate a master key from it. If the seed was encrypted using TPM (Trusted Platform Module), you can use the `-j` flag as shown: + + ./such -c seed_to_master_key -y -j + ### Overwriting Encrypted Files If you want to overwrite an existing encrypted file, you can use the `-w` flag as shown: diff --git a/src/cli/spvnode.c b/src/cli/spvnode.c index d1c1c88ac..e132dc0e6 100644 --- a/src/cli/spvnode.c +++ b/src/cli/spvnode.c @@ -41,6 +41,7 @@ #endif #include +#include #include #include #include @@ -273,6 +274,16 @@ void spv_sync_completed(dogecoin_spv_client* client) { } } +// Signal handler for SIGINT +void handle_sigint() { + // Reset standard input back to blocking mode +#ifndef _WIN32 + int stdin_flags = fcntl(STDIN_FILENO, F_GETFL); + fcntl(STDIN_FILENO, F_SETFL, stdin_flags & ~O_NONBLOCK); +#endif + exit(0); +} + int main(int argc, char* argv[]) { int ret = 0; int long_index = 0; @@ -372,6 +383,7 @@ int main(int argc, char* argv[]) { dogecoin_spv_client* client = dogecoin_spv_client_new(chain, debug, (dbfile && (dbfile[0] == '0' || (strlen(dbfile) > 1 && dbfile[0] == 'n' && dbfile[0] == 'o'))) ? true : false, use_checkpoint, full_sync); client->header_message_processed = spv_header_message_processed; client->sync_completed = spv_sync_completed; + signal(SIGINT, handle_sigint); #if WITH_WALLET dogecoin_wallet* wallet = dogecoin_wallet_init(chain, address, name, mnemonic_in, pass, encrypted, tpm, file_num, master_key); @@ -382,6 +394,8 @@ int main(int argc, char* argv[]) { dogecoin_mem_zero (pass, strlen(pass)); dogecoin_free(pass); } + dogecoin_spv_client_free(client); + dogecoin_ecc_stop(); return EXIT_FAILURE; } // clear and free the passphrase diff --git a/src/cli/such.c b/src/cli/such.c index f8af0139d..4fe65c44a 100644 --- a/src/cli/such.c +++ b/src/cli/such.c @@ -651,6 +651,7 @@ static void print_usage() printf("list_encryption_keys_in_tpm,\n"); printf("decrypt_master_key (requires -y , -j (use_tpm) optional),\n"); printf("decrypt_mnemonic (requires -y , -j (use_tpm) optional),\n"); + printf("seed_to_master_key (-y , -j (use_tpm) optional),\n"); printf("mnemonic_to_key (requires -n or -y , -j (use_tpm), -o , -g , -i and -a, all optional),\n"); printf("mnemonic_to_addresses (requires -n or -y , -j (use_tpm), -o , -g , -i and -a, all optional),\n"); printf("print_keys (requires -p ),\n"); @@ -688,6 +689,7 @@ int main(int argc, char* argv[]) char* pass = 0; char* entropy = 0; MNEMONIC mnemonic = {0}; + SEED seed = {0}; dogecoin_bool tpm = false; dogecoin_bool encrypted = false; dogecoin_bool overwrite = false; @@ -1292,6 +1294,50 @@ int main(int argc, char* argv[]) return showError("decrypt_mnemonic (requires -y , -j (use_tpm) optional\n"); } } + else if (strcmp(cmd, "seed_to_master_key") == 0) { /* Creating a bip32 master key from a seed. */ + + /* if tpm is enabled, get seed from tpm */ + if (encrypted) { + printf("Decrypt seed for master key? Y/N\n"); + + char buffer[MAX_LEN]; + /* get user input */ + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + if (buffer[0] != 'Y' && buffer[0] != 'y') { + + /* if not confirmed, abort */ + printf("aborted\n"); + dogecoin_ecc_stop(); + return 1; + } + } + + if (tpm) { + /* get seed from tpm */ + if (!dogecoin_decrypt_seed_with_tpm (seed, file_num)) { + printf("seed_to_master_key (requires -y , -j (use_tpm) optional),\n"); + return showError("failed to decrypt seed with tpm\n"); + } + } + + else { + /* get seed from software */ + if (dogecoin_decrypt_seed_with_sw (seed, file_num, NULL) == false) { + printf("seed_to_master_key (requires -y , -j (use_tpm) optional),\n"); + return showError("failed to decrypt seed with software\n"); + } + } + } + + /* print master key from seed */ + dogecoin_hdnode node; + char masterkey[HDKEYLEN]; + dogecoin_hdnode_from_seed(seed, sizeof(seed), &node); + dogecoin_hdnode_serialize_private(&node, chain, masterkey, sizeof(masterkey)); + printf("bip32 extended master key: %s\n", masterkey); + dogecoin_mem_zero(masterkey, strlen(masterkey)); + dogecoin_mem_zero(seed, sizeof(seed)); + } else if (strcmp(cmd, "mnemonic_to_key") == 0) { /* Creating a bip32 master key from a mnemonic. */ /* if tpm is enabled, get mnemonic from tpm */ diff --git a/src/headersdb_file.c b/src/headersdb_file.c index 70059a142..ae89e4c0f 100644 --- a/src/headersdb_file.c +++ b/src/headersdb_file.c @@ -129,8 +129,8 @@ void dogecoin_headers_db_free(dogecoin_headers_db* db) { dogecoin_free(scan_tip); scan_tip = prev; } - // If scan_tip is chainbottom, free it - if (scan_tip == db->chainbottom && scan_tip != &db->genesis) { + // If scan_tip is not the genesis block, free it + if (scan_tip != &db->genesis) { dogecoin_free(scan_tip); } } @@ -169,8 +169,30 @@ dogecoin_bool dogecoin_headers_db_load(dogecoin_headers_db* db, const char *file struct stat buffer; dogecoin_bool create = true; - if (stat(file_path_local, &buffer) == 0) - create = false; + if (stat(file_path_local, &buffer) == 0) { + printf("\nLoad %s? ", file_path_local); + char response[MAX_LEN]; + if (!fgets(response, MAX_LEN, stdin)) { + printf("Error reading input.\n"); + return false; + } + if (response[0] == 'o' || response[0] == 'O') { + printf("Are you sure? (y/n): \n"); + char confirm[MAX_LEN]; + if (!fgets(confirm, MAX_LEN, stdin)) { + printf("Error reading input.\n"); + return false; + } + if (confirm[0] == 'y' || confirm[0] == 'Y') { + remove(file_path_local); // remove the existing file + create = true; + } else { + create = false; + } + } else { + create = false; + } + } db->headers_tree_file = fopen(file_path_local, create ? "a+b" : "r+b"); cstr_free(path_ret, true); diff --git a/src/spv.c b/src/spv.c index 7bc4f69b9..1c59798ef 100644 --- a/src/spv.c +++ b/src/spv.c @@ -136,6 +136,12 @@ dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, */ void dogecoin_spv_client_discover_peers(dogecoin_spv_client* client, const char *ips) { +#ifndef _WIN32 + // set stdin to non-blocking for quit command + int stdin_flags = fcntl(STDIN_FILENO, F_GETFL); + fcntl(STDIN_FILENO, F_SETFL, stdin_flags | O_NONBLOCK); +#endif + dogecoin_node_group_add_peers_by_ip_or_seed(client->nodegroup, ips); } @@ -197,12 +203,6 @@ dogecoin_bool dogecoin_spv_client_load(dogecoin_spv_client *client, const char * if (!client->headers_db) return false; -#ifndef _WIN32 - // set stdin to non-blocking for quit command - int stdin_flags = fcntl(STDIN_FILENO, F_GETFL); - fcntl(STDIN_FILENO, F_SETFL, stdin_flags | O_NONBLOCK); -#endif - return client->headers_db->load(client->headers_db_ctx, file_path); } diff --git a/src/wallet.c b/src/wallet.c index 968d40680..151de83a4 100644 --- a/src/wallet.c +++ b/src/wallet.c @@ -354,6 +354,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c dogecoin_free(walletfile); if (!res) { showError("Loading wallet failed\n"); + dogecoin_wallet_free(wallet); return NULL; } if (created) { @@ -365,6 +366,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c // generate seed from mnemonic if (dogecoin_seed_from_mnemonic(mnemonic_in, pass, seed) == -1) { showError("Invalid mnemonic\n"); + dogecoin_wallet_free(wallet); return NULL; } } else if (encrypted && !master_key) { @@ -384,6 +386,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c if (!tpmSuccess) { if (!dogecoin_decrypt_mnemonic_with_sw(mnemonic, file_num, NULL)) { showError("Decrypting mnemonic from software failed\n"); + dogecoin_wallet_free(wallet); return NULL; } } @@ -391,6 +394,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c // generate seed from mnemonic if (dogecoin_seed_from_mnemonic(mnemonic, pass, seed) == -1) { showError("Invalid mnemonic\n"); + dogecoin_wallet_free(wallet); return NULL; } } else if (encrypted && master_key) { @@ -409,6 +413,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c if (!tpmSuccess) { if (!dogecoin_decrypt_hdnode_with_sw(&node, file_num, NULL)) { showError("Decrypting master key from software failed\n"); + dogecoin_wallet_free(wallet); return NULL; } } @@ -417,8 +422,33 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c res = dogecoin_random_bytes(seed, sizeof(seed), true); if (!res) { showError("Generating random bytes failed\n"); + dogecoin_wallet_free(wallet); return NULL; } + printf("Store seed in encrypted file? (Y/n): "); + + char buffer[MAX_LEN]; + int file_id = 0; // Variable to store file ID, defaulting to 0 + + // read user input + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + if (buffer[0] == 'Y' || buffer[0] == 'y') { + printf("Enter file ID (0-999): "); + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + file_id = atoi(buffer); // Update file_id with user input + } + + printf("Overwrite file if found? (Y/n): "); + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + bool overwrite = (buffer[0] == 'Y' || buffer[0] == 'y'); + // encrypt seed for storage with software + if (dogecoin_encrypt_seed_with_sw(seed, sizeof(seed), file_id, overwrite, NULL) == false) { + dogecoin_wallet_free(wallet); + return NULL; + } + } + } + } } if (!master_key) { // generate hdnode from mnemonic or random seed @@ -429,6 +459,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c // ensure we have a key/address if (wallet->masterkey == NULL && address == NULL) { showError("No master key or address in wallet.\n"); + dogecoin_wallet_free(wallet); exit(EXIT_FAILURE); } } @@ -833,7 +864,35 @@ dogecoin_bool dogecoin_wallet_load(dogecoin_wallet* wallet, const char* file_pat struct stat buffer; *created = true; - if (stat(file_path, &buffer) == 0) *created = false; + + if (stat(file_path, &buffer) == 0) { + if (!strstr(file_path, "dummy")) { // ignore dummy wallet + printf("Load %s? (Enter) or (o)verwrite:\n", file_path); + char response[MAX_LEN]; + if (!fgets(response, MAX_LEN, stdin)) { + printf("Error reading input.\n"); + return false; + } + if (response[0] == 'o' || response[0] == 'O') { + printf("Are you sure? (y/n): \n"); + char confirm[MAX_LEN]; + if (!fgets(confirm, MAX_LEN, stdin)) { + printf("Error reading input.\n"); + return false; + } + if (confirm[0] == 'y' || confirm[0] == 'Y') { + remove(file_path); // remove the existing file + *created = true; + } else { + *created = false; + } + } else { + *created = false; + } + } else { + *created = false; + } + } wallet->dbfile = fopen(file_path, *created ? "a+b" : "r+b");