git / moonbite-network / scripts/release/Verify-Release.ps1 · raw
<#.SYNOPSIS Verify a downloaded MoonBite release artifact (Windows PowerShell 5.1+ / pwsh)..DESCRIPTION Checks, in order: 1. sha256 - the artifact's hash appears in SHA256SUMS.txt under its own name (REQUIRED) 2. minisign - SHA256SUMS.txt.minisig verifies against the pinned public key (when minisign.exe is on PATH and the key is not the placeholder) 3. sigstore - `gh attestation verify <artifact> --owner <owner>` passes (when gh is on PATH) Exit code 0 only when every check that could run passed. Checks that could not run are reported as SKIP, and the verdict says what was not proven..EXAMPLE .\scripts\release\Verify-Release.ps1 .\moonbite-miner-windows-x86_64.zip .\scripts\release\Verify-Release.ps1 .\moonbite-wallet-windows-x86_64.zip -Sums .\SHA256SUMS.txt -PubKey .\moonbite-release.pub#>[CmdletBinding()]param( [Parameter(Mandatory = $true, Position = 0)] [string] $Artifact, [Parameter(Position = 1)] [string] $Sums = "", [string] $PubKey = "", [string] $Owner = "moonbitecoin")$ErrorActionPreference = "Stop"$script:pass = 0; $script:fail = 0; $script:skip = 0function Pass($m) { Write-Host "PASS $m"; $script:pass++ }function Fail($m) { Write-Host "FAIL $m"; $script:fail++ }function Skip($m) { Write-Host "SKIP $m"; $script:skip++ }function Have($cmd) { return [bool](Get-Command $cmd -ErrorAction SilentlyContinue) }if (-not (Test-Path -LiteralPath $Artifact -PathType Leaf)) { Write-Host "FAIL artifact not found: $Artifact"; exit 1 }$Artifact = (Resolve-Path -LiteralPath $Artifact).Path$dir = Split-Path -Parent $Artifact$name = Split-Path -Leaf $Artifactif ($Sums -eq "") { $Sums = Join-Path $dir "SHA256SUMS.txt" }if ($PubKey -eq "") { foreach ($c in @((Join-Path $dir "moonbite-release.pub"), (Join-Path $PSScriptRoot "..\..\deploy\moonbite-release.pub"))) { if (Test-Path -LiteralPath $c -PathType Leaf) { $PubKey = (Resolve-Path -LiteralPath $c).Path; break } }}Write-Host "artifact : $Artifact"Write-Host "manifest : $Sums"if ($PubKey -ne "") { Write-Host "pubkey : $PubKey" } else { Write-Host "pubkey : <none found>" }Write-Host ""# 1. sha256 ---------------------------------------------------------------------if (-not (Test-Path -LiteralPath $Sums -PathType Leaf)) { Fail "sha256: manifest not found ($Sums). Download SHA256SUMS.txt from the same release."} else { $actual = (Get-FileHash -LiteralPath $Artifact -Algorithm SHA256).Hash.ToLowerInvariant() $expected = $null $pattern = '^([0-9a-fA-F]{64})\s+\*?(?:.*/)?' + [regex]::Escape($name) + '\s*$' foreach ($line in Get-Content -LiteralPath $Sums) { $m = [regex]::Match($line, $pattern) if ($m.Success) { $expected = $m.Groups[1].Value.ToLowerInvariant(); break } } if ($null -eq $expected) { Fail "sha256: '$name' is not listed in $Sums" } elseif ($expected -eq $actual) { Pass "sha256: $actual matches manifest" } else { Fail "sha256: MISMATCH manifest=$expected actual=$actual -- do not run this file" }}# 2. minisign -------------------------------------------------------------------$keyLine = $nullif ($PubKey -ne "" -and (Test-Path -LiteralPath $PubKey -PathType Leaf)) { foreach ($line in Get-Content -LiteralPath $PubKey) { $t = $line.Trim() if ($t -eq "" -or $t.StartsWith("#") -or $t.StartsWith("untrusted comment")) { continue } $keyLine = $t; break }}$sig = "$Sums.minisig"if (-not (Have "minisign")) { Skip "minisign: tool not installed (https://jedisct1.github.io/minisign/)"} elseif ($null -eq $keyLine -or $keyLine.StartsWith("REPLACE_ME")) { Skip "minisign: release public key is still a placeholder (key ceremony pending, deploy/RELEASE-SIGNING.md)"} elseif (-not (Test-Path -LiteralPath $sig -PathType Leaf)) { Skip "minisign: no $(Split-Path -Leaf $Sums).minisig next to the manifest"} else { # Native stderr must not become a terminating error (PS 5.1 NativeCommandError). $ErrorActionPreference = "Continue" & minisign -Vm $Sums -P $keyLine *> $null $ErrorActionPreference = "Stop" if ($LASTEXITCODE -eq 0) { Pass "minisign: $(Split-Path -Leaf $Sums).minisig verifies against pinned key" } else { Fail "minisign: signature on $(Split-Path -Leaf $Sums) does NOT verify -- manifest may be forged" }}# 3. Sigstore attestation -------------------------------------------------------if (-not (Have "gh")) { Skip "sigstore: GitHub CLI (gh) not installed; cannot check build provenance"} else { $ErrorActionPreference = "Continue" $out = (& gh attestation verify $Artifact --owner $Owner 2>&1 | ForEach-Object { "$_" } | Out-String) $ErrorActionPreference = "Stop" if ($LASTEXITCODE -eq 0) { Pass "sigstore: attestation verified for owner '$Owner' (built by GitHub Actions from the public repo)" } elseif ($out -match "not logged|auth login|authentication|Bad credentials|HTTP 401") { Skip "sigstore: gh is not authenticated or its token is invalid (run 'gh auth login'); attestation not checked" } else { Fail "sigstore: no valid attestation for this file under owner '$Owner'" ($out -split "`n" | Select-Object -Last 5) | ForEach-Object { Write-Host " $_" } }}# Summary ------------------------------------------------------------------------Write-Host ""Write-Host ("result: {0} passed, {1} failed, {2} skipped" -f $script:pass, $script:fail, $script:skip)if ($script:fail -gt 0) { Write-Host "VERDICT: FAIL - do not use this artifact."; exit 1 }if ($script:pass -eq 0) { Write-Host "VERDICT: NOTHING PROVEN - no check could run."; exit 1 }if ($script:skip -gt 0) { Write-Host "VERDICT: PASS (with $($script:skip) check(s) skipped - integrity shown; see SKIP lines for what was not proven)"} else { Write-Host "VERDICT: PASS - integrity, authenticity and build provenance all verified."}exit 0