using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace StellaOps.Provcache.Api; public static partial class ProvcacheEndpointExtensions { /// /// POST /v1/provcache/proofs/{proofRoot}/verify /// private static async Task VerifyProofAsync( string proofRoot, [FromServices] IEvidenceChunkRepository chunkRepository, [FromServices] IEvidenceChunker chunker, ILogger logger, CancellationToken cancellationToken) { logger.LogDebug("POST /v1/provcache/proofs/{ProofRoot}/verify", proofRoot); try { var chunks = await chunkRepository.GetChunksAsync(proofRoot, cancellationToken); if (chunks.Count == 0) { return Results.NotFound(); } var orderedChunks = chunks.OrderBy(c => c.ChunkIndex).ToList(); var chunkResults = new List(); var allValid = true; foreach (var chunk in orderedChunks) { var isValid = chunker.VerifyChunk(chunk); var computedHash = isValid ? chunk.ChunkHash : ComputeChunkHash(chunk.Blob); chunkResults.Add(new ChunkVerificationResult { Index = chunk.ChunkIndex, IsValid = isValid, ExpectedHash = chunk.ChunkHash, ComputedHash = isValid ? null : computedHash }); if (!isValid) { allValid = false; } } // Verify Merkle root var chunkHashes = orderedChunks.Select(c => c.ChunkHash).ToList(); var computedRoot = chunker.ComputeMerkleRoot(chunkHashes); var rootMatches = string.Equals(computedRoot, proofRoot, StringComparison.OrdinalIgnoreCase); return Results.Ok(new ProofVerificationResponse { ProofRoot = proofRoot, IsValid = allValid && rootMatches, ChunkResults = chunkResults, Error = !rootMatches ? $"Merkle root mismatch. Expected: {proofRoot}, Computed: {computedRoot}" : null }); } catch (Exception ex) { logger.LogError(ex, "Error verifying proof root {ProofRoot}", proofRoot); return InternalError("Proof verification failed"); } } private static string ComputeChunkHash(byte[] data) { var hash = System.Security.Cryptography.SHA256.HashData(data); return $"sha256:{Convert.ToHexStringLower(hash)}"; } }