event.go raw
1 // Package nostr implements the Nostr protocol — event structure,
2 // validation, and relay communication.
3 //
4 // This is the organism's own Nostr implementation, synthesized from
5 // ingesting both ORLY (relay) and smesh (client). It speaks the
6 // protocol from structural understanding, not from copying a library.
7 package nostr
8
9 import (
10 "crypto/sha256"
11 "encoding/hex"
12 "encoding/json"
13 "fmt"
14
15 "github.com/btcsuite/btcd/btcec/v2"
16 "github.com/btcsuite/btcd/btcec/v2/schnorr"
17 )
18
19 // Event is a Nostr event as defined by NIP-01.
20 type Event struct {
21 ID string `json:"id"`
22 Pubkey string `json:"pubkey"`
23 CreatedAt int64 `json:"created_at"`
24 Kind int `json:"kind"`
25 Tags [][]string `json:"tags"`
26 Content string `json:"content"`
27 Sig string `json:"sig"`
28 }
29
30 // Canonical returns the NIP-01 canonical serialization for ID computation:
31 // [0,"<pubkey>",<created_at>,<kind>,<tags>,"<content>"]
32 func (e *Event) Canonical() []byte {
33 tags, _ := json.Marshal(e.Tags)
34 if e.Tags == nil {
35 tags = []byte("[]")
36 }
37 return []byte(fmt.Sprintf(`[0,"%s",%d,%d,%s,%s]`,
38 e.Pubkey, e.CreatedAt, e.Kind, tags, quoteContent(e.Content)))
39 }
40
41 // ComputeID returns the SHA256 hex digest of the canonical serialization.
42 func (e *Event) ComputeID() string {
43 h := sha256.Sum256(e.Canonical())
44 return hex.EncodeToString(h[:])
45 }
46
47 // ValidID returns true if the event's ID matches the computed ID.
48 func (e *Event) ValidID() bool {
49 return e.ID == e.ComputeID()
50 }
51
52 // ValidSig returns true if the event's schnorr signature is valid.
53 func (e *Event) ValidSig() bool {
54 // Decode the public key (32-byte x-only).
55 pubBytes, err := hex.DecodeString(e.Pubkey)
56 if err != nil || len(pubBytes) != 32 {
57 return false
58 }
59 pub, err := schnorr.ParsePubKey(pubBytes)
60 if err != nil {
61 return false
62 }
63
64 // Decode the signature (64 bytes).
65 sigBytes, err := hex.DecodeString(e.Sig)
66 if err != nil || len(sigBytes) != 64 {
67 return false
68 }
69 sig, err := schnorr.ParseSignature(sigBytes)
70 if err != nil {
71 return false
72 }
73
74 // The message is the event ID bytes (SHA256 hash).
75 idBytes, err := hex.DecodeString(e.ID)
76 if err != nil || len(idBytes) != 32 {
77 return false
78 }
79
80 return sig.Verify(idBytes, pub)
81 }
82
83 // Valid returns true if both ID and signature are correct.
84 func (e *Event) Valid() bool {
85 return e.ValidID() && e.ValidSig()
86 }
87
88 // quoteContent produces a JSON-encoded string literal for the content field.
89 func quoteContent(s string) string {
90 b, _ := json.Marshal(s)
91 return string(b)
92 }
93
94 // Filter is a NIP-01 subscription filter.
95 type Filter struct {
96 IDs []string `json:"ids,omitempty"`
97 Authors []string `json:"authors,omitempty"`
98 Kinds []int `json:"kinds,omitempty"`
99 Since *int64 `json:"since,omitempty"`
100 Until *int64 `json:"until,omitempty"`
101 Limit *int `json:"limit,omitempty"`
102 // Tag filters: #e, #p, etc.
103 Tags map[string][]string `json:"-"`
104 }
105
106 // MarshalJSON handles the tag filter serialization.
107 func (f Filter) MarshalJSON() ([]byte, error) {
108 type plain Filter
109 m := make(map[string]any)
110
111 // Marshal the plain fields.
112 data, _ := json.Marshal(plain(f))
113 json.Unmarshal(data, &m)
114
115 // Add tag filters as #<letter>.
116 for k, v := range f.Tags {
117 m["#"+k] = v
118 }
119
120 return json.Marshal(m)
121 }
122
123 // Sign creates a schnorr signature for an event using the given private key.
124 // The private key is a 32-byte hex-encoded scalar.
125 func (e *Event) Sign(privKeyHex string) error {
126 privBytes, err := hex.DecodeString(privKeyHex)
127 if err != nil {
128 return fmt.Errorf("decode private key: %w", err)
129 }
130
131 privKey, _ := btcec.PrivKeyFromBytes(privBytes)
132
133 // Set pubkey from private key.
134 pub := privKey.PubKey()
135 e.Pubkey = hex.EncodeToString(schnorr.SerializePubKey(pub))
136
137 // Compute ID.
138 e.ID = e.ComputeID()
139
140 // Sign the ID.
141 idBytes, _ := hex.DecodeString(e.ID)
142 sig, err := schnorr.Sign(privKey, idBytes)
143 if err != nil {
144 return fmt.Errorf("sign: %w", err)
145 }
146 e.Sig = hex.EncodeToString(sig.Serialize())
147
148 return nil
149 }
150