grain/server/utils/checkSig.go

114 lines
3.2 KiB
Go

package utils
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"strings"
relay "grain/server/types"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
)
// EscapeSpecialChars escapes special characters in the content according to NIP-01
func EscapeSpecialChars(content string) string {
content = strings.ReplaceAll(content, "\\", "\\\\")
content = strings.ReplaceAll(content, "\"", "\\\"")
content = strings.ReplaceAll(content, "\n", "\\n")
content = strings.ReplaceAll(content, "\r", "\\r")
content = strings.ReplaceAll(content, "\t", "\\t")
content = strings.ReplaceAll(content, "\b", "\\b")
content = strings.ReplaceAll(content, "\f", "\\f")
return content
}
// SerializeEvent manually constructs the JSON string for event serialization according to NIP-01
func SerializeEvent(evt relay.Event) string {
// Escape special characters in the content
escapedContent := EscapeSpecialChars(evt.Content)
// Manually construct the event data as a JSON array string
return fmt.Sprintf(
`[0,"%s",%d,%d,%s,"%s"]`,
evt.PubKey,
evt.CreatedAt,
evt.Kind,
serializeTags(evt.Tags),
escapedContent, // Special characters are escaped
)
}
// Helper function to serialize the tags array
func serializeTags(tags [][]string) string {
var tagStrings []string
for _, tag := range tags {
tagStrings = append(tagStrings, fmt.Sprintf(`["%s"]`, strings.Join(tag, `","`)))
}
return "[" + strings.Join(tagStrings, ",") + "]"
}
// CheckSignature verifies the event's signature and ID
func CheckSignature(evt relay.Event) bool {
// Manually serialize the event
serializedEvent := SerializeEvent(evt)
// Compute the SHA-256 hash of the serialized event
hash := sha256.Sum256([]byte(serializedEvent))
eventID := hex.EncodeToString(hash[:])
// Log the generated and provided IDs
log.Printf("Generated event ID: %s, Provided event ID: %s", eventID, evt.ID)
// Compare the computed event ID with the one provided by the client
if eventID != evt.ID {
log.Printf("Invalid ID: expected %s, got %s", eventID, evt.ID)
return false
}
// Decode the signature from hex
sigBytes, err := hex.DecodeString(evt.Sig)
if err != nil {
log.Printf("Error decoding signature: %v", err)
return false
}
// Parse the Schnorr signature
sig, err := schnorr.ParseSignature(sigBytes)
if err != nil {
log.Printf("Error parsing signature: %v", err)
return false
}
// Decode the public key from hex
pubKeyBytes, err := hex.DecodeString(evt.PubKey)
if err != nil {
log.Printf("Error decoding public key: %v", err)
return false
}
// Since the public key is 32 bytes, prepend 0x02 (assuming y-coordinate is even)
if len(pubKeyBytes) == 32 {
pubKeyBytes = append([]byte{0x02}, pubKeyBytes...)
} else {
log.Printf("Malformed public key: invalid length: %d", len(pubKeyBytes))
return false
}
// Parse the public key
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
log.Printf("Error parsing public key: %v", err)
return false
}
// Verify the signature using the event's hash and public key
verified := sig.Verify(hash[:], pubKey)
if !verified {
log.Printf("Signature verification failed for event ID: %s", evt.ID)
}
return verified
}