Skip to content

Commit

Permalink
Major Update to the Wallet Client
Browse files Browse the repository at this point in the history
Overview:

This is the 6th beta release of the Denaro Wallet Client by the Sycorax,
featuring a range of new functionalities and improvements aimed at
enhancing the overall efficiency and user experience. This release also
marks 100 commits made to the gihub repo!

A significant enhancement to the wallet client is the ability to view
Denaro balances in real-world monetary values, available in various
international fiat currencies and major cryptocurrencies, with USD set
as the default.

This version also introduces Denaro paper wallets, providing a secure,
physical option for wallet storage. Enhancements include backup support
for wallet files via the CLI and a more streamlined wallet generation
process with the option of a mnemonic phrase for easier recovery.

The wallet decryption process has been optimized for better efficiency
in accessing and viewing wallet contents. The command-line interface
has been updated with newsub-commands and options to accommodate these
features.

Changelog for the Denaro Wallet Client v0.0.6-beta:

- Command-line Interface:
  - Changes:
    - Reorganized the command structure for wallet and address
      generation with a new `generate` sub-command.
      - The `generatewallet` sub-command has been updated to `generate
        wallet`.

      - The `generateaddress` sub-command has been updated to `generate
        address`.

      - This change simplifies the command structure and improves
        usability by grouping related functionalities under a single
        sub-command.

    - The `-pretty` option for the `decryptwallet` sub-command has been
      replaced with `-json`.

    - The `-filter` option has been removed from the `decryptwallet`
      sub-command. It has been removed in support of the `decryptwallet
      filter` sub-command which has the same exact functionality, dose
      not require a specific syntax, and is easier to use.

    - Updated help messages for better documentation and consistency.

  - New Sub-commands and Options:
    - A `generate paperwallet` sub-command has been added to facilitate
      the creation of Denaro paper wallets either by using an address
      that is associated with a wallet file, or directly via a private
      key that corresponds to a particular address (See Paper Wallet
      Support for  more details).

    - A `backupwallet` sub-command has been added which allow users to
      create backups of their wallet files. Users can specify the
      filepath of a directory to save a wallet backup file. If no
      specific filepath is provided then wallet backup files will be
      saved to the `./wallets/wallet_backups/` directory by default.

    - A `-phrase` option has been added to the `generate wallet`
      sub-command to allow the creation of wallets based on a 12 word
      mnemonic phrase. This option will enable deterministic address
      generation by default, therefore it can be used to recover
      determinisitic addresses as long as the mnemonic phrase and
      password are correct.

    - A `-convert-to` option has been added to the `balance` sub-command
      which allows users to convert the monetary value of their balances
      to a specified currency, factoring in current exchange rates
      against the USD price of DNR. This option supports 161
      international currencies and major cryptocurrencies, and requires
      three letter currency code (See New Balance Checking Features for
      more details).

- `wallet_client.py` Changelog:
  - The `generateAddressHelper` and `main` functions have been
    slightly modified to support the creation of wallets based on a 12
    word mnemonic phrase.

  - Added a `custom` parameter to the `ensure_wallet_directories_exist`
    function. This is used to create a custom directory at a specific
    filepath.

  - Various operations that are responsible for retrieving address and
    private key data in the `prepareTransaction` function have been
    moved to single `get_address_and_private_key` function.

  - Added console message when filtering wallet entries by origin.

  - Fixed various typos throughout the codebase.

  - Introduced a `generatePaperWallet` function to facilitate paper
    wallet generation.
    - See Paper Wallet Support for more details.

  - Significant improvements have been introduced to the balance
    checking system.
    - The `checkBalance` function has been updated.

    - Added new functions: `is_valid_currency_code` and `get_price_info`

    - See New Balance Checking Features for more details.

  - Updated various CLI sub-commands to accommodate the newly added
    features.
      - See Command-line Interface more details.

  - Modified the `check_args` function to accommodate changes made to
    the CLI.

    - Refactored `decryptWalletEntries` Function:
      - This sub-section documents the modifications and enhancements
        made to the `decryptWalletEntries` function, highlighting
        significant improvements in it's performance, efficiency, and
        code structure.
        - Addressed issues related to code verbosity and structural
          organization.

        - Resolved inefficiencies in processing large wallet files.

        - Implemented more concise and Pythonic determination of wallet
          type.

        - Enhanced the function's capability to handle deterministic
          wallets.

        - Refined logic for handling both encrypted and non-encrypted
          wallets.

        - Replaced the `pretty` parameter with `to_json`.

        - Introduced a nested function `handle_entry_decryption` for
          entry processing.

        - Introduced a nested function `filter_entries` for more
          efficient filtering of wallet entries.

        - Improved handling of specific scenarios involving sub-commands
          (`balance`, `send`, `generate paperwallet`).

        - Improved conditional output formatting based on user input.

        - Enhanced the mechanism for sorting and filtering fields in the
          output.

        - Removed disorganized address filtering logic.

        - Applied address filtering separately to generated and imported
          entries.

        - Implemented support for non-JSON output, providing a more
          readable format with clear differentiation between internally
          generated and imported entry types.

        - Expanded the use of `DataManipulation.secure_delete`,
          ensuring enhanced data security.

- `wallet_generation_util.py` Changelog:
  - An `is_valid_mnemonic` function has been added to validate mnemonic
    phrases.

- `data_manipulation_util.py` Changelog:
  - A `backup_wallet` method has been added to facilitate backing up
    wallet files.

- `cryptographic_util.py` Changelog:
  - Fixed a none type error in `get_failed_attempts` method.

- New Balance Checking Features:
  - This release of the wallet client introduces significant
    improvements to the balance checking system. It now allows users
    to view the real-world value of their Denaro (DNR) balances.
    Additionally, it introduces the `-convert-to` option in the
    `balance` sub-command, enabling users to view their balances in
    over 161 international currencies or the top 100 cryptocurrencies.
    Detailed technical information about these features are outlined
    below.

  - Modified `checkBalance` Function:
    - New function parameters have been introduced:
      - `currency_code`: This parameter is used to specify the
        currency in which the user wants to view the real-world value
        of their DNR balance. Standard currency abbreviations are used
        (e.g., 'USD', 'EUR', 'BTC').
        - If a currency code is not provided or is invalid, USD is
          used by default.

      - `currency_symbol`: This parameter represents the symbol of
        the currency (e.g., '$', '€', ect...)

    - This function now integrates with the new `get_price_info`
      function to retrieve the current price of DNR in the specified
      currency. This price is then used to calculate the real-world
      value of user balances.

    - Improved Balance Information:
      - Now shows the price of the DNR and the equivalent value of
        user balances in the specified currency.

      - For JSON outputs, changes have been made to accommodate the
        aforementioned features.
        - Balance data now includes additional fields like:
          `{currency}_value`,`exchange_rate`, `total_balance`, and
          `total_{currency}_value`.

  - `is_valid_currency_code` Function (New):
    - This function checks if the user provided currency code exists
      in a pre-defined list of 161 international fiat and top 100
      cryptocurrencies.

    - If the currency code is valid, it returns `True` along with the
      currency's symbol. This symbol is used to represent the currency
      in the price and balance information, ensuring readability and
      user familiarity.

    - When an unrecognized currency code is provided, the function
      outputs a message indicating the invalidity and defaults to USD.
      It then returns `False` and the symbol for USD (`$`). This
      behavior allows the application to continue operations with a
      standard currency (USD) in case of invalid user input.

  - `get_price_info` Function (New):
    - This function is responsible for fetching the current price of
      DNR in USD and converting it to a specified fiat or
      cryptocurrency.

    - Initially, it requests the price of DNR in USD from the
      CoinMarketCap API and implements a fallback mechanism using a
      secondary URL in case of primary API failure.

    - If the requested `currency_code` is 'USD' (the default option),
      the function immediately returns the USD price.

    - When the requested `currency_code` is not 'USD':
      - For fiat currencies, it queries the Open Exchange Rate API to
        obtain exchange rates for fiat currencies against USD. It then
        calculates and returns the price of DNR in the specified fiat
        currency.

      - For cryptocurrencies, it queries the Coincap API to obtain
        cryptocurrency exchange rates in USD. It then calculates and
        returns the price of DNR in the specified cryptocurrency.

    - If all API requests fail then the price of DNR will return 0.

- Paper Wallet Support:
  - The wallet client now includes support for paper wallet
    generation, providing a secure and physical way to store Denaro.
    This release introduces two key components: a new module
    `paper_wallet_util.py` and a new function `generatePaperWallet` in
    `wallet_client.py`. The technical details and file storage
    mechanism of these new additions are outlined below.

  - Introduced a new `paper_wallet_util.py` module has been added to
    facilitate the creation of Denaro paper wallets. The module
    includes a `PaperWalletGenerator` class with two methods:
    - `generate_qr_code`: Generates a QR code for a given data string.
      Utilizes the qrcode library to create QR codes with specific
      properties such as error correction level and box size.

    - `overlay_qr_code`: Overlays the generated QR codes for the
      private key and public address onto a predefined paper wallet
      template. It includes image manipulation such as resizing QR
      codes, converting images to RGBA format, drawing scaled and
      rotated text for private key and address, and pasting QR codes at
      specified positions on the paper wallet template.

  - The front and back images used for the paper wallet template are
    located in the `./denaro/wallet/` directory.

  - Introduced a new `generatePaperWallet` function to
    `wallet_client.py`. This function orchestrates the paper wallet
    generation process. It's primary operations are listed below:
      - Retrieves address and private key data using the new
        `get_address_and_private_key` function.

      - Uses the `paper_wallet_util.py` module which processes address
        and private key data to generate the corresponding QR codes, and
        final paper wallet image.

      - Includes directory and file existence checks, and creates the
        necessary file paths for paper wallets. If an address

      - Depending on the `file_type` parameter, the function will
        create either a single PNG image for the front side of a paper
        wallet, or a PDF file which includes the front of the paper
        wallet on the first page and the back design on the second page.

      - For PDF files, it processes images for transparency, scales
        them to fit the letter page size, and organizes the layout for
        front and back pages.

      - Implements secure deletion of local variables and error
        handling to maintain data security and integrity throughout the
        process.

      - Ensures temporary file clean-up and proper saving of the final
        output, with console notifications about the wallet generation
        status.

- Paper Wallet Storage:
  - The `generatePaperWallet` function features an organized mechanism
    for managing the storage and naming of generated paper wallets. This
    mechanism ensures that paper wallets are not only stored in an
    orderly manner but also named in a way that makes them easily
    identifiable and accessible. Detailed below is how the storage
    mechanism operates:
    - When Specifying an Address from a Wallet File:
      - Directory Creation:
        - A dedicated directory is created in
          `./wallets/paper_wallets`. This directory is named after the
          wallet file ( `./wallets/paper_wallets/[WALLET NAME]`). This
          dedicated directory segregates paper wallets based on their
          corresponding wallet files.

      - File Naming Convention:
        - Generated paper wallet files are distinctly named according
          to the specified address.
          - PNG files are name as: `[ADDRESS]_paper_wallet_front.png`
          - PDF files as `[ADDRESS]_paper_wallet.pdf.`

    - When Only a Private Key is Specified:
      - Centralized Storage:
        - In this scenario, the paper wallet is directly stored in the
          `./wallets/paper_wallets` directory, simplifying the storage
          process when no wallet file is involved.

      - Consistent Naming Scheme:
        - The naming convention for paper wallets generated from a
          private key mirrors the approach used for wallet file
          addresses. However, the [ADDRESS] in the file names is is
          derived directly from the provided private key, maintaining
          consistency across both scenarios.
  • Loading branch information
The-Sycorax committed Jan 20, 2024
1 parent 6f387d6 commit 940da3a
Show file tree
Hide file tree
Showing 11 changed files with 1,113 additions and 368 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ wallets
testing
old_versions
github_data.json
update_config.json
update_config.json
experimental_ui.py
157 changes: 107 additions & 50 deletions README.md

Large diffs are not rendered by default.

Binary file added denaro/wallet/paper_wallet_back.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added denaro/wallet/paper_wallet_front.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions denaro/wallet/utils/cryptographic_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ def get_failed_attempts(data, hmac_salt):
A tuple comprising the descrambled data and the determined number of failed password attempts.
"""
# Loop between 1-10 to get amount of failed password attempts
number_of_attempts = 0
for n in range(10):
# Descrample Data
descrambled_data = data_manipulation_util.DataManipulation.descramble(data,n.to_bytes(4, byteorder='big'))
Expand Down
20 changes: 20 additions & 0 deletions denaro/wallet/utils/data_manipulation_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import time
import ctypes
import json
import shutil
import datetime
from filelock import FileLock
import cryptographic_util
import verification_util
Expand Down Expand Up @@ -171,6 +173,24 @@ def _save_data(filename, data):
logging.error(f"Error saving data to file: {str(e)}")
DataManipulation.secure_delete([var for var in locals().values() if var is not None])

@staticmethod
def backup_wallet(filename, directory):
# Construct the backup filename
base_filename = os.path.basename(filename)
backup_name, _ = os.path.splitext(base_filename)
#backup_path = os.path.join("./wallets/wallet_backups", f"{backup_name}_backup_{datetime.datetime.fromtimestamp(int(time.time())).strftime('%Y-%m-%d_%H-%M-%S')}") + ".json"
backup_path = os.path.join("./wallets/wallet_backups" if not directory else directory, f"{backup_name}_backup_{datetime.datetime.fromtimestamp(int(time.time())).strftime('%Y-%m-%d_%H-%M-%S')}") + ".json"
try:
# Create the backup
shutil.copy(filename, backup_path)
print(f"Backup created at: {backup_path}")
DataManipulation.secure_delete([var for var in locals().values() if var is not None])
return True
except Exception as e:
logging.error(f" Could not create backup: {e}\n")
DataManipulation.secure_delete([var for var in locals().values() if var is not None])
return

@staticmethod
def overwrite_with_pattern(file, pattern, file_size):
"""Overview:
Expand Down
4 changes: 2 additions & 2 deletions denaro/wallet/utils/interface_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ def backup_and_overwrite_helper(data, filename, password, encrypt, backup, disab
try:
# Create the backup
shutil.copy(filename, backup_path)
print(f"Backup created at {backup_path}")
print(f"Backup created at {backup_path}\n")
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None])
return True

Expand Down Expand Up @@ -569,7 +569,7 @@ def backup_and_overwrite_helper(data, filename, password, encrypt, backup, disab
try:
# Overwrite wallet with empty data
data_manipulation_util.DataManipulation.delete_wallet(filename, data)
print("Wallet data has been erased.")
print("Wallet data has been erased.\n")
time.sleep(0.5)
except Exception as e:
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None])
Expand Down
83 changes: 83 additions & 0 deletions denaro/wallet/utils/paper_wallet_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import logging
import qrcode
from PIL import Image, ImageDraw, ImageFont
import data_manipulation_util

class PaperWalletGenerator:
@staticmethod
def generate_qr_code(data):
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=0,
)
qr.add_data(data)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None and var is not qr_image])
return qr_image
except Exception as e:
logging.error(f"Error in generating QR code: {e}")
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None])


@staticmethod
def overlay_qr_code(private_key_qr, public_address_qr, private_key, address, file_type):
try:
# Load background image
background_image_path = './denaro/wallet/paper_wallet_front.png'
background_image = Image.open(background_image_path)

# Resize QR codes
private_key_qr = private_key_qr.resize((474, 474), Image.LANCZOS)
public_address_qr = public_address_qr.resize((474, 474), Image.LANCZOS)

# Convert to RGBA if necessary
if background_image.mode != 'RGBA':
background_image = background_image.convert('RGBA')

# Font settings (using default font)
font = ImageFont.load_default()
text_color = (0, 0, 0) # Black color

# Function to draw and scale up rotated text
def draw_scaled_rotated_text(image, text, position, angle, font, fill, scale_factor):
# Create a new image for the text
text_image = Image.new('RGBA', (1000, 20), (0, 0, 0, 0)) # Create large enough image for the text
text_draw = ImageDraw.Draw(text_image)
text_draw.text((0, 0), text, font=font, fill=fill)

# Scale up the text image
scaled_text_image = text_image.resize((int(text_image.width * scale_factor), int(text_image.height * scale_factor)), Image.NEAREST)

# Rotate the scaled text image
rotated_text_image = scaled_text_image.rotate(angle, expand=1)

# Calculate the new position
text_image_x, text_image_y = position
position = (text_image_x, text_image_y - rotated_text_image.size[1] // 2)

# Paste the text image onto the original image
image.paste(rotated_text_image, position, rotated_text_image)

# Position and rotation settings
pk_text_pos = (133 + 474 + 10, 472 + 460) # Adjust as needed
addr_text_pos = (3083 + 474 + 10, 472 + 510) # Adjust as needed
angle = -90
scale_factor = 1.35 # Adjust scale factor as needed for size

# Draw scaled and rotated text for private key and address
draw_scaled_rotated_text(background_image, private_key, pk_text_pos, angle, font, text_color, scale_factor)
draw_scaled_rotated_text(background_image, address, addr_text_pos, angle, font, text_color, scale_factor)

# Paste QR codes
background_image.paste(private_key_qr, (133, 262), private_key_qr if private_key_qr.mode == 'RGBA' else None)
background_image.paste(public_address_qr, (3083, 263), public_address_qr if public_address_qr.mode == 'RGBA' else None)
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None and var is not background_image])
return background_image
except Exception as e:
logging.error(f"Error in overlaying QR code: {e}")
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None])

25 changes: 25 additions & 0 deletions denaro/wallet/utils/wallet_generation_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,31 @@ def private_to_public_key_fastecdsa(private_key_hex):
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None and var is not result])
return result

def is_valid_mnemonic(mnemonic_phrase):
"""
Validates the given mnemonic phrase.
Args:
mnemonic_phrase (str): A 12-word mnemonic phrase.
Returns:
bool: True if the mnemonic is valid, False otherwise.
"""
mnemo = mnemonic.Mnemonic("english")
words = mnemonic_phrase.split()

# Check if all words are in the wordlist
result = mnemo.check(mnemonic_phrase)

# Check if the number of words is 12
if len(words) != 12 or not result:
logging.error("The mnemonic phrase is invalid.")
data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None])
return False

data_manipulation_util.DataManipulation.secure_delete([var for var in locals().values() if var is not None and var is not result])
return result

def generate(mnemonic_phrase=None, passphrase=None, index=0, deterministic=False, fields=None):
"""
Generate cryptographic keys and addresses.
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ pycryptodome==3.19.0
pyotp==2.9.0
qrcode==7.4.2
Pillow==10.1.0
filelock==3.13.1
filelock==3.13.1
reportlab==4.0.8
Loading

0 comments on commit 940da3a

Please sign in to comment.