event.go raw
1 package nostr
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "strconv"
7
8 "github.com/mailru/easyjson"
9 )
10
11 // Event represents a Nostr event.
12 type Event struct {
13 ID string
14 PubKey string
15 CreatedAt Timestamp
16 Kind int
17 Tags Tags
18 Content string
19 Sig string
20 }
21
22 func (evt Event) String() string {
23 j, _ := easyjson.Marshal(evt)
24 return string(j)
25 }
26
27 // GetID computes the event ID and returns it as a hex string.
28 func (evt *Event) GetID() string {
29 h := sha256.Sum256(evt.Serialize())
30 return hex.EncodeToString(h[:])
31 }
32
33 // CheckID checks if the implied ID matches the given ID more efficiently.
34 func (evt *Event) CheckID() bool {
35 if len(evt.ID) != 64 {
36 return false
37 }
38
39 ser := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80)
40 ser = serializeEventInto(evt, ser)
41 h := sha256.Sum256(ser)
42
43 const hextable = "0123456789abcdef"
44
45 for i := 0; i < 32; i++ {
46 b := hextable[h[i]>>4]
47 if b != evt.ID[i*2] {
48 return false
49 }
50
51 b = hextable[h[i]&0x0f]
52 if b != evt.ID[i*2+1] {
53 return false
54 }
55 }
56
57 return true
58 }
59
60 // Serialize outputs a byte array that can be hashed to produce the canonical event "id".
61 func (evt *Event) Serialize() []byte {
62 // the serialization process is just putting everything into a JSON array
63 // so the order is kept. See NIP-01
64 dst := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80)
65 return serializeEventInto(evt, dst)
66 }
67
68 func serializeEventInto(evt *Event, dst []byte) []byte {
69 // the header portion is easy to serialize
70 // [0,"pubkey",created_at,kind,[
71 dst = append(dst, "[0,\""...)
72 dst = append(dst, evt.PubKey...)
73 dst = append(dst, "\","...)
74 dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...)
75 dst = append(dst, ',')
76 dst = append(dst, strconv.Itoa(evt.Kind)...)
77 dst = append(dst, ',')
78
79 // tags
80 dst = append(dst, '[')
81 for i, tag := range evt.Tags {
82 if i > 0 {
83 dst = append(dst, ',')
84 }
85 // tag item
86 dst = append(dst, '[')
87 for i, s := range tag {
88 if i > 0 {
89 dst = append(dst, ',')
90 }
91 dst = escapeString(dst, s)
92 }
93 dst = append(dst, ']')
94 }
95 dst = append(dst, "],"...)
96
97 // content needs to be escaped in general as it is user generated.
98 dst = escapeString(dst, evt.Content)
99 dst = append(dst, ']')
100
101 return dst
102 }
103