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