From 427bfc7a4bfaebdc3848f904424251a78884aea3 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 29 May 2024 15:32:49 -0300 Subject: [PATCH] add libsecp256k1 wrapper for sign/verify. --- libsecp256k1/README.md | 10 ++++++ libsecp256k1/benchmark_test.go | 38 ++++++++++++++++++++++ libsecp256k1/bip340.go | 58 ++++++++++++++++++++++++++++++++++ libsecp256k1/verify.go | 26 +++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 libsecp256k1/README.md create mode 100644 libsecp256k1/benchmark_test.go create mode 100644 libsecp256k1/bip340.go create mode 100644 libsecp256k1/verify.go diff --git a/libsecp256k1/README.md b/libsecp256k1/README.md new file mode 100644 index 0000000..f404202 --- /dev/null +++ b/libsecp256k1/README.md @@ -0,0 +1,10 @@ +This is faster than the pure Go version: + +``` +goos: linux +goarch: amd64 +pkg: github.com/nbd-wtf/go-nostr/libsecp256k1 +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 +``` diff --git a/libsecp256k1/benchmark_test.go b/libsecp256k1/benchmark_test.go new file mode 100644 index 0000000..f372313 --- /dev/null +++ b/libsecp256k1/benchmark_test.go @@ -0,0 +1,38 @@ +package libsecp256k1 + +import ( + "encoding/json" + "testing" + + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/test_common" +) + +func BenchmarkSignatureVerification(b *testing.B) { + events := make([]*nostr.Event, len(test_common.NormalEvents)) + for i, jevt := range test_common.NormalEvents { + evt := &nostr.Event{} + json.Unmarshal([]byte(jevt), evt) + events[i] = evt + } + + b.Run("btcec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, evt := range events { + evt.CheckSignature() + } + } + }) + + b.Run("libsecp256k1", func(b *testing.B) { + c, err := NewContext() + if err != nil { + panic(err) + } + for i := 0; i < b.N; i++ { + for _, evt := range events { + c.CheckSignature(evt) + } + } + }) +} diff --git a/libsecp256k1/bip340.go b/libsecp256k1/bip340.go new file mode 100644 index 0000000..122f1c0 --- /dev/null +++ b/libsecp256k1/bip340.go @@ -0,0 +1,58 @@ +package libsecp256k1 + +/* +#cgo LDFLAGS: -lsecp256k1 +#include +#include +#include +*/ +import "C" + +import ( + "crypto/rand" + "errors" + "unsafe" +) + +type Context struct { + ctx *C.secp256k1_context +} + +func NewContext() (*Context, error) { + ctx := C.secp256k1_context_create(C.SECP256K1_CONTEXT_SIGN | C.SECP256K1_CONTEXT_VERIFY) + if ctx == nil { + return nil, errors.New("failed to create secp256k1 context") + } + return &Context{ctx: ctx}, nil +} + +func (c *Context) Destroy() { + C.secp256k1_context_destroy(c.ctx) +} + +func (c *Context) Sign(msg [32]byte, sk [32]byte) ([64]byte, error) { + var sig [64]byte + + var keypair C.secp256k1_keypair + if C.secp256k1_keypair_create(c.ctx, &keypair, (*C.uchar)(unsafe.Pointer(&sk[0]))) != 1 { + return sig, errors.New("failed to parse private key") + } + + var random [32]byte + rand.Read(random[:]) + + if C.secp256k1_schnorrsig_sign32(c.ctx, (*C.uchar)(unsafe.Pointer(&sig[0])), (*C.uchar)(unsafe.Pointer(&msg[0])), &keypair, (*C.uchar)(unsafe.Pointer(&random[0]))) != 1 { + return sig, errors.New("failed to sign message") + } + + return sig, nil +} + +func (c *Context) Verify(msg [32]byte, sig [64]byte, pk [32]byte) bool { + var xonly C.secp256k1_xonly_pubkey + if C.secp256k1_xonly_pubkey_parse(c.ctx, &xonly, (*C.uchar)(unsafe.Pointer(&pk[0]))) != 1 { + return false + } + + return C.secp256k1_schnorrsig_verify(c.ctx, (*C.uchar)(unsafe.Pointer(&sig[0])), (*C.uchar)(unsafe.Pointer(&msg[0])), 32, &xonly) == 1 +} diff --git a/libsecp256k1/verify.go b/libsecp256k1/verify.go new file mode 100644 index 0000000..5c67406 --- /dev/null +++ b/libsecp256k1/verify.go @@ -0,0 +1,26 @@ +package libsecp256k1 + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + + "github.com/nbd-wtf/go-nostr" +) + +func (c *Context) CheckSignature(evt *nostr.Event) (bool, error) { + var pk [32]byte + _, err := hex.Decode(pk[:], []byte(evt.PubKey)) + if err != nil { + return false, fmt.Errorf("event pubkey '%s' is invalid hex: %w", evt.PubKey, err) + } + + var sig [64]byte + _, err = hex.Decode(sig[:], []byte(evt.Sig)) + if err != nil { + return false, fmt.Errorf("event signature '%s' is invalid hex: %w", evt.Sig, err) + } + + msg := sha256.Sum256(evt.Serialize()) + return c.Verify(msg, sig, pk), nil +}