From d06f61136d67aea758845db5585642852d91ebdb Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 29 May 2024 17:08:15 -0300 Subject: [PATCH] allow using libsecp256k1 for signature verification in subscriptions. --- libsecp256k1/README.md | 25 +++++++++++++++++++++- pool.go | 11 +++++++++- relay.go | 47 ++++++++++++++++++++++++++---------------- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/libsecp256k1/README.md b/libsecp256k1/README.md index f404202..7ec5563 100644 --- a/libsecp256k1/README.md +++ b/libsecp256k1/README.md @@ -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 @@ -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 +})) +``` diff --git a/pool.go b/pool.go index 75cc9ed..4abf935 100644 --- a/pool.go +++ b/pool.go @@ -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 { @@ -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) } diff --git a/relay.go b/relay.go index 2d4fc40..0715417 100644 --- a/relay.go +++ b/relay.go @@ -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 // @@ -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 @@ -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) + } + }() +} -var _ RelayOption = (WithNoticeHandler)(nil) +// WithSignatureChecker must be a function that checks the signature of an +// event and returns true or false. +type WithSignatureChecker func(Event) bool + +func (sc WithSignatureChecker) ApplyRelayOption(r *Relay) { + r.signatureChecker = sc +} // String just returns the relay URL. func (r *Relay) String() string { @@ -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 } }