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