diff --git a/go.mod b/go.mod index 796d667..f040fab 100644 --- a/go.mod +++ b/go.mod @@ -156,6 +156,7 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa github.com/ulikunitz/xz v0.5.10 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect diff --git a/itest/lspd_node.go b/itest/lspd_node.go index bc6833b..e957988 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -16,6 +16,7 @@ import ( "github.com/breez/lntest" "github.com/breez/lspd/config" + "github.com/breez/lspd/lightning" "github.com/breez/lspd/notifications" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" @@ -24,6 +25,7 @@ import ( ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + "github.com/tv42/zbase32" "google.golang.org/grpc/metadata" ) @@ -290,14 +292,14 @@ func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation, continueOn } func SubscribeNotifications(l LspNode, b BreezClient, url string, continueOnError bool) error { - first := sha256.Sum256([]byte(url)) + msg := append(lightning.SignedMsgPrefix, []byte(url)...) + first := sha256.Sum256([]byte(msg)) second := sha256.Sum256(first[:]) sig, err := ecdsa.SignCompact(b.Node().PrivateKey(), second[:], true) assert.NoError(b.Harness().T, err) - request := notifications.SubscribeNotificationsRequest{ Url: url, - Signature: sig, + Signature: zbase32.EncodeToString(sig), } serialized, err := proto.Marshal(&request) lntest.CheckError(l.Harness().T, err) diff --git a/lightning/verify_message.go b/lightning/verify_message.go new file mode 100644 index 0000000..8331a44 --- /dev/null +++ b/lightning/verify_message.go @@ -0,0 +1,38 @@ +package lightning + +import ( + "crypto/sha256" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/tv42/zbase32" +) + +var ErrInvalidSignature = fmt.Errorf("invalid signature") +var SignedMsgPrefix = []byte("Lightning Signed Message:") + +func VerifyMessage(message []byte, signature string) (*btcec.PublicKey, error) { + // The signature should be zbase32 encoded + sig, err := zbase32.DecodeString(signature) + if err != nil { + return nil, fmt.Errorf("failed to decode signature: %v", err) + } + + msg := append(SignedMsgPrefix, message...) + first := sha256.Sum256(msg) + second := sha256.Sum256(first[:]) + pubkey, wasCompressed, err := ecdsa.RecoverCompact( + sig, + second[:], + ) + if err != nil { + return nil, ErrInvalidSignature + } + + if !wasCompressed { + return nil, ErrInvalidSignature + } + + return pubkey, nil +} diff --git a/notifications/notifications.pb.go b/notifications/notifications.pb.go index 73bdb79..80da290 100644 --- a/notifications/notifications.pb.go +++ b/notifications/notifications.pb.go @@ -73,7 +73,7 @@ type SubscribeNotificationsRequest struct { unknownFields protoimpl.UnknownFields Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *SubscribeNotificationsRequest) Reset() { @@ -115,11 +115,11 @@ func (x *SubscribeNotificationsRequest) GetUrl() string { return "" } -func (x *SubscribeNotificationsRequest) GetSignature() []byte { +func (x *SubscribeNotificationsRequest) GetSignature() string { if x != nil { return x.Signature } - return nil + return "" } type SubscribeNotificationsReply struct { @@ -172,7 +172,7 @@ var file_notifications_proto_rawDesc = []byte{ 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x84, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, diff --git a/notifications/notifications.proto b/notifications/notifications.proto index 0fca073..c7544c2 100644 --- a/notifications/notifications.proto +++ b/notifications/notifications.proto @@ -15,7 +15,7 @@ message EncryptedNotificationRequest { message SubscribeNotificationsRequest { string url = 1; - bytes signature = 2; + string signature = 2; } message SubscribeNotificationsReply { diff --git a/notifications/server.go b/notifications/server.go index 549e082..7c3766b 100644 --- a/notifications/server.go +++ b/notifications/server.go @@ -2,18 +2,16 @@ package notifications import ( context "context" - "crypto/sha256" "encoding/hex" "fmt" "log" + "github.com/breez/lspd/lightning" lspdrpc "github.com/breez/lspd/rpc" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" ) -var ErrInvalidSignature = fmt.Errorf("invalid signature") var ErrInternal = fmt.Errorf("internal error") type server struct { @@ -48,18 +46,9 @@ func (s *server) SubscribeNotifications( return nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", data, err) } - first := sha256.Sum256([]byte(request.Url)) - second := sha256.Sum256(first[:]) - pubkey, wasCompressed, err := ecdsa.RecoverCompact( - request.Signature, - second[:], - ) + pubkey, err := lightning.VerifyMessage([]byte(request.Url), request.Signature) if err != nil { - return nil, ErrInvalidSignature - } - - if !wasCompressed { - return nil, ErrInvalidSignature + return nil, err } err = s.store.Register(ctx, hex.EncodeToString(pubkey.SerializeCompressed()), request.Url)