Electrum Verifier

10 minutos de leitura

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)