#!/usr/bin/env bash
# Package the prebuilt dragonx full-node binaries into per-platform release archives and sign them
# for the wallet's in-app daemon updater (ed25519 over the EXACT archive bytes).
#
# The wallet verifies a detached ed25519 signature over the archive bytes against a public key
# pinned in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64). Verification is MANDATORY
# (kDaemonRequireSignature = true): an in-app update is refused unless a valid "<archive>.sig" is
# published next to the archive. The wallet also checks each archive's SHA-256 against a markdown
# checksum table in the release body, so `release` prints that table for you to paste in.
#
# Uses OpenSSL (>= 1.1.1) only — no Python/PyNaCl. OpenSSL's ed25519 is PureEdDSA (RFC 8032), the
# same primitive libsodium's crypto_sign_verify_detached checks, so the signatures are compatible.
#
# Usage:
#   scripts/sign-daemon-release.sh keygen [out-prefix]         # -> <prefix>.ed25519.{key,pub.b64}
#   scripts/sign-daemon-release.sh pubkey <secret.key>         # print the base64 public key to pin
#   scripts/sign-daemon-release.sh sign   <secret.key> <file>...  # sign existing files -> <file>.sig
#   scripts/sign-daemon-release.sh release <secret.key> <version> [--src DIR] [--out DIR]
#       # zip prebuilt-binaries/dragonxd-{linux,mac,win}/ into dragonx-<version>-{linux-amd64,macos,
#       # win64}.zip, sign each, and print the SHA-256 checksum table. Platforms with no dragonxd
#       # binary staged are skipped.
#
# Keep the secret key (.ed25519.key) OFFLINE (mode 600). Paste the base64 public key into
# kDaemonSignaturePublicKeyBase64 in src/util/daemon_updater.h.

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
die() { echo "error: $*" >&2; exit 1; }
command -v openssl >/dev/null || die "openssl not found (need >= 1.1.1 with ed25519)"

# Raw 32-byte ed25519 public key (base64) from a private key file. The DER SubjectPublicKeyInfo for
# ed25519 is a fixed 12-byte prefix + the 32-byte key, so the trailing 32 bytes are the raw key.
pubkey_b64() { openssl pkey -in "$1" -pubout -outform DER | tail -c 32 | openssl base64 -A; }

sha256_of() {
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}';
  else shasum -a 256 "$1" | awk '{print $1}'; fi
}

# Detached ed25519 signature over the raw file bytes -> <file>.sig (base64 of the 64-byte sig).
sign_file() {
  local key="$1" f="$2" raw
  raw="$(mktemp)"
  openssl pkeyutl -sign -inkey "$key" -rawin -in "$f" -out "$raw"
  openssl base64 -A -in "$raw" > "$f.sig"
  printf '\n' >> "$f.sig"
  rm -f "$raw"
}

# platform -> (staging dir under prebuilt-binaries, release token, expected daemon binary name)
plat_dir()    { case "$1" in linux) echo dragonxd-linux;; mac) echo dragonxd-mac;; win) echo dragonxd-win;; esac; }
plat_token()  { case "$1" in linux) echo linux-amd64;;   mac) echo macos;;        win) echo win64;; esac; }
plat_daemon() { case "$1" in win) echo dragonxd.exe;;    *)   echo dragonxd;; esac; }

cmd="${1:-}"; shift || true
case "$cmd" in
  keygen)
    prefix="${1:-dragonx-daemon}"
    [ -e "$prefix.ed25519.key" ] && die "$prefix.ed25519.key already exists — refusing to overwrite"
    openssl genpkey -algorithm ed25519 -out "$prefix.ed25519.key"
    chmod 600 "$prefix.ed25519.key"
    pub="$(pubkey_b64 "$prefix.ed25519.key")"
    printf '%s\n' "$pub" > "$prefix.ed25519.pub.b64"
    echo "secret key : $prefix.ed25519.key   (KEEP OFFLINE, mode 600)"
    echo "public key : $prefix.ed25519.pub.b64"
    echo
    echo "Pin this in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64):"
    echo "  $pub"
    ;;

  pubkey)
    [ $# -ge 1 ] || die "usage: pubkey <secret.key>"
    pubkey_b64 "$1"
    ;;

  sign)
    [ $# -ge 2 ] || die "usage: sign <secret.key> <file>..."
    key="$1"; shift
    [ -f "$key" ] || die "no such key: $key"
    for f in "$@"; do
      [ -f "$f" ] || die "no such file: $f"
      sign_file "$key" "$f"
      echo "signed: $f -> $f.sig"
    done
    echo "Upload each .sig as a release asset next to its archive."
    ;;

  release)
    [ $# -ge 2 ] || die "usage: release <secret.key> <version> [--src DIR] [--out DIR]"
    key="$1"; version="$2"; shift 2
    src="$PROJECT_ROOT/prebuilt-binaries"
    out="$PROJECT_ROOT/release/daemon"
    while [ $# -gt 0 ]; do
      case "$1" in
        --src) [ $# -ge 2 ] || die "--src needs a value"; src="$2"; shift 2 ;;
        --out) [ $# -ge 2 ] || die "--out needs a value"; out="$2"; shift 2 ;;
        *) die "unknown option: $1" ;;
      esac
    done
    [ -f "$key" ] || die "no such key: $key"
    [ -d "$src" ] || die "no such source dir: $src"
    command -v zip >/dev/null 2>&1 || die "zip not found (install 'zip')"
    mkdir -p "$out"

    # Sanity: warn if this key does not match the public key pinned in the wallet (the wallet would
    # then reject every signature made with it — only expected when deliberately rotating the key).
    pinned="$(grep -oE '"[A-Za-z0-9+/]{43}="' "$PROJECT_ROOT/src/util/daemon_updater.h" 2>/dev/null | head -1 | tr -d '"')"
    mine="$(pubkey_b64 "$key")"
    if [ -n "$pinned" ] && [ "$pinned" != "$mine" ]; then
      echo "WARNING: this key's public key does not match the one pinned in daemon_updater.h:" >&2
      echo "         signing key -> $mine" >&2
      echo "         pinned key  -> $pinned" >&2
      echo "         The wallet will REJECT these signatures unless you are rotating the pinned key." >&2
      echo >&2
    fi

    made=0
    table=""
    for plat in linux mac win; do
      d="$src/$(plat_dir "$plat")"
      daemon="$d/$(plat_daemon "$plat")"
      if [ ! -f "$daemon" ]; then
        echo "skip $plat: no $(plat_daemon "$plat") staged in $d" >&2
        continue
      fi
      archive="dragonx-$version-$(plat_token "$plat").zip"
      apath="$out/$archive"
      rm -f "$apath"
      # Zip the staged files at the archive root (binaries + sapling params + asmap), excluding
      # the .gitkeep placeholder. The updater flattens paths via baseName(), so a flat zip is fine.
      files=()
      while IFS= read -r fn; do files+=("$fn"); done < <(cd "$d" && ls -A | grep -vx '.gitkeep')
      [ "${#files[@]}" -gt 0 ] || { echo "skip $plat: nothing to package in $d" >&2; continue; }
      ( cd "$d" && zip -q -X "$apath" "${files[@]}" )
      sign_file "$key" "$apath"
      sum="$(sha256_of "$apath")"
      table+="| $archive | \`$sum\` |"$'\n'
      echo "packaged + signed: $apath (+ .sig)  sha256=$sum"
      made=$((made + 1))
    done
    [ "$made" -gt 0 ] || die "no platform had a staged daemon binary under $src/dragonxd-{linux,mac,win}/"

    echo
    echo "Checksum table (paste into the release body so the wallet can verify SHA-256):"
    echo "| Archive | SHA-256 |"
    echo "|---|---|"
    printf '%s' "$table"
    echo
    echo "Upload each .zip AND its .zip.sig as release assets. Wallet enforces the ed25519 signature"
    echo "(kDaemonRequireSignature=true) and the SHA-256 from the table above."
    ;;

  *)
    die "usage: $0 {keygen [prefix] | pubkey <secret.key> | sign <secret.key> <file>... | release <secret.key> <version> [--src DIR] [--out DIR]}"
    ;;
esac
