Skip to content

Commit

Permalink
feat: handle nostr subscriptions for lifecycle of apps
Browse files Browse the repository at this point in the history
  • Loading branch information
frnandu committed Oct 18, 2024
1 parent 8cbe566 commit 853beed
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 149 deletions.
1 change: 1 addition & 0 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
scopes,
false,
nil,
svc.keys.GetBIP32ChildKey,
)

if err != nil {
Expand Down
28 changes: 6 additions & 22 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package api

import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -30,7 +29,6 @@ import (
"github.com/getAlby/hub/service/keys"
"github.com/getAlby/hub/utils"
"github.com/getAlby/hub/version"
"github.com/nbd-wtf/go-nostr"
)

type api struct {
Expand Down Expand Up @@ -81,7 +79,9 @@ func (api *api) CreateApp(ctx context.Context, createAppRequest *CreateAppReques
expiresAt,
createAppRequest.Scopes,
createAppRequest.Isolated,
createAppRequest.Metadata)
createAppRequest.Metadata,
api.svc.GetKeys().GetBIP32ChildKey,
)

if err != nil {
return nil, err
Expand All @@ -100,21 +100,12 @@ func (api *api) CreateApp(ctx context.Context, createAppRequest *CreateAppReques
return nil, err
}

appWalletKey, err := api.keys.GetBIP32ChildKey(uint32(app.ID))
if err != nil {
fmt.Println("error creating child key: ", err)
return nil, err
}
fmt.Println("!+!+!+!+!+!+!+ app secret key: ", hex.EncodeToString(appWalletKey.Serialize()))
appWalletPubKey, _ := nostr.GetPublicKey(hex.EncodeToString(appWalletKey.Serialize()))
fmt.Println("!+!+!+!+!+!+!+ app public key: ", appWalletPubKey)

if createAppRequest.ReturnTo != "" {
returnToUrl, err := url.Parse(createAppRequest.ReturnTo)
if err == nil {
query := returnToUrl.Query()
query.Add("relay", relayUrl)
query.Add("pubkey", appWalletPubKey)
query.Add("pubkey", app.WalletChildPubkey)
if lightningAddress != "" && !app.Isolated {
query.Add("lud16", lightningAddress)
}
Expand All @@ -127,14 +118,7 @@ func (api *api) CreateApp(ctx context.Context, createAppRequest *CreateAppReques
if lightningAddress != "" && !app.Isolated {
lud16 = fmt.Sprintf("&lud16=%s", lightningAddress)
}
responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", appWalletPubKey, relayUrl, pairingSecretKey, lud16)

fmt.Println("~+~+~+~+~+~+~+~+~+ GOING TO SUBSCRIBE TO NEW APP WALLET!!!!!!!!!!!!!! ")
err = api.svc.SubscribeToAppRequests(ctx, appWalletPubKey)
if err != nil {
fmt.Println("error subscribing to new app wallet key: ", err)
return nil, err
}
responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", app.WalletChildPubkey, relayUrl, pairingSecretKey, lud16)

return responseBody, nil
}
Expand Down Expand Up @@ -234,7 +218,7 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
}

func (api *api) DeleteApp(userApp *db.App) error {
return api.db.Delete(userApp).Error
return api.dbSvc.DeleteApp(userApp)
}

func (api *api) GetApp(dbApp *db.App) *App {
Expand Down
50 changes: 47 additions & 3 deletions db/db_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ func NewDBService(db *gorm.DB, eventPublisher events.EventPublisher) *dbService
}
}

func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*App, string, error) {
func (svc *dbService) CreateApp(
name string,
pubkey string,
maxAmountSat uint64,
budgetRenewal string,
expiresAt *time.Time,
scopes []string,
isolated bool,
metadata map[string]interface{},
walletChildPrivKeyGeneratorFunc func(appId uint32) (string, error),
) (*App, string, error) {
if isolated && (slices.Contains(scopes, constants.SIGN_MESSAGE_SCOPE)) {
// cannot sign messages because the isolated app is a custodial subaccount
return nil, "", errors.New("isolated app cannot have sign_message scope")
Expand Down Expand Up @@ -59,7 +69,7 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
}
}

app := App{Name: name, NostrPubkey: pairingPublicKey, walletChildIdx: 2, Isolated: isolated, Metadata: datatypes.JSON(metadataBytes)}
app := App{Name: name, NostrPubkey: pairingPublicKey, Isolated: isolated, Metadata: datatypes.JSON(metadataBytes)}

err := svc.db.Transaction(func(tx *gorm.DB) error {
err := tx.Save(&app).Error
Expand All @@ -82,6 +92,21 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
}
}

appWalletChildPrivKey, err := walletChildPrivKeyGeneratorFunc(uint32(app.ID))
if err != nil {
return fmt.Errorf("error generating wallet child private key: %w", err)
}

app.WalletChildPubkey, err = nostr.GetPublicKey(appWalletChildPrivKey)
if err != nil {
return fmt.Errorf("error generating wallet child public key: %w", err)
}

err = tx.Model(&App{}).Where("id", app.ID).Update("wallet_child_pubkey", app.WalletChildPubkey).Error
if err != nil {
return err
}

// commit transaction
return nil
})
Expand All @@ -94,9 +119,28 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
svc.eventPublisher.Publish(&events.Event{
Event: "app_created",
Properties: map[string]interface{}{
"name": name,
"name": name,
"id": app.ID,
"walletChildPubkey": app.WalletChildPubkey,
},
})

return &app, pairingSecretKey, nil
}

func (svc *dbService) DeleteApp(app *App) error {

err := svc.db.Delete(app).Error
if err != nil {
return err
}
svc.eventPublisher.Publish(&events.Event{
Event: "app_deleted",
Properties: map[string]interface{}{
"name": app.Name,
"id": app.ID,
"walletChildPubkey": app.WalletChildPubkey,
},
})
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"gorm.io/gorm"
)

var _202410141503_wallet_child_idx = &gormigrate.Migration{
ID: "202410141503_wallet_child_idx",
var _202410141503_wallet_child_pubkey = &gormigrate.Migration{
ID: "202410141503_wallet_child_pubkey",
Migrate: func(tx *gorm.DB) error {

if err := tx.Exec(`
ALTER TABLE apps ADD COLUMN wallet_child_idx INTEGER;
CREATE UNIQUE INDEX idx_wallet_child_idx ON apps (wallet_child_idx);
ALTER TABLE apps ADD COLUMN wallet_child_pubkey TEXT;
`).Error; err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion db/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func Migrate(gormDB *gorm.DB) error {
_202408061737_add_boostagrams_and_use_json,
_202408191242_transaction_failure_reason,
_202408291715_app_metadata,
_202410141503_wallet_child_idx,
_202410141503_wallet_child_pubkey,
})

return m.Migrate()
Expand Down
21 changes: 11 additions & 10 deletions db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ type UserConfig struct {
}

type App struct {
ID uint
Name string `validate:"required"`
Description string
NostrPubkey string `validate:"required"`
walletChildIdx uint `validate:"required"`
CreatedAt time.Time
UpdatedAt time.Time
Isolated bool
Metadata datatypes.JSON
ID uint
Name string `validate:"required"`
Description string
NostrPubkey string `validate:"required"`
WalletChildPubkey string
CreatedAt time.Time
UpdatedAt time.Time
Isolated bool
Metadata datatypes.JSON
}

type AppPermission struct {
Expand Down Expand Up @@ -88,7 +88,8 @@ type Transaction struct {
}

type DBService interface {
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*App, string, error)
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}, walletChildPrivKeyGeneratorFunc func(uint32) (string, error)) (*App, string, error)
DeleteApp(app *App) error
}

const (
Expand Down
49 changes: 30 additions & 19 deletions nip47/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package nip47

import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -46,6 +45,33 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
}).Error("invalid event signature")
return
}
pTag := event.Tags.GetFirst([]string{"p"})
if pTag == nil {
logger.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Error("invalid event, missing p tag")
return
}
appWalletPubKey := pTag.Value()

app := db.App{}
err = svc.db.First(&app, &db.App{
NostrPubkey: event.PubKey,
}).Error

appWalletPrivKey := svc.keys.GetNostrSecretKey()

if appWalletPubKey != svc.keys.GetNostrPublicKey() {
// This is a new child key derived from master using app ID as index
appWalletPrivKey, err = svc.keys.GetBIP32ChildKey(uint32(app.ID))
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"appId": app.ID,
}).WithError(err).Error("error deriving child key")
return
}
}

ss, err := nip04.ComputeSharedSecret(event.PubKey, svc.keys.GetNostrSecretKey())
if err != nil {
Expand Down Expand Up @@ -87,12 +113,6 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
return
}

app := db.App{}

err = svc.db.First(&app, &db.App{
NostrPubkey: event.PubKey,
}).Error

if err != nil {
logger.Logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
Expand Down Expand Up @@ -162,15 +182,6 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
"appId": app.ID,
}).Debug("App found for nostr event")

appWalletPrivKeyBip32, err := svc.keys.GetBIP32ChildKey(uint32(app.ID))
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"appId": app.ID,
}).WithError(err).Error("error deriving child key")
return
}
appWalletPrivKey, _ := nostr.GetPublicKey(hex.EncodeToString(appWalletPrivKeyBip32.Serialize()))

//to be extra safe, decrypt using the key found from the app
ss, err = nip04.ComputeSharedSecret(app.NostrPubkey, appWalletPrivKey)
if err != nil {
Expand Down Expand Up @@ -368,7 +379,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
}
}

func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content interface{}, tags nostr.Tags, ss []byte, walletPrivKey string) (result *nostr.Event, err error) {
func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content interface{}, tags nostr.Tags, ss []byte, appWalletPrivKey string) (result *nostr.Event, err error) {
payloadBytes, err := json.Marshal(content)
if err != nil {
return nil, err
Expand All @@ -381,7 +392,7 @@ func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content inter
allTags := nostr.Tags{[]string{"p", initialEvent.PubKey}, []string{"e", initialEvent.ID}}
allTags = append(allTags, tags...)

appWalletPubKey, _ := nostr.GetPublicKey(walletPrivKey)
appWalletPubKey, _ := nostr.GetPublicKey(appWalletPrivKey)

resp := &nostr.Event{
PubKey: appWalletPubKey,
Expand All @@ -390,7 +401,7 @@ func (svc *nip47Service) CreateResponse(initialEvent *nostr.Event, content inter
Tags: allTags,
Content: msg,
}
err = resp.Sign(walletPrivKey)
err = resp.Sign(appWalletPrivKey)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions nip47/nip47_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package nip47

import (
"context"

"github.com/getAlby/hub/config"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
Expand All @@ -29,7 +28,8 @@ type Nip47Service interface {
events.EventSubscriber
StartNotifier(ctx context.Context, relay *nostr.Relay, lnClient lnclient.LNClient)
HandleEvent(ctx context.Context, relay nostrmodels.Relay, event *nostr.Event, lnClient lnclient.LNClient)
PublishNip47Info(ctx context.Context, relay nostrmodels.Relay, lnClient lnclient.LNClient) error
PublishNip47Info(ctx context.Context, relay nostrmodels.Relay, appWalletPubKey string, appWalletPrivKey string, lnClient lnclient.LNClient) (*nostr.Event, error)
PublishNip47InfoDeletion(ctx context.Context, relay nostrmodels.Relay, appWalletPubKey string, appWalletPrivKey string, infoEventId string) error
CreateResponse(initialEvent *nostr.Event, content interface{}, tags nostr.Tags, ss []byte, walletPrivKey string) (result *nostr.Event, err error)
}

Expand Down
4 changes: 1 addition & 3 deletions nip47/notifications/nip47_notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package notifications

import (
"context"
"encoding/hex"
"encoding/json"

"github.com/getAlby/hub/config"
Expand Down Expand Up @@ -109,15 +108,14 @@ func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *db.App
"appId": app.ID,
}).Debug("Notifying subscriber")

appWalletPrivKeyBIP32, err := notifier.keys.GetBIP32ChildKey(uint32(app.ID))
appWalletPrivKey, err := notifier.keys.GetBIP32ChildKey(uint32(app.ID))
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("error derivingchild key")
return
}
appWalletPrivKey := hex.EncodeToString(appWalletPrivKeyBIP32.Serialize())

ss, err := nip04.ComputeSharedSecret(app.NostrPubkey, appWalletPrivKey)
if err != nil {
Expand Down
Loading

0 comments on commit 853beed

Please sign in to comment.