event_test.go raw

   1  package nostr
   2  
   3  import (
   4  	"encoding/hex"
   5  	"encoding/json"
   6  	"strings"
   7  	"testing"
   8  )
   9  
  10  // The expected serialization was produced by Node's JSON.stringify and the
  11  // hash by its SHA-256, which is the definition NIP-01 uses.
  12  const expectedCanonicalJSON = "\"[0,\\\"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9\\\",1700000000,1,[[\\\"e\\\",\\\"abc\\\\\\\", def\\\"],[\\\"t\\\",\\\"emoji😀\\\"],[\\\"x\\\",\\\"\\\\u0001\\\\u001f\\\"],[\\\"p\\\",\\\"line\\\\nbreak\\\"]],\\\"héllo \\\\\\\"world\\\\\\\" \\\\\\\\ tab\\\\t nl\\\\n ctrl\\\\u0001 emoji 🚀 ls\u2028ps\u2029 end\\\"]\""
  13  
  14  const expectedCanonicalHash = "eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316"
  15  
  16  func trickyEvent(t *testing.T) *Event {
  17  	t.Helper()
  18  	pub, err := hex.DecodeString("f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9")
  19  	if err != nil {
  20  		t.Fatal(err)
  21  	}
  22  	e := &Event{
  23  		CreatedAt: 1700000000,
  24  		Kind:      1,
  25  		Tags: Tags{
  26  			{"e", "abc\", def"},
  27  			{"t", "emoji😀"},
  28  			{"x", "\u0001\u001f"},
  29  			{"p", "line\nbreak"},
  30  		},
  31  		Content: "héllo \"world\" \\ tab\t nl\n ctrl\u0001 emoji 🚀 ls\u2028ps\u2029 end",
  32  	}
  33  	copy(e.PubKey[:], pub)
  34  	return e
  35  }
  36  
  37  func TestCanonicalMatchesJSONStringify(t *testing.T) {
  38  	var want string
  39  	if err := json.Unmarshal([]byte(expectedCanonicalJSON), &want); err != nil {
  40  		t.Fatalf("bad expected literal: %v", err)
  41  	}
  42  	e := trickyEvent(t)
  43  	got := string(e.Canonical())
  44  	if got != want {
  45  		t.Fatalf("canonical mismatch\n got %q\nwant %q", got, want)
  46  	}
  47  	e.ComputeID()
  48  	if hex.EncodeToString(e.ID[:]) != expectedCanonicalHash {
  49  		t.Fatalf("hash = %x, want %s", e.ID, expectedCanonicalHash)
  50  	}
  51  	if !e.CheckID() {
  52  		t.Fatal("CheckID rejected a matching id")
  53  	}
  54  }
  55  
  56  func TestSignAndVerifyEvent(t *testing.T) {
  57  	var sk [32]byte
  58  	sk[31] = 3
  59  	e := &Event{CreatedAt: 1700000000, Kind: 1, Content: "signed", Tags: Tags{{"t", "test"}}}
  60  	if err := e.Sign(sk); err != nil {
  61  		t.Fatal(err)
  62  	}
  63  	if err := e.Verify(); err != nil {
  64  		t.Fatalf("verify: %v", err)
  65  	}
  66  	e.Content = "tampered"
  67  	if err := e.Verify(); err == nil {
  68  		t.Fatal("tampered event verified")
  69  	}
  70  }
  71  
  72  func TestParseEvent(t *testing.T) {
  73  	raw := []byte(`{"id":"eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316","pubkey":"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9","created_at":1700000000,"kind":1,"tags":[["e","x"]],"content":"hi","sig":"` + hex.EncodeToString(make([]byte, 64)) + `"}`)
  74  	e, err := ParseEvent(raw)
  75  	if err != nil {
  76  		t.Fatal(err)
  77  	}
  78  	if e.Kind != 1 || e.CreatedAt != 1700000000 || e.Content != "hi" || len(e.Tags) != 1 {
  79  		t.Fatalf("unexpected event: %+v", e)
  80  	}
  81  	// Round-trip through the wire encoder.
  82  	out := e.AppendJSON(nil)
  83  	if _, err := ParseEvent(out); err != nil {
  84  		t.Fatalf("re-parse: %v", err)
  85  	}
  86  }
  87  
  88  func TestParseEventRejectsMalformed(t *testing.T) {
  89  	cases := []string{
  90  		`{}`,
  91  		`{"id":"x"}`,
  92  		`{"id":"eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316","pubkey":"zz","created_at":1,"kind":1,"tags":[],"content":"","sig":"00"}`,
  93  		`{"id":"eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316","pubkey":"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9","created_at":1,"kind":70000,"tags":[],"content":"","sig":"` + hex.EncodeToString(make([]byte, 64)) + `"}`,
  94  	}
  95  	for i, c := range cases {
  96  		if _, err := ParseEvent([]byte(c)); err == nil {
  97  			t.Errorf("case %d: expected an error", i)
  98  		}
  99  	}
 100  }
 101  
 102  func TestKindClasses(t *testing.T) {
 103  	cases := []struct {
 104  		kind      int
 105  		replace   bool
 106  		address   bool
 107  		ephemeral bool
 108  	}{
 109  		{0, true, false, false},
 110  		{1, false, false, false},
 111  		{3, true, false, false},
 112  		{5, false, false, false},
 113  		{10002, true, false, false},
 114  		{20000, false, false, true},
 115  		{29999, false, false, true},
 116  		{30000, false, true, false},
 117  		{30023, false, true, false},
 118  		{40000, false, false, false},
 119  	}
 120  	for _, c := range cases {
 121  		e := &Event{Kind: c.kind}
 122  		if e.IsReplaceable() != c.replace || e.IsAddressable() != c.address || e.IsEphemeral() != c.ephemeral {
 123  			t.Errorf("kind %d: classes wrong", c.kind)
 124  		}
 125  	}
 126  }
 127  
 128  func TestTagHelpers(t *testing.T) {
 129  	ev := &Event{
 130  		Kind: 1,
 131  		Tags: Tags{
 132  			{"e", "id1"},
 133  			{"e", "id2"},
 134  			{"p", "pk"},
 135  			{"d", "slug"},
 136  			{"expiration", "12345"},
 137  			{"-"},
 138  		},
 139  	}
 140  	if v, ok := ev.Tags.First("e"); !ok || v != "id1" {
 141  		t.Fatalf("First(e) = %q,%v", v, ok)
 142  	}
 143  	if v, ok := ev.Tags.First("missing"); ok || v != "" {
 144  		t.Fatalf("First(missing) = %q,%v", v, ok)
 145  	}
 146  	vals := ev.Tags.Values("e")
 147  	if len(vals) != 2 || vals[0] != "id1" || vals[1] != "id2" {
 148  		t.Fatalf("Values(e) = %v", vals)
 149  	}
 150  	if !ev.Tags.Has("p", "pk") || ev.Tags.Has("p", "other") {
 151  		t.Fatal("Has")
 152  	}
 153  	if !ev.Tags.HasName("d") || ev.Tags.HasName("x") {
 154  		t.Fatal("HasName")
 155  	}
 156  	if ev.DTag() != "slug" {
 157  		t.Fatalf("DTag = %q", ev.DTag())
 158  	}
 159  	if ev.Expiration() != 12345 || !ev.Expired(20000) || ev.Expired(10000) {
 160  		t.Fatalf("expiration helpers wrong")
 161  	}
 162  	if !ev.IsProtected() {
 163  		t.Fatal("IsProtected")
 164  	}
 165  	unknown := &Event{}
 166  	if unknown.Expiration() != 0 || unknown.IsProtected() || unknown.DTag() != "" {
 167  		t.Fatal("empty event helpers")
 168  	}
 169  }
 170  
 171  func TestPubKeyHex(t *testing.T) {
 172  	var sk [32]byte
 173  	sk[31] = 3
 174  	pub, err := PubKeyHex(sk)
 175  	if err != nil {
 176  		t.Fatal(err)
 177  	}
 178  	if pub != "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9" {
 179  		t.Fatalf("pubkey = %s", pub)
 180  	}
 181  	if _, err := PubKeyHex([32]byte{}); err == nil {
 182  		t.Fatal("zero secret key accepted")
 183  	}
 184  }
 185  
 186  func TestRejectUppercaseHex(t *testing.T) {
 187  	upper := "EAEB41F2E1D44ABC3949052406DB478AEED872A64E46022DAACE5955CD65B316"
 188  	var dst [32]byte
 189  	if err := Decode32(upper, dst[:]); err == nil {
 190  		t.Fatal("uppercase hex accepted")
 191  	}
 192  	raw := []byte(`{"id":"` + upper + `","pubkey":"` + upper + `","created_at":1,"kind":1,"tags":[],"content":"","sig":"` + strings.Repeat("00", 64) + `"}`)
 193  	if _, err := ParseEvent(raw); err == nil {
 194  		t.Fatal("uppercase event accepted")
 195  	}
 196  }
 197  
 198  func TestEventJSONRoundTrip(t *testing.T) {
 199  	var sk [32]byte
 200  	sk[31] = 9
 201  	ev := &Event{
 202  		Kind:      1,
 203  		CreatedAt: 1700000000,
 204  		Tags:      Tags{{"t", "unicode"}, {"p", "quoted\"value"}},
 205  		Content:   "line1\nline2\t\"quoted\" \\ 🚀 \u2028",
 206  	}
 207  	if err := ev.Sign(sk); err != nil {
 208  		t.Fatal(err)
 209  	}
 210  	wire := ev.AppendJSON(nil)
 211  	got, err := ParseEvent(wire)
 212  	if err != nil {
 213  		t.Fatalf("reparse: %v", err)
 214  	}
 215  	if got.ID != ev.ID || got.PubKey != ev.PubKey || got.Sig != ev.Sig {
 216  		t.Fatal("binary fields changed")
 217  	}
 218  	if got.Content != ev.Content || len(got.Tags) != len(ev.Tags) {
 219  		t.Fatalf("content/tags changed: %q", got.Content)
 220  	}
 221  	if err := got.Verify(); err != nil {
 222  		t.Fatalf("round-tripped event failed verification: %v", err)
 223  	}
 224  }
 225