Skip to content

Commit

Permalink
feat: use nip44 and versioning (#834)
Browse files Browse the repository at this point in the history
* feat: use nip44 and versioning

* fix: panic: runtime error: invalid memory address or nil pointer dereference

* chore: check for version before notifying

* chore: add nip47 cipher

* chore: remove version check in event handler

* chore: address feedback

* chore: modify tests to use versioning and nip47 cipher

* chore: add more tests

* chore: add version param and change naming of test app functions

* chore: cleanup event handler tests

* chore: cleanup nip47 notifier tests

* chore: dry up cipher test

* chore: remove version param from create app

* chore: republish info event for existing apps

* chore: simplify isVersionSupported function

* chore: rename event handler legacy test

* chore: add nip04 test for create response

* chore: add comment and allow empty request version

---------

Co-authored-by: Fmar <frnandu@gmail.com>
  • Loading branch information
im-adithya and frnandu authored Jan 9, 2025
1 parent 2a63435 commit 94c209a
Show file tree
Hide file tree
Showing 15 changed files with 623 additions and 346 deletions.
1 change: 1 addition & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ const (
ERROR_RESTRICTED = "RESTRICTED"
ERROR_BAD_REQUEST = "BAD_REQUEST"
ERROR_NOT_FOUND = "NOT_FOUND"
ERROR_UNSUPPORTED_VERSION = "UNSUPPORTED_VERSION"
ERROR_OTHER = "OTHER"
)
87 changes: 87 additions & 0 deletions nip47/cipher/cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cipher

import (
"fmt"

"github.com/nbd-wtf/go-nostr/nip04"
"github.com/nbd-wtf/go-nostr/nip44"
)

const (
SUPPORTED_VERSIONS = "1.0 0.0"
)

type Nip47Cipher struct {
version string
pubkey string
privkey string
sharedSecret []byte
conversationKey [32]byte
}

func NewNip47Cipher(version, pubkey, privkey string) (*Nip47Cipher, error) {
_, err := isVersionSupported(version)
if err != nil {
return nil, err
}

var ss []byte
var ck [32]byte
if version == "0.0" {
ss, err = nip04.ComputeSharedSecret(pubkey, privkey)
if err != nil {
return nil, err
}
} else {
ck, err = nip44.GenerateConversationKey(pubkey, privkey)
if err != nil {
return nil, err
}
}

return &Nip47Cipher{
version: version,
pubkey: pubkey,
privkey: privkey,
sharedSecret: ss,
conversationKey: ck,
}, nil
}

func (c *Nip47Cipher) Encrypt(message string) (msg string, err error) {
if c.version == "0.0" {
msg, err = nip04.Encrypt(message, c.sharedSecret)
if err != nil {
return "", err
}
} else {
msg, err = nip44.Encrypt(message, c.conversationKey)
if err != nil {
return "", err
}
}
return msg, nil
}

func (c *Nip47Cipher) Decrypt(content string) (payload string, err error) {
if c.version == "0.0" {
payload, err = nip04.Decrypt(content, c.sharedSecret)
if err != nil {
return "", err
}
} else {
payload, err = nip44.Decrypt(content, c.conversationKey)
if err != nil {
return "", err
}
}
return payload, nil
}

func isVersionSupported(version string) (bool, error) {
if version == "1.0" || version == "0.0" {
return true, nil
}

return false, fmt.Errorf("invalid version: %s", version)
}
47 changes: 47 additions & 0 deletions nip47/cipher/cipher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cipher

import (
"fmt"
"testing"

"github.com/nbd-wtf/go-nostr"

"github.com/stretchr/testify/assert"
)

func TestCipher(t *testing.T) {
doTestCipher(t, "0.0")
doTestCipher(t, "1.0")
}

func doTestCipher(t *testing.T, version string) {
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)

nip47Cipher, err := NewNip47Cipher(version, reqPubkey, reqPrivateKey)
assert.NoError(t, err)

payload := "test payload"
msg, err := nip47Cipher.Encrypt(payload)
assert.NoError(t, err)

decrypted, err := nip47Cipher.Decrypt(msg)
assert.Equal(t, payload, decrypted)
}

func TestCipher_UnsupportedVersions(t *testing.T) {
doTestCipher_UnsupportedVersions(t, "1")
doTestCipher_UnsupportedVersions(t, "x.1")
doTestCipher_UnsupportedVersions(t, "1.x")
doTestCipher_UnsupportedVersions(t, "2.0")
doTestCipher_UnsupportedVersions(t, "0.5")
}

func doTestCipher_UnsupportedVersions(t *testing.T, version string) {
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)

_, err = NewNip47Cipher(version, reqPubkey, reqPrivateKey)
assert.Error(t, err)
assert.Equal(t, fmt.Sprintf("invalid version: %s", version), err.Error())
}
30 changes: 18 additions & 12 deletions nip47/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/nip47/cipher"
"github.com/getAlby/hub/nip47/controllers"
"github.com/getAlby/hub/nip47/models"
"github.com/getAlby/hub/nip47/permissions"
nostrmodels "github.com/getAlby/hub/nostr/models"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -93,12 +93,21 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
}
}

ss, err := nip04.ComputeSharedSecret(app.AppPubkey, appWalletPrivKey)
version := "0.0"
vTag := event.Tags.GetFirst([]string{"v"})

if vTag != nil && vTag.Value() != "" {
version = vTag.Value()
}

nip47Cipher, err := cipher.NewNip47Cipher(version, app.AppPubkey, appWalletPrivKey)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).WithError(err).Error("Failed to compute shared secret")
"appId": app.ID,
"version": version,
}).WithError(err).Error("Failed to initialize cipher")

requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
Expand All @@ -123,7 +132,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
Message: fmt.Sprintf("Failed to save app to nostr event: %s", err.Error()),
},
}
resp, err := svc.CreateResponse(event, nip47Response, nostr.Tags{}, ss, appWalletPrivKey)
resp, err := svc.CreateResponse(event, nip47Response, nostr.Tags{}, nip47Cipher, appWalletPrivKey)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
Expand All @@ -143,17 +152,13 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
return
}

payload, err := nip04.Decrypt(event.Content, ss)
payload, err := nip47Cipher.Decrypt(event.Content)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).WithError(err).Error("Failed to decrypt content")
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).WithError(err).Error("Failed to decrypt request event")

requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
Expand Down Expand Up @@ -191,7 +196,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
// TODO: replace with a channel
// TODO: update all previous occurences of svc.publishResponseEvent to also use the channel
publishResponse := func(nip47Response *models.Response, tags nostr.Tags) {
resp, err := svc.CreateResponse(event, nip47Response, tags, ss, appWalletPrivKey)
resp, err := svc.CreateResponse(event, nip47Response, tags, nip47Cipher, appWalletPrivKey)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
Expand Down Expand Up @@ -340,12 +345,13 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
}
}

func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content interface{}, tags nostr.Tags, ss []byte, appWalletPrivKey string) (result *nostr.Event, err error) {
func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content interface{}, tags nostr.Tags, cipher *cipher.Nip47Cipher, appWalletPrivKey string) (result *nostr.Event, err error) {
payloadBytes, err := json.Marshal(content)
if err != nil {
return nil, err
}
msg, err := nip04.Encrypt(string(payloadBytes), ss)

msg, err := cipher.Encrypt(string(payloadBytes))
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 94c209a

Please sign in to comment.