Skip to content

Commit

Permalink
feat: do not require unlock password to recover encrypted scb
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Oct 30, 2024
1 parent 731d248 commit ae3984f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 28 deletions.
55 changes: 31 additions & 24 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -723,46 +723,53 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
}
}

func (svc *albyOAuthService) backupChannels(ctx context.Context, event *events.Event) error {
bkpEvent, ok := event.Properties.(*events.StaticChannelsBackupEvent)
if !ok {
return fmt.Errorf("invalid nwc_backup_channels event properties, could not cast to the expected type: %+v", event.Properties)
}
type channelsBackup struct {
Description string `json:"description"`
Data string `json:"data"`
}

token, err := svc.fetchUserToken(ctx)
func (svc *albyOAuthService) createEncryptedChannelBackup(event *events.StaticChannelsBackupEvent) (*channelsBackup, error) {

eventData := bytes.NewBuffer([]byte{})
err := json.NewEncoder(eventData).Encode(event)
if err != nil {
return fmt.Errorf("failed to fetch user token: %w", err)
return nil, fmt.Errorf("failed to encode channels backup data: %w", err)
}

client := svc.oauthConf.Client(ctx, token)
// use the encrypted mnemonic as the password to encrypt the backup data

type channelsBackup struct {
Description string `json:"description"`
Data string `json:"data"`
encrypted, err := svc.keys.EncryptChannelBackupData(eventData.String())
if err != nil {
return nil, fmt.Errorf("failed to encrypt channels backup data: %w", err)
}

eventData := bytes.NewBuffer([]byte{})
err = json.NewEncoder(eventData).Encode(bkpEvent)
if err != nil {
return fmt.Errorf("failed to encode channels backup data: %w", err)
backup := &channelsBackup{
Description: "channels_v2",
Data: encrypted,
}
return backup, nil
}

// use the encrypted mnemonic as the password to encrypt the backup data
encryptedMnemonic, err := svc.cfg.Get("Mnemonic", "")
func (svc *albyOAuthService) backupChannels(ctx context.Context, event *events.Event) error {
bkpEvent, ok := event.Properties.(*events.StaticChannelsBackupEvent)
if !ok {
return fmt.Errorf("invalid nwc_backup_channels event properties, could not cast to the expected type: %+v", event.Properties)
}

backup, err := svc.createEncryptedChannelBackup(bkpEvent)
if err != nil {
return fmt.Errorf("failed to fetch encryption key: %w", err)
return fmt.Errorf("failed to encrypt channel backup: %w", err)
}

encrypted, err := config.AesGcmEncrypt(eventData.String(), encryptedMnemonic)
token, err := svc.fetchUserToken(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt channels backup data: %w", err)
return fmt.Errorf("failed to fetch user token: %w", err)
}

client := svc.oauthConf.Client(ctx, token)

body := bytes.NewBuffer([]byte{})
err = json.NewEncoder(body).Encode(&channelsBackup{
Description: "channels",
Data: encrypted,
})
err = json.NewEncoder(body).Encode(backup)
if err != nil {
return fmt.Errorf("failed to encode channels backup request payload: %w", err)
}
Expand Down
48 changes: 48 additions & 0 deletions alby/alby_oauth_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package alby

import (
"encoding/hex"
"testing"

"github.com/getAlby/hub/config"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/tests"
"github.com/stretchr/testify/assert"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)

func TestEncryptedBackup(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

mnemonic := "limit reward expect search tissue call visa fit thank cream brave jump"
unlockPassword := "123"
svc.Cfg.SetUpdate("Mnemonic", mnemonic, unlockPassword)
err = svc.Keys.Init(svc.Cfg, unlockPassword)
assert.Nil(t, err)

albyOAuthSvc := NewAlbyOAuthService(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
encryptedBackup, err := albyOAuthSvc.createEncryptedChannelBackup(&events.StaticChannelsBackupEvent{
NodeID: "037e702144c4fa485d42f0f69864e943605823763866cf4bf619d2d2cf2eda420b",
Channels: []events.ChannelBackup{},
Monitors: []events.EncodedChannelMonitorBackup{},
})

assert.Nil(t, err)
assert.Equal(t, "channels_v2", encryptedBackup.Description)

masterKey, err := bip32.NewMasterKey(bip39.NewSeed(mnemonic, ""))
assert.Nil(t, err)

appKey, err := masterKey.NewChildKey(0)
assert.Nil(t, err)
encryptedChannelsBackupKey, err := appKey.NewChildKey(0)
assert.Nil(t, err)

decrypted, err := config.AesGcmDecrypt(encryptedBackup.Data, hex.EncodeToString(encryptedChannelsBackupKey.Key))
assert.Nil(t, err)

assert.Equal(t, "{\"node_id\":\"037e702144c4fa485d42f0f69864e943605823763866cf4bf619d2d2cf2eda420b\",\"channels\":[],\"monitors\":[]}\n", decrypted)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/DataDog/gostackparse v0.7.0 // indirect
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
Expand Down Expand Up @@ -237,6 +239,7 @@ require (
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/lightningnetwork/lnd v0.18.2-beta
github.com/sirupsen/logrus v1.9.3
github.com/tyler-smith/go-bip32 v1.0.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
gorm.io/datatypes v1.2.4
)
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE=
github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
Expand Down Expand Up @@ -106,6 +110,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
Expand Down Expand Up @@ -601,6 +606,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
Expand Down Expand Up @@ -634,6 +640,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE=
github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
Expand Down Expand Up @@ -720,6 +728,7 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down Expand Up @@ -947,6 +956,7 @@ gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
Expand Down
50 changes: 46 additions & 4 deletions service/keys/keys.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
package keys

import (
"encoding/hex"

"github.com/getAlby/hub/config"
"github.com/getAlby/hub/logger"
"github.com/nbd-wtf/go-nostr"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)

type Keys interface {
Init(cfg config.Config, encryptionKey string) error
// Wallet Service Nostr pubkey
// Wallet Service Nostr pubkey (DEPRECATED)
GetNostrPublicKey() string
// Wallet Service Nostr secret key
// Wallet Service Nostr secret key (DEPRECATED)
GetNostrSecretKey() string

EncryptChannelBackupData(channelBackupData string) (string, error)
}

type keys struct {
nostrSecretKey string
nostrPublicKey string
nostrSecretKey string
nostrPublicKey string
encryptedChannelsBackupKey string
}

func NewKeys() *keys {
Expand All @@ -41,6 +48,36 @@ func (keys *keys) Init(cfg config.Config, encryptionKey string) error {
}
keys.nostrSecretKey = nostrSecretKey
keys.nostrPublicKey = nostrPublicKey

mnemonic, err := cfg.Get("Mnemonic", encryptionKey)
if err != nil {
logger.Logger.WithError(err).Error("Failed to decrypt mnemonic")
return err
}

if mnemonic != "" {
masterKey, err := bip32.NewMasterKey(bip39.NewSeed(mnemonic, ""))
if err != nil {
logger.Logger.WithError(err).Error("Failed to create seed from mnemonic")
return err
}

APP_INDEX := uint32(0) // TODO: choose an index
appKey, err := masterKey.NewChildKey(APP_INDEX)
if err != nil {
logger.Logger.WithError(err).Error("Failed to create seed from mnemonic")
return err
}

ENCRYPTED_SCB_INDEX := uint32(0) // TODO: choose an index
encryptedChannelsBackupKey, err := appKey.NewChildKey(ENCRYPTED_SCB_INDEX)
if err != nil {
logger.Logger.WithError(err).Error("Failed to create seed from mnemonic")
return err
}
keys.encryptedChannelsBackupKey = hex.EncodeToString(encryptedChannelsBackupKey.Key)
}

return nil
}

Expand All @@ -51,3 +88,8 @@ func (keys *keys) GetNostrPublicKey() string {
func (keys *keys) GetNostrSecretKey() string {
return keys.nostrSecretKey
}

func (keys *keys) EncryptChannelBackupData(channelBackupData string) (string, error) {
// FIXME: this is not the right way to encrypt the data (we already have a key, no need to generate a new one)
return config.AesGcmEncrypt(channelBackupData, keys.encryptedChannelsBackupKey)
}

0 comments on commit ae3984f

Please sign in to comment.