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