| | 1 | | using NostrSure.Domain.Entities; |
| | 2 | | using NostrSure.Domain.Validation; |
| | 3 | |
|
| | 4 | | namespace NostrSure.Domain.Services; |
| | 5 | |
|
| | 6 | | /// <summary> |
| | 7 | | /// Validates cryptographic signatures using optimized Schnorr verification |
| | 8 | | /// </summary> |
| | 9 | | public sealed class EventSignatureValidator : IEventSignatureValidator |
| | 10 | | { |
| | 11 | | private readonly ICryptographicService _cryptographicService; |
| | 12 | | private readonly IHexConverter _hexConverter; |
| | 13 | |
|
| 14 | 14 | | public EventSignatureValidator(ICryptographicService cryptographicService, IHexConverter hexConverter) |
| 14 | 15 | | { |
| 14 | 16 | | _cryptographicService = cryptographicService; |
| 14 | 17 | | _hexConverter = hexConverter; |
| 14 | 18 | | } |
| | 19 | |
|
| | 20 | | public Task<ValidationResult> ValidateSignatureAsync(NostrEvent evt, CancellationToken cancellationToken = default) |
| 102 | 21 | | { |
| 102 | 22 | | var result = ValidateSignature(evt); |
| 102 | 23 | | return Task.FromResult(result); |
| 102 | 24 | | } |
| | 25 | |
|
| | 26 | | public ValidationResult ValidateSignature(NostrEvent evt) |
| 104 | 27 | | { |
| | 28 | | // Quick validation of required fields |
| 104 | 29 | | if (string.IsNullOrWhiteSpace(evt.Sig)) |
| 0 | 30 | | return ValidationResult.Failure("Signature is empty", "EMPTY_SIGNATURE"); |
| | 31 | |
|
| 104 | 32 | | if (string.IsNullOrWhiteSpace(evt.Pubkey?.Value)) |
| 0 | 33 | | return ValidationResult.Failure("Pubkey is empty", "EMPTY_PUBKEY"); |
| | 34 | |
|
| 104 | 35 | | if (string.IsNullOrWhiteSpace(evt.Id)) |
| 0 | 36 | | return ValidationResult.Failure("Event ID is empty", "EMPTY_EVENT_ID"); |
| | 37 | |
|
| | 38 | | try |
| 104 | 39 | | { |
| | 40 | | // Parse hex strings using optimized converter |
| 104 | 41 | | Span<byte> eventIdBytes = stackalloc byte[32]; |
| 104 | 42 | | Span<byte> pubkeyBytes = stackalloc byte[32]; |
| 104 | 43 | | Span<byte> sigBytes = stackalloc byte[64]; |
| | 44 | |
|
| 104 | 45 | | if (!_hexConverter.TryParseHex(evt.Id, eventIdBytes, out var eventIdLength) || eventIdLength != 32) |
| 0 | 46 | | return ValidationResult.Failure("Invalid event ID length (must be 32 bytes)", "INVALID_EVENT_ID_LENGTH") |
| | 47 | |
|
| 104 | 48 | | if (!_hexConverter.TryParseHex(evt.Pubkey.Value, pubkeyBytes, out var pubkeyLength) || pubkeyLength != 32) |
| 0 | 49 | | return ValidationResult.Failure("Invalid pubkey length (must be 32 bytes)", "INVALID_PUBKEY_LENGTH"); |
| | 50 | |
|
| 104 | 51 | | if (!_hexConverter.TryParseHex(evt.Sig, sigBytes, out var sigLength) || sigLength != 64) |
| 1 | 52 | | return ValidationResult.Failure("Invalid signature length (must be 64 bytes)", "INVALID_SIGNATURE_LENGTH |
| | 53 | |
|
| | 54 | | // Verify signature using cryptographic service |
| 103 | 55 | | var isValid = _cryptographicService.VerifySchnorrSignature(sigBytes, eventIdBytes, pubkeyBytes); |
| | 56 | |
|
| 103 | 57 | | return isValid |
| 103 | 58 | | ? ValidationResult.Success() |
| 103 | 59 | | : ValidationResult.Failure("Signature verification failed", "SIGNATURE_VERIFICATION_FAILED"); |
| | 60 | | } |
| 0 | 61 | | catch (System.Exception ex) |
| 0 | 62 | | { |
| 0 | 63 | | return ValidationResult.Failure($"Exception during signature validation: {ex.Message}", ex, ValidationSeveri |
| | 64 | | } |
| 104 | 65 | | } |
| | 66 | | } |