# Permavault evidence package verifier (capture 4f868e92-f12f-4fb7-bb5a-cb096b82eac8)
# Usage: powershell -ExecutionPolicy Bypass -File verify.ps1 [-Offline]
# Requires: PowerShell 5+. OpenSSL 3 with Ed25519 for the signature check (the script
# looks for a suitable openssl itself). Optional: ots for Bitcoin timestamps.
param([switch]$Offline)

Set-Location -Path $PSScriptRoot
$Gateway = 'https://ar-io.dev'
$PrimaryFile = 'permavault.xyz.wacz'
$PrimaryTx = '7iFluxfrjGu-BN6mdnfxUqQCClB6z9rYNIjDMYokF3I'
$ExpectedKeyId = '98ae373420471711'
$PublicKeyUrl = 'https://app.permavault.xyz/.well-known/permavault-signing-key.json'
$PubKey = 'permavault-public-key.pem'

$script:Pass = 0; $script:Fail = 0; $script:Warn = 0
function OK($m)   { $script:Pass++; Write-Host "  [PASS] $m" }
function BAD($m)  { $script:Fail++; Write-Host "  [FAIL] $m" }
function WARN($m) { $script:Warn++; Write-Host "  [WARN] $m" }
# -LiteralPath everywhere: file names may legally contain [ ] which -Path treats as wildcards.
function HashFile($p) { (Get-FileHash -Algorithm SHA256 -LiteralPath $p).Hash.ToLowerInvariant() }

Write-Host "Permavault evidence package verification"
Write-Host "Capture: 4f868e92-f12f-4fb7-bb5a-cb096b82eac8"
Write-Host ""

Write-Host "Step 1: Ed25519 signature over manifest.json"
# OpenSSL capability probe (PV-144): the signature check needs OpenSSL 3 with Ed25519.
# Running an older openssl (or none) would FALSELY report the package as tampered.
# Set $env:PV_OPENSSL to force a specific binary.
$OpenSslBin = $null
if ($env:PV_OPENSSL) {
  $opensslCandidates = @($env:PV_OPENSSL)
} else {
  $opensslCandidates = @(
    'openssl',
    "$env:ProgramFiles\Git\usr\bin\openssl.exe",
    "${env:ProgramFiles(x86)}\Git\usr\bin\openssl.exe",
    'C:\Program Files\OpenSSL-Win64\bin\openssl.exe',
    'C:\Program Files\OpenSSL\bin\openssl.exe'
  )
}
foreach ($candidate in $opensslCandidates) {
  try {
    & $candidate version *> $null
    if ($LASTEXITCODE -ne 0) { continue }
    $algos = & $candidate list -public-key-algorithms 2> $null
    if ($LASTEXITCODE -eq 0 -and ("$algos" -match 'ed25519')) { $OpenSslBin = $candidate; break }
  } catch { continue }
}
if (-not (Test-Path -LiteralPath manifest.json) -or -not (Test-Path -LiteralPath manifest.sig) -or -not (Test-Path -LiteralPath $PubKey)) {
  BAD "manifest.json, manifest.sig, or $PubKey is missing from this folder."
} elseif (-not $OpenSslBin) {
  WARN "this computer has no OpenSSL that can run the signature check (OpenSSL 3 with Ed25519 support is needed)."
  Write-Host "  To complete this check: install OpenSSL 3 (for example 'winget install ShiningLight.OpenSSL.Light'), then run this script again."
  Write-Host "  This warning does NOT mean anything is wrong with the package. This one check simply could not run here."
} else {
  & $OpenSslBin pkeyutl -verify -pubin -inkey $PubKey -rawin -in manifest.json -sigfile manifest.sig *> $null
  if ($LASTEXITCODE -eq 0) { OK "manifest.json signature is valid." }
  else { BAD "manifest signature DID NOT verify. The manifest or key has been altered." }

  # Read the key as its own step and require real output BEFORE fingerprinting:
  # hashing a failed command's empty output would fabricate a mismatch.
  $der = [System.IO.Path]::GetTempFileName()
  & $OpenSslBin pkey -pubin -in $PubKey -outform DER -out $der *> $null
  if ($LASTEXITCODE -eq 0 -and (Get-Item -LiteralPath $der).Length -gt 0) {
    $actualKeyId = (HashFile $der).Substring(0, 16)
    if ($actualKeyId -eq $ExpectedKeyId) { OK "public key fingerprint matches key id $ExpectedKeyId." }
    else { BAD "public key fingerprint is $actualKeyId but this capture was signed with $ExpectedKeyId." }
  } else {
    WARN "the public key file could not be read by $OpenSslBin, so its fingerprint was not checked. This is a tooling problem, not a sign of tampering."
  }
  Remove-Item $der -ErrorAction SilentlyContinue
  Write-Host "  Cross-check this key independently at: $PublicKeyUrl"
}
Write-Host ""

Write-Host "Step 2: file hashes against manifest.json"
try {
  $manifest = Get-Content manifest.json -Raw | ConvertFrom-Json
  foreach ($f in $manifest.files) {
    if (-not (Test-Path -LiteralPath $f.name)) {
      BAD "$($f.name) is listed in the manifest but missing from this folder."
    } elseif ((HashFile $f.name) -eq $f.sha256.ToLowerInvariant()) {
      OK "$($f.name) matches its sealed SHA-256."
    } else {
      BAD "$($f.name) DOES NOT match its sealed SHA-256. The file has been altered."
    }
  }
} catch {
  BAD "could not read the file list from manifest.json."
}
Write-Host ""

Write-Host "Step 3: permanent copy on Arweave (online check)"
if ($Offline) {
  Write-Host "  Skipped (-Offline)."
} elseif (-not $PrimaryTx) {
  WARN "no Arweave transaction id is recorded for the primary artifact yet."
} else {
  $tmp = [System.IO.Path]::GetTempFileName()
  try {
    Invoke-WebRequest -Uri "$Gateway/$PrimaryTx" -OutFile $tmp -UseBasicParsing | Out-Null
    if ((HashFile $tmp) -eq (HashFile $PrimaryFile)) {
      OK "Arweave serves the exact same bytes ($Gateway/$PrimaryTx)."
    } else {
      BAD "the Arweave copy does not match the local primary artifact."
    }
  } catch {
    WARN "could not download $Gateway/$PrimaryTx (network issue, or the bundle is still seeding)."
  }
  Remove-Item $tmp -ErrorAction SilentlyContinue
}
Write-Host ""

Write-Host "Step 4: Bitcoin timestamp (OpenTimestamps)"
if (Test-Path -LiteralPath manifest.ots) {
  $ots = Get-Command ots -ErrorAction SilentlyContinue
  if ($ots) {
    & ots verify -f manifest.json manifest.ots
    if ($LASTEXITCODE -eq 0) { OK "OpenTimestamps proof verified." }
    else { WARN "ots could not verify the proof (it may still be pending Bitcoin confirmation)." }
  } else {
    WARN "manifest.ots is present but the ots client is not installed (pip install opentimestamps-client)."
  }
} else {
  Write-Host "  No manifest.ots in this package. Nothing to check."
}
Write-Host ""

Write-Host "Step 5: RFC 3161 timestamp token"
$ManifestSha = '156c51a5b58a5afc8fc84cb870351f0df49b06440ad2835e169b79ebd5a227b7'
# The probed OpenSSL 3 is preferred; any openssl works for ts -verify.
$tsOpenSsl = $OpenSslBin
if (-not $tsOpenSsl -and (Get-Command openssl -ErrorAction SilentlyContinue)) { $tsOpenSsl = 'openssl' }
if (Test-Path -LiteralPath manifest.tsr) {
  if (-not $tsOpenSsl) {
    WARN "manifest.tsr is present but no openssl is available to check it."
  } elseif (-not (Test-Path -LiteralPath eidas_validation.json)) {
    WARN "eidas_validation.json (the token's certificate chain) is missing; cannot verify manifest.tsr offline."
  } else {
    $chain = [System.IO.Path]::GetTempFileName()
    try {
      $eidas = Get-Content eidas_validation.json -Raw | ConvertFrom-Json
      if ($eidas.certificateChainPem) {
        Set-Content -LiteralPath $chain -Value $eidas.certificateChainPem -NoNewline
        # TSAs differ in returning a bare token vs a full response — try both forms.
        & $tsOpenSsl ts -verify -digest $ManifestSha -in manifest.tsr -token_in -CAfile $chain *> $null
        if ($LASTEXITCODE -ne 0) { & $tsOpenSsl ts -verify -digest $ManifestSha -in manifest.tsr -CAfile $chain *> $null }
        if ($LASTEXITCODE -eq 0) { OK "RFC 3161 timestamp token verified against the manifest hash." }
        else { BAD "the RFC 3161 token DID NOT verify over the manifest hash." }
      } else {
        WARN "eidas_validation.json carries no certificate chain; cannot verify manifest.tsr offline."
      }
    } catch {
      WARN "could not read eidas_validation.json."
    }
    Remove-Item $chain -ErrorAction SilentlyContinue
  }
} else {
  Write-Host "  No manifest.tsr in this package. Nothing to check."
}
Write-Host ""

Write-Host "Result: $script:Pass passed, $script:Fail failed, $script:Warn warnings."
if ($script:Fail -gt 0) {
  Write-Host "VERIFICATION FAILED. Do not rely on this package until the failures are explained."
  exit 1
}
if ($script:Warn -gt 0) {
  Write-Host "Verification passed, with the warnings noted above."
  exit 0
}
Write-Host "Verification passed."
exit 0
