From a823ce5a5e734fef973aa09f1c97dd3b7e6ea0a5 Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Fri, 18 Sep 2026 19:33:36 -0500 Subject: [PATCH 1/5] wallet: Remove legacy wallet code Remove the custom wallet entry point, bundled Codex32 implementation, vault loader, and wallet-specific Python helpers. Keep Persistent Storage and spaced-repetition support intact and leave Sparrow as the installed coordinator. Refs #201. --- b | 2 +- bails/.local/bin/bails-menu | 7 +- bails/.local/bin/bails-wallet | 573 ------------------ bails/.local/bin/decrypt-vault | 61 -- .../bails-wallet/codex32_confirm.py | 195 ------ .../site-packages/bails/create_vault.py | 54 -- .../site-packages/bails/passphrase.py | 79 --- .../python3.11/site-packages/codex32/ms32.py | 396 ------------ .../site-packages/codex32/naive_ms32_ecc.py | 95 --- bails/.local/share/applications/bails.desktop | 2 +- .../share/applications/decrypt-vault.desktop | 12 - 11 files changed, 4 insertions(+), 1472 deletions(-) delete mode 100755 bails/.local/bin/bails-wallet delete mode 100755 bails/.local/bin/decrypt-vault delete mode 100644 bails/.local/lib/python3.11/site-packages/bails-wallet/codex32_confirm.py delete mode 100755 bails/.local/lib/python3.11/site-packages/bails/create_vault.py delete mode 100755 bails/.local/lib/python3.11/site-packages/bails/passphrase.py delete mode 100755 bails/.local/lib/python3.11/site-packages/codex32/ms32.py delete mode 100755 bails/.local/lib/python3.11/site-packages/codex32/naive_ms32_ecc.py mode change 100755 => 100644 bails/.local/share/applications/bails.desktop delete mode 100755 bails/.local/share/applications/decrypt-vault.desktop diff --git a/b b/b index 705a6b8e..b26c5f0a 100755 --- a/b +++ b/b @@ -65,7 +65,7 @@ else ) & # Run persistent setup in background if [ -z "$1" ]; then # Install/Update core if ran without a parameter # shellcheck disable=SC1091 - . install-core && bails-wallet + . install-core wait # Display info about IBD, keeping Tails private and extra reading material zenity --info --title='Setup almost complete' --icon-name=bails128 "$ICON" --text='Bitcoin Core has begun syncing the block chain automatically.\nMake sure no one messes with the PC.\n\nTo lock the screen for privacy, press ❖+L (⊞+L or ⌘+L)\n\nIt is safer to exit Bitcoin Core (Ctrl+Q), shutdown Tails and take your CipherStick USB stick with you or store it in a safe place than leave Tails running unattended where people you distrust could tamper with it.\n\nIf you want to learn more about using Tails safely read the documentation.\n\nAnother excellent read to improve your physical and digital security tactics is the security in-a-box website.' diff --git a/bails/.local/bin/bails-menu b/bails/.local/bin/bails-menu index fd7332b4..2ddce142 100755 --- a/bails/.local/bin/bails-menu +++ b/bails/.local/bin/bails-menu @@ -59,20 +59,17 @@ Bitcoin Core will continue to connect to both clearnet and Tor (onion) peers.' - # Display menu and get user response response=$(zenity --question --window-icon="$XDG_DATA_HOME/icons/bails128.png" \ --icon-name="bails128" --title="CipherStick menu" \ - --text="Wallet - Create a new seed backup or recover an existing seed. - -Clone - Create a synchronized CipherStick node for a friend. + --text="Clone - Create a synchronized CipherStick node for a friend. Backup - Duplicate your CipherStick node to protect data. Settings - Updates, add software, and configure." \ - --extra-button="Wallet" --extra-button="Clone" --extra-button="Backup" --extra-button="Settings" --extra-button="Help" \ + --extra-button="Clone" --extra-button="Backup" --extra-button="Settings" --extra-button="Help" \ --switch) # Menu options which Sparrow && action=Update || action=Install case "$response" in -"Wallet") bails-wallet ;; "Clone") bails-clone ;; "Backup") bails-backup ;; "Settings") response2=$(zenity --window-icon="$XDG_DATA_HOME/icons/bails128.png" --question \ diff --git a/bails/.local/bin/bails-wallet b/bails/.local/bin/bails-wallet deleted file mode 100755 index c1a67ca5..00000000 --- a/bails/.local/bin/bails-wallet +++ /dev/null @@ -1,573 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2023 Ben Westgate -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -############################################################################### -# Creates and Restores Codex32 seed backups to Bitcoin Core -############################################################################### - -# For security reviewing this use: https://explainshell.com/ for commands that are unclear. -# Note: bitcoind must already be running or 'Enable RPC Server' checked in GUI options and restart. - -# TODO FOR L1. After restoring, ask if the user has forgotten their passphrase, if they suspect -# it has been compromised or if any share has been lost or suspected compromised. -# Stolen will advise to create a new wallet and sweep funds. It will also increment the threshold. -# Lost will add shares only (without rotating the backup), or decrement the threshold if no more -# backup locations are available. - -# TODO FOR L2 provide an option for persistent "hot" restore and offline "cold" restore. An offline -# PC keeps the private wallet in RAM and requires restore from seed each time to sign transactions. -# Creates a watch-only. persistent saves public or private key wallets to persistent storage. - -export WAYLAND_DISPLAY="" # Needed for zenity dialogs to have window icon -export ICON="--window-icon=$HOME/.local/share/icons/bails128.png" -export CLI="bitcoin-cli" # rpcport=17600 must be set in bitcoin.conf for Tails -export SOURCE='/live/persistence/TailsData_unlocked' -export DATA_DIR=$SOURCE/Persistent/.bitcoin -export DOTFILES=$SOURCE/dotfiles -export LOCAL_DIR=$DOTFILES/.local -export TMPDIR=$XDG_RUNTIME_DIR - -BITCOIN_ADDRESS_COMPLETED= #TODO add these - -hrp='ms1' -string='string' -secret_or=' secret or' - -################### -# Sets or Resets passphrase with confirmation -# Parameters: $1 = dialog title -################### #TODO check the titles fit the dialogs in Tails -# TODO Do NOT permit default, weak, or well-known passwords, such as "Password1" or "admin/admin". - -set_passphrase() { - until [ "$passphrase" ]; do - passphrase="$(python3 -c 'from bails import set_pass -dialog = set_pass.PassphraseDialog(None,"'"$1"'") -if dialog.passphrase: - print(dialog.passphrase) -else: - print("")' 2>/dev/null)" - if [ ! "$passphrase" ]; then - zenity --warning --text='No passphrase was entered.\n\nA passphrase is required to send bitcoin.' --title="$1" --ellipsize $ICON || exit 1 - fi - done -} - -ask_t_and_n() { - # Explain the seed backup and what conditions will recover the seed - # allows tweaking it or skipping it in a 'Change Something' dialog, TODO decide best formatting in Tails - tn_grammar - until zenity --question --icon-name=org.gnome.TextEditor --title="$threshold-of-$locations codex32 seed backup" \ - --text="In case all CipherSticks fail or you lose the passphrase of your Persistent Storage, we will split your seed into $locations codex32 $string$plurs across $locations location$plurs, with any $threshold of those ${string}s able to restore your seed by typing $pronoun into a codex32-enabled wallet such as CipherStick.$password_derived\n\nYou will need:\n\t• $locations piece$plurs of paper\n\t• Pen or pencil\n\t• Hard surface to write on to avoid leaving an imprint of the secret\n\nOne $string can stay hidden near this CipherStick and the others will go offsite, one to each of your backup locations.\n\n-If $threshold $string$plurtss $plurts stolen$password_steal, an attacker can STEAL your bitcoins.\n\n-If $((locations - threshold + 2 > locations ? locations : locations - threshold + 2)) location$plurs are lost$password_loss, you will NOT be able to restore your seed.\n\nDo you understand?" \ - --cancel-label='Change Something' $ICON; do - backup_choice="$(zenity --title='Change backup parameters' --question --extra-button='Location Quantity' \ - --extra-button='Threshold' --extra-button='Identifier' --switch --text="Location quantity is how many places you will store a codex32 $string.\n\nThreshold is how many shares are needed to steal or recover your seed.\n\nIdentifier is the 4 bech32 character unique ID for your backup.\n\nWhat would you like to change?" --ellipsize $ICON)" - [ "$backup_choice" == 'Location Quantity' ] && locations=$(until zenity --scale --text="How many total locations do you want to store ${string}s at?\n\nMinimum $threshold is for low value data.\nUse $((threshold + 2)) or more for high value data." --min-value=$threshold --value=$locations --max-value=$((locations + threshold * 3 < 31 ? locations + threshold * 3 - 1 : 30)) --title='Location quantity' $ICON; do :; done) - [ "$backup_choice" == 'Threshold' ] && threshold=$(until zenity --scale --text='How many locations do you want required to recover or steal your seed?\n\nMinimum 2 for low value data.\nUse 3 for high value data.' --min-value=1 --max-value=$((locations < 9 ? locations : 9)) --value=$threshold --title='Recovery threshold' $ICON; do :; done) - [ "$backup_choice" == 'Identifier' ] && identifier=$(zenity --entry --title='Recovery code ID') - ((threshold < 2)) && { zenity --question --icon-name=dialog-warning --title='Security warning' --text='Caution: Setting the threshold to 1 means anyone who finds a single location can STEAL ALL YOUR BITCOIN, even without your passphrase.\n\nThis is not recommended, are you sure you want to do this?' --default-cancel $ICON || threshold=2; } - ((threshold > 3)) && { zenity --question --icon-name=dialog-warning --title='Safety warning' --text="Caution: Higher threshold values than 3, in our view, are a bad trade-off between usability and robustness (which are damaged) and security (which is improved).\n\nThis is not recommended, are you sure you want to use threshold $threshold?" --default-cancel $ICON || threshold=3; } - # reset the variables to sensible values if exited - tn_grammar - done -} - -# Set global variables for grammar -tn_grammar() { - ((locations > 2)) && plur=s || plur='' - ((threshold - 2)) && { - plurt=s - plurtt=are - } || { - plurt='' - plurtt=is - } - ((locations - threshold)) && plurl='s' || plurl='' - ((threshold > 1)) && { - string='share' - password_derived="\nAn additional 1 share is formed by a seed backup passphrase." - password_steal=", OR $((threshold - 1)) share$plurt and your passphrase" - password_loss=", OR $((locations - threshold + 1)) location$plurl and your passphrase" - plurts=are - plurtss='s' - } || { - plurts=is - string='secret' - unset plurtss password_derived password_steal password_loss - } - ((locations < 2)) && { - plurs='' - pronoun='it' - } || { - plurs='s' - pronoun='them' - } -} - -################### -# Displays strings for user to write and confirms they were written correctly -# Parameters: $@ an array of the codex32 strings -################### -display_confirm() { - for codex32_string in ${codex32_array[*]}; do - # adds 3per em space every 4 chars and in the center (two groups of six groups of 4 characters - displayed="$(echo "${codex32_string^^}" | sed 's/.\{4\}/& /g' | sed 's/.\{30\}/& /g')" - x=$((backups_made++)) - ((x == 1)) && backup=' Backup' - ((x == 2)) && backup=' second Backup' - ((x == 3)) && backup=' third Backup' - ((x > 3)) && backup=" ${x}th Backup" - progress="($backups_made of $locations)" - while true; do - zenity --title="Create new a seed backup $progress" --text="Write this codex32 $string legibly:\n\n$displayed" --info --icon-name=org.gnome.TextEditor --no-wrap $ICON - choice="Create a new seed" - python3 $LOCAL_DIR/lib/python3.11/site-packages/bails-wallet/codex32_confirm.py "$codex32_string" "${progress:1:-1}" && break - done - done - unset passphrase threshold identifier backups_made share_list choice backup share confirm user_entropy threshold locations extended_private_masterkey codex32_array codex32_string share displayed x backup backups_made progress - zenity --info --title='Seed backup complete' --text='You will now restore your new seed with your codex32 backup before your wallet is created.' --ok-label='Restore Seed' $ICON --icon-name=starred -} - -display_errors_dialog() { - entered="$1" - correct="$2" - - # Build the formatted strings - formatted_entered=$(echo "$entered" | sed 's/.\{4\}/& /g' | sed 's/.\{30\}/& /g') - formatted_correct=$(echo "$correct" | sed 's/.\{4\}/& /g' | sed 's/.\{30\}/& /g') - - # Identify error locations and highlight them in red using Zenity markup - highlighted_entered="" - for ((i = 0; i < ${#formatted_entered}; i++)); do - if [[ "${formatted_entered:$i:1}" != "${formatted_correct:$i:1}" ]]; then - highlighted_entered+="${formatted_entered:$i:1}" - else - highlighted_entered+="${formatted_entered:$i:1}" - fi - done - - zenity --warning --title="Error in entered codex32 $string" \ - --text="The entered codex32 $string contains errors:\n\n$highlighted_entered" \ - --no-wrap --timeout=5 --ok-label='Try Again' -} - -# Accepts and processes user share input, performs error correction -input_share() { # TODO FIXME refactor at 100 lines, this may be better as a separate file - if [ "$choice" == "Create a new seed" ]; then - title="Enter your codex32 $string $progress" - warning="You may have written down your codex32 $string" - else - title="Enter a codex32$secret_or share $progress" - warning="A codex32 $string may have been written down" - [ "$entry" ] || entry="$hrp$threshold$identifier" - [ "$string" ] || string="string" - fi - unset corr - [ "$warned" ] || zenity --info --title="Codex32 entry instructions" \ - --text="$warning in 4 character blocks\nto make it easier to read, but don't include spaces when entering it now." \ - --ok-label="Got it, Proceed" $ICON --no-wrap && warned=true - [ "$?" -ne 0 ] && exit 1 - [ "$share" ] && { - title="Re-enter your codex32 $string" - corr=' corrected' - } - entry="$(zenity --title="$title" --entry --entry-text="$entry" --text="Type your$corr codex32 $string:" --width=650 $ICON)" - if [ "$?" -ne 0 ]; then - if ((${#share_list[*]} > 0)) || [ "$choice" == "Create a new seed" ]; then - zenity --question --title="Warning: Progress will be lost" \ - --text="All progress will be lost, are you sure you want to quit?" \ - --ok-label="Quit" --cancel-label="Continue" --icon-name=dialog-warning $ICON - [ "$?" -ne 1 ] && exit 1 - else - exit 1 - fi - fi - [ "$entry" ] || return 1 - hrp_in="${entry:0:3}" - data="${entry:3}" - if [[ "$share" == *' '* ]]; then - zenity --warning --title="Important: Do not copy and paste" --text="WARNING: You MUST write down your codex32 $string on paper!\n\nFailure to create a paper backup may result in LOSING ALL YOUR BITCOINS\n\nTake this step seriously to ensure the safety of your funds." --no-wrap $ICON - unset share data - return 1 - fi - if [[ "$share" == *' '* ]]; then - zenity --notification --text='Detected non-bech32 character " "' $ICON - unset warned - fi - if [[ "$share" == *'b'* ]]; then - data="${data//b/6}" - zenity --notification --text='Detected non-bech32 character "b"' $ICON - fi - if [[ "$share" == *'i'* ]]; then - data="${data//i/l}" - zenity --notification --text='Detected non-bech32 character "i"' $ICON - fi - if [[ "$share" == *'o'* ]]; then - data="${data//o/0}" - zenity --notification --text='Detected non-bech32 character "o"' $ICON - fi - if [[ "$share" == *'B'* ]]; then - data="${data//B/8}" - zenity --notification --text='Detected non-bech32 character "B"' $ICON - fi - if [[ "$share" == *'I'* ]]; then - data="${data//I/L}" - zenity --notification --text='Detected non-bech32 character "I"' $ICON - fi - if [[ "$share" == *'O'* ]]; then - data="${data//O/0}" - zenity --notification --text='Detected non-bech32 character "O"' $ICON - fi - if [[ "$data" == *'1'* ]]; then - data="${data//1/l}" - zenity --notification --text='Detected non-bech32 character "1" in data portion' $ICON - fi - share="$hrp_in""$data" - threshold="${share:3:1}" - identifier="${share:4:4}" - index="${share:8:1}" - if [[ $index ]] && [[ ${index,,} != 's' ]]; then - string=share - elif [[ ${index,,} = 's' ]] || [[ $threshold = 0 ]]; then - string=secret - else - string=string - fi - if [[ "$share" = "${share^^}" ]]; then - hrp=${hrp^^} - identifier="${identifier^^}" - elif [[ "$share" != "${share,,}" ]]; then - zenity --title='Mixed case is not allowed' --error --text="A codex32 $string MUST be entirely uppercase or entirely lowercase." $ICON --ellipsize & - return 1 - else - hrp="${hrp,,}" - identifier="${identifier,,}" - fi - - if [[ "$share" =~ [^a-zA-Z0-9] ]]; then - zenity --title='Non-alphanumeric character in '$string --error --text="Codex32 ${string}s contain only letters and numbers." $ICON --ellipsize & - return 1 - fi - - if [[ "$hrp_in" != "$hrp" ]]; then - zenity --title='Wrong human-readable prefix' --error --text='Codex32 '$string's start with "'$hrp'".\n\nYou typed "'$hrp_in'".' $ICON --ellipsize & - return 1 - fi - if [[ $threshold = 0 ]]; then - if [[ ${index,,} != 's' ]]; then - zenity --title='Bad share index' --error --text='Share index (9th character) must be "'${hrp:1:1}'" when threshold is "0".\n\nYou typed "'$index'".' $ICON --ellipsize & - return 1 - fi - elif ((threshold < 2 || threshold > 9)); then - zenity --title='Bad threshold' --error --text='Threshold parameter (4th character) MUST be a single digit between "2" and "9", or the digit "0".\n\nYou typed "'$threshold'".' $ICON --ellipsize & - return 1 - fi - if ((${#share} < 48)); then - zenity --title='Entry too short' --error --text="Codex32 ${string}s are at least 48 characters.\n\nYou typed ${#share} characters." $ICON --ellipsize & - return 1 - elif ((${#share} > 93 && ${#share} < 96)); then - zenity --title="Illegal codex32 ${string} length" --error --text="Codex32 ${string}s are 48 to 102 characters or 105 to 127 characters.\n\nYou typed ${#share} characters." $ICON --ellipsize & - return 1 - elif ((${#share} != 74 && ${#share} != 48 && ${#share} != 127)); then - zenity --title='Unusual length' --warning --text="Codex32 ${string}s are usually 48, 74 or 127 characters long.\n\nYou typed ${#share} characters." $ICON --ellipsize & - fi - - # call checksum verification function here - return "$(python3 -c 'from codex32 import ms32 -ms32.verify_checksum("'$share'")')" -} - -get_codex32_secret() { - printf '\033]2;Recover with codex32 seed backup\a' - [ "$threshold" ] || get_share - if [[ ${index,,} = 's' ]]; then - codex32_secret="$share" - reset='your codex32 secret' - threshold=1 - return 0 - fi - unset secret_or - reset="$threshold shares" - while ((${#share_list[*]} < threshold)); do - progress="($((1 + ${#share_list[*]})) of $threshold)" - if ((${#share_list[*]} > threshold - 2)); then - if zenity --title='Use seed backup passphrase?' --text="If you know this seed backup's passphrase click 'Passphrase',\notherwise use 'Another Share'.\n\nChoose restore method:" --question $ICON --ok-label='Passphrase' --cancel-label='Another Share' --no-wrap; then - until [ "$passphrase" ]; do # TODO FIXME this can easily be written in one python3 block - passphrase="$(python3 -c 'from bails import passphrase -dialog = passphrase.PassphraseDialog(None, "Enter seed backup passphrase") -if dialog.passphrase: - print(dialog.passphrase) -else: - print("")')" - done # TODO FIXME these two python3 commands can also be combined into one, possibly with the above - share_list[$((threshold - 1))]=$(python3 -c 'from codex32 import ms32 -k, identifier, share_index, decoded = ms32.decode("ms", "'$share'") -codex32_kdf_share, salt , indices_free = ms32.kdf_share("'"$passphrase"'", k, identifier, len(decoded)) -print(codex32_kdf_share)') - if (($(python3 -c 'from codex32 import ms32 -ms32.identifier_verify_checksum("'"$(recover_seed)"'")'))); then - ((${#share_list[*]} > 2)) && plur='s' - zenity --notification --text="The passphrase you entered does not match your share$plur.\nDouble-check your passphrase and try again." "$ICON" - unset passphrase share_list[$((threshold - 1))] - fi - else - unset passphrase share_list[$((threshold - 1))] - get_share - fi - else - get_share - fi - done - codex32_secret="$(recover_seed)" - return 0 -} - -get_share() { - threshold="${share:3:1}" - unset share entry - until input_share; do - displayed="$(echo "$share" | sed 's/.\{4\}/& /g' | sed 's/.\{30\}/& /g')" - zenity --warning --title="Invalid codex32 $string $progress" --text="Correct the errors in your invalid codex32 $string:\n\n$displayed" --no-wrap & - corrected_string=$(python3 $LOCAL_DIR/lib/python3.11/site-packages/codex32/naive_ms32_ecc.py "$share" "$VALID_IDENTIFIER") - result=$? - pkill zenity - ((result == 0)) && displayed="$(echo "${corrected_string^^}" | sed 's/.\{4\}/& /g' | sed 's/.\{30\}/& /g')" - ((result == 0)) && zenity --title="Confirm corrected codex32 $string $progress" --text="Typos were detected and automatically corrected!\n\nConfirm this corrected codex32 $string exactly matches what is written down on paper:\n\n$displayed" --question --ok-label='Yes, It Matches Exactly' --cancel-label="No, It's Wrong" --default-cancel --icon-name=dialog-warning --no-wrap $ICON && { - share="$corrected_string" - break - } - zenity --notification --text="Invalid checksum. Correct all errors and try again." $ICON - done - VALID_IDENTIFIER=${share:3:5} - if [[ " ${share_list[*]} " =~ " ${share,,} " ]]; then - zenity --notification --text="You have input the same share twice.\nYou must input a unique share." $ICON - get_share - fi - share_list+=(${share,,}) -} - -recover_seed() { - py_share_list="${share_list[*]}" - py_share_list=${py_share_list// /"','"} - printf "$(python3 -c "from codex32 import ms32 -print(ms32.recover_master_seed(['"$py_share_list"']))")" -} - -################### -# Asks user if backup compromised or lost #TODO post-pone for L1 -# Parameters: $1 = dialog title -################### #TODO check the titles fit the dialogs in Tails -rotate_backup() { - if zenity --question --title='Compromised share or passphrase?' --text="Do you know (or suspect) this backup\'s passphrase or any of its shares have been lost or compromised?" --default-cancel $ICON; then - zenity --warning --title='Backup is vulnerable to theft' --text="Backup security is compromised.\n\nA new codex32 backup needs to be created immediately and old backups destroyed.\n\nIMPORTANT: Any previous backups you have made of your seed should be replaced with a newly generated set of codex32 shares then destroy the previous backups. Previous backups are vulnerable to being restored using your compromised data.\n\nIf you cannot destroy ALL remaining old shares to this backup, or you know (or suspect) $threshold shares OR $((threshold - 1)) share(s) and your passphrase are compromised you should Create a new wallet using a new passphrase and sweep all funds immediately using the most urgent transaction fee." $ICON - elif zenity --question --title='Lost share or passphrase?' --text="Do you know (or suspect) this backup\'s passphrase or any of its shares have been lost?" --default-cancel $ICON; then - zenity --warning --title='Backup is vulnerable to loss' --text='Backup safety is compromised.\n\nA new codex32 backup needs to be created immediately.\n\nIMPORTANT: Any previous backups you have made of your seed should be replaced with a newly generated set of codex32 shares. Previous backups of your seed will be useless to restore your wallet using a new passphrase or share.' $ICON - fi -} - -################### -# Modifies a just restored backup to add security or safety #TODO postpone for L1 -################### -modify_backup() { - ((threshold - 2)) && { - plurt=s - plurtt=are - } || { - plurt='' - plurtt=is - } - selection=$(zenity --question --title='Modify backup?' --text="Do you know (or suspect) this backup's passphrase or a share is compromised or lost?\n\n-If no, then select 'No'.\n\n-If compromised or to raise threshold you should select 'Add Security'.\n\n-If lost anything or to create extra shares you should select 'Add Safety'." --switch --extra-button='No' --extra-button='Add Security' --extra-button='Add Safety' $ICON) - if [ "$selection" = 'Add Security' ]; then - zenity --question --window-icon=warning-dialog --title='Add Security' --text="Backup security is reduced or compromised if a share or passphrase has been compromised.\n\nA new codex32 backup needs to be created immediately and old backups destroyed, if possible.\n\nIMPORTANT: Any previous backups you have made of your seed should be replaced with a newly generated set of codex32 shares and then destroy previous backups, if possible. Previous backups are vulnerable to being restored using compromised data.\n\n-If you know (or suspect) a share or passphrase are compromised AND/OR you know (or suspect) you can not destroy ALL previous shares, select 'Create New Seed'. Then immediately sweep all funds to the new wallet using the most urgent transaction fee.\n\n-Otherwise select 'Increase Threshold' which improves security but preserves your wallet." --ok-label='Increase Threshold' --default-cancel --cancel-label='Create New Seed' $ICON #TODO make sure it rejects the previously used passphrase - #TODO add functionality to these buttons - elif [ "$selection" = 'Add Safety' ]; then - zenity --question --title='Add Safety' --text="Backup safety is reduced if you lose a share or passphrase.\n\nA new codex32 backup needs to be created immediately if you forgot your passphrase.\n\nIMPORTANT: Any previous backups you have made of your seed should be replaced with this newly generated codex32 backup. Previous backups of your seed will be useless for recovery with your new passphrase.\n\n-If the passphrase is lost or to reduce threshold select 'New Backup'.\n\n-If you know (or suspect) any shares are lost or to increase safety select 'Generate Extra Shares'." --default-cancel --cancel-label='Rotate Shares' --ok-label='Generate Extra Shares' $ICON - #TODO add functionality to these buttons - fi -} - -####################################### -# Derives xprv from codex32 secret and imports BIP44/49/84/86 descriptors -# Parameter $1: 'now' for new wallets and '0' for wallet restores -# Parameter $2: (optional) load_on_startup True False -####################################### - -restore_wallet() { - identifier=[${codex32_secret:4:4}] - xprv=$(python3 -c 'from codex32 import ms32 -from electrum import bip32 -y = bip32.BIP32Node -print(y.from_rootseed(bytes(ms32.decode("ms","'$codex32_secret'")[3]), xtype="standard").to_xprv())') - # TODO for L2 offline restores do not persist signing wallet /tmp/$tmp_dir/$name # easiest Idea may be just symlink to any tmpfs as target, name it amnesia - zenity --notification --text="Wallet seed restored successfully." "$ICON" - name=$(zenity --title='Name your new wallet' --entry --text='Wallet Name:' --entry-text='Wallet' "$ICON") - [ "$name" ] || name='Wallet' - [ "$passphrase" ] || set_passphrase 'Set passphrase for the wallet' - chmod +w $DATA_DIR/wallets # writes enabled to create wallet - until $CLI createwallet "$name" false true "$passphrase" false true $2; do - name=$(zenity --title='Create failed: name already exists' --entry --text='New Wallet Name:' --entry-text="Wallet $identifier" "$ICON" --width=340) - done - chmod -w $DATA_DIR/wallets # write protect folder so wallets w/o seed backups can't be made - { - zenity --title='Wallet encryption info' --info --icon-name=emblem-readonly --text="Wallet encryption adds an extra layer of security by requiring a passphrase to access your bitcoins. This helps protect against unauthorized access if your wallet file is stolen." $ICON - zenity --title="Wallet encryption warning" --warning --text="Warning: If you lose your passphrase and can not find $reset,\nyou will LOSE ALL OF YOUR BITCOINS\!" $ICON - zenity --title="Wallet encrypted" --warning --text="Your wallet has been encrypted. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer." $ICON - } & - encryption_info=$! - { - zenity --notification --text='Importing your wallet. This may take several minutes...\nLock the screen if you need to walk away from the PC.' "$ICON" - sleep 10 - tails-screen-locker - } &>/dev/null & - lock_if_slow=$! - for change in 0 1; do - ((change)) && internal=true || internal=false - path="h/0h/0h/$change/*)" - for desc in "pkh($xprv/44$path" "sh(wpkh($xprv/49$path)" "wpkh($xprv/84$path" "tr($xprv/86$path"; do - checksum=$($CLI getdescriptorinfo "$desc" | awk 'NR==3 {print $2}') - # unlocks wallet 1 minute - $CLI -rpcwallet="$name" walletpassphrase "$passphrase" 60 - $CLI -rpcwallet="$name" importdescriptors '[{ "desc": "'$desc'#'${checksum:1:8}'", "active": true, "timestamp": '$1', "internal": '$internal' }]' - $CLI -rpcwallet="$name" walletlock - done - done - $CLI unloadwallet "$name" #TODO remove this when Bitcoin Core removes the bug that doesn't load bech32m addresses after importing. - $CLI loadwallet "$name" - kill $lock_if_slow - pkill python3 &>/dev/null # kills tails-screen-locker - wait -f $encryption_info - # TODO this dialog needs improvement - zenity --info --title='Wallet setup complete' --ok-label=Finish "$ICON" --icon-name=bails128 \ - --text="Congratulations! You have created a wallet.\n\nDistribute your backups offsite in secured confidential locations, safe from prying eyes and potential theft. Destroy any written copies of your passphrase before storing significant funds.\n\nIf you have any questions or need further assistance, refer to the provided documentation or visit our support resources.\n\nIf you findCipherStick valuable, consider supporting our project by donating or contributing.\n\nEnjoy using CipherStick!" - exit 0 -} - -### Begins Execution Here ### - -printf '\033]2;Create wallet\a' - -choice=$(zenity --list --radiolist --title "Create wallet" --column "Select" --column "Menu Item" \ - --text='Do you want to make a new seed, or use an existing seed?\n\nIf this is your first use select "Create a new seed".\n' \ - TRUE "Create a new seed" FALSE "Recover with codex32 seed backup" FALSE "Import existing wallet descriptors" $ICON --height=250) -if [ "$choice" == "Create a new seed" ]; then - printf '\033]2;Create a new seed\a' - zenity --warning --title='Important: Safeguard your bitcoin' --text="With CipherStick, you are in control of your bitcoin funds. No one else has access to your private keys.\n\nIt is crucial to understand that if you lose access to your CipherSticks and the paper seed backup we help you create, your bitcoin cannot be recovered." --ok-label='I Understand' "$ICON" || exit 1 - user_entropy="$(if zenity --question --title="Optional: Enhance seed randomness" \ - --cancel-label='Skip' --icon-name=die --ok-label='Add Randomness' --text="A seed is a secret that allows spending your bitcoin as long as you have access to it.\n\nIt is not required but you can provide additional randomness to your seed if paranoid. This step protects your seed against a backdoored random number generator.\n\n\Add extra randomness to your seed?" "$ICON"; then - until zenity --entry --title='Add randomness' --text="Provide additional randomness and click OK.\n\nA good way to create randomness is to roll a 6-sided dice 50+ times or\nflip a coin 128+ times. A fast way is mashing the keyboard." "$ICON"; do :; done - fi)" - printf '\033]2;Create a new seed backup\a' - zenity --info --title='Create your paper seed backup' --text="Your seed has been created. CipherStick will now help you create a paper seed backup.\n\nYour paper backup acts as a lifeline, granting access to your funds if something happens to your digital devices." --ok-label="Let's Begin" $ICON || exit 1 - zenity --question --title='Select backup security level' --text='CipherStick lets you choose the backup that suits your needs.\n\nLow value: Storing and sending amounts less than 2 months of expenses.\nRequires 2 safe locations.\n\nHigh value: Storing and sending amounts less than 6 months of expenses.\nRequires 3 or 4 safe locations.\n\nA safe location is one where it is unlikely to be found by a bad actor or lost.\nIf a single backup location is compromised, your privacy is not jeopardized.' --no-wrap --cancel-label=Low --ok-label=High $ICON --icon-name=security-medium && { - locations=4 - threshold=3 - } || { - locations=2 - threshold=2 - } # TODO this should be modularized into a function or file. This is a disaster to look at. - ask_t_and_n - ((threshold > 1)) && set_passphrase 'Set seed backup passphrase' - user_entropy+="$threshold$locations$passphrase" - zenity --notification --text="Generating your codex32 backup..." $ICON - - # Creates generator wallet, dumps its extended private masterkey, leaves no traces behind - temp="$(mktemp)" # temporary filename - trap 'rm -rf -- "$temp"' EXIT - shred --remove $temp # delete file to make room for wallet - key="$(head -c64 /dev/urandom | base64 --wrap=0)" # 64 byte wallet encryption key to crypto-shred the wallet - $CLI -rpcwait createwallet "$temp" false false "$key" - $CLI -rpcwait -rpcwallet="$temp" walletpassphrase "$key" 60 # unlocks generator wallet for 1 minute - $CLI -rpcwait # waits for the RPC server to respond to ensure we get a private masterkey FIXME this needs to be used w/ the commands - extended_private_masterkey="$($CLI -rpcwallet=$temp listdescriptors "true" | grep --max-count=1 -oP '(?<=tr\().*?(?=/)')" - $CLI -rpcwallet="$temp" walletlock - $CLI -rpcwallet="$temp" unloadwallet - unset $key # deletes encryption key from memory - if [ "${extended_private_masterkey:0:4}" != 'xprv' ]; then - zenity --error --title='Failed to get Bitcoin Core entropy' --text='Bitcoin core may not be running or the RPC server is disabled.' "$ICON" & - exit 1 - fi - # Unconditionally display app entropy for audits - echo "App Entropy: $extended_private_masterkey" - codex32_array=($(python3 -c 'from codex32 import ms32 -master_seed = ms32.fresh_master_seed(16,"'"$user_entropy"'","'"$extended_private_masterkey"'") -id = ms32.seed_identifier(master_seed) -if "'$threshold'" == "1": - codex32_secret = ms32.encode("ms", 16, "0", id, "s", list(master_seed)) - for i in range('$locations'): - print(codex32_secret) -else: - ms32.existing_master_seed(master_seed,"'$threshold'",id,'$locations',"'"$passphrase"'")')) - display_confirm ${codex32_array[*]} - get_codex32_secret # this should be renamed "recover_seed" - # TODO if [ share_list is not in codex32 array ]; then - # warn: this was not a $string you wrote down earlier! and reject the share for recovery - #fi #TODO this should be an until loop so they can fail repeatedly and re-call get_codex32_secret. - restore_wallet '"now"' true # scans blockchain from current time -elif [ "$choice" == "Recover with codex32 seed backup" ]; then - get_codex32_secret - restore_wallet 1689105447 true # scans blockchain from Jul 11, 2023 for transactions -elif [ "$choice" == "Import descriptors" ]; then - echo 'Not yet implemented.' #TODO postpone for L2 -- import descriptors should give a text entry, file select or QR scanner option. - bails-wallet -else - exit 1 -fi -exit 0 - -# the extended_private_masterkey + user_entropy go thru Scrypt and the output determines master seed -# master seed determines identifier -# passphrase & master seed determine shares, and share indexes deterministically. So one extended_private_masterkey + user_entropy + threshold + passphrase always makes the same wallet AND the same share backup. - -# it should accept .bsms files ??? -# L4 the multisig wallets by default, must not have their public keys visible with just the memorized passphrase -# it is superior to use last 10 characters of any share as the "watch passphrase", although adding a memorized passphrase should be an option. - -# Min security: Can sign transactions online. -# Medium security: CipherStick restarts with networking off to restore the seeds whos keys are only held in RAM. The script exits when networked. Uses persistent storage to pass the PSBT -# Max security: separate offline only CipherStick (CryoStick) and dedicated offline PC that never goes back online after creating seed backup or is even destroyed. Uses QRs to pass the PSBT. -# PSBT should never be stored in plaintext in the persistent storage but should have the same privacy level as the watch only wallet. They should be carried in the same encrypted loop-back file. - -# seed is account 0, first share is account 1 and so forth -# the watch-only is a loopback file encrypted by hash160||identifier of the master seed. -# max privacy = 2 shares are required to open the watch-only -# this means each loopback file has a shamir share of the hash160||identifier split at threshold 2 -# normal privacy = 1 share -# this hash+identifier is stored in the 32 loopback files and automatically unlocks the watch only -# low privacy = passphrase - -# Test Vectors - -# git clone https://github.com/BenWestgate/Bails --depth=1; */b - -# Low security level vector -# ms12sq405jt9gxgqsnyg6pv53flaqp3y4n4dg42cwztypfjr -# ms12sq40nux796hz9jm8stsz4pc6vzq0yevxkp0f352gdr2q -# skunk 12 badge whig saline solomon -# first address: bc1qckgm74d36ly8aq0rnx80myy2g5vgcnx9xfpfn3 - -# High security level vector -# ms134uzkdy30apzfzqjzeftu27x56l7lt2v6xqutnd30q9gj -# ms134uzklurlkgz759e279pfycg4zldhwqlyrhvh5e9r3den -# ms134uzk4k5smystn20dvgpunsu5gkf3fdx7z7vn58en975d -# ms134uzkv8qsn5q0wnlg2kf2dduhe7w7jvv5va3czywq57fh -# skunk 12 badge whig saline solomon -# first address: bc1qd2qz3lyrek39hm2pxag8p07kuswmje07lyk3rm diff --git a/bails/.local/bin/decrypt-vault b/bails/.local/bin/decrypt-vault deleted file mode 100755 index df5b57e7..00000000 --- a/bails/.local/bin/decrypt-vault +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -# decrypt-vault: Decrypt image file and load wallets inside - -set -eu -set -o pipefail - -export TEXTDOMAIN='tails' -wallet= - - -pinentry_auth() { - # For consistency, let's use the same text in our prompt as GVFS - # when unlocking volumes from e.g. nautilus. - # shellcheck disable=SC3037 - dialog="$(/bin/echo -en 'Enter a passphrase to unlock the volume\nThe passphrase is needed to access encrypted data on %s.\n')" - gettext --domain gvfs -s "${dialog}" | ( - read -r title - desc="$(cat)" - # shellcheck disable=SC2059 - ( pinentry-gnome3 | sed -n 's/^D //p' ) </dev/null; then - res="$( - pinentry_auth | \ - LANG=C gio mount --device "$loop_device" 2>&1 || : - )" - fi - if mnt_point="$(mount | grep "$uuid on /media/amnesia" | cut -d' ' -f3)"; then - wallet=${mnt_point/'/media/amnesia/'/} - # TODO make sure as much works as possible if Persistent disabled - bitcoin-cli -datadir=/home/amnesia/Persistent/.bitcoin loadwallet "$wallet" false || \ - if pgrep bitcoin-qt &>/dev/null; then - notify-send 'ERROR: Wallet "'"$wallet"'" is already loaded.' --icon=error - break - else - wrapper bitcoin-qt -wallet="$wallet" & - fi - notify-send 'Wallet "'"$wallet"'" has been loaded.' --icon=bitcoin128 - break - elif echo "${res}" | grep -q 'Password dialog aborted'; then - break - fi -done - -if (($(lsblk "$loop_device" | wc -l) < 3)); then - udisksctl loop-delete -b "$loop_device" -fi -[ -z "$wallet" ] && exit 0 -exit 1 diff --git a/bails/.local/lib/python3.11/site-packages/bails-wallet/codex32_confirm.py b/bails/.local/lib/python3.11/site-packages/bails-wallet/codex32_confirm.py deleted file mode 100644 index 210dc6a0..00000000 --- a/bails/.local/lib/python3.11/site-packages/bails-wallet/codex32_confirm.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright (c) 2024 Ben Westgate -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gdk, Pango -import difflib -import sys -from xdg import xdg_data_home - -class EntryWindow(Gtk.Window): - def __init__(self, correct_string, progress=''): - self.string_type = "share" if int(correct_string[3]) else "secret" - Gtk.Window.__init__(self, title=f"Enter your codex32 {self.string_type} {progress}") - self.set_border_width(10) - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) - self.add(hbox) - self.entry_boxes = [] - self.correct_string = correct_string.upper() - self.progress = progress - self.universal_pos = 0 - self.bad_boxes = [] - self.words = (len(correct_string) + 3) // 4 - - # Set a monospace font - font_desc = Pango.FontDescription("Monospace 17") - - self.set_icon_from_file(str(xdg_data_home()) + "/icons/bails128.png") - - for i in range(self.words): - entry = Gtk.Entry() - entry.set_max_length(5) - if i == self.words - 1: - entry.set_max_length(4) - entry.set_width_chars(4) # Set width_chars to 4 - entry.override_font(font_desc) # Set the font - entry.connect("changed", self.on_entry_changed) - entry.connect("key-press-event", self.on_key_press) # Use key-press-event - entry.connect("key-release-event", self.on_key_release) # Use this for Del and Backspace - - if i == self.words // 2: - hbox.pack_start(Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0) - hbox.pack_start(entry, True, True, 0) - - self.entry_boxes.append(entry) - self.connect("delete-event", self.on_delete_event) - - - def get_full_text(self): - full_text = "" - for entry in self.entry_boxes: - text = entry.get_text() - full_text += text - return full_text - - def set_full_text(self,new_full_text=''): - for i, entry in enumerate(self.entry_boxes): - entry.set_text(new_full_text[i*4:i*4+4]) - - def set_cursor(self, offset=0): - # Rebuild full text - full_text = self.get_full_text().upper() - # advance or retreat cursor if needed - cursor_location = (len(full_text) - self.universal_pos - offset) - new_index = cursor_location // 4 - new_position = cursor_location % 4 - if 0 <= new_index < self.words: - next_box = self.entry_boxes[new_index] - next_box.grab_focus() - next_box.set_position(new_position) - self.set_full_text(full_text) - return - - def on_entry_changed(self, entry): - self.set_cursor() - s = difflib.SequenceMatcher(None, self.get_full_text(), self.correct_string) - self.bad_boxes = [] - for tag, i1, i2, _, j2 in s.get_opcodes(): - if tag == 'equal': - continue - if tag == 'insert' and i1 == i2 and j2 == len(self.correct_string): - continue # This just means Entry is incomplete - self.bad_boxes.append(i1 // 4) - - # add error location detection from difflib for highlighting. - correct_are_sensitive = False - for i in range(self.words): - if self.entry_boxes[i].get_text_length() == 4: - if i in self.bad_boxes: - # Set text color to red - self.entry_boxes[i].override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(.80, 0.2, 0.2, 1)) - correct_are_sensitive = True - else: - # Reset color - self.entry_boxes[i].override_color(Gtk.StateFlags.NORMAL, None) - if not correct_are_sensitive: # Lock good boxes from editing - self.entry_boxes[i].set_sensitive(correct_are_sensitive) - self.entry_boxes[i].override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0.8, 1.0, 0.8, 1.0)) - if self.get_full_text() == self.correct_string: - self.show_success_dialog() - Gtk.main_quit() - sys.exit(0) - - def show_success_dialog(self): - dialog = Gtk.MessageDialog( - self, - Gtk.DialogFlags.MODAL, - Gtk.MessageType.INFO, - Gtk.ButtonsType.OK, - f"You or a trustworthy heir will store this codex32 {self.string_type} in a secure place; either under lock and key or hidden where only you or they will find." - ) - dialog.set_title(f"{self.string_type.title()} {self.progress} successfully confirmed") - dialog.run() - dialog.destroy() - - def on_key_release(self, entry, event): - index = self.entry_boxes.index(entry) - pos = entry.get_position() - if index > 0: - prev_box = self.entry_boxes[index - 1] - if index < self.words - 1: - next_box = self.entry_boxes[index + 1] - if event.keyval == Gdk.KEY_BackSpace: - if pos % 5 == 0 and index > 0: - prev_box.grab_focus() - prev_box.set_position(5) - if event.keyval == Gdk.KEY_Delete and index < self.words - 1: - if pos % 5 == 4: - next_box.grab_focus() - next_box.set_position(0) - elif next_box.get_text_length() > 0: - entry.set_position(pos % 5) - self.set_cursor(-1) - - return False - - def on_key_press(self, entry, event): - index = self.entry_boxes.index(entry) - entry_pos = entry.get_position() - full_text = self.get_full_text() - self.universal_pos = len(full_text) - (4 * index + entry_pos) - - if index > 0: - prev_box = self.entry_boxes[index - 1] - if index < self.words - 1: - next_box = self.entry_boxes[index + 1] - if event.keyval == Gdk.KEY_Left: - if index > 0 and entry_pos % 5 == 0: - prev_box.grab_focus() - prev_box.set_position(prev_box.get_text_length()) - if event.keyval == Gdk.KEY_Right: - if entry_pos % 5 == 4 and index < self.words - 1: - next_box.grab_focus() - next_box.set_position(0) - if len(self.bad_boxes): - if event.keyval == Gdk.KEY_Return: # Pop the last bad box - self.entry_boxes[max(self.bad_boxes)].grab_focus() - if event.keyval == Gdk.KEY_Down: # Grab the next bad box - self.bad_boxes.reverse() - self.bad_boxes = [self.bad_boxes.pop()] + self.bad_boxes - self.entry_boxes[self.bad_boxes[0]].grab_focus() - self.bad_boxes.reverse() - if event.keyval == Gdk.KEY_Up: # Grab the previous bad box - self.bad_boxes = [self.bad_boxes.pop()] + self.bad_boxes - self.entry_boxes[self.bad_boxes[0]].grab_focus() - - return False - - def on_delete_event(self, widget, event): - # Exit with code 1 if the dialog is closed before completing the entry - Gtk.main_quit() - sys.exit(1) - -progress = sys.argv[2] if len(sys.argv) > 2 else '' -dialog = EntryWindow(sys.argv[1], progress) -dialog.connect("destroy", Gtk.main_quit) -dialog.show_all() -Gtk.main() diff --git a/bails/.local/lib/python3.11/site-packages/bails/create_vault.py b/bails/.local/lib/python3.11/site-packages/bails/create_vault.py deleted file mode 100755 index 28f67841..00000000 --- a/bails/.local/lib/python3.11/site-packages/bails/create_vault.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -import dbus -import subprocess -import secrets -from pathlib import Path - - -def create_vault(passphrase=secrets.token_urlsafe(32), label='Secret'): - """ - Creates a vault image file and formats it with a passphrase. - - @param passphrase: The passphrase string to use for encryption. - @param label: The filesystem label for the new vault. - """ - VAULT_PATH = "/live/persistence/TailsData_unlocked/dotfiles/Desktop/" - - if not Path(VAULT_PATH).exists(): - Path(VAULT_PATH).mkdir() - - name = 'vault_' + secrets.token_urlsafe(3) + '.img' - while Path(VAULT_PATH+name).exists(): - name = 'vault_' + secrets.token_urlsafe(3) + '.img' - - if subprocess.run(["fallocate", "-l", "64MiB", VAULT_PATH+name], - check=True).returncode: - print("Failed to create vault image") - exit(1) - result = subprocess.run( - ["udisksctl", "loop-setup", "-f", VAULT_PATH+name], - check=True, stdout=subprocess.PIPE) - if result.returncode != 0: - print("Failed to open vault loop device") - exit(1) - # Create a proxy object for the D-Bus service - proxy = dbus.SystemBus().get_object( - "org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/block_devices/loop" - + str(result.stdout).split('/dev/loop')[-1][:-4]) - - # Get the interface for formatting - iface = dbus.SystemBus().Interface(proxy, "org.freedesktop.UDisks2.Block") - # Define the format type and options as a dictionary - format_type = "ext4" - format_options = { - "encrypt.passphrase": dbus.String(passphrase), - "encrypt.type": dbus.String("luks2"), "label": dbus.String(label), - "take-ownership": dbus.Boolean(1)} - - # Call the Format method with the type and options - iface.Format(format_type, format_options) - - return - - diff --git a/bails/.local/lib/python3.11/site-packages/bails/passphrase.py b/bails/.local/lib/python3.11/site-packages/bails/passphrase.py deleted file mode 100755 index a4027b03..00000000 --- a/bails/.local/lib/python3.11/site-packages/bails/passphrase.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (c) 2023 Ben Westgate -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -############################################################################### -# Simple passphrase entry with toggle to unmask -############################################################################### - -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk -from xdg import xdg_data_home - - -class PassphraseDialog(Gtk.Dialog): - def __init__(self, parent, title): - Gtk.Dialog.__init__(self, title, parent, 0, - (Gtk.STOCK_OK, Gtk.ResponseType.OK, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) - self.set_icon_from_file(str(xdg_data_home()) + "/icons/bails128.png") - - self.set_default_size(500, 80) - - box = self.get_content_area() - - passphrase_entry = Gtk.Entry() - passphrase_entry.set_placeholder_text("Passphrase") - passphrase_entry.set_visibility(False) - box.pack_start(passphrase_entry, False, False, 1) - - show_checkbox = Gtk.CheckButton(label="Show Passphrase") - show_checkbox.set_active(False) # Set the checkbox to be active (checked) by default - box.pack_start(show_checkbox, False, False, 2) - - def on_show_checkbox_toggled(widget): - if show_checkbox.get_active(): - passphrase_entry.set_visibility(True) - else: - passphrase_entry.set_visibility(False) - - def on_passphrase_entry_activate(widget): - self.response(Gtk.ResponseType.OK) # Simulate clicking the "OK" button - - passphrase_entry.connect("activate", on_passphrase_entry_activate) - show_checkbox.connect("toggled", on_show_checkbox_toggled) - - self.show_all() - - self.passphrase = None - - response = self.run() - if response == Gtk.ResponseType.OK: - self.passphrase = passphrase_entry.get_text() - else: - self.passphrase = None - - self.destroy() - -# Example usage: -# dialog = PassphraseDialog(None, "Enter passphrase") -# if dialog.passphrase: -# print(dialog.passphrase) diff --git a/bails/.local/lib/python3.11/site-packages/codex32/ms32.py b/bails/.local/lib/python3.11/site-packages/codex32/ms32.py deleted file mode 100755 index 452dd7c4..00000000 --- a/bails/.local/lib/python3.11/site-packages/codex32/ms32.py +++ /dev/null @@ -1,396 +0,0 @@ -#!/bin/python3 -# Author: Leon Olsson Curr and Pearlwort Sneed -# License: BSD-3-Clause - -import hashlib - -CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" -MS32_CONST = 0x10ce0795c2fd1e62a -MS32_LONG_CONST = 0x43381e570bf4798ab26 -bech32_inv = [ - 0, 1, 20, 24, 10, 8, 12, 29, 5, 11, 4, 9, 6, 28, 26, 31, - 22, 18, 17, 23, 2, 25, 16, 19, 3, 21, 14, 30, 13, 7, 27, 15, -] - - -def ms32_polymod(values): - GEN = [ - 0x19dc500ce73fde210, - 0x1bfae00def77fe529, - 0x1fbd920fffe7bee52, - 0x1739640bdeee3fdad, - 0x07729a039cfc75f5a, - ] - residue = 0x23181b3 - for v in values: - b = (residue >> 60) - residue = (residue & 0x0fffffffffffffff) << 5 ^ v - for i in range(5): - residue ^= GEN[i] if ((b >> i) & 1) else 0 - return residue - - -def ms32_verify_checksum(data): - if len(data) >= 96: # See Long codex32 Strings - return ms32_verify_long_checksum(data) - if len(data) <= 93: - return ms32_polymod(data) == MS32_CONST - return False - - -def ms32_create_checksum(data): - if len(data) > 80: # See Long codex32 Strings - return ms32_create_long_checksum(data) - values = data - polymod = ms32_polymod(values + [0] * 13) ^ MS32_CONST - return [(polymod >> 5 * (12 - i)) & 31 for i in range(13)] - - -def ms32_long_polymod(values): - GEN = [ - 0x3d59d273535ea62d897, - 0x7a9becb6361c6c51507, - 0x543f9b7e6c38d8a2a0e, - 0x0c577eaeccf1990d13c, - 0x1887f74f8dc71b10651, - ] - residue = 0x23181b3 - for v in values: - b = (residue >> 70) - residue = (residue & 0x3fffffffffffffffff) << 5 ^ v - for i in range(5): - residue ^= GEN[i] if ((b >> i) & 1) else 0 - return residue - - -def ms32_verify_long_checksum(data): - return ms32_long_polymod(data) == MS32_LONG_CONST - - -def ms32_create_long_checksum(data): - values = data - polymod = ms32_long_polymod(values + [0] * 15) ^ MS32_LONG_CONST - return [(polymod >> 5 * (14 - i)) & 31 for i in range(15)] - - -def bech32_mul(a, b): - res = 0 - for i in range(5): - res ^= a if ((b >> i) & 1) else 0 - a *= 2 - a ^= 41 if (32 <= a) else 0 - return res - - -def bech32_lagrange(l, x): - n = 1 - c = [] - for i in l: - n = bech32_mul(n, i ^ x) - m = 1 - for j in l: - m = bech32_mul(m, (x if i == j else i) ^ j) - c.append(m) - return [bech32_mul(n, bech32_inv[i]) for i in c] - - -def ms32_interpolate(l, x): - w = bech32_lagrange([s[5] for s in l], x) - res = [] - for i in range(len(l[0])): - n = 0 - for j in range(len(l)): - n ^= bech32_mul(w[j], l[j][i]) - res.append(n) - return res - - -def ms32_recover(l): - return ms32_interpolate(l, 16) - - -# Copyright (c) 2023 Ben Westgate -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - - -def ms32_encode(hrp, data): - """Compute a MS32 string given HRP and data values.""" - combined = data + ms32_create_checksum(data) - return hrp + '1' + ''.join([CHARSET[d] for d in combined]) - - -def ms32_decode(bech): - """Validate a ms32 string, and determine HRP and data.""" - if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or - (bech.lower() != bech and bech.upper() != bech)): - return (None, None, None, None, None) - bech = bech.lower() - pos = bech.rfind('1') - if pos < 1 or pos + 46 > len(bech): - return (None, None, None, None, None) - if not all(x in CHARSET for x in bech[pos + 1:]): - return (None, None, None, None, None) - hrp = bech[:pos] - k = bech[pos + 1] - if k == "1" or not k.isdigit(): - return (None, None, None, None, None) - identifier = bech[pos + 2:pos + 6] - share_index = bech[pos + 6] - if k == "0" and share_index != "s": - return (None, None, None, None, None) - data = [CHARSET.find(x) for x in bech[pos + 1:]] - checksum_length = 13 if len(data) < 95 else 15 - if not ms32_verify_checksum(data): - return (None, None, None, None, None) - return (hrp, k, identifier, share_index, data[:-checksum_length]) - - -def convertbits(data, frombits, tobits, pad=True): - """General power-of-2 base conversion.""" - acc = 0 - bits = 0 - ret = [] - maxv = (1 << tobits) - 1 - max_acc = (1 << (frombits + tobits - 1)) - 1 - for value in data: - if value < 0 or (value >> frombits): - return None - acc = ((acc << frombits) | value) & max_acc - bits += frombits - while bits >= tobits: - bits -= tobits - ret.append((acc >> bits) & maxv) - if pad: - if bits: - ret.append((acc << (tobits - bits)) & maxv) - elif bits >= frombits: - return None - return ret - - -def decode(hrp, codex): - """Decode a codex32 string.""" - hrpgot, k, identifier, share_index, data = ms32_decode(codex) - if hrpgot != hrp: - return (None, None, None, None) - decoded = convertbits(data[6:], 5, 8, False) - if k == "1": - return (None, None, None, None) - return (k, identifier, share_index, decoded) - - -def encode(hrp, seed_length, k, identifier, share_index, payload): - """Encode a codex32 string.""" - data_len = len(convertbits(bytes(seed_length), 8, 5)) - combined = payload - if share_index.lower() == 's': # add double sha256 hash byte to pad seeds - combined += [list( - hashlib.sha256(hashlib.sha256(bytes(payload)).digest()).digest())[ - 0]] - data = convertbits(combined, 8, 5, False)[:data_len] - ret = ms32_encode(hrp, [CHARSET.find(x.lower()) for x in - k + identifier + share_index] + data) - if decode(hrp, ret) == (None, None, None, None): - return None - return ret - - -def derive_new_share(share_list=[], new_share_index="s"): - """Derive new codex32 shares from a threshold of shares.""" - ms32_share_list = [] - for share in share_list: - ms32_share_list += [ms32_decode(share)[4]] - share_index = CHARSET.find(new_share_index.lower()) - return ms32_encode("ms", ms32_interpolate(ms32_share_list, share_index)) - - -def fresh_master_seed(seed_length, user_entropy, bitcoin_core_entropy): - """Derive a fresh master seed with seed length bytes from threshold, passphrase, user entropy, bitcoin core entropy and n.""" - if 16 > seed_length > 64: - return None - master_seed = hashlib.scrypt( - password=bytes(user_entropy + str(seed_length), "utf"), - salt=bytes(bitcoin_core_entropy, "utf"), n=2 ** 20, r=8, p=1, - maxmem=1025 ** 3, dklen=seed_length) - return master_seed - - -def seed_identifier(master_seed): - """Derive identifier from master seed hash to distinguish between multiple different master seeds.""" - identifier = '' - h = hashlib.new('ripemd160') - h.update(master_seed) - hd_seed_id = h.digest() - for char in convertbits(list(hd_seed_id), 8, 5)[:4]: - identifier += CHARSET[char] - return identifier # allows connecting different share sets to the same master seed - - -def existing_master_seed(master_seed, k, identifier, n, passphrase): - """Derive codex32 shares from a master_seed, threshold k, identifier, n and passphrase.""" - import random - import hmac - derived_share_list = [] - if int(k) - 1 > n > 30 or 9 < int(k) < 2: - return (None) - seed_length = len(master_seed) - codex32_secret = encode("ms", seed_length, k, identifier, "s", - list(master_seed)) - codex32_kdf_share, salt, indices_free = kdf_share(passphrase, k, identifier, - seed_length) - random.seed(a=hmac.digest(master_seed, salt, 'sha512')) - share_index = random.sample(indices_free, n) - share_list = [codex32_secret, codex32_kdf_share] - if int(k) > 2: - share_entropy = hashlib.scrypt(password=master_seed, salt=salt, - n=2 ** 20, r=8, p=1, maxmem=1025 ** 3, - dklen=(int(k) - 2) * (seed_length + 1)) - for x in range(int(k) - 2): - data = list(share_entropy[x * (seed_length + 1):x * ( - seed_length + 1) + seed_length + 1]) - share_list += [ - encode("ms", seed_length, k, identifier, share_index[x], data)] - for x in range(int(k) - 2, n): - derived_share_list += [derive_new_share(share_list, share_index[x])] - paper_shares = share_list[2:] + derived_share_list - for share in paper_shares: - print(share) - return share_list + derived_share_list - - -def kdf_share(passphrase, k, identifier, seed_length): - """Derive codex32 share from a passphrase.""" - import random - salt = bytes(k + identifier + str(seed_length), "utf") - pw_hash = hashlib.scrypt(password=bytes(passphrase, "utf"), salt=salt, - n=2 ** 20, r=8, p=1, maxmem=1025 ** 3, - dklen=seed_length + 1) - indices_free = CHARSET.replace('s', '') - random.seed(a=salt) - kdf_share_index = random.choice(indices_free) - indices_free = indices_free.replace(kdf_share_index, '') - codex32_kdf_share = encode("ms", seed_length, k, identifier, - kdf_share_index, list(pw_hash)) - return (codex32_kdf_share, salt, indices_free) - - -def recover_master_seed(share_list=[], passphrase=''): - """Derive codex32 secret from a threshold of shares.""" - k = [None] * len(share_list) - identifier = [None] * len(share_list) - share_index = [None] * len(share_list) - decoded = [None] * len(share_list) - for i in range(len(share_list)): - k[i], identifier[i], share_index[i], decoded[i] = decode("ms", - share_list[i]) - if k.count(k[0]) != len(k) or identifier.count(identifier[0]) != len( - identifier): - return (None) - if len({len(i) for i in decoded}) != 1: - return (None) - if passphrase != '': - share_list += [ - kdf_share(passphrase, k[0], identifier[0], len(decoded[0]))[0]] - return derive_new_share(share_list, 's') - - -def identifier_verify_checksum(codex32_secret): - """Verify an identifier checksum in a codex32 secret.""" - k, identifier, share_index, decoded = decode("ms", codex32_secret) - hash_id = seed_identifier(bytes(decoded)) - if share_index != 's' or hash_id[:3] != identifier[:3]: - print('1') - return False - print('0') - return True - - -def verify_checksum(codex32_string): - """Verify a codex32 checksum in a codex32 string.""" - k, identifier, share_index, decoded = decode("ms", codex32_string) - if decoded == None or len(decoded) < 16: - print('1') - return False - print('0') - return True - - -def rotate_shares(codex32_secret, k, n, passphrase=''): - """Rotate codex32 backup shares after a lost or stolen share or passphrase or to change k.""" - old_k, identifier, index, master_seed = decode('ms', codex32_secret) - if index != 's': - return (None) - hrp, old_k, old_id, ms32_index, ms32_decoded = ms32_decode(codex32_secret) - ms32_decoded[4] = (ms32_decoded[4] + 1) % 32 - new_id = ms32_encode('ms', ms32_decoded)[4:8] - return existing_master_seed(bytes(master_seed), k, new_id, n, passphrase) - -# import secrets - -# master_seed = fresh_master_seed(16,'Walletasdfpassword34','L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6') -# master_seed = b"\x8a\xa0'm\xad\xa1\xf9\tY\xc5\x86r\xfd\x96\x1bY" -# id = seed_identifier(master_seed) -# print(id) -# backup = existing_master_seed(master_seed,'3',id,4,'password') -# new_backup = rotate_shares('ms13uccps32szwmdd58usjkw9see0m9smtydt6h374kxafvw','2',2,'password1') -# print(backup) -# print(new_backup) -# new_backup = existing_master_seed(new_master_seed,'2','6666',4,'password') -# print(new_backup) -# print(recover_master_seed(new_backup[2:4])) -# print(recover_master_seed(new_backup[3:5])) -# print(recover_master_seed(new_backup[4:])) -# print(recover_master_seed([new_backup[3]],'password')) -# print(recover_master_seed([new_backup[5]],'password')) -# codex_secret = recover_master_seed([new_backup[5]],'password') -# print(verify_identifier_checksum(codex_secret)) - -# new_backup = existing_master_seed(new_master_seed,'2','0g0d',4,'password') -# print(new_backup) -# codex_secret = recover_master_seed([new_backup[5]],'password') -# print(verify_identifier_checksum(codex_secret)) - - -# test vector 1 -# test_vec1 = ['MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM','MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN'] -# print(recover_master_seed(test_vec1)) -# new_secret = recover_master_seed([test_vec1[0]],'a strong password') - - -# ['ms126gpdszx8y0uuwrqtxfkdvxecqzm52ulrhkkhsn2d6704', 'ms126gpd2uen8lyvrl38wyfp6x6ae7rrd7ff09gp694a74mu', 'ms126gpdxru7qp4j9a4mpzva2xa5jujferem7uh3g4srdakf', 'ms126gpd5s9m79nkv6mcrt47mxrl7m5jxhgdcxyq7yf8vpnp', 'ms126gpd07yg0rks42jwqj5gkxj95t3szf9sa32drfgpenqd', 'ms126gpdadad38s5ududzmdt8xvwcvhtaa5xmteu4c39c099'] -# test vectors -# seed = "dc5423251cb87175ff8110c8531d0952d8d73e1194e95b5f19d6f9df7c01111104c9baecdfea8cccc677fb9ddc8aec5553b86e528bcadfdcc201c17c638c47e9" -# seed_bytes = list(bytes.fromhex(seed)) -# print(encode("ms","0","0C8V","S",seed_bytes)) -# print(decode("ms","MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK")) -# print(encode("ms","0","0c8v","s",decode("ms","MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK")[3])) -# seed = decode("ms","MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK") -# print(encode("ms", '0', 'leet', 's', seed[3])) -# print(seed_bytes) -# print(encode("ms", seed_bytes)) -# print(decode("ms","ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw")[3]) -# print(encode("ms","0","test", "s", seed_bytes)) -# ms_string = encode("ms","0","test","s",list(bytes.fromhex("318c6318c6318c6318c6318c6318c631"))) -# print(ms_string) -# print(decode("ms","ms13cashsllhdmn9m42vcsamx24zrxgs3qqjzqud4m0d6nln")) -# print(decode("ms","ms13cashsllhdmn9m42vcsamx24zrxgs3qpte35dvzkjpt0r")) -# print(ms32_encode("ms",derive_new_share(["MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM","MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN"],"D"))) - -# print(ms32_encode("ms",derive_new_share(["ms13cashsllhdmn9m42vcsamx24zrxgs3qqjzqud4m0d6nln","ms13casha320zyxwvutsrqpnmlkjhgfedca2a8d0zehn8a0t","ms13cashcacdefghjklmnpqrstuvwxyz023949xq35my48dr"],"f"))) \ No newline at end of file diff --git a/bails/.local/lib/python3.11/site-packages/codex32/naive_ms32_ecc.py b/bails/.local/lib/python3.11/site-packages/codex32/naive_ms32_ecc.py deleted file mode 100755 index af9bb8e1..00000000 --- a/bails/.local/lib/python3.11/site-packages/codex32/naive_ms32_ecc.py +++ /dev/null @@ -1,95 +0,0 @@ -import ms32 -import sys -CHARSET=ms32.CHARSET -VALID_LENGTH = 48 -VALID_HRP='ms1' - -def is_valid_codex32(codex32_str): - """Check if the given codex32 string is valid.""" - # Check length and character set - if len(codex32_str) != VALID_LENGTH: - return False - if heading and codex32_str[:len(heading)] != heading: - return False - # Verify the checksum - if ms32.decode("ms",codex32_str) != (None, None, None, None): - return True - - -def find_closest_valid_codex32(codex32_str, insert=[], delete=[], substitute=[], valid_identifier=''): - """Find the closest valid codex32 string using an iterative approach.""" - global heading - heading = VALID_HRP+valid_identifier - if is_valid_codex32(codex32_str): - return codex32_str - - def generate_edits(candidate): - # Generate all possible edits (insertion, deletion, substitution) - edits = set() - - for i in range(len(candidate)): - # Deletions - if delete[search_phase]: - if (i < len(heading) and candidate[i] != heading[i]) or i >= len(heading): - edits.add(candidate[:i] + candidate[i + 1:]) - # Substitutions: - if substitute[search_phase]: - for char in CHARSET: - if char != candidate[i]: - if (i < len(heading) and char == heading[i]) or i >= len(heading): - edits.add(candidate[:i] + char + candidate[i + 1:]) - # Insertions - if insert[search_phase]: - for char in CHARSET: - edits.add(candidate[:i + 1] + char + candidate[i + 1:]) - - # Add insertion at the end of the candidate string - if insert[search_phase]: - for char in CHARSET: - edits.add(candidate[:len(candidate)] + char) - return edits - - candidates = {codex32_str} # Start with the given string as the first candidate - for search_phase in range(0, len(delete)): - new_candidates = set() - for candidate in candidates: - # Generate all possible edits (insertion, deletion, substitution) - edits = generate_edits(candidate) - for new_candidate in edits: - if new_candidate not in new_candidates: - new_candidates.add(new_candidate) - candidates = new_candidates - - # Check if any of the new candidates are valid codex32 strings - for candidate in candidates: - if is_valid_codex32(candidate): - return candidate - - return None # If no valid codex32 string is found within the error correction capabilities - - -# Test the function with an example invalid codex32 string -invalid_codex32_str = sys.argv[1] -valid_identifier = sys.argv[2] -length_syn = len(invalid_codex32_str) - VALID_LENGTH # parameter order (insertion, deletion, substitution) -if length_syn == -2: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [True]*2, [False]*2, [False]*2, valid_identifier) -elif length_syn == -1: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [True, False], [False, False], [False, True], valid_identifier) -elif length_syn == 0: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [False, True], [True, False], [False, False], valid_identifier) - if not closest_valid_codex32: - closest_valid_codex32= find_closest_valid_codex32(invalid_codex32_str, [False, False], [False, False], [True, True], valid_identifier) -elif length_syn == 1: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [False, False], [True, False], [False, True], valid_identifier) - if not closest_valid_codex32: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [False, False, True], [True, True, False], [False, False, False], valid_identifier) -elif length_syn == 2: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [False, False, False], [True, True, False], [False, False, True], valid_identifier) -elif 6 > length_syn > 2: - closest_valid_codex32 = find_closest_valid_codex32(invalid_codex32_str, [False]*length_syn, [True]*length_syn, [False]*length_syn, valid_identifier) - -if closest_valid_codex32: - print(closest_valid_codex32) -else: - exit(1) \ No newline at end of file diff --git a/bails/.local/share/applications/bails.desktop b/bails/.local/share/applications/bails.desktop old mode 100755 new mode 100644 index 5dffb030..3fa03cdc --- a/bails/.local/share/applications/bails.desktop +++ b/bails/.local/share/applications/bails.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=CipherStick -Comment=Create and Restore Codex32 Backups, Configure Settings and Clone CipherStick shareware for a friend. +Comment=Configure Bitcoin Core and Sparrow on Tails. Exec=/live/persistence/TailsData_unlocked/dotfiles/.local/bin/wrapped bails-menu Terminal=false Type=Application diff --git a/bails/.local/share/applications/decrypt-vault.desktop b/bails/.local/share/applications/decrypt-vault.desktop deleted file mode 100755 index d5bf4f2b..00000000 --- a/bails/.local/share/applications/decrypt-vault.desktop +++ /dev/null @@ -1,12 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=CipherStick Wallet Loader -Comment=Create and Restore Codex32 Backups, Configure Settings and Clone CipherStick shareware for a friend. -Exec=/live/persistence/TailsData_unlocked/dotfiles/.local/bin/decrypt-vault %u -Terminal=false -Type=Application -Icon=bails128 -MimeType=application/x-cd-image;application/x-raw-disk-image; -StartupWMClass=bails -NoDisplay=true -Hidden=true From 3cdd6e8cdbbd4de361b5d768ebc3d3f7a05a7504 Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Fri, 18 Sep 2026 19:34:33 -0500 Subject: [PATCH 2/5] docs: Preserve legacy wallet recovery Stop presenting the removed GTK3 wallet as available and document how existing users should preserve Bitcoin Core wallet data and Codex32 recovery material without automatic private-key migration. Fixes #201. --- README.md | 12 +++++------- docs/LEGACY_WALLET_RECOVERY.md | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 docs/LEGACY_WALLET_RECOVERY.md diff --git a/README.md b/README.md index 200000f3..d434f4c6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ CipherStick is the most private way to transact and store bitcoin. It ensures yo ## Bitcoin Core on Tails -Bitcoin Core and Tails are relied upon by millions to safeguard their online privacy and security, particularly in sensitive and high-risk situations. This repository provides a script to install Bitcoin Core on Tails and create a wallet backup. +Bitcoin Core and Tails are relied upon by millions to safeguard their online privacy and security, particularly in sensitive and high-risk situations. This repository provides a script to install Bitcoin Core on Tails. + +The legacy GTK3 Codex32 wallet create/restore flow is disabled on current stable Tails. Replacement Codex32 restoration is tracked separately in issue #215. Bitcoin Core connects to the Bitcoin network to download and validate blocks and transactions, featuring a user-friendly interface and built-in wallet. - [Bitcoin Core :: About](https://bitcoincore.org/en/about/) @@ -100,13 +102,9 @@ To contact Ben Westgate by email `benwestgate@protonmail.com`. For a discussion on the pros and cons of using CipherStick, refer to the [detailed document](docs/Advantages_and_Disadvantages.md). It describes the unique features and limitations of the CipherStick platform. -### CipherStick is the First Codex32-enabled (BIP93) Wallet - -Find more information on [Codex32](https://secretcodex32.com/index.html) and [BIP93](https://github.com/bitcoin/bips/blob/master/bip-0093.mediawiki). - -### Compliant with Auditable Bitcoin Wallets Standard +### Codex32 restoration status -Refer to the [Auditable Bitcoin Wallets Standard](https://github.com/oleganza/bitcoin-papers/blob/master/AuditableBitcoinWallets.md) for compliance details. All necessary information to audit `bails-wallet` is displayed by the terminal. +The legacy GTK3 Codex32 create/restore interface is disabled on current stable Tails. Issue #215 tracks the replacement restoration path using `python-codex32` and Bitcoin Core. ## Source Code Headers diff --git a/docs/LEGACY_WALLET_RECOVERY.md b/docs/LEGACY_WALLET_RECOVERY.md new file mode 100644 index 00000000..856d6895 --- /dev/null +++ b/docs/LEGACY_WALLET_RECOVERY.md @@ -0,0 +1,35 @@ +# Legacy CipherStick Wallet Recovery + +The custom `bails-wallet` application and bundled Codex32 implementation are no longer part of the supported CipherStick runtime. Existing users should preserve recovery material and standard Bitcoin Core wallet data before updating or changing media. + +## Do not destroy existing state + +Before changing an existing CipherStick: + +1. shut Bitcoin Core down cleanly; +2. keep the original CipherStick USB unchanged until recovery has been tested; +3. preserve every Codex32 share and any passphrase needed to reach its threshold; +4. make a separate backup of any Bitcoin Core wallet directory you intend to keep. + +Removal of wallet code from this repository does not intentionally delete Bitcoin Core wallet data. Until deterministic stale-file removal is implemented, an update may also leave old CipherStick scripts on disk; their presence does not make those scripts supported. + +## Existing Bitcoin Core wallets + +The legacy flow created Bitcoin Core wallets through Bitcoin Core RPC and imported descriptors into those wallets. Wallet directories already present under the Bitcoin Core data directory remain Bitcoin Core wallet data; they do not require `bails-wallet` merely to exist. + +Use the Bitcoin Core version appropriate for the wallet data, keep an untouched backup, and use Bitcoin Core's documented wallet loading and migration procedures. Do not automatically copy private keys into Sparrow or another coordinator as part of this removal. + +## Codex32 backups + +Codex32 shares remain recovery material even though the bundled implementation is removed. The replacement restoration path is tracked in issue #215 and uses the separately reviewed `python-codex32` project to recover a master seed for Bitcoin Core. + +Until that replacement is reviewed and merged: + +- keep the original shares unchanged; +- do not discard the original CipherStick based on an untested conversion; +- do not send shares to an online service for decoding; +- do not assume a third-party Codex32 tool has the same error-correction or derivation behavior as the removed implementation. + +## Migration boundary + +This removal deliberately does not migrate private keys automatically. A future recovery tool must make secret lifetime explicit, avoid unintended persistence of the recovered master seed, and restore into Bitcoin Core without reintroducing the old `bails-wallet` codebase. From 3e53933e9d08a118313381a48f6cacc7a1fae1ce Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Fri, 18 Sep 2026 19:34:44 -0500 Subject: [PATCH 3/5] ci: Drop removed wallet lint exclusion Remove the ShellCheck ignore entry for the deleted bails-wallet script so CI reflects the narrowed runtime. Refs #201. --- .github/workflows/lint.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ad443a6c..35895ca2 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -19,7 +19,6 @@ jobs: SHELLCHECK_OPTS: -e 2012 -e SC2046 -e SC2086 -e SC2164 with: ignore_paths: | - ./bails/.local/bin/bails-wallet ./bails/.local/bin/ibd-progress ./bails/.local/bin/backupd # python-lint: From d96752e503cd57c3321086ecd730fd1166973aa6 Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Sat, 19 Sep 2026 03:17:14 -0500 Subject: [PATCH 4/5] core: Remove stale wallet comment The legacy wallet is removed by this branch, so install-core must no longer claim that it starts bails-wallet. Refs #201. --- bails/.local/bin/install-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bails/.local/bin/install-core b/bails/.local/bin/install-core index bac04b26..49ae5b04 100755 --- a/bails/.local/bin/install-core +++ b/bails/.local/bin/install-core @@ -21,7 +21,7 @@ # THE SOFTWARE. ############################################################################### -# Downloads Bitcoin Core and installs it persistently. Starts bails-wallet. +# Downloads Bitcoin Core and installs it persistently. ############################################################################### # For security reviewing use: https://explainshell.com/ for unfamiliar commands. From 2c026525cad05a12d95d8ae3445ebc8084f7540e Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Sat, 19 Sep 2026 14:05:18 -0500 Subject: [PATCH 5/5] wallet: Remove Sparrow support (#216) CipherStick no longer needs a separate wallet coordinator for its supported Bitcoin Core workflow. Remove the Sparrow installer, menu action, launcher preference, and Sparrow-specific documentation so the supported runtime has a smaller trust and supply-chain surface. Refs #201 --- bails/.local/bin/bails-menu | 9 +- bails/.local/bin/install-sparrow | 99 ------------------- bails/.local/bin/wrapped | 1 - bails/.local/share/applications/bails.desktop | 2 +- docs/LEGACY_WALLET_RECOVERY.md | 2 +- 5 files changed, 5 insertions(+), 108 deletions(-) delete mode 100755 bails/.local/bin/install-sparrow diff --git a/bails/.local/bin/bails-menu b/bails/.local/bin/bails-menu index 2ddce142..1c08f2ce 100755 --- a/bails/.local/bin/bails-menu +++ b/bails/.local/bin/bails-menu @@ -63,18 +63,17 @@ response=$(zenity --question --window-icon="$XDG_DATA_HOME/icons/bails128.png" \ Backup - Duplicate your CipherStick node to protect data. -Settings - Updates, add software, and configure." \ +Settings - Updates and configuration." \ --extra-button="Clone" --extra-button="Backup" --extra-button="Settings" --extra-button="Help" \ --switch) -# Menu options -which Sparrow && action=Update || action=Install +# Menu options case "$response" in "Clone") bails-clone ;; "Backup") bails-backup ;; "Settings") response2=$(zenity --window-icon="$XDG_DATA_HOME/icons/bails128.png" --question \ --icon-name=org.gnome.Settings --title=Settings --text="Configure Bitcoin Core on Tails\n\nWarning: Only install or update software retaining a good reputation." \ - --extra-button="Update Bitcoin Core" --extra-button="Update CipherStick" --extra-button="$action Sparrow" --extra-button="Network Privacy" \ + --extra-button="Update Bitcoin Core" --extra-button="Update CipherStick" --extra-button="Network Privacy" \ --switch) ;; "Help") response2=$(zenity --window-icon="$LOCAL/share/icons/bails128.png" --question \ --icon-name="help-faq-symbolic" --title="Help Menu" --text="" \ @@ -94,8 +93,6 @@ case "$response2" in -- "install-core" ;; "Update CipherStick") gnome-terminal --title="Updating CipherStick..." --hide-menubar \ -- /bin/bash -c "git clone https://github.com/benwestgate/bails --depth=1 && bails/b --update" ;; - "$action Sparrow") gnome-terminal --title="$action Sparrow Wallet" --hide-menubar \ - -- "install-sparrow" ;; "Network Privacy") onlynet_onion; bails-menu ;; "") bails-menu esac diff --git a/bails/.local/bin/install-sparrow b/bails/.local/bin/install-sparrow deleted file mode 100755 index b426f7e6..00000000 --- a/bails/.local/bin/install-sparrow +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2024 Ben Westgate -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -############################################################################### -# Downloads/Updates Sparrow and installs it persistently. Starts sparrow. -############################################################################### - -readonly DOTFILES='/live/persistence/TailsData_unlocked/dotfiles' -readonly PERSISTENT='/live/persistence/TailsData_unlocked/Persistent' -readonly XDG_DATA_HOME=$DOTFILES/.local/share -readonly ICON="--window-icon=$XDG_DATA_HOME/icons/bails128.png" -readonly SPARROW_SIGNING_KEY="D4D0D3202FC06849A257B38DE94618334C674B40" -readonly SPARROW_DOWNLOAD_URL="https://sparrowwallet.com/download" - -mkdir {$XDG_DATA_HOME/,$PERSISTENT/.}sparrow -gpg --keyserver=keyserver.ubuntu.com --recv-keys $SPARROW_SIGNING_KEY & keys=$! -cd $XDG_DATA_HOME/sparrow || exit 1 -wget --continue --no-directories --span-hosts --recursive --level=1 \ - --include-directories=sparrowwallet/sparrow/releases/download/ \ - --reject=sparrow-server* --accept=manifest.txt{,.asc},amd64.deb \ - --domains={sparrowwallet,github}.com $SPARROW_DOWNLOAD_URL -wait -f $keys -for version in ./*manifest.txt; do - gpg --verify "$version".asc || exit 1 - deb=$(sha256sum --check "$version" --ignore-missing) && echo "$deb" || exit 1 -done -rm -rf {DEBIAN,opt} -dpkg-deb -vR "${deb::-4}" . || exit 1 -find . -type f -exec sed -i s,/opt/sparrow,$DOTFILES/.local,g {} \; -find . -type f -exec sed -i "s,bin/Sparrow,bin/wrapped Sparrow -d $PERSISTENT/.sparrow,g" {} \; -sed '$ s/$/\nSingleMainWindow=true/' opt/sparrow/lib/sparrow-Sparrow.desktop > $XDG_DATA_HOME/applications/sparrow-Sparrow.desktop -rsync opt/sparrow/lib/Sparrow.png $XDG_DATA_HOME/icons -rsync -r --remove-source-files opt/sparrow/ $DOTFILES/.local -DEBIAN/postinst configure # Install MIME associations and shortcuts -rsync --remove-source-files {~/.local/share,$XDG_DATA_HOME}/applications/defaults.list # Persist MIME -if ln -s {$XDG_DATA_HOME,~/.local/share}/icons/Sparrow.png; then - echo '{ - "mode": "ONLINE", - "bitcoinUnit": "AUTO", - "unitFormat": "DOT", - "feeRatesSource": "ELECTRUM_SERVER", - "loadRecentWallets": true, - "validateDerivationPaths": true, - "groupByAddress": true, - "includeMempoolOutputs": true, - "notifyNewTransactions": true, - "checkNewVersions": true, - "theme": "LIGHT", - "openWalletsInNewWindows": false, - "hideEmptyUsedAddresses": false, - "showTransactionHex": true, - "showLoadingLog": true, - "showAddressTransactionCount": false, - "showDeprecatedImportExport": false, - "signBsmsExports": false, - "preventSleep": false, - "recentWalletFiles": [], - "dustAttackThreshold": 1000, - "enumerateHwPeriod": 30, - "useZbar": true, - "serverType": "BITCOIN_CORE", - "coreServer": "http://127.0.0.1:17600", - "coreAuthType": "COOKIE", - "coreDataDir": "/live/persistence/TailsData_unlocked/Persistent/.bitcoin", - "useLegacyCoreWallet": false, - "useProxy": true, - "proxyServer": "127.0.0.1:9050", - "autoSwitchProxy": true, - "maxServerTimeout": 34, - "maxPageSize": 100, - "usePayNym": false, - "mempoolFullRbf": false, - "appWidth": 927.0, - "appHeight": 922.0 -}' > $PERSISTENT/.sparrow/config - zenity --info --title="$(Sparrow --version) successfully installed" --text="Installation was successful." --icon-name=Sparrow $ICON & -else - zenity --info --title="Successfully upgraded to $(Sparrow --version)" --text="Update was successful." --icon-name=Sparrow $ICON & -fi -gtk-launch sparrow-Sparrow diff --git a/bails/.local/bin/wrapped b/bails/.local/bin/wrapped index 5447a6a9..fdb14650 100755 --- a/bails/.local/bin/wrapped +++ b/bails/.local/bin/wrapped @@ -37,7 +37,6 @@ ln -s /live/persistence/TailsData_unlocked/Persistent/.* ~ # Update favorite apps list favorites="['bails.desktop', 'bitcoin-qt.desktop', 'tor-browser.desktop', 'tca.desktop', 'org.boum.tails.PersistentStorage.desktop', 'tails-documentation.desktop', 'tails-installer.desktop', 'thunderbird.desktop', 'org.keepassxc.KeePassXC.desktop', 'org.kde.kleopatra.desktop']" -which Sparrow && favorites=${favorites/bitcoin-qt/sparrow-Sparrow} gsettings set org.gnome.shell favorite-apps "$favorites" space=$(( ($(df --output=avail "$DATA_DIR" | tail -1) + \ diff --git a/bails/.local/share/applications/bails.desktop b/bails/.local/share/applications/bails.desktop index 3fa03cdc..f06ff6f6 100644 --- a/bails/.local/share/applications/bails.desktop +++ b/bails/.local/share/applications/bails.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=CipherStick -Comment=Configure Bitcoin Core and Sparrow on Tails. +Comment=Configure Bitcoin Core on Tails. Exec=/live/persistence/TailsData_unlocked/dotfiles/.local/bin/wrapped bails-menu Terminal=false Type=Application diff --git a/docs/LEGACY_WALLET_RECOVERY.md b/docs/LEGACY_WALLET_RECOVERY.md index 856d6895..2891bae2 100644 --- a/docs/LEGACY_WALLET_RECOVERY.md +++ b/docs/LEGACY_WALLET_RECOVERY.md @@ -17,7 +17,7 @@ Removal of wallet code from this repository does not intentionally delete Bitcoi The legacy flow created Bitcoin Core wallets through Bitcoin Core RPC and imported descriptors into those wallets. Wallet directories already present under the Bitcoin Core data directory remain Bitcoin Core wallet data; they do not require `bails-wallet` merely to exist. -Use the Bitcoin Core version appropriate for the wallet data, keep an untouched backup, and use Bitcoin Core's documented wallet loading and migration procedures. Do not automatically copy private keys into Sparrow or another coordinator as part of this removal. +Use the Bitcoin Core version appropriate for the wallet data, keep an untouched backup, and use Bitcoin Core's documented wallet loading and migration procedures. Do not automatically copy private keys into another coordinator as part of this removal. ## Codex32 backups