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)}";
}
}