diff --git a/event.go b/event.go index eda0945..37bc20d 100644 --- a/event.go +++ b/event.go @@ -35,6 +35,28 @@ func (evt *Event) GetID() string { return hex.EncodeToString(h[:]) } +// CheckID checks if the implied ID matches the given ID +func (evt *Event) CheckID() bool { + ser := evt.Serialize() + h := sha256.Sum256(ser) + + const hextable = "0123456789abcdef" + + for i := 0; i < 32; i++ { + b := hextable[h[i]>>4] + if b != evt.ID[i*2] { + return false + } + + b = hextable[h[i]&0x0f] + if b != evt.ID[i*2+1] { + return false + } + } + + return true +} + // Serialize outputs a byte array that can be hashed/signed to identify/authenticate. // JSON encoding as defined in RFC4627. func (evt *Event) Serialize() []byte { diff --git a/event_test.go b/event_test.go index 58a0379..b8a7f43 100644 --- a/event_test.go +++ b/event_test.go @@ -2,9 +2,12 @@ package nostr import ( "encoding/json" + "fmt" + "math/rand/v2" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEventParsingAndVerifying(t *testing.T) { @@ -117,7 +120,6 @@ func TestEventSerializationWithExtraFields(t *testing.T) { assert.Condition(t, func() (success bool) { if evt.GetExtra("glub").(bool) != evt.GetExtraBoolean("glub") || evt.GetExtraBoolean("glub") != true { - return false } return true @@ -132,3 +134,39 @@ func mustSignEvent(t *testing.T, privkey string, event *Event) { t.Fatalf("event.Sign: %v", err) } } + +func TestIDCheck(t *testing.T) { + for i := 0; i < 1000; i++ { + evt := Event{ + CreatedAt: Timestamp(rand.Int64N(9999999)), + Content: fmt.Sprintf("hello %d", i), + Tags: Tags{}, + } + evt.Sign(GeneratePrivateKey()) + require.True(t, evt.CheckID()) + + evt.Content += "!" + require.False(t, evt.CheckID()) + } +} + +func BenchmarkIDCheck(b *testing.B) { + evt := Event{ + CreatedAt: Timestamp(rand.Int64N(9999999)), + Content: fmt.Sprintf("hello"), + Tags: Tags{}, + } + evt.Sign(GeneratePrivateKey()) + + b.Run("naïve", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = evt.GetID() == evt.ID + } + }) + + b.Run("big brain", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = evt.CheckID() + } + }) +}