Decred Verifier
1. Introduction
Whenever it is recommended that the digital signature and hashes of a file be verified to avoid files being modified in transit or at rest by trojans or any other malicious code, the average user encounters some difficulties in understanding the process and executing the necessary commands. Sometimes because the user lacks technical knowledge, or because the process may vary depending on how the signature was generated or with the cryptographic method chosen.
The Decred Verifier script does just that: it downloads Decred packages, its digital signatures and hashes from the official Github repo and verifies that the signature and hashes are valid. It is a script (bash or python) that does not need root privileges and runs on macOS and Linux (python version is supposed to run on Windows).
To download the Decred developers’ key the script will initiate a TCP 11371 (hkp) connection to the MIT key server, and to download the packages from Github it will be necessary to ‘talk’ TCP 443 (https).
Learn more about the verification of digital signatures.
2. Bash Script
To run the script, paste the ~180 lines of code below to a local file and add the executable attribute as shown here:
$ chmod +x decred_verifier.sh
Decred Verifier:
#! /bin/bash -
# Decred Verifier v0.2.1
# This script downloads and verifies the digital signature and hash of Decred packages.
# Decrediton and the Decred installer are developed by Decred developers and are available at decred.org
# Reference: https://github.com/decred/decred-binaries/
# Author: Marcelo Martins (stakey.club)
# Syntax: decred_verifier.sh [version number] [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be:
# 0 = Decrediton (default)
# 1 = Decred installer
# Examples: ./decred_verifier.sh
# ./decred_verifier.sh 1.4.0
# ./decred_verifier.sh 1.4.0 1
print_help () {
echo "Version defaults to the latest auto-detected version if left blank"
echo "Mode of operation must be:"
echo "0 = Decrediton (default)"
echo "1 = Decred installer"
echo "Syntax: $0 [version] [mode]"
echo "Examples: $0"
echo " $0 1.4.0"
echo " $0 1.4.0 0"
exit 1
}
if [[ `uname -a | grep -c "Darwin"` -gt 0 ]]; then
FLAVOR="Mac"
elif [[ `uname -a | grep -c "Linux"` -gt 0 ]]; then
FLAVOR="Linux"
elif [[ `uname -m | grep -c "arm"` -gt 0 ]]; then
FLAVOR="ARM"
else
FLAVOR="Unknown"
fi
if [[ -z $1 ]]; then
if [[ $FLAVOR == "Mac" ]]; then
VERSION=`curl -Ssf -L https://github.com/decred/decred-binaries/tags | grep -o 'v[0-9]\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -n 1 | cut -d'v' -f 2`
else
VERSION=`wget -S -nv -q -O - https://github.com/decred/decred-binaries/tags | grep -o 'v[0-9]\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -n 1 | cut -d'v' -f 2`
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
VERSION=$1
else
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
DCR_KEYHOST="pgp.mit.edu"
DCR_KEY="0x518A031D"
DCR_GITHUB="https://github.com/decred/decred-binaries/releases/download"
DCR_GITHUB_TAG="https://github.com/decred/decred-binaries/releases/tag"
MANIFEST_DECREDITON="manifest-decrediton-v$VERSION.txt"
MANIFEST_ASC_DECREDITON="manifest-decrediton-v$VERSION.txt.asc"
DECREDITON_DMG="decrediton-v$VERSION.dmg"
DECREDITON_TAR="decrediton-v$VERSION.tar.gz"
MANIFEST_DECRED="manifest-v$VERSION.txt"
MANIFEST_ASC_DECRED="manifest-v$VERSION.txt.asc"
DECRED_DARWIN="decred-darwin-amd64-v$VERSION.tar.gz"
DECRED_PKG_AMD64="decred-linux-amd64-v$VERSION.tar.gz"
DECRED_PKG_386="decred-linux-386-v$VERSION.tar.gz"
DECRED_PKG_ARM="decred-linux-arm-v$VERSION.tar.gz"
DECRED_PKG_ARM64="decred-linux-arm64-v$VERSION.tar.gz"
[[ ! -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
if [[ $MODE -eq 0 ]]; then
MANIFEST=$MANIFEST_DECREDITON
MANIFEST_ASC=$MANIFEST_ASC_DECREDITON
DECRED_PKG=$DECREDITON_DMG
else
MANIFEST=$MANIFEST_DECRED
MANIFEST_ASC=$MANIFEST_ASC_DECRED
DECRED_PKG=$DECRED_DARWIN
fi
elif [[ $FLAVOR == "Linux" ]]; then
[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
if [[ $MODE -eq 0 ]]; then
MANIFEST=$MANIFEST_DECREDITON
MANIFEST_ASC=$MANIFEST_ASC_DECREDITON
DECRED_PKG=$DECREDITON_TAR
else
MANIFEST=$MANIFEST_DECRED
MANIFEST_ASC=$MANIFEST_ASC_DECRED
if [[ `uname -a | grep -c "386"` -eq 0 ]]; then
DECRED_PKG=$DECRED_PKG_AMD64
else
DECRED_PKG=$DECRED_PKG_386
fi
fi
else
[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
if [[ $MODE -eq 0 ]]; then
echo "ERROR: There is no Decrediton for ARM platform."
exit 8
else
MANIFEST=$MANIFEST_DECRED
MANIFEST_ASC=$MANIFEST_ASC_DECRED
if [[ `uname -a | grep -c "arm64"` -gt 0 ]]; then
DECRED_PKG=$DECRED_PKG_ARM64
else
DECRED_PKG=$DECRED_PKG_ARM
fi
fi
fi
if [[ `gpg --with-colons -k $DCR_KEY | grep -c "Decred"` -eq 1 ]]; then
echo "Decred developers' public key $DCR_KEY has already been imported."
else
echo "Importing Decred developers' key $DCR_KEY from $DCR_KEYHOST"
GPG_OUT=$(gpg --with-colons --status-fd 1 --keyserver $DCR_KEYHOST --recv-keys $DCR_KEY 2> /dev/null)
if [[ `echo $GPG_OUT | grep -c FAILURE` -eq 1 ]]; then
echo "[ FAIL ] GPG failed to import key $DCR_KEY from $DCR_KEYHOST. It may have timed out."
echo "Make sure you can send traffic using port TCP 11371."
exit 2
fi
fi
if [[ $FLAVOR == "Mac" ]]; then
if [[ `curl -Is $DCR_GITHUB_TAG/v$VERSION | head -n 1 | grep -c "404"` -gt 0 ]]; then
echo "ERROR: could not find release version $VERSION on Github."
exit 8
fi
else
if [[ `wget -S -nv -q -O - $DCR_GITHUB_TAG/v$VERSION 2>&1 | head -n 1 | grep -c "404"` -gt 0 ]]; then
echo "ERROR: could not find release version $VERSION on Github."
exit 8
fi
fi
if [[ ! -f $MANIFEST || ! -f $MANIFEST_ASC || ! -f $DECRED_PKG ]]; then
echo "Downloading files from Github..."
if [[ $FLAVOR == "Mac" ]]; then
[[ ! -f $MANIFEST ]] && echo "- $MANIFEST" && curl -L -# $DCR_GITHUB/v$VERSION/$MANIFEST -o $MANIFEST
[[ ! -f $MANIFEST_ASC ]] && echo "- $MANIFEST_ASC" && curl -L -# $DCR_GITHUB/v$VERSION/$MANIFEST_ASC -o $MANIFEST_ASC
[[ ! -f $DECRED_PKG ]] && echo "- $DECRED_PKG" && curl -L -# $DCR_GITHUB/v$VERSION/$DECRED_PKG -o $DECRED_PKG
else
[[ ! -f $MANIFEST ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$MANIFEST
[[ ! -f $MANIFEST_ASC ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$MANIFEST_ASC
[[ ! -f $DECRED_PKG ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$DECRED_PKG
fi
else
echo "All files have already been copied locally."
echo "- $MANIFEST"
echo "- $MANIFEST_ASC"
echo "- $DECRED_PKG"
fi
echo -e "\nVerifying digital signature and hash...\n"
GPG_OUT2=$(gpg --with-colons --status-fd 1 --verify $MANIFEST_ASC 2> /dev/null)
if [[ `echo $GPG_OUT2 | grep -c GOOD` -eq 1 ]]; then
if [[ $FLAVOR == "Mac" ]]; then
[[ ! -x `which shasum` ]] && echo "ERROR: Could not locate shasum." && exit 9
SHASUM=`shasum -a 256 $DECRED_PKG | cut -d' ' -f1`
else # Linux or ARM
[[ ! -x `which sha256sum` ]] && echo "ERROR: Could not locate sha256sum." && exit 9
SHASUM=`sha256sum $DECRED_PKG | cut -d' ' -f1`
fi
if [[ `grep -c $SHASUM $MANIFEST` -eq 1 ]]; then
echo "[ SUCCESS ] GPG signature for $MANIFEST successfully verified."
echo -e "[ SUCCESS ] SHA-256 hash of $DECRED_PKG successfully verified.\n"
echo -e "It's now safe to delete files $MANIFEST and $MANIFEST_ASC\n" # The script won't delete files.
echo "RESULT: SUCCESS. It means that $DECRED_PKG wasn't modified after its creation"
echo "and that the file that contains the proof was signed by Decred developers' private key."
echo -e "Learn more at https://stakey.club/en/verifying-digital-signatures/\n"
# The script won't install or run the wallet.
if [[ $FLAVOR == "Mac" && $MODE -eq 0 ]]; then
echo "To install, open $DECRED_PKG and move Decrediton.app to the Applications folder."
else
echo "To install, unpack $DECRED_PKG, enter the directory and run the executable."
fi
else
echo -e "[ SUCCESS ] GPG signature for $DECRED_PKG successfully verified."
echo -e "[ FAIL ] SHA-256 hash of $DECRED_PKG verification FAILED!\n"
echo "RESULT: FAIL. It means that the manifest wasn't modified after its creation"
echo "but $DECRED_PKG is incomplete or was probably modified after its creation."
exit 3
fi
else
echo -e "[ FAIL ] GPG did not find a \"good signature\" for $MANIFEST. Verification FAILED!"
exit 4
fi
3. Python Script
To execute this Python script, the following packages must be installed on the computer. On Windows, Python and gpg4win must be installed:
$ sudo pip install python-gnupg
$ sudo pip install certifi
$ python decred_verifier.sh
Decred Verifier:
#!/usr/bin/env python3
import argparse
from pathlib import Path
import re
import platform
import urllib.request
import ssl
import hashlib
import gnupg
import shutil
import certifi
import os
# Decred Verifier v0.2
# This script downloads and verifies the digital signature and hash of Decred packages.
# Decrediton and the Decred installer are developed by Decred developers and are available at decred.org
# Reference: https://github.com/decred/decred-binaries/
# Author: Marcelo Martins (stakey.club)
# Syntax: python decred_verifier.py -v [version number] -m [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be:
# 0 = Decrediton (default)
# 1 = Decred installer
# Examples: $ python decred_verifier.py
# $ python decred_verifier.py -v 1.4.0
# $ python decred_verifier.py -v 1.4.0 -m 0
# $ python decred_verifier.py -v 1.4.0 -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:")
print("0 = Decrediton (default)")
print("1 = Decred installer")
print("Syntax: $0 -v [version] -m [mode]")
print("Example: $0")
print(" $0 -v 1.4.0")
print(" $0 -v 1.4.0 -m 0")
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:
reVERSION = re.search('archive/v([0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2}).',
str(urllib.request.urlopen("https://github.com/decred/decred-binaries/tags",
context=client_context).read(90000)))
VERSION = reVERSION.group(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()
HASH_BLOCKSIZE = 65536
DCR_KEYHOST = "pgp.mit.edu"
DCR_KEY = "0x518A031D"
DCR_KEY_LONG_FP = "6D897EDF518A031D"
GITHUB_HOST = "github.com"
DCR_GITHUB_DIR = "/decred/decred-binaries/releases/download"
DCR_GITHUB = "https://github.com/decred/decred-binaries/releases/download"
DCR_GITHUB_TAG = "https://github.com/decred/decred-binaries/releases/tag"
DCR_GITHUB_TAG_DIR = "/decred/decred-binaries/releases/tag"
MANIFEST_DECREDITON = "manifest-decrediton-v" + VERSION + ".txt"
MANIFEST_ASC_DECREDITON = "manifest-decrediton-v" + VERSION + ".txt.asc"
DECREDITON_DMG = "decrediton-v" + VERSION + ".dmg"
DECREDITON_TAR = "decrediton-v" + VERSION + ".tar.gz"
MANIFEST_DECRED = "manifest-v" + VERSION + ".txt"
MANIFEST_ASC_DECRED = "manifest-v" + VERSION + ".txt.asc"
DECRED_DARWIN = "decred-darwin-amd64-v" + VERSION + ".tar.gz"
DECRED_PKG_AMD64 = "decred-linux-amd64-v" + VERSION + ".tar.gz"
DECRED_PKG_386 = "decred-linux-386-v" + VERSION + ".tar.gz"
DECRED_PKG_ARM = "decred-linux-arm-v" + VERSION + ".tar.gz"
DECRED_PKG_ARM64 = "decred-linux-arm64-v" + VERSION + ".tar.gz"
DECRED_EXE_386 = "decred-windows-386-v" + VERSION + ".zip"
DECRED_EXE_AMD64 = "decred-windows-amd64-v" + VERSION + ".zip"
DECREDITON_EXE = "decrediton-v" + VERSION + ".exe"
MANIFEST, MANIFEST_ASC, DECRED_PKG = "", "", ""
if platform.system() == 'Darwin':
if int(MODE) == 0:
MANIFEST = MANIFEST_DECREDITON
MANIFEST_ASC = MANIFEST_ASC_DECREDITON
DECRED_PKG = DECREDITON_DMG
else:
MANIFEST = MANIFEST_DECRED
MANIFEST_ASC = MANIFEST_ASC_DECRED
DECRED_PKG = DECRED_DARWIN
elif platform.system() == 'Linux':
if os.uname()[4].startswith("arm"):
if int(MODE) == 0:
print("ERROR: There is no Decrediton for ARM platform.")
exit(8)
else:
MANIFEST = MANIFEST_DECRED
MANIFEST_ASC = MANIFEST_ASC_DECRED
if platform.architecture()[0] == '64bit':
DECRED_PKG = DECRED_PKG_ARM64
else:
DECRED_PKG = DECRED_PKG_ARM
else:
if int(MODE) == 0:
MANIFEST = MANIFEST_DECREDITON
MANIFEST_ASC = MANIFEST_ASC_DECREDITON
DECRED_PKG = DECREDITON_TAR
else:
MANIFEST = MANIFEST_DECRED
MANIFEST_ASC = MANIFEST_ASC_DECRED
if platform.architecture()[0] == '64bit':
DECRED_PKG = DECRED_PKG_AMD64
else:
DECRED_PKG = DECRED_PKG_386
elif platform.system() == 'Windows':
if int(MODE) == 0:
MANIFEST = MANIFEST_DECREDITON
MANIFEST_ASC = MANIFEST_ASC_DECREDITON
DECRED_PKG = DECREDITON_EXE
else:
MANIFEST = MANIFEST_DECRED
MANIFEST_ASC = MANIFEST_ASC_DECRED
if platform.architecture()[0] == '64bit':
DECRED_PKG = DECRED_EXE_AMD64
else:
DECRED_PKG = DECRED_EXE_386
else:
print("ERROR: Unknown platform.")
exit(9)
gpg = gnupg.GPG()
dcr_key = gpg.list_keys(keys=DCR_KEY_LONG_FP)
if dcr_key:
print("Decred developers' public key", DCR_KEY, "has already been imported.")
else:
print("Importing Decred developers' key", DCR_KEY, "from", DCR_KEYHOST)
gpg_out = gpg.recv_keys(DCR_KEYHOST, DCR_KEY_LONG_FP)
if not gpg_out.results[0]['ok']:
print("[ FAIL ] GPG failed to import key", DCR_KEY, "from", DCR_KEYHOST + ". It may have timed out.")
print("Make sure you can send traffic using port TCP 11371.")
exit(2)
response = urllib.request.urlopen(DCR_GITHUB_TAG + "/v" + VERSION, context=client_context)
if response.code == 404:
print("ERROR: could not find release version", VERSION, "on Github.")
exit(8)
if not Path(MANIFEST).is_file() and not Path(MANIFEST_ASC).is_file() and not Path(DECRED_PKG).is_file():
print("Downloading files from Github...")
if not Path(MANIFEST).is_file():
print("-", MANIFEST)
with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + MANIFEST, context=client_context) \
as response, open(MANIFEST, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
if not Path(MANIFEST_ASC).is_file():
print("-", MANIFEST_ASC)
with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + MANIFEST_ASC, context=client_context) \
as response, open(MANIFEST_ASC, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
if not Path(DECRED_PKG).is_file():
print("-", DECRED_PKG)
with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + DECRED_PKG, context=client_context) \
as response, open(DECRED_PKG, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
else:
print("All files have already been copied locally.")
print("- ", MANIFEST)
print("- ", MANIFEST_ASC)
print("- ", DECRED_PKG)
print("\nVerifying digital signature and hash...\n")
v = open(MANIFEST_ASC, "rb")
verify_result = gpg.verify_file(v, MANIFEST)
v.close()
if verify_result.status == "signature valid":
hasher = hashlib.sha256()
with open(DECRED_PKG, 'rb') as hf:
buf = hf.read(HASH_BLOCKSIZE)
while len(buf) > 0:
hasher.update(buf)
buf = hf.read(HASH_BLOCKSIZE)
DIGEST = hasher.hexdigest()
hash_found = False
file = open(MANIFEST, "r")
for line in file:
if re.search(DIGEST, line):
hash_found = True
file.close()
if hash_found:
print("[ SUCCESS ] GPG signature for", MANIFEST, "successfully verified.")
print("[ SUCCESS ] SHA-256 hash of", DECRED_PKG, "successfully verified.\n")
# The script won't delete files.
print("It's now safe to delete files", MANIFEST, "and", MANIFEST_ASC, "\n")
print("RESULT: SUCCESS. It means that", DECRED_PKG, "wasn't modified after its creation")
print("and that the file that contains the proof was signed by Decred developers' private key.")
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", DECRED_PKG, "and move Decrediton.app to the Applications folder.")
elif platform.system() == 'Windows' and MODE == 0:
print("To install, run", DECRED_PKG)
else:
print("To install, unpack", DECRED_PKG + ", enter the directory and run the executable.")
else:
print("[ SUCCESS ] GPG signature for", DECRED_PKG, "successfully verified.")
print("[ FAIL ] SHA-256 hash of", DECRED_PKG, "verification FAILED!\n")
print("RESULT: FAIL. It means that the manifest wasn't modified after its creation")
print("but", DECRED_PKG, "is incomplete or was probably modified after its creation.")
exit(3)
else:
print("[ FAIL ] GPG did not find a \"good signature\" for", MANIFEST + ". Verification FAILED!")
exit(4)