Electrum Verifier
1. Introdução
Sempre que se recomenda que a assinatura digital e os hashes de um arquivo sejam verificados, o usuário comum esbarra em algumas dificuldades para compreender o processo e executar os comandos necessários. Algumas vezes por falta de conhecimento técnico, outras porque o processo pode variar de acordo com a forma como a assinatura foi gerada ou com a criptografia usada.
O script Electrum Verifier faz apenas isso: baixa o pacote e a assinatura digital da carteira Electrum e verifica se a assinatura é válida. É um script (bash ou python) que não precisa de privilégios de root e roda no macOS e no Linux (a versão python deve rodar também no Windows). A diferença entre esse script e o Decred Verifier é que o desenvolvedor da Electrum assinou diretamente o arquivo da carteira. No caso dos arquivos do Decred foi gerado um hash SHA-256 de cada arquivo, esses hashes foram inseridos em um arquivo de texto chamado manifest, e o manifest foi digitalmente assinado. Assim, lá é necessário verificar a assinatura do manifest e comparar os hashes enquanto aqui só é necessário verificar a assinatura digital.
Para baixar a chave pública do principal desenvolvedor da Electrum, Thomas Voegtlin, será iniciada uma conexão TCP 11371 (hkp) para falar com o servidor de chaves do GNUPG e para baixar os pacotes será necessário falar TCP 443 (https) com o site do Electrum. Há também outro modo de operação onde a chave pública será baixada diretamente do repositório do Github através de TCP 443.
Saiba mais sobre a verificação de assinaturas digitais ou sobre os procedimentos para verificação da assinatura digital da Electrum.
2. Bash Script
Para executar o script, copie o código de ~160 linhas para um arquivo local e adicione o atributo de execução como mostrado a seguir:
$ chmod +x electrum_verifier.sh
Electrum Verifier:
#! /bin/bash -
# Electrum Verifier v0.2.1
# This script downloads and verifies the digital signature and hash of Electrum Bitcoin wallet.
# Electrum was created by Thomas Voegtlin and is available at electrum.org
# Reference: https://electrum.org/#download
# Author: Marcelo Martins (stakey.club)
# Syntax: electrum_verifier.sh [version number] [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be: (public key download)
# 0 = HKP, via TCP 11371 (default)
# 1 = HTTPS, via TCP 443
# Examples: ./electrum_verifier.sh
# ./electrum_verifier.sh 3.3.4
# ./electrum_verifier.sh 3.3.4 1
print_help () {
echo "Version defaults to the latest auto-detected version if left blank"
echo "Mode of operation must be: (public key download)"
echo "0 = HKP, via TCP 11371 (default)"
echo "1 = HTTPS, via TCP 443"
echo "Syntax: $0 [version] [mode]"
echo "Examples: $0"
echo " $0 3.3.4"
echo " $0 3.3.4 1"
exit 1
}
if [[ `uname -a | grep -c "Darwin"` -gt 0 ]]; then
FLAVOR="Mac"
elif [[ `uname -a | grep -c "Linux"` -gt 0 ]]; then
FLAVOR="Linux"
else
FLAVOR="Unknown"
fi
if [[ -z $1 ]]; then
if [[ $FLAVOR == "Mac" ]]; then
VERSION=`curl -L -Ssf https://download.electrum.org/ | grep -o '[0-9]\{1,2\}\.[0-9]\{1,2\}\.[0-9]\{0,2\}' | tail -n 1`
else
VERSION=`wget -S -nv -q -O - https://download.electrum.org/ | grep -o '[0-9]\{1,2\}\.[0-9]\{1,2\}\.[0-9]\{0,2\}' | tail -n 1`
fi
if [[ -z $VERSION ]]; then
echo -e "ERROR: Package version must be provided.\n"
print_help
fi
elif [[ ! $1 =~ ^[0-9]\.[0-9]{1,2}\.?[0-9]{0,2} ]]; then
echo -e "ERROR: Package version seems to be in the wrong format.\n"
print_help
fi
if [[ -z $2 ]]; then
MODE=0
elif [[ $2 =~ ^[0-1]$ ]]; then
MODE=$2
else
echo -e "ERROR: Mode of operation is invalid.\n"
print_help
fi
ELECTRUM_DOWNLOAD="https://download.electrum.org/"
ELECTRUM_DMG="electrum-$VERSION.dmg"
ELECTRUM_DMG_ASC="electrum-$VERSION.dmg.asc"
ELECTRUM_TAR="Electrum-$VERSION.tar.gz"
ELECTRUM_TAR_ASC="Electrum-$VERSION.tar.gz.asc"
THOMASV_PUBKEY_GITHUB="https://raw.githubusercontent.com/spesmilo/electrum/master/pubkeys/ThomasV.asc"
THOMASV_ASC="ThomasV.asc"
THOMASV_PUBKEY_ID="0x2BD5824B7F9470E6" # Same format as fingerprint below
THOMASV_PUBKEY_ID_FINGERPRINT="2BD5824B7F9470E6" #last part, from youtube: https://www.youtube.com/watch?v=hjYCXOyDy7Y
THOMASV_PUBKEY_GNUPG="http://keys.gnupg.net/pks/lookup?op=get&search=$THOMASV_PUBKEY_ID&options=mr" # Not used
GNUPG_KEYHOST="keys.gnupg.net"
[[ ! -x `which gpg` ]] && echo "ERROR: Could not locate gpg." && exit 5
if [[ $FLAVOR="Mac" ]]; then
[[ ! -x `which curl` ]] && echo "ERROR: Could not locate curl." && exit 6
ELECTRUM_PKG_ASC=$ELECTRUM_DMG_ASC
ELECTRUM_PKG=$ELECTRUM_DMG
else # FLAVOR="Linux"
[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
ELECTRUM_PKG_ASC=$ELECTRUM_TAR_ASC
ELECTRUM_PKG=$ELECTRUM_TAR
fi
if [[ `gpg --with-colons -k $THOMASV_PUBKEY_ID | grep -c "electrum.org"` -gt 0 ]]; then
echo "Thomas Voegtlin's public key $THOMASV_PUBKEY_ID has already been imported."
else
if [[ $MODE -eq 0 ]]; then
# Uses keys.gnupg.org to import the key (uses HKP, depends on TCP 11371). This is the default.
echo "Importing Thomas Voegtlin's public key $THOMASV_PUBKEY_ID from $GNUPG_KEYHOST"
GPG_OUT=$(gpg --with-colons --status-fd 1 --keyserver $GNUPG_KEYHOST --recv-keys $THOMASV_PUBKEY_ID 2> /dev/null)
if [[ `echo $GPG_OUT | grep -c FAILURE` -eq 1 ]]; then
echo "[ FAIL ] GPG failed to import key $THOMASV_PUBKEY_ID from $GNUPG_KEYHOST. It may have timed out."
echo "Make sure you can send traffic using port TCP 11371,"
echo "or alternate to 'mode 1' of operation running:"
echo "$0 $VERSION 1"
exit 2
fi
else
# Downloads the key from githubusercontent.com (uses https, TCP 443)
echo "Downloading and importing Thomas Voegtlin's public key $THOMASV_PUBKEY_ID from Github."
if [[ $FLAVOR == "Mac" ]]; then
[[ ! -f $THOMASV_ASC ]] && curl -L -# $THOMASV_PUBKEY_GITHUB -o $THOMASV_ASC
else
[[ ! -f $THOMASV_ASC ]] && wget $THOMASV_PUBKEY_GITHUB
fi
GPG_OUT=$(gpg --with-colons --status-fd 1 --import $THOMASV_ASC 2> /dev/null)
if [[ `echo $GPG_OUT | grep -c $THOMASV_PUBKEY_ID_FINGERPRINT` -lt 1 ]]; then
echo "[ FAIL ] GPG failed to import key $THOMASV_PUBKEY_ID from $THOMASV_ASC."
echo "Try 'mode 0' of operation by running:"
echo "$0 $VERSION"
echo "Or try to reach the author at https://electrum.org/#community"
exit 2
fi
fi
fi
if [[ $FLAVOR == "Mac" ]]; then
if [[ `curl -Is $ELECTRUM_DOWNLOAD/$VERSION/ | head -n 1 | grep -c "404"` -gt 0 ]]; then
echo "ERROR: could not find folder $ELECTRUM_DOWNLOAD/$VERSION/."
exit 8
fi
else
if [[ `wget -S -nv -q -O - $ELECTRUM_DOWNLOAD/$VERSION/ 2>&1 | head -n 1 | grep -c "404"` -gt 0 ]]; then
echo "ERROR: could not find folder $ELECTRUM_DOWNLOAD/$VERSION/."
exit 8
fi
fi
if [[ ! -f $ELECTRUM_PKG_ASC || ! -f $ELECTRUM_PKG ]]; then
echo "Downloading files from Github..."
if [[ $FLAVOR == "Mac" ]]; then
[[ ! -f $ELECTRUM_PKG_ASC ]] && curl -L -# $ELECTRUM_DOWNLOAD/$VERSION/$ELECTRUM_PKG_ASC -o $ELECTRUM_PKG_ASC
[[ ! -f $ELECTRUM_PKG ]] && curl -L -# $ELECTRUM_DOWNLOAD/$VERSION/$ELECTRUM_PKG -o $ELECTRUM_PKG
else
[[ ! -f $ELECTRUM_PKG_ASC ]] && wget -nv -q --show-progress $ELECTRUM_DOWNLOAD/$VERSION/$ELECTRUM_PKG_ASC
[[ ! -f $ELECTRUM_PKG ]] && wget -nv -q --show-progress $ELECTRUM_DOWNLOAD/$VERSION/$ELECTRUM_PKG
fi
else
echo "All files have already been copied locally."
fi
echo "- $ELECTRUM_PKG_ASC"
echo "- $ELECTRUM_PKG"
[[ -f $THOMASV_ASC ]] && echo "- $THOMASV_ASC"
echo -e "\nVerifying digital signature...\n"
GPG_OUT2=$(gpg --with-colons --status-fd 1 --verify $ELECTRUM_PKG_ASC 2> /dev/null)
if [[ `echo $GPG_OUT2 | grep -c GOOD` -eq 1 ]]; then
echo -e "[ SUCCESS ] GPG signature for $ELECTRUM_PKG successfully verified.\n"
# The script won't delete files.
if [[ -f $THOMASV_ASC ]]; then
echo -e "It's now safe to delete files $ELECTRUM_PKG_ASC and $THOMASV_ASC\n"
else
echo -e "It's now safe to delete file $ELECTRUM_PKG_ASC\n"
fi
echo "RESULT: SUCCESS. It means that $ELECTRUM_PKG wasn't modified after its creation"
echo "because it was signed by Thomas Voegtlin's private key (main developer)."
echo "Learn more at https://stakey.club/en/verifying-digital-signatures/\n"
# The script won't install or run the wallet.
if [[ $FLAVOR == "Mac" ]]; then
echo "To install, open $ELECTRUM_PKG and move Electrum.app to the Applications folder."
else
echo "To install:"
echo "1. unpack $ELECTRUM_PKG with 'tar -xvf $ELECTRUM_PKG'"
echo "2. run 'python3 Electrum-$VERSION/run_electrum'"
fi
else
echo -e "[ FAIL ] GPG did not find a \"good signature\" for $ELECTRUM_PKG. Verification FAILED!"
exit 4
fi
3. Python Script
Para executar o script no Python será necessário instalar também os pacotes abaixo. No Windows, será necessário instalar o Python e o gpg4win:
$ sudo pip install python-gnupg
$ sudo pip install certifi
$ python electrum_verifier.sh
Electrum Verifier:
#!/usr/bin/env python3
import argparse
from pathlib import Path
import re
import platform
import urllib.request
import ssl
import gnupg
import shutil
import certifi
# Electrum Verifier v0.2
# This script downloads and verifies the digital signature and hash of Electrum Bitcoin wallet.
# Electrum was created by Thomas Voegtlin and is available at electrum.org
# Reference: https://electrum.org/#download
# Author: Marcelo Martins (stakey.club)
# Syntax: python electrum_verifier.py -v [version number] -m [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be: (public key download)
# 0 = HKP, via TCP 11371 (default)
# 1 = HTTPS, via TCP 443
# Examples: $ python electrum_verifier.py -v 3.3.4
# $ python electrum_verifier.py -v 3.3.4 -m 1
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--version", help="Version to download", dest='version')
parser.add_argument("-m", "--mode", help="Mode of operation", dest='mode')
args = parser.parse_args()
__version__ = '0.2'
def print_help():
print("Version defaults to the latest auto-detected version if left blank")
print("Mode of operation must be: (public key download)")
print("0 = HKP, via TCP 11371 (default)")
print("1 = HTTPS, via TCP 443")
print("Syntax: $0 [version] [mode]")
print("Examples: $0")
print(" $0 -v 3.3.4")
print(" $0 -v 3.3.4 -m 1")
exit(1)
client_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
client_context.options |= ssl.OP_NO_TLSv1
client_context.options |= ssl.OP_NO_TLSv1_1
client_context.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH)
client_context.load_verify_locations(capath=certifi.where())
if not args.version:
VERSION = re.findall('[0-9]{1,2}\.[0-9]{1,2}\.?[0-9]{0,2}',
str(urllib.request.urlopen("https://download.electrum.org/",
context=client_context).read(90000)))[-1]
if not VERSION:
print("ERROR: Package version must be provided.\n")
print_help()
elif re.search('^[0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2}', args.version):
VERSION = args.version
else:
VERSION = ""
print("ERROR: Package version seems to be in the wrong format.\n")
print_help()
if not args.mode:
MODE = 0
elif re.search('^[0-1]$', args.mode):
MODE = args.mode
else:
MODE = 0
print("ERROR: Mode of operation is invalid.\n")
print_help()
ELECTRUM_DOWNLOAD = "https://download.electrum.org/"
ELECTRUM_DMG = "electrum-" + VERSION + ".dmg"
ELECTRUM_DMG_ASC = "electrum-" + VERSION + ".dmg.asc"
ELECTRUM_TAR = "Electrum-" + VERSION + ".tar.gz"
ELECTRUM_TAR_ASC = "Electrum-" + VERSION + ".tar.gz.asc"
ELECTRUM_EXE = "electrum-" + VERSION + ".exe"
ELECTRUM_EXE_ASC = "electrum-" + VERSION + ".exe.asc"
THOMASV_PUBKEY_GITHUB = "https://raw.githubusercontent.com/spesmilo/electrum/master/pubkeys/ThomasV.asc"
THOMASV_ASC = "ThomasV.asc"
THOMASV_PUBKEY_ID = "0x2BD5824B7F9470E6" # Same format as fingerprint below
THOMASV_PUBKEY_ID_FINGERPRINT = "2BD5824B7F9470E6" # last part, youtube: https://www.youtube.com/watch?v=hjYCXOyDy7Y
THOMASV_PUBKEY_GNUPG = "http://keys.gnupg.net/pks/lookup?op=get&search=" + THOMASV_PUBKEY_ID + "&options=mr" # Not used
GNUPG_KEYHOST = "keys.gnupg.net"
ELECTRUM_PKG_ASC, ELECTRUM_PKG = "", ""
if platform.system() == 'Darwin':
ELECTRUM_PKG_ASC = ELECTRUM_DMG_ASC
ELECTRUM_PKG = ELECTRUM_DMG
elif platform.system() == 'Linux':
ELECTRUM_PKG_ASC = ELECTRUM_TAR_ASC
ELECTRUM_PKG = ELECTRUM_TAR
elif platform.system() == 'Windows':
ELECTRUM_PKG_ASC = ELECTRUM_EXE_ASC
ELECTRUM_PKG = ELECTRUM_EXE
else:
print("ERROR: Unknown platform.")
exit(9)
gpg = gnupg.GPG()
thomasv_key = gpg.list_keys(keys=THOMASV_PUBKEY_ID_FINGERPRINT)
if thomasv_key:
print("Thomas Voegtlin's public key", THOMASV_PUBKEY_ID, "has already been imported.")
else:
if int(MODE) == 0:
# Uses keys.gnupg.org to import the key (uses HKP, depends on TCP 11371). This is the default.
print("Importing Thomas Voegtlin's public key", THOMASV_PUBKEY_ID, "from", GNUPG_KEYHOST)
gpg_out = gpg.recv_keys(GNUPG_KEYHOST, THOMASV_PUBKEY_ID_FINGERPRINT)
if not gpg_out.results[0]['ok']:
print("[ FAIL ] GPG failed to import key", THOMASV_PUBKEY_ID,
"from", GNUPG_KEYHOST + ". It may have timed out.")
print("Make sure you can send traffic using port TCP 11371,")
print("or alternate to 'mode 1' of operation running:")
print("python electrum_verifier.py -v", VERSION, "-m 1")
exit(2)
else:
# Downloads the key from githubusercontent.com (uses https, TCP 443)
print("Downloading and importing Thomas Voegtlin's public key", THOMASV_PUBKEY_ID, "from Github.")
print("-", THOMASV_ASC)
with urllib.request.urlopen(THOMASV_PUBKEY_GITHUB, context=client_context) \
as response, open(THOMASV_PUBKEY_GITHUB, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
gpg_out = gpg.import_keys(THOMASV_ASC)
if not gpg_out.results[0]['ok']:
print("[ FAIL ] GPG failed to import key", THOMASV_PUBKEY_ID, "from", THOMASV_ASC + ".")
print("Try 'mode 0' of operation by running:")
print("$0 $VERSION")
print("Or try to reach the author at https://electrum.org/#community")
exit(2)
response = urllib.request.urlopen(ELECTRUM_DOWNLOAD + "/" + VERSION, context=client_context)
if response.code == 404:
print("ERROR: could not find folder", ELECTRUM_DOWNLOAD + "/" + VERSION, "on Github.")
exit(8)
if not Path(ELECTRUM_PKG_ASC).is_file() and not Path(ELECTRUM_PKG).is_file():
print("Downloading files from Github...")
if not Path(ELECTRUM_PKG_ASC).is_file():
print("-", ELECTRUM_PKG_ASC)
with urllib.request.urlopen(ELECTRUM_DOWNLOAD + "/" + VERSION + "/" + ELECTRUM_PKG_ASC,
context=client_context) as response, open(ELECTRUM_PKG_ASC, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
if not Path(ELECTRUM_PKG).is_file():
print("-", ELECTRUM_PKG)
with urllib.request.urlopen(ELECTRUM_DOWNLOAD + "/" + VERSION + "/" + ELECTRUM_PKG, context=client_context) \
as response, open(ELECTRUM_PKG, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
else:
print("All files have already been copied locally.")
print("-", ELECTRUM_PKG_ASC)
print("-", ELECTRUM_PKG)
Path(THOMASV_ASC).is_file() and print("-", THOMASV_ASC)
print("\nVerifying digital signature...\n")
v = open(ELECTRUM_PKG_ASC, "rb")
verify_result = gpg.verify_file(v, ELECTRUM_PKG)
v.close()
if verify_result.status == "signature valid":
print("[ SUCCESS ] GPG signature for", ELECTRUM_PKG, "successfully verified.\n")
# The script won't delete files.
if Path(THOMASV_ASC).is_file():
print("It's now safe to delete files", ELECTRUM_PKG_ASC, "and", THOMASV_ASC, "\n")
else:
print("It's now safe to delete file", ELECTRUM_PKG_ASC, "\n")
print("RESULT: SUCCESS. It means that", ELECTRUM_PKG, "wasn't modified after its creation")
print("because it was signed by Thomas Voegtlin's private key (main developer).")
print("Learn more at https://stakey.club/en/verifying-digital-signatures/\n")
# The script won't install or run the wallet.
if platform.system() == 'Darwin' and MODE == 0:
print("To install, open", ELECTRUM_PKG, "and move Electrum.app to the Applications folder.")
elif platform.system() == 'Windows' and MODE == 0:
print("To install, run", ELECTRUM_PKG)
else:
print("To install:")
print("1. unpack", ELECTRUM_PKG, "with 'tar -xvf", ELECTRUM_PKG + "'")
print("2. run 'python3 Electrum-" + VERSION + "/run_electrum'")
else:
print("[ FAIL ] GPG did not find a \"good signature\" for", ELECTRUM_PKG, ". Verification FAILED!")
exit(4)