package events import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "log" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "golang.org/x/net/websocket" ) type Event struct { ID string `json:"id"` PubKey string `json:"pubkey"` CreatedAt int64 `json:"created_at"` Kind int `json:"kind"` Tags [][]string `json:"tags"` Content string `json:"content"` Sig string `json:"sig"` } var collections = make(map[int]*mongo.Collection) func InitCollections(client *mongo.Client, kinds ...int) { for _, kind := range kinds { collectionName := fmt.Sprintf("event-kind%d", kind) collections[kind] = client.Database("grain").Collection(collectionName) indexModel := mongo.IndexModel{ Keys: bson.D{{Key: "id", Value: 1}}, Options: options.Index().SetUnique(true), } _, err := collections[kind].Indexes().CreateOne(context.TODO(), indexModel) if err != nil { fmt.Printf("Failed to create index on %s: %v\n", collectionName, err) } } } func GetCollection(kind int, client *mongo.Client) *mongo.Collection { if collection, exists := collections[kind]; exists { return collection } collectionName := fmt.Sprintf("event-kind%d", kind) collection := client.Database("grain").Collection(collectionName) collections[kind] = collection indexModel := mongo.IndexModel{ Keys: bson.D{{Key: "id", Value: 1}}, Options: options.Index().SetUnique(true), } _, err := collection.Indexes().CreateOne(context.TODO(), indexModel) if err != nil { fmt.Printf("Failed to create index on %s: %v\n", collectionName, err) } return collection } func HandleEvent(ctx context.Context, evt Event, client *mongo.Client, ws *websocket.Conn) { if !ValidateEvent(evt) { sendOKResponse(ws, evt.ID, false, "invalid: signature verification failed") return } collection := GetCollection(evt.Kind, client) var err error switch evt.Kind { case 0: err = HandleEventKind0(ctx, evt, collection) case 1: err = HandleEventKind1(ctx, evt, collection) default: err = HandleDefaultEvent(ctx, evt, collection) } if err != nil { sendOKResponse(ws, evt.ID, false, fmt.Sprintf("error: %v", err)) return } sendOKResponse(ws, evt.ID, true, "") } func sendOKResponse(ws *websocket.Conn, eventID string, status bool, message string) { response := []interface{}{"OK", eventID, status, message} responseBytes, _ := json.Marshal(response) websocket.Message.Send(ws, string(responseBytes)) } func SerializeEvent(evt Event) []byte { eventData := []interface{}{ 0, evt.PubKey, evt.CreatedAt, evt.Kind, evt.Tags, evt.Content, } serializedEvent, _ := json.Marshal(eventData) return serializedEvent } func ValidateEvent(evt Event) bool { serializedEvent := SerializeEvent(evt) hash := sha256.Sum256(serializedEvent) eventID := hex.EncodeToString(hash[:]) if eventID != evt.ID { log.Printf("Invalid ID: expected %s, got %s\n", eventID, evt.ID) return false } sigBytes, err := hex.DecodeString(evt.Sig) if err != nil { log.Printf("Error decoding signature: %v\n", err) return false } sig, err := schnorr.ParseSignature(sigBytes) if err != nil { log.Printf("Error parsing signature: %v\n", err) return false } pubKeyBytes, err := hex.DecodeString(evt.PubKey) if err != nil { log.Printf("Error decoding public key: %v\n", err) return false } var pubKey *btcec.PublicKey if len(pubKeyBytes) == 32 { // Handle 32-byte public key (x-coordinate only) pubKey, err = btcec.ParsePubKey(append([]byte{0x02}, pubKeyBytes...)) } else { // Handle standard compressed or uncompressed public key pubKey, err = btcec.ParsePubKey(pubKeyBytes) } if err != nil { log.Printf("Error parsing public key: %v\n", err) return false } verified := sig.Verify(hash[:], pubKey) if !verified { log.Printf("Signature verification failed for event ID: %s\n", evt.ID) } return verified } func HandleDefaultEvent(ctx context.Context, evt Event, collection *mongo.Collection) error { _, err := collection.InsertOne(ctx, evt) if err != nil { return fmt.Errorf("Error inserting default event into MongoDB: %v", err) } fmt.Println("Inserted default event into MongoDB:", evt.ID) return nil }