event.go raw

   1  package event
   2  
   3  import (
   4  	"fmt"
   5  	"io"
   6  
   7  	"next.orly.dev/pkg/nostr/crypto/ec/schnorr"
   8  	"next.orly.dev/pkg/nostr/encoders/ints"
   9  	"next.orly.dev/pkg/nostr/encoders/kind"
  10  	"next.orly.dev/pkg/nostr/encoders/tag"
  11  	"next.orly.dev/pkg/nostr/encoders/text"
  12  	"next.orly.dev/pkg/nostr/utils"
  13  	"github.com/minio/sha256-simd"
  14  	"github.com/templexxx/xhex"
  15  	"next.orly.dev/pkg/lol/chk"
  16  	"next.orly.dev/pkg/lol/errorf"
  17  )
  18  
  19  // E is the primary datatype of nostr. This is the form of the structure that
  20  // defines its JSON string-based format.
  21  //
  22  // WARNING: DO NOT use json.Marshal with this type because it will not properly
  23  // encode <, >, and & characters due to legacy bullcrap in the encoding/json
  24  // library. Either call MarshalJSON directly or use a json.Encoder with html
  25  // escaping disabled.
  26  type E struct {
  27  
  28  	// ID is the SHA256 hash of the canonical encoding of the event in binary
  29  	// format
  30  	ID []byte
  31  
  32  	// Pubkey is the public key of the event creator in binary format
  33  	Pubkey []byte
  34  
  35  	// CreatedAt is the UNIX timestamp of the event according to the event
  36  	// creator (never trust a timestamp!)
  37  	CreatedAt int64
  38  
  39  	// Kind is the nostr protocol code for the type of event. See kind.T
  40  	Kind uint16
  41  
  42  	// Tags are a list of tags, which are a list of strings usually structured
  43  	// as a 3-layer scheme indicating specific features of an event.
  44  	Tags *tag.S
  45  
  46  	// Content is an arbitrary string that can contain anything, but usually
  47  	// conforming to a specification relating to the Kind and the Tags.
  48  	Content []byte
  49  
  50  	// Sig is the signature on the ID hash that validates as coming from the
  51  	// Pubkey in binary format.
  52  	Sig []byte
  53  }
  54  
  55  var (
  56  	jId        = []byte("id")
  57  	jPubkey    = []byte("pubkey")
  58  	jCreatedAt = []byte("created_at")
  59  	jKind      = []byte("kind")
  60  	jTags      = []byte("tags")
  61  	jContent   = []byte("content")
  62  	jSig       = []byte("sig")
  63  )
  64  
  65  // New returns a new event.E.
  66  func New() *E {
  67  	return &E{}
  68  }
  69  
  70  // Free nils all of the fields to hint to the GC that the event.E can be freed.
  71  func (ev *E) Free() {
  72  	ev.ID = nil
  73  	ev.Pubkey = nil
  74  	ev.Tags = nil
  75  	ev.Content = nil
  76  	ev.Sig = nil
  77  }
  78  
  79  // Clone creates a deep copy of the event with independent memory allocations.
  80  // The clone does not use bufpool, ensuring it has a separate lifetime from
  81  // the original event. This prevents corruption when the original is freed
  82  // while the clone is still in use (e.g., in asynchronous delivery).
  83  func (ev *E) Clone() *E {
  84  	clone := &E{
  85  		CreatedAt: ev.CreatedAt,
  86  		Kind:      ev.Kind,
  87  	}
  88  
  89  	// Deep copy all byte slices with independent memory
  90  	if ev.ID != nil {
  91  		clone.ID = make([]byte, len(ev.ID))
  92  		copy(clone.ID, ev.ID)
  93  	}
  94  	if ev.Pubkey != nil {
  95  		clone.Pubkey = make([]byte, len(ev.Pubkey))
  96  		copy(clone.Pubkey, ev.Pubkey)
  97  	}
  98  	if ev.Content != nil {
  99  		clone.Content = make([]byte, len(ev.Content))
 100  		copy(clone.Content, ev.Content)
 101  	}
 102  	if ev.Sig != nil {
 103  		clone.Sig = make([]byte, len(ev.Sig))
 104  		copy(clone.Sig, ev.Sig)
 105  	}
 106  
 107  	// Deep copy tags
 108  	if ev.Tags != nil {
 109  		clone.Tags = tag.NewS()
 110  		for _, tg := range *ev.Tags {
 111  			if tg != nil {
 112  				// Create new tag with deep-copied elements
 113  				newTag := tag.NewWithCap(len(tg.T))
 114  				for _, element := range tg.T {
 115  					newElement := make([]byte, len(element))
 116  					copy(newElement, element)
 117  					newTag.T = append(newTag.T, newElement)
 118  				}
 119  				clone.Tags.Append(newTag)
 120  			}
 121  		}
 122  	}
 123  
 124  	return clone
 125  }
 126  
 127  // EstimateSize returns a size for the event that allows for worst case scenario
 128  // expansion of the escaped content and tags.
 129  func (ev *E) EstimateSize() (size int) {
 130  	size = len(ev.ID)*2 + len(ev.Pubkey)*2 + len(ev.Sig)*2 + len(ev.Content)*2
 131  	if ev.Tags == nil {
 132  		return
 133  	}
 134  	for _, v := range *ev.Tags {
 135  		for _, w := range (*v).T {
 136  			size += len(w) * 2
 137  		}
 138  	}
 139  	return
 140  }
 141  
 142  func (ev *E) Marshal(dst []byte) (b []byte) {
 143  	b = dst
 144  	// Pre-allocate buffer if nil to reduce reallocations
 145  	if b == nil {
 146  		estimatedSize := ev.EstimateSize()
 147  		// Add overhead for JSON structure (keys, quotes, commas, etc.)
 148  		estimatedSize += 100
 149  		b = make([]byte, 0, estimatedSize)
 150  	}
 151  	b = append(b, '{')
 152  	b = append(b, '"')
 153  	b = append(b, jId...)
 154  	b = append(b, `":"`...)
 155  	// Pre-allocate hex encoding space
 156  	hexStart := len(b)
 157  	b = append(b, make([]byte, 2*sha256.Size)...)
 158  	xhex.Encode(b[hexStart:], ev.ID)
 159  	b = append(b, `","`...)
 160  	b = append(b, jPubkey...)
 161  	b = append(b, `":"`...)
 162  	hexStart = len(b)
 163  	b = append(b, make([]byte, 2*schnorr.PubKeyBytesLen)...)
 164  	xhex.Encode(b[hexStart:], ev.Pubkey)
 165  	b = append(b, `","`...)
 166  	b = append(b, jCreatedAt...)
 167  	b = append(b, `":`...)
 168  	b = ints.New(ev.CreatedAt).Marshal(b)
 169  	b = append(b, `,"`...)
 170  	b = append(b, jKind...)
 171  	b = append(b, `":`...)
 172  	b = ints.New(ev.Kind).Marshal(b)
 173  	b = append(b, `,"`...)
 174  	b = append(b, jTags...)
 175  	b = append(b, `":`...)
 176  	if ev.Tags != nil {
 177  		b = ev.Tags.Marshal(b)
 178  	} else {
 179  		// Emit empty array for nil tags to keep JSON valid
 180  		b = append(b, '[', ']')
 181  	}
 182  	b = append(b, `,"`...)
 183  	b = append(b, jContent...)
 184  	b = append(b, `":"`...)
 185  	b = text.NostrEscape(b, ev.Content)
 186  	b = append(b, `","`...)
 187  	b = append(b, jSig...)
 188  	b = append(b, `":"`...)
 189  	hexStart = len(b)
 190  	b = append(b, make([]byte, 2*schnorr.SignatureSize)...)
 191  	xhex.Encode(b[hexStart:], ev.Sig)
 192  	b = append(b, `"}`...)
 193  	return
 194  }
 195  
 196  // MarshalJSON marshals an event.E into a JSON byte string.
 197  //
 198  // WARNING: if json.Marshal is called in the hopes of invoking this function on
 199  // an event, if it has <, > or * in the content or tags they are escaped into
 200  // unicode escapes and break the event ID. Call this function directly in order
 201  // to bypass this issue.
 202  func (ev *E) MarshalJSON() (b []byte, err error) {
 203  	b = ev.Marshal(nil)
 204  	return
 205  }
 206  
 207  func (ev *E) Serialize() (b []byte) {
 208  	b = ev.Marshal(nil)
 209  	return
 210  }
 211  
 212  // Unmarshal unmarshalls a JSON string into an event.E.
 213  func (ev *E) Unmarshal(b []byte) (rem []byte, err error) {
 214  	key := make([]byte, 0, 9)
 215  	for ; len(b) > 0; b = b[1:] {
 216  		// Skip whitespace
 217  		if isWhitespace(b[0]) {
 218  			continue
 219  		}
 220  		if b[0] == '{' {
 221  			b = b[1:]
 222  			goto BetweenKeys
 223  		}
 224  	}
 225  	goto eof
 226  BetweenKeys:
 227  	for ; len(b) > 0; b = b[1:] {
 228  		// Skip whitespace
 229  		if isWhitespace(b[0]) {
 230  			continue
 231  		}
 232  		if b[0] == '"' {
 233  			b = b[1:]
 234  			goto InKey
 235  		}
 236  	}
 237  	goto eof
 238  InKey:
 239  	for ; len(b) > 0; b = b[1:] {
 240  		if b[0] == '"' {
 241  			b = b[1:]
 242  			goto InKV
 243  		}
 244  		key = append(key, b[0])
 245  	}
 246  	goto eof
 247  InKV:
 248  	for ; len(b) > 0; b = b[1:] {
 249  		// Skip whitespace
 250  		if isWhitespace(b[0]) {
 251  			continue
 252  		}
 253  		if b[0] == ':' {
 254  			b = b[1:]
 255  			goto InVal
 256  		}
 257  	}
 258  	goto eof
 259  InVal:
 260  	// Skip whitespace before value
 261  	for len(b) > 0 && isWhitespace(b[0]) {
 262  		b = b[1:]
 263  	}
 264  	switch key[0] {
 265  	case jId[0]:
 266  		if !utils.FastEqual(jId, key) {
 267  			goto invalid
 268  		}
 269  		var id []byte
 270  		if id, b, err = text.UnmarshalHex(b); chk.E(err) {
 271  			return
 272  		}
 273  		if len(id) != sha256.Size {
 274  			err = errorf.E(
 275  				"invalid Subscription, require %d got %d", sha256.Size,
 276  				len(id),
 277  			)
 278  			return
 279  		}
 280  		ev.ID = id
 281  		goto BetweenKV
 282  	case jPubkey[0]:
 283  		if !utils.FastEqual(jPubkey, key) {
 284  			goto invalid
 285  		}
 286  		var pk []byte
 287  		if pk, b, err = text.UnmarshalHex(b); chk.E(err) {
 288  			return
 289  		}
 290  		if len(pk) != schnorr.PubKeyBytesLen {
 291  			err = errorf.E(
 292  				"invalid pubkey, require %d got %d",
 293  				schnorr.PubKeyBytesLen, len(pk),
 294  			)
 295  			return
 296  		}
 297  		ev.Pubkey = pk
 298  		goto BetweenKV
 299  	case jKind[0]:
 300  		if !utils.FastEqual(jKind, key) {
 301  			goto invalid
 302  		}
 303  		k := kind.New(0)
 304  		if b, err = k.Unmarshal(b); chk.E(err) {
 305  			return
 306  		}
 307  		ev.Kind = k.ToU16()
 308  		goto BetweenKV
 309  	case jTags[0]:
 310  		if !utils.FastEqual(jTags, key) {
 311  			goto invalid
 312  		}
 313  		ev.Tags = new(tag.S)
 314  		if b, err = ev.Tags.Unmarshal(b); chk.E(err) {
 315  			return
 316  		}
 317  		goto BetweenKV
 318  	case jSig[0]:
 319  		if !utils.FastEqual(jSig, key) {
 320  			goto invalid
 321  		}
 322  		var sig []byte
 323  		if sig, b, err = text.UnmarshalHex(b); chk.E(err) {
 324  			return
 325  		}
 326  		if len(sig) != schnorr.SignatureSize {
 327  			err = errorf.E(
 328  				"invalid sig length, require %d got %d '%s'\n%s",
 329  				schnorr.SignatureSize, len(sig), b, b,
 330  			)
 331  			return
 332  		}
 333  		ev.Sig = sig
 334  		goto BetweenKV
 335  	case jContent[0]:
 336  		if key[1] == jContent[1] {
 337  			if !utils.FastEqual(jContent, key) {
 338  				goto invalid
 339  			}
 340  			if ev.Content, b, err = text.UnmarshalQuoted(b); chk.T(err) {
 341  				return
 342  			}
 343  			goto BetweenKV
 344  		} else if key[1] == jCreatedAt[1] {
 345  			if !utils.FastEqual(jCreatedAt, key) {
 346  				goto invalid
 347  			}
 348  			i := ints.New(0)
 349  			if b, err = i.Unmarshal(b); chk.T(err) {
 350  				return
 351  			}
 352  			ev.CreatedAt = i.Int64()
 353  			goto BetweenKV
 354  		} else {
 355  			goto invalid
 356  		}
 357  	default:
 358  		goto invalid
 359  	}
 360  BetweenKV:
 361  	key = key[:0]
 362  	for ; len(b) > 0; b = b[1:] {
 363  		// Skip whitespace
 364  		if isWhitespace(b[0]) {
 365  			continue
 366  		}
 367  		switch {
 368  		case len(b) == 0:
 369  			return
 370  		case b[0] == '}':
 371  			b = b[1:]
 372  			goto AfterClose
 373  		case b[0] == ',':
 374  			b = b[1:]
 375  			goto BetweenKeys
 376  		case b[0] == '"':
 377  			b = b[1:]
 378  			goto InKey
 379  		}
 380  	}
 381  	// If we reach here, the buffer ended unexpectedly. Treat as end-of-object
 382  	goto AfterClose
 383  AfterClose:
 384  	rem = b
 385  	return
 386  invalid:
 387  	err = fmt.Errorf(
 388  		"invalid key,\n'%s'\n'%s'\n'%s'", string(b), string(b[:]),
 389  		string(b),
 390  	)
 391  	return
 392  eof:
 393  	err = io.EOF
 394  	return
 395  }
 396  
 397  // UnmarshalJSON unmarshalls a JSON string into an event.E.
 398  //
 399  // Call ev.Free() to return the provided buffer to the bufpool afterwards.
 400  func (ev *E) UnmarshalJSON(b []byte) (err error) {
 401  	// log.I.F("UnmarshalJSON: '%s'", b)
 402  	_, err = ev.Unmarshal(b)
 403  	return
 404  }
 405  
 406  // isWhitespace returns true if the byte is a whitespace character (space, tab, newline, carriage return).
 407  func isWhitespace(b byte) bool {
 408  	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
 409  }
 410  
 411  // S is an array of event.E that sorts in reverse chronological order.
 412  type S []*E
 413  
 414  // Len returns the length of the event.Es.
 415  func (ev S) Len() int { return len(ev) }
 416  
 417  // Less returns whether the first is newer than the second (larger unix
 418  // timestamp).
 419  func (ev S) Less(i, j int) bool { return ev[i].CreatedAt > ev[j].CreatedAt }
 420  
 421  // Swap two indexes of the event.Es with each other.
 422  func (ev S) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
 423  
 424  // C is a channel that carries event.E.
 425  type C chan *E
 426