Skip to content

Commit

Permalink
feat(types): add event validation and tag marshal
Browse files Browse the repository at this point in the history
  • Loading branch information
ZigBalthazar committed Sep 8, 2024
1 parent b1e475c commit 2e478bb
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 2 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ module github.com/dezh-tech/immortal
go 1.22.5

require (
github.com/btcsuite/btcd/btcec/v2 v2.3.4
github.com/mailru/easyjson v0.7.7
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.17.3
)

require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand Down
33 changes: 33 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
PACKAGES=$(shell go list ./... | grep -v 'tests' | grep -v 'grpc/gen')

ifneq (,$(filter $(OS),Windows_NT MINGW64))
EXE = .exe
RM = del /q
else
RM = rm -rf
endif

### Tools needed for development
devtools:
@echo "Installing devtools"
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install mvdan.cc/gofumpt@latest
go install github.com/ethereum/go-ethereum/cmd/abigen@latest

### Testing
unit_test:
go test $(PACKAGES)

### Formatting the code
fmt:
gofumpt -l -w .
go mod tidy

check:
golangci-lint run --timeout=20m0s

### pre commit
pre-commit: fmt check unit_test
@echo ready to commit...

.PHONY: build
59 changes: 57 additions & 2 deletions types/event/event.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package event

import (
"crypto/sha256"
"encoding/hex"
"fmt"

"github.com/dezh-tech/immortal/types"
"github.com/dezh-tech/immortal/types/filter"
"github.com/mailru/easyjson"

"github.com/btcsuite/btcd/btcec/v2/schnorr"
)

// Event represents an event structure defined on NIP-01.
Expand Down Expand Up @@ -89,7 +95,56 @@ func (e *Event) Encode() ([]byte, error) {
return b, nil
}

func (evt *Event) Serialize() []byte {
// the serialization process is just putting everything into a JSON array
// so the order is kept. See NIP-01
dst := make([]byte, 0)

// the header portion is easy to serialize
// [0,"pubkey",created_at,kind,[
dst = append(dst, []byte(
fmt.Sprintf(
"[0,\"%s\",%d,%d,",
evt.PublicKey,
evt.CreatedAt,
evt.Kind,
))...)

// tags
dst = types.MarshalTo(evt.Tags, dst)
dst = append(dst, ',')

// content needs to be escaped in general as it is user generated.
dst = types.EscapeString(dst, evt.Content)
dst = append(dst, ']')

return dst
}

// IsValid function validats an event Signature and ID.
func (e *Event) IsValid() bool {
return false // TODO:::
func (e *Event) IsValid() (bool, error) {
// read and check pubkey
pk, err := hex.DecodeString(e.PublicKey)
if err != nil {
return false, fmt.Errorf("event pubkey '%s' is invalid hex: %w", e.PublicKey, err)
}

pubkey, err := schnorr.ParsePubKey(pk)
if err != nil {
return false, fmt.Errorf("event has invalid pubkey '%s': %w", e.PublicKey, err)
}

// read signature
s, err := hex.DecodeString(e.Signature)
if err != nil {
return false, fmt.Errorf("signature '%s' is invalid hex: %w", e.Signature, err)
}
sig, err := schnorr.ParseSignature(s)
if err != nil {
return false, fmt.Errorf("failed to parse signature: %w", err)
}

// check signature
hash := sha256.Sum256(e.Serialize())
return sig.Verify(hash[:], pubkey), nil
}
34 changes: 34 additions & 0 deletions types/event/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package event_test
import (
"testing"

"github.com/dezh-tech/immortal/types"
"github.com/dezh-tech/immortal/types/event"
"github.com/dezh-tech/immortal/types/filter"
"github.com/stretchr/testify/assert"
Expand All @@ -29,6 +30,20 @@ var (

DecodedEvent *event.Event
EncodedEvent []byte

events = []event.Event{
{
ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962",
PublicKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
CreatedAt: 1644271588,
Kind: types.KindTextNote,
Tags: []types.Tag{},
Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?",
Signature: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524",
},
}

EventValidation bool
)

func TestDecode(t *testing.T) {
Expand Down Expand Up @@ -91,3 +106,22 @@ func TestMatch(t *testing.T) {

assert.True(t, e.Match(*f))
}

func TestValidate(t *testing.T) {
for _, e := range events {
valid, err := e.IsValid()

assert.NoError(t, err)
assert.True(t, valid)
}
}

func BenchmarkValidate(b *testing.B) {
var eventValidation bool
for i := 0; i < b.N; i++ {
for _, e := range events {
eventValidation, _ = e.IsValid()
}
}
EventValidation = eventValidation
}
27 changes: 27 additions & 0 deletions types/tag.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
package types

type Tag []string

// Marshal Tag. Used for Serialization so string escaping should be as in RFC8259.
func (tag Tag) MarshalTo(dst []byte) []byte {
dst = append(dst, '[')
for i, s := range tag {
if i > 0 {
dst = append(dst, ',')
}
dst = EscapeString(dst, s)
}
dst = append(dst, ']')
return dst
}

// MarshalTo appends the JSON encoded byte of Tags as [][]string to dst.
// String escaping is as described in RFC8259.
func MarshalTo(tags []Tag, dst []byte) []byte {
dst = append(dst, '[')
for i, tag := range tags {
if i > 0 {
dst = append(dst, ',')
}
dst = tag.MarshalTo(dst)
}
dst = append(dst, ']')
return dst
}
40 changes: 40 additions & 0 deletions types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,43 @@ func ContainsKind(target Kind, arr []Kind) bool {

return false
}

// Escaping strings for JSON encoding according to RFC8259.
// Also encloses result in quotation marks "".
func EscapeString(dst []byte, s string) []byte {
dst = append(dst, '"')
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c == '"':
// quotation mark
dst = append(dst, []byte{'\\', '"'}...)
case c == '\\':
// reverse solidus
dst = append(dst, []byte{'\\', '\\'}...)
case c >= 0x20:
// default, rest below are control chars
dst = append(dst, c)
case c == 0x08:
dst = append(dst, []byte{'\\', 'b'}...)
case c < 0x09:
dst = append(dst, []byte{'\\', 'u', '0', '0', '0', '0' + c}...)
case c == 0x09:
dst = append(dst, []byte{'\\', 't'}...)
case c == 0x0a:
dst = append(dst, []byte{'\\', 'n'}...)
case c == 0x0c:
dst = append(dst, []byte{'\\', 'f'}...)
case c == 0x0d:
dst = append(dst, []byte{'\\', 'r'}...)
case c < 0x10:
dst = append(dst, []byte{'\\', 'u', '0', '0', '0', 0x57 + c}...)
case c < 0x1a:
dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x20 + c}...)
case c < 0x20:
dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x47 + c}...)
}
}
dst = append(dst, '"')
return dst
}

0 comments on commit 2e478bb

Please sign in to comment.