#!/usr/bin/env bash

## See the file COPYING for copying conditions.

# This is an example showing how gpg-bash can be used to validate the signatures of the Bitcoin Core source code.
# This script assumes that you have already downloaded trusted public keys and saved them to $bitcoind_keys_folder
# getting trustworthy gpg keys are thus outside the scope of this script (for testing purposes some keys are provided as
# a convenience but you should find independent ways of verifying them, dont trust the keys included here blindly).
# You can specify keys that you trust (that must verify) and in addition to these specify a number of additional keys
# that must also verify. You can also include keys

# Settings
bitcoind_version='22.0'
bitcoind_folder="/usr/share/gpg-bash-lib/examples/bitcoind-${bitcoind_version}"
bitcoind_keys_folder='/usr/share/gpg-bash-lib/misc/bitcoind-pub-keys.d'
bitcoind_SHA256_file="${bitcoind_folder}/SHA256SUMS"
bitcoind_sig_file="${bitcoind_folder}/SHA256SUMS.asc"

# Key trust settings
#   The verification will fail unless all keys listed in $bitcoind_trusted_key_hashes check out
#   The script will ignore verifying keys listed in $bitcoind_untrusted_key_hashes
#   From the remaining keys (all keys - trusted keys - untrusted keys) at least $bitcoind_min_confirm number
#   of keys needs to check out
bitcoind_trusted_key_hashes=('152812300785C96444D3334D17565732E08E5E41' 'D1DBF2C4B96F2DEBF4C16654410108112E7EA81F' 'CFB16E21C950F67FA95E558F2EEB9F5CC09526C1')
bitcoind_untrusted_key_hashes=('0CCBAAFD76A2ECE2CCD3141DE2FFD5B1D88CA97D')
bitcoind_min_confirm=4


# Output deciding variables (not settings)
declare -a trusted_used=()
declare -a confirmed_used=()
trusted=0
confirmed=0
total=0


# Downloads a file if it does not already exist, exits if the download fails
# Usage:
#  download URL FOLDER FILENAME
download() {
    # Download source code (unless it already exists)
    if [ ! -f "$2"/"$3" ]; then

        # Check that the download succeeded
        if ! wget -q --https-only "$1" --directory-prefix "$2"/; then
            echo "  Could not download $1"
            exit 1
        else
            echo "  Downloaded $2/$3"
        fi
    else
        echo "  Using existing $2/$3"
    fi
}


# Checks if a value exists in array
# Usage:
#  containsElement $value "${array[@]}"
# https://stackoverflow.com/a/8574392
containsElement () {
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 0; done
    return 1
}


## Download source code
bitcoind_begin() {
    echo "${FUNCNAME[0]}: BEGIN"

    # Create folder for the bitcoind files
    mkdir --parents $bitcoind_folder
    chmod --recursive 700 $bitcoind_folder
    test -d $bitcoind_folder

    echo "Downloading source files"

    download https://bitcoincore.org/bin/bitcoin-core-${bitcoind_version}/bitcoin-${bitcoind_version}-x86_64-linux-gnu.tar.gz \
        $bitcoind_folder \
        bitcoin-${bitcoind_version}-x86_64-linux-gnu.tar.gz

    download https://bitcoincore.org/bin/bitcoin-core-${bitcoind_version}/SHA256SUMS \
        $bitcoind_folder \
        SHA256SUMS

    download https://bitcoincore.org/bin/bitcoin-core-${bitcoind_version}/SHA256SUMS.asc \
        $bitcoind_folder \
        SHA256SUMS.asc

    echo "Verifying archive checksum"
    (
    cd $bitcoind_folder || exit 1
    if ! sha256sum --status --ignore-missing --check SHA256SUMS; then
        echo "  File integrity check failed" >&2
        exit 1
    else
        echo "  Checksum OK"
    fi
    )

    source "/usr/libexec/gpg-bash-lib/source_all"

    echo "${FUNCNAME[0]}: END"
}


## Split the .asc file into the individual signatures and verify each
bitcoind_verification() {
    echo "${FUNCNAME[0]}: BEGIN"

    sig=""
    started=true
    i=0
    while read -r line; do
        if [ "$line" = "-----BEGIN PGP SIGNATURE-----" ]; then
            started=true
        elif [ "$line" = "-----END PGP SIGNATURE-----" ]; then
            started=false
            # add ending
            sig="${sig}${line}
"
            verify_signature $i "$sig"
            sig=""
            i=$((i+1))
        fi
        if [ "$started" = true ]; then
            sig="${sig}${line}
"
        fi
    done < $bitcoind_sig_file

    echo "${FUNCNAME[0]}: END"
}


# shellcheck disable=SC2034
verify_signature() {
    echo "${FUNCNAME[0]}: BEGIN"

    # Save the output in a temporary signature file containing only one signature
    tmp_signature_file=$(mktemp /tmp/verify_bitcoind.XXXXXX)
    echo "$2" > "$tmp_signature_file"

    ## Set up gpg-bash
    gpg_bash_lib_input_key_import_dir=$bitcoind_keys_folder
    gpg_bash_lib_input_file_name_enforce="false"
    gpg_bash_lib_input_cleanup="true"
    gpg_bash_lib_input_data_file=$bitcoind_SHA256_file    # We are actually not verifying the archive but the only the SHA256SUMS file here
    gpg_bash_lib_input_sig_file=$tmp_signature_file

    ## Run gpg-bash to let it do the verification for you.
    gpg_bash_lib_function_main_verify

    rm "$tmp_signature_file"

    bitcoind_check_output

    echo "${FUNCNAME[0]}: END"
}


bitcoind_check_output() {
    echo "${FUNCNAME[0]}: BEGIN"

    # Check if it is among our untrusted keys, if it is we just move on and dont count it either towards a failure or success:
    # shellcheck disable=SC2154
    if containsElement "$gpg_bash_lib_output_fingerprint_in_hex" "${bitcoind_untrusted_key_hashes[@]}"; then
        echo "Ignoring untrusted key."
    else

        # Check if the signature was validated
        if [ "$gpg_bash_lib_output_validsig_status" = "true" ]; then
            echo "Signature by key $gpg_bash_lib_output_fingerprint_in_hex was validated OK"

            # Check if it is one of our trusted keys:
            if containsElement "$gpg_bash_lib_output_fingerprint_in_hex" "${bitcoind_trusted_key_hashes[@]}"; then

                # Check that one key is not used to sign multiple times
                if ! containsElement "$gpg_bash_lib_output_fingerprint_in_hex" "${trusted_used[@]}"; then
                    echo "One of the trusted keys was verified."
                    trusted_used+=("$gpg_bash_lib_output_fingerprint_in_hex")
                    trusted=$((trusted+1))
                else
                    echo "Verification failed: One of the trusted keys was used to sign multiple times."
                    exit 1
                fi
            else

                # Check that one key is not used to sign multiple times
                if ! containsElement "$gpg_bash_lib_output_fingerprint_in_hex" "${confirmed_used[@]}"; then
                    echo "One of the normal keys was verified."
                    confirmed_used+=("$gpg_bash_lib_output_fingerprint_in_hex")
                    confirmed=$((confirmed+1))
                else
                    echo "Verification failed: One of the trusted keys was used to sign multiple times."
                    exit 1
                fi
            fi
        else
            echo "Signature by key $gpg_bash_lib_output_fingerprint_in_hex could not be validated"
        fi
    fi
    total=$((total+1))
    echo "${FUNCNAME[0]}: END"
}


bitcoind_decide() {

    # Check each of the specified trusted key hashes
    for hash in "${bitcoind_trusted_key_hashes[@]}"
    do
        # And make sure that it exists in the array containing the verified hashes
        if ! containsElement "$hash" "${trusted_used[@]}"; then
            echo "Verification failed: One of the trusted signers keys ($hash) was not among the verified keys"
            exit 1
        fi
    done

    if [ "$confirmed" -ge "$bitcoind_min_confirm" ]; then

        not_verified=$((total-confirmed-trusted))

        # Loop through the trusted keys and verify that each of them were actually used in the verification
        echo "Verification successful"
        echo "  $((confirmed+trusted))/$total signatures validated ($not_verified signatures could not be verified)"
        echo "  Found valid signatures from all $trusted trusted keys"
        echo "  In addition $confirmed valid signatures was found (minimum $bitcoind_min_confirm required)"
        exit 0
    else
        echo "Verification failed: Could only confirm $confirmed signatures ($bitcoind_min_confirm required)"
        exit 1
    fi
}

# Run the script
bitcoind_begin
bitcoind_verification
bitcoind_decide
