Skip to content

Commit

Permalink
allow using libsecp256k1 for signature verification in subscriptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
fiatjaf committed May 29, 2024
1 parent 31e0645 commit d06f611
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 20 deletions.
25 changes: 24 additions & 1 deletion libsecp256k1/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
This is faster than the pure Go version:
This wraps [libsecp256k1](https://github.com/bitcoin-core/secp256k1) with `cgo`.

It doesn't embed the library or anything smart like that because I don't know how to do it, so you must have it installed in your system.

It is faster than the pure Go version:

```
goos: linux
Expand All @@ -8,3 +12,22 @@ cpu: AMD Ryzen 3 3200G with Radeon Vega Graphics
BenchmarkSignatureVerification/btcec-4 145 7873130 ns/op 127069 B/op 579 allocs/op
BenchmarkSignatureVerification/libsecp256k1-4 502 2314573 ns/op 112241 B/op 392 allocs/op
```

To use it manually, just import. To use it inside the automatic verification that happens for subscriptions, set it up with a `SimplePool`:

```go
pool := nostr.NewSimplePool()
pool.SignatureChecker = func (evt nostr.Event) bool {
ok, _ := libsecp256k1.CheckSignature(evt)
return ok
}
```

Or directly to the `Relay`:

```go
relay := nostr.RelayConnect(context.Background(), "wss://relay.nostr.com", nostr.WithSignatureChecker(func (evt nostr.Event) bool {
ok, _ := libsecp256k1.CheckSignature(evt)
return ok
}))
```
11 changes: 10 additions & 1 deletion pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type SimplePool struct {

authHandler func(*Event) error
cancel context.CancelFunc

// custom things not often used
SignatureChecker func(Event) bool
}

type DirectedFilters struct {
Expand Down Expand Up @@ -85,7 +88,13 @@ func (pool *SimplePool) EnsureRelay(url string) (*Relay, error) {
// we use this ctx here so when the pool dies everything dies
ctx, cancel := context.WithTimeout(pool.Context, time.Second*15)
defer cancel()
if relay, err = RelayConnect(ctx, nm); err != nil {

opts := make([]RelayOption, 0, 1)
if pool.SignatureChecker != nil {
opts = append(opts, WithSignatureChecker(pool.SignatureChecker))
}

if relay, err = RelayConnect(ctx, nm, opts...); err != nil {
return nil, fmt.Errorf("failed to connect: %w", err)
}

Expand Down
47 changes: 29 additions & 18 deletions relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Relay struct {
okCallbacks *xsync.MapOf[string, func(bool, string)]
writeQueue chan writeRequest
subscriptionChannelCloseQueue chan *Subscription
signatureChecker func(Event) bool

// custom things that aren't often used
//
Expand All @@ -60,18 +61,14 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay {
okCallbacks: xsync.NewMapOf[string, func(bool, string)](),
writeQueue: make(chan writeRequest),
subscriptionChannelCloseQueue: make(chan *Subscription),
signatureChecker: func(e Event) bool {
ok, _ := e.CheckSignature()
return ok
},
}

for _, opt := range opts {
switch o := opt.(type) {
case WithNoticeHandler:
r.notices = make(chan string)
go func() {
for notice := range r.notices {
o(notice)
}
}()
}
opt.ApplyRelayOption(r)
}

return r
Expand All @@ -89,16 +86,34 @@ func RelayConnect(ctx context.Context, url string, opts ...RelayOption) (*Relay,
// When instantiating relay connections, some options may be passed.
// RelayOption is the type of the argument passed for that.
type RelayOption interface {
IsRelayOption()
ApplyRelayOption(*Relay)
}

var (
_ RelayOption = (WithNoticeHandler)(nil)
_ RelayOption = (WithSignatureChecker)(nil)
)

// WithNoticeHandler just takes notices and is expected to do something with them.
// when not given, defaults to logging the notices.
type WithNoticeHandler func(notice string)

func (_ WithNoticeHandler) IsRelayOption() {}
func (nh WithNoticeHandler) ApplyRelayOption(r *Relay) {
r.notices = make(chan string)
go func() {
for notice := range r.notices {
nh(notice)
}
}()
}

// WithSignatureChecker must be a function that checks the signature of an
// event and returns true or false.
type WithSignatureChecker func(Event) bool

var _ RelayOption = (WithNoticeHandler)(nil)
func (sc WithSignatureChecker) ApplyRelayOption(r *Relay) {
r.signatureChecker = sc
}

// String just returns the relay URL.
func (r *Relay) String() string {
Expand Down Expand Up @@ -237,12 +252,8 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error

// check signature, ignore invalid, except from trusted (AssumeValid) relays
if !r.AssumeValid {
if ok, err := env.Event.CheckSignature(); !ok {
errmsg := ""
if err != nil {
errmsg = err.Error()
}
InfoLogger.Printf("{%s} bad signature on %s; %s\n", r.URL, env.Event.ID, errmsg)
if ok := r.signatureChecker(env.Event); !ok {
InfoLogger.Printf("{%s} bad signature on %s\n", r.URL, env.Event.ID)
continue
}
}
Expand Down

0 comments on commit d06f611

Please sign in to comment.