event.go raw

   1  package nostr
   2  
   3  import (
   4  	"common/crypto/secp256k1"
   5  	"common/crypto/sha256"
   6  	"common/helpers"
   7  )
   8  
   9  // Event is a Nostr event (NIP-01).
  10  type Event struct {
  11  	ID        string `json:"id"`
  12  	PubKey    string `json:"pubkey"`
  13  	CreatedAt int64  `json:"created_at"`
  14  	Kind      int    `json:"kind"`
  15  	Tags      Tags   `json:"tags"`
  16  	Content   string `json:"content"`
  17  	Sig       string `json:"sig"`
  18  }
  19  
  20  // Serialize returns the canonical JSON array for ID computation:
  21  // [0,<pubkey>,<created_at>,<kind>,<tags>,<content>]
  22  func (e *Event) Serialize() string {
  23  	buf := make([]byte, 0, 256)
  24  	buf = append(buf, "[0,"...)
  25  	buf = append(buf, helpers.JsonString(e.PubKey)...)
  26  	buf = append(buf, ',')
  27  	buf = append(buf, helpers.Itoa(e.CreatedAt)...)
  28  	buf = append(buf, ',')
  29  	buf = append(buf, helpers.Itoa(int64(e.Kind))...)
  30  	buf = append(buf, ',')
  31  	buf = serializeTags(buf, e.Tags)
  32  	buf = append(buf, ',')
  33  	buf = append(buf, helpers.JsonString(e.Content)...)
  34  	buf = append(buf, ']')
  35  	return string(buf)
  36  }
  37  
  38  // ComputeID computes and sets the event ID (SHA-256 of serialized form).
  39  func (e *Event) ComputeID() string {
  40  	ser := e.Serialize()
  41  	hash := sha256.Sum([]byte(ser))
  42  	e.ID = helpers.HexEncode(hash[:])
  43  	return e.ID
  44  }
  45  
  46  // CheckID verifies the event ID matches the content.
  47  func (e *Event) CheckID() bool {
  48  	ser := e.Serialize()
  49  	hash := sha256.Sum([]byte(ser))
  50  	expected := helpers.HexEncode(hash[:])
  51  	return e.ID == expected
  52  }
  53  
  54  // Sign computes the event ID and signs it with the given secret key.
  55  // Sets ID, PubKey, and Sig. Returns false on failure.
  56  func (e *Event) Sign(seckey [32]byte, auxRand [32]byte) bool {
  57  	pk, ok := secp256k1.PubKeyFromSecKey(seckey)
  58  	if !ok {
  59  		return false
  60  	}
  61  	e.PubKey = helpers.HexEncode(pk[:])
  62  	e.ComputeID()
  63  	idBytes, ok := helpers.HexDecode32(e.ID)
  64  	if !ok {
  65  		return false
  66  	}
  67  	sig, ok := secp256k1.SignSchnorr(seckey, idBytes, auxRand)
  68  	if !ok {
  69  		return false
  70  	}
  71  	e.Sig = helpers.HexEncode(sig[:])
  72  	return true
  73  }
  74  
  75  // CheckSig verifies the event signature against its pubkey and ID.
  76  func (e *Event) CheckSig() bool {
  77  	pk, ok := helpers.HexDecode32(e.PubKey)
  78  	if !ok {
  79  		return false
  80  	}
  81  	id, ok := helpers.HexDecode32(e.ID)
  82  	if !ok {
  83  		return false
  84  	}
  85  	if len(e.Sig) != 128 {
  86  		return false
  87  	}
  88  	var sig [64]byte
  89  	sigBytes := helpers.HexDecode(e.Sig)
  90  	if len(sigBytes) != 64 {
  91  		return false
  92  	}
  93  	copy(sig[:], sigBytes)
  94  	return secp256k1.VerifySchnorr(pk, id, sig)
  95  }
  96  
  97  // ToJSON returns the event as a JSON object string.
  98  func (e *Event) ToJSON() string {
  99  	buf := make([]byte, 0, 512)
 100  	buf = append(buf, `{"id":`...)
 101  	buf = append(buf, helpers.JsonString(e.ID)...)
 102  	buf = append(buf, `,"pubkey":`...)
 103  	buf = append(buf, helpers.JsonString(e.PubKey)...)
 104  	buf = append(buf, `,"created_at":`...)
 105  	buf = append(buf, helpers.Itoa(e.CreatedAt)...)
 106  	buf = append(buf, `,"kind":`...)
 107  	buf = append(buf, helpers.Itoa(int64(e.Kind))...)
 108  	buf = append(buf, `,"tags":`...)
 109  	buf = serializeTags(buf, e.Tags)
 110  	buf = append(buf, `,"content":`...)
 111  	buf = append(buf, helpers.JsonString(e.Content)...)
 112  	buf = append(buf, `,"sig":`...)
 113  	buf = append(buf, helpers.JsonString(e.Sig)...)
 114  	buf = append(buf, '}')
 115  	return string(buf)
 116  }
 117  
 118  func serializeTags(buf []byte, tags Tags) []byte {
 119  	buf = append(buf, '[')
 120  	for i, tag := range tags {
 121  		if i > 0 {
 122  			buf = append(buf, ',')
 123  		}
 124  		buf = append(buf, '[')
 125  		for j, s := range tag {
 126  			if j > 0 {
 127  				buf = append(buf, ',')
 128  			}
 129  			buf = append(buf, helpers.JsonString(s)...)
 130  		}
 131  		buf = append(buf, ']')
 132  	}
 133  	buf = append(buf, ']')
 134  	return buf
 135  }
 136