#!/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 ".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] # -> .ed25519.{key,pub.b64} # scripts/sign-daemon-release.sh pubkey # print the base64 public key to pin # scripts/sign-daemon-release.sh sign ... # sign existing files -> .sig # scripts/sign-daemon-release.sh release [--src DIR] [--out DIR] # # zip prebuilt-binaries/dragonxd-{linux,mac,win}/ into dragonx--{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 -> .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 " pubkey_b64 "$1" ;; sign) [ $# -ge 2 ] || die "usage: sign ..." 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 [--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 | sign ... | release [--src DIR] [--out DIR]}" ;; esac